Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place
87
fork

Configure Feed

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

import napi on runtime and be optional dep for ci

+56 -20
+3
build-cli.sh
··· 20 20 bun build --compile --minify --target=bun-linux-x64 "$ENTRY" --outfile "$OUTDIR/wisp-cli-x86_64-linux" 21 21 echo " ✓ x86_64-linux" 22 22 23 + bun build --compile --minify --target=bun-windows-x64 "$ENTRY" --outfile "$OUTDIR/wisp-cli-x86_64-windows.exe" 24 + echo " ✓ x86_64-windows" 25 + 23 26 lipo -create -output "$OUTDIR/wisp-cli-darwin-universal" \ 24 27 "$OUTDIR/wisp-cli-aarch64-darwin" \ 25 28 "$OUTDIR/wisp-cli-x86_64-darwin"
+8 -5
bun.lock
··· 5 5 "": { 6 6 "name": "@wisp/monorepo", 7 7 "dependencies": { 8 - "@napi-rs/keyring": "^1.2.0", 9 8 "@tailwindcss/cli": "^4.1.17", 10 9 "bun-plugin-tailwind": "^0.1.2", 11 10 "node-html-parser": "^7.1.0", ··· 166 165 "bin": { 167 166 "wispctl": "dist/index.js", 168 167 }, 169 - "dependencies": { 170 - "@napi-rs/keyring": "^1.2.0", 171 - }, 172 168 "devDependencies": { 173 169 "@atproto/api": "^0.18.17", 174 170 "@atproto/identity": "^0.4.10", ··· 198 194 "open": "^11.0.0", 199 195 "picocolors": "^1.1.1", 200 196 "typescript": "^5", 197 + }, 198 + "optionalDependencies": { 199 + "@napi-rs/keyring": "^1.2.0", 201 200 }, 202 201 }, 203 202 "packages/@wispplace/atproto-utils": { ··· 2167 2166 2168 2167 "wisp-hosting-service/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], 2169 2168 2170 - "wispctl/@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="], 2169 + "wispctl/@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], 2171 2170 2172 2171 "wispctl/@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="], 2173 2172 ··· 2433 2432 2434 2433 "wisp-hosting-service/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], 2435 2434 2435 + "wispctl/@types/bun/bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], 2436 + 2436 2437 "wispctl/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 2437 2438 2438 2439 "@atproto/lex-cli/@atproto/lexicon/@atproto/common-web/@atproto/lex-json": ["@atproto/lex-json@0.0.9", "", { "dependencies": { "@atproto/lex-data": "0.0.9", "tslib": "^2.8.1" } }, "sha512-Q2v1EVZcnd+ndyZj1r2UlGikA7q6It24CFPLbxokcf5Ba4RBupH8IkkQX7mqUDSRWPgQdmZYIdW9wUln+MKDqw=="], ··· 2480 2481 "@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], 2481 2482 2482 2483 "@types/bun/bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 2484 + 2485 + "wispctl/@types/bun/bun-types/@types/node": ["@types/node@24.10.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-+0/4J266CBGPUq/ELg7QUHhN25WYjE0wYTPSQJn1xeu8DOlIOPxXxrNGiLmfAWl7HMMgWFWXpt9IDjMWrF5Iow=="], 2483 2486 2484 2487 "@atproto/sync/@atproto/xrpc-server/@atproto/ws-client/@atproto/common/@atproto/common-web": ["@atproto/common-web@0.4.13", "", { "dependencies": { "@atproto/lex-data": "0.0.9", "@atproto/lex-json": "0.0.9", "@atproto/syntax": "0.4.3", "zod": "^3.23.8" } }, "sha512-TewRUyB/dVJ5PtI3QmJzEgT3wDsvpnLJ+48hPl+LuUueJPamZevXKJN6dFjtbKAMFRnl2bKfdsf79qwvdSaLKQ=="], 2485 2488
+44 -13
cli/lib/auth.ts
··· 13 13 } from '@atproto/oauth-client-node' 14 14 import { confirm, log } from '@clack/prompts' 15 15 import { serve as honoNodeServe } from '@hono/node-server' 16 - import { Entry as KeyringEntry } from '@napi-rs/keyring' 17 16 import { resolvePdsFromHandle } from '@wispplace/atproto-utils' 18 17 import { isBun } from '@wispplace/bun-firehose' 19 18 import { Hono } from 'hono' ··· 22 21 23 22 const KEYCHAIN_SERVICE = 'wispctl' 24 23 25 - function probeKeychain(): boolean { 24 + interface KeyringEntryLike { 25 + setPassword(password: string): void 26 + getPassword(): string | null 27 + deletePassword(): void 28 + } 29 + 30 + type KeyringEntryConstructor = new (service: string, account: string) => KeyringEntryLike 31 + 32 + let keyringEntryConstructor: KeyringEntryConstructor | null | undefined 33 + 34 + async function getKeyringEntryConstructor(): Promise<KeyringEntryConstructor | null> { 35 + if (keyringEntryConstructor !== undefined) { 36 + return keyringEntryConstructor 37 + } 38 + try { 39 + const module = await import('@napi-rs/keyring') 40 + keyringEntryConstructor = module.Entry as KeyringEntryConstructor 41 + } catch { 42 + keyringEntryConstructor = null 43 + } 44 + return keyringEntryConstructor 45 + } 46 + 47 + async function probeKeychain(): Promise<boolean> { 48 + const KeyringEntry = await getKeyringEntryConstructor() 49 + if (!KeyringEntry) return false 50 + 26 51 const testKey = '__wispctl_probe__' 27 52 try { 28 53 const entry = new KeyringEntry(KEYCHAIN_SERVICE, testKey) ··· 139 164 } 140 165 } 141 166 142 - function createSessionStore(kv: KvAdapter, useKeychain: boolean): NodeSavedSessionStore { 143 - if (useKeychain) { 167 + function createSessionStore( 168 + kv: KvAdapter, 169 + keyringEntryConstructor: KeyringEntryConstructor | null, 170 + ): NodeSavedSessionStore { 171 + if (keyringEntryConstructor) { 144 172 return { 145 173 async set(sub, session) { 146 - new KeyringEntry(KEYCHAIN_SERVICE, sub).setPassword(JSON.stringify(session)) 174 + new keyringEntryConstructor(KEYCHAIN_SERVICE, sub).setPassword(JSON.stringify(session)) 147 175 }, 148 176 async get(sub) { 149 177 try { 150 - const raw = new KeyringEntry(KEYCHAIN_SERVICE, sub).getPassword() 178 + const raw = new keyringEntryConstructor(KEYCHAIN_SERVICE, sub).getPassword() 151 179 if (!raw) return undefined 152 180 return JSON.parse(raw) as NodeSavedSession 153 181 } catch { ··· 156 184 }, 157 185 async del(sub) { 158 186 try { 159 - new KeyringEntry(KEYCHAIN_SERVICE, sub).deletePassword() 187 + new keyringEntryConstructor(KEYCHAIN_SERVICE, sub).deletePassword() 160 188 } catch {} 161 189 }, 162 190 } ··· 219 247 ): Promise<{ agent: Agent; did: string }> { 220 248 const kv = await openKv(options.dbPath || DEFAULT_DB_PATH) 221 249 222 - const useKeychain = probeKeychain() 250 + const useKeychain = await probeKeychain() 223 251 if (!useKeychain) { 224 252 log.warn('System keychain is unavailable (no Secret Service daemon or equivalent).') 225 253 const fallback = await confirm({ ··· 251 279 dpop_bound_access_tokens: false, 252 280 }, 253 281 stateStore: createStateStore(kv), 254 - sessionStore: createSessionStore(kv, useKeychain), 282 + sessionStore: createSessionStore(kv, useKeychain ? await getKeyringEntryConstructor() : null), 255 283 requestLock: requestLocalLock, 256 284 }) 257 285 ··· 478 506 const kv = await openKv(dbPath || DEFAULT_DB_PATH) 479 507 // Delete any keychain entries for DIDs we know about via dir mappings 480 508 const dids = kv.valuesByPrefix('dir:') 481 - for (const did of dids) { 482 - try { 483 - new KeyringEntry(KEYCHAIN_SERVICE, did).deletePassword() 484 - } catch {} 509 + const KeyringEntry = await getKeyringEntryConstructor() 510 + if (KeyringEntry) { 511 + for (const did of dids) { 512 + try { 513 + new KeyringEntry(KEYCHAIN_SERVICE, did).deletePassword() 514 + } catch {} 515 + } 485 516 } 486 517 kv.clear() 487 518 console.log('Cleared all stored OAuth sessions')
+1 -1
cli/package.json
··· 45 45 "typecheck": "tsc --noEmit" 46 46 }, 47 47 "type": "module", 48 - "dependencies": { 48 + "optionalDependencies": { 49 49 "@napi-rs/keyring": "^1.2.0" 50 50 } 51 51 }
-1
package.json
··· 11 11 "cli" 12 12 ], 13 13 "dependencies": { 14 - "@napi-rs/keyring": "^1.2.0", 15 14 "@tailwindcss/cli": "^4.1.17", 16 15 "bun-plugin-tailwind": "^0.1.2", 17 16 "node-html-parser": "^7.1.0",