@agora-sdk/secure-chat-core 0.3.0 → 0.5.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.
Files changed (67) hide show
  1. package/dist/cjs/backup/passphrase-strength.d.ts +22 -0
  2. package/dist/cjs/backup/passphrase-strength.js +75 -0
  3. package/dist/cjs/backup/passphrase-strength.js.map +1 -0
  4. package/dist/cjs/context/secure-chat-context.d.ts +12 -1
  5. package/dist/cjs/context/secure-chat-context.js +3 -1
  6. package/dist/cjs/context/secure-chat-context.js.map +1 -1
  7. package/dist/cjs/contract/index.d.ts +2 -150
  8. package/dist/cjs/contract/index.js +11 -10
  9. package/dist/cjs/contract/index.js.map +1 -1
  10. package/dist/cjs/hooks/useSecureBackup.d.ts +67 -0
  11. package/dist/cjs/hooks/useSecureBackup.js +207 -0
  12. package/dist/cjs/hooks/useSecureBackup.js.map +1 -0
  13. package/dist/cjs/hooks/useSecureDevice.d.ts +21 -1
  14. package/dist/cjs/hooks/useSecureDevice.js +46 -5
  15. package/dist/cjs/hooks/useSecureDevice.js.map +1 -1
  16. package/dist/cjs/hooks/useSecureMessages.d.ts +16 -3
  17. package/dist/cjs/hooks/useSecureMessages.js +38 -15
  18. package/dist/cjs/hooks/useSecureMessages.js.map +1 -1
  19. package/dist/cjs/hooks/useSecureSafetyNumber.d.ts +30 -0
  20. package/dist/cjs/hooks/useSecureSafetyNumber.js +82 -0
  21. package/dist/cjs/hooks/useSecureSafetyNumber.js.map +1 -0
  22. package/dist/cjs/index.d.ts +12 -1
  23. package/dist/cjs/index.js +16 -1
  24. package/dist/cjs/index.js.map +1 -1
  25. package/dist/cjs/transport/socket.d.ts +15 -3
  26. package/dist/cjs/transport/socket.js +4 -2
  27. package/dist/cjs/transport/socket.js.map +1 -1
  28. package/dist/cjs/util/padding.d.ts +39 -0
  29. package/dist/cjs/util/padding.js +80 -0
  30. package/dist/cjs/util/padding.js.map +1 -0
  31. package/dist/cjs/util/safety-number.d.ts +27 -0
  32. package/dist/cjs/util/safety-number.js +84 -0
  33. package/dist/cjs/util/safety-number.js.map +1 -0
  34. package/dist/esm/backup/passphrase-strength.d.ts +22 -0
  35. package/dist/esm/backup/passphrase-strength.js +72 -0
  36. package/dist/esm/backup/passphrase-strength.js.map +1 -0
  37. package/dist/esm/context/secure-chat-context.d.ts +12 -1
  38. package/dist/esm/context/secure-chat-context.js +3 -1
  39. package/dist/esm/context/secure-chat-context.js.map +1 -1
  40. package/dist/esm/contract/index.d.ts +2 -150
  41. package/dist/esm/contract/index.js +11 -10
  42. package/dist/esm/contract/index.js.map +1 -1
  43. package/dist/esm/hooks/useSecureBackup.d.ts +67 -0
  44. package/dist/esm/hooks/useSecureBackup.js +204 -0
  45. package/dist/esm/hooks/useSecureBackup.js.map +1 -0
  46. package/dist/esm/hooks/useSecureDevice.d.ts +21 -1
  47. package/dist/esm/hooks/useSecureDevice.js +46 -5
  48. package/dist/esm/hooks/useSecureDevice.js.map +1 -1
  49. package/dist/esm/hooks/useSecureMessages.d.ts +16 -3
  50. package/dist/esm/hooks/useSecureMessages.js +38 -15
  51. package/dist/esm/hooks/useSecureMessages.js.map +1 -1
  52. package/dist/esm/hooks/useSecureSafetyNumber.d.ts +30 -0
  53. package/dist/esm/hooks/useSecureSafetyNumber.js +79 -0
  54. package/dist/esm/hooks/useSecureSafetyNumber.js.map +1 -0
  55. package/dist/esm/index.d.ts +12 -1
  56. package/dist/esm/index.js +7 -0
  57. package/dist/esm/index.js.map +1 -1
  58. package/dist/esm/transport/socket.d.ts +15 -3
  59. package/dist/esm/transport/socket.js +4 -2
  60. package/dist/esm/transport/socket.js.map +1 -1
  61. package/dist/esm/util/padding.d.ts +39 -0
  62. package/dist/esm/util/padding.js +75 -0
  63. package/dist/esm/util/padding.js.map +1 -0
  64. package/dist/esm/util/safety-number.d.ts +27 -0
  65. package/dist/esm/util/safety-number.js +81 -0
  66. package/dist/esm/util/safety-number.js.map +1 -0
  67. package/package.json +3 -2
