@agora-sdk/secure-chat-core 0.1.0 → 0.2.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 +24 -18
- package/dist/cjs/context/secure-chat-context.js +42 -23
- package/dist/cjs/context/secure-chat-context.js.map +1 -1
- package/dist/cjs/contract/index.js +2 -2
- package/dist/cjs/contract/index.js.map +1 -1
- package/dist/cjs/hooks/useSecureConversations.d.ts +1 -1
- package/dist/cjs/hooks/useSecureConversations.js +9 -7
- package/dist/cjs/hooks/useSecureConversations.js.map +1 -1
- package/dist/cjs/hooks/useSecureDevice.d.ts +14 -13
- package/dist/cjs/hooks/useSecureDevice.js +58 -25
- package/dist/cjs/hooks/useSecureDevice.js.map +1 -1
- package/dist/cjs/hooks/useSecureMessages.d.ts +10 -11
- package/dist/cjs/hooks/useSecureMessages.js +89 -21
- package/dist/cjs/hooks/useSecureMessages.js.map +1 -1
- package/dist/cjs/index.d.ts +18 -14
- package/dist/cjs/index.js +23 -19
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/persistence/memory-store.d.ts +9 -0
- package/dist/cjs/persistence/memory-store.js +27 -0
- package/dist/cjs/persistence/memory-store.js.map +1 -0
- package/dist/cjs/persistence/repository.d.ts +36 -0
- package/dist/cjs/persistence/repository.js +72 -0
- package/dist/cjs/persistence/repository.js.map +1 -0
- package/dist/cjs/persistence/store.d.ts +11 -0
- package/dist/cjs/persistence/store.js +8 -0
- package/dist/cjs/persistence/store.js.map +1 -0
- package/dist/cjs/transport/rest.d.ts +1 -1
- package/dist/cjs/transport/socket.d.ts +1 -1
- package/dist/esm/context/secure-chat-context.d.ts +24 -18
- package/dist/esm/context/secure-chat-context.js +41 -22
- package/dist/esm/context/secure-chat-context.js.map +1 -1
- package/dist/esm/contract/index.js +2 -2
- package/dist/esm/contract/index.js.map +1 -1
- package/dist/esm/hooks/useSecureConversations.d.ts +1 -1
- package/dist/esm/hooks/useSecureConversations.js +6 -4
- package/dist/esm/hooks/useSecureConversations.js.map +1 -1
- package/dist/esm/hooks/useSecureDevice.d.ts +14 -13
- package/dist/esm/hooks/useSecureDevice.js +55 -22
- package/dist/esm/hooks/useSecureDevice.js.map +1 -1
- package/dist/esm/hooks/useSecureMessages.d.ts +10 -11
- package/dist/esm/hooks/useSecureMessages.js +86 -18
- package/dist/esm/hooks/useSecureMessages.js.map +1 -1
- package/dist/esm/index.d.ts +18 -14
- package/dist/esm/index.js +9 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/persistence/memory-store.d.ts +9 -0
- package/dist/esm/persistence/memory-store.js +23 -0
- package/dist/esm/persistence/memory-store.js.map +1 -0
- package/dist/esm/persistence/repository.d.ts +36 -0
- package/dist/esm/persistence/repository.js +68 -0
- package/dist/esm/persistence/repository.js.map +1 -0
- package/dist/esm/persistence/store.d.ts +11 -0
- package/dist/esm/persistence/store.js +7 -0
- package/dist/esm/persistence/store.js.map +1 -0
- package/dist/esm/transport/rest.d.ts +1 -1
- package/dist/esm/transport/socket.d.ts +1 -1
- package/package.json +3 -3
|
@@ -1,48 +1,95 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// useSecureMessages — load, decrypt, send, and live-receive messages in a secure conversation.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// and sending is disabled — keeping the transport usable ahead of the crypto wiring.
|
|
4
|
+
// Self-sufficient once a store is wired: the MLS GroupHandle is auto-resolved from persistence via
|
|
5
|
+
// resolveGroup, and senderDeviceId is read from the persisted device. Both stay overridable through
|
|
6
|
+
// options for advanced use. Without a resolvable handle, ciphertext is still listed/received
|
|
7
|
+
// (plaintext: null) and sending is disabled.
|
|
9
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
9
|
exports.useSecureMessages = useSecureMessages;
|
|
11
10
|
const react_1 = require("react");
|
|
12
|
-
const
|
|
13
|
-
const
|
|
11
|
+
const base64_js_1 = require("../util/base64.js");
|
|
12
|
+
const secure_chat_context_js_1 = require("../context/secure-chat-context.js");
|
|
14
13
|
/**
|
|
15
14
|
* Load, decrypt, send, and live-receive messages in one secure conversation.
|
|
16
15
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* `
|
|
20
|
-
* wiring. Joins the conversation's socket room to receive `secure:message` events live.
|
|
16
|
+
* Auto-resolves the MLS group handle (via `resolveGroup`) and the sender device id (from the
|
|
17
|
+
* persisted device) unless overridden in `options`. Joins the conversation socket room for live
|
|
18
|
+
* `secure:message` events.
|
|
21
19
|
*
|
|
22
20
|
* @param conversationId - The conversation to read and send within.
|
|
23
|
-
* @param options - {@link UseSecureMessagesOptions}
|
|
24
|
-
* @returns {@link UseSecureMessagesValues}
|
|
21
|
+
* @param options - {@link UseSecureMessagesOptions}.
|
|
22
|
+
* @returns {@link UseSecureMessagesValues}.
|
|
25
23
|
*
|
|
26
24
|
* @example
|
|
27
25
|
* ```tsx
|
|
28
|
-
* const { messages, sendMessage } = useSecureMessages(conversationId
|
|
26
|
+
* const { messages, sendMessage } = useSecureMessages(conversationId);
|
|
29
27
|
* await sendMessage("hello 💜");
|
|
30
28
|
* ```
|
|
31
29
|
*/
|
|
32
30
|
function useSecureMessages(conversationId, options = {}) {
|
|
33
|
-
const { rest, crypto, socket } = (0,
|
|
34
|
-
const { group, senderDeviceId } = options;
|
|
31
|
+
const { rest, crypto, socket, repo, resolveGroup } = (0, secure_chat_context_js_1.useSecureChat)();
|
|
35
32
|
const [messages, setMessages] = (0, react_1.useState)([]);
|
|
36
33
|
const [before, setBefore] = (0, react_1.useState)(undefined);
|
|
37
34
|
const [hasMore, setHasMore] = (0, react_1.useState)(true);
|
|
38
35
|
const [loading, setLoading] = (0, react_1.useState)(false);
|
|
39
36
|
const [error, setError] = (0, react_1.useState)(null);
|
|
37
|
+
const [group, setGroup] = (0, react_1.useState)(options.group ?? null);
|
|
38
|
+
const [senderDeviceId, setSenderDeviceId] = (0, react_1.useState)(options.senderDeviceId);
|
|
39
|
+
// Latest messages, read by the "decrypt history once the group resolves" effect below without
|
|
40
|
+
// making `messages` one of its deps (which would loop).
|
|
41
|
+
const messagesRef = (0, react_1.useRef)(messages);
|
|
42
|
+
messagesRef.current = messages;
|
|
43
|
+
// Resolve the group handle: explicit override, else persisted state.
|
|
44
|
+
(0, react_1.useEffect)(() => {
|
|
45
|
+
if (options.group) {
|
|
46
|
+
setGroup(options.group);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// Clear any stale handle from the previous conversation before re-resolving, so live messages
|
|
50
|
+
// for the new conversation never decrypt against the old group during the async window.
|
|
51
|
+
setGroup(null);
|
|
52
|
+
let alive = true;
|
|
53
|
+
resolveGroup(conversationId)
|
|
54
|
+
.then((g) => {
|
|
55
|
+
if (alive)
|
|
56
|
+
setGroup(g);
|
|
57
|
+
})
|
|
58
|
+
.catch(() => {
|
|
59
|
+
if (alive)
|
|
60
|
+
setGroup(null);
|
|
61
|
+
});
|
|
62
|
+
return () => {
|
|
63
|
+
alive = false;
|
|
64
|
+
};
|
|
65
|
+
}, [options.group, conversationId, resolveGroup]);
|
|
66
|
+
// Resolve the sender device id: explicit override, else persisted device row.
|
|
67
|
+
(0, react_1.useEffect)(() => {
|
|
68
|
+
if (options.senderDeviceId) {
|
|
69
|
+
setSenderDeviceId(options.senderDeviceId);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
let alive = true;
|
|
73
|
+
repo
|
|
74
|
+
.loadDevice()
|
|
75
|
+
.then((d) => {
|
|
76
|
+
if (alive)
|
|
77
|
+
setSenderDeviceId(d?.device?.id ?? undefined);
|
|
78
|
+
})
|
|
79
|
+
.catch(() => {
|
|
80
|
+
if (alive)
|
|
81
|
+
setSenderDeviceId(undefined);
|
|
82
|
+
});
|
|
83
|
+
return () => {
|
|
84
|
+
alive = false;
|
|
85
|
+
};
|
|
86
|
+
}, [options.senderDeviceId, repo]);
|
|
40
87
|
const decrypt = (0, react_1.useCallback)(async (model) => {
|
|
41
88
|
if (!group)
|
|
42
89
|
return { model, plaintext: null };
|
|
43
90
|
try {
|
|
44
|
-
const { plaintext } = await crypto.decryptMessage(group, (0,
|
|
45
|
-
return { model, plaintext: (0,
|
|
91
|
+
const { plaintext } = await crypto.decryptMessage(group, (0, base64_js_1.fromBase64)(model.ciphertext));
|
|
92
|
+
return { model, plaintext: (0, base64_js_1.bytesToUtf8)(plaintext) };
|
|
46
93
|
}
|
|
47
94
|
catch {
|
|
48
95
|
// Buffer/skip: epoch not yet reached, or undecryptable. Surface ciphertext without text.
|
|
@@ -81,13 +128,16 @@ function useSecureMessages(conversationId, options = {}) {
|
|
|
81
128
|
await load(false);
|
|
82
129
|
}, [hasMore, loading, load]);
|
|
83
130
|
const sendMessage = (0, react_1.useCallback)(async (text) => {
|
|
131
|
+
// Assumes the crypto identity is already hydrated (mount `useSecureDevice` under the same
|
|
132
|
+
// provider): the crypto layer tags the sender from the restored device identity, so after a
|
|
133
|
+
// reload the first send must wait for useSecureDevice's importDeviceState to complete.
|
|
84
134
|
if (!group)
|
|
85
135
|
throw new Error("Cannot send: no MLS group handle for this conversation.");
|
|
86
136
|
if (!senderDeviceId)
|
|
87
137
|
throw new Error("Cannot send: senderDeviceId is required.");
|
|
88
|
-
const { ciphertext, epoch } = await crypto.encryptMessage(group, (0,
|
|
138
|
+
const { ciphertext, epoch } = await crypto.encryptMessage(group, (0, base64_js_1.utf8ToBytes)(text));
|
|
89
139
|
const sent = await rest.sendMessage(conversationId, {
|
|
90
|
-
ciphertext: (0,
|
|
140
|
+
ciphertext: (0, base64_js_1.toBase64)(ciphertext),
|
|
91
141
|
epoch: epoch.toString(),
|
|
92
142
|
senderDeviceId,
|
|
93
143
|
});
|
|
@@ -98,13 +148,31 @@ function useSecureMessages(conversationId, options = {}) {
|
|
|
98
148
|
refresh();
|
|
99
149
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
100
150
|
}, [conversationId]);
|
|
151
|
+
// Decrypt history that was listed before the group handle resolved. On reload, the first page loads
|
|
152
|
+
// while resolveGroup is still in flight, so those rows come back `plaintext: null`; once the handle
|
|
153
|
+
// arrives (decrypt is recreated with it), re-decrypt the still-undecrypted rows in place — no
|
|
154
|
+
// re-fetch, scroll/pagination preserved.
|
|
155
|
+
(0, react_1.useEffect)(() => {
|
|
156
|
+
if (!group)
|
|
157
|
+
return;
|
|
158
|
+
if (!messagesRef.current.some((m) => m.plaintext === null))
|
|
159
|
+
return;
|
|
160
|
+
let alive = true;
|
|
161
|
+
Promise.all(messagesRef.current.map((m) => (m.plaintext === null ? decrypt(m.model) : Promise.resolve(m)))).then((next) => {
|
|
162
|
+
if (alive)
|
|
163
|
+
setMessages(next);
|
|
164
|
+
});
|
|
165
|
+
return () => {
|
|
166
|
+
alive = false;
|
|
167
|
+
};
|
|
168
|
+
}, [group, decrypt]);
|
|
101
169
|
// Live receive: join the conversation room and decrypt inbound ciphertext.
|
|
102
170
|
(0, react_1.useEffect)(() => {
|
|
103
171
|
socket.joinConversation(conversationId);
|
|
104
172
|
const off = socket.on("secure:message", (model) => {
|
|
105
173
|
if (model.conversationId !== conversationId)
|
|
106
174
|
return;
|
|
107
|
-
decrypt(model).then((m) => setMessages((prev) => prev.some((p) => p.model.id === m.model.id) ? prev : [m, ...prev]));
|
|
175
|
+
decrypt(model).then((m) => setMessages((prev) => (prev.some((p) => p.model.id === m.model.id) ? prev : [m, ...prev])));
|
|
108
176
|
});
|
|
109
177
|
return off;
|
|
110
178
|
}, [socket, conversationId, decrypt]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSecureMessages.js","sourceRoot":"","sources":["../../../src/hooks/useSecureMessages.tsx"],"names":[],"mappings":";AAAA,+FAA+F;AAC/F,EAAE;AACF,
|
|
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
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
export { SecureChatProvider, useSecureChat } from "./context/secure-chat-context";
|
|
2
|
-
export type { SecureChatProviderProps, SecureChatContextValue, } from "./context/secure-chat-context";
|
|
3
|
-
export { useSecureDevice } from "./hooks/useSecureDevice";
|
|
4
|
-
export type { UseSecureDeviceOptions, UseSecureDeviceValues } from "./hooks/useSecureDevice";
|
|
5
|
-
export { useSecureConversations } from "./hooks/useSecureConversations";
|
|
6
|
-
export type { UseSecureConversationsValues } from "./hooks/useSecureConversations";
|
|
7
|
-
export { useSecureMessages } from "./hooks/useSecureMessages";
|
|
8
|
-
export type { UseSecureMessagesOptions, UseSecureMessagesValues, DecryptedSecureMessage, } from "./hooks/useSecureMessages";
|
|
9
|
-
export { SecureChatRestClient } from "./transport/rest";
|
|
10
|
-
export type { SecureChatRestConfig } from "./transport/rest";
|
|
11
|
-
export { SecureChatSocketClient } from "./transport/socket";
|
|
12
|
-
export type { SecureSocket, SecureServerEvents, SecureClientEvents, SecureChatSocketConfig, } from "./transport/socket";
|
|
1
|
+
export { SecureChatProvider, useSecureChat } from "./context/secure-chat-context.js";
|
|
2
|
+
export type { SecureChatProviderProps, SecureChatContextValue, } from "./context/secure-chat-context.js";
|
|
3
|
+
export { useSecureDevice } from "./hooks/useSecureDevice.js";
|
|
4
|
+
export type { UseSecureDeviceOptions, UseSecureDeviceValues } from "./hooks/useSecureDevice.js";
|
|
5
|
+
export { useSecureConversations } from "./hooks/useSecureConversations.js";
|
|
6
|
+
export type { UseSecureConversationsValues } from "./hooks/useSecureConversations.js";
|
|
7
|
+
export { useSecureMessages } from "./hooks/useSecureMessages.js";
|
|
8
|
+
export type { UseSecureMessagesOptions, UseSecureMessagesValues, DecryptedSecureMessage, } from "./hooks/useSecureMessages.js";
|
|
9
|
+
export { SecureChatRestClient } from "./transport/rest.js";
|
|
10
|
+
export type { SecureChatRestConfig } from "./transport/rest.js";
|
|
11
|
+
export { SecureChatSocketClient } from "./transport/socket.js";
|
|
12
|
+
export type { SecureSocket, SecureServerEvents, SecureClientEvents, SecureChatSocketConfig, } from "./transport/socket.js";
|
|
13
13
|
export type { SecureChatCrypto, DeviceIdentity, KeyPackageBundle, GroupHandle, TargetedWelcome, CommitResult, PassphraseBackup, } from "@agora-sdk/secure-chat-crypto";
|
|
14
|
-
export type * from "./contract";
|
|
15
|
-
export {
|
|
14
|
+
export type * from "./contract/index.js";
|
|
15
|
+
export type { SecureChatStore } from "./persistence/store.js";
|
|
16
|
+
export { MemoryStore } from "./persistence/memory-store.js";
|
|
17
|
+
export { SecureChatRepository } from "./persistence/repository.js";
|
|
18
|
+
export type { PersistedDevice } from "./persistence/repository.js";
|
|
19
|
+
export { toBase64, fromBase64, utf8ToBytes, bytesToUtf8 } from "./util/base64.js";
|
package/dist/cjs/index.js
CHANGED
|
@@ -5,27 +5,31 @@
|
|
|
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.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.useSecureMessages = exports.useSecureConversations = exports.useSecureDevice = exports.useSecureChat = exports.SecureChatProvider = void 0;
|
|
9
9
|
// ── context / provider ──────────────────────────────────────────────────────
|
|
10
|
-
var
|
|
11
|
-
Object.defineProperty(exports, "SecureChatProvider", { enumerable: true, get: function () { return
|
|
12
|
-
Object.defineProperty(exports, "useSecureChat", { enumerable: true, get: function () { return
|
|
10
|
+
var secure_chat_context_js_1 = require("./context/secure-chat-context.js");
|
|
11
|
+
Object.defineProperty(exports, "SecureChatProvider", { enumerable: true, get: function () { return secure_chat_context_js_1.SecureChatProvider; } });
|
|
12
|
+
Object.defineProperty(exports, "useSecureChat", { enumerable: true, get: function () { return secure_chat_context_js_1.useSecureChat; } });
|
|
13
13
|
// ── hooks ────────────────────────────────────────────────────────────────────
|
|
14
|
-
var
|
|
15
|
-
Object.defineProperty(exports, "useSecureDevice", { enumerable: true, get: function () { return
|
|
16
|
-
var
|
|
17
|
-
Object.defineProperty(exports, "useSecureConversations", { enumerable: true, get: function () { return
|
|
18
|
-
var
|
|
19
|
-
Object.defineProperty(exports, "useSecureMessages", { enumerable: true, get: function () { return
|
|
14
|
+
var useSecureDevice_js_1 = require("./hooks/useSecureDevice.js");
|
|
15
|
+
Object.defineProperty(exports, "useSecureDevice", { enumerable: true, get: function () { return useSecureDevice_js_1.useSecureDevice; } });
|
|
16
|
+
var useSecureConversations_js_1 = require("./hooks/useSecureConversations.js");
|
|
17
|
+
Object.defineProperty(exports, "useSecureConversations", { enumerable: true, get: function () { return useSecureConversations_js_1.useSecureConversations; } });
|
|
18
|
+
var useSecureMessages_js_1 = require("./hooks/useSecureMessages.js");
|
|
19
|
+
Object.defineProperty(exports, "useSecureMessages", { enumerable: true, get: function () { return useSecureMessages_js_1.useSecureMessages; } });
|
|
20
20
|
// ── transport (for advanced / non-React use) ─────────────────────────────────
|
|
21
|
-
var
|
|
22
|
-
Object.defineProperty(exports, "SecureChatRestClient", { enumerable: true, get: function () { return
|
|
23
|
-
var
|
|
24
|
-
Object.defineProperty(exports, "SecureChatSocketClient", { enumerable: true, get: function () { return
|
|
21
|
+
var rest_js_1 = require("./transport/rest.js");
|
|
22
|
+
Object.defineProperty(exports, "SecureChatRestClient", { enumerable: true, get: function () { return rest_js_1.SecureChatRestClient; } });
|
|
23
|
+
var socket_js_1 = require("./transport/socket.js");
|
|
24
|
+
Object.defineProperty(exports, "SecureChatSocketClient", { enumerable: true, get: function () { return socket_js_1.SecureChatSocketClient; } });
|
|
25
|
+
var memory_store_js_1 = require("./persistence/memory-store.js");
|
|
26
|
+
Object.defineProperty(exports, "MemoryStore", { enumerable: true, get: function () { return memory_store_js_1.MemoryStore; } });
|
|
27
|
+
var repository_js_1 = require("./persistence/repository.js");
|
|
28
|
+
Object.defineProperty(exports, "SecureChatRepository", { enumerable: true, get: function () { return repository_js_1.SecureChatRepository; } });
|
|
25
29
|
// ── utils ────────────────────────────────────────────────────────────────────
|
|
26
|
-
var
|
|
27
|
-
Object.defineProperty(exports, "toBase64", { enumerable: true, get: function () { return
|
|
28
|
-
Object.defineProperty(exports, "fromBase64", { enumerable: true, get: function () { return
|
|
29
|
-
Object.defineProperty(exports, "utf8ToBytes", { enumerable: true, get: function () { return
|
|
30
|
-
Object.defineProperty(exports, "bytesToUtf8", { enumerable: true, get: function () { return
|
|
30
|
+
var base64_js_1 = require("./util/base64.js");
|
|
31
|
+
Object.defineProperty(exports, "toBase64", { enumerable: true, get: function () { return base64_js_1.toBase64; } });
|
|
32
|
+
Object.defineProperty(exports, "fromBase64", { enumerable: true, get: function () { return base64_js_1.fromBase64; } });
|
|
33
|
+
Object.defineProperty(exports, "utf8ToBytes", { enumerable: true, get: function () { return base64_js_1.utf8ToBytes; } });
|
|
34
|
+
Object.defineProperty(exports, "bytesToUtf8", { enumerable: true, get: function () { return base64_js_1.bytesToUtf8; } });
|
|
31
35
|
//# sourceMappingURL=index.js.map
|
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,
|
|
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;AAO1B,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SecureChatStore } from "./store.js";
|
|
2
|
+
/** A `Map`-backed {@link SecureChatStore}. Non-persistent; the default when no store is injected. */
|
|
3
|
+
export declare class MemoryStore implements SecureChatStore {
|
|
4
|
+
private readonly map;
|
|
5
|
+
get(key: string): Promise<Uint8Array | null>;
|
|
6
|
+
set(key: string, value: Uint8Array): Promise<void>;
|
|
7
|
+
delete(key: string): Promise<void>;
|
|
8
|
+
list(prefix: string): Promise<string[]>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// In-memory SecureChatStore — the provider default and the unit-test backing store.
|
|
3
|
+
//
|
|
4
|
+
// Not persistent: state is lost on reload. Platforms inject a durable store (IndexedDB on web) for
|
|
5
|
+
// real persistence; this keeps core usable in tests and SSR without one.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.MemoryStore = void 0;
|
|
8
|
+
/** A `Map`-backed {@link SecureChatStore}. Non-persistent; the default when no store is injected. */
|
|
9
|
+
class MemoryStore {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.map = new Map();
|
|
12
|
+
}
|
|
13
|
+
async get(key) {
|
|
14
|
+
return this.map.get(key) ?? null;
|
|
15
|
+
}
|
|
16
|
+
async set(key, value) {
|
|
17
|
+
this.map.set(key, value);
|
|
18
|
+
}
|
|
19
|
+
async delete(key) {
|
|
20
|
+
this.map.delete(key);
|
|
21
|
+
}
|
|
22
|
+
async list(prefix) {
|
|
23
|
+
return [...this.map.keys()].filter((k) => k.startsWith(prefix));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.MemoryStore = MemoryStore;
|
|
27
|
+
//# sourceMappingURL=memory-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-store.js","sourceRoot":"","sources":["../../../src/persistence/memory-store.ts"],"names":[],"mappings":";AAAA,oFAAoF;AACpF,EAAE;AACF,mGAAmG;AACnG,yEAAyE;;;AAIzE,qGAAqG;AACrG,MAAa,WAAW;IAAxB;QACmB,QAAG,GAAG,IAAI,GAAG,EAAsB,CAAC;IAiBvD,CAAC;IAfC,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAiB;QACtC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAc;QACvB,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IAClE,CAAC;CACF;AAlBD,kCAkBC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SecureDeviceModel } from "../contract/index.js";
|
|
2
|
+
import type { SecureChatStore } from "./store.js";
|
|
3
|
+
/** The persisted device record: stable id, opaque crypto device-state, and the server row. */
|
|
4
|
+
export interface PersistedDevice {
|
|
5
|
+
/** Stable MLS device id; survives reload. */
|
|
6
|
+
deviceId: string;
|
|
7
|
+
/** Opaque bytes from `crypto.exportDeviceState()`. */
|
|
8
|
+
deviceState: Uint8Array;
|
|
9
|
+
/** The registered server row (rehydrates UI without a refetch), or `null` if not yet registered. */
|
|
10
|
+
device: SecureDeviceModel | null;
|
|
11
|
+
}
|
|
12
|
+
/** Typed persistence for device identity, per-conversation group state, and the handshake cursor. */
|
|
13
|
+
export declare class SecureChatRepository {
|
|
14
|
+
private readonly store;
|
|
15
|
+
constructor(store: SecureChatStore);
|
|
16
|
+
/** Load the persisted device record, or `null` if none saved (first run / evicted). */
|
|
17
|
+
loadDevice(): Promise<PersistedDevice | null>;
|
|
18
|
+
/** Persist (replace) the device record. */
|
|
19
|
+
saveDevice(d: PersistedDevice): Promise<void>;
|
|
20
|
+
/** Remove the persisted device record. */
|
|
21
|
+
clearDevice(): Promise<void>;
|
|
22
|
+
/** Load opaque MLS group state for a conversation, or `null` if none. */
|
|
23
|
+
loadGroupState(conversationId: string): Promise<Uint8Array | null>;
|
|
24
|
+
/** Persist opaque MLS group state for a conversation. */
|
|
25
|
+
saveGroupState(conversationId: string, state: Uint8Array): Promise<void>;
|
|
26
|
+
/** Remove persisted group state for a conversation. */
|
|
27
|
+
deleteGroupState(conversationId: string): Promise<void>;
|
|
28
|
+
/** List the conversation ids that have persisted group state. */
|
|
29
|
+
listGroupConversationIds(): Promise<string[]>;
|
|
30
|
+
/** Load the persisted handshake delivery cursor (`seq`), or `null`. */
|
|
31
|
+
loadHandshakeCursor(): Promise<string | null>;
|
|
32
|
+
/** Persist the handshake delivery cursor (`seq`). */
|
|
33
|
+
saveHandshakeCursor(seq: string): Promise<void>;
|
|
34
|
+
/** Wipe all persisted secure-chat state (sign-out / device revoke). */
|
|
35
|
+
clearAll(): Promise<void>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SecureChatRepository — typed façade over a SecureChatStore.
|
|
3
|
+
//
|
|
4
|
+
// The ONLY place in the SDK that knows the persistence key strings and how each record is
|
|
5
|
+
// serialized. Binary fields are base64-wrapped inside JSON; opaque crypto blobs (group/device
|
|
6
|
+
// state) are stored as-is. Built by SecureChatProvider over the injected store.
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.SecureChatRepository = void 0;
|
|
9
|
+
const base64_js_1 = require("../util/base64.js");
|
|
10
|
+
const DEVICE_KEY = "device";
|
|
11
|
+
const GROUP_PREFIX = "group:";
|
|
12
|
+
const CURSOR_KEY = "handshake:cursor";
|
|
13
|
+
/** Typed persistence for device identity, per-conversation group state, and the handshake cursor. */
|
|
14
|
+
class SecureChatRepository {
|
|
15
|
+
constructor(store) {
|
|
16
|
+
this.store = store;
|
|
17
|
+
}
|
|
18
|
+
/** Load the persisted device record, or `null` if none saved (first run / evicted). */
|
|
19
|
+
async loadDevice() {
|
|
20
|
+
const bytes = await this.store.get(DEVICE_KEY);
|
|
21
|
+
if (!bytes)
|
|
22
|
+
return null;
|
|
23
|
+
const rec = JSON.parse((0, base64_js_1.bytesToUtf8)(bytes));
|
|
24
|
+
return { deviceId: rec.deviceId, deviceState: (0, base64_js_1.fromBase64)(rec.deviceState), device: rec.device };
|
|
25
|
+
}
|
|
26
|
+
/** Persist (replace) the device record. */
|
|
27
|
+
async saveDevice(d) {
|
|
28
|
+
const rec = {
|
|
29
|
+
deviceId: d.deviceId,
|
|
30
|
+
deviceState: (0, base64_js_1.toBase64)(d.deviceState),
|
|
31
|
+
device: d.device,
|
|
32
|
+
};
|
|
33
|
+
await this.store.set(DEVICE_KEY, (0, base64_js_1.utf8ToBytes)(JSON.stringify(rec)));
|
|
34
|
+
}
|
|
35
|
+
/** Remove the persisted device record. */
|
|
36
|
+
async clearDevice() {
|
|
37
|
+
await this.store.delete(DEVICE_KEY);
|
|
38
|
+
}
|
|
39
|
+
/** Load opaque MLS group state for a conversation, or `null` if none. */
|
|
40
|
+
async loadGroupState(conversationId) {
|
|
41
|
+
return this.store.get(GROUP_PREFIX + conversationId);
|
|
42
|
+
}
|
|
43
|
+
/** Persist opaque MLS group state for a conversation. */
|
|
44
|
+
async saveGroupState(conversationId, state) {
|
|
45
|
+
await this.store.set(GROUP_PREFIX + conversationId, state);
|
|
46
|
+
}
|
|
47
|
+
/** Remove persisted group state for a conversation. */
|
|
48
|
+
async deleteGroupState(conversationId) {
|
|
49
|
+
await this.store.delete(GROUP_PREFIX + conversationId);
|
|
50
|
+
}
|
|
51
|
+
/** List the conversation ids that have persisted group state. */
|
|
52
|
+
async listGroupConversationIds() {
|
|
53
|
+
const keys = await this.store.list(GROUP_PREFIX);
|
|
54
|
+
return keys.map((k) => k.slice(GROUP_PREFIX.length));
|
|
55
|
+
}
|
|
56
|
+
/** Load the persisted handshake delivery cursor (`seq`), or `null`. */
|
|
57
|
+
async loadHandshakeCursor() {
|
|
58
|
+
const bytes = await this.store.get(CURSOR_KEY);
|
|
59
|
+
return bytes ? (0, base64_js_1.bytesToUtf8)(bytes) : null;
|
|
60
|
+
}
|
|
61
|
+
/** Persist the handshake delivery cursor (`seq`). */
|
|
62
|
+
async saveHandshakeCursor(seq) {
|
|
63
|
+
await this.store.set(CURSOR_KEY, (0, base64_js_1.utf8ToBytes)(seq));
|
|
64
|
+
}
|
|
65
|
+
/** Wipe all persisted secure-chat state (sign-out / device revoke). */
|
|
66
|
+
async clearAll() {
|
|
67
|
+
const keys = await this.store.list("");
|
|
68
|
+
await Promise.all(keys.map((k) => this.store.delete(k)));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.SecureChatRepository = SecureChatRepository;
|
|
72
|
+
//# sourceMappingURL=repository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repository.js","sourceRoot":"","sources":["../../../src/persistence/repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** A platform-agnostic async key→blob store. Implementations: `MemoryStore`, `createIndexedDBStore`. */
|
|
2
|
+
export interface SecureChatStore {
|
|
3
|
+
/** Read the bytes at `key`, or `null` if absent. */
|
|
4
|
+
get(key: string): Promise<Uint8Array | null>;
|
|
5
|
+
/** Write `value` at `key`, overwriting any existing value. */
|
|
6
|
+
set(key: string, value: Uint8Array): Promise<void>;
|
|
7
|
+
/** Remove `key` if present (no-op when absent). */
|
|
8
|
+
delete(key: string): Promise<void>;
|
|
9
|
+
/** Return all keys that start with `prefix` (use `""` for every key). */
|
|
10
|
+
list(prefix: string): Promise<string[]>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The persistence seam — a dumb, async key→blob store.
|
|
3
|
+
//
|
|
4
|
+
// This is ALL a platform must implement: web ships an IndexedDB-backed store, native swaps a
|
|
5
|
+
// keystore later. The SDK owns the key schema and (de)serialization on top of this (see
|
|
6
|
+
// ./repository.ts); the store itself never interprets keys or values. Values are opaque bytes.
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/persistence/store.ts"],"names":[],"mappings":";AAAA,uDAAuD;AACvD,EAAE;AACF,6FAA6F;AAC7F,wFAAwF;AACxF,+FAA+F"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AddSecureMemberBody, CreateSecureConversationBody, PublishKeyPackagesBody, RegisterDeviceBody, RemoveSecureMemberBody, SecureConversationMemberModel, SecureConversationModel, SecureDeviceModel, SecureHandshakeModel, SecureKeyBackupModel, SecureKeyPackageClaim, SecureMessageModel, SendSecureMessageBody, UploadKeyBackupBody } from "../contract";
|
|
1
|
+
import { AddSecureMemberBody, CreateSecureConversationBody, PublishKeyPackagesBody, RegisterDeviceBody, RemoveSecureMemberBody, SecureConversationMemberModel, SecureConversationModel, SecureDeviceModel, SecureHandshakeModel, SecureKeyBackupModel, SecureKeyPackageClaim, SecureMessageModel, SendSecureMessageBody, UploadKeyBackupBody } from "../contract/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Configuration for {@link SecureChatRestClient}. The base URL and access token are read through
|
|
4
4
|
* resolver callbacks rather than captured once, so a late-set `baseUrl` or a refreshed token take
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Socket } from "socket.io-client";
|
|
2
|
-
import { SecureHandshakeModel, SecureMessageModel } from "../contract";
|
|
2
|
+
import { SecureHandshakeModel, SecureMessageModel } from "../contract/index.js";
|
|
3
3
|
/** Server → client events on the `/secure` namespace (§10). */
|
|
4
4
|
export interface SecureServerEvents {
|
|
5
5
|
"secure:message": (message: SecureMessageModel) => void;
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { SecureChatCrypto } from "@agora-sdk/secure-chat-crypto";
|
|
3
|
-
import { SecureChatRestClient } from "../transport/rest";
|
|
4
|
-
import { SecureChatSocketClient } from "../transport/socket";
|
|
2
|
+
import { SecureChatCrypto, GroupHandle } from "@agora-sdk/secure-chat-crypto";
|
|
3
|
+
import { SecureChatRestClient } from "../transport/rest.js";
|
|
4
|
+
import { SecureChatSocketClient } from "../transport/socket.js";
|
|
5
|
+
import { SecureChatStore } from "../persistence/store.js";
|
|
6
|
+
import { SecureChatRepository } from "../persistence/repository.js";
|
|
5
7
|
/**
|
|
6
|
-
* The value exposed by {@link useSecureChat}:
|
|
7
|
-
* and the active project id.
|
|
8
|
+
* The value exposed by {@link useSecureChat}: shared transport clients, the injected crypto, the
|
|
9
|
+
* persistence repository, the group-handle resolver, and the active project id.
|
|
8
10
|
*/
|
|
9
11
|
export interface SecureChatContextValue {
|
|
10
12
|
/** REST client for the blind Delivery Service endpoints. */
|
|
11
13
|
rest: SecureChatRestClient;
|
|
12
14
|
/** Realtime client for the `/secure` socket.io namespace. */
|
|
13
15
|
socket: SecureChatSocketClient;
|
|
14
|
-
/** The injected MLS crypto implementation
|
|
16
|
+
/** The injected MLS crypto implementation. */
|
|
15
17
|
crypto: SecureChatCrypto;
|
|
18
|
+
/** Typed persistence over the injected store. */
|
|
19
|
+
repo: SecureChatRepository;
|
|
20
|
+
/** Resolve a conversation's MLS group handle (cache → store → importGroupState), or `null`. */
|
|
21
|
+
resolveGroup: (conversationId: string) => Promise<GroupHandle | null>;
|
|
22
|
+
/** Cache + persist a conversation's group handle (after createGroup / processWelcome). */
|
|
23
|
+
rememberGroup: (conversationId: string, handle: GroupHandle) => Promise<void>;
|
|
16
24
|
/** The Agora project id these clients are scoped to. */
|
|
17
25
|
projectId: string;
|
|
18
26
|
}
|
|
@@ -22,6 +30,8 @@ export interface SecureChatProviderProps {
|
|
|
22
30
|
crypto: SecureChatCrypto;
|
|
23
31
|
/** Agora project id (path-scoped on every endpoint). */
|
|
24
32
|
projectId: string;
|
|
33
|
+
/** Persistence store. Defaults to a non-persistent in-memory store when omitted. */
|
|
34
|
+
store?: SecureChatStore;
|
|
25
35
|
/** Current access token. Re-pass on refresh; read lazily per request. */
|
|
26
36
|
accessToken?: string;
|
|
27
37
|
/** Override token resolution (takes precedence over `accessToken`). */
|
|
@@ -33,28 +43,24 @@ export interface SecureChatProviderProps {
|
|
|
33
43
|
children: React.ReactNode;
|
|
34
44
|
}
|
|
35
45
|
/**
|
|
36
|
-
* Provides
|
|
37
|
-
* `ReplykeProvider
|
|
38
|
-
* `@agora-sdk/core`'s runtime, and disconnects the socket on unmount.
|
|
46
|
+
* Provides secure-chat transport, crypto, and persistence to the `useSecure*` hooks. Render inside a
|
|
47
|
+
* `ReplykeProvider`; disconnects the socket on unmount.
|
|
39
48
|
*
|
|
40
|
-
* @param props - {@link SecureChatProviderProps}
|
|
41
|
-
* optional base/socket URL overrides.
|
|
49
|
+
* @param props - {@link SecureChatProviderProps}.
|
|
42
50
|
* @returns A context provider wrapping `children`.
|
|
43
51
|
*
|
|
44
52
|
* @example
|
|
45
53
|
* ```tsx
|
|
46
|
-
* <
|
|
47
|
-
* <
|
|
48
|
-
*
|
|
49
|
-
* </SecureChatProvider>
|
|
50
|
-
* </ReplykeProvider>
|
|
54
|
+
* <SecureChatProvider crypto={crypto} projectId={projectId} store={createIndexedDBStore()} accessToken={token}>
|
|
55
|
+
* <Chat />
|
|
56
|
+
* </SecureChatProvider>
|
|
51
57
|
* ```
|
|
52
58
|
*/
|
|
53
|
-
export declare function SecureChatProvider({ crypto, projectId, accessToken, getAccessToken, baseUrl, socketUrl, children, }: SecureChatProviderProps): React.JSX.Element;
|
|
59
|
+
export declare function SecureChatProvider({ crypto, projectId, store, accessToken, getAccessToken, baseUrl, socketUrl, children, }: SecureChatProviderProps): React.JSX.Element;
|
|
54
60
|
/**
|
|
55
61
|
* Access the nearest {@link SecureChatContextValue}.
|
|
56
62
|
*
|
|
57
|
-
* @returns The shared rest/socket/crypto/projectId for this provider subtree.
|
|
63
|
+
* @returns The shared rest/socket/crypto/repo/resolveGroup/projectId for this provider subtree.
|
|
58
64
|
* @throws {Error} When called outside a `<SecureChatProvider>`.
|
|
59
65
|
*/
|
|
60
66
|
export declare function useSecureChat(): SecureChatContextValue;
|