@agora-sdk/secure-chat-core 0.2.0 → 0.3.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/context/secure-chat-context.d.ts +10 -0
- package/dist/cjs/context/secure-chat-context.js +37 -1
- package/dist/cjs/context/secure-chat-context.js.map +1 -1
- package/dist/cjs/hooks/useSecureHandshakes.d.ts +64 -0
- package/dist/cjs/hooks/useSecureHandshakes.js +215 -0
- package/dist/cjs/hooks/useSecureHandshakes.js.map +1 -0
- package/dist/cjs/hooks/useSecureMessages.js +12 -2
- package/dist/cjs/hooks/useSecureMessages.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/context/secure-chat-context.d.ts +10 -0
- package/dist/esm/context/secure-chat-context.js +37 -1
- package/dist/esm/context/secure-chat-context.js.map +1 -1
- package/dist/esm/hooks/useSecureHandshakes.d.ts +64 -0
- package/dist/esm/hooks/useSecureHandshakes.js +212 -0
- package/dist/esm/hooks/useSecureHandshakes.js.map +1 -0
- package/dist/esm/hooks/useSecureMessages.js +12 -2
- package/dist/esm/hooks/useSecureMessages.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -21,6 +21,16 @@ export interface SecureChatContextValue {
|
|
|
21
21
|
resolveGroup: (conversationId: string) => Promise<GroupHandle | null>;
|
|
22
22
|
/** Cache + persist a conversation's group handle (after createGroup / processWelcome). */
|
|
23
23
|
rememberGroup: (conversationId: string, handle: GroupHandle) => Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Current change-version for a conversation's group handle. Bumps every time the handle advances
|
|
26
|
+
* (a join or a processed Commit), so consumers can detect "the group moved" without diffing handles.
|
|
27
|
+
*/
|
|
28
|
+
getGroupVersion: (conversationId: string) => number;
|
|
29
|
+
/**
|
|
30
|
+
* Subscribe to group-handle changes across all conversations (fired by {@link rememberGroup}).
|
|
31
|
+
* @returns An unsubscribe function.
|
|
32
|
+
*/
|
|
33
|
+
subscribeGroupChange: (listener: () => void) => () => void;
|
|
24
34
|
/** The Agora project id these clients are scoped to. */
|
|
25
35
|
projectId: string;
|
|
26
36
|
}
|
|
@@ -51,6 +51,12 @@ function SecureChatProvider({ crypto, projectId, store, accessToken, getAccessTo
|
|
|
51
51
|
// NOTE: not cleared on a `store` prop swap — a store change in practice means a new provider
|
|
52
52
|
// instance (fresh cache), not a live prop change on the same mounted provider.
|
|
53
53
|
const groupCache = (0, react_1.useRef)(new Map());
|
|
54
|
+
// Per-conversation change counter, bumped whenever a group handle advances (a join or a processed
|
|
55
|
+
// Commit). Lets useSecureMessages re-resolve and flush buffered (undecrypted) rows without a
|
|
56
|
+
// re-fetch. Held in a ref + listener set so a bump notifies consumers WITHOUT re-rendering the
|
|
57
|
+
// provider (and thus rebuilding rest/socket/repo).
|
|
58
|
+
const groupVersion = (0, react_1.useRef)(new Map());
|
|
59
|
+
const groupListeners = (0, react_1.useRef)(new Set());
|
|
54
60
|
const resolveGroup = (0, react_1.useCallback)(async (conversationId) => {
|
|
55
61
|
const cached = groupCache.current.get(conversationId);
|
|
56
62
|
if (cached)
|
|
@@ -66,11 +72,41 @@ function SecureChatProvider({ crypto, projectId, store, accessToken, getAccessTo
|
|
|
66
72
|
groupCache.current.set(conversationId, handle);
|
|
67
73
|
const bytes = await crypto.exportGroupState(handle);
|
|
68
74
|
await repo.saveGroupState(conversationId, bytes);
|
|
75
|
+
// Signal that this conversation's group advanced, so message hooks re-resolve + flush.
|
|
76
|
+
groupVersion.current.set(conversationId, (groupVersion.current.get(conversationId) ?? 0) + 1);
|
|
77
|
+
groupListeners.current.forEach((l) => l());
|
|
69
78
|
}, [repo, crypto]);
|
|
79
|
+
const getGroupVersion = (0, react_1.useCallback)((conversationId) => groupVersion.current.get(conversationId) ?? 0, []);
|
|
80
|
+
const subscribeGroupChange = (0, react_1.useCallback)((listener) => {
|
|
81
|
+
groupListeners.current.add(listener);
|
|
82
|
+
return () => {
|
|
83
|
+
groupListeners.current.delete(listener);
|
|
84
|
+
};
|
|
85
|
+
}, []);
|
|
70
86
|
(0, react_1.useEffect)(() => {
|
|
71
87
|
return () => socket.disconnect();
|
|
72
88
|
}, [socket]);
|
|
73
|
-
const value = (0, react_1.useMemo)(() => ({
|
|
89
|
+
const value = (0, react_1.useMemo)(() => ({
|
|
90
|
+
rest,
|
|
91
|
+
socket,
|
|
92
|
+
crypto,
|
|
93
|
+
repo,
|
|
94
|
+
resolveGroup,
|
|
95
|
+
rememberGroup,
|
|
96
|
+
getGroupVersion,
|
|
97
|
+
subscribeGroupChange,
|
|
98
|
+
projectId,
|
|
99
|
+
}), [
|
|
100
|
+
rest,
|
|
101
|
+
socket,
|
|
102
|
+
crypto,
|
|
103
|
+
repo,
|
|
104
|
+
resolveGroup,
|
|
105
|
+
rememberGroup,
|
|
106
|
+
getGroupVersion,
|
|
107
|
+
subscribeGroupChange,
|
|
108
|
+
projectId,
|
|
109
|
+
]);
|
|
74
110
|
return (0, jsx_runtime_1.jsx)(SecureChatContext.Provider, { value: value, children: children });
|
|
75
111
|
}
|
|
76
112
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secure-chat-context.js","sourceRoot":"","sources":["../../../src/context/secure-chat-context.tsx"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"secure-chat-context.js","sourceRoot":"","sources":["../../../src/context/secure-chat-context.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { SecureHandshakeModel } from "../contract/index.js";
|
|
2
|
+
/** Options for {@link useSecureHandshakes}. */
|
|
3
|
+
export interface UseSecureHandshakesOptions {
|
|
4
|
+
/**
|
|
5
|
+
* The device row id whose inbox to drain. Defaults to the persisted device's `.id`. Pass the id
|
|
6
|
+
* from `useSecureDevice` so processing starts the moment the device registers (a persisted device
|
|
7
|
+
* is picked up automatically on reload even without this).
|
|
8
|
+
*/
|
|
9
|
+
deviceId?: string;
|
|
10
|
+
/** Disable processing (e.g. before sign-in). Default true. */
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
/** Page size for the catch-up fetch loop. Default 100. */
|
|
13
|
+
pageSize?: number;
|
|
14
|
+
/** Called per handshake-processing error; the inbox skips the bad row and continues. */
|
|
15
|
+
onError?: (err: unknown, handshake?: SecureHandshakeModel) => void;
|
|
16
|
+
}
|
|
17
|
+
/** The state and actions returned by {@link useSecureHandshakes}. */
|
|
18
|
+
export interface UseSecureHandshakesValues {
|
|
19
|
+
/** True until the initial catch-up loop settles. */
|
|
20
|
+
catchingUp: boolean;
|
|
21
|
+
/** True once catch-up completed and live subscriptions are active. */
|
|
22
|
+
ready: boolean;
|
|
23
|
+
/** The last processed delivery cursor (`seq`), or `null`. */
|
|
24
|
+
cursor: string | null;
|
|
25
|
+
/** Count of handshakes applied this session. */
|
|
26
|
+
processedCount: number;
|
|
27
|
+
/** The last error surfaced (also delivered via `onError`), or `null`. */
|
|
28
|
+
error: unknown;
|
|
29
|
+
/**
|
|
30
|
+
* Re-run the catch-up loop from the persisted cursor. The reusable primitive a future 409
|
|
31
|
+
* epoch-conflict rebase (on a membership Commit) calls before rebuilding + retrying its Commit.
|
|
32
|
+
*/
|
|
33
|
+
resync: () => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Process this device's MLS handshake inbox so the recipient side of secure chat works.
|
|
37
|
+
*
|
|
38
|
+
* On mount (once a device id is available) it catches up via `fetchHandshakes(since=cursor)`, re-joins
|
|
39
|
+
* the socket rooms for groups it already holds, then processes live `secure:welcome` /
|
|
40
|
+
* `secure:handshake` events — all serialized in `seq` order, deduped, and cursor-persisted. Mount it
|
|
41
|
+
* exactly once near `useSecureDevice`.
|
|
42
|
+
*
|
|
43
|
+
* @param options - {@link UseSecureHandshakesOptions}.
|
|
44
|
+
* @returns {@link UseSecureHandshakesValues}.
|
|
45
|
+
*
|
|
46
|
+
* @remarks
|
|
47
|
+
* Single-device only (Phase 3 adds own-device fan-out + MLS generation-counter gap detection). A
|
|
48
|
+
* Commit for a group whose Welcome hasn't been seen is skipped — `seq` ordering puts the Welcome
|
|
49
|
+
* first, so an unknown group means this device is genuinely not a member.
|
|
50
|
+
*
|
|
51
|
+
* Mount it exactly once. Processing is serialized *within* a mount, and the cursor is re-read from
|
|
52
|
+
* storage at the start of each run, so a serialized re-mount (or `deviceId` change) resumes without
|
|
53
|
+
* reprocessing. It does NOT serialize across a *concurrent* re-mount whose prior catch-up is still
|
|
54
|
+
* in flight; that case relies on `processWelcome` / `processCommit` being idempotent for a replayed
|
|
55
|
+
* blob. The mock is idempotent (so dev StrictMode double-invoke is harmless); harden this when the
|
|
56
|
+
* real MLS core lands (Task 1) if its handshake processing is not replay-safe.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* const { device } = useSecureDevice();
|
|
61
|
+
* useSecureHandshakes({ deviceId: device?.id });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function useSecureHandshakes(options?: UseSecureHandshakesOptions): UseSecureHandshakesValues;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// useSecureHandshakes — drain and process this device's MLS handshake inbox (the recipient side).
|
|
3
|
+
//
|
|
4
|
+
// The blind DS delivers each device a stream of handshakes (Welcomes targeted at it, plus broadcast
|
|
5
|
+
// Commits/Proposals for its groups), ordered by a monotonic `seq`. This hook is what makes a
|
|
6
|
+
// RECIPIENT actually join and stay current: on connect it pulls `GET .../handshakes?since=<cursor>`
|
|
7
|
+
// to the end, then processes live `secure:welcome` / `secure:handshake` events — all funneled through
|
|
8
|
+
// ONE serialized, seq-ordered, idempotent path, persisting the cursor as it goes. A processed Welcome
|
|
9
|
+
// joins a group (rememberGroup); a processed Commit advances its epoch. Mount it once, near
|
|
10
|
+
// useSecureDevice.
|
|
11
|
+
//
|
|
12
|
+
// Ordering invariant: live events that arrive WHILE catching up are buffered and replayed in `seq`
|
|
13
|
+
// order AFTER catch-up — otherwise a high-seq live event would advance the cursor past not-yet-fetched
|
|
14
|
+
// rows and the dedupe check would drop them (data loss).
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.useSecureHandshakes = useSecureHandshakes;
|
|
17
|
+
const react_1 = require("react");
|
|
18
|
+
const base64_js_1 = require("../util/base64.js");
|
|
19
|
+
const secure_chat_context_js_1 = require("../context/secure-chat-context.js");
|
|
20
|
+
/** Compare two decimal-string `seq` cursors numerically (string compare is wrong across digit widths). */
|
|
21
|
+
function compareSeq(a, b) {
|
|
22
|
+
const d = BigInt(a) - BigInt(b);
|
|
23
|
+
return d > 0n ? 1 : d < 0n ? -1 : 0;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Process this device's MLS handshake inbox so the recipient side of secure chat works.
|
|
27
|
+
*
|
|
28
|
+
* On mount (once a device id is available) it catches up via `fetchHandshakes(since=cursor)`, re-joins
|
|
29
|
+
* the socket rooms for groups it already holds, then processes live `secure:welcome` /
|
|
30
|
+
* `secure:handshake` events — all serialized in `seq` order, deduped, and cursor-persisted. Mount it
|
|
31
|
+
* exactly once near `useSecureDevice`.
|
|
32
|
+
*
|
|
33
|
+
* @param options - {@link UseSecureHandshakesOptions}.
|
|
34
|
+
* @returns {@link UseSecureHandshakesValues}.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* Single-device only (Phase 3 adds own-device fan-out + MLS generation-counter gap detection). A
|
|
38
|
+
* Commit for a group whose Welcome hasn't been seen is skipped — `seq` ordering puts the Welcome
|
|
39
|
+
* first, so an unknown group means this device is genuinely not a member.
|
|
40
|
+
*
|
|
41
|
+
* Mount it exactly once. Processing is serialized *within* a mount, and the cursor is re-read from
|
|
42
|
+
* storage at the start of each run, so a serialized re-mount (or `deviceId` change) resumes without
|
|
43
|
+
* reprocessing. It does NOT serialize across a *concurrent* re-mount whose prior catch-up is still
|
|
44
|
+
* in flight; that case relies on `processWelcome` / `processCommit` being idempotent for a replayed
|
|
45
|
+
* blob. The mock is idempotent (so dev StrictMode double-invoke is harmless); harden this when the
|
|
46
|
+
* real MLS core lands (Task 1) if its handshake processing is not replay-safe.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* const { device } = useSecureDevice();
|
|
51
|
+
* useSecureHandshakes({ deviceId: device?.id });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
function useSecureHandshakes(options = {}) {
|
|
55
|
+
const { rest, crypto, socket, repo, resolveGroup, rememberGroup } = (0, secure_chat_context_js_1.useSecureChat)();
|
|
56
|
+
const { deviceId: deviceIdOption, pageSize = 100 } = options;
|
|
57
|
+
const enabled = options.enabled ?? true;
|
|
58
|
+
const [catchingUp, setCatchingUp] = (0, react_1.useState)(true);
|
|
59
|
+
const [ready, setReady] = (0, react_1.useState)(false);
|
|
60
|
+
const [cursor, setCursor] = (0, react_1.useState)(null);
|
|
61
|
+
const [processedCount, setProcessedCount] = (0, react_1.useState)(0);
|
|
62
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
63
|
+
// onError read through a ref so passing an inline callback doesn't re-run the effect.
|
|
64
|
+
const onErrorRef = (0, react_1.useRef)(options.onError);
|
|
65
|
+
onErrorRef.current = options.onError;
|
|
66
|
+
// The catch-up routine, published from the effect so `resync()` (stable) can invoke it.
|
|
67
|
+
const runCatchUpRef = (0, react_1.useRef)(null);
|
|
68
|
+
const resync = (0, react_1.useCallback)(async () => {
|
|
69
|
+
await runCatchUpRef.current?.();
|
|
70
|
+
}, []);
|
|
71
|
+
(0, react_1.useEffect)(() => {
|
|
72
|
+
if (!enabled)
|
|
73
|
+
return;
|
|
74
|
+
let alive = true;
|
|
75
|
+
const offFns = [];
|
|
76
|
+
// Async state shared across the catch-up loop, live handlers, and the serial apply queue.
|
|
77
|
+
const cursorRef = { current: null };
|
|
78
|
+
const catchingUpRef = { current: true };
|
|
79
|
+
const liveBuffer = [];
|
|
80
|
+
let queue = Promise.resolve();
|
|
81
|
+
let deviceId;
|
|
82
|
+
const report = (err, h) => {
|
|
83
|
+
if (alive)
|
|
84
|
+
setError(err);
|
|
85
|
+
onErrorRef.current?.(err, h);
|
|
86
|
+
};
|
|
87
|
+
const dispatchByKind = async (h) => {
|
|
88
|
+
const payload = (0, base64_js_1.fromBase64)(h.payload);
|
|
89
|
+
if (h.kind === "welcome") {
|
|
90
|
+
// Targeted at us → join the group, then join its room for future broadcast Commits.
|
|
91
|
+
if (h.targetDeviceId && h.targetDeviceId !== deviceId)
|
|
92
|
+
return;
|
|
93
|
+
const handle = await crypto.processWelcome(payload);
|
|
94
|
+
await rememberGroup(h.conversationId, handle);
|
|
95
|
+
socket.joinConversation(h.conversationId);
|
|
96
|
+
}
|
|
97
|
+
else if (h.kind === "commit") {
|
|
98
|
+
const group = await resolveGroup(h.conversationId);
|
|
99
|
+
if (!group) {
|
|
100
|
+
// Unknown group at a Commit: with seq ordering the Welcome precedes it, so we're not a
|
|
101
|
+
// member of this conversation. Skip (the cursor still advances so we don't re-fetch it).
|
|
102
|
+
report(new Error(`secure-chat: commit for unknown group ${h.conversationId}`), h);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const advanced = await crypto.processCommit(group, payload);
|
|
106
|
+
await rememberGroup(h.conversationId, advanced);
|
|
107
|
+
}
|
|
108
|
+
else if (h.kind === "proposal") {
|
|
109
|
+
const group = await resolveGroup(h.conversationId);
|
|
110
|
+
if (group)
|
|
111
|
+
await crypto.processProposal(group, payload);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
// The ONE place that mutates the cursor + crypto state. Dedupes by seq; advances the cursor even
|
|
115
|
+
// when a row is skipped or its dispatch throws, so a poison blob can never wedge the inbox. A hard
|
|
116
|
+
// crash mid-dispatch leaves the cursor unsaved, so the row replays on restart (no loss).
|
|
117
|
+
const applyOrdered = async (h) => {
|
|
118
|
+
if (cursorRef.current !== null && compareSeq(h.seq, cursorRef.current) <= 0)
|
|
119
|
+
return;
|
|
120
|
+
try {
|
|
121
|
+
await dispatchByKind(h);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
report(err, h);
|
|
125
|
+
}
|
|
126
|
+
cursorRef.current = h.seq;
|
|
127
|
+
await repo.saveHandshakeCursor(h.seq);
|
|
128
|
+
if (alive) {
|
|
129
|
+
setCursor(h.seq);
|
|
130
|
+
setProcessedCount((n) => n + 1);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const schedule = (h) => {
|
|
134
|
+
queue = queue.then(() => applyOrdered(h));
|
|
135
|
+
return queue;
|
|
136
|
+
};
|
|
137
|
+
const enqueueLive = (h) => {
|
|
138
|
+
// Buffer while catching up so a high-seq live event can't advance the cursor past rows the
|
|
139
|
+
// catch-up loop hasn't fetched yet (which the dedupe check would then drop).
|
|
140
|
+
if (catchingUpRef.current)
|
|
141
|
+
liveBuffer.push(h);
|
|
142
|
+
else
|
|
143
|
+
schedule(h);
|
|
144
|
+
};
|
|
145
|
+
// Coalesce overlapping invocations: a `resync()` fired while a catch-up is still draining returns
|
|
146
|
+
// the in-flight promise instead of starting a second drain (two drains would race the
|
|
147
|
+
// `catchingUpRef` gate + `liveBuffer` and could break seq ordering).
|
|
148
|
+
let catchUpInFlight = null;
|
|
149
|
+
const runCatchUp = () => {
|
|
150
|
+
if (catchUpInFlight)
|
|
151
|
+
return catchUpInFlight;
|
|
152
|
+
catchUpInFlight = (async () => {
|
|
153
|
+
catchingUpRef.current = true;
|
|
154
|
+
try {
|
|
155
|
+
for (;;) {
|
|
156
|
+
const page = await rest.fetchHandshakes(deviceId, {
|
|
157
|
+
since: cursorRef.current ?? undefined,
|
|
158
|
+
limit: pageSize,
|
|
159
|
+
});
|
|
160
|
+
for (const h of page.handshakes)
|
|
161
|
+
await schedule(h);
|
|
162
|
+
if (!page.hasMore)
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
catchingUpRef.current = false;
|
|
168
|
+
// Replay anything that landed live during catch-up, in seq order, through the same queue.
|
|
169
|
+
const buffered = liveBuffer.splice(0).sort((a, b) => compareSeq(a.seq, b.seq));
|
|
170
|
+
for (const h of buffered)
|
|
171
|
+
schedule(h);
|
|
172
|
+
}
|
|
173
|
+
})().finally(() => {
|
|
174
|
+
catchUpInFlight = null;
|
|
175
|
+
});
|
|
176
|
+
return catchUpInFlight;
|
|
177
|
+
};
|
|
178
|
+
(async () => {
|
|
179
|
+
// Resolve the device row id (option wins; else the persisted device). Without one, there is no
|
|
180
|
+
// inbox to drain yet — the effect re-runs when `deviceId` is later supplied.
|
|
181
|
+
deviceId = deviceIdOption ?? (await repo.loadDevice())?.device?.id;
|
|
182
|
+
if (!alive || !deviceId)
|
|
183
|
+
return;
|
|
184
|
+
cursorRef.current = await repo.loadHandshakeCursor();
|
|
185
|
+
if (alive)
|
|
186
|
+
setCursor(cursorRef.current);
|
|
187
|
+
// Subscribe to live events BEFORE catch-up (buffered until catch-up completes).
|
|
188
|
+
offFns.push(socket.on("secure:welcome", enqueueLive));
|
|
189
|
+
offFns.push(socket.on("secure:handshake", enqueueLive));
|
|
190
|
+
runCatchUpRef.current = runCatchUp;
|
|
191
|
+
await runCatchUp();
|
|
192
|
+
// After a reload we hold group state but haven't joined the socket rooms, so broadcast Commits
|
|
193
|
+
// wouldn't arrive — re-join every known conversation.
|
|
194
|
+
try {
|
|
195
|
+
const convIds = await repo.listGroupConversationIds();
|
|
196
|
+
for (const c of convIds)
|
|
197
|
+
socket.joinConversation(c);
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
report(err);
|
|
201
|
+
}
|
|
202
|
+
if (alive) {
|
|
203
|
+
setCatchingUp(false);
|
|
204
|
+
setReady(true);
|
|
205
|
+
}
|
|
206
|
+
})().catch((err) => report(err));
|
|
207
|
+
return () => {
|
|
208
|
+
alive = false;
|
|
209
|
+
offFns.forEach((off) => off());
|
|
210
|
+
runCatchUpRef.current = null;
|
|
211
|
+
};
|
|
212
|
+
}, [enabled, deviceIdOption, pageSize, rest, socket, repo, crypto, resolveGroup, rememberGroup]);
|
|
213
|
+
return { catchingUp, ready, cursor, processedCount, error, resync };
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=useSecureHandshakes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSecureHandshakes.js","sourceRoot":"","sources":["../../../src/hooks/useSecureHandshakes.tsx"],"names":[],"mappings":"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"}
|
|
@@ -28,7 +28,7 @@ const secure_chat_context_js_1 = require("../context/secure-chat-context.js");
|
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
30
|
function useSecureMessages(conversationId, options = {}) {
|
|
31
|
-
const { rest, crypto, socket, repo, resolveGroup } = (0, secure_chat_context_js_1.useSecureChat)();
|
|
31
|
+
const { rest, crypto, socket, repo, resolveGroup, getGroupVersion, subscribeGroupChange } = (0, secure_chat_context_js_1.useSecureChat)();
|
|
32
32
|
const [messages, setMessages] = (0, react_1.useState)([]);
|
|
33
33
|
const [before, setBefore] = (0, react_1.useState)(undefined);
|
|
34
34
|
const [hasMore, setHasMore] = (0, react_1.useState)(true);
|
|
@@ -36,10 +36,19 @@ function useSecureMessages(conversationId, options = {}) {
|
|
|
36
36
|
const [error, setError] = (0, react_1.useState)(null);
|
|
37
37
|
const [group, setGroup] = (0, react_1.useState)(options.group ?? null);
|
|
38
38
|
const [senderDeviceId, setSenderDeviceId] = (0, react_1.useState)(options.senderDeviceId);
|
|
39
|
+
// Bumps when THIS conversation's group handle advances (a join or a processed Commit, driven by
|
|
40
|
+
// useSecureHandshakes calling rememberGroup). Feeds the group-resolve effect's deps so we re-resolve
|
|
41
|
+
// the now-current handle and flush buffered (plaintext:null) rows.
|
|
42
|
+
const [groupVersion, setGroupVersion] = (0, react_1.useState)(0);
|
|
39
43
|
// Latest messages, read by the "decrypt history once the group resolves" effect below without
|
|
40
44
|
// making `messages` one of its deps (which would loop).
|
|
41
45
|
const messagesRef = (0, react_1.useRef)(messages);
|
|
42
46
|
messagesRef.current = messages;
|
|
47
|
+
// Subscribe to provider group-change signals; only a change to OUR conversation's version updates
|
|
48
|
+
// state (React bails on an unchanged primitive), so unrelated conversations don't re-resolve us.
|
|
49
|
+
(0, react_1.useEffect)(() => {
|
|
50
|
+
return subscribeGroupChange(() => setGroupVersion(getGroupVersion(conversationId)));
|
|
51
|
+
}, [subscribeGroupChange, getGroupVersion, conversationId]);
|
|
43
52
|
// Resolve the group handle: explicit override, else persisted state.
|
|
44
53
|
(0, react_1.useEffect)(() => {
|
|
45
54
|
if (options.group) {
|
|
@@ -62,7 +71,8 @@ function useSecureMessages(conversationId, options = {}) {
|
|
|
62
71
|
return () => {
|
|
63
72
|
alive = false;
|
|
64
73
|
};
|
|
65
|
-
|
|
74
|
+
// `groupVersion` re-runs this when a Commit/join advances the handle → flushes buffered rows.
|
|
75
|
+
}, [options.group, conversationId, resolveGroup, groupVersion]);
|
|
66
76
|
// Resolve the sender device id: explicit override, else persisted device row.
|
|
67
77
|
(0, react_1.useEffect)(() => {
|
|
68
78
|
if (options.senderDeviceId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSecureMessages.js","sourceRoot":"","sources":["../../../src/hooks/useSecureMessages.tsx"],"names":[],"mappings":";AAAA,+FAA+F;AAC/F,EAAE;AACF,mGAAmG;AACnG,oGAAoG;AACpG,6FAA6F;AAC7F,6CAA6C;;AA2D7C,
|
|
1
|
+
{"version":3,"file":"useSecureMessages.js","sourceRoot":"","sources":["../../../src/hooks/useSecureMessages.tsx"],"names":[],"mappings":"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"}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export { useSecureConversations } from "./hooks/useSecureConversations.js";
|
|
|
6
6
|
export type { UseSecureConversationsValues } from "./hooks/useSecureConversations.js";
|
|
7
7
|
export { useSecureMessages } from "./hooks/useSecureMessages.js";
|
|
8
8
|
export type { UseSecureMessagesOptions, UseSecureMessagesValues, DecryptedSecureMessage, } from "./hooks/useSecureMessages.js";
|
|
9
|
+
export { useSecureHandshakes } from "./hooks/useSecureHandshakes.js";
|
|
10
|
+
export type { UseSecureHandshakesOptions, UseSecureHandshakesValues, } from "./hooks/useSecureHandshakes.js";
|
|
9
11
|
export { SecureChatRestClient } from "./transport/rest.js";
|
|
10
12
|
export type { SecureChatRestConfig } from "./transport/rest.js";
|
|
11
13
|
export { SecureChatSocketClient } from "./transport/socket.js";
|
package/dist/cjs/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// injection of a `SecureChatCrypto`. Platform packages (@agora-sdk/secure-chat-react-js, etc.)
|
|
6
6
|
// re-export this and add the concrete crypto + persistence.
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.bytesToUtf8 = exports.utf8ToBytes = exports.fromBase64 = exports.toBase64 = exports.SecureChatRepository = exports.MemoryStore = exports.SecureChatSocketClient = exports.SecureChatRestClient = exports.useSecureMessages = exports.useSecureConversations = exports.useSecureDevice = exports.useSecureChat = exports.SecureChatProvider = void 0;
|
|
8
|
+
exports.bytesToUtf8 = exports.utf8ToBytes = exports.fromBase64 = exports.toBase64 = exports.SecureChatRepository = exports.MemoryStore = exports.SecureChatSocketClient = exports.SecureChatRestClient = exports.useSecureHandshakes = exports.useSecureMessages = exports.useSecureConversations = exports.useSecureDevice = exports.useSecureChat = exports.SecureChatProvider = void 0;
|
|
9
9
|
// ── context / provider ──────────────────────────────────────────────────────
|
|
10
10
|
var secure_chat_context_js_1 = require("./context/secure-chat-context.js");
|
|
11
11
|
Object.defineProperty(exports, "SecureChatProvider", { enumerable: true, get: function () { return secure_chat_context_js_1.SecureChatProvider; } });
|
|
@@ -17,6 +17,8 @@ var useSecureConversations_js_1 = require("./hooks/useSecureConversations.js");
|
|
|
17
17
|
Object.defineProperty(exports, "useSecureConversations", { enumerable: true, get: function () { return useSecureConversations_js_1.useSecureConversations; } });
|
|
18
18
|
var useSecureMessages_js_1 = require("./hooks/useSecureMessages.js");
|
|
19
19
|
Object.defineProperty(exports, "useSecureMessages", { enumerable: true, get: function () { return useSecureMessages_js_1.useSecureMessages; } });
|
|
20
|
+
var useSecureHandshakes_js_1 = require("./hooks/useSecureHandshakes.js");
|
|
21
|
+
Object.defineProperty(exports, "useSecureHandshakes", { enumerable: true, get: function () { return useSecureHandshakes_js_1.useSecureHandshakes; } });
|
|
20
22
|
// ── transport (for advanced / non-React use) ─────────────────────────────────
|
|
21
23
|
var rest_js_1 = require("./transport/rest.js");
|
|
22
24
|
Object.defineProperty(exports, "SecureChatRestClient", { enumerable: true, get: function () { return rest_js_1.SecureChatRestClient; } });
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA,8FAA8F;AAC9F,EAAE;AACF,6FAA6F;AAC7F,+FAA+F;AAC/F,4DAA4D;;;AAE5D,+EAA+E;AAC/E,2EAAqF;AAA5E,4HAAA,kBAAkB,OAAA;AAAE,uHAAA,aAAa,OAAA;AAM1C,gFAAgF;AAChF,iEAA6D;AAApD,qHAAA,eAAe,OAAA;AAExB,+EAA2E;AAAlE,mIAAA,sBAAsB,OAAA;AAE/B,qEAAiE;AAAxD,yHAAA,iBAAiB,OAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA,8FAA8F;AAC9F,EAAE;AACF,6FAA6F;AAC7F,+FAA+F;AAC/F,4DAA4D;;;AAE5D,+EAA+E;AAC/E,2EAAqF;AAA5E,4HAAA,kBAAkB,OAAA;AAAE,uHAAA,aAAa,OAAA;AAM1C,gFAAgF;AAChF,iEAA6D;AAApD,qHAAA,eAAe,OAAA;AAExB,+EAA2E;AAAlE,mIAAA,sBAAsB,OAAA;AAE/B,qEAAiE;AAAxD,yHAAA,iBAAiB,OAAA;AAM1B,yEAAqE;AAA5D,6HAAA,mBAAmB,OAAA;AAM5B,gFAAgF;AAChF,+CAA2D;AAAlD,+GAAA,oBAAoB,OAAA;AAE7B,mDAA+D;AAAtD,mHAAA,sBAAsB,OAAA;AAwB/B,iEAA4D;AAAnD,8GAAA,WAAW,OAAA;AACpB,6DAAmE;AAA1D,qHAAA,oBAAoB,OAAA;AAG7B,gFAAgF;AAChF,8CAAkF;AAAzE,qGAAA,QAAQ,OAAA;AAAE,uGAAA,UAAU,OAAA;AAAE,wGAAA,WAAW,OAAA;AAAE,wGAAA,WAAW,OAAA"}
|
|
@@ -21,6 +21,16 @@ export interface SecureChatContextValue {
|
|
|
21
21
|
resolveGroup: (conversationId: string) => Promise<GroupHandle | null>;
|
|
22
22
|
/** Cache + persist a conversation's group handle (after createGroup / processWelcome). */
|
|
23
23
|
rememberGroup: (conversationId: string, handle: GroupHandle) => Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Current change-version for a conversation's group handle. Bumps every time the handle advances
|
|
26
|
+
* (a join or a processed Commit), so consumers can detect "the group moved" without diffing handles.
|
|
27
|
+
*/
|
|
28
|
+
getGroupVersion: (conversationId: string) => number;
|
|
29
|
+
/**
|
|
30
|
+
* Subscribe to group-handle changes across all conversations (fired by {@link rememberGroup}).
|
|
31
|
+
* @returns An unsubscribe function.
|
|
32
|
+
*/
|
|
33
|
+
subscribeGroupChange: (listener: () => void) => () => void;
|
|
24
34
|
/** The Agora project id these clients are scoped to. */
|
|
25
35
|
projectId: string;
|
|
26
36
|
}
|
|
@@ -47,6 +47,12 @@ export function SecureChatProvider({ crypto, projectId, store, accessToken, getA
|
|
|
47
47
|
// NOTE: not cleared on a `store` prop swap — a store change in practice means a new provider
|
|
48
48
|
// instance (fresh cache), not a live prop change on the same mounted provider.
|
|
49
49
|
const groupCache = useRef(new Map());
|
|
50
|
+
// Per-conversation change counter, bumped whenever a group handle advances (a join or a processed
|
|
51
|
+
// Commit). Lets useSecureMessages re-resolve and flush buffered (undecrypted) rows without a
|
|
52
|
+
// re-fetch. Held in a ref + listener set so a bump notifies consumers WITHOUT re-rendering the
|
|
53
|
+
// provider (and thus rebuilding rest/socket/repo).
|
|
54
|
+
const groupVersion = useRef(new Map());
|
|
55
|
+
const groupListeners = useRef(new Set());
|
|
50
56
|
const resolveGroup = useCallback(async (conversationId) => {
|
|
51
57
|
const cached = groupCache.current.get(conversationId);
|
|
52
58
|
if (cached)
|
|
@@ -62,11 +68,41 @@ export function SecureChatProvider({ crypto, projectId, store, accessToken, getA
|
|
|
62
68
|
groupCache.current.set(conversationId, handle);
|
|
63
69
|
const bytes = await crypto.exportGroupState(handle);
|
|
64
70
|
await repo.saveGroupState(conversationId, bytes);
|
|
71
|
+
// Signal that this conversation's group advanced, so message hooks re-resolve + flush.
|
|
72
|
+
groupVersion.current.set(conversationId, (groupVersion.current.get(conversationId) ?? 0) + 1);
|
|
73
|
+
groupListeners.current.forEach((l) => l());
|
|
65
74
|
}, [repo, crypto]);
|
|
75
|
+
const getGroupVersion = useCallback((conversationId) => groupVersion.current.get(conversationId) ?? 0, []);
|
|
76
|
+
const subscribeGroupChange = useCallback((listener) => {
|
|
77
|
+
groupListeners.current.add(listener);
|
|
78
|
+
return () => {
|
|
79
|
+
groupListeners.current.delete(listener);
|
|
80
|
+
};
|
|
81
|
+
}, []);
|
|
66
82
|
useEffect(() => {
|
|
67
83
|
return () => socket.disconnect();
|
|
68
84
|
}, [socket]);
|
|
69
|
-
const value = useMemo(() => ({
|
|
85
|
+
const value = useMemo(() => ({
|
|
86
|
+
rest,
|
|
87
|
+
socket,
|
|
88
|
+
crypto,
|
|
89
|
+
repo,
|
|
90
|
+
resolveGroup,
|
|
91
|
+
rememberGroup,
|
|
92
|
+
getGroupVersion,
|
|
93
|
+
subscribeGroupChange,
|
|
94
|
+
projectId,
|
|
95
|
+
}), [
|
|
96
|
+
rest,
|
|
97
|
+
socket,
|
|
98
|
+
crypto,
|
|
99
|
+
repo,
|
|
100
|
+
resolveGroup,
|
|
101
|
+
rememberGroup,
|
|
102
|
+
getGroupVersion,
|
|
103
|
+
subscribeGroupChange,
|
|
104
|
+
projectId,
|
|
105
|
+
]);
|
|
70
106
|
return _jsx(SecureChatContext.Provider, { value: value, children: children });
|
|
71
107
|
}
|
|
72
108
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secure-chat-context.js","sourceRoot":"","sources":["../../../src/context/secure-chat-context.tsx"],"names":[],"mappings":";AAAA,yFAAyF;AACzF,EAAE;AACF,gGAAgG;AAChG,kGAAkG;AAClG,+FAA+F;AAC/F,mGAAmG;AACnG,gEAAgE;AAEhE,OAAc,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAClG,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"secure-chat-context.js","sourceRoot":"","sources":["../../../src/context/secure-chat-context.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { SecureHandshakeModel } from "../contract/index.js";
|
|
2
|
+
/** Options for {@link useSecureHandshakes}. */
|
|
3
|
+
export interface UseSecureHandshakesOptions {
|
|
4
|
+
/**
|
|
5
|
+
* The device row id whose inbox to drain. Defaults to the persisted device's `.id`. Pass the id
|
|
6
|
+
* from `useSecureDevice` so processing starts the moment the device registers (a persisted device
|
|
7
|
+
* is picked up automatically on reload even without this).
|
|
8
|
+
*/
|
|
9
|
+
deviceId?: string;
|
|
10
|
+
/** Disable processing (e.g. before sign-in). Default true. */
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
/** Page size for the catch-up fetch loop. Default 100. */
|
|
13
|
+
pageSize?: number;
|
|
14
|
+
/** Called per handshake-processing error; the inbox skips the bad row and continues. */
|
|
15
|
+
onError?: (err: unknown, handshake?: SecureHandshakeModel) => void;
|
|
16
|
+
}
|
|
17
|
+
/** The state and actions returned by {@link useSecureHandshakes}. */
|
|
18
|
+
export interface UseSecureHandshakesValues {
|
|
19
|
+
/** True until the initial catch-up loop settles. */
|
|
20
|
+
catchingUp: boolean;
|
|
21
|
+
/** True once catch-up completed and live subscriptions are active. */
|
|
22
|
+
ready: boolean;
|
|
23
|
+
/** The last processed delivery cursor (`seq`), or `null`. */
|
|
24
|
+
cursor: string | null;
|
|
25
|
+
/** Count of handshakes applied this session. */
|
|
26
|
+
processedCount: number;
|
|
27
|
+
/** The last error surfaced (also delivered via `onError`), or `null`. */
|
|
28
|
+
error: unknown;
|
|
29
|
+
/**
|
|
30
|
+
* Re-run the catch-up loop from the persisted cursor. The reusable primitive a future 409
|
|
31
|
+
* epoch-conflict rebase (on a membership Commit) calls before rebuilding + retrying its Commit.
|
|
32
|
+
*/
|
|
33
|
+
resync: () => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Process this device's MLS handshake inbox so the recipient side of secure chat works.
|
|
37
|
+
*
|
|
38
|
+
* On mount (once a device id is available) it catches up via `fetchHandshakes(since=cursor)`, re-joins
|
|
39
|
+
* the socket rooms for groups it already holds, then processes live `secure:welcome` /
|
|
40
|
+
* `secure:handshake` events — all serialized in `seq` order, deduped, and cursor-persisted. Mount it
|
|
41
|
+
* exactly once near `useSecureDevice`.
|
|
42
|
+
*
|
|
43
|
+
* @param options - {@link UseSecureHandshakesOptions}.
|
|
44
|
+
* @returns {@link UseSecureHandshakesValues}.
|
|
45
|
+
*
|
|
46
|
+
* @remarks
|
|
47
|
+
* Single-device only (Phase 3 adds own-device fan-out + MLS generation-counter gap detection). A
|
|
48
|
+
* Commit for a group whose Welcome hasn't been seen is skipped — `seq` ordering puts the Welcome
|
|
49
|
+
* first, so an unknown group means this device is genuinely not a member.
|
|
50
|
+
*
|
|
51
|
+
* Mount it exactly once. Processing is serialized *within* a mount, and the cursor is re-read from
|
|
52
|
+
* storage at the start of each run, so a serialized re-mount (or `deviceId` change) resumes without
|
|
53
|
+
* reprocessing. It does NOT serialize across a *concurrent* re-mount whose prior catch-up is still
|
|
54
|
+
* in flight; that case relies on `processWelcome` / `processCommit` being idempotent for a replayed
|
|
55
|
+
* blob. The mock is idempotent (so dev StrictMode double-invoke is harmless); harden this when the
|
|
56
|
+
* real MLS core lands (Task 1) if its handshake processing is not replay-safe.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* const { device } = useSecureDevice();
|
|
61
|
+
* useSecureHandshakes({ deviceId: device?.id });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function useSecureHandshakes(options?: UseSecureHandshakesOptions): UseSecureHandshakesValues;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// useSecureHandshakes — drain and process this device's MLS handshake inbox (the recipient side).
|
|
2
|
+
//
|
|
3
|
+
// The blind DS delivers each device a stream of handshakes (Welcomes targeted at it, plus broadcast
|
|
4
|
+
// Commits/Proposals for its groups), ordered by a monotonic `seq`. This hook is what makes a
|
|
5
|
+
// RECIPIENT actually join and stay current: on connect it pulls `GET .../handshakes?since=<cursor>`
|
|
6
|
+
// to the end, then processes live `secure:welcome` / `secure:handshake` events — all funneled through
|
|
7
|
+
// ONE serialized, seq-ordered, idempotent path, persisting the cursor as it goes. A processed Welcome
|
|
8
|
+
// joins a group (rememberGroup); a processed Commit advances its epoch. Mount it once, near
|
|
9
|
+
// useSecureDevice.
|
|
10
|
+
//
|
|
11
|
+
// Ordering invariant: live events that arrive WHILE catching up are buffered and replayed in `seq`
|
|
12
|
+
// order AFTER catch-up — otherwise a high-seq live event would advance the cursor past not-yet-fetched
|
|
13
|
+
// rows and the dedupe check would drop them (data loss).
|
|
14
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
15
|
+
import { fromBase64 } from "../util/base64.js";
|
|
16
|
+
import { useSecureChat } from "../context/secure-chat-context.js";
|
|
17
|
+
/** Compare two decimal-string `seq` cursors numerically (string compare is wrong across digit widths). */
|
|
18
|
+
function compareSeq(a, b) {
|
|
19
|
+
const d = BigInt(a) - BigInt(b);
|
|
20
|
+
return d > 0n ? 1 : d < 0n ? -1 : 0;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Process this device's MLS handshake inbox so the recipient side of secure chat works.
|
|
24
|
+
*
|
|
25
|
+
* On mount (once a device id is available) it catches up via `fetchHandshakes(since=cursor)`, re-joins
|
|
26
|
+
* the socket rooms for groups it already holds, then processes live `secure:welcome` /
|
|
27
|
+
* `secure:handshake` events — all serialized in `seq` order, deduped, and cursor-persisted. Mount it
|
|
28
|
+
* exactly once near `useSecureDevice`.
|
|
29
|
+
*
|
|
30
|
+
* @param options - {@link UseSecureHandshakesOptions}.
|
|
31
|
+
* @returns {@link UseSecureHandshakesValues}.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* Single-device only (Phase 3 adds own-device fan-out + MLS generation-counter gap detection). A
|
|
35
|
+
* Commit for a group whose Welcome hasn't been seen is skipped — `seq` ordering puts the Welcome
|
|
36
|
+
* first, so an unknown group means this device is genuinely not a member.
|
|
37
|
+
*
|
|
38
|
+
* Mount it exactly once. Processing is serialized *within* a mount, and the cursor is re-read from
|
|
39
|
+
* storage at the start of each run, so a serialized re-mount (or `deviceId` change) resumes without
|
|
40
|
+
* reprocessing. It does NOT serialize across a *concurrent* re-mount whose prior catch-up is still
|
|
41
|
+
* in flight; that case relies on `processWelcome` / `processCommit` being idempotent for a replayed
|
|
42
|
+
* blob. The mock is idempotent (so dev StrictMode double-invoke is harmless); harden this when the
|
|
43
|
+
* real MLS core lands (Task 1) if its handshake processing is not replay-safe.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* const { device } = useSecureDevice();
|
|
48
|
+
* useSecureHandshakes({ deviceId: device?.id });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function useSecureHandshakes(options = {}) {
|
|
52
|
+
const { rest, crypto, socket, repo, resolveGroup, rememberGroup } = useSecureChat();
|
|
53
|
+
const { deviceId: deviceIdOption, pageSize = 100 } = options;
|
|
54
|
+
const enabled = options.enabled ?? true;
|
|
55
|
+
const [catchingUp, setCatchingUp] = useState(true);
|
|
56
|
+
const [ready, setReady] = useState(false);
|
|
57
|
+
const [cursor, setCursor] = useState(null);
|
|
58
|
+
const [processedCount, setProcessedCount] = useState(0);
|
|
59
|
+
const [error, setError] = useState(null);
|
|
60
|
+
// onError read through a ref so passing an inline callback doesn't re-run the effect.
|
|
61
|
+
const onErrorRef = useRef(options.onError);
|
|
62
|
+
onErrorRef.current = options.onError;
|
|
63
|
+
// The catch-up routine, published from the effect so `resync()` (stable) can invoke it.
|
|
64
|
+
const runCatchUpRef = useRef(null);
|
|
65
|
+
const resync = useCallback(async () => {
|
|
66
|
+
await runCatchUpRef.current?.();
|
|
67
|
+
}, []);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (!enabled)
|
|
70
|
+
return;
|
|
71
|
+
let alive = true;
|
|
72
|
+
const offFns = [];
|
|
73
|
+
// Async state shared across the catch-up loop, live handlers, and the serial apply queue.
|
|
74
|
+
const cursorRef = { current: null };
|
|
75
|
+
const catchingUpRef = { current: true };
|
|
76
|
+
const liveBuffer = [];
|
|
77
|
+
let queue = Promise.resolve();
|
|
78
|
+
let deviceId;
|
|
79
|
+
const report = (err, h) => {
|
|
80
|
+
if (alive)
|
|
81
|
+
setError(err);
|
|
82
|
+
onErrorRef.current?.(err, h);
|
|
83
|
+
};
|
|
84
|
+
const dispatchByKind = async (h) => {
|
|
85
|
+
const payload = fromBase64(h.payload);
|
|
86
|
+
if (h.kind === "welcome") {
|
|
87
|
+
// Targeted at us → join the group, then join its room for future broadcast Commits.
|
|
88
|
+
if (h.targetDeviceId && h.targetDeviceId !== deviceId)
|
|
89
|
+
return;
|
|
90
|
+
const handle = await crypto.processWelcome(payload);
|
|
91
|
+
await rememberGroup(h.conversationId, handle);
|
|
92
|
+
socket.joinConversation(h.conversationId);
|
|
93
|
+
}
|
|
94
|
+
else if (h.kind === "commit") {
|
|
95
|
+
const group = await resolveGroup(h.conversationId);
|
|
96
|
+
if (!group) {
|
|
97
|
+
// Unknown group at a Commit: with seq ordering the Welcome precedes it, so we're not a
|
|
98
|
+
// member of this conversation. Skip (the cursor still advances so we don't re-fetch it).
|
|
99
|
+
report(new Error(`secure-chat: commit for unknown group ${h.conversationId}`), h);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const advanced = await crypto.processCommit(group, payload);
|
|
103
|
+
await rememberGroup(h.conversationId, advanced);
|
|
104
|
+
}
|
|
105
|
+
else if (h.kind === "proposal") {
|
|
106
|
+
const group = await resolveGroup(h.conversationId);
|
|
107
|
+
if (group)
|
|
108
|
+
await crypto.processProposal(group, payload);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
// The ONE place that mutates the cursor + crypto state. Dedupes by seq; advances the cursor even
|
|
112
|
+
// when a row is skipped or its dispatch throws, so a poison blob can never wedge the inbox. A hard
|
|
113
|
+
// crash mid-dispatch leaves the cursor unsaved, so the row replays on restart (no loss).
|
|
114
|
+
const applyOrdered = async (h) => {
|
|
115
|
+
if (cursorRef.current !== null && compareSeq(h.seq, cursorRef.current) <= 0)
|
|
116
|
+
return;
|
|
117
|
+
try {
|
|
118
|
+
await dispatchByKind(h);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
report(err, h);
|
|
122
|
+
}
|
|
123
|
+
cursorRef.current = h.seq;
|
|
124
|
+
await repo.saveHandshakeCursor(h.seq);
|
|
125
|
+
if (alive) {
|
|
126
|
+
setCursor(h.seq);
|
|
127
|
+
setProcessedCount((n) => n + 1);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const schedule = (h) => {
|
|
131
|
+
queue = queue.then(() => applyOrdered(h));
|
|
132
|
+
return queue;
|
|
133
|
+
};
|
|
134
|
+
const enqueueLive = (h) => {
|
|
135
|
+
// Buffer while catching up so a high-seq live event can't advance the cursor past rows the
|
|
136
|
+
// catch-up loop hasn't fetched yet (which the dedupe check would then drop).
|
|
137
|
+
if (catchingUpRef.current)
|
|
138
|
+
liveBuffer.push(h);
|
|
139
|
+
else
|
|
140
|
+
schedule(h);
|
|
141
|
+
};
|
|
142
|
+
// Coalesce overlapping invocations: a `resync()` fired while a catch-up is still draining returns
|
|
143
|
+
// the in-flight promise instead of starting a second drain (two drains would race the
|
|
144
|
+
// `catchingUpRef` gate + `liveBuffer` and could break seq ordering).
|
|
145
|
+
let catchUpInFlight = null;
|
|
146
|
+
const runCatchUp = () => {
|
|
147
|
+
if (catchUpInFlight)
|
|
148
|
+
return catchUpInFlight;
|
|
149
|
+
catchUpInFlight = (async () => {
|
|
150
|
+
catchingUpRef.current = true;
|
|
151
|
+
try {
|
|
152
|
+
for (;;) {
|
|
153
|
+
const page = await rest.fetchHandshakes(deviceId, {
|
|
154
|
+
since: cursorRef.current ?? undefined,
|
|
155
|
+
limit: pageSize,
|
|
156
|
+
});
|
|
157
|
+
for (const h of page.handshakes)
|
|
158
|
+
await schedule(h);
|
|
159
|
+
if (!page.hasMore)
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
catchingUpRef.current = false;
|
|
165
|
+
// Replay anything that landed live during catch-up, in seq order, through the same queue.
|
|
166
|
+
const buffered = liveBuffer.splice(0).sort((a, b) => compareSeq(a.seq, b.seq));
|
|
167
|
+
for (const h of buffered)
|
|
168
|
+
schedule(h);
|
|
169
|
+
}
|
|
170
|
+
})().finally(() => {
|
|
171
|
+
catchUpInFlight = null;
|
|
172
|
+
});
|
|
173
|
+
return catchUpInFlight;
|
|
174
|
+
};
|
|
175
|
+
(async () => {
|
|
176
|
+
// Resolve the device row id (option wins; else the persisted device). Without one, there is no
|
|
177
|
+
// inbox to drain yet — the effect re-runs when `deviceId` is later supplied.
|
|
178
|
+
deviceId = deviceIdOption ?? (await repo.loadDevice())?.device?.id;
|
|
179
|
+
if (!alive || !deviceId)
|
|
180
|
+
return;
|
|
181
|
+
cursorRef.current = await repo.loadHandshakeCursor();
|
|
182
|
+
if (alive)
|
|
183
|
+
setCursor(cursorRef.current);
|
|
184
|
+
// Subscribe to live events BEFORE catch-up (buffered until catch-up completes).
|
|
185
|
+
offFns.push(socket.on("secure:welcome", enqueueLive));
|
|
186
|
+
offFns.push(socket.on("secure:handshake", enqueueLive));
|
|
187
|
+
runCatchUpRef.current = runCatchUp;
|
|
188
|
+
await runCatchUp();
|
|
189
|
+
// After a reload we hold group state but haven't joined the socket rooms, so broadcast Commits
|
|
190
|
+
// wouldn't arrive — re-join every known conversation.
|
|
191
|
+
try {
|
|
192
|
+
const convIds = await repo.listGroupConversationIds();
|
|
193
|
+
for (const c of convIds)
|
|
194
|
+
socket.joinConversation(c);
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
report(err);
|
|
198
|
+
}
|
|
199
|
+
if (alive) {
|
|
200
|
+
setCatchingUp(false);
|
|
201
|
+
setReady(true);
|
|
202
|
+
}
|
|
203
|
+
})().catch((err) => report(err));
|
|
204
|
+
return () => {
|
|
205
|
+
alive = false;
|
|
206
|
+
offFns.forEach((off) => off());
|
|
207
|
+
runCatchUpRef.current = null;
|
|
208
|
+
};
|
|
209
|
+
}, [enabled, deviceIdOption, pageSize, rest, socket, repo, crypto, resolveGroup, rememberGroup]);
|
|
210
|
+
return { catchingUp, ready, cursor, processedCount, error, resync };
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=useSecureHandshakes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSecureHandshakes.js","sourceRoot":"","sources":["../../../src/hooks/useSecureHandshakes.tsx"],"names":[],"mappings":"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"}
|
|
@@ -25,7 +25,7 @@ import { useSecureChat } from "../context/secure-chat-context.js";
|
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
export function useSecureMessages(conversationId, options = {}) {
|
|
28
|
-
const { rest, crypto, socket, repo, resolveGroup } = useSecureChat();
|
|
28
|
+
const { rest, crypto, socket, repo, resolveGroup, getGroupVersion, subscribeGroupChange } = useSecureChat();
|
|
29
29
|
const [messages, setMessages] = useState([]);
|
|
30
30
|
const [before, setBefore] = useState(undefined);
|
|
31
31
|
const [hasMore, setHasMore] = useState(true);
|
|
@@ -33,10 +33,19 @@ export function useSecureMessages(conversationId, options = {}) {
|
|
|
33
33
|
const [error, setError] = useState(null);
|
|
34
34
|
const [group, setGroup] = useState(options.group ?? null);
|
|
35
35
|
const [senderDeviceId, setSenderDeviceId] = useState(options.senderDeviceId);
|
|
36
|
+
// Bumps when THIS conversation's group handle advances (a join or a processed Commit, driven by
|
|
37
|
+
// useSecureHandshakes calling rememberGroup). Feeds the group-resolve effect's deps so we re-resolve
|
|
38
|
+
// the now-current handle and flush buffered (plaintext:null) rows.
|
|
39
|
+
const [groupVersion, setGroupVersion] = useState(0);
|
|
36
40
|
// Latest messages, read by the "decrypt history once the group resolves" effect below without
|
|
37
41
|
// making `messages` one of its deps (which would loop).
|
|
38
42
|
const messagesRef = useRef(messages);
|
|
39
43
|
messagesRef.current = messages;
|
|
44
|
+
// Subscribe to provider group-change signals; only a change to OUR conversation's version updates
|
|
45
|
+
// state (React bails on an unchanged primitive), so unrelated conversations don't re-resolve us.
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
return subscribeGroupChange(() => setGroupVersion(getGroupVersion(conversationId)));
|
|
48
|
+
}, [subscribeGroupChange, getGroupVersion, conversationId]);
|
|
40
49
|
// Resolve the group handle: explicit override, else persisted state.
|
|
41
50
|
useEffect(() => {
|
|
42
51
|
if (options.group) {
|
|
@@ -59,7 +68,8 @@ export function useSecureMessages(conversationId, options = {}) {
|
|
|
59
68
|
return () => {
|
|
60
69
|
alive = false;
|
|
61
70
|
};
|
|
62
|
-
|
|
71
|
+
// `groupVersion` re-runs this when a Commit/join advances the handle → flushes buffered rows.
|
|
72
|
+
}, [options.group, conversationId, resolveGroup, groupVersion]);
|
|
63
73
|
// Resolve the sender device id: explicit override, else persisted device row.
|
|
64
74
|
useEffect(() => {
|
|
65
75
|
if (options.senderDeviceId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSecureMessages.js","sourceRoot":"","sources":["../../../src/hooks/useSecureMessages.tsx"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,EAAE;AACF,mGAAmG;AACnG,oGAAoG;AACpG,6FAA6F;AAC7F,6CAA6C;AAE7C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGjE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAoClE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,cAAsB,EACtB,UAAoC,EAAE;IAEtC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"useSecureMessages.js","sourceRoot":"","sources":["../../../src/hooks/useSecureMessages.tsx"],"names":[],"mappings":"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"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export { useSecureConversations } from "./hooks/useSecureConversations.js";
|
|
|
6
6
|
export type { UseSecureConversationsValues } from "./hooks/useSecureConversations.js";
|
|
7
7
|
export { useSecureMessages } from "./hooks/useSecureMessages.js";
|
|
8
8
|
export type { UseSecureMessagesOptions, UseSecureMessagesValues, DecryptedSecureMessage, } from "./hooks/useSecureMessages.js";
|
|
9
|
+
export { useSecureHandshakes } from "./hooks/useSecureHandshakes.js";
|
|
10
|
+
export type { UseSecureHandshakesOptions, UseSecureHandshakesValues, } from "./hooks/useSecureHandshakes.js";
|
|
9
11
|
export { SecureChatRestClient } from "./transport/rest.js";
|
|
10
12
|
export type { SecureChatRestConfig } from "./transport/rest.js";
|
|
11
13
|
export { SecureChatSocketClient } from "./transport/socket.js";
|
package/dist/esm/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { SecureChatProvider, useSecureChat } from "./context/secure-chat-context
|
|
|
9
9
|
export { useSecureDevice } from "./hooks/useSecureDevice.js";
|
|
10
10
|
export { useSecureConversations } from "./hooks/useSecureConversations.js";
|
|
11
11
|
export { useSecureMessages } from "./hooks/useSecureMessages.js";
|
|
12
|
+
export { useSecureHandshakes } from "./hooks/useSecureHandshakes.js";
|
|
12
13
|
// ── transport (for advanced / non-React use) ─────────────────────────────────
|
|
13
14
|
export { SecureChatRestClient } from "./transport/rest.js";
|
|
14
15
|
export { SecureChatSocketClient } from "./transport/socket.js";
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,EAAE;AACF,6FAA6F;AAC7F,+FAA+F;AAC/F,4DAA4D;AAE5D,+EAA+E;AAC/E,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAMrF,gFAAgF;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAE3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,EAAE;AACF,6FAA6F;AAC7F,+FAA+F;AAC/F,4DAA4D;AAE5D,+EAA+E;AAC/E,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAMrF,gFAAgF;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAE3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAMjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAMrE,gFAAgF;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAwB/D,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE,gFAAgF;AAChF,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agora-sdk/secure-chat-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Agora SDK Plus, maintained by Jenova Marie",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"axios": "^1.4.0",
|
|
39
39
|
"socket.io-client": "^4.8.1",
|
|
40
|
-
"@agora-sdk/secure-chat-crypto": "0.
|
|
40
|
+
"@agora-sdk/secure-chat-crypto": "0.3.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@agora-sdk/core": "^1.2.2",
|