@@ -0,0 +1,67 @@
1
+ import { type PassphraseStrength } from "../backup/passphrase-strength.js";
2
+ /** State + actions returned by {@link useSecureBackup}. */
3
+ export interface UseSecureBackupValues {
4
+ /**
5
+ * Seal all local key material under `passphrase` and upload it to the blind server.
6
+ * @throws {Error} If export or upload fails.
7
+ */
8
+ backup: (passphrase: string) => Promise<void>;
9
+ /**
10
+ * Restore local key material on a fresh client from the server's backup, rehydrating the device and
11
+ * every conversation's group state.
12
+ * @throws {Error} If no backup exists, the passphrase is wrong, or the backup is corrupt.
13
+ */
14
+ restore: (passphrase: string) => Promise<void>;
15
+ /** True while {@link UseSecureBackupValues.backup} is in flight. */
16
+ backingUp: boolean;
17
+ /** True while {@link UseSecureBackupValues.restore} is in flight. */
18
+ restoring: boolean;
19
+ /** The last error thrown by backup or restore, or `null`. */
20
+ error: unknown;
21
+ /** Server `updatedAt` of the most recent successful upload this session, or `null`. */
22
+ lastBackupAt: string | null;
23
+ /** True once a group has advanced since the last backup (membership/epoch change) — prompt a re-backup. */
24
+ needsBackup: boolean;
25
+ /**
26
+ * True when there is NO local key material but a backup exists on the server — i.e. this client was
27
+ * evicted (Safari ITP / "clear browsing data") or is a fresh browser, and recovery is possible. The
28
+ * app should route to a passphrase prompt → {@link UseSecureBackupValues.restore} instead of
29
+ * registering a new device. Indistinguishable cases (evicted vs cleared vs new browser) all resolve
30
+ * the same way. Cleared after a successful restore.
31
+ */
32
+ needsRestore: boolean;
33
+ /** True while the mount-time eviction check (or {@link UseSecureBackupValues.recheckRestore}) is in flight. */
34
+ checkingRestore: boolean;
35
+ /**
36
+ * Re-run the eviction check on demand (e.g. after catching a storage error mid-session, or after
37
+ * sign-in). Skips the server entirely when local key material is present.
38
+ * @returns The new {@link UseSecureBackupValues.needsRestore} value.
39
+ */
40
+ recheckRestore: () => Promise<boolean>;
41
+ /** Coarse client-side passphrase-strength estimate for a meter (see {@link estimatePassphraseStrength}). */
42
+ estimateStrength: (passphrase: string) => PassphraseStrength;
43
+ }
44
+ /**
45
+ * Manage passphrase backup + restore of the client's secure-chat key material.
46
+ *
47
+ * Backups are explicit (call {@link UseSecureBackupValues.backup}); `needsBackup` flips true when a
48
+ * group advances so the app can prompt. On mount it also detects an evicted/fresh client
49
+ * (`needsRestore`) so the app restores rather than registering a fresh, history-less identity.
50
+ *
51
+ * @returns Backup/restore actions, the evicted-client `needsRestore` signal, in-flight + error state, a
52
+ * stale-backup signal, and a passphrase-strength helper.
53
+ * @throws {Error} When used outside a `<SecureChatProvider>`.
54
+ *
55
+ * @example
56
+ * ```tsx
57
+ * const { needsRestore, restore, backup } = useSecureBackup();
58
+ * const { device, register } = useSecureDevice();
59
+ * // Route an evicted / fresh client to restore; a true first-run to register.
60
+ * if (needsRestore) await restore(passphrase); // prompt for the passphrase first
61
+ * else if (!device) await register();
62
+ * await backup(passphrase); // later, after a device/chat exists
63
+ * ```
64
+ * Restore should complete before the app relies on `useSecureDevice` (which read `device: null` on its
65
+ * own mount) — gate the chat subtree on a post-restore flag or remount it after restore.
66
+ */
67
+ export declare function useSecureBackup(): UseSecureBackupValues;
@@ -0,0 +1,204 @@
1
+ // useSecureBackup — passphrase backup + restore of all local key material.
2
+ //
3
+ // Backup: crypto.exportBackup(passphrase) seals the device identity + every group's state under a
4
+ // real argon2id KDF + AEAD; we base64 the blob/nonce at the wire boundary and PUT it to the blind
5
+ // server, which stores the ciphertext verbatim (it never sees the passphrase or plaintext).
6
+ //
7
+ // Restore (fresh browser): GET the blob → crypto.importBackup re-derives the identity + groups in
8
+ // memory → re-assert the device server-side (idempotent) to recover its row → persist the device →
9
+ // rebind each conversation's group state by conversationId from the server's conversation list (the
10
+ // server is the source of truth for membership; the backup itself is conversationId-agnostic). The
11
+ // handshake cursor is intentionally left unset so useSecureHandshakes re-pulls from since=0 and
12
+ // dedupes — no cursor needs to live in the backup.
13
+ import { useCallback, useEffect, useRef, useState } from "react";
14
+ import { toBase64, fromBase64 } from "../util/base64.js";
15
+ import { useSecureChat } from "../context/secure-chat-context.js";
16
+ import { estimatePassphraseStrength, } from "../backup/passphrase-strength.js";
17
+ /**
18
+ * Manage passphrase backup + restore of the client's secure-chat key material.
19
+ *
20
+ * Backups are explicit (call {@link UseSecureBackupValues.backup}); `needsBackup` flips true when a
21
+ * group advances so the app can prompt. On mount it also detects an evicted/fresh client
22
+ * (`needsRestore`) so the app restores rather than registering a fresh, history-less identity.
23
+ *
24
+ * @returns Backup/restore actions, the evicted-client `needsRestore` signal, in-flight + error state, a
25
+ * stale-backup signal, and a passphrase-strength helper.
26
+ * @throws {Error} When used outside a `<SecureChatProvider>`.
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * const { needsRestore, restore, backup } = useSecureBackup();
31
+ * const { device, register } = useSecureDevice();
32
+ * // Route an evicted / fresh client to restore; a true first-run to register.
33
+ * if (needsRestore) await restore(passphrase); // prompt for the passphrase first
34
+ * else if (!device) await register();
35
+ * await backup(passphrase); // later, after a device/chat exists
36
+ * ```
37
+ * Restore should complete before the app relies on `useSecureDevice` (which read `device: null` on its
38
+ * own mount) — gate the chat subtree on a post-restore flag or remount it after restore.
39
+ */
40
+ export function useSecureBackup() {
41
+ const { crypto, rest, repo, rememberGroup, subscribeGroupChange } = useSecureChat();
42
+ const [backingUp, setBackingUp] = useState(false);
43
+ const [restoring, setRestoring] = useState(false);
44
+ const [error, setError] = useState(null);
45
+ const [lastBackupAt, setLastBackupAt] = useState(null);
46
+ const [needsBackup, setNeedsBackup] = useState(false);
47
+ const [needsRestore, setNeedsRestore] = useState(false);
48
+ const [checkingRestore, setCheckingRestore] = useState(false);
49
+ // A group advancing (a join or a processed Commit) means the on-server backup is now stale.
50
+ // Guard the very first synchronous fire so mount doesn't immediately flag a backup as needed.
51
+ const armed = useRef(false);
52
+ useEffect(() => {
53
+ armed.current = true;
54
+ return subscribeGroupChange(() => {
55
+ if (armed.current)
56
+ setNeedsBackup(true);
57
+ });
58
+ }, [subscribeGroupChange]);
59
+ // Eviction / fresh-client detection: local key material gone but a server backup exists ⇒ restore is
60
+ // possible (and preferable to registering a new, history-less identity). Evicted, "cleared browsing
61
+ // data", and a brand-new browser are indistinguishable here and resolve the same way. We only hit the
62
+ // server when there's no local device, so the common (healthy) path stays a single IndexedDB read.
63
+ const recheckRestore = useCallback(async () => {
64
+ setCheckingRestore(true);
65
+ try {
66
+ const persisted = await repo.loadDevice();
67
+ if (persisted) {
68
+ setNeedsRestore(false);
69
+ return false;
70
+ }
71
+ const model = await rest.getKeyBackup();
72
+ const possible = model !== null;
73
+ setNeedsRestore(possible);
74
+ return possible;
75
+ }
76
+ catch (err) {
77
+ // Fail soft: a network blip must not strand the user as "needs restore". Surface the error only.
78
+ setError(err);
79
+ setNeedsRestore(false);
80
+ return false;
81
+ }
82
+ finally {
83
+ setCheckingRestore(false);
84
+ }
85
+ }, [repo, rest]);
86
+ useEffect(() => {
87
+ let alive = true;
88
+ repo
89
+ .loadDevice()
90
+ .then(async (persisted) => {
91
+ if (!alive || persisted)
92
+ return; // have local state → not evicted; skip the server call
93
+ const model = await rest.getKeyBackup();
94
+ if (alive)
95
+ setNeedsRestore(model !== null);
96
+ })
97
+ .catch((err) => {
98
+ if (alive)
99
+ setError(err); // fail soft
100
+ });
101
+ return () => {
102
+ alive = false;
103
+ };
104
+ }, [repo, rest]);
105
+ const backup = useCallback(async (passphrase) => {
106
+ setBackingUp(true);
107
+ setError(null);
108
+ try {
109
+ const b = await crypto.exportBackup(passphrase);
110
+ const { updatedAt } = await rest.uploadKeyBackup({
111
+ // base64 at the wire boundary; KDF params (incl. the hex salt) are non-secret metadata.
112
+ blob: toBase64(b.blob),
113
+ nonce: toBase64(b.nonce),
114
+ kdf: b.kdf,
115
+ kdfParams: b.kdfParams,
116
+ cipher: b.cipher,
117
+ version: b.version,
118
+ });
119
+ setLastBackupAt(updatedAt);
120
+ setNeedsBackup(false);
121
+ }
122
+ catch (err) {
123
+ setError(err);
124
+ throw err;
125
+ }
126
+ finally {
127
+ setBackingUp(false);
128
+ }
129
+ }, [crypto, rest]);
130
+ const restore = useCallback(async (passphrase) => {
131
+ setRestoring(true);
132
+ setError(null);
133
+ try {
134
+ const model = await rest.getKeyBackup();
135
+ if (!model)
136
+ throw new Error("No key backup found on the server to restore.");
137
+ // Decrypt + repopulate crypto memory (identity + groups). Returns the restored identity.
138
+ const id = await crypto.importBackup(passphrase, {
139
+ blob: fromBase64(model.blob),
140
+ nonce: fromBase64(model.nonce),
141
+ kdf: model.kdf,
142
+ kdfParams: model.kdfParams,
143
+ cipher: model.cipher,
144
+ version: model.version,
145
+ });
146
+ // Re-assert the (already-registered) device to recover its server row — idempotent on
147
+ // (userId, deviceId). The row's id is the senderDeviceId messages need.
148
+ const device = await rest.registerDevice({
149
+ deviceId: id.deviceId,
150
+ signaturePublicKey: toBase64(id.signaturePublicKey),
151
+ credential: toBase64(id.credential),
152
+ ciphersuite: id.ciphersuite,
153
+ });
154
+ await repo.saveDevice({
155
+ deviceId: id.deviceId,
156
+ deviceState: await crypto.exportDeviceState(),
157
+ device,
158
+ });
159
+ // Rebind conversationId → restored group state from the server's conversation list.
160
+ let cursor;
161
+ for (;;) {
162
+ const page = await rest.listConversations(cursor ? { cursor } : undefined);
163
+ for (const c of page.conversations) {
164
+ try {
165
+ await rememberGroup(c.id, {
166
+ mlsGroupId: fromBase64(c.mlsGroupId),
167
+ epoch: BigInt(c.currentEpoch),
168
+ });
169
+ }
170
+ catch {
171
+ // The group isn't in this backup (created after it, or we're not a member) — skip it.
172
+ }
173
+ }
174
+ if (!page.hasMore || page.conversations.length === 0)
175
+ break;
176
+ const last = page.conversations[page.conversations.length - 1];
177
+ cursor = last.lastMessageAt ?? last.createdAt;
178
+ }
179
+ setNeedsBackup(false);
180
+ setNeedsRestore(false); // we now hold local key material again
181
+ }
182
+ catch (err) {
183
+ setError(err);
184
+ throw err;
185
+ }
186
+ finally {
187
+ setRestoring(false);
188
+ }
189
+ }, [crypto, rest, repo, rememberGroup]);
190
+ return {
191
+ backup,
192
+ restore,
193
+ backingUp,
194
+ restoring,
195
+ error,
196
+ lastBackupAt,
197
+ needsBackup,
198
+ needsRestore,
199
+ checkingRestore,
200
+ recheckRestore,
201
+ estimateStrength: estimatePassphraseStrength,
202
+ };
203
+ }
204
+ //# sourceMappingURL=useSecureBackup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSecureBackup.js","sourceRoot":"","sources":["../../../src/hooks/useSecureBackup.tsx"],"names":[],"mappings":"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"}
@@ -8,7 +8,20 @@ export interface UseSecureDeviceOptions {
8
8
  ciphersuite?: number;
9
9
  /** How many KeyPackages to publish on registration and replenish toward. Default 20. */
10
10
  keyPackageTarget?: number;
11
- /** Auto-replenish when `secure:key-packages-low` fires. Default true. */
11
+ /**
12
+ * Low-water mark: when the server's available count drops **below** this, a top-up refills to
13
+ * {@link UseSecureDeviceOptions.keyPackageTarget}. KeyPackages are single-use, so this guards
14
+ * against exhaustion (peers unable to add this device). Default `ceil(keyPackageTarget / 2)` (10 for
15
+ * the default target of 20). Only governs the proactive/manual path — the server's
16
+ * `secure:key-packages-low` signal always tops up, since the server already judged the stock low.
17
+ */
18
+ keyPackageLowWater?: number;
19
+ /**
20
+ * Auto-replenish without app involvement. Default true. When true the hook subscribes to
21
+ * `secure:key-packages-low` and runs a one-shot proactive count check once the device is ready. When
22
+ * false, both automatic paths are disabled but {@link UseSecureDeviceValues.checkAndReplenish}
23
+ * still works on demand.
24
+ */
12
25
  autoReplenish?: boolean;
13
26
  }
