@estiva-app/protocol 0.1.0
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 +57 -0
- package/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/bridge.d.ts +91 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +138 -0
- package/dist/bridge.js.map +1 -0
- package/dist/events.d.ts +432 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +616 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/live.d.ts +205 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +398 -0
- package/dist/live.js.map +1 -0
- package/dist/nip19.d.ts +98 -0
- package/dist/nip19.d.ts.map +1 -0
- package/dist/nip19.js +320 -0
- package/dist/nip19.js.map +1 -0
- package/dist/nip98.d.ts +61 -0
- package/dist/nip98.d.ts.map +1 -0
- package/dist/nip98.js +134 -0
- package/dist/nip98.js.map +1 -0
- package/dist/sign.d.ts +67 -0
- package/dist/sign.d.ts.map +1 -0
- package/dist/sign.js +58 -0
- package/dist/sign.js.map +1 -0
- package/dist/subscriptions.d.ts +120 -0
- package/dist/subscriptions.d.ts.map +1 -0
- package/dist/subscriptions.js +68 -0
- package/dist/subscriptions.js.map +1 -0
- package/package.json +59 -0
- package/src/bridge.ts +198 -0
- package/src/events.ts +821 -0
- package/src/index.ts +159 -0
- package/src/live.ts +536 -0
- package/src/nip19.ts +354 -0
- package/src/nip98.ts +164 -0
- package/src/sign.ts +113 -0
- package/src/subscriptions.ts +200 -0
package/dist/events.js
ADDED
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buzz-shaped Nostr event builders.
|
|
3
|
+
*
|
|
4
|
+
* Every builder here mirrors a function in Buzz's own SDK
|
|
5
|
+
* (`crates/buzz-sdk/src/builders.rs`) exactly — same kind, same tags, **same tag
|
|
6
|
+
* order**. Tag order is part of the NIP-01 id preimage, so a reordered tag
|
|
7
|
+
* produces a different event id. Where a shape was verified against Buzz's code,
|
|
8
|
+
* the Rust function is named in the comment.
|
|
9
|
+
*
|
|
10
|
+
* Verified: `buildMessage` + `buildCreateChannel` output was compared against
|
|
11
|
+
* `build_message` / `build_create_channel` by running Buzz's own crates over this
|
|
12
|
+
* emitter's output — byte-identical event ids (Peek's
|
|
13
|
+
* docs/buzz-compat/INTEROP_PROOF.md §4).
|
|
14
|
+
*
|
|
15
|
+
* ## This file used to exist three times
|
|
16
|
+
*
|
|
17
|
+
* Until SHA-3 the same builders lived in `peek/convex/nostr/`, `ship/lib/nostr/`
|
|
18
|
+
* and `estiva-agent/lib/nostr/`, and a `diff -r` somebody had to remember to run
|
|
19
|
+
* was what kept two of them honest. They had already drifted: Peek's
|
|
20
|
+
* `buildMessage` grew an `about` parameter emitting `a` tags and Ship's never
|
|
21
|
+
* did, so the same logical message produced different bytes depending on which
|
|
22
|
+
* app sent it. Nothing failed, because each copy was self-consistent — which is
|
|
23
|
+
* the failure mode this package exists to remove.
|
|
24
|
+
*
|
|
25
|
+
* The union is what ships here. Where the two disagreed, Peek's shape won, and
|
|
26
|
+
* `test/wire-vectors.test.ts` pins the bytes both apps were producing *before*
|
|
27
|
+
* the extraction so the merge cannot have moved either one.
|
|
28
|
+
*
|
|
29
|
+
* ## Pure, and global-free
|
|
30
|
+
*
|
|
31
|
+
* No signing, no I/O, no randomness, no ambient globals. That is what lets this
|
|
32
|
+
* run in Convex's default runtime, in a browser bundle, and under `tsx` from the
|
|
33
|
+
* same published artifact. Signing lives in `./sign.ts`, which needs entropy.
|
|
34
|
+
*/
|
|
35
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
36
|
+
import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils';
|
|
37
|
+
/**
|
|
38
|
+
* Buzz kind numbers used across the suite (crates/buzz-core/src/kind.rs).
|
|
39
|
+
*
|
|
40
|
+
* The union of what Peek and Ship each declared before SHA-3. A kind number is
|
|
41
|
+
* a fact about the relay, not about an app, so there is no reason for two apps
|
|
42
|
+
* to hold different subsets of it — and a subset is how an app ends up unable to
|
|
43
|
+
* *read* a kind its neighbour writes.
|
|
44
|
+
*/
|
|
45
|
+
export const KIND = {
|
|
46
|
+
PROFILE: 0,
|
|
47
|
+
DELETION: 5,
|
|
48
|
+
REACTION: 7,
|
|
49
|
+
STREAM_MESSAGE: 9,
|
|
50
|
+
NIP29_PUT_USER: 9000,
|
|
51
|
+
NIP29_EDIT_METADATA: 9002,
|
|
52
|
+
NIP29_CREATE_GROUP: 9007,
|
|
53
|
+
NIP29_DELETE_GROUP: 9008,
|
|
54
|
+
/**
|
|
55
|
+
* Estiva assertion — a statement *about* something in the channel, rather
|
|
56
|
+
* than a message in it (PEEK-128). `resolution` is the first and, for now,
|
|
57
|
+
* only subtype; the `t` tag names it so later subtypes can join without a
|
|
58
|
+
* new kind.
|
|
59
|
+
*
|
|
60
|
+
* The number sits in the **regular** range (1000–9999), which is what makes
|
|
61
|
+
* it stored and append-only. A replaceable kind would overwrite the previous
|
|
62
|
+
* assertion and destroy exactly the history this exists to keep. It clears
|
|
63
|
+
* NIP-29's 9000–9030 block on purpose.
|
|
64
|
+
*/
|
|
65
|
+
ASSERTION: 9101,
|
|
66
|
+
/**
|
|
67
|
+
* NIP-22 comment (REW-10). Ship's comments are posted into the project's
|
|
68
|
+
* Folder — which *is* a Peek topic's channel — so they arrive in a channel as
|
|
69
|
+
* ordinary messages and must be read alongside `STREAM_MESSAGE`.
|
|
70
|
+
*
|
|
71
|
+
* Both kinds, permanently: a `kind:9` is not replaceable, so every comment
|
|
72
|
+
* written before Ship flipped stays `kind:9` forever and this pair can never
|
|
73
|
+
* shrink to one.
|
|
74
|
+
*/
|
|
75
|
+
COMMENT: 1111,
|
|
76
|
+
/** NIP-84 highlight. */
|
|
77
|
+
HIGHLIGHT: 9802,
|
|
78
|
+
/** NIP-FC File — see docs/buzz-compat/nips/NIP-FC.md in the Peek repo. */
|
|
79
|
+
FILE: 30840,
|
|
80
|
+
/** NIP-FC Component. */
|
|
81
|
+
COMPONENT: 30841,
|
|
82
|
+
/**
|
|
83
|
+
* NIP-42 relay auth — the challenge response that turns a connected socket
|
|
84
|
+
* into an authenticated one (`Kind::Authentication`, PEE-5).
|
|
85
|
+
*
|
|
86
|
+
* Unlike every other kind here, this one is **never published**: Buzz builds
|
|
87
|
+
* it into `buzz-auth/src/nip42.rs`, never stores it, and never logs it —
|
|
88
|
+
* "AUTH events are never stored or logged (may contain bearer tokens)" is a
|
|
89
|
+
* comment in that file. It goes over the socket and is gone.
|
|
90
|
+
*/
|
|
91
|
+
RELAY_AUTH: 22242,
|
|
92
|
+
HTTP_AUTH: 27235,
|
|
93
|
+
};
|
|
94
|
+
/** `build_reaction` (builders.rs:463) caps the emoji at 64 chars. */
|
|
95
|
+
export const MAX_EMOJI_CHARS = 64;
|
|
96
|
+
/** kind:9 content cap — `check_content(content, 64 * 1024)` in build_message. */
|
|
97
|
+
export const MAX_MESSAGE_BYTES = 64 * 1024;
|
|
98
|
+
/** `mention_tags` in builders.rs rejects more than this many mentions. */
|
|
99
|
+
export const MAX_MENTIONS = 50;
|
|
100
|
+
/**
|
|
101
|
+
* NIP-01 event id: sha256 over the canonical serialization
|
|
102
|
+
* `[0, pubkey, created_at, kind, tags, content]`.
|
|
103
|
+
*
|
|
104
|
+
* `JSON.stringify` produces exactly the escaping NIP-01 requires (`\n`, `\"`,
|
|
105
|
+
* `\\`, `\r`, `\t`, `\b`, `\f`, `\uXXXX` for other control chars) with no
|
|
106
|
+
* insignificant whitespace, so no custom serializer is needed.
|
|
107
|
+
*
|
|
108
|
+
* Cross-checked against `nostr-tools/pure`'s `getEventHash` over 103 events
|
|
109
|
+
* recorded from production: all 103 ids agree. See `test/oracle.test.ts` — the
|
|
110
|
+
* third-party implementation is a devDependency of this package and a dependency
|
|
111
|
+
* of nothing, so the check costs consumers no bytes.
|
|
112
|
+
*/
|
|
113
|
+
export function computeEventId(e) {
|
|
114
|
+
const serialized = JSON.stringify([0, e.pubkey, e.created_at, e.kind, e.tags, e.content]);
|
|
115
|
+
return bytesToHex(sha256(utf8ToBytes(serialized)));
|
|
116
|
+
}
|
|
117
|
+
/** Apps store ms; Nostr `created_at` is seconds. */
|
|
118
|
+
export function toNostrSeconds(ms) {
|
|
119
|
+
return Math.floor(ms / 1000);
|
|
120
|
+
}
|
|
121
|
+
function assertHex64(value, label) {
|
|
122
|
+
if (!/^[0-9a-f]{64}$/.test(value)) {
|
|
123
|
+
throw new Error(`${label} must be 64 lowercase hex chars, got: ${value.slice(0, 16)}…`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Buzz's `canonical_channel_name` (crates/buzz-core/src/channel.rs:15):
|
|
128
|
+
* strip leading '#' and whitespace, then trim the end. Applied by
|
|
129
|
+
* `build_create_channel` before the name reaches the tag, so we must match it or
|
|
130
|
+
* our ids diverge for any title with a leading '#' or space.
|
|
131
|
+
*/
|
|
132
|
+
export function canonicalChannelName(name) {
|
|
133
|
+
return name.replace(/^[#\s]+/, '').replace(/\s+$/, '');
|
|
134
|
+
}
|
|
135
|
+
/** NIP-01 addressable reference: `<kind>:<pubkey>:<d-tag>`. */
|
|
136
|
+
export function addr(kind, pubkey, dTag) {
|
|
137
|
+
return `${kind}:${pubkey}:${dTag}`;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* kind:0 profile — mirrors `build_profile` (builders.rs:537).
|
|
141
|
+
*
|
|
142
|
+
* Buzz builds the content with `serde_json::Map`, which is a `BTreeMap` unless
|
|
143
|
+
* the `preserve_order` feature is enabled. It is not enabled in Buzz's
|
|
144
|
+
* workspace, so **keys serialize in alphabetical order**: about, display_name,
|
|
145
|
+
* name, nip05, picture. We sort to match; getting this wrong changes the content
|
|
146
|
+
* string and therefore the event id.
|
|
147
|
+
*
|
|
148
|
+
* An app must not call this: the identity service is the sole publisher of
|
|
149
|
+
* `kind:0` and refuses it for every app before consulting any allowlist
|
|
150
|
+
* (SPEC §4.2). It is here because the *identity service* and the seed scripts
|
|
151
|
+
* need it, and because a second copy of this key ordering is the kind of thing
|
|
152
|
+
* that drifts.
|
|
153
|
+
*/
|
|
154
|
+
export function buildProfile(pubkey, createdAtMs, fields) {
|
|
155
|
+
assertHex64(pubkey, 'pubkey');
|
|
156
|
+
const present = Object.entries(fields).filter(([, v]) => v !== undefined && v !== '');
|
|
157
|
+
present.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
158
|
+
const content = `{${present.map(([k, v]) => `${JSON.stringify(k)}:${JSON.stringify(v)}`).join(',')}}`;
|
|
159
|
+
return {
|
|
160
|
+
pubkey,
|
|
161
|
+
created_at: toNostrSeconds(createdAtMs),
|
|
162
|
+
kind: KIND.PROFILE,
|
|
163
|
+
tags: [],
|
|
164
|
+
content,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The inverse of `buildProfile`: a kind:0 event to the fields an app shows.
|
|
169
|
+
*
|
|
170
|
+
* Both name keys are read because both are written: Peek publishes
|
|
171
|
+
* `display_name`, Ship publishes `name`, and a person who has used both apps
|
|
172
|
+
* should resolve either way rather than on a coin flip. Anything that will not
|
|
173
|
+
* parse costs a name, never the read that asked for it.
|
|
174
|
+
*
|
|
175
|
+
* One parser is what keeps a person from having two names depending on which
|
|
176
|
+
* app they appear in — which is the whole reason this is not left to each
|
|
177
|
+
* consumer.
|
|
178
|
+
*/
|
|
179
|
+
export function parseProfile(event) {
|
|
180
|
+
if (!event)
|
|
181
|
+
return {};
|
|
182
|
+
try {
|
|
183
|
+
const meta = JSON.parse(event.content);
|
|
184
|
+
const str = (value) => (typeof value === 'string' && value ? value : undefined);
|
|
185
|
+
return { displayName: str(meta.display_name) ?? str(meta.name), picture: str(meta.picture) };
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return {};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* kind:9007 create channel — mirrors `build_create_channel` (builders.rs:674).
|
|
193
|
+
* Tag order is fixed: h, name, [visibility], [channel_type], [about], [ttl].
|
|
194
|
+
*/
|
|
195
|
+
export function buildCreateChannel(pubkey, createdAtMs, args) {
|
|
196
|
+
assertHex64(pubkey, 'pubkey');
|
|
197
|
+
const name = canonicalChannelName(args.name);
|
|
198
|
+
if (name.trim() === '')
|
|
199
|
+
throw new Error('channel name is required');
|
|
200
|
+
const tags = [
|
|
201
|
+
['h', args.channelUuid],
|
|
202
|
+
['name', name],
|
|
203
|
+
];
|
|
204
|
+
if (args.visibility)
|
|
205
|
+
tags.push(['visibility', args.visibility]);
|
|
206
|
+
if (args.channelType)
|
|
207
|
+
tags.push(['channel_type', args.channelType]);
|
|
208
|
+
if (args.about)
|
|
209
|
+
tags.push(['about', args.about]);
|
|
210
|
+
if (args.ttlSeconds !== undefined)
|
|
211
|
+
tags.push(['ttl', String(args.ttlSeconds)]);
|
|
212
|
+
return {
|
|
213
|
+
pubkey,
|
|
214
|
+
created_at: toNostrSeconds(createdAtMs),
|
|
215
|
+
kind: KIND.NIP29_CREATE_GROUP,
|
|
216
|
+
tags,
|
|
217
|
+
content: '',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* kind:9008 NIP-29 delete-group — what makes a deleted container actually gone.
|
|
222
|
+
*
|
|
223
|
+
* Deleting a topic in Peek used to remove it and its messages from Convex and
|
|
224
|
+
* publish nothing at all. Create propagated as a 9007; delete propagated
|
|
225
|
+
* nowhere. So a conversation somebody deleted, believing it gone, stayed
|
|
226
|
+
* readable to every admitted member indefinitely — measured on production at 145
|
|
227
|
+
* messages across 37 channels with zero deletion requests against any of them
|
|
228
|
+
* (PEEK-170).
|
|
229
|
+
*
|
|
230
|
+
* **9008 rather than a kind:5 per message**, and the difference is capability
|
|
231
|
+
* rather than efficiency. Buzz rejects a multi-target kind:5 outright, and
|
|
232
|
+
* NIP-09 is honoured only for the key that signed the original — so
|
|
233
|
+
* per-message deletion could never reach anybody else's messages in the
|
|
234
|
+
* container. Delete-group soft-deletes the channel, and Buzz's read guards
|
|
235
|
+
* (`c.deleted_at IS NULL` in both `get_accessible_channels` and `is_member`)
|
|
236
|
+
* then hide the whole container regardless of who wrote what is in it.
|
|
237
|
+
*
|
|
238
|
+
* Requires the channel's **owner** — Buzz enforces "only owner can delete
|
|
239
|
+
* group" in `validate_admin_event`, and a container's creator becomes its owner
|
|
240
|
+
* at create. Anybody else is refused, which the caller reports rather than hides.
|
|
241
|
+
*/
|
|
242
|
+
export function buildDeleteChannel(pubkey, createdAtMs, args) {
|
|
243
|
+
assertHex64(pubkey, 'pubkey');
|
|
244
|
+
return {
|
|
245
|
+
pubkey,
|
|
246
|
+
created_at: toNostrSeconds(createdAtMs),
|
|
247
|
+
kind: KIND.NIP29_DELETE_GROUP,
|
|
248
|
+
tags: [['h', args.channelUuid]],
|
|
249
|
+
content: '',
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* kind:9002 edit metadata — renaming a channel (PEE-2).
|
|
254
|
+
*
|
|
255
|
+
* Buzz applies each recognised tag it finds (`name`, `about`, `archived`,
|
|
256
|
+
* `topic`, `purpose`, `visibility`, `ttl`) and refuses an event carrying none
|
|
257
|
+
* of them, so this builds exactly the fields asked for and nothing else.
|
|
258
|
+
*
|
|
259
|
+
* The name goes through `canonicalChannelName` for the same reason
|
|
260
|
+
* `buildCreateChannel` does: Buzz canonicalises before storing, so sending the
|
|
261
|
+
* raw string means the app and the relay disagree about what the container is
|
|
262
|
+
* called. A name that canonicalises away to nothing is not a rename, and
|
|
263
|
+
* throwing here is better than a round trip to be told so.
|
|
264
|
+
*
|
|
265
|
+
* Requires the channel's owner or admin — the relay refuses anyone else.
|
|
266
|
+
*/
|
|
267
|
+
export function buildEditChannelMetadata(pubkey, createdAtMs, args) {
|
|
268
|
+
assertHex64(pubkey, 'pubkey');
|
|
269
|
+
const tags = [['h', args.channelUuid]];
|
|
270
|
+
if (args.name !== undefined) {
|
|
271
|
+
const name = canonicalChannelName(args.name);
|
|
272
|
+
if (name.trim() === '')
|
|
273
|
+
throw new Error('channel name is required');
|
|
274
|
+
tags.push(['name', name]);
|
|
275
|
+
}
|
|
276
|
+
if (args.about !== undefined)
|
|
277
|
+
tags.push(['about', args.about]);
|
|
278
|
+
if (tags.length === 1)
|
|
279
|
+
throw new Error('nothing to edit');
|
|
280
|
+
return {
|
|
281
|
+
pubkey,
|
|
282
|
+
created_at: toNostrSeconds(createdAtMs),
|
|
283
|
+
kind: KIND.NIP29_EDIT_METADATA,
|
|
284
|
+
tags,
|
|
285
|
+
content: '',
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* kind:9000 add member — mirrors `build_add_member` (builders.rs:565).
|
|
290
|
+
* Tag order: h, p, [role]. The target pubkey is lowercased by Buzz.
|
|
291
|
+
*/
|
|
292
|
+
export function buildAddMember(pubkey, createdAtMs, args) {
|
|
293
|
+
assertHex64(pubkey, 'pubkey');
|
|
294
|
+
const target = args.targetPubkey.toLowerCase();
|
|
295
|
+
assertHex64(target, 'targetPubkey');
|
|
296
|
+
const tags = [
|
|
297
|
+
['h', args.channelUuid],
|
|
298
|
+
['p', target],
|
|
299
|
+
];
|
|
300
|
+
if (args.role)
|
|
301
|
+
tags.push(['role', args.role]);
|
|
302
|
+
return {
|
|
303
|
+
pubkey,
|
|
304
|
+
created_at: toNostrSeconds(createdAtMs),
|
|
305
|
+
kind: KIND.NIP29_PUT_USER,
|
|
306
|
+
tags,
|
|
307
|
+
content: '',
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* kind:7 reaction — mirrors `build_reaction` (builders.rs:463).
|
|
312
|
+
*
|
|
313
|
+
* Note there is **no `h` tag**: Buzz derives the channel from the *target's*
|
|
314
|
+
* `#e` tag and explicitly ignores a client-supplied `#h` (`NOSTR.md`). Adding one
|
|
315
|
+
* would change the event id for no benefit.
|
|
316
|
+
*/
|
|
317
|
+
export function buildReaction(pubkey, createdAtMs, args) {
|
|
318
|
+
assertHex64(pubkey, 'pubkey');
|
|
319
|
+
assertHex64(args.targetEventId, 'targetEventId');
|
|
320
|
+
if ([...args.emoji].length > MAX_EMOJI_CHARS) {
|
|
321
|
+
throw new Error(`emoji longer than ${MAX_EMOJI_CHARS} chars`);
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
pubkey,
|
|
325
|
+
created_at: toNostrSeconds(createdAtMs),
|
|
326
|
+
kind: KIND.REACTION,
|
|
327
|
+
tags: [['e', args.targetEventId]],
|
|
328
|
+
content: args.emoji,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* kind:5 deletion — mirrors `build_remove_reaction` (builders.rs:495).
|
|
333
|
+
*
|
|
334
|
+
* Buzz accepts self-authored deletions (and an owner deleting their agent's).
|
|
335
|
+
* NIP-09 is a *request*, author-scoped, and it removes the record rather than
|
|
336
|
+
* the work — so an app must hide the control from non-authors rather than offer
|
|
337
|
+
* one that silently fails (SPEC §6.5).
|
|
338
|
+
*/
|
|
339
|
+
export function buildDeletion(pubkey, createdAtMs, args) {
|
|
340
|
+
assertHex64(pubkey, 'pubkey');
|
|
341
|
+
assertHex64(args.targetEventId, 'targetEventId');
|
|
342
|
+
return {
|
|
343
|
+
pubkey,
|
|
344
|
+
created_at: toNostrSeconds(createdAtMs),
|
|
345
|
+
kind: KIND.DELETION,
|
|
346
|
+
tags: [['e', args.targetEventId]],
|
|
347
|
+
content: '',
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
/** Exactly Buzz's `thread_tags` (builders.rs:173). */
|
|
351
|
+
export function threadTags(ref) {
|
|
352
|
+
if (ref.rootId === ref.parentId) {
|
|
353
|
+
return [['e', ref.rootId, '', 'reply']];
|
|
354
|
+
}
|
|
355
|
+
return [
|
|
356
|
+
['e', ref.rootId, '', 'root'],
|
|
357
|
+
['e', ref.parentId, '', 'reply'],
|
|
358
|
+
];
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* kind:9 stream message — mirrors `build_message` (builders.rs:219).
|
|
362
|
+
* Tag order: h, [thread tags], [a about], [p mentions], [broadcast], [imeta].
|
|
363
|
+
*
|
|
364
|
+
* **This is the shape verified byte-identical against Buzz's SDK** — see Peek's
|
|
365
|
+
* docs/buzz-compat/INTEROP_PROOF.md §4.
|
|
366
|
+
*
|
|
367
|
+
* **`about` is where the two copies had drifted.** Peek grew it so a pasted
|
|
368
|
+
* NIP-19 pointer could carry its address as an `a` tag and Ship could route the
|
|
369
|
+
* conversation to the issue directly; Ship's copy never received it and could
|
|
370
|
+
* not emit one at all. Peek's shape is what ships. Omitting `about` produces
|
|
371
|
+
* byte-identical output to Ship's old builder — pinned by
|
|
372
|
+
* `test/wire-vectors.test.ts`, because "the change is a no-op for Ship" is
|
|
373
|
+
* exactly the kind of claim that deserves a fixture rather than a sentence.
|
|
374
|
+
*/
|
|
375
|
+
export function buildMessage(pubkey, createdAtMs, args) {
|
|
376
|
+
assertHex64(pubkey, 'pubkey');
|
|
377
|
+
const bytes = utf8ToBytes(args.content).length;
|
|
378
|
+
if (bytes > MAX_MESSAGE_BYTES) {
|
|
379
|
+
throw new Error(`content is ${bytes} bytes, max ${MAX_MESSAGE_BYTES}`);
|
|
380
|
+
}
|
|
381
|
+
const tags = [['h', args.channelUuid]];
|
|
382
|
+
if (args.threadRef)
|
|
383
|
+
tags.push(...threadTags(args.threadRef));
|
|
384
|
+
// Keep a pasted NIP-19 pointer in content for portable display, and carry
|
|
385
|
+
// its address here so a consumer can route the conversation to the object.
|
|
386
|
+
for (const address of [...new Set(args.about ?? [])])
|
|
387
|
+
tags.push(['a', address]);
|
|
388
|
+
if (args.mentions && args.mentions.length > 0) {
|
|
389
|
+
if (args.mentions.length > MAX_MENTIONS) {
|
|
390
|
+
throw new Error(`too many mentions: ${args.mentions.length} > ${MAX_MENTIONS}`);
|
|
391
|
+
}
|
|
392
|
+
const seen = new Set();
|
|
393
|
+
for (const hex of args.mentions) {
|
|
394
|
+
const lower = hex.toLowerCase();
|
|
395
|
+
assertHex64(lower, 'mention pubkey');
|
|
396
|
+
if (!seen.has(lower)) {
|
|
397
|
+
seen.add(lower);
|
|
398
|
+
tags.push(['p', lower]);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (args.broadcast)
|
|
403
|
+
tags.push(['broadcast', '1']);
|
|
404
|
+
for (const mt of args.mediaTags ?? [])
|
|
405
|
+
tags.push(mt);
|
|
406
|
+
return {
|
|
407
|
+
pubkey,
|
|
408
|
+
created_at: toNostrSeconds(createdAtMs),
|
|
409
|
+
kind: KIND.STREAM_MESSAGE,
|
|
410
|
+
tags,
|
|
411
|
+
content: args.content,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/** Assertion subtypes. Only `resolution` exists today (PEEK-128). */
|
|
415
|
+
export const ASSERTION_SUBTYPE = { RESOLUTION: 'resolution' };
|
|
416
|
+
/** Rationale cap — the same 64 KiB ceiling a message content carries. */
|
|
417
|
+
export const MAX_RATIONALE_BYTES = MAX_MESSAGE_BYTES;
|
|
418
|
+
/**
|
|
419
|
+
* kind:9101 resolution assertion — "this thread is resolved", said on the wire
|
|
420
|
+
* so any Estiva app reading the channel can see it (PEEK-128).
|
|
421
|
+
*
|
|
422
|
+
* **Append-only.** A reopen is another assertion, never a deletion of the
|
|
423
|
+
* resolve that preceded it; current state is folded from the ordered run, not
|
|
424
|
+
* read off a single canonical event. That is why this is a regular kind rather
|
|
425
|
+
* than a replaceable one — see `KIND.ASSERTION`.
|
|
426
|
+
*
|
|
427
|
+
* The event carries the claim and who made it. It does **not** carry authority:
|
|
428
|
+
* there is no "proposed" or "endorsed" mode here, because how much weight a
|
|
429
|
+
* given actor's assertion deserves is a policy question that belongs to the app
|
|
430
|
+
* reading it, not to the protocol. That is also why the *fold* of these
|
|
431
|
+
* assertions is not in this package — see the README on the line this package
|
|
432
|
+
* does not cross.
|
|
433
|
+
*
|
|
434
|
+
* Tag order is fixed so the event id is reproducible: h, e(target), t, action,
|
|
435
|
+
* [e(support)].
|
|
436
|
+
*/
|
|
437
|
+
export function buildResolution(pubkey, createdAtMs, args) {
|
|
438
|
+
assertHex64(pubkey, 'pubkey');
|
|
439
|
+
assertHex64(args.targetEventId, 'targetEventId');
|
|
440
|
+
if (args.supportingEventId)
|
|
441
|
+
assertHex64(args.supportingEventId, 'supportingEventId');
|
|
442
|
+
if (args.action !== 'resolved' && args.action !== 'reopened') {
|
|
443
|
+
throw new Error(`unknown resolution action: ${String(args.action)}`);
|
|
444
|
+
}
|
|
445
|
+
const rationale = args.rationale ?? '';
|
|
446
|
+
const bytes = utf8ToBytes(rationale).length;
|
|
447
|
+
if (bytes > MAX_RATIONALE_BYTES) {
|
|
448
|
+
throw new Error(`rationale is ${bytes} bytes, max ${MAX_RATIONALE_BYTES}`);
|
|
449
|
+
}
|
|
450
|
+
const tags = [
|
|
451
|
+
['h', args.channelUuid],
|
|
452
|
+
['e', args.targetEventId],
|
|
453
|
+
['t', ASSERTION_SUBTYPE.RESOLUTION],
|
|
454
|
+
['action', args.action],
|
|
455
|
+
];
|
|
456
|
+
// Marked so a reader can tell the supporting reply from the target, which
|
|
457
|
+
// share the `e` tag name.
|
|
458
|
+
if (args.supportingEventId)
|
|
459
|
+
tags.push(['e', args.supportingEventId, '', 'support']);
|
|
460
|
+
return {
|
|
461
|
+
pubkey,
|
|
462
|
+
created_at: toNostrSeconds(createdAtMs),
|
|
463
|
+
kind: KIND.ASSERTION,
|
|
464
|
+
tags,
|
|
465
|
+
content: rationale,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* kind:30840 File — NIP-FC.
|
|
470
|
+
*
|
|
471
|
+
* `componentDTags` are listed **in document order**; order lives on the File so
|
|
472
|
+
* reordering is a single edit in one place.
|
|
473
|
+
*/
|
|
474
|
+
export function buildFile(pubkey, createdAtMs, args) {
|
|
475
|
+
assertHex64(pubkey, 'pubkey');
|
|
476
|
+
const tags = [
|
|
477
|
+
['d', args.fileId],
|
|
478
|
+
['title', args.title],
|
|
479
|
+
];
|
|
480
|
+
if (args.channelUuid)
|
|
481
|
+
tags.push(['h', args.channelUuid]);
|
|
482
|
+
for (const d of args.componentDTags) {
|
|
483
|
+
tags.push(['a', addr(KIND.COMPONENT, pubkey, d)]);
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
pubkey,
|
|
487
|
+
created_at: toNostrSeconds(createdAtMs),
|
|
488
|
+
kind: KIND.FILE,
|
|
489
|
+
tags,
|
|
490
|
+
content: args.metadata ? JSON.stringify(args.metadata) : '',
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* kind:30841 Component — NIP-FC.
|
|
495
|
+
*
|
|
496
|
+
* `type` must be namespaced `<namespace>/<name>`. The protocol defines the
|
|
497
|
+
* container; the payload shape belongs to the type.
|
|
498
|
+
*/
|
|
499
|
+
export function buildComponent(pubkey, createdAtMs, args) {
|
|
500
|
+
assertHex64(pubkey, 'pubkey');
|
|
501
|
+
if (!args.type.includes('/')) {
|
|
502
|
+
throw new Error(`component type must be namespaced "<namespace>/<name>", got "${args.type}"`);
|
|
503
|
+
}
|
|
504
|
+
const tags = [
|
|
505
|
+
['d', args.componentId],
|
|
506
|
+
['a', addr(KIND.FILE, pubkey, args.fileId)],
|
|
507
|
+
['type', args.type],
|
|
508
|
+
];
|
|
509
|
+
if (args.channelUuid)
|
|
510
|
+
tags.push(['h', args.channelUuid]);
|
|
511
|
+
for (const label of args.labels ?? []) {
|
|
512
|
+
tags.push(['L', label.namespace]);
|
|
513
|
+
tags.push(['l', label.value, label.namespace]);
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
pubkey,
|
|
517
|
+
created_at: toNostrSeconds(createdAtMs),
|
|
518
|
+
kind: KIND.COMPONENT,
|
|
519
|
+
tags,
|
|
520
|
+
content: JSON.stringify(args.payload),
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* kind:9802 highlight — NIP-84.
|
|
525
|
+
*
|
|
526
|
+
* `.content` is the excerpt itself. `e`/`a` tags point at a source event, `r` at
|
|
527
|
+
* a URL; `p` attributes the original author.
|
|
528
|
+
*
|
|
529
|
+
* Optional NIP-32 `L`/`l` self-labels categorise the highlight. NIP-32 §52
|
|
530
|
+
* allows those tags on non-1985 events for exactly this ("self-reporting"), so
|
|
531
|
+
* a highlight can say *what kind* of highlight it is without a bespoke kind.
|
|
532
|
+
*/
|
|
533
|
+
export function buildHighlight(pubkey, createdAtMs, args) {
|
|
534
|
+
assertHex64(pubkey, 'pubkey');
|
|
535
|
+
const tags = [];
|
|
536
|
+
if (args.channelUuid)
|
|
537
|
+
tags.push(['h', args.channelUuid]);
|
|
538
|
+
if (args.sourceEventId) {
|
|
539
|
+
assertHex64(args.sourceEventId, 'sourceEventId');
|
|
540
|
+
tags.push(['e', args.sourceEventId]);
|
|
541
|
+
}
|
|
542
|
+
if (args.sourceUrl)
|
|
543
|
+
tags.push(['r', args.sourceUrl]);
|
|
544
|
+
for (const p of args.attribution ?? []) {
|
|
545
|
+
const lower = p.toLowerCase();
|
|
546
|
+
assertHex64(lower, 'attribution pubkey');
|
|
547
|
+
tags.push(['p', lower, '', 'author']);
|
|
548
|
+
}
|
|
549
|
+
// NIP-32: the `L` namespace declaration precedes its `l` values.
|
|
550
|
+
for (const label of args.labels ?? []) {
|
|
551
|
+
tags.push(['L', label.namespace]);
|
|
552
|
+
tags.push(['l', label.value, label.namespace]);
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
pubkey,
|
|
556
|
+
created_at: toNostrSeconds(createdAtMs),
|
|
557
|
+
kind: KIND.HIGHLIGHT,
|
|
558
|
+
tags,
|
|
559
|
+
content: args.content,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* The relay's clock tolerance for a NIP-42 AUTH event, in seconds.
|
|
564
|
+
*
|
|
565
|
+
* `TIMESTAMP_TOLERANCE_SECS` in `crates/buzz-auth/src/nip42.rs` — the same ±60s
|
|
566
|
+
* NIP-98 uses, checked against the *relay's* clock. It is the one failure here
|
|
567
|
+
* a correct client can still hit: a browser whose clock is more than a minute
|
|
568
|
+
* out signs a perfectly valid event that is refused every time, and the socket
|
|
569
|
+
* is left open and permanently unauthenticated rather than closed.
|
|
570
|
+
*/
|
|
571
|
+
export const RELAY_AUTH_TOLERANCE_SECS = 60;
|
|
572
|
+
/**
|
|
573
|
+
* The relay URL a NIP-42 `relay` tag must carry, as Buzz computes it.
|
|
574
|
+
*
|
|
575
|
+
* `nip42_expected_relay_url` (`buzz-relay/src/api/bridge.rs:225`) is literally
|
|
576
|
+
* `format!("{scheme}://{}", tenant.host())` — scheme from the deployment, host
|
|
577
|
+
* from **the tenant the connection arrived on**, never the deployment-wide
|
|
578
|
+
* `config.relay_url`. Buzz has a test asserting exactly that
|
|
579
|
+
* (`nip42_expected_relay_url_uses_tenant_host_not_config_host`), because it
|
|
580
|
+
* regressed once.
|
|
581
|
+
*
|
|
582
|
+
* So: an origin, with no path and no trailing slash, derived from the URL we
|
|
583
|
+
* actually connected to. `normalize_relay_url` on the relay side would forgive
|
|
584
|
+
* a trailing slash, but it would not forgive a path or a different host.
|
|
585
|
+
*/
|
|
586
|
+
export function relayAuthUrl(connectUrl) {
|
|
587
|
+
return new URL(connectUrl).origin;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* An unsigned kind:22242 answering a relay's AUTH challenge.
|
|
591
|
+
*
|
|
592
|
+
* Built here rather than at a call site because the tag layout is part of the
|
|
593
|
+
* event id preimage, and a second copy would be a silent divergence the relay
|
|
594
|
+
* notices and we do not.
|
|
595
|
+
*
|
|
596
|
+
* **Tag order is not load-bearing for this one kind**, unusually for this file.
|
|
597
|
+
* Buzz looks both tags up by name — `tags.find(TagKind::Challenge)` and
|
|
598
|
+
* `tags.find(TagKind::Relay)` in `verify_nip42_event` — and the two reference
|
|
599
|
+
* clients disagree anyway: rust-nostr's `EventBuilder::auth` emits challenge
|
|
600
|
+
* first, nostr-tools' `makeAuthEvent` emits relay first. NIP-42's own example
|
|
601
|
+
* uses relay-then-challenge, which is what this follows. Nothing downstream
|
|
602
|
+
* compares this event's id to anything, because nothing ever stores it.
|
|
603
|
+
*/
|
|
604
|
+
export function buildUnsignedRelayAuthEvent(args) {
|
|
605
|
+
return {
|
|
606
|
+
pubkey: args.pubkey,
|
|
607
|
+
created_at: Math.floor((args.nowMs ?? Date.now()) / 1000),
|
|
608
|
+
kind: KIND.RELAY_AUTH,
|
|
609
|
+
tags: [
|
|
610
|
+
['relay', relayAuthUrl(args.relayUrl)],
|
|
611
|
+
['challenge', args.challenge],
|
|
612
|
+
],
|
|
613
|
+
content: '',
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
|