a tiny oauth browser client for atproto using a service worker
11
fork

Configure Feed

Select the types of activity you want to include in your feed.

Clean up more vibe code

+25 -37
+12 -18
atsw.js
··· 51 51 * @property {string} pushed_authorization_request_endpoint 52 52 */ 53 53 54 - // --- crypto helpers --- 55 - 56 54 const enc = new TextEncoder(); 57 55 58 56 /** @param {ArrayBuffer | Uint8Array} buf */ ··· 110 108 return toSign + "." + b64url(sig); 111 109 } 112 110 111 + const MAX_DPOP_RETRIES = 2; 112 + 113 113 /** 114 - * @param {DPoPKey} dpopKey 114 + * @param {DPoPKey} key 115 115 * @param {string} url 116 116 * @param {URLSearchParams} body 117 117 * @param {string} [nonce] 118 118 * @returns {Promise<{ json: any, dpopNonce: string | undefined }>} 119 119 */ 120 - async function dpopPost(dpopKey, url, body, nonce) { 120 + async function dpopPost(key, url, body, nonce) { 121 121 let dpopNonce = nonce; 122 - for (let attempts = 0; attempts < 2; attempts++) { 123 - const dpop = await createDPoP(dpopKey, "POST", url, dpopNonce); 122 + for (let attempts = 0; attempts < MAX_DPOP_RETRIES; attempts++) { 123 + const dpop = await createDPoP(key, "POST", url, dpopNonce); 124 124 const res = await fetch(url, { 125 125 method: "POST", 126 126 headers: { "content-type": "application/x-www-form-urlencoded", DPoP: dpop }, ··· 420 420 const htm = req.method; 421 421 const ath = b64url(await crypto.subtle.digest("SHA-256", enc.encode(session.access_token))); 422 422 423 - async function signedFetch() { 423 + let res = new Response(); 424 + for (let attempt = 0; attempt < MAX_DPOP_RETRIES; attempt++) { 424 425 const dpop = await createDPoP(session.dpopKey, htm, htu, session.dpopNonce, ath); 426 + 425 427 const headers = new Headers(req.headers); 426 428 headers.set("authorization", `DPoP ${session.access_token}`); 427 429 headers.set("dpop", dpop); 428 - return fetch(new Request(req.clone(), { headers })); 429 - } 430 430 431 - let res = await signedFetch(); 432 - const nonce = res.headers.get("dpop-nonce"); 433 - if (nonce) { 434 - session.dpopNonce = nonce; 435 - await putSession(session); 436 - } 437 - 438 - if (res.status === 401 && session.dpopNonce) { 439 - res = await signedFetch(); 431 + res = await fetch(new Request(req.clone(), { headers })); 440 432 const nonce = res.headers.get("dpop-nonce"); 441 433 if (nonce) { 442 434 session.dpopNonce = nonce; 443 435 await putSession(session); 444 436 } 437 + 438 + if (res.status !== 401 || !session.dpopNonce) break; 445 439 } 446 440 447 441 return res;
+12 -18
example/atsw.js
··· 51 51 * @property {string} pushed_authorization_request_endpoint 52 52 */ 53 53 54 - // --- crypto helpers --- 55 - 56 54 const enc = new TextEncoder(); 57 55 58 56 /** @param {ArrayBuffer | Uint8Array} buf */ ··· 110 108 return toSign + "." + b64url(sig); 111 109 } 112 110 111 + const MAX_DPOP_RETRIES = 2; 112 + 113 113 /** 114 - * @param {DPoPKey} dpopKey 114 + * @param {DPoPKey} key 115 115 * @param {string} url 116 116 * @param {URLSearchParams} body 117 117 * @param {string} [nonce] 118 118 * @returns {Promise<{ json: any, dpopNonce: string | undefined }>} 119 119 */ 120 - async function dpopPost(dpopKey, url, body, nonce) { 120 + async function dpopPost(key, url, body, nonce) { 121 121 let dpopNonce = nonce; 122 - for (let attempts = 0; attempts < 2; attempts++) { 123 - const dpop = await createDPoP(dpopKey, "POST", url, dpopNonce); 122 + for (let attempts = 0; attempts < MAX_DPOP_RETRIES; attempts++) { 123 + const dpop = await createDPoP(key, "POST", url, dpopNonce); 124 124 const res = await fetch(url, { 125 125 method: "POST", 126 126 headers: { "content-type": "application/x-www-form-urlencoded", DPoP: dpop }, ··· 420 420 const htm = req.method; 421 421 const ath = b64url(await crypto.subtle.digest("SHA-256", enc.encode(session.access_token))); 422 422 423 - async function signedFetch() { 423 + let res = new Response(); 424 + for (let attempt = 0; attempt < MAX_DPOP_RETRIES; attempt++) { 424 425 const dpop = await createDPoP(session.dpopKey, htm, htu, session.dpopNonce, ath); 426 + 425 427 const headers = new Headers(req.headers); 426 428 headers.set("authorization", `DPoP ${session.access_token}`); 427 429 headers.set("dpop", dpop); 428 - return fetch(new Request(req.clone(), { headers })); 429 - } 430 430 431 - let res = await signedFetch(); 432 - const nonce = res.headers.get("dpop-nonce"); 433 - if (nonce) { 434 - session.dpopNonce = nonce; 435 - await putSession(session); 436 - } 437 - 438 - if (res.status === 401 && session.dpopNonce) { 439 - res = await signedFetch(); 431 + res = await fetch(new Request(req.clone(), { headers })); 440 432 const nonce = res.headers.get("dpop-nonce"); 441 433 if (nonce) { 442 434 session.dpopNonce = nonce; 443 435 await putSession(session); 444 436 } 437 + 438 + if (res.status !== 401 || !session.dpopNonce) break; 445 439 } 446 440 447 441 return res;
+1 -1
example/client-metadata.json
··· 7 7 "dpop_bound_access_tokens": true, 8 8 "grant_types": ["authorization_code", "refresh_token"], 9 9 "response_types": ["code"], 10 - "scope": "atproto repo?collection=app.bsky.feed.post", 10 + "scope": "atproto repo?collection=com.atproto.server.getSession", 11 11 "token_endpoint_auth_method": "none" 12 12 }