14
27
  /** The state and actions returned by {@link useSecureDevice}. */
@@ -29,6 +42,13 @@ export interface UseSecureDeviceValues {
29
42
  publishKeyPackages: (count?: number) => Promise<number>;
30
43
  /** Re-query the server for the available KeyPackage count and update `keyPackagesAvailable`. */
31
44
  refreshKeyPackageCount: () => Promise<number>;
45
+ /**
46
+ * Refresh the server count and, if it's below the low-water mark, top up to `keyPackageTarget`
47
+ * (publishing only the deficit). Safe to call before {@link UseSecureDeviceValues.register} — it
48
+ * no-ops to 0 when there's no device and never throws for that case — so an app can wire it to a
49
+ * window-focus / app-foreground handler.
50
+ */
51
+ checkAndReplenish: () => Promise<number>;
32
52
  }
33
53
  /**
34
54
  * Register this client as an MLS device, persist its identity, and keep KeyPackages stocked.
@@ -3,7 +3,14 @@
3
3
  //
4
4
  // On mount it re-hydrates a persisted device (stable deviceId + private state via
5
5
  // crypto.importDeviceState) so a reload does NOT mint a new identity. register() generates, registers
6
- // on the server, and persists. Replenishes on the `secure:key-packages-low` realtime signal.
6
+ // on the server, and persists.
7
+ //
8
+ // KeyPackages are single-use (the server consumes one per group-add), so running dry means peers can't
9
+ // add this device. The hook keeps the stock topped up to `keyPackageTarget` from three triggers: the
10
+ // server's `secure:key-packages-low` realtime signal, a one-shot proactive count check once the device
11
+ // is ready (self-heals a client that missed the signal while offline), and an app-callable
12
+ // `checkAndReplenish()` (e.g. on window focus). Each top-up publishes only the DEFICIT to the target
13
+ // (using the actual available count), not a blind full batch.
7
14
  import { useCallback, useEffect, useRef, useState } from "react";
8
15
  import { toBase64 } from "../util/base64.js";
9
16
  import { useSecureChat } from "../context/secure-chat-context.js";
@@ -37,6 +44,7 @@ function newDeviceId() {
37
44
  export function useSecureDevice(options = {}) {
38
45
  const { crypto, rest, socket, repo } = useSecureChat();
39
46
  const { ciphersuite, keyPackageTarget = 20, autoReplenish = true } = options;
47
+ const keyPackageLowWater = options.keyPackageLowWater ?? Math.ceil(keyPackageTarget / 2);
40
48
  const [device, setDevice] = useState(null);
41
49
  const [loading, setLoading] = useState(true);
42
50
  const [registering, setRegistering] = useState(false);
@@ -44,6 +52,8 @@ export function useSecureDevice(options = {}) {
44
52
  const [keyPackagesAvailable, setKeyPackagesAvailable] = useState(null);
45
53
  const deviceIdRef = useRef(options.deviceId ?? newDeviceId());
46
54
  const registerStartedRef = useRef(false);
55
+ // Guards the one-shot proactive count check so it runs once per device-ready, not on every render.
56
+ const proactiveCheckedRef = useRef(false);
47
57
  // On mount: re-hydrate a persisted device (stable id + private state). No persisted device ⇒
48
58
  // first-run; the app calls register().
49
59
  useEffect(() => {
@@ -96,6 +106,25 @@ export function useSecureDevice(options = {}) {
96
106
  setKeyPackagesAvailable(available);
97
107
  return available;
98
108
  }, [rest, device]);
109
+ // Publish only the shortfall (target − available) to refill to the target; nothing if already
110
+ // at/above it. Optimistically bumps the local count so the UI reflects the refill without a re-query.
111
+ // Callers guarantee `device` exists (publishKeyPackages throws otherwise).
112
+ const replenishToTarget = useCallback(async (available) => {
113
+ const deficit = keyPackageTarget - available;
114
+ if (deficit <= 0)
115
+ return 0;
116
+ const published = await publishKeyPackages(deficit);
117
+ setKeyPackagesAvailable(available + published);
118
+ return published;
119
+ }, [keyPackageTarget, publishKeyPackages]);
120
+ const checkAndReplenish = useCallback(async () => {
121
+ if (!device)
122
+ return 0; // not registered yet — no-op (app may call this eagerly on focus)
123
+ const available = await refreshKeyPackageCount();
124
+ if (available >= keyPackageLowWater)
125
+ return 0;
126
+ return replenishToTarget(available);
127
+ }, [device, refreshKeyPackageCount, keyPackageLowWater, replenishToTarget]);
99
128
  const register = useCallback(async () => {
100
129
  registerStartedRef.current = true;
101
130
  setRegistering(true);
@@ -125,17 +154,28 @@ export function useSecureDevice(options = {}) {
125
154
  setRegistering(false);
126
155
  }
127
156
  }, [crypto, rest, repo, ciphersuite]);
128
- // Auto-replenish on the server's low-water signal for this device.
157
+ // Auto-replenish on the server's low-water signal for this device. We top up to the target using the
158
+ // count the signal reports (not a blind full batch), and trust the server's "low" verdict — the
159
+ // client `keyPackageLowWater` only gates the proactive path below.
129
160
  useEffect(() => {
130
161
  if (!autoReplenish || !device)
131
162
  return;
132
163
  const off = socket.on("secure:key-packages-low", (signal) => {
133
164
  if (signal.deviceId !== device.id)
134
- return;
135
- publishKeyPackages().catch(setError);
165
+ return; // device.id is the server ROW id, not the deviceId
166
+ setKeyPackagesAvailable(signal.available);
167
+ replenishToTarget(signal.available).catch(setError);
136
168
  });
137
169
  return off;
138
- }, [autoReplenish, device, socket, publishKeyPackages]);
170
+ }, [autoReplenish, device, socket, replenishToTarget]);
171
+ // One-shot proactive top-up once the device is ready (covers both register and rehydrate-on-mount),
172
+ // so a client that missed the realtime signal while offline self-heals on next load.
173
+ useEffect(() => {
174
+ if (!autoReplenish || !device || proactiveCheckedRef.current)
175
+ return;
176
+ proactiveCheckedRef.current = true;
177
+ checkAndReplenish().catch(setError);
178
+ }, [autoReplenish, device, checkAndReplenish]);
139
179
  return {
140
180
  device,
141
181
  loading,
@@ -145,6 +185,7 @@ export function useSecureDevice(options = {}) {
145
185
  register,
146
186
  publishKeyPackages,
147
187
  refreshKeyPackageCount,
188
+ checkAndReplenish,
148
189
  };
149
190
  }
150
191
  //# sourceMappingURL=useSecureDevice.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSecureDevice.js","sourceRoot":"","sources":["../../../src/hooks/useSecureDevice.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useSecureDevice.js","sourceRoot":"","sources":["../../../src/hooks/useSecureDevice.tsx"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,yBAAyB;AACzB,EAAE;AACF,kFAAkF;AAClF,sGAAsG;AACtG,+BAA+B;AAC/B,EAAE;AACF,uGAAuG;AACvG,qGAAqG;AACrG,uGAAuG;AACvG,2FAA2F;AAC3F,qGAAqG;AACrG,8DAA8D;AAE9D,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAElE;;;;;GAKG;AACH,SAAS,WAAW;IAClB,MAAM,CAAC,GAAI,UAAyD,CAAC,MAAM,CAAC;IAC5E,IAAI,CAAC,EAAE,UAAU;QAAE,OAAO,CAAC,CAAC,UAAU,EAAE,CAAC;IACzC,OAAO,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;AAC1F,CAAC;AAuDD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe,CAAC,UAAkC,EAAE;IAClE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,aAAa,EAAE,CAAC;IACvD,MAAM,EAAE,WAAW,EAAE,gBAAgB,GAAG,EAAE,EAAE,aAAa,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IAC7E,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,IAAI,IAAI,CAAC,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC;IAEzF,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAA2B,IAAI,CAAC,CAAC;IACrE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAU,IAAI,CAAC,CAAC;IAClD,MAAM,CAAC,oBAAoB,EAAE,uBAAuB,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAEtF,MAAM,WAAW,GAAG,MAAM,CAAS,OAAO,CAAC,QAAQ,IAAI,WAAW,EAAE,CAAC,CAAC;IACtE,MAAM,kBAAkB,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACzC,mGAAmG;IACnG,MAAM,mBAAmB,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE1C,6FAA6F;IAC7F,uCAAuC;IACvC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,KAAK,GAAG,IAAI,CAAC;QACjB,CAAC,KAAK,IAAI,EAAE;YACV,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;YAC1C,IAAI,CAAC,KAAK,IAAI,kBAAkB,CAAC,OAAO,EAAE,CAAC;gBACzC,UAAU,CAAC,KAAK,CAAC,CAAC;gBAClB,OAAO;YACT,CAAC;YACD,IAAI,SAAS,EAAE,CAAC;gBACd,MAAM,MAAM,CAAC,iBAAiB,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;gBACtD,+EAA+E;gBAC/E,IAAI,CAAC,KAAK,IAAI,kBAAkB,CAAC,OAAO,EAAE,CAAC;oBACzC,UAAU,CAAC,KAAK,CAAC,CAAC;oBAClB,OAAO;gBACT,CAAC;gBACD,WAAW,CAAC,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC;gBACzC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YAC9B,CAAC;YACD,UAAU,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACjB,IAAI,CAAC,KAAK;gBAAE,OAAO;YACnB,QAAQ,CAAC,GAAG,CAAC,CAAC;YACd,UAAU,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,EAAE;YACV,KAAK,GAAG,KAAK,CAAC;QAChB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAEnB,MAAM,kBAAkB,GAAG,WAAW,CACpC,KAAK,EAAE,QAAgB,gBAAgB,EAAmB,EAAE;QAC1D,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACnF,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACxD,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,EAAE;YACzD,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC/B,aAAa,EAAE,CAAC,CAAC,aAAa;gBAC9B,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC;gBAClC,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,SAAS,EAAE,CAAC,CAAC,SAAS;aACvB,CAAC,CAAC;SACJ,CAAC,CAAC;QACH,OAAO,SAAS,CAAC;IACnB,CAAC,EACD,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,CACzC,CAAC;IAEF,MAAM,sBAAsB,GAAG,WAAW,CAAC,KAAK,IAAqB,EAAE;QACrE,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;QACtF,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACxD,uBAAuB,CAAC,SAAS,CAAC,CAAC;QACnC,OAAO,SAAS,CAAC;IACnB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAEnB,8FAA8F;IAC9F,sGAAsG;IACtG,2EAA2E;IAC3E,MAAM,iBAAiB,GAAG,WAAW,CACnC,KAAK,EAAE,SAAiB,EAAmB,EAAE;QAC3C,MAAM,OAAO,GAAG,gBAAgB,GAAG,SAAS,CAAC;QAC7C,IAAI,OAAO,IAAI,CAAC;YAAE,OAAO,CAAC,CAAC;QAC3B,MAAM,SAAS,GAAG,MAAM,kBAAkB,CAAC,OAAO,CAAC,CAAC;QACpD,uBAAuB,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;QAC/C,OAAO,SAAS,CAAC;IACnB,CAAC,EACD,CAAC,gBAAgB,EAAE,kBAAkB,CAAC,CACvC,CAAC;IAEF,MAAM,iBAAiB,GAAG,WAAW,CAAC,KAAK,IAAqB,EAAE;QAChE,IAAI,CAAC,MAAM;YAAE,OAAO,CAAC,CAAC,CAAC,kEAAkE;QACzF,MAAM,SAAS,GAAG,MAAM,sBAAsB,EAAE,CAAC;QACjD,IAAI,SAAS,IAAI,kBAAkB;YAAE,OAAO,CAAC,CAAC;QAC9C,OAAO,iBAAiB,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC,EAAE,CAAC,MAAM,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAE5E,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,IAAgC,EAAE;QAClE,kBAAkB,CAAC,OAAO,GAAG,IAAI,CAAC;QAClC,cAAc,CAAC,IAAI,CAAC,CAAC;QACrB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC;gBACvD,QAAQ,EAAE,WAAW,CAAC,OAAO;gBAC7B,WAAW;aACZ,CAAC,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC;gBAC3C,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,kBAAkB,EAAE,QAAQ,CAAC,QAAQ,CAAC,kBAAkB,CAAC;gBACzD,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;gBACzC,WAAW,EAAE,QAAQ,CAAC,WAAW;aAClC,CAAC,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,iBAAiB,EAAE,CAAC;YACrD,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;YACxF,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC;YACxC,SAAS,CAAC,UAAU,CAAC,CAAC;YACtB,OAAO,UAAU,CAAC;QACpB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,CAAC,CAAC;YACd,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,cAAc,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;IACH,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;IAEtC,qGAAqG;IACrG,gGAAgG;IAChG,mEAAmE;IACnE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,aAAa,IAAI,CAAC,MAAM;YAAE,OAAO;QACtC,MAAM,GAAG,GAAG,MAAM,CAAC,EAAE,CAAC,yBAAyB,EAAE,CAAC,MAAM,EAAE,EAAE;YAC1D,IAAI,MAAM,CAAC,QAAQ,KAAK,MAAM,CAAC,EAAE;gBAAE,OAAO,CAAC,mDAAmD;YAC9F,uBAAuB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC1C,iBAAiB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,CAAC;IACb,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAEvD,oGAAoG;IACpG,qFAAqF;IACrF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,aAAa,IAAI,CAAC,MAAM,IAAI,mBAAmB,CAAC,OAAO;YAAE,OAAO;QACrE,mBAAmB,CAAC,OAAO,GAAG,IAAI,CAAC;QACnC,iBAAiB,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAE/C,OAAO;QACL,MAAM;QACN,OAAO;QACP,WAAW;QACX,KAAK;QACL,oBAAoB;QACpB,QAAQ;QACR,kBAAkB;QAClB,sBAAsB;QACtB,iBAAiB;KAClB,CAAC;AACJ,CAAC"}
@@ -1,11 +1,24 @@
1
1
  import { SecureMessageModel } from "../contract/index.js";
2
- import { GroupHandle } from "@agora-sdk/secure-chat-crypto";
3
- /** A stored message paired with its decrypted text (when a group handle is available). */
2
+ import { GroupHandle, type SecureDecryptFailureReason } from "@agora-sdk/secure-chat-crypto";
3
+ /**
4
+ * Decryption outcome for a stored message:
5
+ * - `ok` — decrypted + authenticated; `plaintext` is set.
6
+ * - `pending` — not decryptable yet (no group handle, or the message's epoch is ahead of ours); it will
7
+ * be retried when the group advances. `plaintext` is null.
8
+ * - `rejected` — fails closed: the MLS core rejected it (replay, over-window gap, bad auth, malformed,
9
+ * too-old epoch). NEVER retried; `plaintext` is null and `rejectedReason` says why.
10
+ */
11
+ export type SecureMessageStatus = "ok" | "pending" | "rejected";
12
+ /** A stored message paired with its decryption outcome. */
4
13
  export interface DecryptedSecureMessage {
5
14
  /** The raw message row from the server (still holds the base64 ciphertext). */
6
15
  model: SecureMessageModel;
7
- /** Decrypted text, or null when no group handle is available or decryption is pending/failed. */
16
+ /** Decrypted text, or null when {@link DecryptedSecureMessage.status} is `pending` or `rejected`. */
8
17
  plaintext: string | null;
18
+ /** Decryption outcome — drives fail-closed handling and what the UI renders. */
19
+ status: SecureMessageStatus;
20
+ /** When `status` is `rejected`, why the MLS core refused the message. */
21
+ rejectedReason?: SecureDecryptFailureReason;
9
22
  }
