diff --git a/.env.example b/.env.example index 6cf1885..3b921f9 100644 --- a/.env.example +++ b/.env.example @@ -17,4 +17,11 @@ REDIS_URL= OPENAI_API_KEY= # Messaging -XMTP_ENV=dev \ No newline at end of file +XMTP_ENV=dev + +# Push Notifications (VAPID) +VAPID_PUBLIC_KEY= +VAPID_PRIVATE_KEY= +VAPID_SUBJECT=mailto:admin@example.com +# Exposed to the browser via Next.js +NEXT_PUBLIC_VAPID_PUBLIC_KEY= \ No newline at end of file diff --git a/apps/backend/drizzle/0009_push_subscriptions.sql b/apps/backend/drizzle/0009_push_subscriptions.sql new file mode 100644 index 0000000..0b1f9b7 --- /dev/null +++ b/apps/backend/drizzle/0009_push_subscriptions.sql @@ -0,0 +1,13 @@ +CREATE TABLE "push_subscriptions" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "device_id" uuid NOT NULL, + "endpoint" text NOT NULL, + "p256dh" text NOT NULL, + "auth" text NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "last_used_at" timestamp, + "disabled_at" timestamp, + CONSTRAINT "push_subscriptions_endpoint_unique" UNIQUE("endpoint") +); +--> statement-breakpoint +ALTER TABLE "push_subscriptions" ADD CONSTRAINT "push_subscriptions_device_id_user_devices_id_fk" FOREIGN KEY ("device_id") REFERENCES "public"."user_devices"("id") ON DELETE cascade ON UPDATE no action; diff --git a/apps/backend/drizzle/meta/_journal.json b/apps/backend/drizzle/meta/_journal.json index 6f5a899..1937672 100644 --- a/apps/backend/drizzle/meta/_journal.json +++ b/apps/backend/drizzle/meta/_journal.json @@ -64,6 +64,13 @@ "when": 1783000000000, "tag": "0008_extend_messages", "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1751140955086, + "tag": "0009_push_subscriptions", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/backend/package.json b/apps/backend/package.json index b7b4c69..ce4122b 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -40,6 +40,7 @@ "postgres": "^3.4.9", "redis": "^6.0.0", "socket.io": "^4.8.3", + "web-push": "3.6.7", "zod": "^4.4.3" }, "devDependencies": { @@ -50,6 +51,7 @@ "@types/morgan": "^1.9.10", "@types/node": "^20.19.37", "@types/supertest": "^7.2.0", + "@types/web-push": "3.6.4", "@vitest/coverage-v8": "^4.1.6", "drizzle-kit": "^0.31.10", "eslint": "^9.39.4", diff --git a/apps/backend/src/config.ts b/apps/backend/src/config.ts index ce53b6c..c5cfdea 100644 --- a/apps/backend/src/config.ts +++ b/apps/backend/src/config.ts @@ -11,6 +11,9 @@ export const EnvSchema = z.object({ JWT_SECRET: z.string().min(1, 'JWT_SECRET is required'), PORT: z.coerce.number().int('PORT must be an integer').positive('PORT must be positive'), TOKEN_TRANSFER_CONTRACT_ID: z.string().min(1, 'TOKEN_TRANSFER_CONTRACT_ID is required'), + VAPID_PUBLIC_KEY: z.string().min(1, 'VAPID_PUBLIC_KEY is required'), + VAPID_PRIVATE_KEY: z.string().min(1, 'VAPID_PRIVATE_KEY is required'), + VAPID_SUBJECT: z.string().min(1, 'VAPID_SUBJECT is required'), }); export type Env = z.infer; diff --git a/apps/backend/src/db/schema.ts b/apps/backend/src/db/schema.ts index 3305884..46b3eeb 100644 --- a/apps/backend/src/db/schema.ts +++ b/apps/backend/src/db/schema.ts @@ -271,6 +271,8 @@ export const pushSubscriptions = pgTable('push_subscriptions', { p256dh: text('p256dh').notNull(), auth: text('auth').notNull(), createdAt: timestamp('created_at').notNull().defaultNow(), + lastUsedAt: timestamp('last_used_at'), + disabledAt: timestamp('disabled_at'), }); export type PushSubscription = typeof pushSubscriptions.$inferSelect; diff --git a/apps/backend/src/routes/push.ts b/apps/backend/src/routes/push.ts index 9205dfb..b4e31ca 100644 --- a/apps/backend/src/routes/push.ts +++ b/apps/backend/src/routes/push.ts @@ -1,6 +1,6 @@ import { Router } from 'express'; import type { IRouter } from 'express'; -import { eq, and } from 'drizzle-orm'; +import { eq, and, isNull } from 'drizzle-orm'; import { db } from '../db/index.js'; import { pushSubscriptions } from '../db/schema.js'; import { requireAuth, type AuthRequest } from '../middleware/auth.js'; @@ -18,7 +18,6 @@ pushRouter.post('/subscriptions', async (req: AuthRequest, res) => { } try { - // Upsert subscription await db .insert(pushSubscriptions) .values({ @@ -26,6 +25,7 @@ pushRouter.post('/subscriptions', async (req: AuthRequest, res) => { endpoint, p256dh: keys.p256dh, auth: keys.auth, + lastUsedAt: new Date(), }) .onConflictDoUpdate({ target: [pushSubscriptions.endpoint], @@ -33,6 +33,8 @@ pushRouter.post('/subscriptions', async (req: AuthRequest, res) => { deviceId, p256dh: keys.p256dh, auth: keys.auth, + disabledAt: null, + lastUsedAt: new Date(), }, }); @@ -62,3 +64,24 @@ pushRouter.delete('/subscriptions', async (req: AuthRequest, res) => { res.status(500).json({ error: 'Failed to delete subscription' }); } }); + +/** + * Touch lastUsedAt for active (non-disabled) subscriptions by device. + * Called internally before sending a push notification. + */ +export async function touchSubscription(endpoint: string): Promise { + await db + .update(pushSubscriptions) + .set({ lastUsedAt: new Date() }) + .where(and(eq(pushSubscriptions.endpoint, endpoint), isNull(pushSubscriptions.disabledAt))); +} + +/** + * Mark a subscription as disabled (e.g. after a 410 Gone from the push service). + */ +export async function disableSubscription(endpoint: string): Promise { + await db + .update(pushSubscriptions) + .set({ disabledAt: new Date() }) + .where(eq(pushSubscriptions.endpoint, endpoint)); +} diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index e9ffa30..8608dcb 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -1,7 +1,9 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + env: { + NEXT_PUBLIC_VAPID_PUBLIC_KEY: process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY ?? '', + }, }; export default nextConfig; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c1ac0a3..85bade5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,10 +10,10 @@ importers: devDependencies: prettier: specifier: latest - version: 3.8.3 + version: 3.9.1 turbo: specifier: latest - version: 2.9.16 + version: 2.10.0 apps/backend: dependencies: @@ -62,6 +62,9 @@ importers: socket.io: specifier: ^4.8.3 version: 4.8.3 + web-push: + specifier: 3.6.7 + version: 3.6.7 zod: specifier: ^4.4.3 version: 4.4.3 @@ -87,6 +90,9 @@ importers: '@types/supertest': specifier: ^7.2.0 version: 7.2.0 + '@types/web-push': + specifier: 3.6.4 + version: 3.6.4 '@vitest/coverage-v8': specifier: ^4.1.6 version: 4.1.6(vitest@4.1.6) @@ -1342,6 +1348,7 @@ packages: '@stellar/stellar-base@15.0.0': resolution: {integrity: sha512-XQhxUr9BYiEcFcgc4oWcCMR9QJCny/GmmGsuwPKf/ieIcOeb5149KLHYx9mJCA0ea8QbucR2/GzV58QbXOTxQA==} engines: {node: '>=20.0.0'} + deprecated: This package is now rolled into @stellar/stellar-sdk. Please use @stellar/stellar-sdk to continue receiving updates and support. '@stellar/stellar-sdk@15.1.0': resolution: {integrity: sha512-GsJUcWx2yboVzYdhTe/LHS3V1wVLSHkUkglC5bBoYWGJt31vzIhbSGno60NP9CdCTNkLJdnrsLJ63oA58Zvh5A==} @@ -1451,33 +1458,33 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@turbo/darwin-64@2.9.16': - resolution: {integrity: sha512-jLjApWTSNd7JZ5JaLYfelW1ytnGQOvB7ivl+2RD1xQvJTbi8I9gBjzcga7tDZVPyaxpl10YTfJt3BrYXR18KDw==} + '@turbo/darwin-64@2.10.0': + resolution: {integrity: sha512-EwvHThXzpY0KGd1/NAmuewI5D+aVa3Rl/OlxE36yfjUKb/+ySrfJrSlEFt8aD1OXwnnaHnQnPKHFndor0Zxlsg==} cpu: [x64] os: [darwin] - '@turbo/darwin-arm64@2.9.16': - resolution: {integrity: sha512-YPgrn+5HIGzrx0O2a631SV4MBQUe4W/DafMFUuBVgaU32PW9/OTT0ehviF0QSxTXuRJlHvW2eUTemddF5/spmw==} + '@turbo/darwin-arm64@2.10.0': + resolution: {integrity: sha512-9d2fTyyG0lf5Wq1bwJA9qUaeecViMkLcdctWaMMmCkxZ/JqypmqOwK3W6vmejeKVgkr06gSoiX8bD+xN5Jpxcg==} cpu: [arm64] os: [darwin] - '@turbo/linux-64@2.9.16': - resolution: {integrity: sha512-vAEf1H6l26lTpl9FJ/peQo1NUB8RC0sbEJJz5mPcUhHA2bPDup2x3CZPgo/bH8S4cUcBLm4FN3UHd5iUO2RAew==} + '@turbo/linux-64@2.10.0': + resolution: {integrity: sha512-sZBtjMuufitanjzi6UssoUpJMnnPlLMcdcJj3m3ptNsSq31Xh7MnjhwA5nWvLDTfEFg8GPcbYFXMo8vSdKRfqQ==} cpu: [x64] os: [linux] - '@turbo/linux-arm64@2.9.16': - resolution: {integrity: sha512-xDBLR2PZg4BrQOchfG6svgpv5FCNJ2TOtT2psLdEJcdKo1BH+pnPs9Xj6pvUjgfkHbuvBOfeE4R6tvxMoQKDHQ==} + '@turbo/linux-arm64@2.10.0': + resolution: {integrity: sha512-vkq/Z8R+1DQ+kifWFa810IjRy2NNBVvha3cg9sWA3nFh6nnGrHSMnnJKrzH7c/No9kq4Jb55Ru44YKsCSBgrKg==} cpu: [arm64] os: [linux] - '@turbo/windows-64@2.9.16': - resolution: {integrity: sha512-NBAJnaUiGdgkSzQwUIdOvkCkcpTSu58G/sBGa0mvBtzfvFOOgrQwepKOOQ8cp6sWM6OcKDNFj2p1dsZA1OWjPg==} + '@turbo/windows-64@2.10.0': + resolution: {integrity: sha512-CRUEguLWxFQHptYZS7HjPhNhAFawfea07iR+xAQ5e4klgLrPCMdexBkXwSCwOxqTFknJ7RZFN3gOaADsw+Gttg==} cpu: [x64] os: [win32] - '@turbo/windows-arm64@2.9.16': - resolution: {integrity: sha512-Y7SJppD0Z8wjO3Ec0ZGd9KQ4Yv0BMnA8CIowj5Vp+OEVsosXDG2weK6/t1RRLfJmc2Ozrnd6y4DOgQys+mn3WQ==} + '@turbo/windows-arm64@2.10.0': + resolution: {integrity: sha512-dVHGaf9F8twzgibcBqKoADT/LLqf9++jDb+hq/LPWWaOmRpp4M+/pVOm7vy4z9D++xg8eaxWLT0+wQxFwhYu9A==} cpu: [arm64] os: [win32] @@ -1561,6 +1568,9 @@ packages: '@types/supertest@7.2.0': resolution: {integrity: sha512-uh2Lv57xvggst6lCqNdFAmDSvoMG7M/HDtX4iUCquxQ5EGPtaPM5PL5Hmi7LCvOG8db7YaCPNJEeoI8s/WzIQw==} + '@types/web-push@3.6.4': + resolution: {integrity: sha512-GnJmSr40H3RAnj0s34FNTcJi1hmWFV5KXugE0mYWnYhgTAHLJ/dJKAwDmvPJYMke0RplY2XE9LnM4hqSqKIjhQ==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} @@ -1837,6 +1847,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} @@ -1889,6 +1903,9 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + asn1.js@5.4.1: + resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -1970,6 +1987,9 @@ packages: bignumber.js@9.3.1: resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + bn.js@4.12.4: + resolution: {integrity: sha512-njR1b+ixG2ufvL9Zn9JGneW+b5GV6jqpYyPPpg4QVt723b5kJPGUczkUyWEH9BwEA74UakJZ43I4FDLBF7ci0g==} + body-parser@2.2.2: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} @@ -2731,6 +2751,14 @@ packages: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} + http_ece@1.2.0: + resolution: {integrity: sha512-JrF8SSLVmcvc5NducxgyOrKXe3EsyHMgBFgSaIUGmArKe+rwr0uphRkRXvwiom3I+fpIfoItveHrfudL8/rxuA==} + engines: {node: '>=16'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + iconv-lite@0.7.2: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} @@ -3138,6 +3166,9 @@ packages: engines: {node: '>=4.0.0'} hasBin: true + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + minimatch@10.2.4: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} @@ -3351,6 +3382,11 @@ packages: engines: {node: '>=14'} hasBin: true + prettier@3.9.1: + resolution: {integrity: sha512-ppiDo2CSwexck1eyZUwJHg/N3nf1+6IRCv7W/VJ5vaLnVCmB7+3CdRfMwoCHBBX6xTrREDTksZ4OZl5SSf4zXA==} + engines: {node: '>=14'} + hasBin: true + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -3735,8 +3771,8 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - turbo@2.9.16: - resolution: {integrity: sha512-NqgRQy6j6dPYcdSdv0q1g9QsZg7SWg87RERM8otw/1AtKU2yTFVClOM7cbwKzOonZr/Ek1blTBucw64L9H0Bwg==} + turbo@2.10.0: + resolution: {integrity: sha512-o016H9PPtuH2deb3mh3Vci3Avfi9UYgM/RONQisY7HnloupP0IFSbFS3gFYJgFJP8nwBrByHWFQIDa8T2zIXPw==} hasBin: true tweetnacl@1.0.3: @@ -3906,6 +3942,11 @@ packages: jsdom: optional: true + web-push@3.6.7: + resolution: {integrity: sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A==} + engines: {node: '>= 16'} + hasBin: true + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -5118,22 +5159,22 @@ snapshots: '@tsconfig/node16@1.0.4': {} - '@turbo/darwin-64@2.9.16': + '@turbo/darwin-64@2.10.0': optional: true - '@turbo/darwin-arm64@2.9.16': + '@turbo/darwin-arm64@2.10.0': optional: true - '@turbo/linux-64@2.9.16': + '@turbo/linux-64@2.10.0': optional: true - '@turbo/linux-arm64@2.9.16': + '@turbo/linux-arm64@2.10.0': optional: true - '@turbo/windows-64@2.9.16': + '@turbo/windows-64@2.10.0': optional: true - '@turbo/windows-arm64@2.9.16': + '@turbo/windows-arm64@2.10.0': optional: true '@tybys/wasm-util@0.10.1': @@ -5234,6 +5275,10 @@ snapshots: '@types/methods': 1.1.4 '@types/superagent': 8.1.10 + '@types/web-push@3.6.4': + dependencies: + '@types/node': 20.19.37 + '@types/ws@8.18.1': dependencies: '@types/node': 20.19.37 @@ -5554,6 +5599,8 @@ snapshots: acorn@8.16.0: {} + agent-base@7.1.4: {} + ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 @@ -5640,6 +5687,13 @@ snapshots: asap@2.0.6: {} + asn1.js@5.4.1: + dependencies: + bn.js: 4.12.4 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + safer-buffer: 2.1.2 + assertion-error@2.0.1: {} ast-types-flow@0.0.8: {} @@ -5702,6 +5756,8 @@ snapshots: bignumber.js@9.3.1: {} + bn.js@4.12.4: {} + body-parser@2.2.2: dependencies: bytes: 3.1.2 @@ -5917,7 +5973,7 @@ snapshots: ecdsa-sig-formatter@1.0.11: dependencies: - safe-buffer: 5.1.2 + safe-buffer: 5.2.1 ee-first@1.1.1: {} @@ -6162,7 +6218,7 @@ snapshots: eslint: 9.39.4(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@9.39.4(jiti@2.6.1)) @@ -6195,7 +6251,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -6210,7 +6266,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -6612,6 +6668,15 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 + http_ece@1.2.0: {} + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -6843,12 +6908,12 @@ snapshots: dependencies: buffer-equal-constant-time: 1.0.1 ecdsa-sig-formatter: 1.0.11 - safe-buffer: 5.1.2 + safe-buffer: 5.2.1 jws@4.0.1: dependencies: jwa: 2.0.1 - safe-buffer: 5.1.2 + safe-buffer: 5.2.1 keyv@4.5.4: dependencies: @@ -6991,6 +7056,8 @@ snapshots: mime@2.6.0: {} + minimalistic-assert@1.0.1: {} + minimatch@10.2.4: dependencies: brace-expansion: 5.0.4 @@ -7197,6 +7264,8 @@ snapshots: prettier@3.8.3: {} + prettier@3.9.1: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -7742,14 +7811,14 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - turbo@2.9.16: + turbo@2.10.0: optionalDependencies: - '@turbo/darwin-64': 2.9.16 - '@turbo/darwin-arm64': 2.9.16 - '@turbo/linux-64': 2.9.16 - '@turbo/linux-arm64': 2.9.16 - '@turbo/windows-64': 2.9.16 - '@turbo/windows-arm64': 2.9.16 + '@turbo/darwin-64': 2.10.0 + '@turbo/darwin-arm64': 2.10.0 + '@turbo/linux-64': 2.10.0 + '@turbo/linux-arm64': 2.10.0 + '@turbo/windows-64': 2.10.0 + '@turbo/windows-arm64': 2.10.0 tweetnacl@1.0.3: {} @@ -7915,6 +7984,16 @@ snapshots: transitivePeerDependencies: - msw + web-push@3.6.7: + dependencies: + asn1.js: 5.4.1 + http_ece: 1.2.0 + https-proxy-agent: 7.0.6 + jws: 4.0.1 + minimist: 1.2.8 + transitivePeerDependencies: + - supports-color + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0