@openclaw/nostr 2026.7.1-beta.2 → 2026.7.1-beta.4

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.
Files changed (114) hide show
  1. package/dist/api.js +5 -16
  2. package/dist/{channel-CV0aX1uq.js → channel-B2Y66pl3.js} +11 -4
  3. package/dist/channel-plugin-api.js +1 -1
  4. package/dist/doctor-contract-api.js +3 -22
  5. package/dist/setup-api.js +1 -1
  6. package/dist/setup-plugin-api.js +1 -1
  7. package/dist/{setup-surface-BqBa2C0K.js → setup-surface-CKamwGa3.js} +1 -1
  8. package/node_modules/nostr-tools/README.md +37 -15
  9. package/node_modules/nostr-tools/lib/cjs/abstract-pool.js +134 -6
  10. package/node_modules/nostr-tools/lib/cjs/abstract-pool.js.map +4 -4
  11. package/node_modules/nostr-tools/lib/cjs/abstract-relay.js +35 -6
  12. package/node_modules/nostr-tools/lib/cjs/abstract-relay.js.map +2 -2
  13. package/node_modules/nostr-tools/lib/cjs/filter.js.map +1 -1
  14. package/node_modules/nostr-tools/lib/cjs/index.js +401 -72
  15. package/node_modules/nostr-tools/lib/cjs/index.js.map +4 -4
  16. package/node_modules/nostr-tools/lib/cjs/kinds.js +196 -4
  17. package/node_modules/nostr-tools/lib/cjs/kinds.js.map +2 -2
  18. package/node_modules/nostr-tools/lib/cjs/nip10.js +5 -4
  19. package/node_modules/nostr-tools/lib/cjs/nip10.js.map +2 -2
  20. package/node_modules/nostr-tools/lib/cjs/nip17.js.map +2 -2
  21. package/node_modules/nostr-tools/lib/cjs/nip18.js.map +2 -2
  22. package/node_modules/nostr-tools/lib/cjs/nip19.js +2 -0
  23. package/node_modules/nostr-tools/lib/cjs/nip19.js.map +2 -2
  24. package/node_modules/nostr-tools/lib/cjs/nip21.js +2 -0
  25. package/node_modules/nostr-tools/lib/cjs/nip21.js.map +2 -2
  26. package/node_modules/nostr-tools/lib/cjs/nip22.js +11 -5
  27. package/node_modules/nostr-tools/lib/cjs/nip22.js.map +2 -2
  28. package/node_modules/nostr-tools/lib/cjs/nip25.js.map +1 -1
  29. package/node_modules/nostr-tools/lib/cjs/nip27.js +2 -0
  30. package/node_modules/nostr-tools/lib/cjs/nip27.js.map +2 -2
  31. package/node_modules/nostr-tools/lib/cjs/nip28.js.map +2 -2
  32. package/node_modules/nostr-tools/lib/cjs/nip29.js +2 -0
  33. package/node_modules/nostr-tools/lib/cjs/nip29.js.map +2 -2
  34. package/node_modules/nostr-tools/lib/cjs/nip42.js.map +2 -2
  35. package/node_modules/nostr-tools/lib/cjs/nip45.js +183 -0
  36. package/node_modules/nostr-tools/lib/cjs/nip45.js.map +7 -0
  37. package/node_modules/nostr-tools/lib/cjs/nip46.js +149 -9
  38. package/node_modules/nostr-tools/lib/cjs/nip46.js.map +4 -4
  39. package/node_modules/nostr-tools/lib/cjs/nip47.js.map +2 -2
  40. package/node_modules/nostr-tools/lib/cjs/nip49.js.map +2 -2
  41. package/node_modules/nostr-tools/lib/cjs/nip57.js.map +1 -1
  42. package/node_modules/nostr-tools/lib/cjs/nip58.js.map +2 -2
  43. package/node_modules/nostr-tools/lib/cjs/nip59.js.map +2 -2
  44. package/node_modules/nostr-tools/lib/cjs/nip75.js.map +2 -2
  45. package/node_modules/nostr-tools/lib/cjs/nip94.js.map +2 -2
  46. package/node_modules/nostr-tools/lib/cjs/nip98.js.map +2 -2
  47. package/node_modules/nostr-tools/lib/cjs/nip99.js.map +2 -2
  48. package/node_modules/nostr-tools/lib/cjs/nipb0.js +1064 -0
  49. package/node_modules/nostr-tools/lib/cjs/nipb0.js.map +7 -0
  50. package/node_modules/nostr-tools/lib/cjs/pool.js +134 -6
  51. package/node_modules/nostr-tools/lib/cjs/pool.js.map +4 -4
  52. package/node_modules/nostr-tools/lib/cjs/references.js +2 -0
  53. package/node_modules/nostr-tools/lib/cjs/references.js.map +2 -2
  54. package/node_modules/nostr-tools/lib/cjs/relay.js +35 -6
  55. package/node_modules/nostr-tools/lib/cjs/relay.js.map +2 -2
  56. package/node_modules/nostr-tools/lib/esm/abstract-pool.js +134 -6
  57. package/node_modules/nostr-tools/lib/esm/abstract-pool.js.map +4 -4
  58. package/node_modules/nostr-tools/lib/esm/abstract-relay.js +35 -6
  59. package/node_modules/nostr-tools/lib/esm/abstract-relay.js.map +2 -2
  60. package/node_modules/nostr-tools/lib/esm/filter.js.map +1 -1
  61. package/node_modules/nostr-tools/lib/esm/index.js +377 -48
  62. package/node_modules/nostr-tools/lib/esm/index.js.map +4 -4
  63. package/node_modules/nostr-tools/lib/esm/kinds.js +196 -4
  64. package/node_modules/nostr-tools/lib/esm/kinds.js.map +2 -2
  65. package/node_modules/nostr-tools/lib/esm/nip10.js +5 -4
  66. package/node_modules/nostr-tools/lib/esm/nip10.js.map +2 -2
  67. package/node_modules/nostr-tools/lib/esm/nip17.js.map +2 -2
  68. package/node_modules/nostr-tools/lib/esm/nip18.js.map +2 -2
  69. package/node_modules/nostr-tools/lib/esm/nip19.js +2 -0
  70. package/node_modules/nostr-tools/lib/esm/nip19.js.map +2 -2
  71. package/node_modules/nostr-tools/lib/esm/nip21.js +2 -0
  72. package/node_modules/nostr-tools/lib/esm/nip21.js.map +2 -2
  73. package/node_modules/nostr-tools/lib/esm/nip22.js +11 -5
  74. package/node_modules/nostr-tools/lib/esm/nip22.js.map +2 -2
  75. package/node_modules/nostr-tools/lib/esm/nip25.js.map +1 -1
  76. package/node_modules/nostr-tools/lib/esm/nip27.js +2 -0
  77. package/node_modules/nostr-tools/lib/esm/nip27.js.map +2 -2
  78. package/node_modules/nostr-tools/lib/esm/nip28.js.map +2 -2
  79. package/node_modules/nostr-tools/lib/esm/nip29.js +2 -0
  80. package/node_modules/nostr-tools/lib/esm/nip29.js.map +2 -2
  81. package/node_modules/nostr-tools/lib/esm/nip42.js.map +2 -2
  82. package/node_modules/nostr-tools/lib/esm/nip45.js +162 -0
  83. package/node_modules/nostr-tools/lib/esm/nip45.js.map +7 -0
  84. package/node_modules/nostr-tools/lib/esm/nip46.js +149 -9
  85. package/node_modules/nostr-tools/lib/esm/nip46.js.map +4 -4
  86. package/node_modules/nostr-tools/lib/esm/nip47.js.map +2 -2
  87. package/node_modules/nostr-tools/lib/esm/nip49.js.map +2 -2
  88. package/node_modules/nostr-tools/lib/esm/nip57.js.map +1 -1
  89. package/node_modules/nostr-tools/lib/esm/nip58.js.map +2 -2
  90. package/node_modules/nostr-tools/lib/esm/nip59.js.map +2 -2
  91. package/node_modules/nostr-tools/lib/esm/nip75.js.map +2 -2
  92. package/node_modules/nostr-tools/lib/esm/nip94.js.map +2 -2
  93. package/node_modules/nostr-tools/lib/esm/nip98.js.map +2 -2
  94. package/node_modules/nostr-tools/lib/esm/nip99.js.map +2 -2
  95. package/node_modules/nostr-tools/lib/esm/nipb0.js +1049 -0
  96. package/node_modules/nostr-tools/lib/esm/nipb0.js.map +7 -0
  97. package/node_modules/nostr-tools/lib/esm/pool.js +134 -6
  98. package/node_modules/nostr-tools/lib/esm/pool.js.map +4 -4
  99. package/node_modules/nostr-tools/lib/esm/references.js +2 -0
  100. package/node_modules/nostr-tools/lib/esm/references.js.map +2 -2
  101. package/node_modules/nostr-tools/lib/esm/relay.js +35 -6
  102. package/node_modules/nostr-tools/lib/esm/relay.js.map +2 -2
  103. package/node_modules/nostr-tools/lib/nostr.bundle.js +350 -23
  104. package/node_modules/nostr-tools/lib/nostr.bundle.js.map +4 -4
  105. package/node_modules/nostr-tools/lib/types/abstract-pool.d.ts +9 -0
  106. package/node_modules/nostr-tools/lib/types/abstract-relay.d.ts +10 -1
  107. package/node_modules/nostr-tools/lib/types/kinds.d.ts +200 -8
  108. package/node_modules/nostr-tools/lib/types/nip45.d.ts +13 -0
  109. package/node_modules/nostr-tools/lib/types/nip45.test.d.ts +1 -0
  110. package/node_modules/nostr-tools/lib/types/nip46.d.ts +12 -5
  111. package/node_modules/nostr-tools/lib/types/nipb0.d.ts +129 -0
  112. package/node_modules/nostr-tools/package.json +13 -1
  113. package/npm-shrinkwrap.json +7 -7
  114. package/package.json +5 -5