10
23
  /** Options for {@link useSecureMessages}. */
11
24
  export interface UseSecureMessagesOptions {
@@ -5,7 +5,9 @@
5
5
  // options for advanced use. Without a resolvable handle, ciphertext is still listed/received
6
6
  // (plaintext: null) and sending is disabled.
7
7
  import { useCallback, useEffect, useRef, useState } from "react";
8
+ import { SecureChatDecryptError, } from "@agora-sdk/secure-chat-crypto";
8
9
  import { toBase64, fromBase64, utf8ToBytes, bytesToUtf8 } from "../util/base64.js";
10
+ import { padPlaintext, unpadPlaintext } from "../util/padding.js";
9
11
  import { useSecureChat } from "../context/secure-chat-context.js";
10
12
  /**
11
13
  * Load, decrypt, send, and live-receive messages in one secure conversation.
@@ -25,7 +27,7 @@ import { useSecureChat } from "../context/secure-chat-context.js";
25
27
  * ```
26
28
  */
27
29
  export function useSecureMessages(conversationId, options = {}) {
28
- const { rest, crypto, socket, repo, resolveGroup, getGroupVersion, subscribeGroupChange } = useSecureChat();
30
+ const { rest, crypto, socket, repo, resolveGroup, getGroupVersion, subscribeGroupChange, padding } = useSecureChat();
29
31
  const [messages, setMessages] = useState([]);
30
32
  const [before, setBefore] = useState(undefined);
31
33
  const [hasMore, setHasMore] = useState(true);
@@ -92,15 +94,34 @@ export function useSecureMessages(conversationId, options = {}) {
92
94
  };
93
95
  }, [options.senderDeviceId, repo]);
