tangled
alpha
login
or
join now
bad-example.com
/
spacedust-utils
6
fork
atom
demos for spacedust
6
fork
atom
overview
issues
pulls
pipelines
service worker take 2 (yay atcute)
bad-example.com
8 months ago
37e8b328
63f7fd40
+149
-5
7 changed files
expand all
collapse all
unified
split
atproto-notifications
.gitignore
package-lock.json
package.json
src
App.tsx
atproto
resolve.ts
service-worker.ts
vite.config.ts
+3
atproto-notifications/.gitignore
···
22
22
*.njsproj
23
23
*.sln
24
24
*.sw?
25
25
+
26
26
+
# built files
27
27
+
public/service-worker.js
+59
atproto-notifications/package-lock.json
···
8
8
"name": "atproto-notifications",
9
9
"version": "0.0.0",
10
10
"dependencies": {
11
11
+
"@atcute/identity-resolver": "^1.1.3",
11
12
"@uidotdev/usehooks": "^2.4.1",
12
13
"react": "^19.1.0",
13
14
"react-dom": "^19.1.0"
···
38
39
},
39
40
"engines": {
40
41
"node": ">=6.0.0"
42
42
+
}
43
43
+
},
44
44
+
"node_modules/@atcute/identity": {
45
45
+
"version": "1.0.3",
46
46
+
"resolved": "https://registry.npmjs.org/@atcute/identity/-/identity-1.0.3.tgz",
47
47
+
"integrity": "sha512-mNMxbKHFGys03A8JXKk0KfMBzdd0vrYMzZZWjpw1nYTs0+ea6bo5S1hwqVUZxHdo1gFHSe/t63jxQIF4yL9aKw==",
48
48
+
"license": "0BSD",
49
49
+
"peer": true,
50
50
+
"dependencies": {
51
51
+
"@atcute/lexicons": "^1.0.4",
52
52
+
"@badrap/valita": "^0.4.5"
53
53
+
}
54
54
+
},
55
55
+
"node_modules/@atcute/identity-resolver": {
56
56
+
"version": "1.1.3",
57
57
+
"resolved": "https://registry.npmjs.org/@atcute/identity-resolver/-/identity-resolver-1.1.3.tgz",
58
58
+
"integrity": "sha512-KZgGgg99CWaV7Df3+h3X/WMrDzTPQVfsaoIVbTNLx2B56BvCL2EmaxPSVw/7BFUJMZHlVU4rtoEB4lyvNyMswA==",
59
59
+
"license": "MIT",
60
60
+
"dependencies": {
61
61
+
"@atcute/lexicons": "^1.0.4",
62
62
+
"@atcute/util-fetch": "^1.0.1",
63
63
+
"@badrap/valita": "^0.4.4"
64
64
+
},
65
65
+
"peerDependencies": {
66
66
+
"@atcute/identity": "^1.0.0"
67
67
+
}
68
68
+
},
69
69
+
"node_modules/@atcute/lexicons": {
70
70
+
"version": "1.1.0",
71
71
+
"resolved": "https://registry.npmjs.org/@atcute/lexicons/-/lexicons-1.1.0.tgz",
72
72
+
"integrity": "sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q==",
73
73
+
"license": "0BSD",
74
74
+
"dependencies": {
75
75
+
"esm-env": "^1.2.2"
76
76
+
}
77
77
+
},
78
78
+
"node_modules/@atcute/util-fetch": {
79
79
+
"version": "1.0.1",
80
80
+
"resolved": "https://registry.npmjs.org/@atcute/util-fetch/-/util-fetch-1.0.1.tgz",
81
81
+
"integrity": "sha512-Clc0E/5ufyGBVfYBUwWNlHONlZCoblSr4Ho50l1LhmRPGB1Wu/AQ9Sz+rsBg7fdaW/auve8ulmwhRhnX2cGRow==",
82
82
+
"license": "MIT",
83
83
+
"dependencies": {
84
84
+
"@badrap/valita": "^0.4.2"
41
85
}
42
86
},
43
87
"node_modules/@babel/code-frame": {
···
320
364
},
321
365
"engines": {
322
366
"node": ">=6.9.0"
367
367
+
}
368
368
+
},
369
369
+
"node_modules/@badrap/valita": {
370
370
+
"version": "0.4.5",
371
371
+
"resolved": "https://registry.npmjs.org/@badrap/valita/-/valita-0.4.5.tgz",
372
372
+
"integrity": "sha512-4QwGbuhh/JesHRQj79mO/l37PvJj4l/tlAu7+S1n4h47qwaNpZ0WDvIwUGLYUsdi9uQ5UPpiG9wb1Wm3XUFBUQ==",
373
373
+
"license": "MIT",
374
374
+
"engines": {
375
375
+
"node": ">= 18"
323
376
}
324
377
},
325
378
"node_modules/@esbuild/aix-ppc64": {
···
2181
2234
"funding": {
2182
2235
"url": "https://opencollective.com/eslint"
2183
2236
}
2237
2237
+
},
2238
2238
+
"node_modules/esm-env": {
2239
2239
+
"version": "1.2.2",
2240
2240
+
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
2241
2241
+
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
2242
2242
+
"license": "MIT"
2184
2243
},
2185
2244
"node_modules/espree": {
2186
2245
"version": "10.4.0",
+2
-1
atproto-notifications/package.json
···
4
4
"version": "0.0.0",
5
5
"type": "module",
6
6
"scripts": {
7
7
-
"dev": "vite",
7
7
+
"dev": "vite --host 127.0.0.1",
8
8
"build": "tsc -b && vite build",
9
9
"lint": "eslint .",
10
10
"preview": "vite preview"
11
11
},
12
12
"dependencies": {
13
13
+
"@atcute/identity-resolver": "^1.1.3",
13
14
"@uidotdev/usehooks": "^2.4.1",
14
15
"react": "^19.1.0",
15
16
"react-dom": "^19.1.0"
+1
-2
atproto-notifications/src/App.tsx
···
35
35
}
36
36
37
37
async function subscribeToPush() {
38
38
-
const worker_url = new URL('service-worker.ts', import.meta.url);
39
39
-
const registration = await navigator.serviceWorker.register(worker_url);
38
38
+
const registration = await navigator.serviceWorker.register('/service-worker.js');
40
39
41
40
const subscribeOptions = {
42
41
userVisibleOnly: true,
+51
atproto-notifications/src/atproto/resolve.ts
···
1
1
+
import { CompositeDidDocumentResolver, PlcDidDocumentResolver, WebDidDocumentResolver } from '@atcute/identity-resolver';
2
2
+
3
3
+
const docResolver = new CompositeDidDocumentResolver({
4
4
+
methods: {
5
5
+
plc: new PlcDidDocumentResolver(),
6
6
+
web: new WebDidDocumentResolver(),
7
7
+
},
8
8
+
});
9
9
+
10
10
+
export async function resolveDid(did) {
11
11
+
let doc;
12
12
+
try {
13
13
+
doc = await docResolver.resolve(did);
14
14
+
} catch (err) {
15
15
+
throw err;
16
16
+
// if (err instanceof DocumentNotFoundError) {
17
17
+
// // did returned no document
18
18
+
// }
19
19
+
// if (err instanceof UnsupportedDidMethodError) {
20
20
+
// // resolver doesn't support did method (composite resolver)
21
21
+
// }
22
22
+
// if (err instanceof ImproperDidError) {
23
23
+
// // resolver considers did as invalid (atproto did:web)
24
24
+
// }
25
25
+
// if (err instanceof FailedDocumentResolutionError) {
26
26
+
// // document resolution had thrown something unexpected (fetch error)
27
27
+
// }
28
28
+
// if (err instanceof HandleResolutionError) {
29
29
+
// // the errors above extend this class, so you can do a catch-all.
30
30
+
// }
31
31
+
}
32
32
+
33
33
+
if (!(doc.alsoKnownAs && doc.alsoKnownAs.length >= 1)) {
34
34
+
console.error('questionable doc', doc);
35
35
+
throw new Error('doc missing aka');
36
36
+
}
37
37
+
38
38
+
const aka = doc.alsoKnownAs[0];
39
39
+
if (!aka.startsWith('at://')) {
40
40
+
console.error('questionable aka doesn\'t start with aka://', aka);
41
41
+
throw new Error('aka not an at-uri');
42
42
+
}
43
43
+
44
44
+
const handle = aka.slice('at://'.length);
45
45
+
if (handle.length === 0) {
46
46
+
console.error('empty handle? aka:', aka);
47
47
+
throw new Error('empty handle');
48
48
+
}
49
49
+
50
50
+
return handle;
51
51
+
}
+13
-1
atproto-notifications/src/service-worker.ts
···
1
1
+
import { resolveDid } from './atproto/resolve';
2
2
+
1
3
self.addEventListener('push', handlePush);
2
4
self.addEventListener('notificationclick', handleNotificationClick);
3
5
···
43
45
'app.bsky.feed.like:subject.uri': 'New like 💜',
44
46
}[source] ?? source;
45
47
48
48
+
let handle = 'unknown';
49
49
+
if (source_record.startsWith('at://')) {
50
50
+
const did = source_record.slice('at://'.length).split('/')[0];
51
51
+
try {
52
52
+
handle = await resolveDid(did);
53
53
+
} catch (err) {
54
54
+
console.error('failed to get handle', err);
55
55
+
}
56
56
+
}
57
57
+
46
58
// const tag = 'simple-push-demo-notification-tag';
47
59
// TODO: resubscribe to notifs to try to stay alive
48
60
···
67
79
68
80
const notification = self.registration.showNotification(title, {
69
81
icon,
70
70
-
body: source_record,
82
82
+
body: `from ${handle}`,
71
83
// actions: [
72
84
// {'action': 'bsky', title: 'Bluesky'},
73
85
// {'action': 'spacedust', title: 'All notifications'},
+20
-1
atproto-notifications/vite.config.ts
···
1
1
import { defineConfig } from 'vite'
2
2
+
import { join } from 'node:path';
3
3
+
import { buildSync } from 'esbuild';
2
4
import react from '@vitejs/plugin-react'
3
5
6
6
+
const buildServiceWorker = forProd => ({
7
7
+
apply: forProd ? 'build' : 'serve',
8
8
+
enforce: 'pre',
9
9
+
transformIndexHtml() {
10
10
+
buildSync({
11
11
+
minify: true,
12
12
+
bundle: true,
13
13
+
entryPoints: [join(process.cwd(), 'src', 'service-worker.ts')],
14
14
+
outfile: join(process.cwd(), forProd ? 'dist' : 'public', 'service-worker.js'),
15
15
+
});
16
16
+
},
17
17
+
});
18
18
+
4
19
// https://vite.dev/config/
5
20
export default defineConfig({
6
6
-
plugins: [react()],
21
21
+
plugins: [
22
22
+
buildServiceWorker(true),
23
23
+
buildServiceWorker(false),
24
24
+
react(),
25
25
+
],
7
26
})