@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 @@
1
+ {"version":3,"file":"useSecureMessages.js","sourceRoot":"","sources":["../../../src/hooks/useSecureMessages.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
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";
13
+ export type { SecureChatCrypto, DeviceIdentity, KeyPackageBundle, GroupHandle, TargetedWelcome, CommitResult, PassphraseBackup, } from "@agora-sdk/secure-chat-crypto";
14
+ export type * from "./contract";
15
+ export { toBase64, fromBase64, utf8ToBytes, bytesToUtf8 } from "./util/base64";
@@ -0,0 +1,17 @@
1
+ // @agora-sdk/secure-chat-core — platform-agnostic client for Agora end-to-end-encrypted chat.
2
+ //
3
+ // Transport (REST + /secure socket) + provider/hooks, with MLS crypto supplied by dependency
4
+ // injection of a `SecureChatCrypto`. Platform packages (@agora-sdk/secure-chat-react-js, etc.)
5
+ // re-export this and add the concrete crypto + persistence.
6
+ // ── context / provider ──────────────────────────────────────────────────────
7
+ export { SecureChatProvider, useSecureChat } from "./context/secure-chat-context";
8
+ // ── hooks ────────────────────────────────────────────────────────────────────
9
+ export { useSecureDevice } from "./hooks/useSecureDevice";
10
+ export { useSecureConversations } from "./hooks/useSecureConversations";
11
+ export { useSecureMessages } from "./hooks/useSecureMessages";
12
+ // ── transport (for advanced / non-React use) ─────────────────────────────────
13
+ export { SecureChatRestClient } from "./transport/rest";
14
+ export { SecureChatSocketClient } from "./transport/socket";
15
+ // ── utils ────────────────────────────────────────────────────────────────────
16
+ export { toBase64, fromBase64, utf8ToBytes, bytesToUtf8 } from "./util/base64";
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,EAAE;AACF,6FAA6F;AAC7F,+FAA+F;AAC/F,4DAA4D;AAE5D,+EAA+E;AAC/E,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAMlF,gFAAgF;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAExE,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAO9D,gFAAgF;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAsB5D,gFAAgF;AAChF,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,154 @@
1
+ import { AddSecureMemberBody, CreateSecureConversationBody, PublishKeyPackagesBody, RegisterDeviceBody, RemoveSecureMemberBody, SecureConversationMemberModel, SecureConversationModel, SecureDeviceModel, SecureHandshakeModel, SecureKeyBackupModel, SecureKeyPackageClaim, SecureMessageModel, SendSecureMessageBody, UploadKeyBackupBody } from "../contract";
2
+ /**
3
+ * Configuration for {@link SecureChatRestClient}. The base URL and access token are read through
4
+ * resolver callbacks rather than captured once, so a late-set `baseUrl` or a refreshed token take
5
+ * effect on the next request without rebuilding the client.
6
+ */
7
+ export interface SecureChatRestConfig {
8
+ /** Resolve the API base URL (e.g. `getApiBaseUrl()` from @agora-sdk/core → `http://host/v7`). */
9
+ getBaseUrl: () => string;
10
+ /** Resolve the current access token, or undefined when signed out. */
11
+ getAccessToken: () => string | undefined;
12
+ /** The Agora project id (path-scoped on every endpoint). */
13
+ projectId: string;
14
+ }
15
+ /**
16
+ * Typed REST client for the secure-chat blind Delivery Service.
17
+ *
18
+ * Wraps every endpoint in agora-server `docs/SECURE_CHAT.md` §9. Each request lazily resolves the
19
+ * base URL and bearer token via the configured resolvers, scopes the path to
20
+ * `{baseUrl}/{projectId}/secure-chat`, and passes payloads through untouched — all binary is base64
21
+ * on the wire and the client never inspects it.
22
+ *
23
+ * @remarks
24
+ * Construct this directly only for advanced / non-React use. Inside React, prefer the
25
+ * `SecureChatProvider` + hooks, which build and share one client for you.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const rest = new SecureChatRestClient({
30
+ * projectId,
31
+ * getBaseUrl: () => getApiBaseUrl(),
32
+ * getAccessToken: () => session.accessToken,
33
+ * });
34
+ * const device = await rest.registerDevice(body);
35
+ * ```
36
+ */
37
+ export declare class SecureChatRestClient {
38
+ private readonly config;
39
+ private readonly http;
40
+ constructor(config: SecureChatRestConfig);
41
+ /**
42
+ * Register (or idempotently re-assert) the caller's MLS device — its signature key + credential.
43
+ *
44
+ * @param body - The device identity to publish (deviceId, base64 signature key, credential, ciphersuite).
45
+ * @returns The persisted device row; its `.id` is the uuid used as `targetDeviceId` elsewhere.
46
+ */
47
+ registerDevice(body: RegisterDeviceBody): Promise<SecureDeviceModel>;
48
+ /** Public device records (signature key + credential) so peers can build a group. */
49
+ listDevices(userId: string): Promise<SecureDeviceModel[]>;
50
+ /** Revoke the caller's own device. */
51
+ revokeDevice(deviceId: string): Promise<void>;
52
+ /**
53
+ * Publish a batch of fresh, single-use KeyPackages for one of the caller's devices so peers can
54
+ * add it to groups.
55
+ *
56
+ * @param deviceId - The caller's device row id (`SecureDeviceModel.id`).
57
+ * @param body - The KeyPackage bundles to upload (base64 KeyPackage + ref + ciphersuite).
58
+ * @returns How many KeyPackages the server accepted and stored.
59
+ */
60
+ publishKeyPackages(deviceId: string, body: PublishKeyPackagesBody): Promise<number>;
61
+ /** Unconsumed + unexpired KeyPackage count for the caller's device (replenishment signal). */
62
+ keyPackageCount(deviceId: string): Promise<number>;
63
+ /**
64
+ * Atomically claim one KeyPackage for a TARGET device (to add it to a group).
65
+ * Throws on `409 secure-chat/key-packages-exhausted` — caller should surface "device unavailable".
66
+ */
67
+ claimKeyPackage(targetDeviceId: string): Promise<SecureKeyPackageClaim>;
68
+ /**
69
+ * Register a new secure conversation on the blind DS, relaying the client-built Welcomes.
70
+ *
71
+ * @param body - The conversation type, base64 MLS group id, member user ids, and targeted Welcomes.
72
+ * @returns The created conversation row.
73
+ */
74
+ createConversation(body: CreateSecureConversationBody): Promise<SecureConversationModel>;
75
+ /**
76
+ * List the caller's secure conversations, newest activity first.
77
+ *
78
+ * @param params - Optional `limit` and opaque `cursor` for keyset pagination.
79
+ * @returns A page of conversations plus a `hasMore` flag for further paging.
80
+ */
81
+ listConversations(params?: {
82
+ limit?: number;
83
+ cursor?: string;
84
+ }): Promise<{
85
+ conversations: SecureConversationModel[];
86
+ hasMore: boolean;
87
+ }>;
88
+ /**
89
+ * Fetch a single conversation by id (including the caller's membership + unread metadata).
90
+ *
91
+ * @param conversationId - The conversation's id.
92
+ * @returns The conversation row.
93
+ */
94
+ getConversation(conversationId: string): Promise<SecureConversationModel>;
95
+ /** Mark the conversation read up to now (sets the member's `last_read_at`). */
96
+ markRead(conversationId: string): Promise<void>;
97
+ /**
98
+ * Add a user to a conversation by relaying the caller-built Commit + per-device Welcomes.
99
+ *
100
+ * @param conversationId - The conversation to add the member to.
101
+ * @param body - The new member's user id, the broadcast Commit, and the targeted Welcomes.
102
+ * @returns The newly created membership row.
103
+ */
104
+ addMember(conversationId: string, body: AddSecureMemberBody): Promise<SecureConversationMemberModel>;
105
+ /** Admin remove, or self-leave. Returns the new epoch. Throws on `409 epoch-conflict` (rebase). */
106
+ removeMember(conversationId: string, userId: string, body: RemoveSecureMemberBody): Promise<{
107
+ epoch: string;
108
+ }>;
109
+ /**
110
+ * Send one MLS application message (opaque ciphertext) to a conversation.
111
+ *
112
+ * @param conversationId - The target conversation.
113
+ * @param body - The base64 ciphertext, its epoch, the sending device id, and optional content type.
114
+ * @returns The stored message row.
115
+ */
116
+ sendMessage(conversationId: string, body: SendSecureMessageBody): Promise<SecureMessageModel>;
117
+ /**
118
+ * Page through a conversation's messages, newest first (still encrypted — decrypt client-side).
119
+ *
120
+ * @param conversationId - The conversation to read.
121
+ * @param params - Optional `limit` and a `before` cursor (page older than this point).
122
+ * @returns A page of message rows plus a `hasMore` flag.
123
+ */
124
+ listMessages(conversationId: string, params?: {
125
+ limit?: number;
126
+ before?: string;
127
+ }): Promise<{
128
+ messages: SecureMessageModel[];
129
+ hasMore: boolean;
130
+ }>;
131
+ /**
132
+ * The durable catch-up path: union of targeted Welcomes + broadcast Commits/Proposals for the
133
+ * caller's device, ordered by `seq > since`. Page by passing the last `seq` you processed.
134
+ */
135
+ fetchHandshakes(deviceId: string, params?: {
136
+ since?: string;
137
+ limit?: number;
138
+ }): Promise<{
139
+ handshakes: SecureHandshakeModel[];
140
+ hasMore: boolean;
141
+ }>;
142
+ /**
143
+ * Upload (replace) the caller's passphrase-encrypted key backup blob. The server stores it opaquely
144
+ * and cannot decrypt it.
145
+ *
146
+ * @param body - The encrypted blob, nonce, KDF + cipher descriptors, and version.
147
+ * @returns The server's `updatedAt` timestamp for the stored backup.
148
+ */
149
+ uploadKeyBackup(body: UploadKeyBackupBody): Promise<{
150
+ updatedAt: string;
151
+ }>;
152
+ /** Returns the caller's passphrase-encrypted backup blob, or null on 404. */
153
+ getKeyBackup(deviceId?: string): Promise<SecureKeyBackupModel | null>;
154
+ }
@@ -0,0 +1,201 @@
1
+ // Typed REST client for the secure-chat blind Delivery Service.
2
+ //
3
+ // Covers every endpoint in agora-server `docs/SECURE_CHAT.md` §9. Base URL and access token are
4
+ // resolved **lazily per request** (matching the @agora-sdk/core base-URL runtime), so a token
5
+ // refresh or a late-set baseUrl always wins. All binary is base64 on the wire; this client never
6
+ // inspects payloads.
7
+ import axios from "axios";
8
+ /**
9
+ * Typed REST client for the secure-chat blind Delivery Service.
10
+ *
11
+ * Wraps every endpoint in agora-server `docs/SECURE_CHAT.md` §9. Each request lazily resolves the
12
+ * base URL and bearer token via the configured resolvers, scopes the path to
13
+ * `{baseUrl}/{projectId}/secure-chat`, and passes payloads through untouched — all binary is base64
14
+ * on the wire and the client never inspects it.
15
+ *
16
+ * @remarks
17
+ * Construct this directly only for advanced / non-React use. Inside React, prefer the
18
+ * `SecureChatProvider` + hooks, which build and share one client for you.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const rest = new SecureChatRestClient({
23
+ * projectId,
24
+ * getBaseUrl: () => getApiBaseUrl(),
25
+ * getAccessToken: () => session.accessToken,
26
+ * });
27
+ * const device = await rest.registerDevice(body);
28
+ * ```
29
+ */
30
+ export class SecureChatRestClient {
31
+ constructor(config) {
32
+ this.config = config;
33
+ this.http = axios.create();
34
+ this.http.interceptors.request.use((req) => {
35
+ const base = config.getBaseUrl().replace(/\/$/, "");
36
+ req.baseURL = `${base}/${config.projectId}/secure-chat`;
37
+ const token = config.getAccessToken();
38
+ if (token)
39
+ req.headers.set("Authorization", `Bearer ${token}`);
40
+ return req;
41
+ });
42
+ }
43
+ // ── Devices ────────────────────────────────────────────────────────────────
44
+ /**
45
+ * Register (or idempotently re-assert) the caller's MLS device — its signature key + credential.
46
+ *
47
+ * @param body - The device identity to publish (deviceId, base64 signature key, credential, ciphersuite).
48
+ * @returns The persisted device row; its `.id` is the uuid used as `targetDeviceId` elsewhere.
49
+ */
50
+ async registerDevice(body) {
51
+ const { data } = await this.http.post("/devices", body);
52
+ return data;
53
+ }
54
+ /** Public device records (signature key + credential) so peers can build a group. */
55
+ async listDevices(userId) {
56
+ const { data } = await this.http.get("/devices", {
57
+ params: { userId },
58
+ });
59
+ return data.data;
60
+ }
61
+ /** Revoke the caller's own device. */
62
+ async revokeDevice(deviceId) {
63
+ await this.http.delete(`/devices/${encodeURIComponent(deviceId)}`);
64
+ }
65
+ // ── KeyPackages ──────────────────────────────────────────────────────────────
66
+ /**
67
+ * Publish a batch of fresh, single-use KeyPackages for one of the caller's devices so peers can
68
+ * add it to groups.
69
+ *
70
+ * @param deviceId - The caller's device row id (`SecureDeviceModel.id`).
71
+ * @param body - The KeyPackage bundles to upload (base64 KeyPackage + ref + ciphersuite).
72
+ * @returns How many KeyPackages the server accepted and stored.
73
+ */
74
+ async publishKeyPackages(deviceId, body) {
75
+ const { data } = await this.http.post(`/devices/${encodeURIComponent(deviceId)}/key-packages`, body);
76
+ return data.published;
77
+ }
78
+ /** Unconsumed + unexpired KeyPackage count for the caller's device (replenishment signal). */
79
+ async keyPackageCount(deviceId) {
80
+ const { data } = await this.http.get(`/devices/${encodeURIComponent(deviceId)}/key-packages/count`);
81
+ return data.available;
82
+ }
83
+ /**
84
+ * Atomically claim one KeyPackage for a TARGET device (to add it to a group).
85
+ * Throws on `409 secure-chat/key-packages-exhausted` — caller should surface "device unavailable".
86
+ */
87
+ async claimKeyPackage(targetDeviceId) {
88
+ const { data } = await this.http.post(`/devices/${encodeURIComponent(targetDeviceId)}/key-packages/claim`);
89
+ return data;
90
+ }
91
+ // ── Conversations ────────────────────────────────────────────────────────────
92
+ /**
93
+ * Register a new secure conversation on the blind DS, relaying the client-built Welcomes.
94
+ *
95
+ * @param body - The conversation type, base64 MLS group id, member user ids, and targeted Welcomes.
96
+ * @returns The created conversation row.
97
+ */
98
+ async createConversation(body) {
99
+ const { data } = await this.http.post("/conversations", body);
100
+ return data;
101
+ }
102
+ /**
103
+ * List the caller's secure conversations, newest activity first.
104
+ *
105
+ * @param params - Optional `limit` and opaque `cursor` for keyset pagination.
106
+ * @returns A page of conversations plus a `hasMore` flag for further paging.
107
+ */
108
+ async listConversations(params) {
109
+ const { data } = await this.http.get("/conversations", { params });
110
+ return data;
111
+ }
112
+ /**
113
+ * Fetch a single conversation by id (including the caller's membership + unread metadata).
114
+ *
115
+ * @param conversationId - The conversation's id.
116
+ * @returns The conversation row.
117
+ */
118
+ async getConversation(conversationId) {
119
+ const { data } = await this.http.get(`/conversations/${encodeURIComponent(conversationId)}`);
120
+ return data;
121
+ }
122
+ /** Mark the conversation read up to now (sets the member's `last_read_at`). */
123
+ async markRead(conversationId) {
124
+ await this.http.post(`/conversations/${encodeURIComponent(conversationId)}/read`);
125
+ }
126
+ // ── Membership (client supplies the MLS Commit + Welcomes; the DS linearizes) ──
127
+ /**
128
+ * Add a user to a conversation by relaying the caller-built Commit + per-device Welcomes.
129
+ *
130
+ * @param conversationId - The conversation to add the member to.
131
+ * @param body - The new member's user id, the broadcast Commit, and the targeted Welcomes.
132
+ * @returns The newly created membership row.
133
+ */
134
+ async addMember(conversationId, body) {
135
+ const { data } = await this.http.post(`/conversations/${encodeURIComponent(conversationId)}/members`, body);
136
+ return data;
137
+ }
138
+ /** Admin remove, or self-leave. Returns the new epoch. Throws on `409 epoch-conflict` (rebase). */
139
+ async removeMember(conversationId, userId, body) {
140
+ const { data } = await this.http.delete(`/conversations/${encodeURIComponent(conversationId)}/members/${encodeURIComponent(userId)}`, { data: body });
141
+ return { epoch: data.epoch };
142
+ }
143
+ // ── Messages ──────────────────────────────────────────────────────────────────
144
+ /**
145
+ * Send one MLS application message (opaque ciphertext) to a conversation.
146
+ *
147
+ * @param conversationId - The target conversation.
148
+ * @param body - The base64 ciphertext, its epoch, the sending device id, and optional content type.
149
+ * @returns The stored message row.
150
+ */
151
+ async sendMessage(conversationId, body) {
152
+ const { data } = await this.http.post(`/conversations/${encodeURIComponent(conversationId)}/messages`, body);
153
+ return data;
154
+ }
155
+ /**
156
+ * Page through a conversation's messages, newest first (still encrypted — decrypt client-side).
157
+ *
158
+ * @param conversationId - The conversation to read.
159
+ * @param params - Optional `limit` and a `before` cursor (page older than this point).
160
+ * @returns A page of message rows plus a `hasMore` flag.
161
+ */
162
+ async listMessages(conversationId, params) {
163
+ const { data } = await this.http.get(`/conversations/${encodeURIComponent(conversationId)}/messages`, { params });
164
+ return data;
165
+ }
166
+ // ── Handshake inbox + key backup ───────────────────────────────────────────────
167
+ /**
168
+ * The durable catch-up path: union of targeted Welcomes + broadcast Commits/Proposals for the
169
+ * caller's device, ordered by `seq > since`. Page by passing the last `seq` you processed.
170
+ */
171
+ async fetchHandshakes(deviceId, params) {
172
+ const { data } = await this.http.get(`/devices/${encodeURIComponent(deviceId)}/handshakes`, { params });
173
+ return data;
174
+ }
175
+ /**
176
+ * Upload (replace) the caller's passphrase-encrypted key backup blob. The server stores it opaquely
177
+ * and cannot decrypt it.
178
+ *
179
+ * @param body - The encrypted blob, nonce, KDF + cipher descriptors, and version.
180
+ * @returns The server's `updatedAt` timestamp for the stored backup.
181
+ */
182
+ async uploadKeyBackup(body) {
183
+ const { data } = await this.http.put("/key-backup", body);
184
+ return { updatedAt: data.updatedAt };
185
+ }
186
+ /** Returns the caller's passphrase-encrypted backup blob, or null on 404. */
187
+ async getKeyBackup(deviceId) {
188
+ try {
189
+ const { data } = await this.http.get("/key-backup", {
190
+ params: deviceId ? { deviceId } : undefined,
191
+ });
192
+ return data;
193
+ }
194
+ catch (err) {
195
+ if (axios.isAxiosError(err) && err.response?.status === 404)
196
+ return null;
197
+ throw err;
198
+ }
199
+ }
200
+ }
201
+ //# 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,66 @@
1
+ // Client for the `/secure` socket.io namespace — realtime notification layer for secure chat.
2
+ //
3
+ // A SEPARATE namespace from the plaintext chat socket (defense-in-depth: ciphertext events never
4
+ // mix with plaintext handlers). Realtime is an OPTIMIZATION — the REST `fetchHandshakes(since)` +
5
+ // `listMessages` endpoints remain the durable source of truth for offline catch-up. All event
6
+ // payloads carry ciphertext only.
7
+ import { io } from "socket.io-client";
8
+ /**
9
+ * Thin wrapper around the `/secure` namespace connection. Devices the user owns are auto-joined to
10
+ * their `secure:device:{id}` rooms on connect (server-side), so targeted Welcomes arrive without an
11
+ * explicit join; conversation rooms are membership-gated and joined on demand.
12
+ */
13
+ export class SecureChatSocketClient {
14
+ constructor(config) {
15
+ this.config = config;
16
+ this.socket = null;
17
+ }
18
+ /**
19
+ * Lazily open (or reuse) the `/secure` namespace connection.
20
+ *
21
+ * @returns The live socket; idempotent while already connected.
22
+ */
23
+ connect() {
24
+ if (this.socket?.connected)
25
+ return this.socket;
26
+ const origin = this.config.getSocketUrl().replace(/\/$/, "");
27
+ this.socket = io(`${origin}/secure`, {
28
+ auth: { token: this.config.getAccessToken() },
29
+ query: { projectId: this.config.projectId },
30
+ transports: ["websocket"],
31
+ autoConnect: true,
32
+ });
33
+ return this.socket;
34
+ }
35
+ /** Tear down the connection and drop the cached socket (e.g. on provider unmount or sign-out). */
36
+ disconnect() {
37
+ this.socket?.disconnect();
38
+ this.socket = null;
39
+ }
40
+ /** The underlying socket if one exists, else `null` — escape hatch for advanced socket.io use. */
41
+ get raw() {
42
+ return this.socket;
43
+ }
44
+ /** Join a conversation room (membership-gated server-side) to receive its broadcasts. */
45
+ joinConversation(conversationId) {
46
+ this.connect().emit("join:secure-conversation", conversationId);
47
+ }
48
+ /** Explicitly join a device room (ownership-verified). Owned devices auto-join on connect. */
49
+ joinDevice(deviceId) {
50
+ this.connect().emit("join:secure-device", deviceId);
51
+ }
52
+ /**
53
+ * Subscribe to a server → client event, auto-connecting if needed.
54
+ *
55
+ * @param event - The `secure:*` event name to listen for.
56
+ * @param handler - The typed handler for that event's payload.
57
+ * @returns An unsubscribe function that removes this handler.
58
+ */
59
+ on(event, handler) {
60
+ const s = this.connect();
61
+ // socket.io typings accept the event/handler pair; the cast keeps our strict map ergonomic.
62
+ s.on(event, handler);
63
+ return () => s.off(event, handler);
64
+ }
65
+ }
66
+ //# 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,OAAO,EAAE,EAAE,EAAU,MAAM,kBAAkB,CAAC;AAqC9C;;;;GAIG;AACH,MAAM,OAAO,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,EAAE,CAAC,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"}
@@ -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,63 @@
1
+ // Base64 ⇄ Uint8Array at the wire boundary.
2
+ //
3
+ // The `SecureChatCrypto` seam works in `Uint8Array`; the server contract is base64. These helpers
4
+ // are the only place that conversion happens. Pure (no Buffer / atob / btoa) so the same code runs
5
+ // on web, React Native, and Node without a polyfill.
6
+ const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7
+ const LOOKUP = (() => {
8
+ const table = new Array(256).fill(-1);
9
+ for (let i = 0; i < ALPHABET.length; i++)
10
+ table[ALPHABET.charCodeAt(i)] = i;
11
+ table["=".charCodeAt(0)] = 0;
12
+ return table;
13
+ })();
14
+ /** Encode bytes to a standard (padded) base64 string. */
15
+ export function toBase64(bytes) {
16
+ let out = "";
17
+ let i = 0;
18
+ for (; i + 2 < bytes.length; i += 3) {
19
+ const n = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
20
+ out += ALPHABET[(n >> 18) & 63] + ALPHABET[(n >> 12) & 63] + ALPHABET[(n >> 6) & 63] + ALPHABET[n & 63];
21
+ }
22
+ const rem = bytes.length - i;
23
+ if (rem === 1) {
24
+ const n = bytes[i] << 16;
25
+ out += ALPHABET[(n >> 18) & 63] + ALPHABET[(n >> 12) & 63] + "==";
26
+ }
27
+ else if (rem === 2) {
28
+ const n = (bytes[i] << 16) | (bytes[i + 1] << 8);
29
+ out += ALPHABET[(n >> 18) & 63] + ALPHABET[(n >> 12) & 63] + ALPHABET[(n >> 6) & 63] + "=";
30
+ }
31
+ return out;
32
+ }
33
+ /** Decode a standard base64 string to bytes. Ignores ASCII whitespace; throws on bad chars. */
34
+ export function fromBase64(b64) {
35
+ const clean = b64.replace(/[\r\n\t ]/g, "");
36
+ const padless = clean.replace(/=+$/, "");
37
+ const outLen = (padless.length * 3) >> 2;
38
+ const out = new Uint8Array(outLen);
39
+ let o = 0;
40
+ let buffer = 0;
41
+ let bits = 0;
42
+ for (let i = 0; i < padless.length; i++) {
43
+ const v = LOOKUP[padless.charCodeAt(i)];
44
+ if (v === -1)
45
+ throw new Error("invalid base64 character");
46
+ buffer = (buffer << 6) | v;
47
+ bits += 6;
48
+ if (bits >= 8) {
49
+ bits -= 8;
50
+ out[o++] = (buffer >> bits) & 0xff;
51
+ }
52
+ }
53
+ return out;
54
+ }
55
+ /** UTF-8 text → bytes (for encrypting message plaintext). */
56
+ export function utf8ToBytes(text) {
57
+ return new TextEncoder().encode(text);
58
+ }
59
+ /** Bytes → UTF-8 text (for decrypted message plaintext). */
60
+ export function bytesToUtf8(bytes) {
61
+ return new TextDecoder().decode(bytes);
62
+ }
63
+ //# sourceMappingURL=base64.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64.js","sourceRoot":"","sources":["../../../src/util/base64.ts"],"names":[],"mappings":"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"}