diff --git a/publish/.gitignore b/publish/.gitignore index 7a6501a..f76b4e1 100644 --- a/publish/.gitignore +++ b/publish/.gitignore @@ -1,2 +1,3 @@ .env logs/ +deno.lock diff --git a/publish/src/cli.ts b/publish/src/cli.ts index 8aec696..cc65362 100644 --- a/publish/src/cli.ts +++ b/publish/src/cli.ts @@ -79,9 +79,15 @@ async function cmdPublish(flags: { const runId = uuid() const logger = createLogger({ mode, runId }) - const signer = await createBunkerSigner(config.bunkerUrl) + console.log('[1/3] signer…') + const signer = await createBunkerSigner(config.bunkerUrl, { + clientSecretHex: config.clientSecretHex, + }) + console.log('[2/3] outbox…') const outbox = await loadOutbox(config.bootstrapRelay, config.authorPubkeyHex) + console.log('[3/3] blossom-server-liste…') const blossomServers = await loadBlossomServers(config.bootstrapRelay, config.authorPubkeyHex) + console.log('setup done') if (outbox.write.length === 0) { console.error('no write relays in kind:10002') return 1 diff --git a/publish/src/core/config.ts b/publish/src/core/config.ts index 1439fec..9478e4a 100644 --- a/publish/src/core/config.ts +++ b/publish/src/core/config.ts @@ -5,6 +5,7 @@ export interface Config { contentRoot: string clientTag: string minRelayAcks: number + clientSecretHex?: string } type EnvReader = (key: string) => string | undefined @@ -36,6 +37,10 @@ export function loadConfig(read: EnvReader = (k) => Deno.env.get(k)): Config { if (!Number.isInteger(minAcks) || minAcks < 1) { throw new Error(`MIN_RELAY_ACKS must be a positive integer, got "${minAcksRaw}"`) } + const clientSecretHex = read('CLIENT_SECRET_HEX') + if (clientSecretHex && !/^[0-9a-f]{64}$/.test(clientSecretHex)) { + throw new Error('CLIENT_SECRET_HEX must be 64 lowercase hex characters') + } return { bunkerUrl: values.BUNKER_URL, authorPubkeyHex: values.AUTHOR_PUBKEY_HEX, @@ -43,5 +48,6 @@ export function loadConfig(read: EnvReader = (k) => Deno.env.get(k)): Config { contentRoot: read('CONTENT_ROOT') ?? DEFAULTS.CONTENT_ROOT, clientTag: read('CLIENT_TAG') ?? DEFAULTS.CLIENT_TAG, minRelayAcks: minAcks, + clientSecretHex, } } diff --git a/publish/src/core/signer.ts b/publish/src/core/signer.ts index 94aa04f..3544bd7 100644 --- a/publish/src/core/signer.ts +++ b/publish/src/core/signer.ts @@ -1,4 +1,4 @@ -import { NostrConnectSigner } from 'applesauce-signers' +import { NostrConnectSigner, SimpleSigner } from 'applesauce-signers' import { RelayPool } from 'applesauce-relay' import type { UnsignedEvent } from './event.ts' import type { SignedEvent } from './relays.ts' @@ -13,6 +13,25 @@ const signerPool = new RelayPool() NostrConnectSigner.subscriptionMethod = (relays, filters) => signerPool.req(relays, filters) NostrConnectSigner.publishMethod = (relays, event) => signerPool.event(relays, event) +// Workaround: amber sendet bei wiederholten connect-requests mit bereits +// bekanntem secret "already connected" oder "no permission". applesauce- +// signers wirft daraufhin unhandled rejections, weil der request intern +// schon aufgelöst wurde. wir schlucken diese benannten fehler prozessweit. +const BENIGN_CONNECT_ERRORS = ['already connected', 'no permission'] + +function isBenignConnectError(msg: string): boolean { + const lower = msg.toLowerCase() + return BENIGN_CONNECT_ERRORS.some((e) => lower.includes(e)) +} + +globalThis.addEventListener('unhandledrejection', (e: PromiseRejectionEvent) => { + const reason = e.reason + const msg = reason instanceof Error ? reason.message : String(reason) + if (isBenignConnectError(msg)) { + e.preventDefault() + } +}) + function withTimeout(p: Promise, ms: number, label: string): Promise { let timerId: number | undefined const timeoutPromise = new Promise((_r, rej) => { @@ -23,13 +42,44 @@ function withTimeout(p: Promise, ms: number, label: string): Promise { }) as Promise } -export async function createBunkerSigner(bunkerUrl: string): Promise { - const signer = await withTimeout( - NostrConnectSigner.fromBunkerURI(bunkerUrl), - 30_000, - 'Bunker connect', - ) +export interface CreateSignerOptions { + clientSecretHex?: string +} + +export async function createBunkerSigner( + bunkerUrl: string, + options: CreateSignerOptions = {}, +): Promise { + const { remote, relays, secret } = NostrConnectSigner.parseBunkerURI(bunkerUrl) + console.log(` signer: setup (remote=${remote.slice(0, 8)}…, relays=${relays.length})`) + // Stabile client-identität: ohne festen CLIENT_SECRET_HEX erzeugt + // applesauce pro lauf einen zufälligen key, und amber sieht jeden lauf + // als neue app → permissions greifen nie. mit festem key bleibt die + // identität über läufe erhalten. + const clientSigner = options.clientSecretHex + ? SimpleSigner.fromKey(options.clientSecretHex) + : undefined + const signer = new NostrConnectSigner({ relays, remote, signer: clientSigner }) + const clientPubkey = await signer.signer.getPublicKey() + console.log(` signer: client-pubkey=${clientPubkey.slice(0, 8)}…`) + // connect() beim ersten mal nötig (damit amber die app registriert); + // bei späteren runs ist amber schon gepaired mit diesem client-pubkey + // und antwortet auf get_public_key / sign_event ohne erneuten connect. + // wir versuchen connect, schlucken benign errors, und fallen-back auf + // manuelles open(). + try { + await withTimeout(signer.connect(secret), 60_000, 'Bunker connect') + console.log(' signer: connect ok') + } catch (err) { + const msg = err instanceof Error ? err.message : String(err) + if (!isBenignConnectError(msg)) throw err + console.log(` signer: connect benign "${msg}", fallback to open+force`) + await signer.open() + ;(signer as unknown as { isConnected: boolean }).isConnected = true + } + console.log(' signer: getPublicKey…') const pubkey = await withTimeout(signer.getPublicKey(), 30_000, 'Bunker getPublicKey') + console.log(` signer: pubkey ok (${pubkey.slice(0, 8)}…)`) return { getPublicKey: () => Promise.resolve(pubkey), signEvent: async (ev: UnsignedEvent) => { diff --git a/publish/src/subcommands/check.ts b/publish/src/subcommands/check.ts index 3c5f77f..7a31406 100644 --- a/publish/src/subcommands/check.ts +++ b/publish/src/subcommands/check.ts @@ -12,7 +12,9 @@ export async function runCheck(config: Config): Promise { const issues: string[] = [] try { - const signer = await createBunkerSigner(config.bunkerUrl) + const signer = await createBunkerSigner(config.bunkerUrl, { + clientSecretHex: config.clientSecretHex, + }) const pk = await signer.getPublicKey() if (pk !== config.authorPubkeyHex) { issues.push(