tangled
alpha
login
or
join now
bad-example.com
/
spacedust-utils
6
fork
atom
demos for spacedust
6
fork
atom
overview
issues
pulls
pipelines
subscriptions almost working??
bad-example.com
8 months ago
e6200fec
fa9f7b13
+41
-18
2 changed files
expand all
collapse all
unified
split
atproto-notifications
src
App.tsx
server
index.js
+12
-4
atproto-notifications/src/App.tsx
···
11
11
</div>
12
12
);
13
13
14
14
-
function requestPermission(setAsking) {
14
14
+
function requestPermission(host, setAsking) {
15
15
return async () => {
16
16
setAsking(true);
17
17
let err;
18
18
try {
19
19
await Notification.requestPermission();
20
20
-
await subscribeToPush();
20
20
+
const sub = await subscribeToPush();
21
21
+
const res = await fetch(`${host}/subscribe`, {
22
22
+
method: 'POST',
23
23
+
headers: {'Content-Type': 'application/json'},
24
24
+
body: JSON.stringify({ sub }),
25
25
+
credentials: 'include',
26
26
+
});
27
27
+
if (!res.ok) throw res;
21
28
} catch (e) {
22
29
err = e;
23
30
}
24
31
setAsking(false);
25
32
if (err) throw err;
26
26
-
27
33
}
28
34
}
29
35
···
35
41
};
36
42
const pushSubscription = await registration.pushManager.subscribe(subscribeOptions);
37
43
console.log({ pushSubscription });
44
44
+
return pushSubscription;
38
45
}
39
46
40
47
async function verifyUser(host, token) {
···
42
49
method: 'POST',
43
50
headers: {'Content-Type': 'applicaiton/json'},
44
51
body: JSON.stringify({ token }),
52
52
+
credentials: 'include',
45
53
});
46
54
if (!res.ok) throw res;
47
55
}
···
92
100
<p>To show atproto notifications we need permission:</p>
93
101
<p>
94
102
<button
95
95
-
onClick={requestPermission(setAsking)}
103
103
+
onClick={requestPermission(host, setAsking)}
96
104
disabled={asking}
97
105
>
98
106
{asking ? <>Requesting…</> : <>Request permission</>}
+29
-14
server/index.js
···
10
10
11
11
const DUMMY_DID = 'did:plc:zzzzzzzzzzzzzzzzzzzzzzzz';
12
12
13
13
-
const CORS_PERMISSIVE = {
14
14
-
'Access-Control-Allow-Origin': '*',
13
13
+
const CORS_PERMISSIVE = req => ({
14
14
+
'Access-Control-Allow-Origin': req.headers.origin, // DANGERRRRR
15
15
'Access-Control-Allow-Methods': 'OPTIONS, GET, POST',
16
16
'Access-Control-Allow-Headers': 'Content-Type',
17
17
-
};
17
17
+
'Access-Control-Allow-Credentials': 'true', // TODO: *def* want to restrict allowed origin, probably
18
18
+
});
18
19
19
20
let spacedust;
20
21
let spacedustEverStarted = false;
···
166
167
const handleIndex = handleFile('index.html', 'text/html');
167
168
const handleServiceWorker = handleFile('service-worker.js', 'application/javascript');
168
169
169
169
-
const handleVerify = async (req, res, jwks, app_secret) => {
170
170
+
const handleVerify = async (req, res, jwks, appSecret) => {
170
171
const body = await getRequesBody(req);
171
172
const { token } = JSON.parse(body);
172
173
let did;
···
177
178
res.setHeader('Set-Cookie', cookie.serialize('verified-did', '', { expires: new Date(0) }));
178
179
return res.writeHead(400).end(JSON.stringify({ reason: 'verification failed' }));
179
180
}
180
180
-
const signed = cookieSig.sign(did, app_secret);
181
181
+
const signed = cookieSig.sign(did, appSecret);
181
182
res.setHeader('Set-Cookie', cookie.serialize('verified-did', signed, {
182
183
httpOnly: true,
183
184
secure: true,
···
186
187
return res.writeHead(200).end('okayyyy');
187
188
};
188
189
189
189
-
const handleSubscribe = async (req, res) => {
190
190
+
const handleSubscribe = async (req, res, appSecret) => {
191
191
+
const rawCookies = req.headers.cookie;
192
192
+
const cookies = cookie.parse(req.headers.cookie ?? '');
193
193
+
const untrusted = cookies['verified-did'] ?? '';
194
194
+
const did = cookieSig.unsign(untrusted, appSecret);
195
195
+
if (!did) {
196
196
+
res.setHeader('Set-Cookie', cookie.serialize('verified-did', '', { expires: new Date(0) }));
197
197
+
return res.writeHead(400).end(JSON.stringify({ reason: 'failed to verify cookie signature' }));
198
198
+
}
190
199
const body = await getRequesBody(req);
191
191
-
const { did, sub } = JSON.parse(body);
200
200
+
const { sub } = JSON.parse(body);
201
201
+
addSub('did:plc:z72i7hdynmk6r22z27h6tvur', sub); // DELETEME @bsky.app (DEBUG)
192
202
addSub(did, sub);
193
203
res.setHeader('Content-Type', 'application/json');
194
204
res.writeHead(201);
195
205
res.end('{"oh": "hi"}');
196
206
};
197
207
198
198
-
const requestListener = (pubkey, jwks, app_secret) => (req, res) => {
208
208
+
const requestListener = (pubkey, jwks, appSecret) => (req, res) => {
199
209
if (req.method === 'GET' && req.url === '/') {
200
210
return handleIndex(req, res, { PUBKEY: pubkey });
201
211
}
···
205
215
206
216
if (req.method === 'OPTIONS' && req.url === '/verify') {
207
217
// TODO: probably restrict the origin
208
208
-
return res.writeHead(204, CORS_PERMISSIVE).end();
218
218
+
return res.writeHead(204, CORS_PERMISSIVE(req)).end();
209
219
}
210
220
if (req.method === 'POST' && req.url === '/verify') {
211
211
-
res.setHeaders(new Headers(CORS_PERMISSIVE));
212
212
-
return handleVerify(req, res, jwks, app_secret);
221
221
+
res.setHeaders(new Headers(CORS_PERMISSIVE(req)));
222
222
+
return handleVerify(req, res, jwks, appSecret);
213
223
}
214
224
225
225
+
if (req.method === 'OPTIONS' && req.url === '/subscribe') {
226
226
+
// TODO: probably restrict the origin
227
227
+
return res.writeHead(204, CORS_PERMISSIVE(req)).end();
228
228
+
}
215
229
if (req.method === 'POST' && req.url === '/subscribe') {
216
216
-
return handleSubscribe(req, res);
230
230
+
res.setHeaders(new Headers(CORS_PERMISSIVE(req)));
231
231
+
return handleSubscribe(req, res, appSecret);
217
232
}
218
233
219
234
res.writeHead(200);
···
230
245
);
231
246
232
247
if (!env.APP_SECRET) throw new Error('APP_SECRET is required to run');
233
233
-
const app_secret = env.APP_SECRET;
248
248
+
const appSecret = env.APP_SECRET;
234
249
235
250
const whoamiHost = env.WHOAMI_HOST ?? 'https://who-am-i.microcosm.blue';
236
251
const jwks = jose.createRemoteJWKSet(new URL(`${whoamiHost}/.well-known/jwks.json`));
···
242
257
const port = parseInt(env.PORT ?? 8000, 10);
243
258
244
259
http
245
245
-
.createServer(requestListener(keys.publicKey, jwks, app_secret))
260
260
+
.createServer(requestListener(keys.publicKey, jwks, appSecret))
246
261
.listen(port, host, () => console.log(`listening at http://${host}:${port}`));
247
262
};
248
263