package/dist/api.js CHANGED
@@ -1,11 +1,12 @@
1
- import { o as resolveNostrAccount } from "./setup-surface-BqBa2C0K.js";
1
+ import { o as resolveNostrAccount } from "./setup-surface-CKamwGa3.js";
2
2
  import { getPluginRuntimeGatewayRequestScope } from "./runtime-api.js";
3
3
  import { n as NostrProfileSchema } from "./config-schema-DIiXiBKr.js";
4
- import { a as setNostrRuntime, i as getNostrRuntime, n as nostrPlugin, o as contentToProfile, r as publishNostrProfile, t as getNostrProfileState } from "./channel-CV0aX1uq.js";
4
+ import { a as setNostrRuntime, i as getNostrRuntime, n as nostrPlugin, o as contentToProfile, r as publishNostrProfile, t as getNostrProfileState } from "./channel-B2Y66pl3.js";
5
5
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
6
6
  import { z } from "zod";
7
7
  import { SimplePool, verifyEvent } from "nostr-tools";
8
8
  import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
9
+ import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
9
10
  import { readJsonBodyWithLimit, requestBodyErrorToText } from "openclaw/plugin-sdk/webhook-request-guards";
10
11
  import { createFixedWindowRateLimiter } from "openclaw/plugin-sdk/webhook-ingress";
