@antzsoft/chat-core 1.1.3 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-3QYXS3IW.js +415 -0
- package/dist/chunk-3QYXS3IW.js.map +1 -0
- package/dist/index.cjs +48 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -619
- package/dist/index.d.ts +3 -619
- package/dist/index.js +47 -396
- package/dist/index.js.map +1 -1
- package/dist/internal-CACK6gGJ.d.cts +624 -0
- package/dist/internal-CACK6gGJ.d.ts +624 -0
- package/dist/internal.cjs +209 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.cts +1 -0
- package/dist/internal.d.ts +1 -0
- package/dist/internal.js +7 -0
- package/dist/internal.js.map +1 -0
- package/docs/integration-guide.html +45 -5
- package/package.json +6 -1
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
// src/compression/compress.ts
|
|
2
|
+
var GZIP_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
3
|
+
"text/plain",
|
|
4
|
+
"text/csv",
|
|
5
|
+
"text/markdown",
|
|
6
|
+
"text/x-markdown",
|
|
7
|
+
"text/xml",
|
|
8
|
+
"application/xml",
|
|
9
|
+
"text/yaml",
|
|
10
|
+
"text/x-yaml",
|
|
11
|
+
"application/x-yaml",
|
|
12
|
+
"application/rtf",
|
|
13
|
+
"text/rtf",
|
|
14
|
+
"application/json",
|
|
15
|
+
"image/svg+xml"
|
|
16
|
+
]);
|
|
17
|
+
var IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
18
|
+
"image/jpeg",
|
|
19
|
+
"image/png",
|
|
20
|
+
"image/gif",
|
|
21
|
+
"image/webp",
|
|
22
|
+
"image/bmp",
|
|
23
|
+
"image/tiff"
|
|
24
|
+
]);
|
|
25
|
+
var SKIP_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
26
|
+
"video/mp4",
|
|
27
|
+
"video/webm",
|
|
28
|
+
"video/quicktime",
|
|
29
|
+
"audio/mpeg",
|
|
30
|
+
"audio/wav",
|
|
31
|
+
"audio/ogg",
|
|
32
|
+
"audio/webm",
|
|
33
|
+
"audio/mp4",
|
|
34
|
+
"application/zip",
|
|
35
|
+
"application/pdf",
|
|
36
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
37
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
38
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
39
|
+
]);
|
|
40
|
+
function getCompressionStrategy(mimeType, config) {
|
|
41
|
+
if (SKIP_MIME_TYPES.has(mimeType)) return "skip";
|
|
42
|
+
if (IMAGE_MIME_TYPES.has(mimeType)) return "image";
|
|
43
|
+
if (config.compressDocuments && GZIP_MIME_TYPES.has(mimeType)) return "gzip";
|
|
44
|
+
return "skip";
|
|
45
|
+
}
|
|
46
|
+
async function compressFile(file, platformCompressFn, config) {
|
|
47
|
+
const noop = {
|
|
48
|
+
...file,
|
|
49
|
+
originalSize: file.size,
|
|
50
|
+
compressed: false,
|
|
51
|
+
compressionAlgorithm: "none"
|
|
52
|
+
};
|
|
53
|
+
if (!config.enabled || !platformCompressFn) return noop;
|
|
54
|
+
const strategy = getCompressionStrategy(file.type, config);
|
|
55
|
+
if (strategy === "skip") return noop;
|
|
56
|
+
try {
|
|
57
|
+
return await platformCompressFn(file, config);
|
|
58
|
+
} catch {
|
|
59
|
+
return noop;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/crypto/session.ts
|
|
64
|
+
var _KEY = /* @__PURE__ */ Symbol.for("__antz_chat_transit__");
|
|
65
|
+
function getState() {
|
|
66
|
+
const g = globalThis;
|
|
67
|
+
if (!g[_KEY]) {
|
|
68
|
+
g[_KEY] = {
|
|
69
|
+
session: null,
|
|
70
|
+
sessionEverEstablished: false,
|
|
71
|
+
readyResolve: null,
|
|
72
|
+
readyPromise: null,
|
|
73
|
+
transitConfigured: null
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return g[_KEY];
|
|
77
|
+
}
|
|
78
|
+
function configureTransit(enabled) {
|
|
79
|
+
const s = getState();
|
|
80
|
+
s.transitConfigured = enabled;
|
|
81
|
+
if (!enabled) {
|
|
82
|
+
s.readyResolve?.();
|
|
83
|
+
s.readyResolve = null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function waitForTransitReady() {
|
|
87
|
+
const s = getState();
|
|
88
|
+
if (!s.transitConfigured) return Promise.resolve();
|
|
89
|
+
if (s.session) return Promise.resolve();
|
|
90
|
+
if (s.sessionEverEstablished) return Promise.resolve();
|
|
91
|
+
if (!s.readyPromise) {
|
|
92
|
+
s.readyPromise = new Promise((resolve) => {
|
|
93
|
+
s.readyResolve = resolve;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return s.readyPromise;
|
|
97
|
+
}
|
|
98
|
+
function setTransitSession(session) {
|
|
99
|
+
const s = getState();
|
|
100
|
+
s.session = session;
|
|
101
|
+
s.sessionEverEstablished = true;
|
|
102
|
+
s.readyResolve?.();
|
|
103
|
+
s.readyResolve = null;
|
|
104
|
+
}
|
|
105
|
+
function clearTransitSession() {
|
|
106
|
+
const s = getState();
|
|
107
|
+
s.session = null;
|
|
108
|
+
s.readyPromise = null;
|
|
109
|
+
s.readyResolve = null;
|
|
110
|
+
}
|
|
111
|
+
function isTransitEnabled() {
|
|
112
|
+
return getState().session?.enabled === true;
|
|
113
|
+
}
|
|
114
|
+
function getSessionKey() {
|
|
115
|
+
return getState().session?.sessionKey ?? null;
|
|
116
|
+
}
|
|
117
|
+
function getSessionId() {
|
|
118
|
+
return getState().session?.sessionId ?? null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/api/client.ts
|
|
122
|
+
import axios from "axios";
|
|
123
|
+
|
|
124
|
+
// src/crypto/transit.ts
|
|
125
|
+
async function encryptPayload(data, sessionKey) {
|
|
126
|
+
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
|
|
127
|
+
const plaintext = new TextEncoder().encode(JSON.stringify(data));
|
|
128
|
+
const encrypted = await globalThis.crypto.subtle.encrypt(
|
|
129
|
+
{ name: "AES-GCM", iv },
|
|
130
|
+
sessionKey,
|
|
131
|
+
plaintext
|
|
132
|
+
);
|
|
133
|
+
const ct = encrypted.slice(0, encrypted.byteLength - 16);
|
|
134
|
+
const tag = encrypted.slice(encrypted.byteLength - 16);
|
|
135
|
+
return {
|
|
136
|
+
v: 1,
|
|
137
|
+
iv: bufToB64(iv),
|
|
138
|
+
tag: bufToB64(tag),
|
|
139
|
+
ct: bufToB64(ct)
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
async function decryptPayload(envelope, sessionKey) {
|
|
143
|
+
const iv = b64ToBuf(envelope.iv);
|
|
144
|
+
const tag = b64ToBuf(envelope.tag);
|
|
145
|
+
const ct = b64ToBuf(envelope.ct);
|
|
146
|
+
const combined = new Uint8Array(ct.byteLength + tag.byteLength);
|
|
147
|
+
combined.set(new Uint8Array(ct), 0);
|
|
148
|
+
combined.set(new Uint8Array(tag), ct.byteLength);
|
|
149
|
+
const decrypted = await globalThis.crypto.subtle.decrypt(
|
|
150
|
+
{ name: "AES-GCM", iv: new Uint8Array(iv) },
|
|
151
|
+
sessionKey,
|
|
152
|
+
combined
|
|
153
|
+
);
|
|
154
|
+
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
155
|
+
}
|
|
156
|
+
function isTransitEnvelope(v) {
|
|
157
|
+
return typeof v === "object" && v !== null && v.v === 1 && typeof v.iv === "string" && typeof v.tag === "string" && typeof v.ct === "string";
|
|
158
|
+
}
|
|
159
|
+
function bufToB64(buf) {
|
|
160
|
+
const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
|
|
161
|
+
let str = "";
|
|
162
|
+
bytes.forEach((b) => {
|
|
163
|
+
str += String.fromCharCode(b);
|
|
164
|
+
});
|
|
165
|
+
return btoa(str);
|
|
166
|
+
}
|
|
167
|
+
function b64ToBuf(b64) {
|
|
168
|
+
const bin = atob(b64);
|
|
169
|
+
const buf = new Uint8Array(bin.length);
|
|
170
|
+
for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
|
|
171
|
+
return buf.buffer;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/api/client.ts
|
|
175
|
+
var _tokenStore = null;
|
|
176
|
+
var _config = null;
|
|
177
|
+
var _avatarSent = false;
|
|
178
|
+
function initApiClient(config, tokenStore) {
|
|
179
|
+
_config = config;
|
|
180
|
+
_tokenStore = tokenStore;
|
|
181
|
+
_avatarSent = false;
|
|
182
|
+
const client = axios.create({
|
|
183
|
+
baseURL: config.apiUrl,
|
|
184
|
+
headers: { "Content-Type": "application/json" }
|
|
185
|
+
});
|
|
186
|
+
configureTransit(config.transitEncryption);
|
|
187
|
+
client.interceptors.request.use(async (req) => {
|
|
188
|
+
const token = _tokenStore?.getAccessToken();
|
|
189
|
+
if (token) req.headers["Authorization"] = `Bearer ${token}`;
|
|
190
|
+
if (_config?.userId) req.headers["x-user-id"] = _config.userId;
|
|
191
|
+
if (_config?.tenantId) req.headers["X-Tenant-ID"] = _config.tenantId;
|
|
192
|
+
if (token && !_avatarSent && _config?.avatar) {
|
|
193
|
+
if (_config.avatar.base64) req.headers["x-avatar-base64"] = _config.avatar.base64;
|
|
194
|
+
else if (_config.avatar.url) req.headers["x-avatar-url"] = _config.avatar.url;
|
|
195
|
+
_avatarSent = true;
|
|
196
|
+
}
|
|
197
|
+
await waitForTransitReady();
|
|
198
|
+
if (isTransitEnabled()) {
|
|
199
|
+
const sessionId = getSessionId();
|
|
200
|
+
const key = getSessionKey();
|
|
201
|
+
if (sessionId && key) {
|
|
202
|
+
req.headers["x-transit-session"] = sessionId;
|
|
203
|
+
if (req.data !== void 0 && req.data !== null) {
|
|
204
|
+
const envelope = await encryptPayload(req.data, key);
|
|
205
|
+
req.data = envelope;
|
|
206
|
+
req.headers["x-transit-encrypted"] = "1";
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return req;
|
|
211
|
+
});
|
|
212
|
+
let isRefreshing = false;
|
|
213
|
+
let refreshQueue = [];
|
|
214
|
+
client.interceptors.response.use(
|
|
215
|
+
async (response) => {
|
|
216
|
+
if (isTransitEnabled()) {
|
|
217
|
+
const key = getSessionKey();
|
|
218
|
+
if (key) {
|
|
219
|
+
if (isTransitEnvelope(response.data)) {
|
|
220
|
+
response.data = await decryptPayload(response.data, key);
|
|
221
|
+
} else if (response.data?.data && isTransitEnvelope(response.data.data)) {
|
|
222
|
+
response.data.data = await decryptPayload(response.data.data, key);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (response.data && typeof response.data === "object" && "success" in response.data && "data" in response.data) {
|
|
227
|
+
response.data = response.data.data;
|
|
228
|
+
}
|
|
229
|
+
return response;
|
|
230
|
+
},
|
|
231
|
+
async (error) => {
|
|
232
|
+
if (isTransitEnabled() && error.response?.data) {
|
|
233
|
+
const key = getSessionKey();
|
|
234
|
+
if (key) {
|
|
235
|
+
try {
|
|
236
|
+
if (isTransitEnvelope(error.response.data)) {
|
|
237
|
+
error.response.data = await decryptPayload(error.response.data, key);
|
|
238
|
+
} else if (error.response.data?.data && isTransitEnvelope(error.response.data.data)) {
|
|
239
|
+
error.response.data.data = await decryptPayload(error.response.data.data, key);
|
|
240
|
+
}
|
|
241
|
+
} catch {
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const original = error.config;
|
|
246
|
+
if (error.response?.status === 401 && !original._retry) {
|
|
247
|
+
const refreshToken = _tokenStore?.getRefreshToken();
|
|
248
|
+
if (!refreshToken) {
|
|
249
|
+
_tokenStore?.clearTokens();
|
|
250
|
+
return Promise.reject(error);
|
|
251
|
+
}
|
|
252
|
+
if (isRefreshing) {
|
|
253
|
+
return new Promise((resolve) => {
|
|
254
|
+
refreshQueue.push((newToken) => {
|
|
255
|
+
original.headers["Authorization"] = `Bearer ${newToken}`;
|
|
256
|
+
resolve(client(original));
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
original._retry = true;
|
|
261
|
+
isRefreshing = true;
|
|
262
|
+
try {
|
|
263
|
+
const { data } = await axios.post(
|
|
264
|
+
`${_config.apiUrl}/auth/refresh`,
|
|
265
|
+
{ refreshToken }
|
|
266
|
+
);
|
|
267
|
+
const tokens = data.data ?? data;
|
|
268
|
+
_tokenStore?.setTokens(tokens);
|
|
269
|
+
refreshQueue.forEach((cb) => cb(tokens.accessToken));
|
|
270
|
+
refreshQueue = [];
|
|
271
|
+
original.headers["Authorization"] = `Bearer ${tokens.accessToken}`;
|
|
272
|
+
return client(original);
|
|
273
|
+
} catch {
|
|
274
|
+
_tokenStore?.clearTokens();
|
|
275
|
+
return Promise.reject(error);
|
|
276
|
+
} finally {
|
|
277
|
+
isRefreshing = false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return Promise.reject(error);
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
return client;
|
|
284
|
+
}
|
|
285
|
+
var _instance = null;
|
|
286
|
+
function setApiClientInstance(instance) {
|
|
287
|
+
_instance = instance;
|
|
288
|
+
}
|
|
289
|
+
function getApiClient() {
|
|
290
|
+
if (!_instance) throw new Error("[AntzChat] API client not initialized. Call initApiClient first.");
|
|
291
|
+
return _instance;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/api/storage.ts
|
|
295
|
+
var storageApi = {
|
|
296
|
+
async requestPresignedUrl(payload) {
|
|
297
|
+
const { data } = await getApiClient().post("/storage/presigned-url", payload);
|
|
298
|
+
return data;
|
|
299
|
+
},
|
|
300
|
+
async requestPresignedUrlBatch(files) {
|
|
301
|
+
const { data } = await getApiClient().post("/storage/presigned-url/batch", { files });
|
|
302
|
+
return data;
|
|
303
|
+
},
|
|
304
|
+
async confirmUpload(fileId) {
|
|
305
|
+
const { data } = await getApiClient().post(`/storage/confirm/${fileId}`);
|
|
306
|
+
return data;
|
|
307
|
+
},
|
|
308
|
+
async getFile(fileId) {
|
|
309
|
+
const { data } = await getApiClient().get(`/storage/files/${fileId}`);
|
|
310
|
+
return data;
|
|
311
|
+
},
|
|
312
|
+
async getFileUrl(fileId, expiresIn) {
|
|
313
|
+
const { data } = await getApiClient().get(`/storage/files/${fileId}/url`, {
|
|
314
|
+
params: expiresIn ? { expiresIn } : {}
|
|
315
|
+
});
|
|
316
|
+
return data;
|
|
317
|
+
},
|
|
318
|
+
async deleteFile(fileId) {
|
|
319
|
+
await getApiClient().delete(`/storage/files/${fileId}`);
|
|
320
|
+
},
|
|
321
|
+
async getConversationFiles(conversationId, params = {}) {
|
|
322
|
+
const { data } = await getApiClient().get(
|
|
323
|
+
`/storage/conversations/${conversationId}/files`,
|
|
324
|
+
{ params }
|
|
325
|
+
);
|
|
326
|
+
return data;
|
|
327
|
+
},
|
|
328
|
+
async getMyFiles(params = {}) {
|
|
329
|
+
const { data } = await getApiClient().get("/storage/my-files", { params });
|
|
330
|
+
return data;
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
async function runUploadBatch(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig) {
|
|
334
|
+
const compressedFiles = await Promise.all(
|
|
335
|
+
files.map((f) => compressFile(f, platformCompressFn, compressionConfig ?? { enabled: false, imageQuality: 0.85, imageMaxDimension: 1920, compressDocuments: true }))
|
|
336
|
+
);
|
|
337
|
+
const slotted = compressedFiles.map((f, i) => ({ file: f, slotId: slotIds[i] }));
|
|
338
|
+
const requests = slotted.map(({ file: f }) => ({
|
|
339
|
+
filename: f.name,
|
|
340
|
+
mimeType: f.type,
|
|
341
|
+
size: f.size,
|
|
342
|
+
conversationId,
|
|
343
|
+
...f.compressed && {
|
|
344
|
+
metadata: {
|
|
345
|
+
compressed: f.compressed,
|
|
346
|
+
originalSize: f.originalSize,
|
|
347
|
+
compressionAlgorithm: f.compressionAlgorithm
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}));
|
|
351
|
+
const { urls, errors: requestErrors } = await storageApi.requestPresignedUrlBatch(requests);
|
|
352
|
+
const failedSlotIds = /* @__PURE__ */ new Set();
|
|
353
|
+
const failed = requestErrors.map((e, i) => {
|
|
354
|
+
const slotId = slotted[urls.length + i]?.slotId ?? slotIds[i];
|
|
355
|
+
failedSlotIds.add(slotId);
|
|
356
|
+
return { filename: e.filename, error: e.error };
|
|
357
|
+
});
|
|
358
|
+
const uploadableSlots = slotted.filter(({ slotId }) => !failedSlotIds.has(slotId));
|
|
359
|
+
const progressMap = {};
|
|
360
|
+
const reportProgress = () => {
|
|
361
|
+
if (!onProgress) return;
|
|
362
|
+
const vals = Object.values(progressMap);
|
|
363
|
+
const avg = vals.reduce((s, v) => s + v, 0) / Math.max(vals.length, 1);
|
|
364
|
+
onProgress(Math.round(avg));
|
|
365
|
+
};
|
|
366
|
+
const successful = [];
|
|
367
|
+
const slotToFile = /* @__PURE__ */ new Map();
|
|
368
|
+
await Promise.all(
|
|
369
|
+
urls.map(async (presigned, idx) => {
|
|
370
|
+
const { file, slotId } = uploadableSlots[idx];
|
|
371
|
+
progressMap[idx] = 0;
|
|
372
|
+
try {
|
|
373
|
+
await platformUploadFn(presigned, file, (pct) => {
|
|
374
|
+
progressMap[idx] = Math.round(pct * 0.9);
|
|
375
|
+
reportProgress();
|
|
376
|
+
});
|
|
377
|
+
const fileResponse = await storageApi.confirmUpload(presigned.fileId);
|
|
378
|
+
progressMap[idx] = 100;
|
|
379
|
+
reportProgress();
|
|
380
|
+
successful.push(fileResponse);
|
|
381
|
+
slotToFile.set(slotId, fileResponse);
|
|
382
|
+
} catch (err) {
|
|
383
|
+
failed.push({ filename: file.name, error: err.message });
|
|
384
|
+
}
|
|
385
|
+
})
|
|
386
|
+
);
|
|
387
|
+
return { result: { successful, failed }, slotToFile };
|
|
388
|
+
}
|
|
389
|
+
async function uploadBatch(files, platformUploadFn, conversationId, onProgress, platformCompressFn, compressionConfig) {
|
|
390
|
+
const slotIds = files.map(() => crypto.randomUUID());
|
|
391
|
+
const { result } = await runUploadBatch(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig);
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
394
|
+
async function uploadBatchWithSlots(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig) {
|
|
395
|
+
return runUploadBatch(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export {
|
|
399
|
+
getCompressionStrategy,
|
|
400
|
+
encryptPayload,
|
|
401
|
+
decryptPayload,
|
|
402
|
+
isTransitEnvelope,
|
|
403
|
+
configureTransit,
|
|
404
|
+
setTransitSession,
|
|
405
|
+
clearTransitSession,
|
|
406
|
+
isTransitEnabled,
|
|
407
|
+
getSessionKey,
|
|
408
|
+
initApiClient,
|
|
409
|
+
setApiClientInstance,
|
|
410
|
+
getApiClient,
|
|
411
|
+
storageApi,
|
|
412
|
+
uploadBatch,
|
|
413
|
+
uploadBatchWithSlots
|
|
414
|
+
};
|
|
415
|
+
//# sourceMappingURL=chunk-3QYXS3IW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/compression/compress.ts","../src/crypto/session.ts","../src/api/client.ts","../src/crypto/transit.ts","../src/api/storage.ts"],"sourcesContent":["import type { UploadableFile, CompressedFile, CompressionAlgorithm } from '../types/index.js';\nimport type { PlatformCompressFn, ResolvedCompressionConfig } from '../config/types.js';\n\n// MIME types that benefit from gzip (text-based, not already compressed)\nconst GZIP_MIME_TYPES = new Set([\n 'text/plain', 'text/csv', 'text/markdown', 'text/x-markdown',\n 'text/xml', 'application/xml', 'text/yaml', 'text/x-yaml',\n 'application/x-yaml', 'application/rtf', 'text/rtf',\n 'application/json', 'image/svg+xml',\n]);\n\nconst IMAGE_MIME_TYPES = new Set([\n 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/bmp', 'image/tiff',\n]);\n\n// Already-compressed formats — no gain from recompressing\nconst SKIP_MIME_TYPES = new Set([\n 'video/mp4', 'video/webm', 'video/quicktime',\n 'audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/webm', 'audio/mp4',\n 'application/zip', 'application/pdf',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n]);\n\nexport type CompressionStrategy = 'image' | 'gzip' | 'skip';\n\nexport function getCompressionStrategy(\n mimeType: string,\n config: ResolvedCompressionConfig,\n): CompressionStrategy {\n if (SKIP_MIME_TYPES.has(mimeType)) return 'skip';\n if (IMAGE_MIME_TYPES.has(mimeType)) return 'image';\n if (config.compressDocuments && GZIP_MIME_TYPES.has(mimeType)) return 'gzip';\n return 'skip';\n}\n\n/**\n * Attempt to compress a file using the platform-provided compressor.\n * Returns the original file unchanged (as a CompressedFile with compressed=false)\n * if compression is disabled, no compressor is provided, or the strategy is 'skip'.\n */\nexport async function compressFile(\n file: UploadableFile,\n platformCompressFn: PlatformCompressFn | undefined,\n config: ResolvedCompressionConfig,\n): Promise<CompressedFile> {\n const noop: CompressedFile = {\n ...file,\n originalSize: file.size,\n compressed: false,\n compressionAlgorithm: 'none' as CompressionAlgorithm,\n };\n\n if (!config.enabled || !platformCompressFn) return noop;\n\n const strategy = getCompressionStrategy(file.type, config);\n if (strategy === 'skip') return noop;\n\n try {\n return await platformCompressFn(file, config);\n } catch {\n // Compression failure is non-fatal — fall back to original\n return noop;\n }\n}\n","import type { TransitAlgo } from './detect.js';\n\ninterface TransitSession {\n sessionKey: CryptoKey;\n algo: TransitAlgo;\n sessionId: string;\n enabled: boolean;\n}\n\n// All state lives on globalThis so Turbopack <locals> module splits and any\n// other bundler that creates multiple instances of this module still share a\n// single source of truth. The symbol key prevents accidental collisions.\nconst _KEY = Symbol.for('__antz_chat_transit__');\n\ninterface TransitState {\n session: TransitSession | null;\n sessionEverEstablished: boolean;\n readyResolve: (() => void) | null;\n readyPromise: Promise<void> | null;\n transitConfigured: boolean | null;\n}\n\nfunction getState(): TransitState {\n const g = globalThis as any;\n if (!g[_KEY]) {\n g[_KEY] = {\n session: null,\n sessionEverEstablished: false,\n readyResolve: null,\n readyPromise: null,\n transitConfigured: null,\n } satisfies TransitState;\n }\n return g[_KEY] as TransitState;\n}\n\nexport function configureTransit(enabled: boolean): void {\n const s = getState();\n s.transitConfigured = enabled;\n if (!enabled) {\n // Transit disabled — resolve immediately so HTTP requests don't block\n s.readyResolve?.();\n s.readyResolve = null;\n }\n}\n\nexport function waitForTransitReady(): Promise<void> {\n const s = getState();\n // Not configured yet or disabled — resolve immediately\n if (!s.transitConfigured) return Promise.resolve();\n // Already have an active session — resolve immediately\n if (s.session) return Promise.resolve();\n // Session was previously established but is now cleared (socket dropped/reconnecting).\n // The encryption block in the interceptor is guarded by isTransitEnabled() and will\n // skip itself with no session, so there is no point blocking here.\n if (s.sessionEverEstablished) return Promise.resolve();\n // First startup — block until the initial ECDH handshake completes.\n if (!s.readyPromise) {\n s.readyPromise = new Promise<void>((resolve) => {\n s.readyResolve = resolve;\n });\n }\n return s.readyPromise;\n}\n\nexport function setTransitSession(session: TransitSession): void {\n const s = getState();\n s.session = session;\n s.sessionEverEstablished = true;\n // Resolve any pending HTTP requests waiting for the session key\n s.readyResolve?.();\n s.readyResolve = null;\n}\n\nexport function getTransitSession(): TransitSession | null {\n return getState().session;\n}\n\nexport function clearTransitSession(): void {\n const s = getState();\n s.session = null;\n // Reset the ready promise so it can be recreated on next waitForTransitReady call.\n // sessionEverEstablished intentionally left true — HTTP requests fired while\n // reconnecting should not block (isTransitEnabled() guards encryption anyway).\n s.readyPromise = null;\n s.readyResolve = null;\n}\n\nexport function isTransitEnabled(): boolean {\n return getState().session?.enabled === true;\n}\n\nexport function getSessionKey(): CryptoKey | null {\n return getState().session?.sessionKey ?? null;\n}\n\n// Returns sessionId for the x-transit-session header sent with HTTP requests.\nexport function getSessionId(): string | null {\n return getState().session?.sessionId ?? null;\n}\n","import axios, {\n AxiosInstance,\n InternalAxiosRequestConfig,\n} from 'axios';\nimport type { ResolvedConfig } from '../config/types.js';\nimport type { AuthTokens } from '../types/index.js';\nimport { encryptPayload, decryptPayload, isTransitEnvelope } from '../crypto/transit.js';\nimport { getSessionKey, getSessionId, isTransitEnabled, waitForTransitReady, configureTransit } from '../crypto/session.js';\n\nexport type TokenStore = {\n getAccessToken: () => string | null | undefined;\n getRefreshToken: () => string | null | undefined;\n setTokens: (tokens: AuthTokens) => void;\n clearTokens: () => void;\n};\n\nlet _tokenStore: TokenStore | null = null;\nlet _config: ResolvedConfig | null = null;\n// Avatar headers are sent only on the first authenticated request after init.\n// The server hashes on receive and deduplicates — subsequent requests don't need them.\nlet _avatarSent = false;\n\nexport function initApiClient(config: ResolvedConfig, tokenStore: TokenStore): AxiosInstance {\n _config = config;\n _tokenStore = tokenStore;\n _avatarSent = false; // reset on re-init (new session / authToken change)\n\n const client = axios.create({\n baseURL: config.apiUrl,\n headers: { 'Content-Type': 'application/json' },\n });\n\n // Configure transit as early as possible — before any requests fire —\n // so waitForTransitReady() in the interceptor knows whether to block or not.\n configureTransit(config.transitEncryption);\n\n // ── Request interceptor ──────────────────────────────────────────────────\n client.interceptors.request.use(async (req: InternalAxiosRequestConfig) => {\n const token = _tokenStore?.getAccessToken();\n if (token) req.headers['Authorization'] = `Bearer ${token}`;\n if (_config?.userId) req.headers['x-user-id'] = _config.userId;\n if (_config?.tenantId) req.headers['X-Tenant-ID'] = _config.tenantId;\n // Send avatar on the first request only — server hashes and deduplicates\n if (token && !_avatarSent && _config?.avatar) {\n if (_config.avatar.base64) req.headers['x-avatar-base64'] = _config.avatar.base64;\n else if (_config.avatar.url) req.headers['x-avatar-url'] = _config.avatar.url;\n _avatarSent = true;\n }\n\n // Wait for the transit session key to be established before sending.\n // HTTP requests that fire before connectSocket completes (e.g. getMe, getConfig)\n // are held here until the ECDH handshake finishes. Resolves immediately when\n // transit is disabled or already established.\n await waitForTransitReady();\n\n // Transit encryption — attach session ID on every request so the server\n // can look up the session key. Encrypt body when present.\n if (isTransitEnabled()) {\n const sessionId = getSessionId();\n const key = getSessionKey();\n if (sessionId && key) {\n req.headers['x-transit-session'] = sessionId;\n if (req.data !== undefined && req.data !== null) {\n const envelope = await encryptPayload(req.data, key);\n req.data = envelope;\n req.headers['x-transit-encrypted'] = '1';\n }\n }\n }\n\n return req;\n });\n\n let isRefreshing = false;\n let refreshQueue: Array<(token: string) => void> = [];\n\n // ── Response interceptor ─────────────────────────────────────────────────\n client.interceptors.response.use(\n async (response) => {\n // Transit decryption — server wraps encrypted payload inside { success, data: <envelope> }.\n // Decrypt the inner envelope, then let the standard unwrap below handle { success, data }.\n if (isTransitEnabled()) {\n const key = getSessionKey();\n if (key) {\n // Case 1: entire response is the envelope (unlikely but handle it)\n if (isTransitEnvelope(response.data)) {\n response.data = await decryptPayload(response.data, key);\n }\n // Case 2: envelope is nested inside { success, data: <envelope> }\n else if (response.data?.data && isTransitEnvelope(response.data.data)) {\n response.data.data = await decryptPayload(response.data.data, key);\n }\n }\n }\n\n // Standard { success, data } unwrap\n if (\n response.data &&\n typeof response.data === 'object' &&\n 'success' in response.data &&\n 'data' in response.data\n ) {\n response.data = response.data.data;\n }\n return response;\n },\n async (error) => {\n // Decrypt error response body — error filter encrypts it too\n if (isTransitEnabled() && error.response?.data) {\n const key = getSessionKey();\n if (key) {\n try {\n if (isTransitEnvelope(error.response.data)) {\n error.response.data = await decryptPayload(error.response.data, key);\n } else if (error.response.data?.data && isTransitEnvelope(error.response.data.data)) {\n error.response.data.data = await decryptPayload(error.response.data.data, key);\n }\n } catch { /* decryption failed — leave as-is */ }\n }\n }\n\n const original = error.config as InternalAxiosRequestConfig & { _retry?: boolean };\n\n if (error.response?.status === 401 && !original._retry) {\n const refreshToken = _tokenStore?.getRefreshToken();\n if (!refreshToken) {\n _tokenStore?.clearTokens();\n return Promise.reject(error);\n }\n\n if (isRefreshing) {\n return new Promise((resolve) => {\n refreshQueue.push((newToken) => {\n original.headers['Authorization'] = `Bearer ${newToken}`;\n resolve(client(original));\n });\n });\n }\n\n original._retry = true;\n isRefreshing = true;\n\n try {\n const { data } = await axios.post<{ data: AuthTokens }>(\n `${_config!.apiUrl}/auth/refresh`,\n { refreshToken },\n );\n const tokens: AuthTokens = (data as any).data ?? data;\n _tokenStore?.setTokens(tokens);\n refreshQueue.forEach((cb) => cb(tokens.accessToken));\n refreshQueue = [];\n original.headers['Authorization'] = `Bearer ${tokens.accessToken}`;\n return client(original);\n } catch {\n _tokenStore?.clearTokens();\n return Promise.reject(error);\n } finally {\n isRefreshing = false;\n }\n }\n\n return Promise.reject(error);\n },\n );\n\n return client;\n}\n\nlet _instance: AxiosInstance | null = null;\n\nexport function setApiClientInstance(instance: AxiosInstance) {\n _instance = instance;\n}\n\nexport function getApiClient(): AxiosInstance {\n if (!_instance) throw new Error('[AntzChat] API client not initialized. Call initApiClient first.');\n return _instance;\n}\n","export interface TransitEnvelope {\n v: 1;\n iv: string; // base64, 12 bytes\n tag: string; // base64, 16 bytes\n ct: string; // base64, ciphertext\n}\n\n// ─── Encrypt ─────────────────────────────────────────────────────────────────\n\nexport async function encryptPayload(\n data: unknown,\n sessionKey: CryptoKey,\n): Promise<TransitEnvelope> {\n const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));\n const plaintext = new TextEncoder().encode(JSON.stringify(data));\n\n const encrypted = await globalThis.crypto.subtle.encrypt(\n { name: 'AES-GCM', iv },\n sessionKey,\n plaintext,\n );\n\n // AES-GCM appends the 16-byte auth tag to the ciphertext buffer\n const ct = encrypted.slice(0, encrypted.byteLength - 16);\n const tag = encrypted.slice(encrypted.byteLength - 16);\n\n return {\n v: 1,\n iv: bufToB64(iv),\n tag: bufToB64(tag),\n ct: bufToB64(ct),\n };\n}\n\n// ─── Decrypt ─────────────────────────────────────────────────────────────────\n\nexport async function decryptPayload(\n envelope: TransitEnvelope,\n sessionKey: CryptoKey,\n): Promise<unknown> {\n const iv = b64ToBuf(envelope.iv);\n const tag = b64ToBuf(envelope.tag);\n const ct = b64ToBuf(envelope.ct);\n\n // Reassemble ct + tag as SubtleCrypto expects them concatenated\n const combined = new Uint8Array(ct.byteLength + tag.byteLength);\n combined.set(new Uint8Array(ct), 0);\n combined.set(new Uint8Array(tag), ct.byteLength);\n\n const decrypted = await globalThis.crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: new Uint8Array(iv) },\n sessionKey,\n combined,\n );\n\n return JSON.parse(new TextDecoder().decode(decrypted));\n}\n\nexport function isTransitEnvelope(v: unknown): v is TransitEnvelope {\n return (\n typeof v === 'object' &&\n v !== null &&\n (v as any).v === 1 &&\n typeof (v as any).iv === 'string' &&\n typeof (v as any).tag === 'string' &&\n typeof (v as any).ct === 'string'\n );\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction bufToB64(buf: ArrayBuffer | Uint8Array): string {\n const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);\n let str = '';\n bytes.forEach(b => { str += String.fromCharCode(b); });\n return btoa(str);\n}\n\nfunction b64ToBuf(b64: string): ArrayBuffer {\n const bin = atob(b64);\n const buf = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);\n return buf.buffer;\n}\n","import type {\n BatchUploadResult,\n FileResponse,\n PaginatedResponse,\n PresignedUrlRequest,\n PresignedUrlResponse,\n FileType,\n UploadableFile,\n} from '../types/index.js';\nimport type { PlatformUploadFn, PlatformCompressFn, ResolvedCompressionConfig } from '../config/types.js';\nimport { compressFile } from '../compression/compress.js';\nimport { getApiClient } from './client.js';\n\nexport const storageApi = {\n async requestPresignedUrl(payload: PresignedUrlRequest): Promise<PresignedUrlResponse> {\n const { data } = await getApiClient().post<PresignedUrlResponse>('/storage/presigned-url', payload);\n return data;\n },\n\n async requestPresignedUrlBatch(files: PresignedUrlRequest[]): Promise<{\n urls: PresignedUrlResponse[];\n errors: Array<{ filename: string; error: string }>;\n }> {\n const { data } = await getApiClient().post('/storage/presigned-url/batch', { files });\n return data;\n },\n\n async confirmUpload(fileId: string): Promise<FileResponse> {\n const { data } = await getApiClient().post<FileResponse>(`/storage/confirm/${fileId}`);\n return data;\n },\n\n async getFile(fileId: string): Promise<FileResponse> {\n const { data } = await getApiClient().get<FileResponse>(`/storage/files/${fileId}`);\n return data;\n },\n\n async getFileUrl(fileId: string, expiresIn?: number): Promise<{ url: string; expiresAt: string }> {\n const { data } = await getApiClient().get(`/storage/files/${fileId}/url`, {\n params: expiresIn ? { expiresIn } : {},\n });\n return data;\n },\n\n async deleteFile(fileId: string): Promise<void> {\n await getApiClient().delete(`/storage/files/${fileId}`);\n },\n\n async getConversationFiles(\n conversationId: string,\n params: { page?: number; limit?: number; type?: FileType } = {},\n ): Promise<PaginatedResponse<FileResponse>> {\n const { data } = await getApiClient().get(\n `/storage/conversations/${conversationId}/files`,\n { params },\n );\n return data;\n },\n\n async getMyFiles(params: { page?: number; limit?: number } = {}): Promise<PaginatedResponse<FileResponse>> {\n const { data } = await getApiClient().get('/storage/my-files', { params });\n return data;\n },\n};\n\n/**\n * High-level batch upload.\n * The actual binary transfer is delegated to platformUploadFn so this\n * function is platform-agnostic (works on web and React Native).\n * If platformCompressFn + compressionConfig are provided, each file is\n * compressed before the presigned URL is requested (so the server receives\n * the correct compressed size and MIME type).\n */\n/**\n * Core upload implementation. Returns the public BatchUploadResult plus a\n * slotId → FileResponse map that useChat hooks use internally to match\n * confirmed uploads back to optimistic UI slots by position rather than\n * filename. The slotToFile map is never part of the public API.\n */\nasync function runUploadBatch(\n files: UploadableFile[],\n platformUploadFn: PlatformUploadFn,\n slotIds: string[],\n conversationId?: string,\n onProgress?: (pct: number) => void,\n platformCompressFn?: PlatformCompressFn,\n compressionConfig?: ResolvedCompressionConfig,\n): Promise<{ result: BatchUploadResult; slotToFile: Map<string, FileResponse> }> {\n // Compress all files first (no-ops for unsupported types or when disabled)\n const compressedFiles = await Promise.all(\n files.map((f) => compressFile(f, platformCompressFn, compressionConfig ?? { enabled: false, imageQuality: 0.85, imageMaxDimension: 1920, compressDocuments: true })),\n );\n\n // Pair each compressed file with its slot ID for position-safe tracking.\n const slotted = compressedFiles.map((f, i) => ({ file: f, slotId: slotIds[i] }));\n\n const requests: PresignedUrlRequest[] = slotted.map(({ file: f }) => ({\n filename: f.name,\n mimeType: f.type,\n size: f.size,\n conversationId,\n ...(f.compressed && {\n metadata: {\n compressed: f.compressed,\n originalSize: f.originalSize,\n compressionAlgorithm: f.compressionAlgorithm,\n },\n }),\n }));\n\n const { urls, errors: requestErrors } = await storageApi.requestPresignedUrlBatch(requests);\n\n // The server returns a *shorter* urls array when some files fail presigned-URL\n // generation — it skips failures rather than leaving gaps. Build the failed set\n // by slot index (not filename) so same-named files are never misidentified.\n const failedSlotIds = new Set<string>();\n const failed: Array<{ filename: string; error: string }> = requestErrors.map((e, i) => {\n const slotId = slotted[urls.length + i]?.slotId ?? slotIds[i];\n failedSlotIds.add(slotId);\n return { filename: e.filename, error: e.error };\n });\n\n const uploadableSlots = slotted.filter(({ slotId }) => !failedSlotIds.has(slotId));\n\n const progressMap: Record<number, number> = {};\n const reportProgress = () => {\n if (!onProgress) return;\n const vals = Object.values(progressMap);\n const avg = vals.reduce((s, v) => s + v, 0) / Math.max(vals.length, 1);\n onProgress(Math.round(avg));\n };\n\n const successful: FileResponse[] = [];\n const slotToFile = new Map<string, FileResponse>();\n\n await Promise.all(\n urls.map(async (presigned, idx) => {\n const { file, slotId } = uploadableSlots[idx];\n progressMap[idx] = 0;\n try {\n await platformUploadFn(presigned, file, (pct) => {\n progressMap[idx] = Math.round(pct * 0.9);\n reportProgress();\n });\n\n const fileResponse = await storageApi.confirmUpload(presigned.fileId);\n progressMap[idx] = 100;\n reportProgress();\n successful.push(fileResponse);\n slotToFile.set(slotId, fileResponse);\n } catch (err) {\n failed.push({ filename: file.name, error: (err as Error).message });\n }\n }),\n );\n\n return { result: { successful, failed }, slotToFile };\n}\n\n/** Public API — returns standard BatchUploadResult, slot tracking is internal. */\nexport async function uploadBatch(\n files: UploadableFile[],\n platformUploadFn: PlatformUploadFn,\n conversationId?: string,\n onProgress?: (pct: number) => void,\n platformCompressFn?: PlatformCompressFn,\n compressionConfig?: ResolvedCompressionConfig,\n): Promise<BatchUploadResult> {\n const slotIds = files.map(() => crypto.randomUUID());\n const { result } = await runUploadBatch(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig);\n return result;\n}\n\n/**\n * Used only by useChat hooks (web + RN) to get the slotId → FileResponse map\n * for matching confirmed uploads back to optimistic UI slots.\n * Not exported from the package index — internal SDK use only.\n */\nexport async function uploadBatchWithSlots(\n files: UploadableFile[],\n platformUploadFn: PlatformUploadFn,\n slotIds: string[],\n conversationId?: string,\n onProgress?: (pct: number) => void,\n platformCompressFn?: PlatformCompressFn,\n compressionConfig?: ResolvedCompressionConfig,\n): Promise<{ result: BatchUploadResult; slotToFile: Map<string, FileResponse> }> {\n return runUploadBatch(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig);\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -648,7 +648,8 @@ function normalizeLastMessage(lastMsg) {
|
|
|
648
648
|
status: lastMsg.status ?? "sent",
|
|
649
649
|
isEdited: false,
|
|
650
650
|
sentAt: lastMsg.sentAt ?? "",
|
|
651
|
-
createdAt: lastMsg.sentAt ?? ""
|
|
651
|
+
createdAt: lastMsg.sentAt ?? "",
|
|
652
|
+
...lastMsg.senderName && { senderName: lastMsg.senderName }
|
|
652
653
|
};
|
|
653
654
|
}
|
|
654
655
|
function normalizeConversation(conv) {
|
|
@@ -805,11 +806,12 @@ var storageApi = {
|
|
|
805
806
|
return data;
|
|
806
807
|
}
|
|
807
808
|
};
|
|
808
|
-
async function
|
|
809
|
+
async function runUploadBatch(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig) {
|
|
809
810
|
const compressedFiles = await Promise.all(
|
|
810
811
|
files.map((f) => compressFile(f, platformCompressFn, compressionConfig ?? { enabled: false, imageQuality: 0.85, imageMaxDimension: 1920, compressDocuments: true }))
|
|
811
812
|
);
|
|
812
|
-
const
|
|
813
|
+
const slotted = compressedFiles.map((f, i) => ({ file: f, slotId: slotIds[i] }));
|
|
814
|
+
const requests = slotted.map(({ file: f }) => ({
|
|
813
815
|
filename: f.name,
|
|
814
816
|
mimeType: f.type,
|
|
815
817
|
size: f.size,
|
|
@@ -823,6 +825,13 @@ async function uploadBatch(files, platformUploadFn, conversationId, onProgress,
|
|
|
823
825
|
}
|
|
824
826
|
}));
|
|
825
827
|
const { urls, errors: requestErrors } = await storageApi.requestPresignedUrlBatch(requests);
|
|
828
|
+
const failedSlotIds = /* @__PURE__ */ new Set();
|
|
829
|
+
const failed = requestErrors.map((e, i) => {
|
|
830
|
+
const slotId = slotted[urls.length + i]?.slotId ?? slotIds[i];
|
|
831
|
+
failedSlotIds.add(slotId);
|
|
832
|
+
return { filename: e.filename, error: e.error };
|
|
833
|
+
});
|
|
834
|
+
const uploadableSlots = slotted.filter(({ slotId }) => !failedSlotIds.has(slotId));
|
|
826
835
|
const progressMap = {};
|
|
827
836
|
const reportProgress = () => {
|
|
828
837
|
if (!onProgress) return;
|
|
@@ -831,26 +840,32 @@ async function uploadBatch(files, platformUploadFn, conversationId, onProgress,
|
|
|
831
840
|
onProgress(Math.round(avg));
|
|
832
841
|
};
|
|
833
842
|
const successful = [];
|
|
834
|
-
const
|
|
843
|
+
const slotToFile = /* @__PURE__ */ new Map();
|
|
835
844
|
await Promise.all(
|
|
836
845
|
urls.map(async (presigned, idx) => {
|
|
837
|
-
const file =
|
|
846
|
+
const { file, slotId } = uploadableSlots[idx];
|
|
838
847
|
progressMap[idx] = 0;
|
|
839
848
|
try {
|
|
840
849
|
await platformUploadFn(presigned, file, (pct) => {
|
|
841
850
|
progressMap[idx] = Math.round(pct * 0.9);
|
|
842
851
|
reportProgress();
|
|
843
852
|
});
|
|
844
|
-
const
|
|
853
|
+
const fileResponse = await storageApi.confirmUpload(presigned.fileId);
|
|
845
854
|
progressMap[idx] = 100;
|
|
846
855
|
reportProgress();
|
|
847
|
-
successful.push(
|
|
856
|
+
successful.push(fileResponse);
|
|
857
|
+
slotToFile.set(slotId, fileResponse);
|
|
848
858
|
} catch (err) {
|
|
849
859
|
failed.push({ filename: file.name, error: err.message });
|
|
850
860
|
}
|
|
851
861
|
})
|
|
852
862
|
);
|
|
853
|
-
return { successful, failed };
|
|
863
|
+
return { result: { successful, failed }, slotToFile };
|
|
864
|
+
}
|
|
865
|
+
async function uploadBatch(files, platformUploadFn, conversationId, onProgress, platformCompressFn, compressionConfig) {
|
|
866
|
+
const slotIds = files.map(() => crypto.randomUUID());
|
|
867
|
+
const { result } = await runUploadBatch(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig);
|
|
868
|
+
return result;
|
|
854
869
|
}
|
|
855
870
|
|
|
856
871
|
// src/api/devices.ts
|
|
@@ -1017,6 +1032,7 @@ function b64ToBuf2(b64) {
|
|
|
1017
1032
|
|
|
1018
1033
|
// src/socket/socket.ts
|
|
1019
1034
|
var _socket = null;
|
|
1035
|
+
var _connectingPromise = null;
|
|
1020
1036
|
var _status = "disconnected";
|
|
1021
1037
|
var _statusListeners = /* @__PURE__ */ new Set();
|
|
1022
1038
|
var _getToken = null;
|
|
@@ -1090,6 +1106,13 @@ function secureOn(socket, event, handler) {
|
|
|
1090
1106
|
}
|
|
1091
1107
|
async function connectSocket(config, getToken) {
|
|
1092
1108
|
if (_socket && !_socket.disconnected) return _socket;
|
|
1109
|
+
if (_connectingPromise) return _connectingPromise;
|
|
1110
|
+
_connectingPromise = _doConnect(config, getToken).finally(() => {
|
|
1111
|
+
_connectingPromise = null;
|
|
1112
|
+
});
|
|
1113
|
+
return _connectingPromise;
|
|
1114
|
+
}
|
|
1115
|
+
async function _doConnect(config, getToken) {
|
|
1093
1116
|
_getToken = getToken;
|
|
1094
1117
|
_userId = config.userId;
|
|
1095
1118
|
_tenantId = config.tenantId;
|
|
@@ -1216,6 +1239,7 @@ function createSecureSocketProxy(socket) {
|
|
|
1216
1239
|
});
|
|
1217
1240
|
}
|
|
1218
1241
|
function disconnectSocket() {
|
|
1242
|
+
_connectingPromise = null;
|
|
1219
1243
|
if (_socket) {
|
|
1220
1244
|
_socket.disconnect();
|
|
1221
1245
|
_socket = null;
|
|
@@ -1254,13 +1278,14 @@ var ACK_TIMEOUT = 5e3;
|
|
|
1254
1278
|
var RECONNECT_WAIT_TIMEOUT = 15e3;
|
|
1255
1279
|
var QUEUE_MAX_SIZE = 100;
|
|
1256
1280
|
var QUEUE_ENTRY_TTL = 3e4;
|
|
1257
|
-
var
|
|
1258
|
-
var sendQueueRunning =
|
|
1259
|
-
async function drainSendQueue() {
|
|
1260
|
-
if (sendQueueRunning) return;
|
|
1261
|
-
sendQueueRunning
|
|
1262
|
-
|
|
1263
|
-
|
|
1281
|
+
var sendQueues = /* @__PURE__ */ new Map();
|
|
1282
|
+
var sendQueueRunning = /* @__PURE__ */ new Map();
|
|
1283
|
+
async function drainSendQueue(conversationId) {
|
|
1284
|
+
if (sendQueueRunning.get(conversationId)) return;
|
|
1285
|
+
sendQueueRunning.set(conversationId, true);
|
|
1286
|
+
const queue = sendQueues.get(conversationId);
|
|
1287
|
+
while (queue.length > 0) {
|
|
1288
|
+
const entry = queue.shift();
|
|
1264
1289
|
if (Date.now() - entry.enqueuedAt > QUEUE_ENTRY_TTL) {
|
|
1265
1290
|
entry.reject(new Error("[AntzChat] Message dropped: queued too long"));
|
|
1266
1291
|
continue;
|
|
@@ -1271,15 +1296,19 @@ async function drainSendQueue() {
|
|
|
1271
1296
|
entry.reject(e);
|
|
1272
1297
|
}
|
|
1273
1298
|
}
|
|
1274
|
-
|
|
1299
|
+
sendQueues.delete(conversationId);
|
|
1300
|
+
sendQueueRunning.delete(conversationId);
|
|
1275
1301
|
}
|
|
1276
1302
|
function queueSendMessage(payload) {
|
|
1277
|
-
|
|
1303
|
+
const conversationId = payload.conversationId;
|
|
1304
|
+
if (!sendQueues.has(conversationId)) sendQueues.set(conversationId, []);
|
|
1305
|
+
const queue = sendQueues.get(conversationId);
|
|
1306
|
+
if (queue.length >= QUEUE_MAX_SIZE) {
|
|
1278
1307
|
return Promise.reject(new Error("[AntzChat] Send queue full: too many messages in flight"));
|
|
1279
1308
|
}
|
|
1280
1309
|
return new Promise((resolve, reject) => {
|
|
1281
|
-
|
|
1282
|
-
drainSendQueue();
|
|
1310
|
+
queue.push({ run: () => withAck("send_message", payload), resolve, reject, enqueuedAt: Date.now() });
|
|
1311
|
+
drainSendQueue(conversationId);
|
|
1283
1312
|
});
|
|
1284
1313
|
}
|
|
1285
1314
|
function waitForReconnect() {
|