@openclaw/nostr 2026.1.29 → 2026.2.1
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 +23 -0
- package/README.md +16 -16
- package/index.ts +6 -7
- package/openclaw.plugin.json +1 -3
- package/package.json +10 -7
- package/src/channel.test.ts +15 -5
- package/src/channel.ts +28 -17
- package/src/config-schema.ts +1 -1
- package/src/metrics.ts +33 -19
- package/src/nostr-bus.fuzz.test.ts +11 -22
- package/src/nostr-bus.integration.test.ts +2 -6
- package/src/nostr-bus.test.ts +3 -3
- package/src/nostr-bus.ts +56 -82
- package/src/nostr-profile-http.test.ts +10 -10
- package/src/nostr-profile-http.ts +37 -18
- package/src/nostr-profile-import.test.ts +2 -3
- package/src/nostr-profile-import.ts +10 -7
- package/src/nostr-profile.fuzz.test.ts +7 -9
- package/src/nostr-profile.test.ts +7 -7
- package/src/nostr-profile.ts +56 -21
- package/src/nostr-state-store.test.ts +10 -8
- package/src/nostr-state-store.ts +29 -29
- package/src/seen-tracker.ts +48 -16
- package/src/types.test.ts +1 -5
- package/src/types.ts +4 -2
package/src/nostr-profile.ts
CHANGED
|
@@ -49,14 +49,30 @@ export function profileToContent(profile: NostrProfile): ProfileContent {
|
|
|
49
49
|
|
|
50
50
|
const content: ProfileContent = {};
|
|
51
51
|
|
|
52
|
-
if (validated.name !== undefined)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (validated.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (validated.
|
|
59
|
-
|
|
52
|
+
if (validated.name !== undefined) {
|
|
53
|
+
content.name = validated.name;
|
|
54
|
+
}
|
|
55
|
+
if (validated.displayName !== undefined) {
|
|
56
|
+
content.display_name = validated.displayName;
|
|
57
|
+
}
|
|
58
|
+
if (validated.about !== undefined) {
|
|
59
|
+
content.about = validated.about;
|
|
60
|
+
}
|
|
61
|
+
if (validated.picture !== undefined) {
|
|
62
|
+
content.picture = validated.picture;
|
|
63
|
+
}
|
|
64
|
+
if (validated.banner !== undefined) {
|
|
65
|
+
content.banner = validated.banner;
|
|
66
|
+
}
|
|
67
|
+
if (validated.website !== undefined) {
|
|
68
|
+
content.website = validated.website;
|
|
69
|
+
}
|
|
70
|
+
if (validated.nip05 !== undefined) {
|
|
71
|
+
content.nip05 = validated.nip05;
|
|
72
|
+
}
|
|
73
|
+
if (validated.lud16 !== undefined) {
|
|
74
|
+
content.lud16 = validated.lud16;
|
|
75
|
+
}
|
|
60
76
|
|
|
61
77
|
return content;
|
|
62
78
|
}
|
|
@@ -68,14 +84,30 @@ export function profileToContent(profile: NostrProfile): ProfileContent {
|
|
|
68
84
|
export function contentToProfile(content: ProfileContent): NostrProfile {
|
|
69
85
|
const profile: NostrProfile = {};
|
|
70
86
|
|
|
71
|
-
if (content.name !== undefined)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (content.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (content.
|
|
78
|
-
|
|
87
|
+
if (content.name !== undefined) {
|
|
88
|
+
profile.name = content.name;
|
|
89
|
+
}
|
|
90
|
+
if (content.display_name !== undefined) {
|
|
91
|
+
profile.displayName = content.display_name;
|
|
92
|
+
}
|
|
93
|
+
if (content.about !== undefined) {
|
|
94
|
+
profile.about = content.about;
|
|
95
|
+
}
|
|
96
|
+
if (content.picture !== undefined) {
|
|
97
|
+
profile.picture = content.picture;
|
|
98
|
+
}
|
|
99
|
+
if (content.banner !== undefined) {
|
|
100
|
+
profile.banner = content.banner;
|
|
101
|
+
}
|
|
102
|
+
if (content.website !== undefined) {
|
|
103
|
+
profile.website = content.website;
|
|
104
|
+
}
|
|
105
|
+
if (content.nip05 !== undefined) {
|
|
106
|
+
profile.nip05 = content.nip05;
|
|
107
|
+
}
|
|
108
|
+
if (content.lud16 !== undefined) {
|
|
109
|
+
profile.lud16 = content.lud16;
|
|
110
|
+
}
|
|
79
111
|
|
|
80
112
|
return profile;
|
|
81
113
|
}
|
|
@@ -95,7 +127,7 @@ export function contentToProfile(content: ProfileContent): NostrProfile {
|
|
|
95
127
|
export function createProfileEvent(
|
|
96
128
|
sk: Uint8Array,
|
|
97
129
|
profile: NostrProfile,
|
|
98
|
-
lastPublishedAt?: number
|
|
130
|
+
lastPublishedAt?: number,
|
|
99
131
|
): Event {
|
|
100
132
|
const content = profileToContent(profile);
|
|
101
133
|
const contentJson = JSON.stringify(content);
|
|
@@ -111,7 +143,7 @@ export function createProfileEvent(
|
|
|
111
143
|
tags: [],
|
|
112
144
|
created_at: createdAt,
|
|
113
145
|
},
|
|
114
|
-
sk
|
|
146
|
+
sk,
|
|
115
147
|
);
|
|
116
148
|
|
|
117
149
|
return event;
|
|
@@ -138,7 +170,7 @@ const RELAY_PUBLISH_TIMEOUT_MS = 5000;
|
|
|
138
170
|
export async function publishProfileEvent(
|
|
139
171
|
pool: SimplePool,
|
|
140
172
|
relays: string[],
|
|
141
|
-
event: Event
|
|
173
|
+
event: Event,
|
|
142
174
|
): Promise<ProfilePublishResult> {
|
|
143
175
|
const successes: string[] = [];
|
|
144
176
|
const failures: Array<{ relay: string; error: string }> = [];
|
|
@@ -150,6 +182,7 @@ export async function publishProfileEvent(
|
|
|
150
182
|
setTimeout(() => reject(new Error("timeout")), RELAY_PUBLISH_TIMEOUT_MS);
|
|
151
183
|
});
|
|
152
184
|
|
|
185
|
+
// oxlint-disable-next-line typescript/no-floating-promises
|
|
153
186
|
await Promise.race([pool.publish([relay], event), timeoutPromise]);
|
|
154
187
|
|
|
155
188
|
successes.push(relay);
|
|
@@ -184,7 +217,7 @@ export async function publishProfile(
|
|
|
184
217
|
sk: Uint8Array,
|
|
185
218
|
relays: string[],
|
|
186
219
|
profile: NostrProfile,
|
|
187
|
-
lastPublishedAt?: number
|
|
220
|
+
lastPublishedAt?: number,
|
|
188
221
|
): Promise<ProfilePublishResult> {
|
|
189
222
|
const event = createProfileEvent(sk, profile, lastPublishedAt);
|
|
190
223
|
return publishProfileEvent(pool, relays, event);
|
|
@@ -220,7 +253,9 @@ export function validateProfile(profile: unknown): {
|
|
|
220
253
|
*/
|
|
221
254
|
export function sanitizeProfileForDisplay(profile: NostrProfile): NostrProfile {
|
|
222
255
|
const escapeHtml = (str: string | undefined): string | undefined => {
|
|
223
|
-
if (str === undefined)
|
|
256
|
+
if (str === undefined) {
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
224
259
|
return str
|
|
225
260
|
.replace(/&/g, "&")
|
|
226
261
|
.replace(/</g, "<")
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
|
-
|
|
5
5
|
import { describe, expect, it } from "vitest";
|
|
6
|
-
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
|
7
|
-
|
|
8
6
|
import {
|
|
9
7
|
readNostrBusState,
|
|
10
8
|
writeNostrBusState,
|
|
@@ -19,9 +17,10 @@ async function withTempStateDir<T>(fn: (dir: string) => Promise<T>) {
|
|
|
19
17
|
setNostrRuntime({
|
|
20
18
|
state: {
|
|
21
19
|
resolveStateDir: (env, homedir) => {
|
|
22
|
-
const override =
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
const override = env.OPENCLAW_STATE_DIR?.trim() || env.OPENCLAW_STATE_DIR?.trim();
|
|
21
|
+
if (override) {
|
|
22
|
+
return override;
|
|
23
|
+
}
|
|
25
24
|
return path.join(homedir(), ".openclaw");
|
|
26
25
|
},
|
|
27
26
|
},
|
|
@@ -29,8 +28,11 @@ async function withTempStateDir<T>(fn: (dir: string) => Promise<T>) {
|
|
|
29
28
|
try {
|
|
30
29
|
return await fn(dir);
|
|
31
30
|
} finally {
|
|
32
|
-
if (previous === undefined)
|
|
33
|
-
|
|
31
|
+
if (previous === undefined) {
|
|
32
|
+
delete process.env.OPENCLAW_STATE_DIR;
|
|
33
|
+
} else {
|
|
34
|
+
process.env.OPENCLAW_STATE_DIR = previous;
|
|
35
|
+
}
|
|
34
36
|
await fs.rm(dir, { recursive: true, force: true });
|
|
35
37
|
}
|
|
36
38
|
}
|
package/src/nostr-state-store.ts
CHANGED
|
@@ -2,7 +2,6 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
|
|
6
5
|
import { getNostrRuntime } from "./runtime.js";
|
|
7
6
|
|
|
8
7
|
const STORE_VERSION = 2;
|
|
@@ -39,14 +38,13 @@ export type NostrProfileState = {
|
|
|
39
38
|
|
|
40
39
|
function normalizeAccountId(accountId?: string): string {
|
|
41
40
|
const trimmed = accountId?.trim();
|
|
42
|
-
if (!trimmed)
|
|
41
|
+
if (!trimmed) {
|
|
42
|
+
return "default";
|
|
43
|
+
}
|
|
43
44
|
return trimmed.replace(/[^a-z0-9._-]+/gi, "_");
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
function resolveNostrStatePath(
|
|
47
|
-
accountId?: string,
|
|
48
|
-
env: NodeJS.ProcessEnv = process.env
|
|
49
|
-
): string {
|
|
47
|
+
function resolveNostrStatePath(accountId?: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
50
48
|
const stateDir = getNostrRuntime().state.resolveStateDir(env, os.homedir);
|
|
51
49
|
const normalized = normalizeAccountId(accountId);
|
|
52
50
|
return path.join(stateDir, "nostr", `bus-state-${normalized}.json`);
|
|
@@ -54,7 +52,7 @@ function resolveNostrStatePath(
|
|
|
54
52
|
|
|
55
53
|
function resolveNostrProfileStatePath(
|
|
56
54
|
accountId?: string,
|
|
57
|
-
env: NodeJS.ProcessEnv = process.env
|
|
55
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
58
56
|
): string {
|
|
59
57
|
const stateDir = getNostrRuntime().state.resolveStateDir(env, os.homedir);
|
|
60
58
|
const normalized = normalizeAccountId(accountId);
|
|
@@ -69,7 +67,8 @@ function safeParseState(raw: string): NostrBusState | null {
|
|
|
69
67
|
return {
|
|
70
68
|
version: 2,
|
|
71
69
|
lastProcessedAt: typeof parsed.lastProcessedAt === "number" ? parsed.lastProcessedAt : null,
|
|
72
|
-
gatewayStartedAt:
|
|
70
|
+
gatewayStartedAt:
|
|
71
|
+
typeof parsed.gatewayStartedAt === "number" ? parsed.gatewayStartedAt : null,
|
|
73
72
|
recentEventIds: Array.isArray(parsed.recentEventIds)
|
|
74
73
|
? parsed.recentEventIds.filter((x): x is string => typeof x === "string")
|
|
75
74
|
: [],
|
|
@@ -81,7 +80,8 @@ function safeParseState(raw: string): NostrBusState | null {
|
|
|
81
80
|
return {
|
|
82
81
|
version: 2,
|
|
83
82
|
lastProcessedAt: typeof parsed.lastProcessedAt === "number" ? parsed.lastProcessedAt : null,
|
|
84
|
-
gatewayStartedAt:
|
|
83
|
+
gatewayStartedAt:
|
|
84
|
+
typeof parsed.gatewayStartedAt === "number" ? parsed.gatewayStartedAt : null,
|
|
85
85
|
recentEventIds: [],
|
|
86
86
|
};
|
|
87
87
|
}
|
|
@@ -102,7 +102,9 @@ export async function readNostrBusState(params: {
|
|
|
102
102
|
return safeParseState(raw);
|
|
103
103
|
} catch (err) {
|
|
104
104
|
const code = (err as { code?: string }).code;
|
|
105
|
-
if (code === "ENOENT")
|
|
105
|
+
if (code === "ENOENT") {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
106
108
|
return null;
|
|
107
109
|
}
|
|
108
110
|
}
|
|
@@ -117,10 +119,7 @@ export async function writeNostrBusState(params: {
|
|
|
117
119
|
const filePath = resolveNostrStatePath(params.accountId, params.env);
|
|
118
120
|
const dir = path.dirname(filePath);
|
|
119
121
|
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
120
|
-
const tmp = path.join(
|
|
121
|
-
dir,
|
|
122
|
-
`${path.basename(filePath)}.${crypto.randomUUID()}.tmp`
|
|
123
|
-
);
|
|
122
|
+
const tmp = path.join(dir, `${path.basename(filePath)}.${crypto.randomUUID()}.tmp`);
|
|
124
123
|
const payload: NostrBusState = {
|
|
125
124
|
version: STORE_VERSION,
|
|
126
125
|
lastProcessedAt: params.lastProcessedAt,
|
|
@@ -141,17 +140,20 @@ export async function writeNostrBusState(params: {
|
|
|
141
140
|
*/
|
|
142
141
|
export function computeSinceTimestamp(
|
|
143
142
|
state: NostrBusState | null,
|
|
144
|
-
nowSec: number = Math.floor(Date.now() / 1000)
|
|
143
|
+
nowSec: number = Math.floor(Date.now() / 1000),
|
|
145
144
|
): number {
|
|
146
|
-
if (!state)
|
|
145
|
+
if (!state) {
|
|
146
|
+
return nowSec;
|
|
147
|
+
}
|
|
147
148
|
|
|
148
149
|
// Use the most recent timestamp we have
|
|
149
|
-
const candidates = [
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
].filter((t): t is number => t !== null && t > 0);
|
|
150
|
+
const candidates = [state.lastProcessedAt, state.gatewayStartedAt].filter(
|
|
151
|
+
(t): t is number => t !== null && t > 0,
|
|
152
|
+
);
|
|
153
153
|
|
|
154
|
-
if (candidates.length === 0)
|
|
154
|
+
if (candidates.length === 0) {
|
|
155
|
+
return nowSec;
|
|
156
|
+
}
|
|
155
157
|
return Math.max(...candidates);
|
|
156
158
|
}
|
|
157
159
|
|
|
@@ -166,13 +168,12 @@ function safeParseProfileState(raw: string): NostrProfileState | null {
|
|
|
166
168
|
if (parsed?.version === 1) {
|
|
167
169
|
return {
|
|
168
170
|
version: 1,
|
|
169
|
-
lastPublishedAt:
|
|
170
|
-
typeof parsed.lastPublishedAt === "number" ? parsed.lastPublishedAt : null,
|
|
171
|
+
lastPublishedAt: typeof parsed.lastPublishedAt === "number" ? parsed.lastPublishedAt : null,
|
|
171
172
|
lastPublishedEventId:
|
|
172
173
|
typeof parsed.lastPublishedEventId === "string" ? parsed.lastPublishedEventId : null,
|
|
173
174
|
lastPublishResults:
|
|
174
175
|
parsed.lastPublishResults && typeof parsed.lastPublishResults === "object"
|
|
175
|
-
?
|
|
176
|
+
? parsed.lastPublishResults
|
|
176
177
|
: null,
|
|
177
178
|
};
|
|
178
179
|
}
|
|
@@ -193,7 +194,9 @@ export async function readNostrProfileState(params: {
|
|
|
193
194
|
return safeParseProfileState(raw);
|
|
194
195
|
} catch (err) {
|
|
195
196
|
const code = (err as { code?: string }).code;
|
|
196
|
-
if (code === "ENOENT")
|
|
197
|
+
if (code === "ENOENT") {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
197
200
|
return null;
|
|
198
201
|
}
|
|
199
202
|
}
|
|
@@ -208,10 +211,7 @@ export async function writeNostrProfileState(params: {
|
|
|
208
211
|
const filePath = resolveNostrProfileStatePath(params.accountId, params.env);
|
|
209
212
|
const dir = path.dirname(filePath);
|
|
210
213
|
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
211
|
-
const tmp = path.join(
|
|
212
|
-
dir,
|
|
213
|
-
`${path.basename(filePath)}.${crypto.randomUUID()}.tmp`
|
|
214
|
-
);
|
|
214
|
+
const tmp = path.join(dir, `${path.basename(filePath)}.${crypto.randomUUID()}.tmp`);
|
|
215
215
|
const payload: NostrProfileState = {
|
|
216
216
|
version: PROFILE_STATE_VERSION,
|
|
217
217
|
lastPublishedAt: params.lastPublishedAt,
|
package/src/seen-tracker.ts
CHANGED
|
@@ -56,19 +56,27 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
|
|
|
56
56
|
// Move an entry to the front (most recently used)
|
|
57
57
|
function moveToFront(id: string): void {
|
|
58
58
|
const entry = entries.get(id);
|
|
59
|
-
if (!entry)
|
|
59
|
+
if (!entry) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
60
62
|
|
|
61
63
|
// Already at front
|
|
62
|
-
if (head === id)
|
|
64
|
+
if (head === id) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
63
67
|
|
|
64
68
|
// Remove from current position
|
|
65
69
|
if (entry.prev) {
|
|
66
70
|
const prevEntry = entries.get(entry.prev);
|
|
67
|
-
if (prevEntry)
|
|
71
|
+
if (prevEntry) {
|
|
72
|
+
prevEntry.next = entry.next;
|
|
73
|
+
}
|
|
68
74
|
}
|
|
69
75
|
if (entry.next) {
|
|
70
76
|
const nextEntry = entries.get(entry.next);
|
|
71
|
-
if (nextEntry)
|
|
77
|
+
if (nextEntry) {
|
|
78
|
+
nextEntry.prev = entry.prev;
|
|
79
|
+
}
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
// Update tail if this was the tail
|
|
@@ -81,29 +89,39 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
|
|
|
81
89
|
entry.next = head;
|
|
82
90
|
if (head) {
|
|
83
91
|
const headEntry = entries.get(head);
|
|
84
|
-
if (headEntry)
|
|
92
|
+
if (headEntry) {
|
|
93
|
+
headEntry.prev = id;
|
|
94
|
+
}
|
|
85
95
|
}
|
|
86
96
|
head = id;
|
|
87
97
|
|
|
88
98
|
// If no tail, this is also the tail
|
|
89
|
-
if (!tail)
|
|
99
|
+
if (!tail) {
|
|
100
|
+
tail = id;
|
|
101
|
+
}
|
|
90
102
|
}
|
|
91
103
|
|
|
92
104
|
// Remove an entry from the linked list
|
|
93
105
|
function removeFromList(id: string): void {
|
|
94
106
|
const entry = entries.get(id);
|
|
95
|
-
if (!entry)
|
|
107
|
+
if (!entry) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
96
110
|
|
|
97
111
|
if (entry.prev) {
|
|
98
112
|
const prevEntry = entries.get(entry.prev);
|
|
99
|
-
if (prevEntry)
|
|
113
|
+
if (prevEntry) {
|
|
114
|
+
prevEntry.next = entry.next;
|
|
115
|
+
}
|
|
100
116
|
} else {
|
|
101
117
|
head = entry.next;
|
|
102
118
|
}
|
|
103
119
|
|
|
104
120
|
if (entry.next) {
|
|
105
121
|
const nextEntry = entries.get(entry.next);
|
|
106
|
-
if (nextEntry)
|
|
122
|
+
if (nextEntry) {
|
|
123
|
+
nextEntry.prev = entry.prev;
|
|
124
|
+
}
|
|
107
125
|
} else {
|
|
108
126
|
tail = entry.prev;
|
|
109
127
|
}
|
|
@@ -111,7 +129,9 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
|
|
|
111
129
|
|
|
112
130
|
// Evict the least recently used entry
|
|
113
131
|
function evictLRU(): void {
|
|
114
|
-
if (!tail)
|
|
132
|
+
if (!tail) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
115
135
|
const idToEvict = tail;
|
|
116
136
|
removeFromList(idToEvict);
|
|
117
137
|
entries.delete(idToEvict);
|
|
@@ -139,7 +159,9 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
|
|
|
139
159
|
if (pruneIntervalMs > 0) {
|
|
140
160
|
pruneTimer = setInterval(pruneExpired, pruneIntervalMs);
|
|
141
161
|
// Don't keep process alive just for pruning
|
|
142
|
-
if (pruneTimer.unref)
|
|
162
|
+
if (pruneTimer.unref) {
|
|
163
|
+
pruneTimer.unref();
|
|
164
|
+
}
|
|
143
165
|
}
|
|
144
166
|
|
|
145
167
|
function add(id: string): void {
|
|
@@ -167,12 +189,16 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
|
|
|
167
189
|
|
|
168
190
|
if (head) {
|
|
169
191
|
const headEntry = entries.get(head);
|
|
170
|
-
if (headEntry)
|
|
192
|
+
if (headEntry) {
|
|
193
|
+
headEntry.prev = id;
|
|
194
|
+
}
|
|
171
195
|
}
|
|
172
196
|
|
|
173
197
|
entries.set(id, newEntry);
|
|
174
198
|
head = id;
|
|
175
|
-
if (!tail)
|
|
199
|
+
if (!tail) {
|
|
200
|
+
tail = id;
|
|
201
|
+
}
|
|
176
202
|
}
|
|
177
203
|
|
|
178
204
|
function has(id: string): boolean {
|
|
@@ -198,7 +224,9 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
|
|
|
198
224
|
|
|
199
225
|
function peek(id: string): boolean {
|
|
200
226
|
const entry = entries.get(id);
|
|
201
|
-
if (!entry)
|
|
227
|
+
if (!entry) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
202
230
|
|
|
203
231
|
// Check if expired
|
|
204
232
|
if (Date.now() - entry.seenAt > ttlMs) {
|
|
@@ -248,12 +276,16 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
|
|
|
248
276
|
|
|
249
277
|
if (head) {
|
|
250
278
|
const headEntry = entries.get(head);
|
|
251
|
-
if (headEntry)
|
|
279
|
+
if (headEntry) {
|
|
280
|
+
headEntry.prev = id;
|
|
281
|
+
}
|
|
252
282
|
}
|
|
253
283
|
|
|
254
284
|
entries.set(id, newEntry);
|
|
255
285
|
head = id;
|
|
256
|
-
if (!tail)
|
|
286
|
+
if (!tail) {
|
|
287
|
+
tail = id;
|
|
288
|
+
}
|
|
257
289
|
}
|
|
258
290
|
}
|
|
259
291
|
}
|
package/src/types.test.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
listNostrAccountIds,
|
|
4
|
-
resolveDefaultNostrAccountId,
|
|
5
|
-
resolveNostrAccount,
|
|
6
|
-
} from "./types.js";
|
|
2
|
+
import { listNostrAccountIds, resolveDefaultNostrAccountId, resolveNostrAccount } from "./types.js";
|
|
7
3
|
|
|
8
4
|
const TEST_PRIVATE_KEY = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
9
5
|
|
package/src/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { NostrProfile } from "./config-schema.js";
|
|
2
3
|
import { getPublicKeyFromPrivate } from "./nostr-bus.js";
|
|
3
4
|
import { DEFAULT_RELAYS } from "./nostr-bus.js";
|
|
4
|
-
import type { NostrProfile } from "./config-schema.js";
|
|
5
5
|
|
|
6
6
|
export interface NostrAccountConfig {
|
|
7
7
|
enabled?: boolean;
|
|
@@ -48,7 +48,9 @@ export function listNostrAccountIds(cfg: OpenClawConfig): string[] {
|
|
|
48
48
|
*/
|
|
49
49
|
export function resolveDefaultNostrAccountId(cfg: OpenClawConfig): string {
|
|
50
50
|
const ids = listNostrAccountIds(cfg);
|
|
51
|
-
if (ids.includes(DEFAULT_ACCOUNT_ID))
|
|
51
|
+
if (ids.includes(DEFAULT_ACCOUNT_ID)) {
|
|
52
|
+
return DEFAULT_ACCOUNT_ID;
|
|
53
|
+
}
|
|
52
54
|
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
|
53
55
|
}
|
|
54
56
|
|