94
96
  const decrypt = useCallback(async (model) => {
97
+ // No handle yet (still resolving) → retryable once it arrives.
95
98
  if (!group)
96
- return { model, plaintext: null };
99
+ return { model, plaintext: null, status: "pending" };
100
+ let plaintext;
97
101
  try {
98
- const { plaintext } = await crypto.decryptMessage(group, fromBase64(model.ciphertext));
99
- return { model, plaintext: bytesToUtf8(plaintext) };
102
+ ({ plaintext } = await crypto.decryptMessage(group, fromBase64(model.ciphertext)));
103
+ }
104
+ catch (err) {
105
+ // Classify, don't conflate. A message from an epoch we HAVEN'T reached yet is legitimately
106
+ // buffered (a future Commit will advance us, then this re-decrypts). Anything else that fails
107
+ // at an epoch we HAVE reached is a terminal rejection — the MLS core refused it (replay,
108
+ // over-window gap, bad auth, malformed, too-old epoch). Fail closed: never show it as text and
109
+ // never silently retry it forever (the old behavior masked replays/forgeries as "pending").
110
+ if (BigInt(model.epoch) > group.epoch) {
111
+ return { model, plaintext: null, status: "pending" };
112
+ }
113
+ const rejectedReason = err instanceof SecureChatDecryptError ? err.reason : "unknown";
114
+ return { model, plaintext: null, status: "rejected", rejectedReason };
115
+ }
116
+ // Decrypt + MLS authentication succeeded, so the bytes are from a real group member. Strip the
117
+ // size-bucket padding frame (see util/padding). A bad frame here is NOT a decrypt failure — it's a
118
+ // framing/version mismatch from an authenticated sender — so fail closed as "malformed" rather
119
+ // than rendering raw padded bytes as text.
120
+ try {
121
+ return { model, plaintext: bytesToUtf8(unpadPlaintext(plaintext)), status: "ok" };
100
122
  }
101
123
  catch {
102
- // Buffer/skip: epoch not yet reached, or undecryptable. Surface ciphertext without text.
103
- return { model, plaintext: null };
124
+ return { model, plaintext: null, status: "rejected", rejectedReason: "malformed" };
104
125
  }
105
126
  }, [crypto, group]);
