diff --git a/apps/frontend/e2e/vue.spec.js b/apps/frontend/e2e/vue.spec.js deleted file mode 100644 index c690267..0000000 --- a/apps/frontend/e2e/vue.spec.js +++ /dev/null @@ -1,55 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); - return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (g && (g = 0, op[0] && (_ = 0)), _) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -Object.defineProperty(exports, "__esModule", { value: true }); -var test_1 = require("@playwright/test"); -// See here how to get started: -// https://playwright.dev/docs/intro -(0, test_1.test)('visits the app root url', function (_a) { return __awaiter(void 0, [_a], void 0, function (_b) { - var page = _b.page; - return __generator(this, function (_c) { - switch (_c.label) { - case 0: return [4 /*yield*/, page.goto('/')]; - case 1: - _c.sent(); - return [4 /*yield*/, (0, test_1.expect)(page.locator('div.greetings > h1')).toHaveText('You did it!')]; - case 2: - _c.sent(); - return [2 /*return*/]; - } - }); -}); }); diff --git a/apps/frontend/package.json b/apps/frontend/package.json index c67a51a..38d8112 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -25,6 +25,7 @@ "pinia": "^2.2.6", "pouchdb-browser": "^9.0.0", "pouchdb-find": "^9.0.0", + "pouchdb-upsert": "^2.2.0", "primeicons": "^7.0.0", "primevue": "^4.2.1", "qr-code-styling": "^1.8.4", @@ -43,6 +44,7 @@ "@types/md5": "^2.3.5", "@types/node": "^20.17.6", "@types/pouchdb-browser": "^6.1.5", + "@types/pouchdb-upsert": "^2.2.9", "@vitejs/plugin-vue": "^5.1.4", "@vitest/eslint-plugin": "1.1.7", "@vue/eslint-config-prettier": "^10.1.0", diff --git a/apps/frontend/src/api/PouchDB.ts b/apps/frontend/src/api/PouchDB.ts index f5fa359..72dbabe 100644 --- a/apps/frontend/src/api/PouchDB.ts +++ b/apps/frontend/src/api/PouchDB.ts @@ -1,4 +1,15 @@ import PouchDB from 'pouchdb-browser' import PouchDBFindPlugin from 'pouchdb-find' +import PouchDBUpsertPlugin from 'pouchdb-upsert' -export default PouchDB.plugin(PouchDBFindPlugin) +export default PouchDB.plugin(PouchDBFindPlugin).plugin(PouchDBUpsertPlugin) + +// Type guard to check if the error is of type `Error` and has a `name` property +export function isNotFoundError(error: unknown): error is { name: string } { + return ( + typeof error === 'object' && + error !== null && + 'name' in error && + (error as { name: string }).name === 'not_found' + ) +} diff --git a/apps/frontend/src/api/dbs.ts b/apps/frontend/src/api/dbs.ts index 1005a5f..b225347 100644 --- a/apps/frontend/src/api/dbs.ts +++ b/apps/frontend/src/api/dbs.ts @@ -1,6 +1,10 @@ import PouchDB from '@/api/PouchDB' -import type { QRCodeDocument, MessageDocument } from '@/types/DBDocumentTypes' +import type { + QRCodeDocument, + MessageDocument, + WebPushSubscriptionDocument, +} from '@/types/DBDocumentTypes' export const qrCodeDbRemote = new PouchDB('https://hereconnect.condev.ru/api/qr', { skip_setup: true, @@ -10,6 +14,10 @@ export const qrCodeDbLocal = new PouchDB('qr') export const messagesDbLocal = new PouchDB('messages') +export const webPushSubscriptionsDbRemote = new PouchDB( + 'https://hereconnect.condev.ru/api/web_push_subscriptions', +) + export const messagesDbRemote = new PouchDB( 'https://hereconnect.condev.ru/api/messages', { diff --git a/apps/frontend/src/api/qrCode.ts b/apps/frontend/src/api/qrCode.ts index ef4ecd8..6e4804b 100644 --- a/apps/frontend/src/api/qrCode.ts +++ b/apps/frontend/src/api/qrCode.ts @@ -1,5 +1,8 @@ import type { QRCodeDocument } from '@/types/DBDocumentTypes' import { qrCodeDbLocal, qrCodeDbRemote } from '@/api/dbs' +import { getUserUuid } from '@/utils/getUserUuid' +import { getBrowserUuid } from '@/utils/getBrowserUuid' +import { isNotFoundError } from '@/api/PouchDB' const EPOHE = 1731661630981 @@ -21,6 +24,8 @@ export const createQrCodeDocument = async ( type: 'qr_code', uri, url: `https://hereconnect.condev.ru/read/${uri}`, + user_uuid: getUserUuid(), + browser_uuid: getBrowserUuid(), created_at: new Date().toISOString(), ...qrCodeDocPartial, } @@ -48,11 +53,13 @@ export const createQrCodeDocument = async ( return qrCodeDoc } -export const getQrCodeDocument = (uri: string) => { - return qrCodeDbLocal.get(`qr_code:${uri}`).catch((error) => { - if (error.name === 'not_found') { - return qrCodeDbRemote.get(`qr_code:${uri}`) +export const getQrCodeDocument = async (uri: string) => { + try { + return await qrCodeDbLocal.get(`qr_code:${uri}`) + } catch (error) { + if (isNotFoundError(error)) { + return await qrCodeDbRemote.get(`qr_code:${uri}`) } throw error - }) + } } diff --git a/apps/frontend/src/api/webPush.ts b/apps/frontend/src/api/webPush.ts new file mode 100644 index 0000000..da3cecf --- /dev/null +++ b/apps/frontend/src/api/webPush.ts @@ -0,0 +1,41 @@ +import type { WebPushSubscriptionDocument } from '@/types/DBDocumentTypes' +import { webPushSubscriptionsDbRemote } from '@/api/dbs' +import { getUserUuid } from '@/utils/getUserUuid' +import { getBrowserUuid } from '@/utils/getBrowserUuid' + +export const saveWebPushSubscription = async (subscription: PushSubscription) => { + const type = 'web_push_subscription' + const user_uuid = getUserUuid() + const browser_uuid = getBrowserUuid() + const _id = `${type}:${user_uuid}:${browser_uuid}` + const created_at = new Date().toISOString() + const webPushSubscriptionDoc: WebPushSubscriptionDocument = { + _id, + type, + user_uuid, + browser_uuid, + subscription: subscription.toJSON(), + created_at, + } + try { + const { rev } = await webPushSubscriptionsDbRemote.upsert( + _id, + (doc): WebPushSubscriptionDocument | false => { + webPushSubscriptionDoc._rev = doc._rev + const isNotChanged = + JSON.stringify(webPushSubscriptionDoc.subscription) === JSON.stringify(doc.subscription) + if (isNotChanged) { + return false + } + webPushSubscriptionDoc.created_at = doc.created_at ?? created_at + webPushSubscriptionDoc.updated_at = doc.created_at ? new Date().toISOString() : created_at + return webPushSubscriptionDoc + }, + ) + webPushSubscriptionDoc._rev = rev + console.log('Подписка успешно сохранена', webPushSubscriptionDoc) + return webPushSubscriptionDoc + } catch (error) { + console.error('Ошибка сохранения подписки:', error) + } +} diff --git a/apps/frontend/src/components/create/CreateMessageDeliveryOptionWebPush.vue b/apps/frontend/src/components/create/CreateMessageDeliveryOptionWebPush.vue index ee3d3d8..b3add45 100644 --- a/apps/frontend/src/components/create/CreateMessageDeliveryOptionWebPush.vue +++ b/apps/frontend/src/components/create/CreateMessageDeliveryOptionWebPush.vue @@ -13,6 +13,8 @@ import { type QRCodeDocument } from '@/types/DBDocumentTypes' import { type DeliveryOption, DELIVERY_METHOD_WEB_PUSH } from '@/constants/deliveryOptions' import { ref } from 'vue' +import { saveWebPushSubscription } from '@/api/webPush' + type DeliveryOptionWebPush = Extract defineProps<{ @@ -61,30 +63,7 @@ async function requestNotificationPermission(): Promise { 'BMPwpR1Q24ZOFxy2T9M-I-Y6F6bucsHFVZKP8QYslgD_4hGCDv16qnZnji-ldngcZ_vBWVBwJ6OIfknVBnCxLsY', }) - console.log('Push-подписка создана:', subscription) - console.log(JSON.stringify(subscription)) - // subscription = { - // endpoint: - // 'https://jmt17.google.com/fcm/send/eFYnP7qxt8A:APA91bHYGItzGpnzhXkfKzgMhAXgwcQXaLuRLqq9mt9RPS0VgGpEvVASxW8AF_fJmtSBtK-po0V3mM7fGWSN6tMCrImZqgZG14kGv0chQoghOLuWgHTUQYtQEqhd6f89eVonJdtjAlDN', - // expirationTime: null, - // keys: { - // p256dh: - // 'BAIGLAQ5cZg6e8psnK6vy_MJ1kUJbs2Tymjc38-_6zM1VQXWP2Yj-604uPMye4smHosNKmBEPRDJJOH6Wxr1MLQ', - // auth: '1k48wvT0U3pK_xaXW95Ekw', - // }, - // } - // subscription = { - // endpoint: - // 'https://jmt17.google.com/fcm/send/eFYnP7qxt8A:APA91bHYGItzGpnzhXkfKzgMhAXgwcQXaLuRLqq9mt9RPS0VgGpEvVASxW8AF_fJmtSBtK-po0V3mM7fGWSN6tMCrImZqgZG14kGv0chQoghOLuWgHTUQYtQEqhd6f89eVonJdtjAlDN', - // expirationTime: null, - // keys: { - // p256dh: - // 'BAIGLAQ5cZg6e8psnK6vy_MJ1kUJbs2Tymjc38-_6zM1VQXWP2Yj-604uPMye4smHosNKmBEPRDJJOH6Wxr1MLQ', - // auth: '1k48wvT0U3pK_xaXW95Ekw', - // }, - // } - - // Здесь вы можете сохранить подписку на сервере + await saveWebPushSubscription(subscription) return true } catch (err) { console.error('Ошибка при создании Push-подписки:', err) diff --git a/apps/frontend/src/constants/localStorageKeys.ts b/apps/frontend/src/constants/localStorageKeys.ts new file mode 100644 index 0000000..acf76d9 --- /dev/null +++ b/apps/frontend/src/constants/localStorageKeys.ts @@ -0,0 +1,2 @@ +export const LOCALSTORAGE_KEY_USER_UUID = 'user_uuid' +export const LOCALSTORAGE_KEY_BROWSER_UUID = 'browser_uuid' diff --git a/apps/frontend/src/types/DBDocumentTypes/QRCodeDocument.ts b/apps/frontend/src/types/DBDocumentTypes/QRCodeDocument.ts index 83a37e5..8b6a073 100644 --- a/apps/frontend/src/types/DBDocumentTypes/QRCodeDocument.ts +++ b/apps/frontend/src/types/DBDocumentTypes/QRCodeDocument.ts @@ -21,7 +21,10 @@ export interface QRCodeDocument extends BaseDocument { url: string /** Идентификатор пользователя, который создал QR‑код */ - // user_uuid: string + user_uuid: string + + /** Идентификатор браузера, через который был создан QR-код */ + browser_uuid: string /** Название объекта, для которого создан QR‑код, например "Мой автомобиль" */ name: string diff --git a/apps/frontend/src/types/DBDocumentTypes/WebPushSubscriptionDocument.ts b/apps/frontend/src/types/DBDocumentTypes/WebPushSubscriptionDocument.ts new file mode 100644 index 0000000..549050f --- /dev/null +++ b/apps/frontend/src/types/DBDocumentTypes/WebPushSubscriptionDocument.ts @@ -0,0 +1,11 @@ +import type { BaseDocument } from '@/types/DBDocumentTypes/BaseDocument' + +export interface WebPushSubscriptionDocument extends BaseDocument { + _id: string // Формат: "web_push_subscription::" + type: 'web_push_subscription' + user_uuid: string // UUID пользователя, связанного с подпиской + browser_uuid: string // UUID браузера, связанного с подпиской + subscription: PushSubscriptionJSON + created_at: string // Дата создания подписки в ISO формате + updated_at?: string // Дата последнего обновления подписки +} diff --git a/apps/frontend/src/types/DBDocumentTypes/index.ts b/apps/frontend/src/types/DBDocumentTypes/index.ts index 35fda95..ce05ca3 100644 --- a/apps/frontend/src/types/DBDocumentTypes/index.ts +++ b/apps/frontend/src/types/DBDocumentTypes/index.ts @@ -2,3 +2,4 @@ export * from './BaseDocument' export * from './QRCodeDocument' export * from './MessageDocument' export * from './UserDocument' +export * from './WebPushSubscriptionDocument' diff --git a/apps/frontend/src/utils/getBrowserUuid.ts b/apps/frontend/src/utils/getBrowserUuid.ts new file mode 100644 index 0000000..67f8214 --- /dev/null +++ b/apps/frontend/src/utils/getBrowserUuid.ts @@ -0,0 +1,10 @@ +import { LOCALSTORAGE_KEY_BROWSER_UUID } from '@/constants/localStorageKeys' + +export function getBrowserUuid(): string { + let browserUuid = localStorage.getItem(LOCALSTORAGE_KEY_BROWSER_UUID) + if (!browserUuid) { + browserUuid = crypto.randomUUID() // Браузерная API для генерации UUID + localStorage.setItem(LOCALSTORAGE_KEY_BROWSER_UUID, browserUuid) + } + return browserUuid +} diff --git a/apps/frontend/src/utils/getUserUuid.ts b/apps/frontend/src/utils/getUserUuid.ts new file mode 100644 index 0000000..c06bfd3 --- /dev/null +++ b/apps/frontend/src/utils/getUserUuid.ts @@ -0,0 +1,10 @@ +import { LOCALSTORAGE_KEY_USER_UUID } from '@/constants/localStorageKeys' + +export function getUserUuid(): string { + let userUuid = localStorage.getItem(LOCALSTORAGE_KEY_USER_UUID) + if (!userUuid) { + userUuid = crypto.randomUUID() + localStorage.setItem(LOCALSTORAGE_KEY_USER_UUID, userUuid) + } + return userUuid +} diff --git a/package-lock.json b/package-lock.json index 621e879..3f52b03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "pinia": "^2.2.6", "pouchdb-browser": "^9.0.0", "pouchdb-find": "^9.0.0", + "pouchdb-upsert": "^2.2.0", "primeicons": "^7.0.0", "primevue": "^4.2.1", "qr-code-styling": "^1.8.4", @@ -52,6 +53,7 @@ "@types/md5": "^2.3.5", "@types/node": "^20.17.6", "@types/pouchdb-browser": "^6.1.5", + "@types/pouchdb-upsert": "^2.2.9", "@vitejs/plugin-vue": "^5.1.4", "@vitest/eslint-plugin": "1.1.7", "@vue/eslint-config-prettier": "^10.1.0", @@ -2020,6 +2022,16 @@ "@types/pouchdb-find": "*" } }, + "node_modules/@types/pouchdb-upsert": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@types/pouchdb-upsert/-/pouchdb-upsert-2.2.9.tgz", + "integrity": "sha512-S8K4EGkN8K+xMBRP87eKY6eBM3rJjBEDuMFYK+Kk8WiRGxfwmLQhyI88pu+OA0tMC0pkDWFQbhtFy20grFEDug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/pouchdb-core": "*" + } + }, "node_modules/@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", @@ -4591,6 +4603,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -5058,6 +5076,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -6149,6 +6176,18 @@ "pouchdb-utils": "9.0.0" } }, + "node_modules/pouchdb-adapter-http": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/pouchdb-adapter-http/-/pouchdb-adapter-http-9.0.0.tgz", + "integrity": "sha512-2eL008XeRZkdyp3hMHHOhdIPqK9H6Mn4SLlQvit4zCbqnOFfAswzPjUmHULGMbDUCrQBTu6y82FnV6NHXv9kgw==", + "license": "Apache-2.0", + "dependencies": { + "pouchdb-binary-utils": "9.0.0", + "pouchdb-errors": "9.0.0", + "pouchdb-fetch": "9.0.0", + "pouchdb-utils": "9.0.0" + } + }, "node_modules/pouchdb-binary-utils": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-binary-utils/-/pouchdb-binary-utils-9.0.0.tgz", @@ -6166,12 +6205,37 @@ "vuvuzela": "1.0.3" } }, + "node_modules/pouchdb-changes-filter": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/pouchdb-changes-filter/-/pouchdb-changes-filter-9.0.0.tgz", + "integrity": "sha512-ig0fo0WLgIjAniFJ19Uw1Y+oxiypqC+Skhd8BCETRVXOhLBzueRwEQR4thffyo0UayYVqldJfSR5wHSDvEVk/A==", + "license": "Apache-2.0", + "dependencies": { + "pouchdb-errors": "9.0.0", + "pouchdb-selector-core": "9.0.0", + "pouchdb-utils": "9.0.0" + } + }, "node_modules/pouchdb-collate": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-collate/-/pouchdb-collate-9.0.0.tgz", "integrity": "sha512-TrnEDNZEmIIl+W3xKUO8h+geqVLQ90oZe5ujPkl8myUzpREULWXWQBnV5EzPXVEKDBpJlb8T3I6oy/zdWGQpdA==", "license": "Apache-2.0" }, + "node_modules/pouchdb-core": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/pouchdb-core/-/pouchdb-core-9.0.0.tgz", + "integrity": "sha512-98SJgs8bqXhr4gMGuOTR8yVeLlMYy797zlOtdlvlXIxIicvocyA8ColhVVhdBXPNOGxT2HwReIMywdIVAgibpg==", + "license": "Apache-2.0", + "dependencies": { + "pouchdb-changes-filter": "9.0.0", + "pouchdb-errors": "9.0.0", + "pouchdb-fetch": "9.0.0", + "pouchdb-merge": "9.0.0", + "pouchdb-utils": "9.0.0", + "uuid": "8.3.2" + } + }, "node_modules/pouchdb-errors": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-errors/-/pouchdb-errors-9.0.0.tgz", @@ -6222,6 +6286,24 @@ "spark-md5": "3.0.2" } }, + "node_modules/pouchdb-merge": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/pouchdb-merge/-/pouchdb-merge-9.0.0.tgz", + "integrity": "sha512-Xh+TgOZCkGoZpI589btKf/cTiuQ5CsnPl9YpdW4h0cAPusniN6XNsR62F+/HbL9wirI6XTEPHUrk7MsQbk3S3A==", + "license": "Apache-2.0", + "dependencies": { + "pouchdb-utils": "9.0.0" + } + }, + "node_modules/pouchdb-promise": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/pouchdb-promise/-/pouchdb-promise-6.4.3.tgz", + "integrity": "sha512-ruJaSFXwzsxRHQfwNHjQfsj58LBOY1RzGzde4PM5CWINZwFjCQAhZwfMrch2o/0oZT6d+Xtt0HTWhq35p3b0qw==", + "license": "Apache-2.0", + "dependencies": { + "lie": "3.1.1" + } + }, "node_modules/pouchdb-selector-core": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-selector-core/-/pouchdb-selector-core-9.0.0.tgz", @@ -6232,6 +6314,15 @@ "pouchdb-utils": "9.0.0" } }, + "node_modules/pouchdb-upsert": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pouchdb-upsert/-/pouchdb-upsert-2.2.0.tgz", + "integrity": "sha512-hZceBqSnBLodlHtsJJdGJP9ZN+OHj3IXj8+YCBoHUPpzgLw04wfIOp+PLYROqyL6BODeYF00VFdiX8iIGcCtUw==", + "license": "Apache-2.0", + "dependencies": { + "pouchdb-promise": "^6.1.2" + } + }, "node_modules/pouchdb-utils": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-utils/-/pouchdb-utils-9.0.0.tgz", @@ -8284,12 +8375,13 @@ "version": "1.0.0", "license": "@hereconnect/license", "dependencies": { + "pouchdb-adapter-http": "^9.0.0", + "pouchdb-core": "^9.0.0", "web-push": "^3.6.7" }, - "devDependencies": {}, "engines": { - "node": ">= 14.0.0", - "npm": ">= 6.0.0" + "node": ">=v20.10.0", + "pnpm": ">=8.15.0" } } } diff --git a/services/message-delivery-method-web-push/PouchDB.js b/services/message-delivery-method-web-push/PouchDB.js new file mode 100644 index 0000000..8dede2c --- /dev/null +++ b/services/message-delivery-method-web-push/PouchDB.js @@ -0,0 +1,4 @@ +import PouchDB from "pouchdb-core"; +import PouchDBAdapterHttp from "pouchdb-adapter-http"; + +export default PouchDB.plugin(PouchDBAdapterHttp); diff --git a/services/message-delivery-method-web-push/package.json b/services/message-delivery-method-web-push/package.json index 6ca1aa6..6b26691 100644 --- a/services/message-delivery-method-web-push/package.json +++ b/services/message-delivery-method-web-push/package.json @@ -28,6 +28,8 @@ "test": "" }, "dependencies": { + "pouchdb-adapter-http": "^9.0.0", + "pouchdb-core": "^9.0.0", "web-push": "^3.6.7" } } diff --git a/services/message-delivery-method-web-push/test-send-notification.js b/services/message-delivery-method-web-push/test-send-notification.js new file mode 100644 index 0000000..3705215 --- /dev/null +++ b/services/message-delivery-method-web-push/test-send-notification.js @@ -0,0 +1,54 @@ +import webpush from "web-push"; +import PouchDB from "./PouchDB.js"; + +if (!process.env.API_URL) { + throw new Error(`env API_URL is not set`); +} + +const settings = await new PouchDB(`${process.env.API_URL}/server_settings`, { + skip_setup: true, +}).get("server_settings:message_delivery_method_web_push:vapid"); + +webpush.setVapidDetails( + settings.vapid.subject, + settings.vapid.keys.publicKey, + settings.vapid.keys.privateKey, +); + +const notification = { + type: "notification", + notification: { + title: "test", + options: { + icon: "/images/qr-code-logo-200x200.jpg", + body: "Test push message from DevTools.", + tag: "2105ef6f-3ca6-4f99-b258-0ca23f014309", + // requireInteraction: true, + // silent: false, + // vibrate: [200, 100, 200, 100, 200, 100, 200], + data: { + url: "/chat/6ilhxu/13trvu", + payload: { tag: "2105ef6f-3ca6-4f99-b258-0ca23f014309" }, + }, + }, + }, +}; + +const { rows } = await new PouchDB( + `${process.env.API_URL}/web_push_subscriptions`, + { skip_setup: true }, +).allDocs({ + startkey: "web_push_subscription:", + endkey: "web_push_subscription:\uFFFF", + include_docs: true, +}); + +await Promise.allSettled( + rows.map(({ doc }) => + webpush.sendNotification(doc.subscription, JSON.stringify(notification)), + ), +); + +// issues: +// 1. NO SOUND +// 2. SAFARI NO OPEN PAGE