diff --git a/apps/frontend/src/components/ChatSubscribeWebPush.vue b/apps/frontend/src/components/ChatSubscribeWebPush.vue index 6e1dba6..94d69f6 100644 --- a/apps/frontend/src/components/ChatSubscribeWebPush.vue +++ b/apps/frontend/src/components/ChatSubscribeWebPush.vue @@ -59,6 +59,7 @@ import { getUserChatSettingsDocument } from '@/api/userChatSettings' import type { UserChatSettingsDocument } from '@hereconnect/types' import { isNotFoundError } from '@/api/PouchDB' import { saveUserChatSettings } from '@/api/userChatSettings' +import { getQrCodeDocument } from '@/api/qrCode' // Проверки iOS и запуска из главного экрана const isIOS = computed( @@ -95,18 +96,16 @@ async function requestNotificationPermission() { } const registration = await navigator.serviceWorker.ready - let subscription: PushSubscription | null = null - - try { - subscription = await registration.pushManager.subscribe(await subscribeOptionsPromise) - } catch (error) { - subscription = await registration.pushManager.getSubscription() - if (!subscription) { - throw error - } - await subscription.unsubscribe() - subscription = await registration.pushManager.subscribe(await subscribeOptionsPromise) - } + const subscription = await registration.pushManager + .subscribe(await subscribeOptionsPromise) + .catch(async (error) => { + const oldSubscription = await registration.pushManager.getSubscription() + if (!oldSubscription) { + throw error + } + await oldSubscription.unsubscribe() + return registration.pushManager.subscribe(await subscribeOptionsPromise) + }) return saveWebPushSubscription(subscription) } @@ -161,7 +160,12 @@ const { isPending, isError, error, mutate } = useMutation({ }).catch((error) => { if (!isNotFoundError(error)) throw error }) - if (chatSettingsDocument && chatSettingsDocument.is_push_notification_enabled) { + if ( + (chatSettingsDocument && chatSettingsDocument.is_push_notification_enabled) || + (!chatSettingsDocument && + user_chat_role === 'owner' && + (await getQrCodeDocument(qr_code_uri)).messageDeliveryMethod === 'webPush') + ) { const [subscription, subscriptionDocument] = await Promise.all([ getPushSubscription(), getWebPushSubscriptionDocument().catch((error) => { diff --git a/apps/frontend/src/components/create/CreateMessageDeliveryOptionWebPush.vue b/apps/frontend/src/components/create/CreateMessageDeliveryOptionWebPush.vue index 3ed7b68..d561b1f 100644 --- a/apps/frontend/src/components/create/CreateMessageDeliveryOptionWebPush.vue +++ b/apps/frontend/src/components/create/CreateMessageDeliveryOptionWebPush.vue @@ -78,7 +78,10 @@ async function requestNotificationPermission() { } try { - const applicationServerKeyPromise = getVapidPublicKey() + const subscribeOptionsPromise = getVapidPublicKey().then((applicationServerKey) => ({ + userVisibleOnly: true, + applicationServerKey, + })) const permission = await Notification.requestPermission() if (permission !== 'granted') { @@ -87,10 +90,16 @@ async function requestNotificationPermission() { const registration = await navigator.serviceWorker.ready - const subscription = await registration.pushManager.subscribe({ - userVisibleOnly: true, - applicationServerKey: await applicationServerKeyPromise, - }) + const subscription = await registration.pushManager + .subscribe(await subscribeOptionsPromise) + .catch(async (error) => { + const oldSubscription = await registration.pushManager.getSubscription() + if (!oldSubscription) { + throw error + } + await oldSubscription.unsubscribe() + return registration.pushManager.subscribe(await subscribeOptionsPromise) + }) const webPushSubscriptionDoc = await saveWebPushSubscription(subscription) emit('change') diff --git a/packages/types/package.json b/packages/types/package.json index 7da7fe4..f7ee4d1 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -2,7 +2,13 @@ "name": "@hereconnect/types", "version": "1.0.0", "main": "index.ts", - "types": "index.ts", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.d.ts", + "default": "./dist/index.d.ts" + } + }, "private": true, "scripts": { "build": "tsc" diff --git a/services/message-delivery-method-web-push/src/generate-keys.ts b/services/message-delivery-method-web-push/src/generate-keys.ts index 87a4be6..1053115 100644 --- a/services/message-delivery-method-web-push/src/generate-keys.ts +++ b/services/message-delivery-method-web-push/src/generate-keys.ts @@ -1,5 +1,6 @@ import webpush from "web-push"; import PouchDB from "./PouchDB"; +import { ServerSettingsDocument } from "@hereconnect/types"; if (!process.env.VAPID_SUBJECT) { throw new Error("VAPID_SUBJECT env is empty!"); @@ -7,7 +8,10 @@ if (!process.env.VAPID_SUBJECT) { const vapidKeys = webpush.generateVAPIDKeys(); -await new PouchDB(`${process.env.API_URL}/server_settings`, { +await new PouchDB( + `${process.env.API_URL}/server_settings`, + { skip_setup: true }, +).put({ _id: "server_settings:message_delivery_method_web_push_vapid", type: "server_settings", name: "message_delivery_method_web_push_vapid", @@ -15,6 +19,7 @@ await new PouchDB(`${process.env.API_URL}/server_settings`, { subject: process.env.VAPID_SUBJECT, keys: vapidKeys, }, + created_at: new Date().toISOString(), }); await new PouchDB(`${process.env.API_URL}/public_settings`, { @@ -24,6 +29,7 @@ await new PouchDB(`${process.env.API_URL}/public_settings`, { type: "public_settings", name: "message_delivery_method_web_push_vapid_public_key", value: vapidKeys.publicKey, + created_at: new Date().toISOString(), }); if (process.env.GCM_API_KEY) { @@ -34,5 +40,6 @@ if (process.env.GCM_API_KEY) { type: "server_settings", name: "message_delivery_method_web_push_gcm_api_key", value: process.env.GCM_API_KEY, + created_at: new Date().toISOString(), }); } diff --git a/services/message-delivery-method-web-push/src/server.ts b/services/message-delivery-method-web-push/src/server.ts index 5f0ddd5..d9a713f 100644 --- a/services/message-delivery-method-web-push/src/server.ts +++ b/services/message-delivery-method-web-push/src/server.ts @@ -62,6 +62,7 @@ const start = async () => { try { if (msgDoc.from === "guest") { + console.log("FROM GUEST"); // send message to owner const qrDoc = await qrDb.get(`qr_code:${msgDoc.qr_code_uri}`); if (qrDoc.messageDeliveryMethod !== "webPush") { @@ -69,19 +70,37 @@ const start = async () => { continue; } - const { rows } = await webPushSubscriptionsDb.allDocs({ - startkey: `web_push_subscription:${qrDoc.user_uuid}:`, - endkey: `web_push_subscription:${qrDoc.user_uuid}:\uffff`, - include_docs: true, - }); + const [{ rows: pushSubscriptionRows }, { rows: userChatSettingsRows }] = + await Promise.all([ + webPushSubscriptionsDb.allDocs({ + startkey: `web_push_subscription:${qrDoc.user_uuid}:`, + endkey: `web_push_subscription:${qrDoc.user_uuid}:\uffff`, + include_docs: true, + }), - if (rows.length === 0) { - console.log("NO SUBSCRIPTIONS"); - } else { - console.log(`sending to ${rows.length} devices`); + userChatSettingsDb.allDocs({ + startkey: `user_chat_settings:${msgDoc.qr_code_uri}:${msgDoc.chat}:owner:`, + endkey: `user_chat_settings:${msgDoc.qr_code_uri}:${msgDoc.chat}:owner:\uffff`, + include_docs: true, + }), + ]); + + if ( + userChatSettingsRows.some( + (row) => row.doc!.is_push_notification_enabled === false, + ) + ) { + console.log("push notifications is disabled"); + continue; } - for (const row of rows) { + if (pushSubscriptionRows.length === 0) { + console.log("NO SUBSCRIPTIONS"); + } else { + console.log(`sending to ${pushSubscriptionRows.length} devices`); + } + + for (const row of pushSubscriptionRows) { try { const subscription = row.doc!.subscription; const topic = `chat-${msgDoc.qr_code_uri}-${msgDoc.chat}`; @@ -138,10 +157,11 @@ const start = async () => { ); for (const { doc: userChatSettingDoc } of userChatSettingsRows) { if (!userChatSettingDoc) { + console.log("push notification has not been configured"); continue; } if (!userChatSettingDoc.is_push_notification_enabled) { - console.log("PUSH NOTIFICATION DISABLED"); + console.log("push notification is disabled"); continue; } @@ -153,56 +173,56 @@ const start = async () => { }); if (subscriptionRows.length === 0) { - console.log("NO SUBSCRIPTIONS"); + console.log("no subscriptions"); } else { console.log(`sending to ${subscriptionRows.length} devices`); } - // for (const row of subscriptionRows) { - // try { - // const subscription = row.doc!.subscription; - // const topic = `chat-${msgDoc.qr_code_uri}-${msgDoc.chat}`; - // const timestamp = new Date(msgDoc.created_at).getTime(); - // const tag = `${topic}-${(timestamp - new Date(qrDoc.created_at).getTime()).toString(36)}`; - // - // const title = limitStringLengthWithEllipsis( - // `${qrDoc.name}: новое сообщение`, - // 53, - // ); - // const body = limitStringLengthWithEllipsis(msgDoc.body, 470); - // - // const options: NotificationOptions & { timestamp: number } = { - // icon: "/images/qr-code-logo-200x200.jpg", - // body, - // tag, - // requireInteraction: true, - // timestamp, - // data: { - // url: `/manage/${msgDoc.qr_code_uri}/chat/${msgDoc.chat}`, - // payload: { tag }, - // }, - // } as const; - // - // await webpush.sendNotification( - // subscription, - // JSON.stringify({ - // type: "notification", - // notification: { - // title, - // options, - // }, - // }), - // { - // urgency: "high", - // topic, - // }, - // ); - // } catch (error) { - // console.error( - // `Error sending notification to ${row.id} - ${JSON.stringify(error)}`, - // ); - // } - // } + for (const row of subscriptionRows) { + try { + const subscription = row.doc!.subscription; + const topic = `chat-${msgDoc.qr_code_uri}-${msgDoc.chat}`; + const timestamp = new Date(msgDoc.created_at).getTime(); + const tag = `${topic}-${(timestamp - new Date(qrDoc.created_at).getTime()).toString(36)}`; + + const title = limitStringLengthWithEllipsis( + `Новое сообщение`, + 53, + ); + const body = limitStringLengthWithEllipsis(msgDoc.body, 470); + + const options: NotificationOptions & { timestamp: number } = { + icon: "/images/qr-code-logo-200x200.jpg", + body, + tag, + requireInteraction: true, + timestamp, + data: { + url: `/manage/${msgDoc.qr_code_uri}/chat/${msgDoc.chat}`, + payload: { tag }, + }, + } as const; + + await webpush.sendNotification( + subscription, + JSON.stringify({ + type: "notification", + notification: { + title, + options, + }, + }), + { + urgency: "high", + topic, + }, + ); + } catch (error) { + console.error( + `Error sending notification to ${row.id} - ${JSON.stringify(error)}`, + ); + } + } } } else { continue; diff --git a/services/message-delivery-method-web-push/tsconfig.json b/services/message-delivery-method-web-push/tsconfig.json index 817bcd0..6461663 100644 --- a/services/message-delivery-method-web-push/tsconfig.json +++ b/services/message-delivery-method-web-push/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "ES2020", + "target": "ESNext", "module": "ESNext", "outDir": "./dist", "rootDir": "./src",