@antzsoft/chat-core 1.1.4 → 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/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-3QYXS3IW.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) {
@@ -524,7 +247,8 @@ function normalizeLastMessage(lastMsg) {
524
247
  status: lastMsg.status ?? "sent",
525
248
  isEdited: false,
526
249
  sentAt: lastMsg.sentAt ?? "",
527
- createdAt: lastMsg.sentAt ?? ""
250
+ createdAt: lastMsg.sentAt ?? "",
251
+ ...lastMsg.senderName && { senderName: lastMsg.senderName }
528
252
  };
529
253
  }
530
254
  function normalizeConversation(conv) {
@@ -642,93 +366,6 @@ var conversationsApi = {
642
366
  }
643
367
  };
644
368
 
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
369
  // src/api/devices.ts
733
370
  var devicesApi = {
734
371
  /**
@@ -838,14 +475,14 @@ async function performHandshake(algo, serverKeys, socketHandshakeAuth) {
838
475
  ["deriveBits"]
839
476
  );
840
477
  const pubRaw = await globalThis.crypto.subtle.exportKey("raw", ephemeral.publicKey);
841
- socketHandshakeAuth["transitEphemeralPub"] = bufToB642(pubRaw);
478
+ socketHandshakeAuth["transitEphemeralPub"] = bufToB64(pubRaw);
842
479
  socketHandshakeAuth["transitAlgo"] = algo;
843
480
  const ephemeralPriv = ephemeral.privateKey;
844
481
  return (sessionId) => deriveSessionKey(ephemeralPriv, algo, serverKeys, sessionId);
845
482
  }
846
483
  async function deriveSessionKey(ephemeralPriv, algo, serverKeys, sessionId) {
847
484
  const serverPubB64 = algo === "x25519" ? serverKeys.x25519 : serverKeys.p256;
848
- const serverPubRaw = b64ToBuf2(serverPubB64);
485
+ const serverPubRaw = b64ToBuf(serverPubB64);
849
486
  const keyAlgoParams = algo === "x25519" ? { name: "X25519" } : { name: "ECDH", namedCurve: "P-256" };
850
487
  const serverPubKey = await globalThis.crypto.subtle.importKey(
851
488
  "raw",
@@ -876,7 +513,7 @@ async function deriveSessionKey(ephemeralPriv, algo, serverKeys, sessionId) {
876
513
  ["encrypt", "decrypt"]
877
514
  );
878
515
  }
879
- function bufToB642(buf) {
516
+ function bufToB64(buf) {
880
517
  const bytes = new Uint8Array(buf);
881
518
  let str = "";
882
519
  bytes.forEach((b) => {
@@ -884,7 +521,7 @@ function bufToB642(buf) {
884
521
  });
885
522
  return btoa(str);
886
523
  }
887
- function b64ToBuf2(b64) {
524
+ function b64ToBuf(b64) {
888
525
  const bin = atob(b64);
889
526
  const buf = new Uint8Array(bin.length);
890
527
  for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
@@ -893,6 +530,7 @@ function b64ToBuf2(b64) {
893
530
 
894
531
  // src/socket/socket.ts
895
532
  var _socket = null;
533
+ var _connectingPromise = null;
896
534
  var _status = "disconnected";
897
535
  var _statusListeners = /* @__PURE__ */ new Set();
898
536
  var _getToken = null;
@@ -966,6 +604,13 @@ function secureOn(socket, event, handler) {
966
604
  }
967
605
  async function connectSocket(config, getToken) {
968
606
  if (_socket && !_socket.disconnected) return _socket;
607
+ if (_connectingPromise) return _connectingPromise;
608
+ _connectingPromise = _doConnect(config, getToken).finally(() => {
609
+ _connectingPromise = null;
610
+ });
611
+ return _connectingPromise;
612
+ }
613
+ async function _doConnect(config, getToken) {
969
614
  _getToken = getToken;
970
615
  _userId = config.userId;
971
616
  _tenantId = config.tenantId;
@@ -1092,6 +737,7 @@ function createSecureSocketProxy(socket) {
1092
737
  });
1093
738
  }
1094
739
  function disconnectSocket() {
740
+ _connectingPromise = null;
1095
741
  if (_socket) {
1096
742
  _socket.disconnect();
1097
743
  _socket = null;