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.

Handle PDSes that are not themselves authorization servers

+38 -18
+19 -9
atsw.js
··· 193 193 194 194 /** @param {string} handle */ 195 195 export async function resolveDID(handle) { 196 + // try to resolve DID using the DNS record 196 197 try { 197 198 const r = await fetch(`https://dns.google/resolve?name=_atproto.${handle}&type=TXT`); 198 199 const j = await r.json(); 199 200 const txt = j.Answer?.find(/** @param {any} a */ (a) => a.data?.startsWith('"did=')); 200 201 if (txt) return /** @type {string} */ (txt.data.replace(/"/g, "").replace("did=", "")); 201 202 } catch {} 203 + 202 204 // HTTP .well-known resolution is blocked by CORS from the browser, so fall 203 - // back to the public AppView which exposes a CORS-enabled resolver. 205 + // back to a public AppView which exposes a CORS-enabled resolver. 204 206 const r = await fetch(`https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${handle}`); 205 207 const j = await r.json(); 206 208 if (!j.did) throw new Error(`Could not resolve handle ${handle}: ${JSON.stringify(j)}`); ··· 209 211 210 212 /** @param {string} did */ 211 213 async function resolvePDS(did) { 214 + // find URL of DID doc 212 215 const url = did.startsWith("did:web:") 213 216 ? `https://${did.split(":")[2]}/.well-known/did.json` 214 217 : `https://plc.directory/${did}`; 215 - const doc = await (await fetch(url)).json(); 216 - const endpoint = doc.service?.find( 217 - /** @param {{type: string}} s */ (s) => s.type === "AtprotoPersonalDataServer", 218 - )?.serviceEndpoint; 218 + 219 + /** @type {{ service?: { type: string; serviceEndpoint: string }[] }} */ 220 + const doc = await fetch(url).then((res) => res.json()); 221 + 222 + // get service endpoint 223 + const endpoint = doc.service?.find(({ type }) => type === "AtprotoPersonalDataServer")?.serviceEndpoint; 219 224 if (!endpoint) throw new Error(`No PDS found for ${did}`); 220 - return /** @type {string} */ (endpoint); 225 + 226 + return endpoint; 221 227 } 222 228 223 229 /** ··· 225 231 * @returns {Promise<AuthServerMetadata>} 226 232 */ 227 233 async function discoverAuthServer(pds) { 228 - const res = await (await fetch(`${pds}/.well-known/oauth-protected-resource`)).json(); 229 - const issuer = /** @type {string} */ (res.authorization_servers[0]); 230 - return (await fetch(`${issuer}/.well-known/oauth-authorization-server`)).json(); 234 + try { 235 + const res = await fetch(`${pds}/.well-known/oauth-protected-resource`).then((res) => res.json()); 236 + const issuer = /** @type {string} */ (res.authorization_servers[0]); 237 + return await fetch(`${issuer}/.well-known/oauth-authorization-server`).then((res) => res.json()); 238 + } catch { 239 + return await fetch(`${pds}/.well-known/oauth-authorization-server`).then((res) => res.json()); 240 + } 231 241 } 232 242 233 243 /**
+19 -9
example/atsw.js
··· 193 193 194 194 /** @param {string} handle */ 195 195 export async function resolveDID(handle) { 196 + // try to resolve DID using the DNS record 196 197 try { 197 198 const r = await fetch(`https://dns.google/resolve?name=_atproto.${handle}&type=TXT`); 198 199 const j = await r.json(); 199 200 const txt = j.Answer?.find(/** @param {any} a */ (a) => a.data?.startsWith('"did=')); 200 201 if (txt) return /** @type {string} */ (txt.data.replace(/"/g, "").replace("did=", "")); 201 202 } catch {} 203 + 202 204 // HTTP .well-known resolution is blocked by CORS from the browser, so fall 203 - // back to the public AppView which exposes a CORS-enabled resolver. 205 + // back to a public AppView which exposes a CORS-enabled resolver. 204 206 const r = await fetch(`https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${handle}`); 205 207 const j = await r.json(); 206 208 if (!j.did) throw new Error(`Could not resolve handle ${handle}: ${JSON.stringify(j)}`); ··· 209 211 210 212 /** @param {string} did */ 211 213 async function resolvePDS(did) { 214 + // find URL of DID doc 212 215 const url = did.startsWith("did:web:") 213 216 ? `https://${did.split(":")[2]}/.well-known/did.json` 214 217 : `https://plc.directory/${did}`; 215 - const doc = await (await fetch(url)).json(); 216 - const endpoint = doc.service?.find( 217 - /** @param {{type: string}} s */ (s) => s.type === "AtprotoPersonalDataServer", 218 - )?.serviceEndpoint; 218 + 219 + /** @type {{ service?: { type: string; serviceEndpoint: string }[] }} */ 220 + const doc = await fetch(url).then((res) => res.json()); 221 + 222 + // get service endpoint 223 + const endpoint = doc.service?.find(({ type }) => type === "AtprotoPersonalDataServer")?.serviceEndpoint; 219 224 if (!endpoint) throw new Error(`No PDS found for ${did}`); 220 - return /** @type {string} */ (endpoint); 225 + 226 + return endpoint; 221 227 } 222 228 223 229 /** ··· 225 231 * @returns {Promise<AuthServerMetadata>} 226 232 */ 227 233 async function discoverAuthServer(pds) { 228 - const res = await (await fetch(`${pds}/.well-known/oauth-protected-resource`)).json(); 229 - const issuer = /** @type {string} */ (res.authorization_servers[0]); 230 - return (await fetch(`${issuer}/.well-known/oauth-authorization-server`)).json(); 234 + try { 235 + const res = await fetch(`${pds}/.well-known/oauth-protected-resource`).then((res) => res.json()); 236 + const issuer = /** @type {string} */ (res.authorization_servers[0]); 237 + return await fetch(`${issuer}/.well-known/oauth-authorization-server`).then((res) => res.json()); 238 + } catch { 239 + return await fetch(`${pds}/.well-known/oauth-authorization-server`).then((res) => res.json()); 240 + } 231 241 } 232 242 233 243 /**