@openclaw/nostr 2026.7.2-beta.5 → 2026.7.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{channel-DNeI-LB-.js → .setup/channel-B4SX7Iq6.mjs} +552 -886
- package/dist/{config-schema-CelPzg1N.js → .setup/config-schema-DIiXiBKr.mjs} +1 -1
- package/dist/.setup/inbound-direct-dm-runtime-EdLLVtkh.mjs +2 -0
- package/dist/{setup-adapter-BjKmSwdW.js → .setup/setup-adapter-DEU3o8MF.mjs} +1 -31
- package/dist/.setup/setup-plugin-api-DzlCumkz.mjs +2 -0
- package/dist/.setup/setup-plugin-api-csfJrRxD.mjs +111 -0
- package/dist/{setup-surface-BJCFpe02.js → .setup/setup-surface-DWa6-9ku.mjs} +70 -41
- package/dist/api.js +70 -56
- package/dist/channel-plugin-api.js +1 -1
- package/dist/doctor-contract-api.js +3 -2
- package/dist/setup-api.js +1 -1
- package/dist/setup-entry.js +2 -1
- package/dist/setup-plugin-api.js +1 -112
- package/node_modules/nostr-tools/lib/cjs/abstract-pool.js +3 -35
- package/node_modules/nostr-tools/lib/cjs/abstract-pool.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/abstract-relay.js +2 -30
- package/node_modules/nostr-tools/lib/cjs/abstract-relay.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/index.js +3 -35
- package/node_modules/nostr-tools/lib/cjs/index.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip46.js +3 -35
- package/node_modules/nostr-tools/lib/cjs/nip46.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nipb0.js +1 -4
- package/node_modules/nostr-tools/lib/cjs/nipb0.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/pool.js +3 -35
- package/node_modules/nostr-tools/lib/cjs/pool.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/relay.js +2 -30
- package/node_modules/nostr-tools/lib/cjs/relay.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/abstract-pool.js +3 -35
- package/node_modules/nostr-tools/lib/esm/abstract-pool.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/abstract-relay.js +2 -30
- package/node_modules/nostr-tools/lib/esm/abstract-relay.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/index.js +3 -35
- package/node_modules/nostr-tools/lib/esm/index.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip46.js +3 -35
- package/node_modules/nostr-tools/lib/esm/nip46.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nipb0.js +1 -4
- package/node_modules/nostr-tools/lib/esm/nipb0.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/pool.js +3 -35
- package/node_modules/nostr-tools/lib/esm/pool.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/relay.js +2 -30
- package/node_modules/nostr-tools/lib/esm/relay.js.map +2 -2
- package/node_modules/nostr-tools/lib/nostr.bundle.js +3 -35
- package/node_modules/nostr-tools/lib/nostr.bundle.js.map +2 -2
- package/node_modules/nostr-tools/lib/types/abstract-pool.d.ts +0 -1
- package/node_modules/nostr-tools/lib/types/abstract-relay.d.ts +0 -5
- package/node_modules/nostr-tools/lib/types/relay.d.ts +2 -2
- package/node_modules/nostr-tools/package.json +1 -1
- package/openclaw.plugin.json +5 -0
- package/package.json +15 -41
- package/dist/inbound-direct-dm-runtime-DHn9NoYN.js +0 -2
- /package/dist/{state-account-id-CvBZ9s6P.js → .setup/state-account-id-CvBZ9s6P.mjs} +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/LICENSE +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/README.md +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/_arx.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/_arx.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/_arx.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/_arx.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/_poly1305.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/_poly1305.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/_poly1305.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/_poly1305.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/_polyval.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/_polyval.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/_polyval.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/_polyval.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/aes.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/aes.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/aes.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/aes.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/chacha.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/chacha.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/chacha.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/chacha.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/ff1.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/ff1.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/ff1.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/ff1.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/index.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/index.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/index.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/index.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/package.json +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/salsa.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/salsa.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/salsa.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/salsa.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/src/_arx.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/src/_poly1305.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/src/_polyval.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/src/aes.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/src/chacha.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/src/ff1.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/src/index.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/src/salsa.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/src/utils.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/src/webcrypto.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/utils.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/utils.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/utils.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/utils.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/webcrypto.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/webcrypto.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/webcrypto.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/ciphers/webcrypto.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/LICENSE +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/README.md +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/bls.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/bls.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/bls.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/bls.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/curve.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/curve.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/curve.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/curve.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/edwards.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/edwards.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/edwards.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/edwards.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/fft.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/fft.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/fft.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/fft.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/hash-to-curve.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/hash-to-curve.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/hash-to-curve.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/hash-to-curve.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/modular.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/modular.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/modular.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/modular.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/montgomery.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/montgomery.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/montgomery.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/montgomery.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/oprf.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/oprf.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/oprf.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/oprf.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/poseidon.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/poseidon.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/poseidon.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/poseidon.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/tower.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/tower.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/tower.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/tower.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/weierstrass.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/weierstrass.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/weierstrass.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/abstract/weierstrass.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/bls12-381.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/bls12-381.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/bls12-381.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/bls12-381.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/bn254.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/bn254.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/bn254.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/bn254.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/ed25519.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/ed25519.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/ed25519.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/ed25519.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/ed448.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/ed448.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/ed448.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/ed448.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/index.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/index.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/index.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/index.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/misc.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/misc.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/misc.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/misc.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/nist.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/nist.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/nist.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/nist.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/package.json +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/secp256k1.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/secp256k1.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/secp256k1.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/secp256k1.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/abstract/bls.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/abstract/curve.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/abstract/edwards.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/abstract/fft.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/abstract/hash-to-curve.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/abstract/modular.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/abstract/montgomery.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/abstract/oprf.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/abstract/poseidon.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/abstract/tower.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/abstract/weierstrass.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/bls12-381.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/bn254.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/ed25519.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/ed448.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/index.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/misc.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/nist.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/secp256k1.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/utils.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/src/webcrypto.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/utils.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/utils.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/utils.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/utils.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/webcrypto.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/webcrypto.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/webcrypto.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/curves/webcrypto.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/LICENSE +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/README.md +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/_blake.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/_blake.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/_blake.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/_blake.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/_md.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/_md.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/_md.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/_md.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/_u64.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/_u64.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/_u64.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/_u64.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/argon2.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/argon2.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/argon2.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/argon2.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/blake1.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/blake1.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/blake1.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/blake1.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/blake2.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/blake2.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/blake2.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/blake2.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/blake3.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/blake3.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/blake3.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/blake3.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/eskdf.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/eskdf.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/eskdf.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/eskdf.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/hkdf.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/hkdf.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/hkdf.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/hkdf.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/hmac.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/hmac.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/hmac.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/hmac.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/index.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/index.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/index.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/index.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/legacy.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/legacy.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/legacy.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/legacy.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/package.json +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/pbkdf2.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/pbkdf2.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/pbkdf2.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/pbkdf2.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/scrypt.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/scrypt.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/scrypt.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/scrypt.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/sha2.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/sha2.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/sha2.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/sha2.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/sha3-addons.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/sha3-addons.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/sha3-addons.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/sha3-addons.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/sha3.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/sha3.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/sha3.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/sha3.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/_blake.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/_md.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/_u64.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/argon2.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/blake1.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/blake2.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/blake3.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/eskdf.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/hkdf.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/hmac.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/index.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/legacy.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/pbkdf2.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/scrypt.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/sha2.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/sha3-addons.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/sha3.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/utils.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/src/webcrypto.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/utils.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/utils.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/utils.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/utils.js.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/webcrypto.d.ts +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/webcrypto.d.ts.map +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/webcrypto.js +0 -0
- /package/node_modules/{@noble → nostr-tools/node_modules/@noble}/hashes/webcrypto.js.map +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/base/LICENSE +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/base/README.md +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/base/index.d.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/base/index.d.ts.map +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/base/index.js +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/base/index.js.map +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/base/index.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/base/package.json +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip32/LICENSE +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip32/README.md +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip32/index.d.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip32/index.d.ts.map +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip32/index.js +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip32/index.js.map +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip32/index.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip32/package.json +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/LICENSE +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/README.md +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/index.d.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/index.js +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/package.json +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/src/index.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/czech.d.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/czech.js +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/english.d.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/english.js +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/french.d.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/french.js +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/italian.d.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/italian.js +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/japanese.d.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/japanese.js +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/korean.d.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/korean.js +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/portuguese.d.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/portuguese.js +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/simplified-chinese.d.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/simplified-chinese.js +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/spanish.d.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/spanish.js +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/traditional-chinese.d.ts +0 -0
- /package/node_modules/{@scure → nostr-tools/node_modules/@scure}/bip39/wordlists/traditional-chinese.js +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/README.md +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/api/emsimp.d.ts +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/api/nostr.d.ts +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/api/secp256k1-types.d.ts +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/api/secp256k1.d.ts +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/api/types.d.ts +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/api/wasm-env.d.ts +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/gen/wasm.d.ts +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/generate.d.ts +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/gzipped.d.ts +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/gzipped.js +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/headless.d.ts +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/main.d.ts +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/main.js +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/nostr.js +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/secp256k1.js +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/dist/types.d.ts +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/package.json +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/public/out/secp256k1.js +0 -0
- /package/node_modules/{nostr-wasm → nostr-tools/node_modules/nostr-wasm}/public/out/secp256k1.wasm +0 -0
|
@@ -1,82 +1,60 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { a as collectStatusIssuesFromLastError, i as buildChannelConfigSchema, n as NostrProfileSchema, o as createDefaultChannelRuntimeState, r as DEFAULT_ACCOUNT_ID, s as formatPairingApproveHint, t as NostrConfigSchema } from "./config-schema-
|
|
3
|
-
import {
|
|
4
|
-
import { t as normalizeNostrStateAccountId } from "./state-account-id-CvBZ9s6P.
|
|
1
|
+
import { a as resolveDefaultNostrAccountId, c as validatePrivateKey, i as listNostrAccountIds, n as nostrSetupWizard, o as resolveNostrAccount, s as normalizePubkey, t as nostrSetupAdapter } from "./setup-surface-DWa6-9ku.mjs";
|
|
2
|
+
import { a as collectStatusIssuesFromLastError, i as buildChannelConfigSchema, n as NostrProfileSchema, o as createDefaultChannelRuntimeState, r as DEFAULT_ACCOUNT_ID, s as formatPairingApproveHint, t as NostrConfigSchema } from "./config-schema-DIiXiBKr.mjs";
|
|
3
|
+
import { i as DEFAULT_RELAYS } from "./setup-adapter-DEU3o8MF.mjs";
|
|
4
|
+
import { t as normalizeNostrStateAccountId } from "./state-account-id-CvBZ9s6P.mjs";
|
|
5
5
|
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
6
6
|
import { createScopedDmSecurityResolver, createTopLevelChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
7
7
|
import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { buildPassiveChannelStatusSummary, buildTrafficStatusSummary } from "openclaw/plugin-sdk/extension-shared";
|
|
8
|
+
import { createChannelMessageAdapterFromOutbound } from "openclaw/plugin-sdk/channel-outbound";
|
|
9
|
+
import { buildPassiveChannelStatusSummary, buildTrafficStatusSummary, runStoppablePassiveMonitor } from "openclaw/plugin-sdk/extension-shared";
|
|
11
10
|
import { createComputedAccountStatusAdapter } from "openclaw/plugin-sdk/status-helpers";
|
|
12
11
|
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
13
12
|
import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
14
13
|
import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
|
|
15
14
|
import { attachChannelToResult } from "openclaw/plugin-sdk/channel-send-result";
|
|
16
|
-
import { stripMarkdown } from "openclaw/plugin-sdk/text-chunking";
|
|
17
15
|
import { SimplePool, finalizeEvent, getPublicKey, verifyEvent } from "nostr-tools";
|
|
18
16
|
import { decrypt, encrypt } from "nostr-tools/nip04";
|
|
19
17
|
import { createDirectDmPreCryptoGuardPolicy } from "openclaw/plugin-sdk/direct-dm-guard-policy";
|
|
20
18
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
21
19
|
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
20
|
+
import { resolveIntegerOption, resolvePositiveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
22
21
|
import { buildChannelOutboundSessionRoute, stripChannelTargetPrefix } from "openclaw/plugin-sdk/core";
|
|
23
22
|
//#region extensions/nostr/src/metrics.ts
|
|
24
|
-
function createZeroMetricsState() {
|
|
25
|
-
return {
|
|
26
|
-
eventsReceived: 0,
|
|
27
|
-
eventsProcessed: 0,
|
|
28
|
-
eventsDuplicate: 0,
|
|
29
|
-
eventsRejected: {
|
|
30
|
-
invalidShape: 0,
|
|
31
|
-
wrongKind: 0,
|
|
32
|
-
stale: 0,
|
|
33
|
-
future: 0,
|
|
34
|
-
rateLimited: 0,
|
|
35
|
-
invalidSignature: 0,
|
|
36
|
-
oversizedCiphertext: 0,
|
|
37
|
-
oversizedPlaintext: 0,
|
|
38
|
-
decryptFailed: 0,
|
|
39
|
-
selfMessage: 0
|
|
40
|
-
},
|
|
41
|
-
relays: /* @__PURE__ */ new Map(),
|
|
42
|
-
rateLimiting: {
|
|
43
|
-
perSenderHits: 0,
|
|
44
|
-
globalHits: 0
|
|
45
|
-
},
|
|
46
|
-
decrypt: {
|
|
47
|
-
success: 0,
|
|
48
|
-
failure: 0
|
|
49
|
-
},
|
|
50
|
-
memory: {
|
|
51
|
-
seenTrackerSize: 0,
|
|
52
|
-
rateLimiterEntries: 0
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
function createMetricsSnapshot(state, snapshotAt) {
|
|
57
|
-
const relays = {};
|
|
58
|
-
for (const [url, stats] of state.relays) relays[url] = {
|
|
59
|
-
...stats,
|
|
60
|
-
messagesReceived: { ...stats.messagesReceived }
|
|
61
|
-
};
|
|
62
|
-
return {
|
|
63
|
-
...state,
|
|
64
|
-
eventsRejected: { ...state.eventsRejected },
|
|
65
|
-
relays,
|
|
66
|
-
rateLimiting: { ...state.rateLimiting },
|
|
67
|
-
decrypt: { ...state.decrypt },
|
|
68
|
-
memory: { ...state.memory },
|
|
69
|
-
snapshotAt: snapshotAt ?? Date.now()
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
23
|
/**
|
|
73
24
|
* Create a metrics collector instance.
|
|
74
25
|
* Optionally pass an onMetric callback to receive real-time metric events.
|
|
75
26
|
*/
|
|
76
27
|
function createMetrics(onMetric) {
|
|
77
|
-
let
|
|
28
|
+
let eventsReceived = 0;
|
|
29
|
+
let eventsProcessed = 0;
|
|
30
|
+
let eventsDuplicate = 0;
|
|
31
|
+
const eventsRejected = {
|
|
32
|
+
invalidShape: 0,
|
|
33
|
+
wrongKind: 0,
|
|
34
|
+
stale: 0,
|
|
35
|
+
future: 0,
|
|
36
|
+
rateLimited: 0,
|
|
37
|
+
invalidSignature: 0,
|
|
38
|
+
oversizedCiphertext: 0,
|
|
39
|
+
oversizedPlaintext: 0,
|
|
40
|
+
decryptFailed: 0,
|
|
41
|
+
selfMessage: 0
|
|
42
|
+
};
|
|
43
|
+
const relays = /* @__PURE__ */ new Map();
|
|
44
|
+
const rateLimiting = {
|
|
45
|
+
perSenderHits: 0,
|
|
46
|
+
globalHits: 0
|
|
47
|
+
};
|
|
48
|
+
const decrypt = {
|
|
49
|
+
success: 0,
|
|
50
|
+
failure: 0
|
|
51
|
+
};
|
|
52
|
+
const memory = {
|
|
53
|
+
seenTrackerSize: 0,
|
|
54
|
+
rateLimiterEntries: 0
|
|
55
|
+
};
|
|
78
56
|
function getOrCreateRelay(url) {
|
|
79
|
-
let relay =
|
|
57
|
+
let relay = relays.get(url);
|
|
80
58
|
if (!relay) {
|
|
81
59
|
relay = {
|
|
82
60
|
connects: 0,
|
|
@@ -95,7 +73,7 @@ function createMetrics(onMetric) {
|
|
|
95
73
|
circuitBreakerOpens: 0,
|
|
96
74
|
circuitBreakerCloses: 0
|
|
97
75
|
};
|
|
98
|
-
|
|
76
|
+
relays.set(url, relay);
|
|
99
77
|
}
|
|
100
78
|
return relay;
|
|
101
79
|
}
|
|
@@ -109,43 +87,43 @@ function createMetrics(onMetric) {
|
|
|
109
87
|
const relayUrl = labels?.relay;
|
|
110
88
|
switch (name) {
|
|
111
89
|
case "event.received":
|
|
112
|
-
|
|
90
|
+
eventsReceived += value;
|
|
113
91
|
break;
|
|
114
92
|
case "event.processed":
|
|
115
|
-
|
|
93
|
+
eventsProcessed += value;
|
|
116
94
|
break;
|
|
117
95
|
case "event.duplicate":
|
|
118
|
-
|
|
96
|
+
eventsDuplicate += value;
|
|
119
97
|
break;
|
|
120
98
|
case "event.rejected.invalid_shape":
|
|
121
|
-
|
|
99
|
+
eventsRejected.invalidShape += value;
|
|
122
100
|
break;
|
|
123
101
|
case "event.rejected.wrong_kind":
|
|
124
|
-
|
|
102
|
+
eventsRejected.wrongKind += value;
|
|
125
103
|
break;
|
|
126
104
|
case "event.rejected.stale":
|
|
127
|
-
|
|
105
|
+
eventsRejected.stale += value;
|
|
128
106
|
break;
|
|
129
107
|
case "event.rejected.future":
|
|
130
|
-
|
|
108
|
+
eventsRejected.future += value;
|
|
131
109
|
break;
|
|
132
110
|
case "event.rejected.rate_limited":
|
|
133
|
-
|
|
111
|
+
eventsRejected.rateLimited += value;
|
|
134
112
|
break;
|
|
135
113
|
case "event.rejected.invalid_signature":
|
|
136
|
-
|
|
114
|
+
eventsRejected.invalidSignature += value;
|
|
137
115
|
break;
|
|
138
116
|
case "event.rejected.oversized_ciphertext":
|
|
139
|
-
|
|
117
|
+
eventsRejected.oversizedCiphertext += value;
|
|
140
118
|
break;
|
|
141
119
|
case "event.rejected.oversized_plaintext":
|
|
142
|
-
|
|
120
|
+
eventsRejected.oversizedPlaintext += value;
|
|
143
121
|
break;
|
|
144
122
|
case "event.rejected.decrypt_failed":
|
|
145
|
-
|
|
123
|
+
eventsRejected.decryptFailed += value;
|
|
146
124
|
break;
|
|
147
125
|
case "event.rejected.self_message":
|
|
148
|
-
|
|
126
|
+
eventsRejected.selfMessage += value;
|
|
149
127
|
break;
|
|
150
128
|
case "relay.connect":
|
|
151
129
|
if (relayUrl) getOrCreateRelay(relayUrl).connects += value;
|
|
@@ -195,30 +173,64 @@ function createMetrics(onMetric) {
|
|
|
195
173
|
if (relayUrl) getOrCreateRelay(relayUrl).circuitBreakerState = "half_open";
|
|
196
174
|
break;
|
|
197
175
|
case "rate_limit.per_sender":
|
|
198
|
-
|
|
176
|
+
rateLimiting.perSenderHits += value;
|
|
199
177
|
break;
|
|
200
178
|
case "rate_limit.global":
|
|
201
|
-
|
|
179
|
+
rateLimiting.globalHits += value;
|
|
202
180
|
break;
|
|
203
181
|
case "decrypt.success":
|
|
204
|
-
|
|
182
|
+
decrypt.success += value;
|
|
205
183
|
break;
|
|
206
184
|
case "decrypt.failure":
|
|
207
|
-
|
|
185
|
+
decrypt.failure += value;
|
|
208
186
|
break;
|
|
209
187
|
case "memory.seen_tracker_size":
|
|
210
|
-
|
|
211
|
-
break;
|
|
212
|
-
case "memory.rate_limiter_entries":
|
|
213
|
-
state.memory.rateLimiterEntries = value;
|
|
188
|
+
memory.seenTrackerSize = value;
|
|
214
189
|
break;
|
|
190
|
+
case "memory.rate_limiter_entries": memory.rateLimiterEntries = value;
|
|
215
191
|
}
|
|
216
192
|
}
|
|
217
193
|
function getSnapshot() {
|
|
218
|
-
|
|
194
|
+
const relaysObj = {};
|
|
195
|
+
for (const [url, stats] of relays) relaysObj[url] = {
|
|
196
|
+
...stats,
|
|
197
|
+
messagesReceived: { ...stats.messagesReceived }
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
eventsReceived,
|
|
201
|
+
eventsProcessed,
|
|
202
|
+
eventsDuplicate,
|
|
203
|
+
eventsRejected: { ...eventsRejected },
|
|
204
|
+
relays: relaysObj,
|
|
205
|
+
rateLimiting: { ...rateLimiting },
|
|
206
|
+
decrypt: { ...decrypt },
|
|
207
|
+
memory: { ...memory },
|
|
208
|
+
snapshotAt: Date.now()
|
|
209
|
+
};
|
|
219
210
|
}
|
|
220
211
|
function reset() {
|
|
221
|
-
|
|
212
|
+
eventsReceived = 0;
|
|
213
|
+
eventsProcessed = 0;
|
|
214
|
+
eventsDuplicate = 0;
|
|
215
|
+
Object.assign(eventsRejected, {
|
|
216
|
+
invalidShape: 0,
|
|
217
|
+
wrongKind: 0,
|
|
218
|
+
stale: 0,
|
|
219
|
+
future: 0,
|
|
220
|
+
rateLimited: 0,
|
|
221
|
+
invalidSignature: 0,
|
|
222
|
+
oversizedCiphertext: 0,
|
|
223
|
+
oversizedPlaintext: 0,
|
|
224
|
+
decryptFailed: 0,
|
|
225
|
+
selfMessage: 0
|
|
226
|
+
});
|
|
227
|
+
relays.clear();
|
|
228
|
+
rateLimiting.perSenderHits = 0;
|
|
229
|
+
rateLimiting.globalHits = 0;
|
|
230
|
+
decrypt.success = 0;
|
|
231
|
+
decrypt.failure = 0;
|
|
232
|
+
memory.seenTrackerSize = 0;
|
|
233
|
+
memory.rateLimiterEntries = 0;
|
|
222
234
|
}
|
|
223
235
|
return {
|
|
224
236
|
emit,
|
|
@@ -230,7 +242,37 @@ function createMetrics(onMetric) {
|
|
|
230
242
|
* Create a no-op metrics instance (for when metrics are disabled).
|
|
231
243
|
*/
|
|
232
244
|
function createNoopMetrics() {
|
|
233
|
-
const emptySnapshot =
|
|
245
|
+
const emptySnapshot = {
|
|
246
|
+
eventsReceived: 0,
|
|
247
|
+
eventsProcessed: 0,
|
|
248
|
+
eventsDuplicate: 0,
|
|
249
|
+
eventsRejected: {
|
|
250
|
+
invalidShape: 0,
|
|
251
|
+
wrongKind: 0,
|
|
252
|
+
stale: 0,
|
|
253
|
+
future: 0,
|
|
254
|
+
rateLimited: 0,
|
|
255
|
+
invalidSignature: 0,
|
|
256
|
+
oversizedCiphertext: 0,
|
|
257
|
+
oversizedPlaintext: 0,
|
|
258
|
+
decryptFailed: 0,
|
|
259
|
+
selfMessage: 0
|
|
260
|
+
},
|
|
261
|
+
relays: {},
|
|
262
|
+
rateLimiting: {
|
|
263
|
+
perSenderHits: 0,
|
|
264
|
+
globalHits: 0
|
|
265
|
+
},
|
|
266
|
+
decrypt: {
|
|
267
|
+
success: 0,
|
|
268
|
+
failure: 0
|
|
269
|
+
},
|
|
270
|
+
memory: {
|
|
271
|
+
seenTrackerSize: 0,
|
|
272
|
+
rateLimiterEntries: 0
|
|
273
|
+
},
|
|
274
|
+
snapshotAt: 0
|
|
275
|
+
};
|
|
234
276
|
return {
|
|
235
277
|
emit: () => {},
|
|
236
278
|
getSnapshot: () => ({
|
|
@@ -241,395 +283,6 @@ function createNoopMetrics() {
|
|
|
241
283
|
};
|
|
242
284
|
}
|
|
243
285
|
//#endregion
|
|
244
|
-
//#region extensions/nostr/src/nostr-cursor.ts
|
|
245
|
-
const CURSOR_WRITE_RETRY_MS = [
|
|
246
|
-
0,
|
|
247
|
-
100,
|
|
248
|
-
300
|
|
249
|
-
];
|
|
250
|
-
const CURSOR_RECOVERY_RETRY_MS = 1e3;
|
|
251
|
-
/** Tracks the largest EOSE-safe Nostr timestamp without skipping undurable relay events. */
|
|
252
|
-
function createNostrDurableCursor(options) {
|
|
253
|
-
const nowSec = options.nowSec ?? (() => Math.floor(Date.now() / 1e3));
|
|
254
|
-
let durableCandidate;
|
|
255
|
-
let transientReplayCeiling;
|
|
256
|
-
let backfillComplete = false;
|
|
257
|
-
const safeCandidate = () => {
|
|
258
|
-
if (durableCandidate === void 0) return;
|
|
259
|
-
return Math.min(durableCandidate, transientReplayCeiling ?? Number.MAX_SAFE_INTEGER);
|
|
260
|
-
};
|
|
261
|
-
return {
|
|
262
|
-
recordDurableAppend: (event) => {
|
|
263
|
-
if (!Number.isSafeInteger(event.created_at)) return;
|
|
264
|
-
const previousSafeCandidate = safeCandidate();
|
|
265
|
-
durableCandidate = Math.max(durableCandidate ?? 0, Math.min(event.created_at, nowSec()));
|
|
266
|
-
const nextSafeCandidate = safeCandidate();
|
|
267
|
-
return backfillComplete && nextSafeCandidate !== previousSafeCandidate ? nextSafeCandidate : void 0;
|
|
268
|
-
},
|
|
269
|
-
recordTransientRejection: (event) => {
|
|
270
|
-
if (!Number.isSafeInteger(event.created_at) || event.created_at < options.since) return;
|
|
271
|
-
const previousSafeCandidate = safeCandidate();
|
|
272
|
-
transientReplayCeiling = Math.min(transientReplayCeiling ?? Number.MAX_SAFE_INTEGER, event.created_at + options.replayOverlapSec);
|
|
273
|
-
const nextSafeCandidate = safeCandidate();
|
|
274
|
-
return backfillComplete && nextSafeCandidate !== previousSafeCandidate ? nextSafeCandidate : void 0;
|
|
275
|
-
},
|
|
276
|
-
markBackfillComplete: () => {
|
|
277
|
-
backfillComplete = true;
|
|
278
|
-
return safeCandidate();
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
/** Serializes cursor writes so a safety rewind always lands after older progress writes. */
|
|
283
|
-
function createNostrCursorStateWriter(options) {
|
|
284
|
-
let desiredCursor = Math.max(options.minimumCursor, options.initialCursor);
|
|
285
|
-
let dirty = false;
|
|
286
|
-
let timer;
|
|
287
|
-
let writeTail = Promise.resolve();
|
|
288
|
-
let activeFlush;
|
|
289
|
-
let recoveryFlush;
|
|
290
|
-
const writeWithRetry = async (cursor) => {
|
|
291
|
-
let lastError;
|
|
292
|
-
for (const delayMs of CURSOR_WRITE_RETRY_MS) {
|
|
293
|
-
if (delayMs > 0) await new Promise((resolve) => {
|
|
294
|
-
setTimeout(resolve, delayMs);
|
|
295
|
-
});
|
|
296
|
-
try {
|
|
297
|
-
await options.write(cursor);
|
|
298
|
-
return;
|
|
299
|
-
} catch (error) {
|
|
300
|
-
lastError = error;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
throw new Error("Nostr cursor state write failed.", { cause: lastError });
|
|
304
|
-
};
|
|
305
|
-
const enqueueWrite = (cursor) => {
|
|
306
|
-
const write = writeTail.then(() => writeWithRetry(cursor));
|
|
307
|
-
writeTail = write.catch(() => void 0);
|
|
308
|
-
return write;
|
|
309
|
-
};
|
|
310
|
-
const clearTimer = () => {
|
|
311
|
-
if (timer) {
|
|
312
|
-
clearTimeout(timer);
|
|
313
|
-
timer = void 0;
|
|
314
|
-
}
|
|
315
|
-
};
|
|
316
|
-
const runFlush = async () => {
|
|
317
|
-
for (;;) {
|
|
318
|
-
if (!dirty) {
|
|
319
|
-
await writeTail;
|
|
320
|
-
if (!dirty) return;
|
|
321
|
-
}
|
|
322
|
-
dirty = false;
|
|
323
|
-
const cursor = desiredCursor;
|
|
324
|
-
try {
|
|
325
|
-
await enqueueWrite(cursor);
|
|
326
|
-
} catch (error) {
|
|
327
|
-
dirty = true;
|
|
328
|
-
throw error;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
};
|
|
332
|
-
const flush = () => {
|
|
333
|
-
clearTimer();
|
|
334
|
-
if (activeFlush) return activeFlush;
|
|
335
|
-
const tracked = runFlush().finally(() => {
|
|
336
|
-
if (activeFlush === tracked) activeFlush = void 0;
|
|
337
|
-
});
|
|
338
|
-
activeFlush = tracked;
|
|
339
|
-
return tracked;
|
|
340
|
-
};
|
|
341
|
-
const setDesiredCursor = (cursor) => {
|
|
342
|
-
desiredCursor = Math.max(options.minimumCursor, cursor);
|
|
343
|
-
dirty = true;
|
|
344
|
-
};
|
|
345
|
-
return {
|
|
346
|
-
schedule: (cursor) => {
|
|
347
|
-
setDesiredCursor(cursor);
|
|
348
|
-
clearTimer();
|
|
349
|
-
timer = setTimeout(() => {
|
|
350
|
-
timer = void 0;
|
|
351
|
-
flush().catch((error) => options.onBackgroundError?.(error));
|
|
352
|
-
}, options.debounceMs);
|
|
353
|
-
timer.unref?.();
|
|
354
|
-
},
|
|
355
|
-
persistNow: async (cursor) => {
|
|
356
|
-
setDesiredCursor(cursor);
|
|
357
|
-
await flush();
|
|
358
|
-
},
|
|
359
|
-
flush,
|
|
360
|
-
flushUntilSuccess: () => {
|
|
361
|
-
recoveryFlush ??= (async () => {
|
|
362
|
-
for (;;) try {
|
|
363
|
-
await flush();
|
|
364
|
-
return;
|
|
365
|
-
} catch (error) {
|
|
366
|
-
options.onBackgroundError?.(error);
|
|
367
|
-
await new Promise((resolve) => {
|
|
368
|
-
setTimeout(resolve, options.recoveryRetryMs ?? CURSOR_RECOVERY_RETRY_MS);
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
})().finally(() => {
|
|
372
|
-
recoveryFlush = void 0;
|
|
373
|
-
});
|
|
374
|
-
return recoveryFlush;
|
|
375
|
-
}
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
var NostrIngressPermanentError = class extends Error {
|
|
379
|
-
constructor(reason, message, options) {
|
|
380
|
-
super(message, options);
|
|
381
|
-
this.name = "NostrIngressPermanentError";
|
|
382
|
-
this.reason = reason;
|
|
383
|
-
}
|
|
384
|
-
};
|
|
385
|
-
function isNostrIngressRecord(value) {
|
|
386
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
387
|
-
}
|
|
388
|
-
function requiredString(value, field) {
|
|
389
|
-
if (typeof value === "string" && value.trim()) return value;
|
|
390
|
-
throw new NostrIngressPermanentError("invalid-event", `Nostr event is missing ${field}.`);
|
|
391
|
-
}
|
|
392
|
-
function inspectNostrIngressEvent(event) {
|
|
393
|
-
if (!isNostrIngressRecord(event)) throw new NostrIngressPermanentError("invalid-event", "Nostr event must be an object.");
|
|
394
|
-
return {
|
|
395
|
-
eventId: requiredString(event.id, "id"),
|
|
396
|
-
laneKey: `direct:${requiredString(event.pubkey, "pubkey")}`
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
/** Convert the retired persisted LRU seed into durable completion tombstones. */
|
|
400
|
-
async function migrateNostrLegacyRecentEventIds(params) {
|
|
401
|
-
const migratedAt = params.migratedAt ?? Date.now();
|
|
402
|
-
let migrated = 0;
|
|
403
|
-
for (const eventId of new Set(params.eventIds)) {
|
|
404
|
-
if (!eventId.trim()) continue;
|
|
405
|
-
const result = await params.queue.enqueue(eventId, {
|
|
406
|
-
version: 1,
|
|
407
|
-
receivedAt: migratedAt,
|
|
408
|
-
rawEvent: ""
|
|
409
|
-
}, {
|
|
410
|
-
receivedAt: migratedAt,
|
|
411
|
-
laneKey: `legacy:${eventId}`
|
|
412
|
-
});
|
|
413
|
-
if (result.kind === "accepted" || result.kind === "pending" && result.record.payload.rawEvent === "") {
|
|
414
|
-
if (!await params.queue.complete(eventId, { completedAt: migratedAt })) throw new Error(`Failed to migrate Nostr replay event ${eventId}.`);
|
|
415
|
-
}
|
|
416
|
-
migrated += 1;
|
|
417
|
-
}
|
|
418
|
-
return migrated;
|
|
419
|
-
}
|
|
420
|
-
//#endregion
|
|
421
|
-
//#region extensions/nostr/src/runtime.ts
|
|
422
|
-
const { setRuntime: setNostrRuntime, getRuntime: getNostrRuntime } = createPluginRuntimeStore({
|
|
423
|
-
pluginId: "nostr",
|
|
424
|
-
errorMessage: "Nostr runtime not initialized"
|
|
425
|
-
});
|
|
426
|
-
//#endregion
|
|
427
|
-
//#region extensions/nostr/src/nostr-ingress.ts
|
|
428
|
-
const NOSTR_INGRESS_POLL_INTERVAL_MS = 500;
|
|
429
|
-
const NOSTR_INGRESS_PRUNE_INTERVAL_MS = 3600 * 1e3;
|
|
430
|
-
const NOSTR_INGRESS_COMPLETED_TTL_MS = 720 * 60 * 60 * 1e3;
|
|
431
|
-
const NOSTR_INGRESS_COMPLETED_MAX_ENTRIES = 1e5;
|
|
432
|
-
const NOSTR_INGRESS_FAILED_TTL_MS = 720 * 60 * 60 * 1e3;
|
|
433
|
-
const NOSTR_INGRESS_FAILED_MAX_ENTRIES = 1e5;
|
|
434
|
-
const NOSTR_INGRESS_APPEND_RETRY_MS = [
|
|
435
|
-
0,
|
|
436
|
-
100,
|
|
437
|
-
300
|
|
438
|
-
];
|
|
439
|
-
var NostrIngressAdmissionRejectedError = class extends Error {
|
|
440
|
-
constructor(reason, message) {
|
|
441
|
-
super(message);
|
|
442
|
-
this.name = "NostrIngressAdmissionRejectedError";
|
|
443
|
-
this.reason = reason;
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
function deserializeNostrIngressEvent(rawEvent, claimedId) {
|
|
447
|
-
let parsed;
|
|
448
|
-
try {
|
|
449
|
-
parsed = JSON.parse(rawEvent);
|
|
450
|
-
} catch (error) {
|
|
451
|
-
throw new NostrIngressPermanentError("invalid-event", `Nostr ingress row ${claimedId} contains invalid JSON.`, { cause: error });
|
|
452
|
-
}
|
|
453
|
-
if (!isNostrIngressRecord(parsed)) throw new NostrIngressPermanentError("invalid-event", `Nostr ingress row ${claimedId} has an invalid event shape.`);
|
|
454
|
-
if (typeof parsed.kind !== "number" || typeof parsed.created_at !== "number" || typeof parsed.content !== "string" || typeof parsed.sig !== "string" || !Array.isArray(parsed.tags)) throw new NostrIngressPermanentError("invalid-event", `Nostr ingress row ${claimedId} has an invalid event shape.`);
|
|
455
|
-
return parsed;
|
|
456
|
-
}
|
|
457
|
-
function createNostrIngress(options) {
|
|
458
|
-
let queue = options.queue;
|
|
459
|
-
let admissionFailure;
|
|
460
|
-
let admissionWindowStartedAt = Date.now();
|
|
461
|
-
let admissionWindowCount = 0;
|
|
462
|
-
let queuedAdmissions = 0;
|
|
463
|
-
let stopping = false;
|
|
464
|
-
let stopTask;
|
|
465
|
-
const createStoppedError = () => /* @__PURE__ */ new Error("Nostr ingress stopped");
|
|
466
|
-
const getQueue = () => {
|
|
467
|
-
queue ??= getNostrRuntime().state.openChannelIngressQueue({ accountId: options.accountId });
|
|
468
|
-
return queue;
|
|
469
|
-
};
|
|
470
|
-
const legacyMigration = migrateNostrLegacyRecentEventIds({
|
|
471
|
-
queue: getQueue(),
|
|
472
|
-
eventIds: options.legacyEventIds ?? []
|
|
473
|
-
});
|
|
474
|
-
const monitor = createChannelIngressMonitor({
|
|
475
|
-
queue: getQueue,
|
|
476
|
-
inspect: (event) => {
|
|
477
|
-
const facts = inspectNostrIngressEvent(event);
|
|
478
|
-
return {
|
|
479
|
-
eventId: facts.eventId,
|
|
480
|
-
laneKey: facts.laneKey
|
|
481
|
-
};
|
|
482
|
-
},
|
|
483
|
-
payload: {
|
|
484
|
-
version: 1,
|
|
485
|
-
serialize: (event, { receivedAt }) => ({
|
|
486
|
-
receivedAt,
|
|
487
|
-
rawEvent: JSON.stringify(event)
|
|
488
|
-
}),
|
|
489
|
-
deserialize: (body, { claim }) => deserializeNostrIngressEvent(body.rawEvent, claim.id),
|
|
490
|
-
encode: ({ body }) => ({
|
|
491
|
-
version: 1,
|
|
492
|
-
...body
|
|
493
|
-
}),
|
|
494
|
-
decode: (payload, { claim }) => {
|
|
495
|
-
if (typeof payload !== "object" || payload === null || typeof payload.rawEvent !== "string") throw new NostrIngressPermanentError("invalid-event", `Nostr ingress row ${claim.id} has an invalid payload.`);
|
|
496
|
-
return {
|
|
497
|
-
version: payload.version,
|
|
498
|
-
body: {
|
|
499
|
-
receivedAt: typeof payload.receivedAt === "number" ? payload.receivedAt : 0,
|
|
500
|
-
rawEvent: payload.rawEvent
|
|
501
|
-
}
|
|
502
|
-
};
|
|
503
|
-
},
|
|
504
|
-
createClaimError: (kind, claim) => new NostrIngressPermanentError("invalid-event", kind === "invalid-version" ? `Nostr ingress row ${claim.id} has an unsupported version.` : `Nostr ingress row ${claim.id} changed event identity.`)
|
|
505
|
-
},
|
|
506
|
-
deliver: (event, lifecycle) => options.deliver(event, lifecycle),
|
|
507
|
-
pollIntervalMs: options.pollIntervalMs ?? NOSTR_INGRESS_POLL_INTERVAL_MS,
|
|
508
|
-
retention: {
|
|
509
|
-
pruneIntervalMs: NOSTR_INGRESS_PRUNE_INTERVAL_MS,
|
|
510
|
-
completedTtlMs: NOSTR_INGRESS_COMPLETED_TTL_MS,
|
|
511
|
-
completedMaxEntries: NOSTR_INGRESS_COMPLETED_MAX_ENTRIES,
|
|
512
|
-
failedTtlMs: NOSTR_INGRESS_FAILED_TTL_MS,
|
|
513
|
-
failedMaxEntries: NOSTR_INGRESS_FAILED_MAX_ENTRIES
|
|
514
|
-
},
|
|
515
|
-
drain: {
|
|
516
|
-
adoptionStallTimeoutMs: options.adoptionStallTimeoutMs ?? DEFAULT_INGRESS_ADOPTION_STALL_MS,
|
|
517
|
-
resolveNonRetryableFailure: (error) => error instanceof NostrIngressPermanentError ? {
|
|
518
|
-
reason: error.reason,
|
|
519
|
-
message: error.message
|
|
520
|
-
} : null,
|
|
521
|
-
onLog: (message) => options.onError?.(new Error(message), "ingress drain")
|
|
522
|
-
},
|
|
523
|
-
createStoppedError,
|
|
524
|
-
onError: (error) => options.onError?.(error, "ingress drain")
|
|
525
|
-
});
|
|
526
|
-
const monitorStart = legacyMigration.then(() => {
|
|
527
|
-
if (!stopping) monitor.start();
|
|
528
|
-
});
|
|
529
|
-
monitorStart.catch((error) => options.onError?.(error, "ingress drain"));
|
|
530
|
-
let admissionTail = Promise.resolve();
|
|
531
|
-
const prepareAdmission = (event) => {
|
|
532
|
-
const facts = inspectNostrIngressEvent(event);
|
|
533
|
-
const receivedAt = Date.now();
|
|
534
|
-
if (receivedAt - admissionWindowStartedAt >= options.admissionRateLimit.windowMs) {
|
|
535
|
-
admissionWindowStartedAt = receivedAt;
|
|
536
|
-
admissionWindowCount = 0;
|
|
537
|
-
}
|
|
538
|
-
if (admissionWindowCount >= options.admissionRateLimit.maxEvents) throw new NostrIngressAdmissionRejectedError("rate-limited", "Nostr event exceeds the durable admission rate.");
|
|
539
|
-
admissionWindowCount += 1;
|
|
540
|
-
if (queuedAdmissions >= options.maxQueuedAdmissions) throw new NostrIngressAdmissionRejectedError("backpressure", "Nostr event exceeds the in-memory admission backlog.");
|
|
541
|
-
let payload;
|
|
542
|
-
let serializedPayload;
|
|
543
|
-
try {
|
|
544
|
-
payload = {
|
|
545
|
-
version: 1,
|
|
546
|
-
receivedAt,
|
|
547
|
-
rawEvent: JSON.stringify(event)
|
|
548
|
-
};
|
|
549
|
-
serializedPayload = JSON.stringify(payload);
|
|
550
|
-
} catch (error) {
|
|
551
|
-
throw new NostrIngressPermanentError("invalid-event", "Nostr event could not be serialized for durable ingress.", { cause: error });
|
|
552
|
-
}
|
|
553
|
-
if (Buffer.byteLength(serializedPayload, "utf8") > options.maxSerializedPayloadBytes) throw new NostrIngressAdmissionRejectedError("oversized-event", "Nostr event exceeds the durable ingress size limit.");
|
|
554
|
-
return {
|
|
555
|
-
event,
|
|
556
|
-
facts,
|
|
557
|
-
receivedAt,
|
|
558
|
-
payload
|
|
559
|
-
};
|
|
560
|
-
};
|
|
561
|
-
const admitOnce = async (prepared) => {
|
|
562
|
-
await legacyMigration;
|
|
563
|
-
const pending = await getQueue().listPending({ limit: options.maxPendingEvents });
|
|
564
|
-
const claims = await getQueue().listClaims();
|
|
565
|
-
if (pending.length + claims.length >= options.maxPendingEvents) throw new NostrIngressAdmissionRejectedError("backpressure", "Nostr event exceeds the durable ingress backlog.");
|
|
566
|
-
let lastError;
|
|
567
|
-
for (const delayMs of NOSTR_INGRESS_APPEND_RETRY_MS) {
|
|
568
|
-
if (delayMs > 0) await new Promise((resolve) => {
|
|
569
|
-
setTimeout(resolve, delayMs);
|
|
570
|
-
});
|
|
571
|
-
try {
|
|
572
|
-
const result = await getQueue().enqueue(prepared.facts.eventId, prepared.payload, {
|
|
573
|
-
receivedAt: prepared.receivedAt,
|
|
574
|
-
laneKey: prepared.facts.laneKey
|
|
575
|
-
});
|
|
576
|
-
options.afterDurableAppend(prepared.event);
|
|
577
|
-
monitor.requestDrain();
|
|
578
|
-
return result.kind === "accepted" ? "accepted" : "duplicate";
|
|
579
|
-
} catch (error) {
|
|
580
|
-
lastError = error;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
throw new Error(`Nostr durable admission failed: ${formatErrorMessage(lastError)}`, { cause: lastError });
|
|
584
|
-
};
|
|
585
|
-
return {
|
|
586
|
-
ready: async () => {
|
|
587
|
-
await monitorStart;
|
|
588
|
-
},
|
|
589
|
-
receive: (event) => {
|
|
590
|
-
if (stopping) return Promise.reject(createStoppedError());
|
|
591
|
-
let prepared;
|
|
592
|
-
try {
|
|
593
|
-
prepared = prepareAdmission(event);
|
|
594
|
-
} catch (error) {
|
|
595
|
-
return Promise.reject(error);
|
|
596
|
-
}
|
|
597
|
-
queuedAdmissions += 1;
|
|
598
|
-
const settledAdmission = admissionTail.then(async () => {
|
|
599
|
-
if (admissionFailure) throw admissionFailure;
|
|
600
|
-
try {
|
|
601
|
-
return await admitOnce(prepared);
|
|
602
|
-
} catch (error) {
|
|
603
|
-
if (error instanceof NostrIngressAdmissionRejectedError || error instanceof NostrIngressPermanentError) throw error;
|
|
604
|
-
admissionFailure = error instanceof Error ? error : new Error(formatErrorMessage(error), { cause: error });
|
|
605
|
-
throw admissionFailure;
|
|
606
|
-
}
|
|
607
|
-
}).finally(() => {
|
|
608
|
-
queuedAdmissions -= 1;
|
|
609
|
-
});
|
|
610
|
-
admissionTail = settledAdmission.then(() => void 0, () => void 0);
|
|
611
|
-
return settledAdmission;
|
|
612
|
-
},
|
|
613
|
-
stop: () => {
|
|
614
|
-
if (stopTask) return stopTask;
|
|
615
|
-
stopping = true;
|
|
616
|
-
const pauseTask = monitor.pause();
|
|
617
|
-
stopTask = (async () => {
|
|
618
|
-
await admissionTail;
|
|
619
|
-
await monitorStart.catch(() => void 0);
|
|
620
|
-
await pauseTask;
|
|
621
|
-
await monitor.stop();
|
|
622
|
-
})();
|
|
623
|
-
return stopTask;
|
|
624
|
-
},
|
|
625
|
-
waitForIdle: async () => {
|
|
626
|
-
await admissionTail;
|
|
627
|
-
await monitorStart;
|
|
628
|
-
await monitor.waitForIdle();
|
|
629
|
-
}
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
//#endregion
|
|
633
286
|
//#region extensions/nostr/src/nostr-profile-core.ts
|
|
634
287
|
/**
|
|
635
288
|
* Convert our config profile schema to NIP-01 content format.
|
|
@@ -665,16 +318,6 @@ function contentToProfile(content) {
|
|
|
665
318
|
return profile;
|
|
666
319
|
}
|
|
667
320
|
//#endregion
|
|
668
|
-
//#region extensions/nostr/src/relay-publish.ts
|
|
669
|
-
const CONNECTION_FAILURE_PREFIX = "connection failure: ";
|
|
670
|
-
async function publishNostrEventToRelay(pool, relay, event) {
|
|
671
|
-
const publishPromise = pool.publish([relay], event)[0];
|
|
672
|
-
if (!publishPromise) throw new Error(`Failed to create publish promise for relay ${relay}`);
|
|
673
|
-
const result = await publishPromise;
|
|
674
|
-
if (result.startsWith(CONNECTION_FAILURE_PREFIX)) throw new Error(result.slice(20));
|
|
675
|
-
return result;
|
|
676
|
-
}
|
|
677
|
-
//#endregion
|
|
678
321
|
//#region extensions/nostr/src/nostr-profile.ts
|
|
679
322
|
/**
|
|
680
323
|
* Nostr Profile Management (NIP-01 kind:0)
|
|
@@ -694,11 +337,12 @@ function createProfileEvent(sk, profile, lastPublishedAt) {
|
|
|
694
337
|
const content = profileToContent(profile);
|
|
695
338
|
const contentJson = JSON.stringify(content);
|
|
696
339
|
const now = Math.floor(Date.now() / 1e3);
|
|
340
|
+
const createdAt = lastPublishedAt !== void 0 ? Math.max(now, lastPublishedAt + 1) : now;
|
|
697
341
|
return finalizeEvent({
|
|
698
342
|
kind: 0,
|
|
699
343
|
content: contentJson,
|
|
700
344
|
tags: [],
|
|
701
|
-
created_at:
|
|
345
|
+
created_at: createdAt
|
|
702
346
|
}, sk);
|
|
703
347
|
}
|
|
704
348
|
/** Per-relay publish timeout (ms) */
|
|
@@ -723,7 +367,7 @@ async function publishProfileEvent(pool, relays, event) {
|
|
|
723
367
|
const timeoutPromise = new Promise((_, reject) => {
|
|
724
368
|
timer = setTimeout(() => reject(/* @__PURE__ */ new Error("timeout")), RELAY_PUBLISH_TIMEOUT_MS);
|
|
725
369
|
});
|
|
726
|
-
await Promise.race([
|
|
370
|
+
await Promise.race([...pool.publish([relay], event), timeoutPromise]);
|
|
727
371
|
successes.push(relay);
|
|
728
372
|
} catch (err) {
|
|
729
373
|
const errorMessage = formatErrorMessage(err);
|
|
@@ -757,111 +401,11 @@ async function publishProfile(pool, sk, relays, profile, lastPublishedAt) {
|
|
|
757
401
|
return publishProfileEvent(pool, relays, createProfileEvent(sk, profile, lastPublishedAt));
|
|
758
402
|
}
|
|
759
403
|
//#endregion
|
|
760
|
-
//#region extensions/nostr/src/
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
const state = /* @__PURE__ */ new Map();
|
|
766
|
-
const touch = (key, value) => {
|
|
767
|
-
state.delete(key);
|
|
768
|
-
state.set(key, value);
|
|
769
|
-
};
|
|
770
|
-
const prune = (nowMs) => {
|
|
771
|
-
for (const [key, entry] of state) if (nowMs - entry.windowStartMs >= windowMs) state.delete(key);
|
|
772
|
-
while (state.size > maxTrackedKeys) {
|
|
773
|
-
const oldest = state.keys().next().value;
|
|
774
|
-
if (!oldest) break;
|
|
775
|
-
state.delete(oldest);
|
|
776
|
-
}
|
|
777
|
-
};
|
|
778
|
-
return {
|
|
779
|
-
isRateLimited: (key, nowMs = Date.now()) => {
|
|
780
|
-
if (!key) return false;
|
|
781
|
-
prune(nowMs);
|
|
782
|
-
const existing = state.get(key);
|
|
783
|
-
if (!existing || nowMs - existing.windowStartMs >= windowMs) {
|
|
784
|
-
touch(key, {
|
|
785
|
-
count: 1,
|
|
786
|
-
windowStartMs: nowMs
|
|
787
|
-
});
|
|
788
|
-
return false;
|
|
789
|
-
}
|
|
790
|
-
const nextCount = existing.count + 1;
|
|
791
|
-
touch(key, {
|
|
792
|
-
count: nextCount,
|
|
793
|
-
windowStartMs: existing.windowStartMs
|
|
794
|
-
});
|
|
795
|
-
return nextCount > maxRequests;
|
|
796
|
-
},
|
|
797
|
-
size: () => state.size,
|
|
798
|
-
clear: () => state.clear()
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
//#endregion
|
|
802
|
-
//#region extensions/nostr/src/nostr-relay-subscription.ts
|
|
803
|
-
const DEFAULT_EOSE_CONFIRM_DEADLINE_MS = 1e4;
|
|
804
|
-
const LIBRARY_EOSE_TIMEOUT_MARGIN_MS = 1e3;
|
|
805
|
-
/** Separates real relay EOSE frames from nostr-tools timeout/close synthesis. */
|
|
806
|
-
function createNostrRelaySubscriptionGroup(options) {
|
|
807
|
-
const relays = [...new Set(options.relays)];
|
|
808
|
-
const subscriptions = [];
|
|
809
|
-
const deadlineTimers = /* @__PURE__ */ new Set();
|
|
810
|
-
let locallyClosing = false;
|
|
811
|
-
const backfillStatus = new Map(relays.map((relay) => [relay, "pending"]));
|
|
812
|
-
const confirmDeadlineMs = options.eoseConfirmDeadlineMs ?? DEFAULT_EOSE_CONFIRM_DEADLINE_MS;
|
|
813
|
-
const settleBackfill = (relay, status) => {
|
|
814
|
-
if (backfillStatus.get(relay) !== "pending") return;
|
|
815
|
-
backfillStatus.set(relay, status);
|
|
816
|
-
if ([...backfillStatus.values()].some((value) => value === "pending")) return;
|
|
817
|
-
if ([...backfillStatus.values()].every((value) => value === "confirmed")) options.onBackfillComplete(relays);
|
|
818
|
-
};
|
|
819
|
-
const clearDeadlines = () => {
|
|
820
|
-
for (const timer of deadlineTimers) clearTimeout(timer);
|
|
821
|
-
deadlineTimers.clear();
|
|
822
|
-
};
|
|
823
|
-
return {
|
|
824
|
-
start: () => {
|
|
825
|
-
for (const relay of relays) {
|
|
826
|
-
let relayClosed = false;
|
|
827
|
-
let deadlineReached = false;
|
|
828
|
-
const deadlineTimer = setTimeout(() => {
|
|
829
|
-
deadlineTimers.delete(deadlineTimer);
|
|
830
|
-
deadlineReached = true;
|
|
831
|
-
settleBackfill(relay, "incomplete");
|
|
832
|
-
}, confirmDeadlineMs);
|
|
833
|
-
deadlineTimer.unref?.();
|
|
834
|
-
deadlineTimers.add(deadlineTimer);
|
|
835
|
-
subscriptions.push(options.pool.subscribeMany([relay], options.filter, {
|
|
836
|
-
onevent: options.onEvent,
|
|
837
|
-
oneose: () => {
|
|
838
|
-
queueMicrotask(() => {
|
|
839
|
-
if (!relayClosed && !deadlineReached) {
|
|
840
|
-
clearTimeout(deadlineTimer);
|
|
841
|
-
deadlineTimers.delete(deadlineTimer);
|
|
842
|
-
settleBackfill(relay, "confirmed");
|
|
843
|
-
}
|
|
844
|
-
});
|
|
845
|
-
},
|
|
846
|
-
onclose: (reasons) => {
|
|
847
|
-
relayClosed = true;
|
|
848
|
-
clearTimeout(deadlineTimer);
|
|
849
|
-
deadlineTimers.delete(deadlineTimer);
|
|
850
|
-
settleBackfill(relay, "incomplete");
|
|
851
|
-
if (!locallyClosing && !options.abort.aborted) options.onClose(relay, reasons);
|
|
852
|
-
},
|
|
853
|
-
maxWait: confirmDeadlineMs + LIBRARY_EOSE_TIMEOUT_MARGIN_MS,
|
|
854
|
-
abort: options.abort
|
|
855
|
-
}));
|
|
856
|
-
}
|
|
857
|
-
},
|
|
858
|
-
close: async (reason) => {
|
|
859
|
-
locallyClosing = true;
|
|
860
|
-
clearDeadlines();
|
|
861
|
-
await Promise.all(subscriptions.map(async (subscription) => subscription.close(reason)));
|
|
862
|
-
}
|
|
863
|
-
};
|
|
864
|
-
}
|
|
404
|
+
//#region extensions/nostr/src/runtime.ts
|
|
405
|
+
const { setRuntime: setNostrRuntime, getRuntime: getNostrRuntime } = createPluginRuntimeStore({
|
|
406
|
+
pluginId: "nostr",
|
|
407
|
+
errorMessage: "Nostr runtime not initialized"
|
|
408
|
+
});
|
|
865
409
|
//#endregion
|
|
866
410
|
//#region extensions/nostr/src/nostr-state-store.ts
|
|
867
411
|
const STORE_VERSION = 2;
|
|
@@ -916,15 +460,213 @@ async function writeNostrProfileState(params) {
|
|
|
916
460
|
await openNostrProfileStateStore(params.env).register(normalizeNostrStateAccountId(params.accountId), payload);
|
|
917
461
|
}
|
|
918
462
|
//#endregion
|
|
463
|
+
//#region extensions/nostr/src/seen-tracker.ts
|
|
464
|
+
/**
|
|
465
|
+
* LRU-based seen event tracker with TTL support.
|
|
466
|
+
* Prevents unbounded memory growth under high load or abuse.
|
|
467
|
+
*/
|
|
468
|
+
/**
|
|
469
|
+
* Create a new seen tracker with LRU eviction and TTL expiration.
|
|
470
|
+
*/
|
|
471
|
+
function createSeenTracker(options) {
|
|
472
|
+
const maxEntries = resolveIntegerOption(options?.maxEntries, 1e5, { min: 1 });
|
|
473
|
+
const ttlMs = resolvePositiveTimerTimeoutMs(options?.ttlMs, 36e5);
|
|
474
|
+
const pruneIntervalMs = resolvePositiveTimerTimeoutMs(options?.pruneIntervalMs, 6e5);
|
|
475
|
+
const entries = /* @__PURE__ */ new Map();
|
|
476
|
+
let head = null;
|
|
477
|
+
let tail = null;
|
|
478
|
+
function moveToFront(id) {
|
|
479
|
+
const entry = entries.get(id);
|
|
480
|
+
if (!entry) return;
|
|
481
|
+
if (head === id) return;
|
|
482
|
+
if (entry.prev) {
|
|
483
|
+
const prevEntry = entries.get(entry.prev);
|
|
484
|
+
if (prevEntry) prevEntry.next = entry.next;
|
|
485
|
+
}
|
|
486
|
+
if (entry.next) {
|
|
487
|
+
const nextEntry = entries.get(entry.next);
|
|
488
|
+
if (nextEntry) nextEntry.prev = entry.prev;
|
|
489
|
+
}
|
|
490
|
+
if (tail === id) tail = entry.prev;
|
|
491
|
+
entry.prev = null;
|
|
492
|
+
entry.next = head;
|
|
493
|
+
if (head) {
|
|
494
|
+
const headEntry = entries.get(head);
|
|
495
|
+
if (headEntry) headEntry.prev = id;
|
|
496
|
+
}
|
|
497
|
+
head = id;
|
|
498
|
+
if (!tail) tail = id;
|
|
499
|
+
}
|
|
500
|
+
function removeFromList(id) {
|
|
501
|
+
const entry = entries.get(id);
|
|
502
|
+
if (!entry) return;
|
|
503
|
+
if (entry.prev) {
|
|
504
|
+
const prevEntry = entries.get(entry.prev);
|
|
505
|
+
if (prevEntry) prevEntry.next = entry.next;
|
|
506
|
+
} else head = entry.next;
|
|
507
|
+
if (entry.next) {
|
|
508
|
+
const nextEntry = entries.get(entry.next);
|
|
509
|
+
if (nextEntry) nextEntry.prev = entry.prev;
|
|
510
|
+
} else tail = entry.prev;
|
|
511
|
+
}
|
|
512
|
+
function evictLRU() {
|
|
513
|
+
if (!tail) return;
|
|
514
|
+
const idToEvict = tail;
|
|
515
|
+
removeFromList(idToEvict);
|
|
516
|
+
entries.delete(idToEvict);
|
|
517
|
+
}
|
|
518
|
+
function insertAtFront(id, seenAt) {
|
|
519
|
+
const newEntry = {
|
|
520
|
+
seenAt,
|
|
521
|
+
prev: null,
|
|
522
|
+
next: head
|
|
523
|
+
};
|
|
524
|
+
if (head) {
|
|
525
|
+
const headEntry = entries.get(head);
|
|
526
|
+
if (headEntry) headEntry.prev = id;
|
|
527
|
+
}
|
|
528
|
+
entries.set(id, newEntry);
|
|
529
|
+
head = id;
|
|
530
|
+
if (!tail) tail = id;
|
|
531
|
+
}
|
|
532
|
+
function pruneExpired() {
|
|
533
|
+
const now = Date.now();
|
|
534
|
+
const toDelete = [];
|
|
535
|
+
for (const [id, entry] of entries) if (now - entry.seenAt > ttlMs) toDelete.push(id);
|
|
536
|
+
for (const id of toDelete) {
|
|
537
|
+
removeFromList(id);
|
|
538
|
+
entries.delete(id);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
let pruneTimer;
|
|
542
|
+
if (pruneIntervalMs > 0) {
|
|
543
|
+
pruneTimer = setInterval(pruneExpired, pruneIntervalMs);
|
|
544
|
+
if (pruneTimer.unref) pruneTimer.unref();
|
|
545
|
+
}
|
|
546
|
+
function add(id) {
|
|
547
|
+
const now = Date.now();
|
|
548
|
+
const existing = entries.get(id);
|
|
549
|
+
if (existing) {
|
|
550
|
+
existing.seenAt = now;
|
|
551
|
+
moveToFront(id);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
while (entries.size >= maxEntries) evictLRU();
|
|
555
|
+
insertAtFront(id, now);
|
|
556
|
+
}
|
|
557
|
+
function has(id) {
|
|
558
|
+
const entry = entries.get(id);
|
|
559
|
+
if (!entry) {
|
|
560
|
+
add(id);
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
if (Date.now() - entry.seenAt > ttlMs) {
|
|
564
|
+
removeFromList(id);
|
|
565
|
+
entries.delete(id);
|
|
566
|
+
add(id);
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
entry.seenAt = Date.now();
|
|
570
|
+
moveToFront(id);
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
function peek(id) {
|
|
574
|
+
const entry = entries.get(id);
|
|
575
|
+
if (!entry) return false;
|
|
576
|
+
if (Date.now() - entry.seenAt > ttlMs) {
|
|
577
|
+
removeFromList(id);
|
|
578
|
+
entries.delete(id);
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
function deleteEntry(id) {
|
|
584
|
+
if (entries.has(id)) {
|
|
585
|
+
removeFromList(id);
|
|
586
|
+
entries.delete(id);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function clear() {
|
|
590
|
+
entries.clear();
|
|
591
|
+
head = null;
|
|
592
|
+
tail = null;
|
|
593
|
+
}
|
|
594
|
+
function size() {
|
|
595
|
+
return entries.size;
|
|
596
|
+
}
|
|
597
|
+
function stop() {
|
|
598
|
+
if (pruneTimer) {
|
|
599
|
+
clearInterval(pruneTimer);
|
|
600
|
+
pruneTimer = void 0;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function seed(ids) {
|
|
604
|
+
const now = Date.now();
|
|
605
|
+
for (let i = ids.length - 1; i >= 0; i--) {
|
|
606
|
+
const id = ids[i];
|
|
607
|
+
if (!entries.has(id) && entries.size < maxEntries) insertAtFront(id, now);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return {
|
|
611
|
+
has,
|
|
612
|
+
add,
|
|
613
|
+
peek,
|
|
614
|
+
delete: deleteEntry,
|
|
615
|
+
clear,
|
|
616
|
+
size,
|
|
617
|
+
stop,
|
|
618
|
+
seed
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
//#endregion
|
|
919
622
|
//#region extensions/nostr/src/nostr-bus.ts
|
|
920
623
|
const STARTUP_LOOKBACK_SEC = 120;
|
|
624
|
+
const MAX_PERSISTED_EVENT_IDS = 5e3;
|
|
921
625
|
const STATE_PERSIST_DEBOUNCE_MS = 5e3;
|
|
922
|
-
const NOSTR_INGRESS_ENVELOPE_OVERHEAD_BYTES = 16 * 1024;
|
|
923
|
-
const NOSTR_INGRESS_MAX_PENDING_EVENTS = 1e3;
|
|
924
626
|
const DEFAULT_INBOUND_GUARD_POLICY = createDirectDmPreCryptoGuardPolicy();
|
|
925
627
|
const CIRCUIT_BREAKER_THRESHOLD = 5;
|
|
926
628
|
const CIRCUIT_BREAKER_RESET_MS = 3e4;
|
|
927
629
|
const HEALTH_WINDOW_MS = 6e4;
|
|
630
|
+
function createFixedWindowRateLimiter(params) {
|
|
631
|
+
const windowMs = Math.max(1, Math.floor(params.windowMs));
|
|
632
|
+
const maxRequests = Math.max(1, Math.floor(params.maxRequests));
|
|
633
|
+
const maxTrackedKeys = Math.max(1, Math.floor(params.maxTrackedKeys));
|
|
634
|
+
const state = /* @__PURE__ */ new Map();
|
|
635
|
+
const touch = (key, value) => {
|
|
636
|
+
state.delete(key);
|
|
637
|
+
state.set(key, value);
|
|
638
|
+
};
|
|
639
|
+
const prune = (nowMs) => {
|
|
640
|
+
for (const [key, entry] of state) if (nowMs - entry.windowStartMs >= windowMs) state.delete(key);
|
|
641
|
+
while (state.size > maxTrackedKeys) {
|
|
642
|
+
const oldest = state.keys().next().value;
|
|
643
|
+
if (!oldest) break;
|
|
644
|
+
state.delete(oldest);
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
return {
|
|
648
|
+
isRateLimited: (key, nowMs = Date.now()) => {
|
|
649
|
+
if (!key) return false;
|
|
650
|
+
prune(nowMs);
|
|
651
|
+
const existing = state.get(key);
|
|
652
|
+
if (!existing || nowMs - existing.windowStartMs >= windowMs) {
|
|
653
|
+
touch(key, {
|
|
654
|
+
count: 1,
|
|
655
|
+
windowStartMs: nowMs
|
|
656
|
+
});
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
const nextCount = existing.count + 1;
|
|
660
|
+
touch(key, {
|
|
661
|
+
count: nextCount,
|
|
662
|
+
windowStartMs: existing.windowStartMs
|
|
663
|
+
});
|
|
664
|
+
return nextCount > maxRequests;
|
|
665
|
+
},
|
|
666
|
+
size: () => state.size,
|
|
667
|
+
clear: () => state.clear()
|
|
668
|
+
};
|
|
669
|
+
}
|
|
928
670
|
function createCircuitBreaker(relay, metrics, threshold = CIRCUIT_BREAKER_THRESHOLD, resetMs = CIRCUIT_BREAKER_RESET_MS) {
|
|
929
671
|
const state = {
|
|
930
672
|
state: "closed",
|
|
@@ -1020,11 +762,10 @@ function createRelayHealthTracker() {
|
|
|
1020
762
|
* Start the Nostr DM bus - subscribes to NIP-04 encrypted DMs
|
|
1021
763
|
*/
|
|
1022
764
|
async function startNostrBus(options) {
|
|
1023
|
-
const { privateKey, relays = DEFAULT_RELAYS, onMessage, authorizeSender, onError, onEose, onMetric } = options;
|
|
765
|
+
const { privateKey, relays = DEFAULT_RELAYS, onMessage, authorizeSender, onError, onEose, onMetric, maxSeenEntries = 1e5, seenTtlMs = 36e5 } = options;
|
|
1024
766
|
const sk = validatePrivateKey(privateKey);
|
|
1025
767
|
const pk = getPublicKey(sk);
|
|
1026
768
|
const pool = new SimplePool();
|
|
1027
|
-
pool.onRelayConnectionSuccess = options.onConnect;
|
|
1028
769
|
const accountId = options.accountId ?? pk.slice(0, 16);
|
|
1029
770
|
const gatewayStartedAt = Math.floor(Date.now() / 1e3);
|
|
1030
771
|
const guardPolicy = createDirectDmPreCryptoGuardPolicy({
|
|
@@ -1036,32 +777,41 @@ async function startNostrBus(options) {
|
|
|
1036
777
|
}
|
|
1037
778
|
});
|
|
1038
779
|
const metrics = onMetric ? createMetrics(onMetric) : createNoopMetrics();
|
|
780
|
+
const seen = createSeenTracker({
|
|
781
|
+
maxEntries: maxSeenEntries,
|
|
782
|
+
ttlMs: seenTtlMs
|
|
783
|
+
});
|
|
1039
784
|
const circuitBreakers = /* @__PURE__ */ new Map();
|
|
1040
785
|
const healthTracker = createRelayHealthTracker();
|
|
1041
786
|
for (const relay of relays) circuitBreakers.set(relay, createCircuitBreaker(relay, metrics));
|
|
1042
787
|
const state = await readNostrBusState({ accountId });
|
|
1043
788
|
const baseSince = computeSinceTimestamp(state, gatewayStartedAt);
|
|
1044
789
|
const since = Math.max(0, baseSince - STARTUP_LOOKBACK_SEC);
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
write: async (cursor) => {
|
|
1052
|
-
await writeNostrBusState({
|
|
1053
|
-
accountId,
|
|
1054
|
-
lastProcessedAt: cursor,
|
|
1055
|
-
gatewayStartedAt: cursorStartedAt,
|
|
1056
|
-
recentEventIds: []
|
|
1057
|
-
});
|
|
1058
|
-
},
|
|
1059
|
-
onBackgroundError: (error) => onError?.(error, "persist state")
|
|
1060
|
-
});
|
|
1061
|
-
const durableCursor = createNostrDurableCursor({
|
|
1062
|
-
since,
|
|
1063
|
-
replayOverlapSec: STARTUP_LOOKBACK_SEC
|
|
790
|
+
if (state?.recentEventIds?.length) seen.seed(state.recentEventIds);
|
|
791
|
+
await writeNostrBusState({
|
|
792
|
+
accountId,
|
|
793
|
+
lastProcessedAt: state?.lastProcessedAt ?? gatewayStartedAt,
|
|
794
|
+
gatewayStartedAt,
|
|
795
|
+
recentEventIds: state?.recentEventIds ?? []
|
|
1064
796
|
});
|
|
797
|
+
let pendingWrite;
|
|
798
|
+
let lastProcessedAt = state?.lastProcessedAt ?? gatewayStartedAt;
|
|
799
|
+
let recentEventIds = (state?.recentEventIds ?? []).slice(-5e3);
|
|
800
|
+
function scheduleStatePersist(eventCreatedAt, eventId) {
|
|
801
|
+
lastProcessedAt = Math.max(lastProcessedAt, eventCreatedAt);
|
|
802
|
+
recentEventIds.push(eventId);
|
|
803
|
+
if (recentEventIds.length > MAX_PERSISTED_EVENT_IDS) recentEventIds = recentEventIds.slice(-5e3);
|
|
804
|
+
if (pendingWrite) clearTimeout(pendingWrite);
|
|
805
|
+
pendingWrite = setTimeout(() => {
|
|
806
|
+
writeNostrBusState({
|
|
807
|
+
accountId,
|
|
808
|
+
lastProcessedAt,
|
|
809
|
+
gatewayStartedAt,
|
|
810
|
+
recentEventIds
|
|
811
|
+
}).catch((err) => onError?.(err, "persist state"));
|
|
812
|
+
}, STATE_PERSIST_DEBOUNCE_MS);
|
|
813
|
+
}
|
|
814
|
+
const inflight = /* @__PURE__ */ new Set();
|
|
1065
815
|
const perSenderRateLimiter = createFixedWindowRateLimiter({
|
|
1066
816
|
windowMs: guardPolicy.rateLimit.windowMs,
|
|
1067
817
|
maxRequests: guardPolicy.rateLimit.maxPerSenderPerWindow,
|
|
@@ -1075,217 +825,147 @@ async function startNostrBus(options) {
|
|
|
1075
825
|
const updateRateLimiterSizeMetric = () => {
|
|
1076
826
|
metrics.emit("memory.rate_limiter_entries", perSenderRateLimiter.size() + globalRateLimiter.size());
|
|
1077
827
|
};
|
|
1078
|
-
|
|
1079
|
-
updateRateLimiterSizeMetric();
|
|
1080
|
-
if (globalRateLimiter.isRateLimited("global")) {
|
|
1081
|
-
metrics.emit("rate_limit.global");
|
|
1082
|
-
metrics.emit("event.rejected.rate_limited");
|
|
1083
|
-
updateRateLimiterSizeMetric();
|
|
1084
|
-
return true;
|
|
1085
|
-
}
|
|
1086
|
-
updateRateLimiterSizeMetric();
|
|
1087
|
-
return false;
|
|
1088
|
-
};
|
|
1089
|
-
const rejectIfVerifiedSenderRateLimited = (senderPubkey) => {
|
|
1090
|
-
updateRateLimiterSizeMetric();
|
|
1091
|
-
if (perSenderRateLimiter.isRateLimited(senderPubkey)) {
|
|
1092
|
-
metrics.emit("rate_limit.per_sender");
|
|
1093
|
-
metrics.emit("event.rejected.rate_limited");
|
|
1094
|
-
updateRateLimiterSizeMetric();
|
|
1095
|
-
return true;
|
|
1096
|
-
}
|
|
1097
|
-
updateRateLimiterSizeMetric();
|
|
1098
|
-
return false;
|
|
1099
|
-
};
|
|
1100
|
-
async function dispatchEvent(event, lifecycle) {
|
|
1101
|
-
if (event.pubkey === pk) {
|
|
1102
|
-
metrics.emit("event.rejected.self_message");
|
|
1103
|
-
return;
|
|
1104
|
-
}
|
|
1105
|
-
if (event.created_at > Math.floor(Date.now() / 1e3) + guardPolicy.maxFutureSkewSec) {
|
|
1106
|
-
metrics.emit("event.rejected.future");
|
|
1107
|
-
throw new Error(`Nostr event ${event.id} is too far in the future.`);
|
|
1108
|
-
}
|
|
1109
|
-
if (!guardPolicy.allowedKinds.includes(event.kind)) {
|
|
1110
|
-
metrics.emit("event.rejected.wrong_kind");
|
|
1111
|
-
return;
|
|
1112
|
-
}
|
|
1113
|
-
let targetsUs = false;
|
|
1114
|
-
for (const tag of event.tags) if (tag[0] === "p" && tag[1] === pk) {
|
|
1115
|
-
targetsUs = true;
|
|
1116
|
-
break;
|
|
1117
|
-
}
|
|
1118
|
-
if (!targetsUs) {
|
|
1119
|
-
metrics.emit("event.rejected.wrong_kind");
|
|
1120
|
-
return;
|
|
1121
|
-
}
|
|
1122
|
-
const replyTo = async (text) => {
|
|
1123
|
-
await sendEncryptedDm(pool, sk, event.pubkey, text, relays, metrics, circuitBreakers, healthTracker, onError, event.id);
|
|
1124
|
-
};
|
|
1125
|
-
if (Buffer.byteLength(event.content, "utf8") > guardPolicy.maxCiphertextBytes) {
|
|
1126
|
-
if (rejectIfGlobalRateLimited()) throw new Error(`Nostr event ${event.id} hit the global rate limit.`);
|
|
1127
|
-
metrics.emit("event.rejected.oversized_ciphertext");
|
|
1128
|
-
return;
|
|
1129
|
-
}
|
|
1130
|
-
if (rejectIfGlobalRateLimited()) throw new Error(`Nostr event ${event.id} hit the global rate limit.`);
|
|
1131
|
-
if (!verifyEvent(event)) {
|
|
1132
|
-
metrics.emit("event.rejected.invalid_signature");
|
|
1133
|
-
const error = new NostrIngressPermanentError("invalid-signature", `Nostr event ${event.id} has an invalid signature.`);
|
|
1134
|
-
onError?.(error, `event ${event.id}`);
|
|
1135
|
-
throw error;
|
|
1136
|
-
}
|
|
1137
|
-
if (rejectIfVerifiedSenderRateLimited(event.pubkey)) throw new Error(`Nostr sender ${event.pubkey} hit the rate limit.`);
|
|
1138
|
-
if (authorizeSender) {
|
|
1139
|
-
if (await authorizeSender({
|
|
1140
|
-
senderPubkey: event.pubkey,
|
|
1141
|
-
reply: replyTo
|
|
1142
|
-
}) !== "allow") return;
|
|
1143
|
-
}
|
|
1144
|
-
let plaintext;
|
|
828
|
+
async function handleEvent(event) {
|
|
1145
829
|
try {
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
metrics.emit("event.rejected.decrypt_failed");
|
|
1151
|
-
onError?.(error, `decrypt from ${event.pubkey}`);
|
|
1152
|
-
throw new NostrIngressPermanentError("decrypt-failed", `Nostr event ${event.id} could not be decrypted.`, { cause: error });
|
|
1153
|
-
}
|
|
1154
|
-
if (Buffer.byteLength(plaintext, "utf8") > guardPolicy.maxPlaintextBytes) {
|
|
1155
|
-
metrics.emit("event.rejected.oversized_plaintext");
|
|
1156
|
-
return;
|
|
1157
|
-
}
|
|
1158
|
-
if (lifecycle.abortSignal.aborted) throw new Error(`Nostr event ${event.id} stopped before dispatch.`);
|
|
1159
|
-
await onMessage(event.pubkey, plaintext, replyTo, {
|
|
1160
|
-
eventId: event.id,
|
|
1161
|
-
createdAt: event.created_at
|
|
1162
|
-
}, lifecycle);
|
|
1163
|
-
metrics.emit("event.processed");
|
|
1164
|
-
}
|
|
1165
|
-
const dmFilter = {
|
|
1166
|
-
kinds: [4],
|
|
1167
|
-
"#p": [pk],
|
|
1168
|
-
since
|
|
1169
|
-
};
|
|
1170
|
-
const relayAbort = new AbortController();
|
|
1171
|
-
let relaySubscriptions;
|
|
1172
|
-
let relayStopPromise;
|
|
1173
|
-
const stopRelays = (reason) => {
|
|
1174
|
-
relayStopPromise ??= (async () => {
|
|
1175
|
-
relayAbort.abort(reason);
|
|
1176
|
-
try {
|
|
1177
|
-
await relaySubscriptions?.close(reason);
|
|
1178
|
-
} catch (error) {
|
|
1179
|
-
onError?.(error, "close subscription");
|
|
1180
|
-
} finally {
|
|
1181
|
-
try {
|
|
1182
|
-
pool.close(relays);
|
|
1183
|
-
} catch (error) {
|
|
1184
|
-
onError?.(error, "close relay pool");
|
|
1185
|
-
}
|
|
830
|
+
metrics.emit("event.received");
|
|
831
|
+
if (seen.peek(event.id) || inflight.has(event.id)) {
|
|
832
|
+
metrics.emit("event.duplicate");
|
|
833
|
+
return;
|
|
1186
834
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
835
|
+
inflight.add(event.id);
|
|
836
|
+
const markSeen = () => {
|
|
837
|
+
seen.add(event.id);
|
|
838
|
+
metrics.emit("memory.seen_tracker_size", seen.size());
|
|
839
|
+
};
|
|
840
|
+
const rejectAndMarkSeen = (metric) => {
|
|
841
|
+
markSeen();
|
|
842
|
+
metrics.emit(metric);
|
|
843
|
+
};
|
|
844
|
+
if (event.pubkey === pk) {
|
|
845
|
+
rejectAndMarkSeen("event.rejected.self_message");
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
if (event.created_at < since) {
|
|
849
|
+
rejectAndMarkSeen("event.rejected.stale");
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
if (event.created_at > Math.floor(Date.now() / 1e3) + guardPolicy.maxFutureSkewSec) {
|
|
853
|
+
metrics.emit("event.rejected.future");
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
if (!guardPolicy.allowedKinds.includes(event.kind)) {
|
|
857
|
+
rejectAndMarkSeen("event.rejected.wrong_kind");
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
let targetsUs = false;
|
|
861
|
+
for (const t of event.tags) if (t[0] === "p" && t[1] === pk) {
|
|
862
|
+
targetsUs = true;
|
|
863
|
+
break;
|
|
864
|
+
}
|
|
865
|
+
if (!targetsUs) {
|
|
866
|
+
rejectAndMarkSeen("event.rejected.wrong_kind");
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const replyTo = async (text) => {
|
|
870
|
+
await sendEncryptedDm(pool, sk, event.pubkey, text, relays, metrics, circuitBreakers, healthTracker, onError);
|
|
871
|
+
};
|
|
872
|
+
const rejectIfGlobalRateLimited = () => {
|
|
873
|
+
updateRateLimiterSizeMetric();
|
|
874
|
+
if (globalRateLimiter.isRateLimited("global")) {
|
|
1226
875
|
metrics.emit("rate_limit.global");
|
|
1227
876
|
metrics.emit("event.rejected.rate_limited");
|
|
877
|
+
updateRateLimiterSizeMetric();
|
|
878
|
+
return true;
|
|
1228
879
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
880
|
+
updateRateLimiterSizeMetric();
|
|
881
|
+
return false;
|
|
882
|
+
};
|
|
883
|
+
const rejectIfVerifiedSenderRateLimited = () => {
|
|
884
|
+
updateRateLimiterSizeMetric();
|
|
885
|
+
if (perSenderRateLimiter.isRateLimited(event.pubkey)) {
|
|
886
|
+
metrics.emit("rate_limit.per_sender");
|
|
887
|
+
metrics.emit("event.rejected.rate_limited");
|
|
888
|
+
updateRateLimiterSizeMetric();
|
|
889
|
+
return true;
|
|
1235
890
|
}
|
|
891
|
+
updateRateLimiterSizeMetric();
|
|
892
|
+
return false;
|
|
893
|
+
};
|
|
894
|
+
if (Buffer.byteLength(event.content, "utf8") > guardPolicy.maxCiphertextBytes) {
|
|
895
|
+
if (rejectIfGlobalRateLimited()) return;
|
|
896
|
+
rejectAndMarkSeen("event.rejected.oversized_ciphertext");
|
|
1236
897
|
return;
|
|
1237
898
|
}
|
|
1238
|
-
if (
|
|
1239
|
-
|
|
899
|
+
if (rejectIfGlobalRateLimited()) return;
|
|
900
|
+
if (!verifyEvent(event)) {
|
|
901
|
+
rejectAndMarkSeen("event.rejected.invalid_signature");
|
|
902
|
+
onError?.(/* @__PURE__ */ new Error("Invalid signature"), `event ${event.id}`);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
if (rejectIfVerifiedSenderRateLimited()) return;
|
|
906
|
+
if (authorizeSender) {
|
|
907
|
+
if (await authorizeSender({
|
|
908
|
+
senderPubkey: event.pubkey,
|
|
909
|
+
reply: replyTo
|
|
910
|
+
}) !== "allow") {
|
|
911
|
+
markSeen();
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
let plaintext;
|
|
1240
916
|
try {
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
917
|
+
plaintext = decrypt(sk, event.pubkey, event.content);
|
|
918
|
+
metrics.emit("decrypt.success");
|
|
919
|
+
} catch (err) {
|
|
920
|
+
markSeen();
|
|
921
|
+
metrics.emit("decrypt.failure");
|
|
922
|
+
metrics.emit("event.rejected.decrypt_failed");
|
|
923
|
+
onError?.(err, `decrypt from ${event.pubkey}`);
|
|
924
|
+
return;
|
|
1245
925
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
926
|
+
if (Buffer.byteLength(plaintext, "utf8") > guardPolicy.maxPlaintextBytes) {
|
|
927
|
+
markSeen();
|
|
928
|
+
metrics.emit("event.rejected.oversized_plaintext");
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
await onMessage(event.pubkey, plaintext, replyTo, {
|
|
932
|
+
eventId: event.id,
|
|
933
|
+
createdAt: event.created_at
|
|
934
|
+
});
|
|
935
|
+
markSeen();
|
|
936
|
+
metrics.emit("event.processed");
|
|
937
|
+
scheduleStatePersist(event.created_at, event.id);
|
|
938
|
+
} catch (err) {
|
|
939
|
+
onError?.(err, `event ${event.id}`);
|
|
940
|
+
} finally {
|
|
941
|
+
inflight.delete(event.id);
|
|
1248
942
|
}
|
|
943
|
+
}
|
|
944
|
+
const dmFilter = {
|
|
945
|
+
kinds: [4],
|
|
946
|
+
"#p": [pk],
|
|
947
|
+
since
|
|
1249
948
|
};
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
relays,
|
|
1262
|
-
filter: dmFilter,
|
|
1263
|
-
abort: relayAbort.signal,
|
|
1264
|
-
onEvent: (event) => {
|
|
1265
|
-
const task = handleRelayEvent(event);
|
|
1266
|
-
if (options.trackIngressTask) options.trackIngressTask(task.then(() => ingress.waitForIdle()));
|
|
1267
|
-
},
|
|
1268
|
-
onBackfillComplete: (confirmedRelays) => {
|
|
1269
|
-
backfillFinalizePromise ??= ingress.waitForIdle().then(() => {
|
|
1270
|
-
const cursor = durableCursor.markBackfillComplete();
|
|
1271
|
-
if (cursor !== void 0) cursorWriter.schedule(cursor);
|
|
1272
|
-
for (const relay of confirmedRelays) metrics.emit("relay.message.eose", 1, { relay });
|
|
1273
|
-
onEose?.(confirmedRelays.join(", "));
|
|
1274
|
-
}).catch((error) => onError?.(error, "finalize relay backfill"));
|
|
1275
|
-
},
|
|
1276
|
-
onClose: (relay, reasons) => {
|
|
949
|
+
const relayAbort = new AbortController();
|
|
950
|
+
const sub = pool.subscribeMany(relays, dmFilter, {
|
|
951
|
+
onevent: (event) => {
|
|
952
|
+
handleEvent(event);
|
|
953
|
+
},
|
|
954
|
+
oneose: () => {
|
|
955
|
+
for (const relay of relays) metrics.emit("relay.message.eose", 1, { relay });
|
|
956
|
+
onEose?.(relays.join(", "));
|
|
957
|
+
},
|
|
958
|
+
onclose: (reason) => {
|
|
959
|
+
for (const relay of relays) {
|
|
1277
960
|
metrics.emit("relay.message.closed", 1, { relay });
|
|
1278
961
|
options.onDisconnect?.(relay);
|
|
1279
|
-
onError?.(/* @__PURE__ */ new Error(`Subscription closed: ${reasons.join(", ")}`), "subscription");
|
|
1280
962
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
throw error;
|
|
1286
|
-
}
|
|
963
|
+
onError?.(/* @__PURE__ */ new Error(`Subscription closed: ${reason.join(", ")}`), "subscription");
|
|
964
|
+
},
|
|
965
|
+
abort: relayAbort.signal
|
|
966
|
+
});
|
|
1287
967
|
const sendDm = async (toPubkey, text) => {
|
|
1288
|
-
|
|
968
|
+
await sendEncryptedDm(pool, sk, toPubkey, text, relays, metrics, circuitBreakers, healthTracker, onError);
|
|
1289
969
|
};
|
|
1290
970
|
const publishProfile$1 = async (profile) => {
|
|
1291
971
|
const lastPublishedAt = (await readNostrProfileState({ accountId }))?.lastPublishedAt ?? void 0;
|
|
@@ -1309,20 +989,25 @@ async function startNostrBus(options) {
|
|
|
1309
989
|
lastPublishResults: stateLocal?.lastPublishResults ?? null
|
|
1310
990
|
};
|
|
1311
991
|
};
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
992
|
+
return {
|
|
993
|
+
close: () => {
|
|
994
|
+
relayAbort.abort("closed by caller");
|
|
995
|
+
Promise.resolve(sub.close("closed by caller")).catch((err) => onError?.(err, "close subscription")).finally(() => {
|
|
996
|
+
pool.close(relays);
|
|
997
|
+
});
|
|
998
|
+
seen.stop();
|
|
1319
999
|
perSenderRateLimiter.clear();
|
|
1320
1000
|
globalRateLimiter.clear();
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1001
|
+
if (pendingWrite) {
|
|
1002
|
+
clearTimeout(pendingWrite);
|
|
1003
|
+
writeNostrBusState({
|
|
1004
|
+
accountId,
|
|
1005
|
+
lastProcessedAt,
|
|
1006
|
+
gatewayStartedAt,
|
|
1007
|
+
recentEventIds
|
|
1008
|
+
}).catch((err) => onError?.(err, "persist state on close"));
|
|
1009
|
+
}
|
|
1010
|
+
},
|
|
1326
1011
|
publicKey: pk,
|
|
1327
1012
|
sendDm,
|
|
1328
1013
|
getMetrics: () => metrics.getSnapshot(),
|
|
@@ -1333,14 +1018,12 @@ async function startNostrBus(options) {
|
|
|
1333
1018
|
/**
|
|
1334
1019
|
* Send an encrypted DM to a pubkey
|
|
1335
1020
|
*/
|
|
1336
|
-
async function sendEncryptedDm(pool, sk, toPubkey, text, relays, metrics, circuitBreakers, healthTracker, onError
|
|
1021
|
+
async function sendEncryptedDm(pool, sk, toPubkey, text, relays, metrics, circuitBreakers, healthTracker, onError) {
|
|
1337
1022
|
const ciphertext = encrypt(sk, toPubkey, text);
|
|
1338
|
-
const tags = [["p", toPubkey]];
|
|
1339
|
-
if (replyToEventId) tags.push(["e", replyToEventId]);
|
|
1340
1023
|
const reply = finalizeEvent({
|
|
1341
1024
|
kind: 4,
|
|
1342
1025
|
content: ciphertext,
|
|
1343
|
-
tags,
|
|
1026
|
+
tags: [["p", toPubkey]],
|
|
1344
1027
|
created_at: Math.floor(Date.now() / 1e3)
|
|
1345
1028
|
}, sk);
|
|
1346
1029
|
const sortedRelays = healthTracker.getSortedRelays(relays);
|
|
@@ -1350,13 +1033,15 @@ async function sendEncryptedDm(pool, sk, toPubkey, text, relays, metrics, circui
|
|
|
1350
1033
|
if (cb && !cb.canAttempt()) continue;
|
|
1351
1034
|
const startTime = Date.now();
|
|
1352
1035
|
try {
|
|
1353
|
-
|
|
1036
|
+
const publishPromises = pool.publish([relay], reply);
|
|
1037
|
+
if (publishPromises.length === 0) throw new Error(`Failed to create publish promise for relay ${relay}`);
|
|
1038
|
+
await publishPromises[0];
|
|
1354
1039
|
const latency = Date.now() - startTime;
|
|
1355
1040
|
cb?.recordSuccess();
|
|
1356
1041
|
healthTracker.recordSuccess(relay, latency);
|
|
1357
|
-
return
|
|
1042
|
+
return;
|
|
1358
1043
|
} catch (err) {
|
|
1359
|
-
lastError = err
|
|
1044
|
+
lastError = err;
|
|
1360
1045
|
const latency = Date.now() - startTime;
|
|
1361
1046
|
cb?.recordFailure();
|
|
1362
1047
|
healthTracker.recordFailure(relay);
|
|
@@ -1455,7 +1140,7 @@ const startNostrGatewayAccount = async (ctx) => {
|
|
|
1455
1140
|
ctx.log?.debug?.(`[${account.accountId}] blocked Nostr sender ${input.senderId} (${resolved.senderAccess.reasonCode})`);
|
|
1456
1141
|
return "block";
|
|
1457
1142
|
};
|
|
1458
|
-
await
|
|
1143
|
+
await runStoppablePassiveMonitor({
|
|
1459
1144
|
abortSignal: ctx.abortSignal,
|
|
1460
1145
|
start: async () => {
|
|
1461
1146
|
const bus = await startNostrBus({
|
|
@@ -1466,15 +1151,16 @@ const startNostrGatewayAccount = async (ctx) => {
|
|
|
1466
1151
|
senderId: senderPubkey,
|
|
1467
1152
|
reply
|
|
1468
1153
|
}),
|
|
1469
|
-
onMessage: async (senderPubkey, text, reply, meta
|
|
1154
|
+
onMessage: async (senderPubkey, text, reply, meta) => {
|
|
1470
1155
|
const resolvedAccess = await resolveInboundAccess(senderPubkey, text);
|
|
1471
1156
|
if (resolvedAccess.senderAccess.decision !== "allow") {
|
|
1472
1157
|
ctx.log?.warn?.(`[${account.accountId}] dropping Nostr DM after preflight drift (${senderPubkey}, ${resolvedAccess.senderAccess.reasonCode})`);
|
|
1473
1158
|
return;
|
|
1474
1159
|
}
|
|
1475
|
-
const {
|
|
1476
|
-
await
|
|
1160
|
+
const { dispatchInboundDirectDmWithRuntime } = await import("./inbound-direct-dm-runtime-EdLLVtkh.mjs");
|
|
1161
|
+
await dispatchInboundDirectDmWithRuntime({
|
|
1477
1162
|
cfg: ctx.cfg,
|
|
1163
|
+
runtime,
|
|
1478
1164
|
channel: "nostr",
|
|
1479
1165
|
channelLabel: "Nostr",
|
|
1480
1166
|
accountId: account.accountId,
|
|
@@ -1490,7 +1176,6 @@ const startNostrGatewayAccount = async (ctx) => {
|
|
|
1490
1176
|
messageId: meta.eventId,
|
|
1491
1177
|
timestamp: meta.createdAt * 1e3,
|
|
1492
1178
|
commandAuthorized: resolvedAccess.commandAccess.requested ? resolvedAccess.commandAccess.authorized : void 0,
|
|
1493
|
-
turnAdoptionLifecycle: bindIngressLifecycleToReplyOptions(lifecycle).turnAdoptionLifecycle,
|
|
1494
1179
|
deliver: async (payload) => {
|
|
1495
1180
|
const outboundText = payload && typeof payload === "object" && "text" in payload ? payload.text ?? "" : "";
|
|
1496
1181
|
if (!outboundText.trim()) return;
|
|
@@ -1499,8 +1184,7 @@ const startNostrGatewayAccount = async (ctx) => {
|
|
|
1499
1184
|
channel: "nostr",
|
|
1500
1185
|
accountId: account.accountId
|
|
1501
1186
|
});
|
|
1502
|
-
|
|
1503
|
-
if (message) await reply(message);
|
|
1187
|
+
await reply(runtime.channel.text.convertMarkdownTables(outboundText, tableMode));
|
|
1504
1188
|
},
|
|
1505
1189
|
onRecordError: (err) => {
|
|
1506
1190
|
ctx.log?.error?.(`[${account.accountId}] failed recording Nostr inbound session: ${String(err)}`);
|
|
@@ -1530,19 +1214,19 @@ const startNostrGatewayAccount = async (ctx) => {
|
|
|
1530
1214
|
if (busHandle) metricsSnapshots.set(account.accountId, busHandle.getMetrics());
|
|
1531
1215
|
}
|
|
1532
1216
|
});
|
|
1217
|
+
let stopped = false;
|
|
1533
1218
|
busHandle = bus;
|
|
1534
1219
|
activeBuses.set(account.accountId, bus);
|
|
1535
|
-
ctx.log?.info?.(`[${account.accountId}] Nostr provider started
|
|
1536
|
-
return { stop:
|
|
1537
|
-
|
|
1220
|
+
ctx.log?.info?.(`[${account.accountId}] Nostr provider started, connected to ${account.relays.length} relay(s)`);
|
|
1221
|
+
return { stop: () => {
|
|
1222
|
+
if (stopped) return;
|
|
1223
|
+
stopped = true;
|
|
1224
|
+
bus.close();
|
|
1538
1225
|
if (busHandle === bus) busHandle = null;
|
|
1539
1226
|
if (activeBuses.get(account.accountId) === bus) activeBuses.delete(account.accountId);
|
|
1540
1227
|
metricsSnapshots.delete(account.accountId);
|
|
1541
1228
|
ctx.log?.info?.(`[${account.accountId}] Nostr provider stopped`);
|
|
1542
1229
|
} };
|
|
1543
|
-
},
|
|
1544
|
-
stop: async (monitor) => {
|
|
1545
|
-
await monitor.stop();
|
|
1546
1230
|
}
|
|
1547
1231
|
});
|
|
1548
1232
|
};
|
|
@@ -1578,12 +1262,12 @@ const nostrOutboundAdapter = {
|
|
|
1578
1262
|
channel: "nostr",
|
|
1579
1263
|
accountId: aid
|
|
1580
1264
|
});
|
|
1581
|
-
const message =
|
|
1582
|
-
if (!message) throw new Error("Nostr send requires non-empty text after markdown stripping.");
|
|
1265
|
+
const message = core.channel.text.convertMarkdownTables(text ?? "", tableMode);
|
|
1583
1266
|
const normalizedTo = normalizePubkey(to);
|
|
1267
|
+
await bus.sendDm(normalizedTo, message);
|
|
1584
1268
|
return attachChannelToResult("nostr", {
|
|
1585
1269
|
to: normalizedTo,
|
|
1586
|
-
messageId:
|
|
1270
|
+
messageId: `nostr-${Date.now()}`
|
|
1587
1271
|
});
|
|
1588
1272
|
}
|
|
1589
1273
|
};
|
|
@@ -1617,18 +1301,6 @@ function resolveNostrOutboundSessionRoute(params) {
|
|
|
1617
1301
|
}
|
|
1618
1302
|
//#endregion
|
|
1619
1303
|
//#region extensions/nostr/src/channel.ts
|
|
1620
|
-
const NOSTR_TARGET_HINT = "<npub|hex pubkey|nostr:npub...>";
|
|
1621
|
-
function stripNostrTargetPrefix(target) {
|
|
1622
|
-
return target.trim().replace(/^nostr:/i, "");
|
|
1623
|
-
}
|
|
1624
|
-
function normalizeNostrTarget(target) {
|
|
1625
|
-
const cleaned = stripNostrTargetPrefix(target);
|
|
1626
|
-
try {
|
|
1627
|
-
return normalizePubkey(cleaned);
|
|
1628
|
-
} catch {
|
|
1629
|
-
return target.trim();
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
1304
|
const resolveNostrDmPolicy = createScopedDmSecurityResolver({
|
|
1633
1305
|
channelKey: "nostr",
|
|
1634
1306
|
resolvePolicy: (account) => account.config.dmPolicy,
|
|
@@ -1636,7 +1308,13 @@ const resolveNostrDmPolicy = createScopedDmSecurityResolver({
|
|
|
1636
1308
|
policyPathSuffix: "dmPolicy",
|
|
1637
1309
|
defaultPolicy: "pairing",
|
|
1638
1310
|
approveHint: formatPairingApproveHint("nostr"),
|
|
1639
|
-
normalizeEntry:
|
|
1311
|
+
normalizeEntry: (raw) => {
|
|
1312
|
+
try {
|
|
1313
|
+
return normalizePubkey(raw.trim().replace(/^nostr:/i, ""));
|
|
1314
|
+
} catch {
|
|
1315
|
+
return raw.trim();
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1640
1318
|
});
|
|
1641
1319
|
const nostrConfigAdapter = createTopLevelChannelConfigAdapter({
|
|
1642
1320
|
sectionKey: "nostr",
|
|
@@ -1656,35 +1334,17 @@ const nostrConfigAdapter = createTopLevelChannelConfigAdapter({
|
|
|
1656
1334
|
resolveAllowFrom: (account) => account.config.allowFrom,
|
|
1657
1335
|
formatAllowFrom: (allowFrom) => normalizeStringEntries(allowFrom).map((entry) => {
|
|
1658
1336
|
if (entry === "*") return "*";
|
|
1659
|
-
|
|
1337
|
+
try {
|
|
1338
|
+
return normalizePubkey(entry);
|
|
1339
|
+
} catch {
|
|
1340
|
+
return entry;
|
|
1341
|
+
}
|
|
1660
1342
|
}).filter(Boolean)
|
|
1661
1343
|
});
|
|
1662
1344
|
const nostrMessageAdapter = createChannelMessageAdapterFromOutbound({
|
|
1663
1345
|
id: "nostr",
|
|
1664
1346
|
outbound: nostrOutboundAdapter
|
|
1665
1347
|
});
|
|
1666
|
-
const nostrPluginOutboundAdapter = {
|
|
1667
|
-
...nostrOutboundAdapter,
|
|
1668
|
-
resolveTarget: ({ to }) => {
|
|
1669
|
-
const trimmed = to?.trim() ?? "";
|
|
1670
|
-
if (!trimmed) return {
|
|
1671
|
-
ok: false,
|
|
1672
|
-
error: missingTargetError("Nostr", NOSTR_TARGET_HINT)
|
|
1673
|
-
};
|
|
1674
|
-
const normalized = normalizeNostrTarget(trimmed);
|
|
1675
|
-
try {
|
|
1676
|
-
return {
|
|
1677
|
-
ok: true,
|
|
1678
|
-
to: normalizePubkey(normalized)
|
|
1679
|
-
};
|
|
1680
|
-
} catch {
|
|
1681
|
-
return {
|
|
1682
|
-
ok: false,
|
|
1683
|
-
error: /* @__PURE__ */ new Error("Nostr target must be a 64-character hex pubkey or npub value")
|
|
1684
|
-
};
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
};
|
|
1688
1348
|
const nostrPlugin = createChatChannelPlugin({
|
|
1689
1349
|
base: {
|
|
1690
1350
|
id: "nostr",
|
|
@@ -1704,7 +1364,6 @@ const nostrPlugin = createChatChannelPlugin({
|
|
|
1704
1364
|
reload: { configPrefixes: ["channels.nostr"] },
|
|
1705
1365
|
configSchema: buildChannelConfigSchema(NostrConfigSchema),
|
|
1706
1366
|
setup: nostrSetupAdapter,
|
|
1707
|
-
setupContract: nostrSetupContract,
|
|
1708
1367
|
setupWizard: nostrSetupWizard,
|
|
1709
1368
|
config: {
|
|
1710
1369
|
...nostrConfigAdapter,
|
|
@@ -1717,13 +1376,20 @@ const nostrPlugin = createChatChannelPlugin({
|
|
|
1717
1376
|
},
|
|
1718
1377
|
messaging: {
|
|
1719
1378
|
targetPrefixes: ["nostr"],
|
|
1720
|
-
normalizeTarget:
|
|
1379
|
+
normalizeTarget: (target) => {
|
|
1380
|
+
const cleaned = target.trim().replace(/^nostr:/i, "");
|
|
1381
|
+
try {
|
|
1382
|
+
return normalizePubkey(cleaned);
|
|
1383
|
+
} catch {
|
|
1384
|
+
return cleaned;
|
|
1385
|
+
}
|
|
1386
|
+
},
|
|
1721
1387
|
targetResolver: {
|
|
1722
|
-
looksLikeId: (input
|
|
1723
|
-
const trimmed =
|
|
1724
|
-
return trimmed.startsWith("npub1") ||
|
|
1388
|
+
looksLikeId: (input) => {
|
|
1389
|
+
const trimmed = input.trim();
|
|
1390
|
+
return trimmed.startsWith("npub1") || /^[0-9a-fA-F]{64}$/.test(trimmed);
|
|
1725
1391
|
},
|
|
1726
|
-
hint:
|
|
1392
|
+
hint: "<npub|hex pubkey|nostr:npub...>"
|
|
1727
1393
|
},
|
|
1728
1394
|
resolveOutboundSessionRoute: (params) => resolveNostrOutboundSessionRoute(params)
|
|
1729
1395
|
},
|
|
@@ -1748,7 +1414,7 @@ const nostrPlugin = createChatChannelPlugin({
|
|
|
1748
1414
|
},
|
|
1749
1415
|
pairing: { text: nostrPairingTextAdapter },
|
|
1750
1416
|
security: { resolveDmPolicy: resolveNostrDmPolicy },
|
|
1751
|
-
outbound:
|
|
1417
|
+
outbound: nostrOutboundAdapter
|
|
1752
1418
|
});
|
|
1753
1419
|
/**
|
|
1754
1420
|
* Publish a profile (kind:0) for a Nostr account.
|
|
@@ -1774,4 +1440,4 @@ async function getNostrProfileState(accountId = DEFAULT_ACCOUNT_ID) {
|
|
|
1774
1440
|
return bus.getProfileState();
|
|
1775
1441
|
}
|
|
1776
1442
|
//#endregion
|
|
1777
|
-
export {
|
|
1443
|
+
export { setNostrRuntime as a, getNostrRuntime as i, nostrPlugin as n, contentToProfile as o, publishNostrProfile as r, getNostrProfileState as t };
|