@agora-sdk/secure-chat-core 0.7.0 → 0.8.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/dist/cjs/content/builders.d.ts +40 -0
- package/dist/cjs/content/builders.js +85 -0
- package/dist/cjs/content/builders.js.map +1 -0
- package/dist/cjs/content/cbor.d.ts +48 -0
- package/dist/cjs/content/cbor.js +298 -0
- package/dist/cjs/content/cbor.js.map +1 -0
- package/dist/cjs/content/frame.d.ts +24 -0
- package/dist/cjs/content/frame.js +39 -0
- package/dist/cjs/content/frame.js.map +1 -0
- package/dist/cjs/content/mimi-content.d.ts +194 -0
- package/dist/cjs/content/mimi-content.js +289 -0
- package/dist/cjs/content/mimi-content.js.map +1 -0
- package/dist/cjs/hooks/message-fold.d.ts +91 -0
- package/dist/cjs/hooks/message-fold.js +218 -0
- package/dist/cjs/hooks/message-fold.js.map +1 -0
- package/dist/cjs/hooks/useSecureMessages.d.ts +30 -18
- package/dist/cjs/hooks/useSecureMessages.js +190 -238
- package/dist/cjs/hooks/useSecureMessages.js.map +1 -1
- package/dist/cjs/index.d.ts +7 -0
- package/dist/cjs/index.js +27 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/persistence/repository.d.ts +11 -11
- package/dist/cjs/persistence/repository.js +19 -19
- package/dist/cjs/persistence/repository.js.map +1 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/content/builders.d.ts +40 -0
- package/dist/esm/content/builders.js +77 -0
- package/dist/esm/content/builders.js.map +1 -0
- package/dist/esm/content/cbor.d.ts +48 -0
- package/dist/esm/content/cbor.js +292 -0
- package/dist/esm/content/cbor.js.map +1 -0
- package/dist/esm/content/frame.d.ts +24 -0
- package/dist/esm/content/frame.js +34 -0
- package/dist/esm/content/frame.js.map +1 -0
- package/dist/esm/content/mimi-content.d.ts +194 -0
- package/dist/esm/content/mimi-content.js +283 -0
- package/dist/esm/content/mimi-content.js.map +1 -0
- package/dist/esm/hooks/message-fold.d.ts +91 -0
- package/dist/esm/hooks/message-fold.js +214 -0
- package/dist/esm/hooks/message-fold.js.map +1 -0
- package/dist/esm/hooks/useSecureMessages.d.ts +30 -18
- package/dist/esm/hooks/useSecureMessages.js +192 -240
- package/dist/esm/hooks/useSecureMessages.js.map +1 -1
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/persistence/repository.d.ts +11 -11
- package/dist/esm/persistence/repository.js +19 -19
- package/dist/esm/persistence/repository.js.map +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Fold reducer — collapses reaction/edit/delete/un-react MimiContent messages onto their target post.
|
|
3
|
+
//
|
|
4
|
+
// The hybrid reference model: messages REFERENCE each other by MIMI content-hash (resolved here through
|
|
5
|
+
// an in-memory hex(contentHash) → messageId index), while the hook keys STORAGE/ordering/dedup by the
|
|
6
|
+
// server messageId. A mutation whose target hasn't been decoded yet (MLS delivery isn't globally
|
|
7
|
+
// ordered — a reaction can arrive before its message) is BUFFERED by target-hash and applied the moment
|
|
8
|
+
// the target lands ("buffer, never silently drop" — the same fail-closed discipline used for epoch
|
|
9
|
+
// ordering). On reload the index rebuilds for free by recomputing contentHash from the stored canonical
|
|
10
|
+
// bytes, so no hash→id index is persisted. Pure: no React, no crypto, no I/O.
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.MessageFold = void 0;
|
|
13
|
+
const mimi_content_js_1 = require("../content/mimi-content.js");
|
|
14
|
+
const hex = (b) => {
|
|
15
|
+
let s = "";
|
|
16
|
+
for (const x of b)
|
|
17
|
+
s += x.toString(16).padStart(2, "0");
|
|
18
|
+
return s;
|
|
19
|
+
};
|
|
20
|
+
function textBody(p) {
|
|
21
|
+
if (p.cardinality === mimi_content_js_1.Cardinality.Single)
|
|
22
|
+
return new TextDecoder().decode(p.content);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
function classify(m) {
|
|
26
|
+
const isNull = m.nestedPart.cardinality === mimi_content_js_1.Cardinality.Null;
|
|
27
|
+
const isReaction = m.nestedPart.cardinality === mimi_content_js_1.Cardinality.Single &&
|
|
28
|
+
m.nestedPart.disposition === mimi_content_js_1.Disposition.Reaction;
|
|
29
|
+
if (m.replaces) {
|
|
30
|
+
if (isNull)
|
|
31
|
+
return { kind: "delete-or-unreact", target: m.replaces };
|
|
32
|
+
return { kind: "edit", target: m.replaces, body: textBody(m.nestedPart) };
|
|
33
|
+
}
|
|
34
|
+
if (m.inReplyTo) {
|
|
35
|
+
if (isReaction)
|
|
36
|
+
return { kind: "reaction", target: m.inReplyTo, token: textBody(m.nestedPart) ?? "" };
|
|
37
|
+
return { kind: "reply", body: textBody(m.nestedPart), replyTo: m.inReplyTo };
|
|
38
|
+
}
|
|
39
|
+
return { kind: "post", body: textBody(m.nestedPart), replyTo: null };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Stateful fold over decoded content messages. Feed every decoded message via {@link MessageFold.apply};
|
|
43
|
+
* read the rendered projection of a post/reply via {@link MessageFold.getContent}. Mutation messages
|
|
44
|
+
* (edit/delete/reaction/un-react) fold onto their target and are reported non-renderable.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const fold = new MessageFold();
|
|
49
|
+
* const { renderable } = fold.apply(decoded); // false for a reaction/edit/delete
|
|
50
|
+
* const content = fold.getContent(decoded.messageId); // post/reply projection or null
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
class MessageFold {
|
|
54
|
+
constructor() {
|
|
55
|
+
this.rows = new Map(); // messageId → rendered row (post/reply only)
|
|
56
|
+
this.idByHash = new Map(); // hex(contentHash) → messageId (ALL content messages)
|
|
57
|
+
this.reactionReg = new Map(); // reactionMsgId → …
|
|
58
|
+
this.pending = new Map(); // hex(targetHash) → buffered mutations
|
|
59
|
+
}
|
|
60
|
+
/** Drop all state (e.g. on conversation switch or full reload before re-folding). */
|
|
61
|
+
reset() {
|
|
62
|
+
this.rows.clear();
|
|
63
|
+
this.idByHash.clear();
|
|
64
|
+
this.reactionReg.clear();
|
|
65
|
+
this.pending.clear();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* True if `messageId` produced a visible (post/reply) row.
|
|
69
|
+
* @param messageId - The server message id to test.
|
|
70
|
+
* @returns Whether a rendered row exists for it.
|
|
71
|
+
*/
|
|
72
|
+
isRenderable(messageId) {
|
|
73
|
+
return this.rows.has(messageId);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The rendered projection for a post/reply, or `null` for a folded mutation / unknown id.
|
|
77
|
+
* @param messageId - The server message id to project.
|
|
78
|
+
* @returns A fresh {@link RenderedContent} snapshot, or `null` when the id is not a rendered row.
|
|
79
|
+
*/
|
|
80
|
+
getContent(messageId) {
|
|
81
|
+
const r = this.rows.get(messageId);
|
|
82
|
+
if (!r)
|
|
83
|
+
return null;
|
|
84
|
+
const reactions = {};
|
|
85
|
+
for (const [token, set] of r.reactions)
|
|
86
|
+
if (set.size > 0)
|
|
87
|
+
reactions[token] = set.size;
|
|
88
|
+
// Copy `replyTo` so a caller can't mutate the fold's internal hash bytes (body is a string — immutable).
|
|
89
|
+
return { body: r.body, replyTo: r.replyTo ? r.replyTo.slice() : null, editedAt: r.editedAt, deleted: r.deleted, reactions };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Apply one decoded message. Buffers a mutation whose target is unknown.
|
|
93
|
+
*
|
|
94
|
+
* Idempotent on a strict immediate re-apply of the same `messageId`, but NOT across an interleaved
|
|
95
|
+
* re-delivery: re-applying an already-withdrawn reaction id resurrects it (the replayed reaction
|
|
96
|
+
* re-adds itself to the now-empty token set and the prior un-react is not re-triggered). The CONSUMER
|
|
97
|
+
* (the hook) MUST dedup by `messageId` and apply each id at most once — do not re-feed an
|
|
98
|
+
* already-applied message.
|
|
99
|
+
* @param msg - The decoded, content-hashed message to fold in.
|
|
100
|
+
* @returns `{ renderable }` — whether this id is a standalone (post/reply) row.
|
|
101
|
+
*/
|
|
102
|
+
apply(msg) {
|
|
103
|
+
const selfHex = hex(msg.contentHash);
|
|
104
|
+
this.idByHash.set(selfHex, msg.messageId);
|
|
105
|
+
const op = classify(msg.mimi);
|
|
106
|
+
if (op.kind === "post" || op.kind === "reply") {
|
|
107
|
+
const existing = this.rows.get(msg.messageId);
|
|
108
|
+
const row = existing ?? {
|
|
109
|
+
messageId: msg.messageId,
|
|
110
|
+
createdAt: msg.createdAt,
|
|
111
|
+
senderDeviceId: msg.senderDeviceId,
|
|
112
|
+
body: op.body,
|
|
113
|
+
replyTo: op.kind === "reply" ? op.replyTo : null,
|
|
114
|
+
editedAt: null,
|
|
115
|
+
deleted: false,
|
|
116
|
+
reactions: new Map(),
|
|
117
|
+
};
|
|
118
|
+
if (!existing)
|
|
119
|
+
this.rows.set(msg.messageId, row);
|
|
120
|
+
this.drain(selfHex); // a target just appeared → apply anything buffered against it
|
|
121
|
+
return { renderable: true };
|
|
122
|
+
}
|
|
123
|
+
// Mutations resolve a target by content-hash; buffer if the target isn't present yet.
|
|
124
|
+
if (op.kind === "edit") {
|
|
125
|
+
const row = this.resolveRow(op.target);
|
|
126
|
+
if (!row)
|
|
127
|
+
return this.buffer(op.target, msg);
|
|
128
|
+
row.body = op.body;
|
|
129
|
+
row.editedAt = msg.createdAt;
|
|
130
|
+
return { renderable: false };
|
|
131
|
+
}
|
|
132
|
+
if (op.kind === "reaction") {
|
|
133
|
+
const row = this.resolveRow(op.target);
|
|
134
|
+
if (!row)
|
|
135
|
+
return this.buffer(op.target, msg);
|
|
136
|
+
let set = row.reactions.get(op.token);
|
|
137
|
+
if (!set)
|
|
138
|
+
row.reactions.set(op.token, (set = new Set()));
|
|
139
|
+
set.add(msg.messageId);
|
|
140
|
+
this.reactionReg.set(msg.messageId, { targetHash: hex(op.target), token: op.token });
|
|
141
|
+
this.drain(selfHex); // a pending un-react of THIS reaction can now apply
|
|
142
|
+
return { renderable: false };
|
|
143
|
+
}
|
|
144
|
+
// delete-or-unreact: distinguish by whether `replaces` points at a known reaction message.
|
|
145
|
+
if (op.kind === "delete-or-unreact") {
|
|
146
|
+
const targetId = this.idByHash.get(hex(op.target));
|
|
147
|
+
const reaction = targetId ? this.reactionReg.get(targetId) : undefined;
|
|
148
|
+
if (reaction) {
|
|
149
|
+
// Un-react: drop exactly the one reaction message from its target's token set.
|
|
150
|
+
const row = this.resolveRow(fromHex(reaction.targetHash) ?? new Uint8Array());
|
|
151
|
+
row?.reactions.get(reaction.token)?.delete(targetId);
|
|
152
|
+
return { renderable: false };
|
|
153
|
+
}
|
|
154
|
+
// Un-react-before-reaction guard: the reaction message was INDEXED on first sight (so `targetId`
|
|
155
|
+
// resolves) but BUFFERED, not applied — hence no `reactionReg` entry yet AND no rendered row for
|
|
156
|
+
// it. Treat this as an un-react of an as-yet-unapplied reaction: buffer it under the reaction's
|
|
157
|
+
// hash so the reaction branch's `drain(selfHex)` replays the un-react after the reaction folds in.
|
|
158
|
+
// Without this guard the delete path below would tombstone a non-row target as a no-op, then the
|
|
159
|
+
// replayed reaction would survive — net wrong. Fail closed: buffer, never silently drop.
|
|
160
|
+
if (targetId && !this.rows.has(targetId))
|
|
161
|
+
return this.buffer(op.target, msg);
|
|
162
|
+
const row = this.resolveRow(op.target);
|
|
163
|
+
if (!row)
|
|
164
|
+
return this.buffer(op.target, msg); // a delete of an as-yet-unseen post → buffer it
|
|
165
|
+
row.deleted = true;
|
|
166
|
+
row.body = null;
|
|
167
|
+
return { renderable: false };
|
|
168
|
+
}
|
|
169
|
+
// Unreachable: `classify` is exhaustive over Op.kind; satisfies the compiler's return analysis.
|
|
170
|
+
return { renderable: false };
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Resolve the rendered row a content-hash points at, if it has been decoded.
|
|
174
|
+
* @param targetHash - The referenced message's content-hash.
|
|
175
|
+
* @returns The {@link Row}, or `undefined` if the target is unknown or not a rendered row.
|
|
176
|
+
*/
|
|
177
|
+
resolveRow(targetHash) {
|
|
178
|
+
const id = this.idByHash.get(hex(targetHash));
|
|
179
|
+
return id ? this.rows.get(id) : undefined;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Buffer a mutation against the hex of its (not-yet-decoded) target; replayed by {@link MessageFold.drain}.
|
|
183
|
+
* @param targetHash - The target content-hash to key the buffer under.
|
|
184
|
+
* @param msg - The mutation to defer.
|
|
185
|
+
* @returns `{ renderable: false }` — a buffered mutation is never a standalone row.
|
|
186
|
+
*/
|
|
187
|
+
buffer(targetHash, msg) {
|
|
188
|
+
const key = hex(targetHash);
|
|
189
|
+
const list = this.pending.get(key) ?? [];
|
|
190
|
+
list.push(msg);
|
|
191
|
+
this.pending.set(key, list);
|
|
192
|
+
return { renderable: false };
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Replay every mutation buffered against a content-hash that has just been decoded.
|
|
196
|
+
* @param hashHex - The hex content-hash that just appeared (a post/reply or a now-applied reaction).
|
|
197
|
+
*/
|
|
198
|
+
drain(hashHex) {
|
|
199
|
+
const list = this.pending.get(hashHex);
|
|
200
|
+
if (!list)
|
|
201
|
+
return;
|
|
202
|
+
this.pending.delete(hashHex);
|
|
203
|
+
for (const m of list)
|
|
204
|
+
this.apply(m);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
exports.MessageFold = MessageFold;
|
|
208
|
+
// Map a hex key back to bytes (for resolving a reaction's stored target-hash). Kept local — the fold is
|
|
209
|
+
// the only place that round-trips hash hex.
|
|
210
|
+
function fromHex(h) {
|
|
211
|
+
if (h.length % 2 !== 0)
|
|
212
|
+
return null;
|
|
213
|
+
const out = new Uint8Array(h.length / 2);
|
|
214
|
+
for (let i = 0; i < out.length; i++)
|
|
215
|
+
out[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16);
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=message-fold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-fold.js","sourceRoot":"","sources":["../../../src/hooks/message-fold.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
import { SecureMessageModel } from "../contract/index.js";
|
|
2
2
|
import { GroupHandle, type SecureDecryptFailureReason } from "@agora-sdk/secure-chat-crypto";
|
|
3
|
+
import { type MimiContent } from "../content/mimi-content.js";
|
|
4
|
+
import { type RenderedContent } from "./message-fold.js";
|
|
3
5
|
/**
|
|
4
6
|
* Decryption outcome for a stored message:
|
|
5
|
-
* - `ok` — decrypted + authenticated; `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* - `rejected` — fails closed
|
|
9
|
-
* too-old epoch). NEVER retried; `plaintext` is null and `rejectedReason` says why.
|
|
7
|
+
* - `ok` — decrypted + authenticated + decoded; `content`/`mimi`/`contentHash` are set (unless it is a
|
|
8
|
+
* folded mutation, which the list omits, or a reserved control frame, which is hidden).
|
|
9
|
+
* - `pending` — not decryptable yet (no handle, or epoch ahead). Retried when the group advances.
|
|
10
|
+
* - `rejected` — fails closed (replay/gap/bad-auth/malformed/too-old, or undecodable content). Never retried.
|
|
10
11
|
*/
|
|
11
12
|
export type SecureMessageStatus = "ok" | "pending" | "rejected";
|
|
12
|
-
/** A stored message paired with its
|
|
13
|
+
/** A stored message paired with its decoded, folded content. */
|
|
13
14
|
export interface DecryptedSecureMessage {
|
|
14
15
|
/** The raw message row from the server (still holds the base64 ciphertext). */
|
|
15
16
|
model: SecureMessageModel;
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
/**
|
|
17
|
+
/** The Tier-2 projection (body/replyTo/editedAt/deleted/reactions), or `null` when not `ok`. */
|
|
18
|
+
content: RenderedContent | null;
|
|
19
|
+
/** The raw decoded `MimiContent` (power users), or `null` when not `ok`. */
|
|
20
|
+
mimi: MimiContent | null;
|
|
21
|
+
/** SHA-256 content-hash — reference this to reply/react/edit/delete — or `null` when not `ok`. */
|
|
22
|
+
contentHash: Uint8Array | null;
|
|
23
|
+
/** Decryption outcome. */
|
|
19
24
|
status: SecureMessageStatus;
|
|
20
|
-
/** When `status` is `rejected`, why
|
|
25
|
+
/** When `status` is `rejected`, why. */
|
|
21
26
|
rejectedReason?: SecureDecryptFailureReason;
|
|
22
27
|
}
|
|
23
28
|
/** Options for {@link useSecureMessages}. */
|
|
@@ -29,7 +34,7 @@ export interface UseSecureMessagesOptions {
|
|
|
29
34
|
}
|
|
30
35
|
/** The state and actions returned by {@link useSecureMessages}. */
|
|
31
36
|
export interface UseSecureMessagesValues {
|
|
32
|
-
/**
|
|
37
|
+
/** Rendered messages (post/reply rows with folded reactions/edits/deletes), newest first. */
|
|
33
38
|
messages: DecryptedSecureMessage[];
|
|
34
39
|
/** True while a page load or refresh is in flight. */
|
|
35
40
|
loading: boolean;
|
|
@@ -41,15 +46,21 @@ export interface UseSecureMessagesValues {
|
|
|
41
46
|
loadMore: () => Promise<void>;
|
|
42
47
|
/** Reload from the newest message, replacing the current list. */
|
|
43
48
|
refresh: () => Promise<void>;
|
|
44
|
-
/** Encrypt + send a text
|
|
49
|
+
/** Encrypt + send a text post. */
|
|
45
50
|
sendMessage: (text: string) => Promise<void>;
|
|
51
|
+
/** Send a reply to the message with `targetHash` (its {@link DecryptedSecureMessage.contentHash}). */
|
|
52
|
+
reply: (targetHash: Uint8Array, text: string) => Promise<void>;
|
|
53
|
+
/** React to the message with `targetHash` using `token` (e.g. an emoji). */
|
|
54
|
+
react: (targetHash: Uint8Array, token: string) => Promise<void>;
|
|
55
|
+
/** Edit the message with `targetHash`, replacing its body with `text`. */
|
|
56
|
+
editMessage: (targetHash: Uint8Array, text: string) => Promise<void>;
|
|
57
|
+
/** Delete (tombstone) the message with `targetHash`. */
|
|
58
|
+
deleteMessage: (targetHash: Uint8Array) => Promise<void>;
|
|
59
|
+
/** Withdraw your reaction whose own content-hash is `reactionHash`. */
|
|
60
|
+
unreact: (reactionHash: Uint8Array) => Promise<void>;
|
|
46
61
|
}
|
|
47
62
|
/**
|
|
48
|
-
* Load, decrypt, send, and live-receive messages in one secure conversation.
|
|
49
|
-
*
|
|
50
|
-
* Auto-resolves the MLS group handle (via `resolveGroup`) and the sender device id (from the
|
|
51
|
-
* persisted device) unless overridden in `options`. Joins the conversation socket room for live
|
|
52
|
-
* `secure:message` events.
|
|
63
|
+
* Load, decrypt, send, and live-receive MIMI-content messages in one secure conversation.
|
|
53
64
|
*
|
|
54
65
|
* @param conversationId - The conversation to read and send within.
|
|
55
66
|
* @param options - {@link UseSecureMessagesOptions}.
|
|
@@ -57,8 +68,9 @@ export interface UseSecureMessagesValues {
|
|
|
57
68
|
*
|
|
58
69
|
* @example
|
|
59
70
|
* ```tsx
|
|
60
|
-
* const { messages, sendMessage } = useSecureMessages(conversationId);
|
|
71
|
+
* const { messages, sendMessage, react } = useSecureMessages(conversationId);
|
|
61
72
|
* await sendMessage("hello 💜");
|
|
73
|
+
* await react(messages[0].contentHash!, "👍");
|
|
62
74
|
* ```
|
|
63
75
|
*/
|
|
64
76
|
export declare function useSecureMessages(conversationId: string, options?: UseSecureMessagesOptions): UseSecureMessagesValues;
|