Monorepo for Tangled tangled.org

avatar: fetch tangled avatar before falling back to bsky & random color #893

merged opened by anirudh.fi targeting master from icy/tolqpt
Labels

None yet.

assignee

None yet.

Participants 2
AT URI
at://did:plc:hwevmowznbiukdf6uk5dwrrq/sh.tangled.repo.pull/3m7znwnzpyn22
+149 -137
Interdiff #2 #3
+70 -94
avatar/package-lock.json
··· 8 "name": "avatar", 9 "version": "0.0.0", 10 "dependencies": { 11 - "@atproto/identity": "^0.4.1" 12 }, 13 "devDependencies": { 14 "@cloudflare/vitest-pool-workers": "^0.8.19", ··· 16 "wrangler": "^4.14.1" 17 } 18 }, 19 - "node_modules/@atproto/common-web": { 20 - "version": "0.4.7", 21 - "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.7.tgz", 22 - "integrity": "sha512-vjw2+81KPo2/SAbbARGn64Ln+6JTI0FTI4xk8if0ebBfDxFRmHb2oSN1y77hzNq/ybGHqA2mecfhS03pxC5+lg==", 23 - "license": "MIT", 24 "dependencies": { 25 - "@atproto/lex-data": "0.0.3", 26 - "@atproto/lex-json": "0.0.3", 27 - "zod": "^3.23.8" 28 } 29 }, 30 - "node_modules/@atproto/crypto": { 31 - "version": "0.4.5", 32 - "resolved": "https://registry.npmjs.org/@atproto/crypto/-/crypto-0.4.5.tgz", 33 - "integrity": "sha512-n40aKkMoCatP0u9Yvhrdk6fXyOHFDDbkdm4h4HCyWW+KlKl8iXfD5iV+ECq+w5BM+QH25aIpt3/j6EUNerhLxw==", 34 - "license": "MIT", 35 "dependencies": { 36 - "@noble/curves": "^1.7.0", 37 - "@noble/hashes": "^1.6.1", 38 - "uint8arrays": "3.0.0" 39 }, 40 - "engines": { 41 - "node": ">=18.7.0" 42 } 43 }, 44 - "node_modules/@atproto/identity": { 45 - "version": "0.4.10", 46 - "resolved": "https://registry.npmjs.org/@atproto/identity/-/identity-0.4.10.tgz", 47 - "integrity": "sha512-nQbzDLXOhM8p/wo0cTh5DfMSOSHzj6jizpodX37LJ4S1TZzumSxAjHEZa5Rev3JaoD5uSWMVE0MmKEGWkPPvfQ==", 48 - "license": "MIT", 49 "dependencies": { 50 - "@atproto/common-web": "^0.4.4", 51 - "@atproto/crypto": "^0.4.4" 52 - }, 53 - "engines": { 54 - "node": ">=18.7.0" 55 } 56 }, 57 - "node_modules/@atproto/lex-data": { 58 - "version": "0.0.3", 59 - "resolved": "https://registry.npmjs.org/@atproto/lex-data/-/lex-data-0.0.3.tgz", 60 - "integrity": "sha512-ivo1IpY/EX+RIpxPgCf4cPhQo5bfu4nrpa1vJCt8hCm9SfoonJkDFGa0n4SMw4JnXZoUcGcrJ46L+D8bH6GI2g==", 61 - "license": "MIT", 62 "dependencies": { 63 - "@atproto/syntax": "0.4.2", 64 - "multiformats": "^9.9.0", 65 - "tslib": "^2.8.1", 66 - "uint8arrays": "3.0.0", 67 - "unicode-segmenter": "^0.14.0" 68 } 69 }, 70 - "node_modules/@atproto/lex-json": { 71 - "version": "0.0.3", 72 - "resolved": "https://registry.npmjs.org/@atproto/lex-json/-/lex-json-0.0.3.tgz", 73 - "integrity": "sha512-ZVcY7XlRfdPYvQQ2WroKUepee0+NCovrSXgXURM3Xv+n5jflJCoczguROeRr8sN0xvT0ZbzMrDNHCUYKNnxcjw==", 74 - "license": "MIT", 75 "dependencies": { 76 - "@atproto/lex-data": "0.0.3", 77 - "tslib": "^2.8.1" 78 } 79 }, 80 - "node_modules/@atproto/syntax": { 81 - "version": "0.4.2", 82 - "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz", 83 - "integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==", 84 - "license": "MIT" 85 }, 86 "node_modules/@cloudflare/kv-asset-handler": { 87 "version": "0.4.0", ··· 1085 "@jridgewell/sourcemap-codec": "^1.4.10" 1086 } 1087 }, 1088 - "node_modules/@noble/curves": { 1089 - "version": "1.9.7", 1090 - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", 1091 - "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", 1092 - "license": "MIT", 1093 - "dependencies": { 1094 - "@noble/hashes": "1.8.0" 1095 - }, 1096 - "engines": { 1097 - "node": "^14.21.3 || >=16" 1098 - }, 1099 - "funding": { 1100 - "url": "https://paulmillr.com/funding/" 1101 - } 1102 - }, 1103 - "node_modules/@noble/hashes": { 1104 - "version": "1.8.0", 1105 - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", 1106 - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", 1107 - "license": "MIT", 1108 - "engines": { 1109 - "node": "^14.21.3 || >=16" 1110 - }, 1111 - "funding": { 1112 - "url": "https://paulmillr.com/funding/" 1113 - } 1114 - }, 1115 "node_modules/@rollup/rollup-android-arm-eabi": { 1116 "version": "4.40.1", 1117 "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz", ··· 1392 "win32" 1393 ] 1394 }, 1395 "node_modules/@types/estree": { 1396 "version": "1.0.7", 1397 "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", ··· 1809 "@esbuild/win32-x64": "0.25.3" 1810 } 1811 }, 1812 "node_modules/estree-walker": { 1813 "version": "3.0.3", 1814 "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", ··· 1978 "dev": true, 1979 "license": "MIT" 1980 }, 1981 - "node_modules/multiformats": { 1982 - "version": "9.9.0", 1983 - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", 1984 - "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", 1985 - "license": "(Apache-2.0 AND MIT)" 1986 - }, 1987 "node_modules/mustache": { 1988 "version": "4.2.0", 1989 "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", ··· 2333 "version": "2.8.1", 2334 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 2335 "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 2336 - "license": "0BSD" 2337 }, 2338 "node_modules/ufo": { 2339 "version": "1.6.1", ··· 2342 "dev": true, 2343 "license": "MIT" 2344 }, 2345 - "node_modules/uint8arrays": { 2346 - "version": "3.0.0", 2347 - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz", 2348 - "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", 2349 - "license": "MIT", 2350 - "dependencies": { 2351 - "multiformats": "^9.4.2" 2352 - } 2353 - }, 2354 "node_modules/undici": { 2355 "version": "5.29.0", 2356 "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", ··· 3130 "version": "3.24.3", 3131 "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", 3132 "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", 3133 "license": "MIT", 3134 "funding": { 3135 "url": "https://github.com/sponsors/colinhacks"
··· 8 "name": "avatar", 9 "version": "0.0.0", 10 "dependencies": { 11 + "@atcute/identity-resolver": "^1.2.2" 12 }, 13 "devDependencies": { 14 "@cloudflare/vitest-pool-workers": "^0.8.19", ··· 16 "wrangler": "^4.14.1" 17 } 18 }, 19 + "node_modules/@atcute/identity": { 20 + "version": "1.1.3", 21 + "resolved": "https://registry.npmjs.org/@atcute/identity/-/identity-1.1.3.tgz", 22 + "integrity": "sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng==", 23 + "license": "0BSD", 24 + "peer": true, 25 "dependencies": { 26 + "@atcute/lexicons": "^1.2.4", 27 + "@badrap/valita": "^0.4.6" 28 } 29 }, 30 + "node_modules/@atcute/identity-resolver": { 31 + "version": "1.2.2", 32 + "resolved": "https://registry.npmjs.org/@atcute/identity-resolver/-/identity-resolver-1.2.2.tgz", 33 + "integrity": "sha512-eUh/UH4bFvuXS0X7epYCeJC/kj4rbBXfSRumLEH4smMVwNOgTo7cL/0Srty+P/qVPoZEyXdfEbS0PHJyzoXmHw==", 34 + "license": "0BSD", 35 "dependencies": { 36 + "@atcute/lexicons": "^1.2.6", 37 + "@atcute/util-fetch": "^1.0.5", 38 + "@badrap/valita": "^0.4.6" 39 }, 40 + "peerDependencies": { 41 + "@atcute/identity": "^1.0.0" 42 } 43 }, 44 + "node_modules/@atcute/lexicons": { 45 + "version": "1.2.6", 46 + "resolved": "https://registry.npmjs.org/@atcute/lexicons/-/lexicons-1.2.6.tgz", 47 + "integrity": "sha512-s76UQd8D+XmHIzrjD9CJ9SOOeeLPHc+sMmcj7UFakAW/dDFXc579fcRdRfuUKvXBL5v1Gs2VgDdlh/IvvQZAwA==", 48 + "license": "0BSD", 49 "dependencies": { 50 + "@atcute/uint8array": "^1.0.6", 51 + "@atcute/util-text": "^0.0.1", 52 + "@standard-schema/spec": "^1.1.0", 53 + "esm-env": "^1.2.2" 54 } 55 }, 56 + "node_modules/@atcute/uint8array": { 57 + "version": "1.0.6", 58 + "resolved": "https://registry.npmjs.org/@atcute/uint8array/-/uint8array-1.0.6.tgz", 59 + "integrity": "sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A==", 60 + "license": "0BSD" 61 + }, 62 + "node_modules/@atcute/util-fetch": { 63 + "version": "1.0.5", 64 + "resolved": "https://registry.npmjs.org/@atcute/util-fetch/-/util-fetch-1.0.5.tgz", 65 + "integrity": "sha512-qjHj01BGxjSjIFdPiAjSARnodJIIyKxnCMMEcXMESo9TAyND6XZQqrie5fia+LlYWVXdpsTds8uFQwc9jdKTig==", 66 + "license": "0BSD", 67 "dependencies": { 68 + "@badrap/valita": "^0.4.6" 69 } 70 }, 71 + "node_modules/@atcute/util-text": { 72 + "version": "0.0.1", 73 + "resolved": "https://registry.npmjs.org/@atcute/util-text/-/util-text-0.0.1.tgz", 74 + "integrity": "sha512-t1KZqvn0AYy+h2KcJyHnKF9aEqfRfMUmyY8j1ELtAEIgqN9CxINAjxnoRCJIFUlvWzb+oY3uElQL/Vyk3yss0g==", 75 + "license": "0BSD", 76 "dependencies": { 77 + "unicode-segmenter": "^0.14.4" 78 } 79 }, 80 + "node_modules/@badrap/valita": { 81 + "version": "0.4.6", 82 + "resolved": "https://registry.npmjs.org/@badrap/valita/-/valita-0.4.6.tgz", 83 + "integrity": "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==", 84 + "license": "MIT", 85 + "engines": { 86 + "node": ">= 18" 87 + } 88 }, 89 "node_modules/@cloudflare/kv-asset-handler": { 90 "version": "0.4.0", ··· 1088 "@jridgewell/sourcemap-codec": "^1.4.10" 1089 } 1090 }, 1091 "node_modules/@rollup/rollup-android-arm-eabi": { 1092 "version": "4.40.1", 1093 "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz", ··· 1368 "win32" 1369 ] 1370 }, 1371 + "node_modules/@standard-schema/spec": { 1372 + "version": "1.1.0", 1373 + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", 1374 + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", 1375 + "license": "MIT" 1376 + }, 1377 "node_modules/@types/estree": { 1378 "version": "1.0.7", 1379 "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", ··· 1791 "@esbuild/win32-x64": "0.25.3" 1792 } 1793 }, 1794 + "node_modules/esm-env": { 1795 + "version": "1.2.2", 1796 + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", 1797 + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", 1798 + "license": "MIT" 1799 + }, 1800 "node_modules/estree-walker": { 1801 "version": "3.0.3", 1802 "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", ··· 1966 "dev": true, 1967 "license": "MIT" 1968 }, 1969 "node_modules/mustache": { 1970 "version": "4.2.0", 1971 "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", ··· 2315 "version": "2.8.1", 2316 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 2317 "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 2318 + "dev": true, 2319 + "license": "0BSD", 2320 + "optional": true 2321 }, 2322 "node_modules/ufo": { 2323 "version": "1.6.1", ··· 2326 "dev": true, 2327 "license": "MIT" 2328 }, 2329 "node_modules/undici": { 2330 "version": "5.29.0", 2331 "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", ··· 3105 "version": "3.24.3", 3106 "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", 3107 "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", 3108 + "dev": true, 3109 "license": "MIT", 3110 "funding": { 3111 "url": "https://github.com/sponsors/colinhacks"
+2 -1
avatar/package.json
··· 2 "name": "avatar", 3 "version": "0.0.0", 4 "private": true, 5 "scripts": { 6 "deploy": "wrangler deploy", 7 "dev": "wrangler dev", ··· 9 "test": "vitest" 10 }, 11 "dependencies": { 12 - "@atproto/identity": "^0.4.1" 13 }, 14 "devDependencies": { 15 "@cloudflare/vitest-pool-workers": "^0.8.19",
··· 2 "name": "avatar", 3 "version": "0.0.0", 4 "private": true, 5 + "type": "module", 6 "scripts": { 7 "deploy": "wrangler deploy", 8 "dev": "wrangler dev", ··· 10 "test": "vitest" 11 }, 12 "dependencies": { 13 + "@atcute/identity-resolver": "^1.2.2" 14 }, 15 "devDependencies": { 16 "@cloudflare/vitest-pool-workers": "^0.8.19",
+64 -29
avatar/src/index.js
··· 1 - import { IdResolver } from "@atproto/identity"; 2 3 export default { 4 async fetch(request, env) { 5 // Helper function to generate a color from a string ··· 17 }; 18 19 // Helper function to fetch Tangled profile from PDS 20 - const getTangledAvatarFromPDS = async (actor, resolver) => { 21 try { 22 // Resolve the identity 23 - const identity = await resolver.resolve(actor); 24 if (!identity) { 25 console.log({ 26 level: "debug", ··· 31 } 32 33 const did = identity.did; 34 - const pdsEndpoint = identity.pdsUrl; 35 36 if (!pdsEndpoint) { 37 console.log({ ··· 43 return null; 44 } 45 46 - console.log({ 47 - level: "debug", 48 - message: "fetching Tangled profile from PDS", 49 - actor: actor, 50 - did: did, 51 - pdsEndpoint: pdsEndpoint, 52 - }); 53 54 // Fetch the Tangled profile record from PDS 55 - const profileResponse = await fetch( 56 - `${pdsEndpoint}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=org.tangled.actor.profile&rkey=self`, 57 - ); 58 59 if (!profileResponse.ok) { 60 console.log({ ··· 67 } 68 69 const profileData = await profileResponse.json(); 70 - const avatarCID = profileData?.value?.avatar; 71 72 - if (!avatarCID) { 73 console.log({ 74 level: "debug", 75 - message: "Tangled profile has no avatar CID", 76 actor: actor, 77 }); 78 return null; 79 } 80 81 - // Construct blob URL 82 - const blobUrl = `${pdsEndpoint}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${avatarCID}`; 83 84 - console.log({ 85 - level: "debug", 86 - message: "found Tangled avatar", 87 - actor: actor, 88 - avatarCID: avatarCID, 89 - }); 90 91 return blobUrl; 92 } catch (e) { 93 console.log({ ··· 158 try { 159 let avatarUrl = null; 160 161 - // Create identity resolver 162 - const resolver = new IdResolver(); 163 - 164 // Try to get Tangled avatar from user's PDS first 165 - avatarUrl = await getTangledAvatarFromPDS(actor, resolver); 166 167 // If no Tangled avatar, fall back to Bluesky 168 if (!avatarUrl) {
··· 1 + import { 2 + LocalActorResolver, 3 + CompositeHandleResolver, 4 + DohJsonHandleResolver, 5 + WellKnownHandleResolver, 6 + CompositeDidDocumentResolver, 7 + PlcDidDocumentResolver, 8 + WebDidDocumentResolver, 9 + } from "@atcute/identity-resolver"; 10 11 + // Initialize resolvers for Cloudflare Workers 12 + const handleResolver = new CompositeHandleResolver({ 13 + strategy: "race", 14 + methods: { 15 + dns: new DohJsonHandleResolver({ 16 + dohUrl: "https://cloudflare-dns.com/dns-query", 17 + }), 18 + http: new WellKnownHandleResolver(), 19 + }, 20 + }); 21 + 22 + const didDocumentResolver = new CompositeDidDocumentResolver({ 23 + methods: { 24 + plc: new PlcDidDocumentResolver(), 25 + web: new WebDidDocumentResolver(), 26 + }, 27 + }); 28 + 29 + const actorResolver = new LocalActorResolver({ 30 + handleResolver, 31 + didDocumentResolver, 32 + }); 33 + 34 export default { 35 async fetch(request, env) { 36 // Helper function to generate a color from a string ··· 48 }; 49 50 // Helper function to fetch Tangled profile from PDS 51 + const getTangledAvatarFromPDS = async (actor) => { 52 try { 53 // Resolve the identity 54 + const identity = await actorResolver.resolve(actor); 55 if (!identity) { 56 console.log({ 57 level: "debug", ··· 62 } 63 64 const did = identity.did; 65 + const pdsEndpoint = identity.pds.replace(/\/$/, ""); // Remove trailing slash 66 67 if (!pdsEndpoint) { 68 console.log({ ··· 74 return null; 75 } 76 77 + const profileUrl = `${pdsEndpoint}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=sh.tangled.actor.profile&rkey=self`; 78 79 // Fetch the Tangled profile record from PDS 80 + const profileResponse = await fetch(profileUrl); 81 82 if (!profileResponse.ok) { 83 console.log({ ··· 90 } 91 92 const profileData = await profileResponse.json(); 93 + const avatarBlob = profileData?.value?.avatar; 94 95 + if (!avatarBlob) { 96 console.log({ 97 level: "debug", 98 + message: "Tangled profile has no avatar", 99 actor: actor, 100 }); 101 return null; 102 } 103 104 + // Extract CID from blob reference object 105 + // The ref might be an object with $link property or a string 106 + let avatarCID; 107 + if (typeof avatarBlob.ref === "string") { 108 + avatarCID = avatarBlob.ref; 109 + } else if (avatarBlob.ref?.$link) { 110 + avatarCID = avatarBlob.ref.$link; 111 + } else if (typeof avatarBlob === "string") { 112 + avatarCID = avatarBlob; 113 + } 114 115 + if (!avatarCID || typeof avatarCID !== "string") { 116 + console.log({ 117 + level: "warn", 118 + message: "could not extract valid CID from avatar blob", 119 + actor: actor, 120 + avatarBlob: avatarBlob, 121 + avatarBlobRef: avatarBlob.ref, 122 + }); 123 + return null; 124 + } 125 126 + // Construct blob URL (pdsEndpoint already has trailing slash removed) 127 + const blobUrl = `${pdsEndpoint}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${avatarCID}`; 128 + 129 return blobUrl; 130 } catch (e) { 131 console.log({ ··· 196 try { 197 let avatarUrl = null; 198 199 // Try to get Tangled avatar from user's PDS first 200 + avatarUrl = await getTangledAvatarFromPDS(actor); 201 202 // If no Tangled avatar, fall back to Bluesky 203 if (!avatarUrl) {
+13 -13
avatar/wrangler.jsonc
··· 1 { 2 - "$schema": "node_modules/wrangler/config-schema.json", 3 - "name": "avatar", 4 - "main": "src/index.js", 5 - "compatibility_date": "2025-05-03", 6 - "observability": { 7 - "enabled": true, 8 - }, 9 - "routes": [ 10 - { 11 - "pattern": "avatar.tangled.sh", 12 - "custom_domain": true, 13 - }, 14 - ], 15 }
··· 1 { 2 + "$schema": "node_modules/wrangler/config-schema.json", 3 + "name": "avatar", 4 + "main": "src/index.js", 5 + "compatibility_date": "2025-05-03", 6 + "observability": { 7 + "enabled": true, 8 + }, 9 + "routes": [ 10 + { 11 + "pattern": "avatar.tangled.sh", 12 + "custom_domain": true, 13 + }, 14 + ], 15 }

History

7 rounds 2 comments
sign up or login to add to the discussion
1 commit
expand
avatar: fetch tangled avatar before falling back to bsky & random color
expand 0 comments
pull request successfully merged
1 commit
expand
avatar: fetch tangled avatar before falling back to bsky & random color
expand 0 comments
1 commit
expand
avatar: fetch tangled avatar before falling back to bsky & random color
expand 0 comments
1 commit
expand
avatar: fetch tangled avatar before falling back to bsky & random color
expand 0 comments
1 commit
expand
avatar: fetch tangled avatar before falling back to bsky & random color
expand 0 comments
1 commit
expand
avatar: fetch tangled avatar before falling back to bsky & random color
expand 2 comments

why do we do this (just out of curiosity): !!blob:

  • we can cast using Boolean(blobUrl)
  • or be more explicit: blobUrl != null && blobUrl != ""

Didn't know about Boolean(blobUrl)! (also this was Claude)

1 commit
expand
avatar: fetch tangled avatar before falling back to bsky & random color
expand 0 comments