11
12
  import { isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
@@ -199,21 +200,9 @@ const profileRateLimiter = createFixedWindowRateLimiter({
199
200
  function checkRateLimit(accountId) {
200
201
  return !profileRateLimiter.isRateLimited(accountId);
201
202
  }
202
- const publishLocks = /* @__PURE__ */ new Map();
203
+ const publishLocks = new KeyedAsyncQueue();
203
204
  async function withPublishLock(accountId, fn) {
204
- const prev = publishLocks.get(accountId) ?? Promise.resolve();
205
- let resolve;
206
- const next = new Promise((r) => {
207
- resolve = r;
208
- });
209
- publishLocks.set(accountId, next);
210
- await prev.catch(() => {});
211
- try {
212
- return await fn();
213
- } finally {
214
- resolve();
215
- if (publishLocks.get(accountId) === next) publishLocks.delete(accountId);
216
- }
205
+ return await publishLocks.enqueue(accountId, fn);
217
206
  }
218
207
  const nip05FormatSchema = z.string().regex(/^[a-z0-9._-]+@[a-z0-9.-]+\.[a-z]{2,}$/i, "Invalid NIP-05 format (user@domain.com)").optional();
219
208
  const lud16FormatSchema = z.string().regex(/^[a-z0-9._-]+@[a-z0-9.-]+\.[a-z]{2,}$/i, "Invalid Lightning address format").optional();
@@ -1,4 +1,4 @@
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-BqBa2C0K.js";
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-CKamwGa3.js";
2
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.js";
3
3
  import { i as DEFAULT_RELAYS } from "./setup-adapter-DEU3o8MF.js";
4
4
  import { t as normalizeNostrStateAccountId } from "./state-account-id-CvBZ9s6P.js";
@@ -969,7 +969,8 @@ async function startNostrBus(options) {
969
969
  await sendEncryptedDm(pool, sk, toPubkey, text, relays, metrics, circuitBreakers, healthTracker, onError);
970
970
  };
971
971
  const publishProfile$1 = async (profile) => {
972
- const result = await publishProfile(pool, sk, relays, profile, (await readNostrProfileState({ accountId }))?.lastPublishedAt ?? void 0);
972
+ const lastPublishedAt = (await readNostrProfileState({ accountId }))?.lastPublishedAt ?? void 0;
973
+ const result = await publishProfile(pool, sk, relays, profile, lastPublishedAt);
973
974
  const publishResults = {};
974
975
  for (const relay of result.successes) publishResults[relay] = "ok";
975
976
  for (const { relay, error } of result.failures) publishResults[relay] = error === "timeout" ? "timeout" : "failed";
@@ -1276,13 +1277,19 @@ function getActiveNostrBuses() {
1276
1277
  //#endregion
1277
1278
  //#region extensions/nostr/src/session-route.ts
1278
1279
  function resolveNostrOutboundSessionRoute(params) {
1279
- const target = stripChannelTargetPrefix(params.target, "nostr");
1280
- if (!target) return null;
1280
+ const rawTarget = stripChannelTargetPrefix(params.target, "nostr");
1281
+ let target;
1282
+ try {
1283
+ target = normalizePubkey(rawTarget);
1284
+ } catch {
1285
+ return null;
1286
+ }
1281
1287
  return buildChannelOutboundSessionRoute({
1282
1288
  cfg: params.cfg,
1283
1289
  agentId: params.agentId,
1284
1290
  channel: "nostr",
1285
1291
  accountId: params.accountId,
1292
+ recipientSessionExact: true,
1286
1293
  peer: {
1287
1294
  kind: "direct",
1288
1295
  id: target
@@ -1,2 +1,2 @@
1
- import { n as nostrPlugin } from "./channel-CV0aX1uq.js";
1
+ import { n as nostrPlugin } from "./channel-B2Y66pl3.js";
2
2
  export { nostrPlugin };
@@ -1,6 +1,7 @@
1
1
  import { t as normalizeNostrStateAccountId } from "./state-account-id-CvBZ9s6P.js";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import { archiveLegacyStateSource } from "openclaw/plugin-sdk/runtime-doctor";
4
5
  //#region extensions/nostr/doctor-contract-api.ts
5
6
  const BUS_STATE_NAMESPACE = "bus-state";
6
7
  const PROFILE_STATE_NAMESPACE = "profile-state";
@@ -35,13 +36,6 @@ function parseProfileState(value) {
35
36
  lastPublishResults: rawResults === null || Object.keys(lastPublishResults).length === 0 ? null : lastPublishResults
36
37
  };
37
38
  }
38
- async function fileExists(filePath) {
39
- try {
40
- return (await fs.stat(filePath)).isFile();
41
- } catch {
42
- return false;
43
- }
44
- }
45
39
  async function readJsonFile(filePath) {
46
40
  return JSON.parse(await fs.readFile(filePath, "utf8"));
47
41
  }
@@ -70,19 +64,6 @@ async function listLegacyFiles(params) {
70
64
  }
71
65
  return files;
72
66
  }
73
- async function archiveLegacySource(params) {
74
- const archivedPath = `${params.filePath}.migrated`;
75
- if (await fileExists(archivedPath)) {
76
- params.warnings.push(`Left migrated ${params.label} source in place because ${archivedPath} already exists`);
77
- return;
78
- }
79
- try {
80
- await fs.rename(params.filePath, archivedPath);
81
- params.changes.push(`Archived ${params.label} legacy source -> ${archivedPath}`);
82
- } catch (err) {
83
- params.warnings.push(`Failed archiving ${params.label} legacy source: ${String(err)}`);
84
- }
85
- }
86
67
  async function ensureStoreCapacity(params) {
87
68
  const existingKeys = new Set((await params.store.entries()).map((entry) => entry.key));
88
69
  const missingKeys = new Set(params.files.map((file) => file.accountId).filter((key) => !existingKeys.has(key)));
@@ -134,7 +115,7 @@ const stateMigrations = [{
134
115
  existingKeys.add(file.accountId);
135
116
  imported++;
136
117
  }
137
- await archiveLegacySource({
118
+ await archiveLegacyStateSource({
138
119
  filePath: file.filePath,
139
120
  label: "Nostr bus state",
140
121
  changes,
@@ -189,7 +170,7 @@ const stateMigrations = [{
189
170
  existingKeys.add(file.accountId);
190
171
  imported++;
191
172
  }
192
- await archiveLegacySource({
173
+ await archiveLegacyStateSource({
193
174
  filePath: file.filePath,
194
175
  label: "Nostr profile state",
195
176
  changes,
package/dist/setup-api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as nostrSetupWizard, t as nostrSetupAdapter } from "./setup-surface-BqBa2C0K.js";
1
+ import { n as nostrSetupWizard, t as nostrSetupAdapter } from "./setup-surface-CKamwGa3.js";
2
2
  export { nostrSetupAdapter, nostrSetupWizard };
@@ -51,7 +51,7 @@ const nostrSetupAdapter = createNostrSetupAdapter({
51
51
  });
52
52
  const nostrSetupWizard = createDelegatedSetupWizardProxy({
53
53
  channel,
54
- loadWizard: async () => (await import("./setup-surface-BqBa2C0K.js").then((n) => n.r)).nostrSetupWizard,
54
+ loadWizard: async () => (await import("./setup-surface-CKamwGa3.js").then((n) => n.r)).nostrSetupWizard,
55
55
  status: { ...createStandardChannelSetupStatus({
56
56
  channelLabel: "Nostr",
57
57
  configuredLabel: t("wizard.channels.statusConfigured"),
@@ -30,7 +30,7 @@ function validatePrivateKey(key) {
30
30
  return decoded.data;
31
31
  }
32
32
  if (!/^[0-9a-fA-F]{64}$/.test(trimmed)) throw new Error("Private key must be 64 hex characters or nsec bech32 format");
33
- const bytes = new Uint8Array(32);
33
+ const bytes = /* @__PURE__ */ new Uint8Array(32);
34
34
  for (let i = 0; i < 32; i++) bytes[i] = Number.parseInt(trimmed.slice(i * 2, i * 2 + 2), 16);
35
35
  return bytes;
36
36
  }
@@ -130,34 +130,56 @@ import WebSocket from 'ws'
130
130
  useWebSocketImplementation(WebSocket)
131
131
  ```
132
132
 
133
- #### enablePing
133
+ ### Authenticating with relays (NIP-42)
134
134
 
135
- You can enable regular pings of connected relays with the `enablePing` option. This will set up a heartbeat that closes the websocket if it doesn't receive a response in time. Some platforms, like Node.js, don't report websocket disconnections due to network issues, and enabling this can increase the reliability of the `onclose` event.
135
+ Some relays will return a `CLOSED` message with an `"auth-required:"` prefix in order to signal that such request requires authentication.
136
136
 
137
- ```js
138
- import { SimplePool } from '@nostr/tools/pool'
139
-
140
- const pool = new SimplePool({ enablePing: true })
141
- ```
137
+ Upon receiving that you're supposed to send an `AUTH` message and restart the subscription.
142
138
 
143
- #### enableReconnect
139
+ **Using `SimplePool`:**
144
140
 
145
- You can also enable automatic reconnection with the `enableReconnect` option. This will make the pool try to reconnect to relays with an exponential backoff delay if the connection is lost unexpectedly.
141
+ `subscribeMany`, `subscribeManyEose` and `subscribeEose` accept `onauth` option. It signs challenge and resubscribes automatically for you:
146
142
 
147
143
  ```js
148
144
  import { SimplePool } from '@nostr/tools/pool'
149
145
 
150
- const pool = new SimplePool({ enableReconnect: true })
146
+ const pool = new SimplePool()
147
+
148
+ pool.subscribeMany(
149
+ ['wss://myrelay.com'],
150
+ [{ '#t': ['restricted'] }],
151
+ {
152
+ onevent(event) { console.log('got event:', event) },
153
+ onauth: (eventTemplate) => window.nostr.signEvent(eventTemplate),
154
+ }
155
+ )
151
156
  ```
152
157
 
153
- Using both `enablePing: true` and `enableReconnect: true` is recommended as it will improve the reliability and timeliness of the reconnection (at the expense of slighly higher bandwidth due to the ping messages).
158
+ **Using `Relay` directly:**
159
+
160
+ Unrecommended unless you really need fine-grained control over each `Relay` object:
154
161
 
155
162
  ```js
156
- // on Node.js
157
- const pool = new SimplePool({ enablePing: true, enableReconnect: true })
158
- ```
163
+ import { Relay } from '@nostr/tools/relay'
164
+
165
+ const relay = await Relay.connect('wss://myrelay.com')
166
+ let alreadyAuthenticatedOnce = false
167
+ const subscribe = () => {
168
+ relay.subscribe([{ '#t': ['restricted'] }], {
169
+ onevent(event) { console.log(event) },
170
+ onclose(reason) {
171
+ if (reason.startsWith('auth-required:')) {
172
+ if (alreadyAuthenticatedOnce) return
173
+ await relay.auth((eventTemplate) = window.nostr.signEvent(eventTemplate))
174
+ alreadyAuthenticatedOnce = true
175
+ subscribe()
176
+ }
177
+ }
178
+ })
179
+ }
159
180
 
160
- When reconnecting, all existing subscriptions will have their filters automatically updated with `since:` set to the timestamp of the last event received on them `+1`, then restarted.
181
+ subscribe()
182
+ ```
161
183
 
162
184
  ### Parsing references (mentions) from a content based on NIP-27
163
185
 
@@ -227,7 +227,9 @@ var AbstractRelay = class {
227
227
  connectionTimeoutHandle = setTimeout(() => {
228
228
  reject("connection timed out");
229
229
  this.connectionPromise = void 0;
230
- this.skipReconnection = true;
230
+ if (this.reconnectAttempts === 0) {
231
+ this.skipReconnection = true;
232
+ }
231
233
  this.onclose?.();
232
234
  this.handleHardClose("relay connection timed out");
233
235
  }, opts.timeout);
@@ -271,7 +273,9 @@ var AbstractRelay = class {
271
273
  clearTimeout(connectionTimeoutHandle);
272
274
  reject("connection failed");
273
275
  this.connectionPromise = void 0;
274
- this.skipReconnection = true;
276
+ if (this.reconnectAttempts === 0) {
277
+ this.skipReconnection = true;
278
+ }
275
279
  this.onclose?.();
276
280
  this.handleHardClose("relay connection failed");
277
281
  };
@@ -372,19 +376,38 @@ var AbstractRelay = class {
372
376
  }, this.publishTimeout);
373
377
  this.openEventPublishes.set(event.id, { resolve, reject, timeout });
374
378
  });
375
- this.send('["EVENT",' + JSON.stringify(event) + "]");
379
+ try {
380
+ await this.send('["EVENT",' + JSON.stringify(event) + "]");
381
+ } catch (err) {
382
+ const ep = this.openEventPublishes.get(event.id);
383
+ if (ep) {
384
+ ep.reject(err);
385
+ this.openEventPublishes.delete(event.id);
386
+ }
387
+ }
376
388
  this.ongoingOperations--;
377
389
  if (this.ongoingOperations === 0)
378
390
  this.idleSince = Date.now();
379
391
  return ret;
380
392
  }
381
393
  async count(filters, params) {
394
+ return (await this.countWithHLL(filters, params)).count;
395
+ }
396
+ async countWithHLL(filters, params) {
382
397
  this.serial++;
383
398
  const id = params?.id || "count:" + this.serial;
384
399
  const ret = new Promise((resolve, reject) => {
385
400
  this.openCountRequests.set(id, { resolve, reject });
386
401
  });
387
- this.send('["COUNT","' + id + '",' + JSON.stringify(filters).substring(1));
402
+ try {
403
+ await this.send('["COUNT","' + id + '",' + JSON.stringify(filters).substring(1));
404
+ } catch (err) {
405
+ const cr = this.openCountRequests.get(id);
406
+ if (cr) {
407
+ cr.reject(err);
408
+ this.openCountRequests.delete(id);
409
+ }
410
+ }
388
411
  return ret;
389
412
  }
390
413
  subscribe(filters, params) {
@@ -462,7 +485,7 @@ var AbstractRelay = class {
462
485
  const payload = data[2];
463
486
  const cr = this.openCountRequests.get(id);
464
487
  if (cr) {
465
- cr.resolve(payload.count);
488
+ cr.resolve(payload);
466
489
  this.openCountRequests.delete(id);
467
490
  }
468
491
  return;
@@ -492,8 +515,14 @@ var AbstractRelay = class {
492
515
  case "CLOSED": {
493
516
  const id = data[1];
494
517
  const so = this.openSubs.get(id);
495
- if (!so)
518
+ if (!so) {
519
+ const cr = this.openCountRequests.get(id);
520
+ if (cr) {
521
+ cr.reject(new Error(data[2]));
522
+ this.openCountRequests.delete(id);
523
+ }
496
524
  return;
525
+ }
497
526
  so.closed = true;
498
527
  so.close(data[2]);
499
528
  return;
@@ -602,6 +631,63 @@ var alwaysTrue = (t) => {
602
631
  return true;
603
632
  };
604
633
 
634
+ // nip45.ts
635
+ var import_sha2 = require("@noble/hashes/sha2.js");
636
+ var import_utils3 = require("@noble/hashes/utils.js");
637
+ var M = 256;
638
+ var HLL_HEX_LENGTH = M * 2;
639
+ var utf8Encoder2 = new TextEncoder();
640
+ function getCountManyFilter(target, directive) {
641
+ switch (directive) {
642
+ case "reactions":
643
+ return { "#e": [target], kinds: [7] };
644
+ case "reposts":
645
+ return { "#e": [target], kinds: [6] };
646
+ case "quotes":
647
+ return { "#q": [target], kinds: [1, 1111] };
648
+ case "replies":
649
+ return { "#e": [target], kinds: [1] };
650
+ case "comments":
651
+ return { "#E": [target], kinds: [1111] };
652
+ case "followers":
653
+ return { "#p": [target], kinds: [3] };
654
+ }
655
+ }
656
+ function newHll() {
657
+ return new Uint8Array(M);
658
+ }
659
+ function hllDecode(hex) {
660
+ if (hex.length !== HLL_HEX_LENGTH || !/^[0-9a-fA-F]+$/.test(hex))
661
+ return void 0;
662
+ const registers = new Uint8Array(M);
663
+ for (let i = 0; i < M; i++) {
664
+ registers[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
665
+ }
666
+ return registers;
667
+ }
668
+ function hllEncode(registers) {
669
+ if (registers.length !== M)
670
+ throw new Error(`invalid number of registers ${registers.length}`);
671
+ let hex = "";
672
+ for (let i = 0; i < M; i++) {
673
+ hex += registers[i].toString(16).padStart(2, "0");
674
+ }
675
+ return hex;
676
+ }
677
+ function mergeHll(target, source) {
678
+ if (target.length === 0)
679
+ target = newHll();
680
+ if (target.length !== M)
681
+ throw new Error(`invalid number of registers ${target.length}`);
682
+ if (source.length !== M)
683
+ throw new Error(`invalid number of registers ${source.length}`);
684
+ for (let i = 0; i < M; i++) {
685
+ if (source[i] > target[i])
686
+ target[i] = source[i];
687
+ }
688
+ return target;
689
+ }
690
+
605
691
  // abstract-pool.ts
606
692
  var AbstractSimplePool = class {
607
693
  relays = /* @__PURE__ */ new Map();
@@ -829,6 +915,48 @@ var AbstractSimplePool = class {
829
915
  events.sort((a, b) => b.created_at - a.created_at);
830
916
  return events[0] || null;
831
917
  }
918
+ async countMany(relays, target, directive, params) {
919
+ const filter = getCountManyFilter(target, directive);
920
+ const urls = [];
921
+ for (let i = 0; i < relays.length; i++) {
922
+ const url = normalizeURL(relays[i]);
923
+ if (urls.indexOf(url) === -1)
924
+ urls.push(url);
925
+ }
926
+ const responses = await Promise.all(
927
+ urls.map(async (url) => {
928
+ if (this.allowConnectingToRelay?.(url, ["read", [filter]]) === false)
929
+ return null;
930
+ let relay;
931
+ try {
932
+ relay = await this.ensureRelay(url, {
933
+ connectionTimeout: this.maxWaitForConnection < (params?.maxWait || 0) ? Math.max(params.maxWait * 0.8, params.maxWait - 1e3) : this.maxWaitForConnection,
934
+ abort: params?.abort
935
+ });
936
+ } catch (err) {
937
+ this.onRelayConnectionFailure?.(url);
938
+ return null;
939
+ }
940
+ this.onRelayConnectionSuccess?.(url);
941
+ return relay.countWithHLL([filter], { id: params?.id }).catch(() => null);
942
+ })
943
+ );
944
+ let count = 0;
945
+ let hll;
946
+ for (const response of responses) {
947
+ if (!response)
948
+ continue;
949
+ if (response.count > count)
950
+ count = response.count;
951
+ if (!response.hll || response.hll.length !== 512)
952
+ continue;
953
+ const registers = hllDecode(response.hll);
954
+ if (!registers)
955
+ continue;
956
+ hll = mergeHll(hll || new Uint8Array(0), registers);
957
+ }
958
+ return hll ? { count, hll: hllEncode(hll) } : { count };
959
+ }
832
960
  publish(relays, event, params) {
833
961
  return relays.map(normalizeURL).map(async (url, i, arr) => {
834
962
  if (arr.indexOf(url) !== i) {