@openclaw/nostr 2026.2.2 → 2026.2.9
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/CHANGELOG.md +24 -0
- package/package.json +1 -2
- package/src/channel.ts +22 -11
- package/src/nostr-bus.ts +22 -18
- package/src/nostr-profile-http.ts +37 -10
- package/src/nostr-profile-import.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026.2.6-3
|
|
4
|
+
|
|
5
|
+
### Changes
|
|
6
|
+
|
|
7
|
+
- Version alignment with core OpenClaw release numbers.
|
|
8
|
+
|
|
9
|
+
## 2026.2.6-2
|
|
10
|
+
|
|
11
|
+
### Changes
|
|
12
|
+
|
|
13
|
+
- Version alignment with core OpenClaw release numbers.
|
|
14
|
+
|
|
15
|
+
## 2026.2.6
|
|
16
|
+
|
|
17
|
+
### Changes
|
|
18
|
+
|
|
19
|
+
- Version alignment with core OpenClaw release numbers.
|
|
20
|
+
|
|
21
|
+
## 2026.2.4
|
|
22
|
+
|
|
23
|
+
### Changes
|
|
24
|
+
|
|
25
|
+
- Version alignment with core OpenClaw release numbers.
|
|
26
|
+
|
|
3
27
|
## 2026.2.2
|
|
4
28
|
|
|
5
29
|
### Changes
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/nostr",
|
|
3
|
-
"version": "2026.2.
|
|
3
|
+
"version": "2026.2.9",
|
|
4
4
|
"description": "OpenClaw Nostr channel plugin for NIP-04 encrypted DMs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"nostr-tools": "^2.23.0",
|
|
8
|
-
"openclaw": "workspace:*",
|
|
9
8
|
"zod": "^4.3.6"
|
|
10
9
|
},
|
|
11
10
|
"devDependencies": {
|
package/src/channel.ts
CHANGED
|
@@ -148,7 +148,11 @@ export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = {
|
|
|
148
148
|
const message = core.channel.text.convertMarkdownTables(text ?? "", tableMode);
|
|
149
149
|
const normalizedTo = normalizePubkey(to);
|
|
150
150
|
await bus.sendDm(normalizedTo, message);
|
|
151
|
-
return {
|
|
151
|
+
return {
|
|
152
|
+
channel: "nostr" as const,
|
|
153
|
+
to: normalizedTo,
|
|
154
|
+
messageId: `nostr-${Date.now()}`,
|
|
155
|
+
};
|
|
152
156
|
},
|
|
153
157
|
},
|
|
154
158
|
|
|
@@ -224,10 +228,15 @@ export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = {
|
|
|
224
228
|
privateKey: account.privateKey,
|
|
225
229
|
relays: account.relays,
|
|
226
230
|
onMessage: async (senderPubkey, text, reply) => {
|
|
227
|
-
ctx.log?.debug(
|
|
231
|
+
ctx.log?.debug?.(
|
|
232
|
+
`[${account.accountId}] DM from ${senderPubkey}: ${text.slice(0, 50)}...`,
|
|
233
|
+
);
|
|
228
234
|
|
|
229
235
|
// Forward to OpenClaw's message pipeline
|
|
230
|
-
|
|
236
|
+
// TODO: Replace with proper dispatchReplyWithBufferedBlockDispatcher call
|
|
237
|
+
await (
|
|
238
|
+
runtime.channel.reply as { handleInboundMessage?: (params: unknown) => Promise<void> }
|
|
239
|
+
).handleInboundMessage?.({
|
|
231
240
|
channel: "nostr",
|
|
232
241
|
accountId: account.accountId,
|
|
233
242
|
senderId: senderPubkey,
|
|
@@ -240,31 +249,33 @@ export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = {
|
|
|
240
249
|
});
|
|
241
250
|
},
|
|
242
251
|
onError: (error, context) => {
|
|
243
|
-
ctx.log?.error(`[${account.accountId}] Nostr error (${context}): ${error.message}`);
|
|
252
|
+
ctx.log?.error?.(`[${account.accountId}] Nostr error (${context}): ${error.message}`);
|
|
244
253
|
},
|
|
245
254
|
onConnect: (relay) => {
|
|
246
|
-
ctx.log?.debug(`[${account.accountId}] Connected to relay: ${relay}`);
|
|
255
|
+
ctx.log?.debug?.(`[${account.accountId}] Connected to relay: ${relay}`);
|
|
247
256
|
},
|
|
248
257
|
onDisconnect: (relay) => {
|
|
249
|
-
ctx.log?.debug(`[${account.accountId}] Disconnected from relay: ${relay}`);
|
|
258
|
+
ctx.log?.debug?.(`[${account.accountId}] Disconnected from relay: ${relay}`);
|
|
250
259
|
},
|
|
251
260
|
onEose: (relays) => {
|
|
252
|
-
ctx.log?.debug(`[${account.accountId}] EOSE received from relays: ${relays}`);
|
|
261
|
+
ctx.log?.debug?.(`[${account.accountId}] EOSE received from relays: ${relays}`);
|
|
253
262
|
},
|
|
254
263
|
onMetric: (event: MetricEvent) => {
|
|
255
264
|
// Log significant metrics at appropriate levels
|
|
256
265
|
if (event.name.startsWith("event.rejected.")) {
|
|
257
|
-
ctx.log?.debug(
|
|
266
|
+
ctx.log?.debug?.(
|
|
267
|
+
`[${account.accountId}] Metric: ${event.name} ${JSON.stringify(event.labels)}`,
|
|
268
|
+
);
|
|
258
269
|
} else if (event.name === "relay.circuit_breaker.open") {
|
|
259
|
-
ctx.log?.warn(
|
|
270
|
+
ctx.log?.warn?.(
|
|
260
271
|
`[${account.accountId}] Circuit breaker opened for relay: ${event.labels?.relay}`,
|
|
261
272
|
);
|
|
262
273
|
} else if (event.name === "relay.circuit_breaker.close") {
|
|
263
|
-
ctx.log?.info(
|
|
274
|
+
ctx.log?.info?.(
|
|
264
275
|
`[${account.accountId}] Circuit breaker closed for relay: ${event.labels?.relay}`,
|
|
265
276
|
);
|
|
266
277
|
} else if (event.name === "relay.error") {
|
|
267
|
-
ctx.log?.debug(`[${account.accountId}] Relay error: ${event.labels?.relay}`);
|
|
278
|
+
ctx.log?.debug?.(`[${account.accountId}] Relay error: ${event.labels?.relay}`);
|
|
268
279
|
}
|
|
269
280
|
// Update cached metrics snapshot
|
|
270
281
|
if (busHandle) {
|
package/src/nostr-bus.ts
CHANGED
|
@@ -488,24 +488,28 @@ export async function startNostrBus(options: NostrBusOptions): Promise<NostrBusH
|
|
|
488
488
|
}
|
|
489
489
|
}
|
|
490
490
|
|
|
491
|
-
const sub = pool.subscribeMany(
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
491
|
+
const sub = pool.subscribeMany(
|
|
492
|
+
relays,
|
|
493
|
+
[{ kinds: [4], "#p": [pk], since }] as unknown as Parameters<typeof pool.subscribeMany>[1],
|
|
494
|
+
{
|
|
495
|
+
onevent: handleEvent,
|
|
496
|
+
oneose: () => {
|
|
497
|
+
// EOSE handler - called when all stored events have been received
|
|
498
|
+
for (const relay of relays) {
|
|
499
|
+
metrics.emit("relay.message.eose", 1, { relay });
|
|
500
|
+
}
|
|
501
|
+
onEose?.(relays.join(", "));
|
|
502
|
+
},
|
|
503
|
+
onclose: (reason) => {
|
|
504
|
+
// Handle subscription close
|
|
505
|
+
for (const relay of relays) {
|
|
506
|
+
metrics.emit("relay.message.closed", 1, { relay });
|
|
507
|
+
options.onDisconnect?.(relay);
|
|
508
|
+
}
|
|
509
|
+
onError?.(new Error(`Subscription closed: ${reason.join(", ")}`), "subscription");
|
|
510
|
+
},
|
|
507
511
|
},
|
|
508
|
-
|
|
512
|
+
);
|
|
509
513
|
|
|
510
514
|
// Public sendDm function
|
|
511
515
|
const sendDm = async (toPubkey: string, text: string): Promise<void> => {
|
|
@@ -693,7 +697,7 @@ export function normalizePubkey(input: string): string {
|
|
|
693
697
|
throw new Error("Invalid npub key");
|
|
694
698
|
}
|
|
695
699
|
// Convert Uint8Array to hex string
|
|
696
|
-
return Array.from(decoded.data)
|
|
700
|
+
return Array.from(decoded.data as unknown as Uint8Array)
|
|
697
701
|
.map((b) => b.toString(16).padStart(2, "0"))
|
|
698
702
|
.join("");
|
|
699
703
|
}
|
|
@@ -229,31 +229,58 @@ function sendJson(res: ServerResponse, status: number, body: unknown): void {
|
|
|
229
229
|
res.end(JSON.stringify(body));
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
async function readJsonBody(
|
|
232
|
+
async function readJsonBody(
|
|
233
|
+
req: IncomingMessage,
|
|
234
|
+
maxBytes = 64 * 1024,
|
|
235
|
+
timeoutMs = 30_000,
|
|
236
|
+
): Promise<unknown> {
|
|
233
237
|
return new Promise((resolve, reject) => {
|
|
238
|
+
let done = false;
|
|
239
|
+
const finish = (fn: () => void) => {
|
|
240
|
+
if (done) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
done = true;
|
|
244
|
+
clearTimeout(timer);
|
|
245
|
+
fn();
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const timer = setTimeout(() => {
|
|
249
|
+
finish(() => {
|
|
250
|
+
const err = new Error("Request body timeout");
|
|
251
|
+
req.destroy(err);
|
|
252
|
+
reject(err);
|
|
253
|
+
});
|
|
254
|
+
}, timeoutMs);
|
|
255
|
+
|
|
234
256
|
const chunks: Buffer[] = [];
|
|
235
257
|
let totalBytes = 0;
|
|
236
258
|
|
|
237
259
|
req.on("data", (chunk: Buffer) => {
|
|
238
260
|
totalBytes += chunk.length;
|
|
239
261
|
if (totalBytes > maxBytes) {
|
|
240
|
-
|
|
241
|
-
|
|
262
|
+
finish(() => {
|
|
263
|
+
reject(new Error("Request body too large"));
|
|
264
|
+
req.destroy();
|
|
265
|
+
});
|
|
242
266
|
return;
|
|
243
267
|
}
|
|
244
268
|
chunks.push(chunk);
|
|
245
269
|
});
|
|
246
270
|
|
|
247
271
|
req.on("end", () => {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
272
|
+
finish(() => {
|
|
273
|
+
try {
|
|
274
|
+
const body = Buffer.concat(chunks).toString("utf-8");
|
|
275
|
+
resolve(body ? JSON.parse(body) : {});
|
|
276
|
+
} catch {
|
|
277
|
+
reject(new Error("Invalid JSON"));
|
|
278
|
+
}
|
|
279
|
+
});
|
|
254
280
|
});
|
|
255
281
|
|
|
256
|
-
req.on("error", reject);
|
|
282
|
+
req.on("error", (err) => finish(() => reject(err)));
|
|
283
|
+
req.on("close", () => finish(() => reject(new Error("Connection closed"))));
|
|
257
284
|
});
|
|
258
285
|
}
|
|
259
286
|
|