106
127
  const load = useCallback(async (reset) => {
@@ -142,30 +163,32 @@ export function useSecureMessages(conversationId, options = {}) {
142
163
  throw new Error("Cannot send: no MLS group handle for this conversation.");
143
164
  if (!senderDeviceId)
144
165
  throw new Error("Cannot send: senderDeviceId is required.");
145
- const { ciphertext, epoch } = await crypto.encryptMessage(group, utf8ToBytes(text));
166
+ // Pad the plaintext to a size bucket BEFORE encryption so the ciphertext length leaks less
167
+ // (the receiver strips the frame in `decrypt`). See util/padding for the framing.
168
+ const { ciphertext, epoch } = await crypto.encryptMessage(group, padPlaintext(utf8ToBytes(text), padding));
146
169
  const sent = await rest.sendMessage(conversationId, {
147
170
  ciphertext: toBase64(ciphertext),
148
171
  epoch: epoch.toString(),
149
172
  senderDeviceId,
150
173
  });
151
174
  // Optimistic: we know our own plaintext without a round-trip through decrypt.
152
- setMessages((prev) => [{ model: sent, plaintext: text }, ...prev]);
153
- }, [crypto, rest, conversationId, group, senderDeviceId]);
175
+ setMessages((prev) => [{ model: sent, plaintext: text, status: "ok" }, ...prev]);
176
+ }, [crypto, rest, conversationId, group, senderDeviceId, padding]);
154
177
  useEffect(() => {
155
178
  refresh();
156
179
  // eslint-disable-next-line react-hooks/exhaustive-deps
157
180
  }, [conversationId]);
