@agora-sdk/secure-chat-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE +202 -0
  2. package/dist/cjs/context/secure-chat-context.d.ts +60 -0
  3. package/dist/cjs/context/secure-chat-context.js +70 -0
  4. package/dist/cjs/context/secure-chat-context.js.map +1 -0
  5. package/dist/cjs/contract/index.d.ts +150 -0
  6. package/dist/cjs/contract/index.js +16 -0
  7. package/dist/cjs/contract/index.js.map +1 -0
  8. package/dist/cjs/hooks/useSecureConversations.d.ts +35 -0
  9. package/dist/cjs/hooks/useSecureConversations.js +109 -0
  10. package/dist/cjs/hooks/useSecureConversations.js.map +1 -0
  11. package/dist/cjs/hooks/useSecureDevice.d.ts +47 -0
  12. package/dist/cjs/hooks/useSecureDevice.js +120 -0
  13. package/dist/cjs/hooks/useSecureDevice.js.map +1 -0
  14. package/dist/cjs/hooks/useSecureMessages.d.ts +52 -0
  15. package/dist/cjs/hooks/useSecureMessages.js +113 -0
  16. package/dist/cjs/hooks/useSecureMessages.js.map +1 -0
  17. package/dist/cjs/index.d.ts +15 -0
  18. package/dist/cjs/index.js +31 -0
  19. package/dist/cjs/index.js.map +1 -0
  20. package/dist/cjs/transport/rest.d.ts +154 -0
  21. package/dist/cjs/transport/rest.js +208 -0
  22. package/dist/cjs/transport/rest.js.map +1 -0
  23. package/dist/cjs/transport/socket.d.ts +79 -0
  24. package/dist/cjs/transport/socket.js +70 -0
  25. package/dist/cjs/transport/socket.js.map +1 -0
  26. package/dist/cjs/util/base64.d.ts +8 -0
  27. package/dist/cjs/util/base64.js +69 -0
  28. package/dist/cjs/util/base64.js.map +1 -0
  29. package/dist/esm/context/secure-chat-context.d.ts +60 -0
  30. package/dist/esm/context/secure-chat-context.js +66 -0
  31. package/dist/esm/context/secure-chat-context.js.map +1 -0
  32. package/dist/esm/contract/index.d.ts +150 -0
  33. package/dist/esm/contract/index.js +15 -0
  34. package/dist/esm/contract/index.js.map +1 -0
  35. package/dist/esm/hooks/useSecureConversations.d.ts +35 -0
  36. package/dist/esm/hooks/useSecureConversations.js +106 -0
  37. package/dist/esm/hooks/useSecureConversations.js.map +1 -0
  38. package/dist/esm/hooks/useSecureDevice.d.ts +47 -0
  39. package/dist/esm/hooks/useSecureDevice.js +117 -0
  40. package/dist/esm/hooks/useSecureDevice.js.map +1 -0
  41. package/dist/esm/hooks/useSecureMessages.d.ts +52 -0
  42. package/dist/esm/hooks/useSecureMessages.js +110 -0
  43. package/dist/esm/hooks/useSecureMessages.js.map +1 -0
  44. package/dist/esm/index.d.ts +15 -0
  45. package/dist/esm/index.js +17 -0
  46. package/dist/esm/index.js.map +1 -0
  47. package/dist/esm/transport/rest.d.ts +154 -0
  48. package/dist/esm/transport/rest.js +201 -0
  49. package/dist/esm/transport/rest.js.map +1 -0
  50. package/dist/esm/transport/socket.d.ts +79 -0
  51. package/dist/esm/transport/socket.js +66 -0
  52. package/dist/esm/transport/socket.js.map +1 -0
  53. package/dist/esm/util/base64.d.ts +8 -0
  54. package/dist/esm/util/base64.js +63 -0
  55. package/dist/esm/util/base64.js.map +1 -0
  56. package/package.json +52 -0
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ // Typed REST client for the secure-chat blind Delivery Service.
3
+ //
4
+ // Covers every endpoint in agora-server `docs/SECURE_CHAT.md` §9. Base URL and access token are
5
+ // resolved **lazily per request** (matching the @agora-sdk/core base-URL runtime), so a token
6
+ // refresh or a late-set baseUrl always wins. All binary is base64 on the wire; this client never
7
+ // inspects payloads.
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.SecureChatRestClient = void 0;
13
+ const axios_1 = __importDefault(require("axios"));
14
+ /**
15
+ * Typed REST client for the secure-chat blind Delivery Service.
16
+ *
17
+ * Wraps every endpoint in agora-server `docs/SECURE_CHAT.md` §9. Each request lazily resolves the
18
+ * base URL and bearer token via the configured resolvers, scopes the path to
19
+ * `{baseUrl}/{projectId}/secure-chat`, and passes payloads through untouched — all binary is base64
20
+ * on the wire and the client never inspects it.
21
+ *
22
+ * @remarks
23
+ * Construct this directly only for advanced / non-React use. Inside React, prefer the
24
+ * `SecureChatProvider` + hooks, which build and share one client for you.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const rest = new SecureChatRestClient({
29
+ * projectId,
30
+ * getBaseUrl: () => getApiBaseUrl(),
31
+ * getAccessToken: () => session.accessToken,
32
+ * });
33
+ * const device = await rest.registerDevice(body);
34
+ * ```
35
+ */
36
+ class SecureChatRestClient {
37
+ constructor(config) {
38
+ this.config = config;
39
+ this.http = axios_1.default.create();
40
+ this.http.interceptors.request.use((req) => {
41
+ const base = config.getBaseUrl().replace(/\/$/, "");
42
+ req.baseURL = `${base}/${config.projectId}/secure-chat`;
43
+ const token = config.getAccessToken();
44
+ if (token)
45
+ req.headers.set("Authorization", `Bearer ${token}`);
46
+ return req;
47
+ });
48
+ }
49
+ // ── Devices ────────────────────────────────────────────────────────────────
50
+ /**
51
+ * Register (or idempotently re-assert) the caller's MLS device — its signature key + credential.
52
+ *
53
+ * @param body - The device identity to publish (deviceId, base64 signature key, credential, ciphersuite).
54
+ * @returns The persisted device row; its `.id` is the uuid used as `targetDeviceId` elsewhere.
55
+ */
56
+ async registerDevice(body) {
57
+ const { data } = await this.http.post("/devices", body);
58
+ return data;
59
+ }
60
+ /** Public device records (signature key + credential) so peers can build a group. */
61
+ async listDevices(userId) {
62
+ const { data } = await this.http.get("/devices", {
63
+ params: { userId },
64
+ });
65
+ return data.data;
66
+ }
67
+ /** Revoke the caller's own device. */
68
+ async revokeDevice(deviceId) {
69
+ await this.http.delete(`/devices/${encodeURIComponent(deviceId)}`);
70
+ }
71
+ // ── KeyPackages ──────────────────────────────────────────────────────────────
72
+ /**
73
+ * Publish a batch of fresh, single-use KeyPackages for one of the caller's devices so peers can
74
+ * add it to groups.
75
+ *
76
+ * @param deviceId - The caller's device row id (`SecureDeviceModel.id`).
77
+ * @param body - The KeyPackage bundles to upload (base64 KeyPackage + ref + ciphersuite).
78
+ * @returns How many KeyPackages the server accepted and stored.
79
+ */
80
+ async publishKeyPackages(deviceId, body) {
81
+ const { data } = await this.http.post(`/devices/${encodeURIComponent(deviceId)}/key-packages`, body);
82
+ return data.published;
83
+ }
84
+ /** Unconsumed + unexpired KeyPackage count for the caller's device (replenishment signal). */
85
+ async keyPackageCount(deviceId) {
86
+ const { data } = await this.http.get(`/devices/${encodeURIComponent(deviceId)}/key-packages/count`);
87
+ return data.available;
88
+ }
89
+ /**
90
+ * Atomically claim one KeyPackage for a TARGET device (to add it to a group).
91
+ * Throws on `409 secure-chat/key-packages-exhausted` — caller should surface "device unavailable".
92
+ */
93
+ async claimKeyPackage(targetDeviceId) {
94
+ const { data } = await this.http.post(`/devices/${encodeURIComponent(targetDeviceId)}/key-packages/claim`);
95
+ return data;
96
+ }
97
+ // ── Conversations ────────────────────────────────────────────────────────────
98
+ /**
99
+ * Register a new secure conversation on the blind DS, relaying the client-built Welcomes.
100
+ *
101
+ * @param body - The conversation type, base64 MLS group id, member user ids, and targeted Welcomes.
102
+ * @returns The created conversation row.
103
+ */
104
+ async createConversation(body) {
105
+ const { data } = await this.http.post("/conversations", body);
106
+ return data;
107
+ }
108
+ /**
109
+ * List the caller's secure conversations, newest activity first.
110
+ *
111
+ * @param params - Optional `limit` and opaque `cursor` for keyset pagination.
112
+ * @returns A page of conversations plus a `hasMore` flag for further paging.
113
+ */
114
+ async listConversations(params) {
115
+ const { data } = await this.http.get("/conversations", { params });
116
+ return data;
117
+ }
118
+ /**
119
+ * Fetch a single conversation by id (including the caller's membership + unread metadata).
120
+ *
121
+ * @param conversationId - The conversation's id.
122
+ * @returns The conversation row.
123
+ */
124
+ async getConversation(conversationId) {
125
+ const { data } = await this.http.get(`/conversations/${encodeURIComponent(conversationId)}`);
126
+ return data;
127
+ }
128
+ /** Mark the conversation read up to now (sets the member's `last_read_at`). */
129
+ async markRead(conversationId) {
130
+ await this.http.post(`/conversations/${encodeURIComponent(conversationId)}/read`);
131
+ }
132
+ // ── Membership (client supplies the MLS Commit + Welcomes; the DS linearizes) ──
133
+ /**
134
+ * Add a user to a conversation by relaying the caller-built Commit + per-device Welcomes.
135
+ *
136
+ * @param conversationId - The conversation to add the member to.
137
+ * @param body - The new member's user id, the broadcast Commit, and the targeted Welcomes.
138
+ * @returns The newly created membership row.
139
+ */
140
+ async addMember(conversationId, body) {
141
+ const { data } = await this.http.post(`/conversations/${encodeURIComponent(conversationId)}/members`, body);
142
+ return data;
143
+ }
144
+ /** Admin remove, or self-leave. Returns the new epoch. Throws on `409 epoch-conflict` (rebase). */
145
+ async removeMember(conversationId, userId, body) {
146
+ const { data } = await this.http.delete(`/conversations/${encodeURIComponent(conversationId)}/members/${encodeURIComponent(userId)}`, { data: body });
147
+ return { epoch: data.epoch };
148
+ }
149
+ // ── Messages ──────────────────────────────────────────────────────────────────
150
+ /**
151
+ * Send one MLS application message (opaque ciphertext) to a conversation.
152
+ *
153
+ * @param conversationId - The target conversation.
154
+ * @param body - The base64 ciphertext, its epoch, the sending device id, and optional content type.
155
+ * @returns The stored message row.
156
+ */
157
+ async sendMessage(conversationId, body) {
158
+ const { data } = await this.http.post(`/conversations/${encodeURIComponent(conversationId)}/messages`, body);
159
+ return data;
160
+ }
161
+ /**
162
+ * Page through a conversation's messages, newest first (still encrypted — decrypt client-side).
163
+ *
164
+ * @param conversationId - The conversation to read.
165
+ * @param params - Optional `limit` and a `before` cursor (page older than this point).
166
+ * @returns A page of message rows plus a `hasMore` flag.
167
+ */
168
+ async listMessages(conversationId, params) {
169
+ const { data } = await this.http.get(`/conversations/${encodeURIComponent(conversationId)}/messages`, { params });
170
+ return data;
171
+ }
172
+ // ── Handshake inbox + key backup ───────────────────────────────────────────────
173
+ /**
174
+ * The durable catch-up path: union of targeted Welcomes + broadcast Commits/Proposals for the
175
+ * caller's device, ordered by `seq > since`. Page by passing the last `seq` you processed.
176
+ */
177
+ async fetchHandshakes(deviceId, params) {
178
+ const { data } = await this.http.get(`/devices/${encodeURIComponent(deviceId)}/handshakes`, { params });
179
+ return data;
180
+ }
181
+ /**
182
+ * Upload (replace) the caller's passphrase-encrypted key backup blob. The server stores it opaquely
183
+ * and cannot decrypt it.
184
+ *
185
+ * @param body - The encrypted blob, nonce, KDF + cipher descriptors, and version.
186
+ * @returns The server's `updatedAt` timestamp for the stored backup.
187
+ */
188
+ async uploadKeyBackup(body) {
189
+ const { data } = await this.http.put("/key-backup", body);
190
+ return { updatedAt: data.updatedAt };
191
+ }
192
+ /** Returns the caller's passphrase-encrypted backup blob, or null on 404. */
193
+ async getKeyBackup(deviceId) {
194
+ try {
195
+ const { data } = await this.http.get("/key-backup", {
196
+ params: deviceId ? { deviceId } : undefined,
197
+ });
198
+ return data;
199
+ }
200
+ catch (err) {
201
+ if (axios_1.default.isAxiosError(err) && err.response?.status === 404)
202
+ return null;
203
+ throw err;
204
+ }
205
+ }
206
+ }
207
+ exports.SecureChatRestClient = SecureChatRestClient;
208
+ //# sourceMappingURL=rest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rest.js","sourceRoot":"","sources":["../../../src/transport/rest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import { Socket } from "socket.io-client";
2
+ import { SecureHandshakeModel, SecureMessageModel } from "../contract";
3
+ /** Server → client events on the `/secure` namespace (§10). */
4
+ export interface SecureServerEvents {
5
+ "secure:message": (message: SecureMessageModel) => void;
6
+ "secure:handshake": (handshake: SecureHandshakeModel) => void;
7
+ "secure:welcome": (welcome: SecureHandshakeModel) => void;
8
+ "secure:member:joined": (signal: {
9
+ conversationId: string;
10
+ userId: string;
11
+ }) => void;
12
+ "secure:member:left": (signal: {
13
+ conversationId: string;
14
+ userId: string;
15
+ }) => void;
16
+ "secure:key-packages-low": (signal: {
17
+ deviceId: string;
18
+ available: number;
19
+ }) => void;
20
+ "secure:typing:start": (signal: {
21
+ conversationId: string;
22
+ userId: string;
23
+ }) => void;
24
+ "secure:typing:stop": (signal: {
25
+ conversationId: string;
26
+ userId: string;
27
+ }) => void;
28
+ }
29
+ /** Client → server events. */
30
+ export interface SecureClientEvents {
31
+ "join:secure-conversation": (conversationId: string) => void;
32
+ "join:secure-device": (deviceId: string) => void;
33
+ }
34
+ /** A socket.io `Socket` typed with the secure-chat event maps in both directions. */
35
+ export type SecureSocket = Socket<SecureServerEvents, SecureClientEvents>;
36
+ /**
37
+ * Configuration for {@link SecureChatSocketClient}. The origin and token are read through resolvers
38
+ * (not captured once) so a refreshed token is used on the next (re)connect.
39
+ */
40
+ export interface SecureChatSocketConfig {
41
+ /** Resolve the socket origin (e.g. `getSocketUrl()` from @agora-sdk/core). */
42
+ getSocketUrl: () => string;
43
+ /** Resolve the current access token (sent in the socket `auth` handshake). */
44
+ getAccessToken: () => string | undefined;
45
+ /** The Agora project id, sent as a connection query param. */
46
+ projectId: string;
47
+ }
48
+ /**
49
+ * Thin wrapper around the `/secure` namespace connection. Devices the user owns are auto-joined to
50
+ * their `secure:device:{id}` rooms on connect (server-side), so targeted Welcomes arrive without an
51
+ * explicit join; conversation rooms are membership-gated and joined on demand.
52
+ */
53
+ export declare class SecureChatSocketClient {
54
+ private readonly config;
55
+ private socket;
56
+ constructor(config: SecureChatSocketConfig);
57
+ /**
58
+ * Lazily open (or reuse) the `/secure` namespace connection.
59
+ *
60
+ * @returns The live socket; idempotent while already connected.
61
+ */
62
+ connect(): SecureSocket;
63
+ /** Tear down the connection and drop the cached socket (e.g. on provider unmount or sign-out). */
64
+ disconnect(): void;
65
+ /** The underlying socket if one exists, else `null` — escape hatch for advanced socket.io use. */
66
+ get raw(): SecureSocket | null;
67
+ /** Join a conversation room (membership-gated server-side) to receive its broadcasts. */
68
+ joinConversation(conversationId: string): void;
69
+ /** Explicitly join a device room (ownership-verified). Owned devices auto-join on connect. */
70
+ joinDevice(deviceId: string): void;
71
+ /**
72
+ * Subscribe to a server → client event, auto-connecting if needed.
73
+ *
74
+ * @param event - The `secure:*` event name to listen for.
75
+ * @param handler - The typed handler for that event's payload.
76
+ * @returns An unsubscribe function that removes this handler.
77
+ */
78
+ on<E extends keyof SecureServerEvents>(event: E, handler: SecureServerEvents[E]): () => void;
79
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ // Client for the `/secure` socket.io namespace — realtime notification layer for secure chat.
3
+ //
4
+ // A SEPARATE namespace from the plaintext chat socket (defense-in-depth: ciphertext events never
5
+ // mix with plaintext handlers). Realtime is an OPTIMIZATION — the REST `fetchHandshakes(since)` +
6
+ // `listMessages` endpoints remain the durable source of truth for offline catch-up. All event
7
+ // payloads carry ciphertext only.
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.SecureChatSocketClient = void 0;
10
+ const socket_io_client_1 = require("socket.io-client");
11
+ /**
12
+ * Thin wrapper around the `/secure` namespace connection. Devices the user owns are auto-joined to
13
+ * their `secure:device:{id}` rooms on connect (server-side), so targeted Welcomes arrive without an
14
+ * explicit join; conversation rooms are membership-gated and joined on demand.
15
+ */
16
+ class SecureChatSocketClient {
17
+ constructor(config) {
18
+ this.config = config;
19
+ this.socket = null;
20
+ }
21
+ /**
22
+ * Lazily open (or reuse) the `/secure` namespace connection.
23
+ *
24
+ * @returns The live socket; idempotent while already connected.
25
+ */
26
+ connect() {
27
+ if (this.socket?.connected)
28
+ return this.socket;
29
+ const origin = this.config.getSocketUrl().replace(/\/$/, "");
30
+ this.socket = (0, socket_io_client_1.io)(`${origin}/secure`, {
31
+ auth: { token: this.config.getAccessToken() },
32
+ query: { projectId: this.config.projectId },
33
+ transports: ["websocket"],
34
+ autoConnect: true,
35
+ });
36
+ return this.socket;
37
+ }
38
+ /** Tear down the connection and drop the cached socket (e.g. on provider unmount or sign-out). */
39
+ disconnect() {
40
+ this.socket?.disconnect();
41
+ this.socket = null;
42
+ }
43
+ /** The underlying socket if one exists, else `null` — escape hatch for advanced socket.io use. */
44
+ get raw() {
45
+ return this.socket;
46
+ }
47
+ /** Join a conversation room (membership-gated server-side) to receive its broadcasts. */
48
+ joinConversation(conversationId) {
49
+ this.connect().emit("join:secure-conversation", conversationId);
50
+ }
51
+ /** Explicitly join a device room (ownership-verified). Owned devices auto-join on connect. */
52
+ joinDevice(deviceId) {
53
+ this.connect().emit("join:secure-device", deviceId);
54
+ }
55
+ /**
56
+ * Subscribe to a server → client event, auto-connecting if needed.
57
+ *
58
+ * @param event - The `secure:*` event name to listen for.
59
+ * @param handler - The typed handler for that event's payload.
60
+ * @returns An unsubscribe function that removes this handler.
61
+ */
62
+ on(event, handler) {
63
+ const s = this.connect();
64
+ // socket.io typings accept the event/handler pair; the cast keeps our strict map ergonomic.
65
+ s.on(event, handler);
66
+ return () => s.off(event, handler);
67
+ }
68
+ }
69
+ exports.SecureChatSocketClient = SecureChatSocketClient;
70
+ //# sourceMappingURL=socket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket.js","sourceRoot":"","sources":["../../../src/transport/socket.ts"],"names":[],"mappings":";AAAA,8FAA8F;AAC9F,EAAE;AACF,iGAAiG;AACjG,kGAAkG;AAClG,8FAA8F;AAC9F,kCAAkC;;;AAElC,uDAA8C;AAqC9C;;;;GAIG;AACH,MAAa,sBAAsB;IAGjC,YAA6B,MAA8B;QAA9B,WAAM,GAAN,MAAM,CAAwB;QAFnD,WAAM,GAAwB,IAAI,CAAC;IAEmB,CAAC;IAE/D;;;;OAIG;IACH,OAAO;QACL,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7D,IAAI,CAAC,MAAM,GAAG,IAAA,qBAAE,EAAC,GAAG,MAAM,SAAS,EAAE;YACnC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE;YAC7C,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE;YAC3C,UAAU,EAAE,CAAC,WAAW,CAAC;YACzB,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,kGAAkG;IAClG,UAAU;QACR,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IACrB,CAAC;IAED,kGAAkG;IAClG,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,yFAAyF;IACzF,gBAAgB,CAAC,cAAsB;QACrC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,0BAA0B,EAAE,cAAc,CAAC,CAAC;IAClE,CAAC;IAED,8FAA8F;IAC9F,UAAU,CAAC,QAAgB;QACzB,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC;IACtD,CAAC;IAED;;;;;;OAMG;IACH,EAAE,CAAqC,KAAQ,EAAE,OAA8B;QAC7E,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QACzB,4FAA4F;QAC5F,CAAC,CAAC,EAAE,CAAC,KAAc,EAAE,OAAgB,CAAC,CAAC;QACvC,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAc,EAAE,OAAgB,CAAC,CAAC;IACvD,CAAC;CACF;AAxDD,wDAwDC"}
@@ -0,0 +1,8 @@
1
+ /** Encode bytes to a standard (padded) base64 string. */
2
+ export declare function toBase64(bytes: Uint8Array): string;
3
+ /** Decode a standard base64 string to bytes. Ignores ASCII whitespace; throws on bad chars. */
4
+ export declare function fromBase64(b64: string): Uint8Array;
5
+ /** UTF-8 text → bytes (for encrypting message plaintext). */
6
+ export declare function utf8ToBytes(text: string): Uint8Array;
7
+ /** Bytes → UTF-8 text (for decrypted message plaintext). */
8
+ export declare function bytesToUtf8(bytes: Uint8Array): string;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ // Base64 ⇄ Uint8Array at the wire boundary.
3
+ //
4
+ // The `SecureChatCrypto` seam works in `Uint8Array`; the server contract is base64. These helpers
5
+ // are the only place that conversion happens. Pure (no Buffer / atob / btoa) so the same code runs
6
+ // on web, React Native, and Node without a polyfill.
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.toBase64 = toBase64;
9
+ exports.fromBase64 = fromBase64;
10
+ exports.utf8ToBytes = utf8ToBytes;
11
+ exports.bytesToUtf8 = bytesToUtf8;
12
+ const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
13
+ const LOOKUP = (() => {
14
+ const table = new Array(256).fill(-1);
15
+ for (let i = 0; i < ALPHABET.length; i++)
16
+ table[ALPHABET.charCodeAt(i)] = i;
17
+ table["=".charCodeAt(0)] = 0;
18
+ return table;
19
+ })();
20
+ /** Encode bytes to a standard (padded) base64 string. */
21
+ function toBase64(bytes) {
22
+ let out = "";
23
+ let i = 0;
24
+ for (; i + 2 < bytes.length; i += 3) {
25
+ const n = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
26
+ out += ALPHABET[(n >> 18) & 63] + ALPHABET[(n >> 12) & 63] + ALPHABET[(n >> 6) & 63] + ALPHABET[n & 63];
27
+ }
28
+ const rem = bytes.length - i;
29
+ if (rem === 1) {
30
+ const n = bytes[i] << 16;
31
+ out += ALPHABET[(n >> 18) & 63] + ALPHABET[(n >> 12) & 63] + "==";
32
+ }
33
+ else if (rem === 2) {
34
+ const n = (bytes[i] << 16) | (bytes[i + 1] << 8);
35
+ out += ALPHABET[(n >> 18) & 63] + ALPHABET[(n >> 12) & 63] + ALPHABET[(n >> 6) & 63] + "=";
36
+ }
37
+ return out;
38
+ }
39
+ /** Decode a standard base64 string to bytes. Ignores ASCII whitespace; throws on bad chars. */
40
+ function fromBase64(b64) {
41
+ const clean = b64.replace(/[\r\n\t ]/g, "");
42
+ const padless = clean.replace(/=+$/, "");
43
+ const outLen = (padless.length * 3) >> 2;
44
+ const out = new Uint8Array(outLen);
45
+ let o = 0;
46
+ let buffer = 0;
47
+ let bits = 0;
48
+ for (let i = 0; i < padless.length; i++) {
49
+ const v = LOOKUP[padless.charCodeAt(i)];
50
+ if (v === -1)
51
+ throw new Error("invalid base64 character");
52
+ buffer = (buffer << 6) | v;
53
+ bits += 6;
54
+ if (bits >= 8) {
55
+ bits -= 8;
56
+ out[o++] = (buffer >> bits) & 0xff;
57
+ }
58
+ }
59
+ return out;
60
+ }
61
+ /** UTF-8 text → bytes (for encrypting message plaintext). */
62
+ function utf8ToBytes(text) {
63
+ return new TextEncoder().encode(text);
64
+ }
65
+ /** Bytes → UTF-8 text (for decrypted message plaintext). */
66
+ function bytesToUtf8(bytes) {
67
+ return new TextDecoder().decode(bytes);
68
+ }
69
+ //# sourceMappingURL=base64.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64.js","sourceRoot":"","sources":["../../../src/util/base64.ts"],"names":[],"mappings":";AAAA,4CAA4C;AAC5C,EAAE;AACF,kGAAkG;AAClG,mGAAmG;AACnG,qDAAqD;;AAYrD,4BAgBC;AAGD,gCAmBC;AAGD,kCAEC;AAGD,kCAEC;AA1DD,MAAM,QAAQ,GAAG,kEAAkE,CAAC;AAEpF,MAAM,MAAM,GAAa,CAAC,GAAG,EAAE;IAC7B,MAAM,KAAK,GAAG,IAAI,KAAK,CAAS,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC5E,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,KAAK,CAAC;AACf,CAAC,CAAC,EAAE,CAAC;AAEL,yDAAyD;AACzD,SAAgB,QAAQ,CAAC,KAAiB;IACxC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAChE,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IAC1G,CAAC;IACD,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAC7B,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;QACd,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACzB,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;IACpE,CAAC;SAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACjD,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC;IAC7F,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,+FAA+F;AAC/F,SAAgB,UAAU,CAAC,GAAW;IACpC,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC1D,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,IAAI,CAAC,CAAC;QACV,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;YACd,IAAI,IAAI,CAAC,CAAC;YACV,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC;QACrC,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,6DAA6D;AAC7D,SAAgB,WAAW,CAAC,IAAY;IACtC,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED,4DAA4D;AAC5D,SAAgB,WAAW,CAAC,KAAiB;IAC3C,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,60 @@
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";
5
+ /**
6
+ * The value exposed by {@link useSecureChat}: the shared transport clients, the injected crypto,
7
+ * and the active project id. The hooks build everything else on top of these.
8
+ */
9
+ export interface SecureChatContextValue {
10
+ /** REST client for the blind Delivery Service endpoints. */
11
+ rest: SecureChatRestClient;
12
+ /** Realtime client for the `/secure` socket.io namespace. */
13
+ socket: SecureChatSocketClient;
14
+ /** The injected MLS crypto implementation (mock, or platform ts-mls/native). */
15
+ crypto: SecureChatCrypto;
16
+ /** The Agora project id these clients are scoped to. */
17
+ projectId: string;
18
+ }
19
+ /** Props for {@link SecureChatProvider}. */
20
+ export interface SecureChatProviderProps {
21
+ /** The MLS crypto implementation (mock for tests; ts-mls/OpenMLS-WASM in platform packages). */
22
+ crypto: SecureChatCrypto;
23
+ /** Agora project id (path-scoped on every endpoint). */
24
+ projectId: string;
25
+ /** Current access token. Re-pass on refresh; read lazily per request. */
26
+ accessToken?: string;
27
+ /** Override token resolution (takes precedence over `accessToken`). */
28
+ getAccessToken?: () => string | undefined;
29
+ /** Override the API base URL. Defaults to @agora-sdk/core `getApiBaseUrl()`. */
30
+ baseUrl?: string;
31
+ /** Override the socket origin. Defaults to @agora-sdk/core `getSocketUrl()`. */
32
+ socketUrl?: string;
33
+ children: React.ReactNode;
34
+ }
35
+ /**
36
+ * Provides the secure-chat transport + crypto to the `useSecure*` hooks. Render it inside a
37
+ * `ReplykeProvider`: by default it inherits the API base URL and socket origin from
38
+ * `@agora-sdk/core`'s runtime, and disconnects the socket on unmount.
39
+ *
40
+ * @param props - {@link SecureChatProviderProps} — the injected crypto, project id, token, and
41
+ * optional base/socket URL overrides.
42
+ * @returns A context provider wrapping `children`.
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * <ReplykeProvider projectId={projectId} baseUrl={baseUrl}>
47
+ * <SecureChatProvider crypto={crypto} projectId={projectId} accessToken={token}>
48
+ * <Chat />
49
+ * </SecureChatProvider>
50
+ * </ReplykeProvider>
51
+ * ```
52
+ */
53
+ export declare function SecureChatProvider({ crypto, projectId, accessToken, getAccessToken, baseUrl, socketUrl, children, }: SecureChatProviderProps): React.JSX.Element;
54
+ /**
55
+ * Access the nearest {@link SecureChatContextValue}.
56
+ *
57
+ * @returns The shared rest/socket/crypto/projectId for this provider subtree.
58
+ * @throws {Error} When called outside a `<SecureChatProvider>`.
59
+ */
60
+ export declare function useSecureChat(): SecureChatContextValue;
@@ -0,0 +1,66 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // SecureChatProvider — wires the transport + crypto for the secure-chat hooks.
3
+ //
4
+ // Sits INSIDE a ReplykeProvider: by default it resolves the API base URL and socket origin from
5
+ // @agora-sdk/core's runtime singletons (getApiBaseUrl / getSocketUrl), so whatever `baseUrl` the
6
+ // app set on ReplykeProvider is honored here too. Crypto is injected (a `SecureChatCrypto`), keeping
7
+ // core platform- and library-agnostic — the web/native packages supply the concrete MLS impl.
8
+ import { createContext, useContext, useEffect, useMemo, useRef } from "react";
9
+ import { getApiBaseUrl, getSocketUrl } from "@agora-sdk/core";
10
+ import { SecureChatRestClient } from "../transport/rest";
11
+ import { SecureChatSocketClient } from "../transport/socket";
12
+ const SecureChatContext = createContext(null);
13
+ /**
14
+ * Provides the secure-chat transport + crypto to the `useSecure*` hooks. Render it inside a
15
+ * `ReplykeProvider`: by default it inherits the API base URL and socket origin from
16
+ * `@agora-sdk/core`'s runtime, and disconnects the socket on unmount.
17
+ *
18
+ * @param props - {@link SecureChatProviderProps} — the injected crypto, project id, token, and
19
+ * optional base/socket URL overrides.
20
+ * @returns A context provider wrapping `children`.
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * <ReplykeProvider projectId={projectId} baseUrl={baseUrl}>
25
+ * <SecureChatProvider crypto={crypto} projectId={projectId} accessToken={token}>
26
+ * <Chat />
27
+ * </SecureChatProvider>
28
+ * </ReplykeProvider>
29
+ * ```
30
+ */
31
+ export function SecureChatProvider({ crypto, projectId, accessToken, getAccessToken, baseUrl, socketUrl, children, }) {
32
+ // Keep the latest token in a ref so the per-request resolver always returns the current value
33
+ // without rebuilding the transport clients on every token change.
34
+ const tokenRef = useRef(accessToken);
35
+ tokenRef.current = accessToken;
36
+ const resolveToken = useMemo(() => getAccessToken ?? (() => tokenRef.current), [getAccessToken]);
37
+ const rest = useMemo(() => new SecureChatRestClient({
38
+ projectId,
39
+ getAccessToken: resolveToken,
40
+ getBaseUrl: () => baseUrl ?? getApiBaseUrl(),
41
+ }), [projectId, resolveToken, baseUrl]);
42
+ const socket = useMemo(() => new SecureChatSocketClient({
43
+ projectId,
44
+ getAccessToken: resolveToken,
45
+ getSocketUrl: () => socketUrl ?? getSocketUrl(),
46
+ }), [projectId, resolveToken, socketUrl]);
47
+ useEffect(() => {
48
+ return () => socket.disconnect();
49
+ }, [socket]);
50
+ const value = useMemo(() => ({ rest, socket, crypto, projectId }), [rest, socket, crypto, projectId]);
51
+ return _jsx(SecureChatContext.Provider, { value: value, children: children });
52
+ }
53
+ /**
54
+ * Access the nearest {@link SecureChatContextValue}.
55
+ *
56
+ * @returns The shared rest/socket/crypto/projectId for this provider subtree.
57
+ * @throws {Error} When called outside a `<SecureChatProvider>`.
58
+ */
59
+ export function useSecureChat() {
60
+ const ctx = useContext(SecureChatContext);
61
+ if (!ctx) {
62
+ throw new Error("useSecureChat must be used within a <SecureChatProvider>.");
63
+ }
64
+ return ctx;
65
+ }
66
+ //# sourceMappingURL=secure-chat-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secure-chat-context.js","sourceRoot":"","sources":["../../../src/context/secure-chat-context.tsx"],"names":[],"mappings":";AAAA,+EAA+E;AAC/E,EAAE;AACF,gGAAgG;AAChG,iGAAiG;AACjG,qGAAqG;AACrG,8FAA8F;AAE9F,OAAc,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAiB7D,MAAM,iBAAiB,GAAG,aAAa,CAAgC,IAAI,CAAC,CAAC;AAmB7E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,kBAAkB,CAAC,EACjC,MAAM,EACN,SAAS,EACT,WAAW,EACX,cAAc,EACd,OAAO,EACP,SAAS,EACT,QAAQ,GACgB;IACxB,8FAA8F;IAC9F,kEAAkE;IAClE,MAAM,QAAQ,GAAG,MAAM,CAAqB,WAAW,CAAC,CAAC;IACzD,QAAQ,CAAC,OAAO,GAAG,WAAW,CAAC;IAE/B,MAAM,YAAY,GAAG,OAAO,CAC1B,GAAG,EAAE,CAAC,cAAc,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,EAChD,CAAC,cAAc,CAAC,CACjB,CAAC;IAEF,MAAM,IAAI,GAAG,OAAO,CAClB,GAAG,EAAE,CACH,IAAI,oBAAoB,CAAC;QACvB,SAAS;QACT,cAAc,EAAE,YAAY;QAC5B,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,IAAI,aAAa,EAAE;KAC7C,CAAC,EACJ,CAAC,SAAS,EAAE,YAAY,EAAE,OAAO,CAAC,CACnC,CAAC;IAEF,MAAM,MAAM,GAAG,OAAO,CACpB,GAAG,EAAE,CACH,IAAI,sBAAsB,CAAC;QACzB,SAAS;QACT,cAAc,EAAE,YAAY;QAC5B,YAAY,EAAE,GAAG,EAAE,CAAC,SAAS,IAAI,YAAY,EAAE;KAChD,CAAC,EACJ,CAAC,SAAS,EAAE,YAAY,EAAE,SAAS,CAAC,CACrC,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;IACnC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,EAC3C,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAClC,CAAC;IAEF,OAAO,KAAC,iBAAiB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAA8B,CAAC;AAC3F,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,GAAG,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAC1C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}