diff --git a/atproto-notifications/src/App.tsx b/atproto-notifications/src/App.tsx index 3e4faa6..73f5fef 100644 --- a/atproto-notifications/src/App.tsx +++ b/atproto-notifications/src/App.tsx @@ -14,7 +14,7 @@ const Problem = ({ children }) => ( ); -function requestPermission(host, setAsking) { +function requestPermission(host, setAsking, setPermissionError) { return async () => { setAsking(true); let err; @@ -27,8 +27,17 @@ function requestPermission(host, setAsking) { body: JSON.stringify({ sub }), credentials: 'include', }); - if (!res.ok) throw res; + if (!res.ok) { + let content; + try { + content = (await res.json()).reason; + } catch (_) { + content = await res.text(); + } + throw content; + } } catch (e) { + setPermissionError(e); err = e; } setAsking(false); @@ -68,6 +77,7 @@ function App() { const [user, setUser] = useLocalStorage('spacedust-notif-user', null); const [verif, setVerif] = useState(null); const [asking, setAsking] = useState(false); + const [permissionError, setPermissionError] = useState(null); const onIdentify = useCallback(async details => { setVerif('verifying'); @@ -102,14 +112,14 @@ function App() { content = <>
Sorry, failed to verify that identity. please let us know!
{content}>; } } - } else if (notifPerm !== 'granted') { + } else if (permissionError !== null || notifPerm !== 'granted') { content = ( <>To show notifications we need permission:
{notifPerm === 'denied' ? (
Notification permission was denied. You may need to clear the browser setting to try again.
- ) : ( -You can revoke this any time
- )} + ) : permissionError ? ( +Sorry, something went wrong: {permissionError}
+ ) : ( +You can revoke this any time
+ ) + } > ); } else { diff --git a/server/db.js b/server/db.js index 79dae8d..15edeb9 100644 --- a/server/db.js +++ b/server/db.js @@ -37,7 +37,8 @@ export class DB { this.#stmt_insert_account = db.prepare( `insert into accounts (did) - values (?)`); + values (?) + on conflict do nothing`); this.#stmt_get_account = db.prepare( `select a.first_seen, @@ -53,7 +54,9 @@ export class DB { this.#stmt_insert_push_sub = db.prepare( `insert into push_subs (account_did, session, subscription) - values (?, ?, ?)`); + values (?, ?, ?) + on conflict do update + set subscription = excluded.subscription`); this.#stmt_get_all_sub_dids = db.prepare( `select distinct account_did diff --git a/server/index.js b/server/index.js index 019e056..498c144 100755 --- a/server/index.js +++ b/server/index.js @@ -225,11 +225,21 @@ const handleVerify = async (db, req, res, jwks, appSecret) => { return res.writeHead(200).end('okayyyy'); }; -const handleSubscribe = async (db, req, res, appSecret) => { +const handleSubscribe = async (db, req, res, appSecret, adminDid) => { let info = getAccountCookie(req, res, appSecret); if (!info) return res.writeHead(400).end(JSON.stringify({ reason: 'failed to verify cookie signature' })); const [did, session] = info; + // not yet public!! + if (did !== adminDid) { + res.setHeader('Content-Type', 'application/json'); + res.writeHead(403); + + return clearAccountCookie(res).end(JSON.stringify({ + reason: 'the spacedust notifications demo isn\'t public yet!', + })); + } + const body = await getRequesBody(req); const { sub } = JSON.parse(body); // addSub('did:plc:z72i7hdynmk6r22z27h6tvur', sub); // DELETEME @bsky.app (DEBUG) @@ -240,7 +250,7 @@ const handleSubscribe = async (db, req, res, appSecret) => { res.end('{"oh": "hi"}'); }; -const requestListener = (secrets, jwks, db) => (req, res) => { +const requestListener = (secrets, jwks, db, adminDid) => (req, res) => { if (req.method === 'GET' && req.url === '/') { return handleIndex(req, res, { PUBKEY: secrets.pushKeys.publicKey }); } @@ -263,7 +273,7 @@ const requestListener = (secrets, jwks, db) => (req, res) => { } if (req.method === 'POST' && req.url === '/subscribe') { res.setHeaders(new Headers(CORS_PERMISSIVE(req))); - return handleSubscribe(db, req, res, secrets.appSecret); + return handleSubscribe(db, req, res, secrets.appSecret, adminDid); } res.writeHead(200); @@ -271,6 +281,9 @@ const requestListener = (secrets, jwks, db) => (req, res) => { } const main = env => { + if (!env.ADMIN_DID) throw new Error('ADMIN_DID is required to run'); + const adminDid = env.ADMIN_DID; + if (!env.SECRETS_FILE) throw new Error('SECRETS_FILE is required to run'); const secrets = getOrCreateSecrets(env.SECRETS_FILE); webpush.setVapidDetails( @@ -294,7 +307,7 @@ const main = env => { const port = parseInt(env.PORT ?? 8000, 10); http - .createServer(requestListener(secrets, jwks, db)) + .createServer(requestListener(secrets, jwks, db, adminDid)) .listen(port, host, () => console.log(`listening at http://${host}:${port}`)); };