158
- // Decrypt history that was listed before the group handle resolved. On reload, the first page loads
159
- // while resolveGroup is still in flight, so those rows come back `plaintext: null`; once the handle
160
- // arrives (decrypt is recreated with it), re-decrypt the still-undecrypted rows in place no
161
- // re-fetch, scroll/pagination preserved.
181
+ // Re-decrypt buffered rows when the group handle advances. On reload the first page loads while
182
+ // resolveGroup is still in flight (those rows come back `pending`); a Commit/join also advances the
183
+ // epoch, letting previously-ahead rows decrypt. Retry ONLY `pending` rows never `rejected` ones, so
184
+ // a replay/forgery the core already refused isn't retried on every epoch bump. `ok` rows are kept.
162
185
  useEffect(() => {
163
186
  if (!group)
164
187
  return;
165
- if (!messagesRef.current.some((m) => m.plaintext === null))
188
+ if (!messagesRef.current.some((m) => m.status === "pending"))
166
189
  return;
167
190
  let alive = true;
168
- Promise.all(messagesRef.current.map((m) => (m.plaintext === null ? decrypt(m.model) : Promise.resolve(m)))).then((next) => {
191
+ Promise.all(messagesRef.current.map((m) => (m.status === "pending" ? decrypt(m.model) : Promise.resolve(m)))).then((next) => {
169
192
  if (alive)
170
193
  setMessages(next);
171
194
  });
@@ -1 +1 @@
1
- {"version":3,"file":"useSecureMessages.js","sourceRoot":"","sources":["../../../src/hooks/useSecureMessages.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useSecureMessages.js","sourceRoot":"","sources":["../../../src/hooks/useSecureMessages.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { type SafetyNumber } from "../util/safety-number.js";
2
+ /** The state and actions returned by {@link useSecureSafetyNumber}. */
3
+ export interface UseSecureSafetyNumberValues {
4
+ /** The derived safety number, or `null` until ready / when the conversation is not a resolvable DM. */
5
+ safetyNumber: SafetyNumber | null;
6
+ /** True while the number is being (re)computed. */
7
+ loading: boolean;
8
+ /** The last error from resolving the roster or computing, or `null`. */
9
+ error: unknown;
10
+ /** Force a recompute (e.g. after the user re-verifies). */
11
+ refresh: () => void;
12
+ }
13
+ /**
14
+ * Compute the out-of-band key-verification safety number for a direct-message conversation.
15
+ *
16
+ * Resolves the conversation's MLS group, reads its two members' public signature keys, and derives a
17
+ * symmetric {@link SafetyNumber}. Recomputes when the conversation's group handle advances (e.g. a
18
+ * processed Commit changes the roster). Returns `safetyNumber: null` when the group is unresolved or is
19
+ * not a 2-member DM.
20
+ *
21
+ * @param conversationId - The conversation to compute the safety number for.
22
+ * @returns {@link UseSecureSafetyNumberValues}.
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * const { safetyNumber } = useSecureSafetyNumber(conversationId);
27
+ * // render safetyNumber?.groups for the user to compare with their peer
28
+ * ```
29
+ */
30
+ export declare function useSecureSafetyNumber(conversationId: string): UseSecureSafetyNumberValues;