@antzsoft/chat-core 1.1.4 → 1.1.6
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/README.md +17 -0
- package/dist/chunk-3ISWHKXI.js +429 -0
- package/dist/chunk-3ISWHKXI.js.map +1 -0
- package/dist/index.cjs +72 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -619
- package/dist/index.d.ts +4 -619
- package/dist/index.js +54 -409
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +224 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.cts +5 -0
- package/dist/internal.d.ts +5 -0
- package/dist/internal.js +9 -0
- package/dist/internal.js.map +1 -0
- package/dist/storage-Dc0__sXE.d.cts +635 -0
- package/dist/storage-Dc0__sXE.d.ts +635 -0
- package/docs/integration-guide.html +174 -5
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearTransitSession,
|
|
3
|
+
configureTransit,
|
|
4
|
+
decryptPayload,
|
|
5
|
+
encryptPayload,
|
|
6
|
+
getApiClient,
|
|
7
|
+
getCompressionStrategy,
|
|
8
|
+
getSessionKey,
|
|
9
|
+
initApiClient,
|
|
10
|
+
isTransitEnabled,
|
|
11
|
+
isTransitEnvelope,
|
|
12
|
+
setApiClientInstance,
|
|
13
|
+
setTransitSession,
|
|
14
|
+
storageApi,
|
|
15
|
+
uploadBatch
|
|
16
|
+
} from "./chunk-3ISWHKXI.js";
|
|
1
17
|
import {
|
|
2
18
|
useChatStore
|
|
3
19
|
} from "./chunk-TB52RCSF.js";
|
|
@@ -61,299 +77,6 @@ function resolveConfig(config) {
|
|
|
61
77
|
};
|
|
62
78
|
}
|
|
63
79
|
|
|
64
|
-
// src/compression/compress.ts
|
|
65
|
-
var GZIP_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
66
|
-
"text/plain",
|
|
67
|
-
"text/csv",
|
|
68
|
-
"text/markdown",
|
|
69
|
-
"text/x-markdown",
|
|
70
|
-
"text/xml",
|
|
71
|
-
"application/xml",
|
|
72
|
-
"text/yaml",
|
|
73
|
-
"text/x-yaml",
|
|
74
|
-
"application/x-yaml",
|
|
75
|
-
"application/rtf",
|
|
76
|
-
"text/rtf",
|
|
77
|
-
"application/json",
|
|
78
|
-
"image/svg+xml"
|
|
79
|
-
]);
|
|
80
|
-
var IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
81
|
-
"image/jpeg",
|
|
82
|
-
"image/png",
|
|
83
|
-
"image/gif",
|
|
84
|
-
"image/webp",
|
|
85
|
-
"image/bmp",
|
|
86
|
-
"image/tiff"
|
|
87
|
-
]);
|
|
88
|
-
var SKIP_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
89
|
-
"video/mp4",
|
|
90
|
-
"video/webm",
|
|
91
|
-
"video/quicktime",
|
|
92
|
-
"audio/mpeg",
|
|
93
|
-
"audio/wav",
|
|
94
|
-
"audio/ogg",
|
|
95
|
-
"audio/webm",
|
|
96
|
-
"audio/mp4",
|
|
97
|
-
"application/zip",
|
|
98
|
-
"application/pdf",
|
|
99
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
100
|
-
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
101
|
-
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
102
|
-
]);
|
|
103
|
-
function getCompressionStrategy(mimeType, config) {
|
|
104
|
-
if (SKIP_MIME_TYPES.has(mimeType)) return "skip";
|
|
105
|
-
if (IMAGE_MIME_TYPES.has(mimeType)) return "image";
|
|
106
|
-
if (config.compressDocuments && GZIP_MIME_TYPES.has(mimeType)) return "gzip";
|
|
107
|
-
return "skip";
|
|
108
|
-
}
|
|
109
|
-
async function compressFile(file, platformCompressFn, config) {
|
|
110
|
-
const noop = {
|
|
111
|
-
...file,
|
|
112
|
-
originalSize: file.size,
|
|
113
|
-
compressed: false,
|
|
114
|
-
compressionAlgorithm: "none"
|
|
115
|
-
};
|
|
116
|
-
if (!config.enabled || !platformCompressFn) return noop;
|
|
117
|
-
const strategy = getCompressionStrategy(file.type, config);
|
|
118
|
-
if (strategy === "skip") return noop;
|
|
119
|
-
try {
|
|
120
|
-
return await platformCompressFn(file, config);
|
|
121
|
-
} catch {
|
|
122
|
-
return noop;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// src/api/client.ts
|
|
127
|
-
import axios from "axios";
|
|
128
|
-
|
|
129
|
-
// src/crypto/transit.ts
|
|
130
|
-
async function encryptPayload(data, sessionKey) {
|
|
131
|
-
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
|
|
132
|
-
const plaintext = new TextEncoder().encode(JSON.stringify(data));
|
|
133
|
-
const encrypted = await globalThis.crypto.subtle.encrypt(
|
|
134
|
-
{ name: "AES-GCM", iv },
|
|
135
|
-
sessionKey,
|
|
136
|
-
plaintext
|
|
137
|
-
);
|
|
138
|
-
const ct = encrypted.slice(0, encrypted.byteLength - 16);
|
|
139
|
-
const tag = encrypted.slice(encrypted.byteLength - 16);
|
|
140
|
-
return {
|
|
141
|
-
v: 1,
|
|
142
|
-
iv: bufToB64(iv),
|
|
143
|
-
tag: bufToB64(tag),
|
|
144
|
-
ct: bufToB64(ct)
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
async function decryptPayload(envelope, sessionKey) {
|
|
148
|
-
const iv = b64ToBuf(envelope.iv);
|
|
149
|
-
const tag = b64ToBuf(envelope.tag);
|
|
150
|
-
const ct = b64ToBuf(envelope.ct);
|
|
151
|
-
const combined = new Uint8Array(ct.byteLength + tag.byteLength);
|
|
152
|
-
combined.set(new Uint8Array(ct), 0);
|
|
153
|
-
combined.set(new Uint8Array(tag), ct.byteLength);
|
|
154
|
-
const decrypted = await globalThis.crypto.subtle.decrypt(
|
|
155
|
-
{ name: "AES-GCM", iv: new Uint8Array(iv) },
|
|
156
|
-
sessionKey,
|
|
157
|
-
combined
|
|
158
|
-
);
|
|
159
|
-
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
160
|
-
}
|
|
161
|
-
function isTransitEnvelope(v) {
|
|
162
|
-
return typeof v === "object" && v !== null && v.v === 1 && typeof v.iv === "string" && typeof v.tag === "string" && typeof v.ct === "string";
|
|
163
|
-
}
|
|
164
|
-
function bufToB64(buf) {
|
|
165
|
-
const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
|
|
166
|
-
let str = "";
|
|
167
|
-
bytes.forEach((b) => {
|
|
168
|
-
str += String.fromCharCode(b);
|
|
169
|
-
});
|
|
170
|
-
return btoa(str);
|
|
171
|
-
}
|
|
172
|
-
function b64ToBuf(b64) {
|
|
173
|
-
const bin = atob(b64);
|
|
174
|
-
const buf = new Uint8Array(bin.length);
|
|
175
|
-
for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
|
|
176
|
-
return buf.buffer;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// src/crypto/session.ts
|
|
180
|
-
var _KEY = /* @__PURE__ */ Symbol.for("__antz_chat_transit__");
|
|
181
|
-
function getState() {
|
|
182
|
-
const g = globalThis;
|
|
183
|
-
if (!g[_KEY]) {
|
|
184
|
-
g[_KEY] = {
|
|
185
|
-
session: null,
|
|
186
|
-
sessionEverEstablished: false,
|
|
187
|
-
readyResolve: null,
|
|
188
|
-
readyPromise: null,
|
|
189
|
-
transitConfigured: null
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
return g[_KEY];
|
|
193
|
-
}
|
|
194
|
-
function configureTransit(enabled) {
|
|
195
|
-
const s = getState();
|
|
196
|
-
s.transitConfigured = enabled;
|
|
197
|
-
if (!enabled) {
|
|
198
|
-
s.readyResolve?.();
|
|
199
|
-
s.readyResolve = null;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
function waitForTransitReady() {
|
|
203
|
-
const s = getState();
|
|
204
|
-
if (!s.transitConfigured) return Promise.resolve();
|
|
205
|
-
if (s.session) return Promise.resolve();
|
|
206
|
-
if (s.sessionEverEstablished) return Promise.resolve();
|
|
207
|
-
if (!s.readyPromise) {
|
|
208
|
-
s.readyPromise = new Promise((resolve) => {
|
|
209
|
-
s.readyResolve = resolve;
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
return s.readyPromise;
|
|
213
|
-
}
|
|
214
|
-
function setTransitSession(session) {
|
|
215
|
-
const s = getState();
|
|
216
|
-
s.session = session;
|
|
217
|
-
s.sessionEverEstablished = true;
|
|
218
|
-
s.readyResolve?.();
|
|
219
|
-
s.readyResolve = null;
|
|
220
|
-
}
|
|
221
|
-
function clearTransitSession() {
|
|
222
|
-
const s = getState();
|
|
223
|
-
s.session = null;
|
|
224
|
-
s.readyPromise = null;
|
|
225
|
-
s.readyResolve = null;
|
|
226
|
-
}
|
|
227
|
-
function isTransitEnabled() {
|
|
228
|
-
return getState().session?.enabled === true;
|
|
229
|
-
}
|
|
230
|
-
function getSessionKey() {
|
|
231
|
-
return getState().session?.sessionKey ?? null;
|
|
232
|
-
}
|
|
233
|
-
function getSessionId() {
|
|
234
|
-
return getState().session?.sessionId ?? null;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// src/api/client.ts
|
|
238
|
-
var _tokenStore = null;
|
|
239
|
-
var _config = null;
|
|
240
|
-
var _avatarSent = false;
|
|
241
|
-
function initApiClient(config, tokenStore) {
|
|
242
|
-
_config = config;
|
|
243
|
-
_tokenStore = tokenStore;
|
|
244
|
-
_avatarSent = false;
|
|
245
|
-
const client = axios.create({
|
|
246
|
-
baseURL: config.apiUrl,
|
|
247
|
-
headers: { "Content-Type": "application/json" }
|
|
248
|
-
});
|
|
249
|
-
configureTransit(config.transitEncryption);
|
|
250
|
-
client.interceptors.request.use(async (req) => {
|
|
251
|
-
const token = _tokenStore?.getAccessToken();
|
|
252
|
-
if (token) req.headers["Authorization"] = `Bearer ${token}`;
|
|
253
|
-
if (_config?.userId) req.headers["x-user-id"] = _config.userId;
|
|
254
|
-
if (_config?.tenantId) req.headers["X-Tenant-ID"] = _config.tenantId;
|
|
255
|
-
if (token && !_avatarSent && _config?.avatar) {
|
|
256
|
-
if (_config.avatar.base64) req.headers["x-avatar-base64"] = _config.avatar.base64;
|
|
257
|
-
else if (_config.avatar.url) req.headers["x-avatar-url"] = _config.avatar.url;
|
|
258
|
-
_avatarSent = true;
|
|
259
|
-
}
|
|
260
|
-
await waitForTransitReady();
|
|
261
|
-
if (isTransitEnabled()) {
|
|
262
|
-
const sessionId = getSessionId();
|
|
263
|
-
const key = getSessionKey();
|
|
264
|
-
if (sessionId && key) {
|
|
265
|
-
req.headers["x-transit-session"] = sessionId;
|
|
266
|
-
if (req.data !== void 0 && req.data !== null) {
|
|
267
|
-
const envelope = await encryptPayload(req.data, key);
|
|
268
|
-
req.data = envelope;
|
|
269
|
-
req.headers["x-transit-encrypted"] = "1";
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return req;
|
|
274
|
-
});
|
|
275
|
-
let isRefreshing = false;
|
|
276
|
-
let refreshQueue = [];
|
|
277
|
-
client.interceptors.response.use(
|
|
278
|
-
async (response) => {
|
|
279
|
-
if (isTransitEnabled()) {
|
|
280
|
-
const key = getSessionKey();
|
|
281
|
-
if (key) {
|
|
282
|
-
if (isTransitEnvelope(response.data)) {
|
|
283
|
-
response.data = await decryptPayload(response.data, key);
|
|
284
|
-
} else if (response.data?.data && isTransitEnvelope(response.data.data)) {
|
|
285
|
-
response.data.data = await decryptPayload(response.data.data, key);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
if (response.data && typeof response.data === "object" && "success" in response.data && "data" in response.data) {
|
|
290
|
-
response.data = response.data.data;
|
|
291
|
-
}
|
|
292
|
-
return response;
|
|
293
|
-
},
|
|
294
|
-
async (error) => {
|
|
295
|
-
if (isTransitEnabled() && error.response?.data) {
|
|
296
|
-
const key = getSessionKey();
|
|
297
|
-
if (key) {
|
|
298
|
-
try {
|
|
299
|
-
if (isTransitEnvelope(error.response.data)) {
|
|
300
|
-
error.response.data = await decryptPayload(error.response.data, key);
|
|
301
|
-
} else if (error.response.data?.data && isTransitEnvelope(error.response.data.data)) {
|
|
302
|
-
error.response.data.data = await decryptPayload(error.response.data.data, key);
|
|
303
|
-
}
|
|
304
|
-
} catch {
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
const original = error.config;
|
|
309
|
-
if (error.response?.status === 401 && !original._retry) {
|
|
310
|
-
const refreshToken = _tokenStore?.getRefreshToken();
|
|
311
|
-
if (!refreshToken) {
|
|
312
|
-
_tokenStore?.clearTokens();
|
|
313
|
-
return Promise.reject(error);
|
|
314
|
-
}
|
|
315
|
-
if (isRefreshing) {
|
|
316
|
-
return new Promise((resolve) => {
|
|
317
|
-
refreshQueue.push((newToken) => {
|
|
318
|
-
original.headers["Authorization"] = `Bearer ${newToken}`;
|
|
319
|
-
resolve(client(original));
|
|
320
|
-
});
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
original._retry = true;
|
|
324
|
-
isRefreshing = true;
|
|
325
|
-
try {
|
|
326
|
-
const { data } = await axios.post(
|
|
327
|
-
`${_config.apiUrl}/auth/refresh`,
|
|
328
|
-
{ refreshToken }
|
|
329
|
-
);
|
|
330
|
-
const tokens = data.data ?? data;
|
|
331
|
-
_tokenStore?.setTokens(tokens);
|
|
332
|
-
refreshQueue.forEach((cb) => cb(tokens.accessToken));
|
|
333
|
-
refreshQueue = [];
|
|
334
|
-
original.headers["Authorization"] = `Bearer ${tokens.accessToken}`;
|
|
335
|
-
return client(original);
|
|
336
|
-
} catch {
|
|
337
|
-
_tokenStore?.clearTokens();
|
|
338
|
-
return Promise.reject(error);
|
|
339
|
-
} finally {
|
|
340
|
-
isRefreshing = false;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
return Promise.reject(error);
|
|
344
|
-
}
|
|
345
|
-
);
|
|
346
|
-
return client;
|
|
347
|
-
}
|
|
348
|
-
var _instance = null;
|
|
349
|
-
function setApiClientInstance(instance) {
|
|
350
|
-
_instance = instance;
|
|
351
|
-
}
|
|
352
|
-
function getApiClient() {
|
|
353
|
-
if (!_instance) throw new Error("[AntzChat] API client not initialized. Call initApiClient first.");
|
|
354
|
-
return _instance;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
80
|
// src/api/auth.ts
|
|
358
81
|
var authApi = {
|
|
359
82
|
async login(credentials) {
|
|
@@ -382,7 +105,7 @@ var authApi = {
|
|
|
382
105
|
async uploadAvatar(file, mimeType) {
|
|
383
106
|
const form = new FormData();
|
|
384
107
|
form.append("avatar", file instanceof File ? file : new File([file], "avatar.jpg", { type: mimeType ?? "image/jpeg" }));
|
|
385
|
-
const { data } = await getApiClient().
|
|
108
|
+
const { data } = await getApiClient().post("/users/me/avatar", form, {
|
|
386
109
|
headers: { "Content-Type": "multipart/form-data" }
|
|
387
110
|
});
|
|
388
111
|
return data;
|
|
@@ -431,22 +154,22 @@ var messagesApi = {
|
|
|
431
154
|
return data;
|
|
432
155
|
},
|
|
433
156
|
async update(messageId, text) {
|
|
434
|
-
const { data } = await getApiClient().
|
|
157
|
+
const { data } = await getApiClient().post(`/messages/${messageId}/update`, { text });
|
|
435
158
|
return data;
|
|
436
159
|
},
|
|
437
160
|
async delete(messageId) {
|
|
438
|
-
await getApiClient().
|
|
161
|
+
await getApiClient().post(`/messages/${messageId}/delete`);
|
|
439
162
|
},
|
|
440
163
|
async deleteForMe(messageId) {
|
|
441
|
-
await getApiClient().
|
|
164
|
+
await getApiClient().post(`/messages/${messageId}/delete-for-me`);
|
|
442
165
|
},
|
|
443
166
|
async addReaction(messageId, emoji) {
|
|
444
167
|
const { data } = await getApiClient().post(`/messages/${messageId}/reactions`, { emoji });
|
|
445
168
|
return data;
|
|
446
169
|
},
|
|
447
170
|
async removeReaction(messageId, emoji) {
|
|
448
|
-
const { data } = await getApiClient().
|
|
449
|
-
`/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`
|
|
171
|
+
const { data } = await getApiClient().post(
|
|
172
|
+
`/messages/${messageId}/reactions/${encodeURIComponent(emoji)}/remove`
|
|
450
173
|
);
|
|
451
174
|
return data;
|
|
452
175
|
},
|
|
@@ -454,7 +177,7 @@ var messagesApi = {
|
|
|
454
177
|
await getApiClient().post(`/messages/${messageId}/star`);
|
|
455
178
|
},
|
|
456
179
|
async unstar(messageId) {
|
|
457
|
-
await getApiClient().
|
|
180
|
+
await getApiClient().post(`/messages/${messageId}/unstar`);
|
|
458
181
|
},
|
|
459
182
|
async getStarred(params = {}) {
|
|
460
183
|
const { data } = await getApiClient().get("/messages/starred", { params });
|
|
@@ -478,7 +201,7 @@ var messagesApi = {
|
|
|
478
201
|
return data;
|
|
479
202
|
},
|
|
480
203
|
async unpin(messageId) {
|
|
481
|
-
const { data } = await getApiClient().
|
|
204
|
+
const { data } = await getApiClient().post(`/messages/${messageId}/unpin`);
|
|
482
205
|
return data;
|
|
483
206
|
},
|
|
484
207
|
async getPinned(conversationId) {
|
|
@@ -521,10 +244,12 @@ function normalizeLastMessage(lastMsg) {
|
|
|
521
244
|
text: lastMsg.contentPreview
|
|
522
245
|
},
|
|
523
246
|
reactions: [],
|
|
247
|
+
lastReaction: lastMsg.lastReaction ?? null,
|
|
524
248
|
status: lastMsg.status ?? "sent",
|
|
525
249
|
isEdited: false,
|
|
526
250
|
sentAt: lastMsg.sentAt ?? "",
|
|
527
|
-
createdAt: lastMsg.sentAt ?? ""
|
|
251
|
+
createdAt: lastMsg.sentAt ?? "",
|
|
252
|
+
...lastMsg.senderName && { senderName: lastMsg.senderName }
|
|
528
253
|
};
|
|
529
254
|
}
|
|
530
255
|
function normalizeConversation(conv) {
|
|
@@ -553,11 +278,11 @@ var conversationsApi = {
|
|
|
553
278
|
return normalizeConversation(data);
|
|
554
279
|
},
|
|
555
280
|
async update(conversationId, payload) {
|
|
556
|
-
const { data } = await getApiClient().
|
|
281
|
+
const { data } = await getApiClient().post(`/conversations/${conversationId}/update`, payload);
|
|
557
282
|
return normalizeConversation(data);
|
|
558
283
|
},
|
|
559
284
|
async delete(conversationId) {
|
|
560
|
-
await getApiClient().
|
|
285
|
+
await getApiClient().post(`/conversations/${conversationId}/delete`);
|
|
561
286
|
},
|
|
562
287
|
async addParticipants(conversationId, userIds, role) {
|
|
563
288
|
const { data } = await getApiClient().post(
|
|
@@ -567,13 +292,13 @@ var conversationsApi = {
|
|
|
567
292
|
return normalizeConversation(data);
|
|
568
293
|
},
|
|
569
294
|
async removeParticipant(conversationId, userId) {
|
|
570
|
-
const { data } = await getApiClient().
|
|
571
|
-
`/conversations/${conversationId}/participants/${userId}`
|
|
295
|
+
const { data } = await getApiClient().post(
|
|
296
|
+
`/conversations/${conversationId}/participants/${userId}/remove`
|
|
572
297
|
);
|
|
573
298
|
return normalizeConversation(data);
|
|
574
299
|
},
|
|
575
300
|
async updateParticipantRole(conversationId, userId, role) {
|
|
576
|
-
const { data } = await getApiClient().
|
|
301
|
+
const { data } = await getApiClient().post(
|
|
577
302
|
`/conversations/${conversationId}/participants/${userId}/role`,
|
|
578
303
|
{ role }
|
|
579
304
|
);
|
|
@@ -583,17 +308,17 @@ var conversationsApi = {
|
|
|
583
308
|
await getApiClient().post(`/conversations/${conversationId}/mute`, mutedUntil ? { mutedUntil } : {});
|
|
584
309
|
},
|
|
585
310
|
async unmute(conversationId) {
|
|
586
|
-
await getApiClient().
|
|
311
|
+
await getApiClient().post(`/conversations/${conversationId}/unmute`);
|
|
587
312
|
},
|
|
588
313
|
async pin(conversationId) {
|
|
589
314
|
await getApiClient().post(`/conversations/${conversationId}/pin`);
|
|
590
315
|
},
|
|
591
316
|
async unpin(conversationId) {
|
|
592
|
-
await getApiClient().
|
|
317
|
+
await getApiClient().post(`/conversations/${conversationId}/unpin`);
|
|
593
318
|
},
|
|
594
319
|
async leave(conversationId, andDelete) {
|
|
595
320
|
const url = andDelete ? `/conversations/${conversationId}/leave?delete=true` : `/conversations/${conversationId}/leave`;
|
|
596
|
-
await getApiClient().
|
|
321
|
+
await getApiClient().post(url);
|
|
597
322
|
},
|
|
598
323
|
async getMembers(conversationId, filter) {
|
|
599
324
|
const { data } = await getApiClient().get(
|
|
@@ -628,107 +353,18 @@ var conversationsApi = {
|
|
|
628
353
|
* Server copies storageKey into conversation.iconMeta and deletes the chat_files record.
|
|
629
354
|
*/
|
|
630
355
|
async uploadIcon(conversationId, fileId) {
|
|
631
|
-
const { data } = await getApiClient().
|
|
356
|
+
const { data } = await getApiClient().post(
|
|
632
357
|
`/conversations/${conversationId}/icon`,
|
|
633
358
|
{ fileId }
|
|
634
359
|
);
|
|
635
360
|
return normalizeConversation(data);
|
|
636
361
|
},
|
|
637
362
|
async removeIcon(conversationId) {
|
|
638
|
-
const { data } = await getApiClient().
|
|
639
|
-
`/conversations/${conversationId}/icon`
|
|
640
|
-
);
|
|
363
|
+
const { data } = await getApiClient().post(`/conversations/${conversationId}/icon/remove`);
|
|
641
364
|
return normalizeConversation(data);
|
|
642
365
|
}
|
|
643
366
|
};
|
|
644
367
|
|
|
645
|
-
// src/api/storage.ts
|
|
646
|
-
var storageApi = {
|
|
647
|
-
async requestPresignedUrl(payload) {
|
|
648
|
-
const { data } = await getApiClient().post("/storage/presigned-url", payload);
|
|
649
|
-
return data;
|
|
650
|
-
},
|
|
651
|
-
async requestPresignedUrlBatch(files) {
|
|
652
|
-
const { data } = await getApiClient().post("/storage/presigned-url/batch", { files });
|
|
653
|
-
return data;
|
|
654
|
-
},
|
|
655
|
-
async confirmUpload(fileId) {
|
|
656
|
-
const { data } = await getApiClient().post(`/storage/confirm/${fileId}`);
|
|
657
|
-
return data;
|
|
658
|
-
},
|
|
659
|
-
async getFile(fileId) {
|
|
660
|
-
const { data } = await getApiClient().get(`/storage/files/${fileId}`);
|
|
661
|
-
return data;
|
|
662
|
-
},
|
|
663
|
-
async getFileUrl(fileId, expiresIn) {
|
|
664
|
-
const { data } = await getApiClient().get(`/storage/files/${fileId}/url`, {
|
|
665
|
-
params: expiresIn ? { expiresIn } : {}
|
|
666
|
-
});
|
|
667
|
-
return data;
|
|
668
|
-
},
|
|
669
|
-
async deleteFile(fileId) {
|
|
670
|
-
await getApiClient().delete(`/storage/files/${fileId}`);
|
|
671
|
-
},
|
|
672
|
-
async getConversationFiles(conversationId, params = {}) {
|
|
673
|
-
const { data } = await getApiClient().get(
|
|
674
|
-
`/storage/conversations/${conversationId}/files`,
|
|
675
|
-
{ params }
|
|
676
|
-
);
|
|
677
|
-
return data;
|
|
678
|
-
},
|
|
679
|
-
async getMyFiles(params = {}) {
|
|
680
|
-
const { data } = await getApiClient().get("/storage/my-files", { params });
|
|
681
|
-
return data;
|
|
682
|
-
}
|
|
683
|
-
};
|
|
684
|
-
async function uploadBatch(files, platformUploadFn, conversationId, onProgress, platformCompressFn, compressionConfig) {
|
|
685
|
-
const compressedFiles = await Promise.all(
|
|
686
|
-
files.map((f) => compressFile(f, platformCompressFn, compressionConfig ?? { enabled: false, imageQuality: 0.85, imageMaxDimension: 1920, compressDocuments: true }))
|
|
687
|
-
);
|
|
688
|
-
const requests = compressedFiles.map((f) => ({
|
|
689
|
-
filename: f.name,
|
|
690
|
-
mimeType: f.type,
|
|
691
|
-
size: f.size,
|
|
692
|
-
conversationId,
|
|
693
|
-
...f.compressed && {
|
|
694
|
-
metadata: {
|
|
695
|
-
compressed: f.compressed,
|
|
696
|
-
originalSize: f.originalSize,
|
|
697
|
-
compressionAlgorithm: f.compressionAlgorithm
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
}));
|
|
701
|
-
const { urls, errors: requestErrors } = await storageApi.requestPresignedUrlBatch(requests);
|
|
702
|
-
const progressMap = {};
|
|
703
|
-
const reportProgress = () => {
|
|
704
|
-
if (!onProgress) return;
|
|
705
|
-
const vals = Object.values(progressMap);
|
|
706
|
-
const avg = vals.reduce((s, v) => s + v, 0) / Math.max(vals.length, 1);
|
|
707
|
-
onProgress(Math.round(avg));
|
|
708
|
-
};
|
|
709
|
-
const successful = [];
|
|
710
|
-
const failed = [...requestErrors];
|
|
711
|
-
await Promise.all(
|
|
712
|
-
urls.map(async (presigned, idx) => {
|
|
713
|
-
const file = compressedFiles[idx];
|
|
714
|
-
progressMap[idx] = 0;
|
|
715
|
-
try {
|
|
716
|
-
await platformUploadFn(presigned, file, (pct) => {
|
|
717
|
-
progressMap[idx] = Math.round(pct * 0.9);
|
|
718
|
-
reportProgress();
|
|
719
|
-
});
|
|
720
|
-
const result = await storageApi.confirmUpload(presigned.fileId);
|
|
721
|
-
progressMap[idx] = 100;
|
|
722
|
-
reportProgress();
|
|
723
|
-
successful.push(result);
|
|
724
|
-
} catch (err) {
|
|
725
|
-
failed.push({ filename: file.name, error: err.message });
|
|
726
|
-
}
|
|
727
|
-
})
|
|
728
|
-
);
|
|
729
|
-
return { successful, failed };
|
|
730
|
-
}
|
|
731
|
-
|
|
732
368
|
// src/api/devices.ts
|
|
733
369
|
var devicesApi = {
|
|
734
370
|
/**
|
|
@@ -749,7 +385,7 @@ var devicesApi = {
|
|
|
749
385
|
* Call this on logout so the user stops receiving push notifications on this device.
|
|
750
386
|
*/
|
|
751
387
|
async remove(deviceId) {
|
|
752
|
-
await getApiClient().
|
|
388
|
+
await getApiClient().post(`/users/me/devices/${deviceId}/remove`);
|
|
753
389
|
}
|
|
754
390
|
};
|
|
755
391
|
|
|
@@ -774,7 +410,7 @@ var usersApi = {
|
|
|
774
410
|
* happened — without waiting for the next 2-hour sync cycle.
|
|
775
411
|
*/
|
|
776
412
|
async updateProfile(payload) {
|
|
777
|
-
const { data } = await getApiClient().
|
|
413
|
+
const { data } = await getApiClient().post("/users/me/update", payload);
|
|
778
414
|
return data;
|
|
779
415
|
},
|
|
780
416
|
/**
|
|
@@ -784,7 +420,7 @@ var usersApi = {
|
|
|
784
420
|
* token is first registered, so this never fails with "not found".
|
|
785
421
|
*/
|
|
786
422
|
async updatePreferences(prefs) {
|
|
787
|
-
const { data } = await getApiClient().
|
|
423
|
+
const { data } = await getApiClient().post("/users/me/preferences", prefs);
|
|
788
424
|
return data;
|
|
789
425
|
},
|
|
790
426
|
/**
|
|
@@ -838,14 +474,14 @@ async function performHandshake(algo, serverKeys, socketHandshakeAuth) {
|
|
|
838
474
|
["deriveBits"]
|
|
839
475
|
);
|
|
840
476
|
const pubRaw = await globalThis.crypto.subtle.exportKey("raw", ephemeral.publicKey);
|
|
841
|
-
socketHandshakeAuth["transitEphemeralPub"] =
|
|
477
|
+
socketHandshakeAuth["transitEphemeralPub"] = bufToB64(pubRaw);
|
|
842
478
|
socketHandshakeAuth["transitAlgo"] = algo;
|
|
843
479
|
const ephemeralPriv = ephemeral.privateKey;
|
|
844
480
|
return (sessionId) => deriveSessionKey(ephemeralPriv, algo, serverKeys, sessionId);
|
|
845
481
|
}
|
|
846
482
|
async function deriveSessionKey(ephemeralPriv, algo, serverKeys, sessionId) {
|
|
847
483
|
const serverPubB64 = algo === "x25519" ? serverKeys.x25519 : serverKeys.p256;
|
|
848
|
-
const serverPubRaw =
|
|
484
|
+
const serverPubRaw = b64ToBuf(serverPubB64);
|
|
849
485
|
const keyAlgoParams = algo === "x25519" ? { name: "X25519" } : { name: "ECDH", namedCurve: "P-256" };
|
|
850
486
|
const serverPubKey = await globalThis.crypto.subtle.importKey(
|
|
851
487
|
"raw",
|
|
@@ -876,7 +512,7 @@ async function deriveSessionKey(ephemeralPriv, algo, serverKeys, sessionId) {
|
|
|
876
512
|
["encrypt", "decrypt"]
|
|
877
513
|
);
|
|
878
514
|
}
|
|
879
|
-
function
|
|
515
|
+
function bufToB64(buf) {
|
|
880
516
|
const bytes = new Uint8Array(buf);
|
|
881
517
|
let str = "";
|
|
882
518
|
bytes.forEach((b) => {
|
|
@@ -884,7 +520,7 @@ function bufToB642(buf) {
|
|
|
884
520
|
});
|
|
885
521
|
return btoa(str);
|
|
886
522
|
}
|
|
887
|
-
function
|
|
523
|
+
function b64ToBuf(b64) {
|
|
888
524
|
const bin = atob(b64);
|
|
889
525
|
const buf = new Uint8Array(bin.length);
|
|
890
526
|
for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
|
|
@@ -893,6 +529,7 @@ function b64ToBuf2(b64) {
|
|
|
893
529
|
|
|
894
530
|
// src/socket/socket.ts
|
|
895
531
|
var _socket = null;
|
|
532
|
+
var _connectingPromise = null;
|
|
896
533
|
var _status = "disconnected";
|
|
897
534
|
var _statusListeners = /* @__PURE__ */ new Set();
|
|
898
535
|
var _getToken = null;
|
|
@@ -966,6 +603,13 @@ function secureOn(socket, event, handler) {
|
|
|
966
603
|
}
|
|
967
604
|
async function connectSocket(config, getToken) {
|
|
968
605
|
if (_socket && !_socket.disconnected) return _socket;
|
|
606
|
+
if (_connectingPromise) return _connectingPromise;
|
|
607
|
+
_connectingPromise = _doConnect(config, getToken).finally(() => {
|
|
608
|
+
_connectingPromise = null;
|
|
609
|
+
});
|
|
610
|
+
return _connectingPromise;
|
|
611
|
+
}
|
|
612
|
+
async function _doConnect(config, getToken) {
|
|
969
613
|
_getToken = getToken;
|
|
970
614
|
_userId = config.userId;
|
|
971
615
|
_tenantId = config.tenantId;
|
|
@@ -1092,6 +736,7 @@ function createSecureSocketProxy(socket) {
|
|
|
1092
736
|
});
|
|
1093
737
|
}
|
|
1094
738
|
function disconnectSocket() {
|
|
739
|
+
_connectingPromise = null;
|
|
1095
740
|
if (_socket) {
|
|
1096
741
|
_socket.disconnect();
|
|
1097
742
|
_socket = null;
|
|
@@ -1210,7 +855,7 @@ var socketEmit = {
|
|
|
1210
855
|
fireAndForget("leave_room", { conversationId });
|
|
1211
856
|
},
|
|
1212
857
|
sendMessage(payload) {
|
|
1213
|
-
return queueSendMessage(payload);
|
|
858
|
+
return queueSendMessage({ ...payload, sentAt: Date.now() });
|
|
1214
859
|
},
|
|
1215
860
|
updateMessage(messageId, text) {
|
|
1216
861
|
return withAck("update_message", { messageId, text });
|