From 37e8b3286a1504a21cfabf5c4872895487e6a987 Mon Sep 17 00:00:00 2001 From: phil Date: Sun, 6 Jul 2025 19:51:18 -0400 Subject: [PATCH] service worker take 2 (yay atcute) --- atproto-notifications/.gitignore | 3 + atproto-notifications/package-lock.json | 59 ++++++++++++++++++++ atproto-notifications/package.json | 3 +- atproto-notifications/src/App.tsx | 3 +- atproto-notifications/src/atproto/resolve.ts | 51 +++++++++++++++++ atproto-notifications/src/service-worker.ts | 14 ++++- atproto-notifications/vite.config.ts | 21 ++++++- 7 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 atproto-notifications/src/atproto/resolve.ts diff --git a/atproto-notifications/.gitignore b/atproto-notifications/.gitignore index a547bf3..fac6407 100644 --- a/atproto-notifications/.gitignore +++ b/atproto-notifications/.gitignore @@ -22,3 +22,6 @@ dist-ssr *.njsproj *.sln *.sw? + +# built files +public/service-worker.js diff --git a/atproto-notifications/package-lock.json b/atproto-notifications/package-lock.json index 1165801..27d4db5 100644 --- a/atproto-notifications/package-lock.json +++ b/atproto-notifications/package-lock.json @@ -8,6 +8,7 @@ "name": "atproto-notifications", "version": "0.0.0", "dependencies": { + "@atcute/identity-resolver": "^1.1.3", "@uidotdev/usehooks": "^2.4.1", "react": "^19.1.0", "react-dom": "^19.1.0" @@ -40,6 +41,49 @@ "node": ">=6.0.0" } }, + "node_modules/@atcute/identity": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@atcute/identity/-/identity-1.0.3.tgz", + "integrity": "sha512-mNMxbKHFGys03A8JXKk0KfMBzdd0vrYMzZZWjpw1nYTs0+ea6bo5S1hwqVUZxHdo1gFHSe/t63jxQIF4yL9aKw==", + "license": "0BSD", + "peer": true, + "dependencies": { + "@atcute/lexicons": "^1.0.4", + "@badrap/valita": "^0.4.5" + } + }, + "node_modules/@atcute/identity-resolver": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@atcute/identity-resolver/-/identity-resolver-1.1.3.tgz", + "integrity": "sha512-KZgGgg99CWaV7Df3+h3X/WMrDzTPQVfsaoIVbTNLx2B56BvCL2EmaxPSVw/7BFUJMZHlVU4rtoEB4lyvNyMswA==", + "license": "MIT", + "dependencies": { + "@atcute/lexicons": "^1.0.4", + "@atcute/util-fetch": "^1.0.1", + "@badrap/valita": "^0.4.4" + }, + "peerDependencies": { + "@atcute/identity": "^1.0.0" + } + }, + "node_modules/@atcute/lexicons": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@atcute/lexicons/-/lexicons-1.1.0.tgz", + "integrity": "sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q==", + "license": "0BSD", + "dependencies": { + "esm-env": "^1.2.2" + } + }, + "node_modules/@atcute/util-fetch": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@atcute/util-fetch/-/util-fetch-1.0.1.tgz", + "integrity": "sha512-Clc0E/5ufyGBVfYBUwWNlHONlZCoblSr4Ho50l1LhmRPGB1Wu/AQ9Sz+rsBg7fdaW/auve8ulmwhRhnX2cGRow==", + "license": "MIT", + "dependencies": { + "@badrap/valita": "^0.4.2" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -322,6 +366,15 @@ "node": ">=6.9.0" } }, + "node_modules/@badrap/valita": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@badrap/valita/-/valita-0.4.5.tgz", + "integrity": "sha512-4QwGbuhh/JesHRQj79mO/l37PvJj4l/tlAu7+S1n4h47qwaNpZ0WDvIwUGLYUsdi9uQ5UPpiG9wb1Wm3XUFBUQ==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", @@ -2182,6 +2235,12 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "license": "MIT" + }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", diff --git a/atproto-notifications/package.json b/atproto-notifications/package.json index 1ecabe0..6e3cbfb 100644 --- a/atproto-notifications/package.json +++ b/atproto-notifications/package.json @@ -4,12 +4,13 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host 127.0.0.1", "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview" }, "dependencies": { + "@atcute/identity-resolver": "^1.1.3", "@uidotdev/usehooks": "^2.4.1", "react": "^19.1.0", "react-dom": "^19.1.0" diff --git a/atproto-notifications/src/App.tsx b/atproto-notifications/src/App.tsx index b9a380d..239c3f7 100644 --- a/atproto-notifications/src/App.tsx +++ b/atproto-notifications/src/App.tsx @@ -35,8 +35,7 @@ function requestPermission(host, setAsking) { } async function subscribeToPush() { - const worker_url = new URL('service-worker.ts', import.meta.url); - const registration = await navigator.serviceWorker.register(worker_url); + const registration = await navigator.serviceWorker.register('/service-worker.js'); const subscribeOptions = { userVisibleOnly: true, diff --git a/atproto-notifications/src/atproto/resolve.ts b/atproto-notifications/src/atproto/resolve.ts new file mode 100644 index 0000000..13aef2a --- /dev/null +++ b/atproto-notifications/src/atproto/resolve.ts @@ -0,0 +1,51 @@ +import { CompositeDidDocumentResolver, PlcDidDocumentResolver, WebDidDocumentResolver } from '@atcute/identity-resolver'; + +const docResolver = new CompositeDidDocumentResolver({ + methods: { + plc: new PlcDidDocumentResolver(), + web: new WebDidDocumentResolver(), + }, +}); + +export async function resolveDid(did) { + let doc; + try { + doc = await docResolver.resolve(did); + } catch (err) { + throw err; + // if (err instanceof DocumentNotFoundError) { + // // did returned no document + // } + // if (err instanceof UnsupportedDidMethodError) { + // // resolver doesn't support did method (composite resolver) + // } + // if (err instanceof ImproperDidError) { + // // resolver considers did as invalid (atproto did:web) + // } + // if (err instanceof FailedDocumentResolutionError) { + // // document resolution had thrown something unexpected (fetch error) + // } + // if (err instanceof HandleResolutionError) { + // // the errors above extend this class, so you can do a catch-all. + // } + } + + if (!(doc.alsoKnownAs && doc.alsoKnownAs.length >= 1)) { + console.error('questionable doc', doc); + throw new Error('doc missing aka'); + } + + const aka = doc.alsoKnownAs[0]; + if (!aka.startsWith('at://')) { + console.error('questionable aka doesn\'t start with aka://', aka); + throw new Error('aka not an at-uri'); + } + + const handle = aka.slice('at://'.length); + if (handle.length === 0) { + console.error('empty handle? aka:', aka); + throw new Error('empty handle'); + } + + return handle; +} diff --git a/atproto-notifications/src/service-worker.ts b/atproto-notifications/src/service-worker.ts index bca600a..01e011e 100644 --- a/atproto-notifications/src/service-worker.ts +++ b/atproto-notifications/src/service-worker.ts @@ -1,3 +1,5 @@ +import { resolveDid } from './atproto/resolve'; + self.addEventListener('push', handlePush); self.addEventListener('notificationclick', handleNotificationClick); @@ -43,6 +45,16 @@ async function handlePush(ev) { 'app.bsky.feed.like:subject.uri': 'New like 💜', }[source] ?? source; + let handle = 'unknown'; + if (source_record.startsWith('at://')) { + const did = source_record.slice('at://'.length).split('/')[0]; + try { + handle = await resolveDid(did); + } catch (err) { + console.error('failed to get handle', err); + } + } + // const tag = 'simple-push-demo-notification-tag'; // TODO: resubscribe to notifs to try to stay alive @@ -67,7 +79,7 @@ async function handlePush(ev) { const notification = self.registration.showNotification(title, { icon, - body: source_record, + body: `from ${handle}`, // actions: [ // {'action': 'bsky', title: 'Bluesky'}, // {'action': 'spacedust', title: 'All notifications'}, diff --git a/atproto-notifications/vite.config.ts b/atproto-notifications/vite.config.ts index 8b0f57b..c4cebc9 100644 --- a/atproto-notifications/vite.config.ts +++ b/atproto-notifications/vite.config.ts @@ -1,7 +1,26 @@ import { defineConfig } from 'vite' +import { join } from 'node:path'; +import { buildSync } from 'esbuild'; import react from '@vitejs/plugin-react' +const buildServiceWorker = forProd => ({ + apply: forProd ? 'build' : 'serve', + enforce: 'pre', + transformIndexHtml() { + buildSync({ + minify: true, + bundle: true, + entryPoints: [join(process.cwd(), 'src', 'service-worker.ts')], + outfile: join(process.cwd(), forProd ? 'dist' : 'public', 'service-worker.js'), + }); + }, +}); + // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [ + buildServiceWorker(true), + buildServiceWorker(false), + react(), + ], }) -- 2.51.2