@antzsoft/chat-core 1.1.0 → 1.1.2
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 +55 -20
- package/dist/index.cjs +381 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -31
- package/dist/index.d.ts +37 -31
- package/dist/index.js +380 -29
- package/dist/index.js.map +1 -1
- package/docs/integration-guide.html +222 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ function resolveConfig(config) {
|
|
|
38
38
|
userId: config.userId,
|
|
39
39
|
tenantId: config.tenantId,
|
|
40
40
|
avatar: config.avatar,
|
|
41
|
-
|
|
41
|
+
transitEncryption: config.transitEncryption ?? true,
|
|
42
42
|
upload: {
|
|
43
43
|
maxFileSizeMB: limits,
|
|
44
44
|
maxFilesPerMessage: config.upload?.maxFilesPerMessage ?? 10,
|
|
@@ -125,6 +125,100 @@ async function compressFile(file, platformCompressFn, config) {
|
|
|
125
125
|
|
|
126
126
|
// src/api/client.ts
|
|
127
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 _session = null;
|
|
181
|
+
var _readyResolve = null;
|
|
182
|
+
var _readyPromise = null;
|
|
183
|
+
var _transitConfigured = null;
|
|
184
|
+
function configureTransit(enabled) {
|
|
185
|
+
_transitConfigured = enabled;
|
|
186
|
+
if (!enabled) {
|
|
187
|
+
_readyResolve?.();
|
|
188
|
+
_readyResolve = null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function waitForTransitReady() {
|
|
192
|
+
if (!_transitConfigured) return Promise.resolve();
|
|
193
|
+
if (_session) return Promise.resolve();
|
|
194
|
+
if (!_readyPromise) {
|
|
195
|
+
_readyPromise = new Promise((resolve) => {
|
|
196
|
+
_readyResolve = resolve;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return _readyPromise;
|
|
200
|
+
}
|
|
201
|
+
function setTransitSession(session) {
|
|
202
|
+
_session = session;
|
|
203
|
+
_readyResolve?.();
|
|
204
|
+
_readyResolve = null;
|
|
205
|
+
}
|
|
206
|
+
function clearTransitSession() {
|
|
207
|
+
_session = null;
|
|
208
|
+
_readyPromise = null;
|
|
209
|
+
_readyResolve = null;
|
|
210
|
+
}
|
|
211
|
+
function isTransitEnabled() {
|
|
212
|
+
return _session?.enabled === true;
|
|
213
|
+
}
|
|
214
|
+
function getSessionKey() {
|
|
215
|
+
return _session?.sessionKey ?? null;
|
|
216
|
+
}
|
|
217
|
+
function getSessionId() {
|
|
218
|
+
return _session?.sessionId ?? null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/api/client.ts
|
|
128
222
|
var _tokenStore = null;
|
|
129
223
|
var _config = null;
|
|
130
224
|
var _avatarSent = false;
|
|
@@ -136,7 +230,8 @@ function initApiClient(config, tokenStore) {
|
|
|
136
230
|
baseURL: config.apiUrl,
|
|
137
231
|
headers: { "Content-Type": "application/json" }
|
|
138
232
|
});
|
|
139
|
-
|
|
233
|
+
configureTransit(config.transitEncryption);
|
|
234
|
+
client.interceptors.request.use(async (req) => {
|
|
140
235
|
const token = _tokenStore?.getAccessToken();
|
|
141
236
|
if (token) req.headers["Authorization"] = `Bearer ${token}`;
|
|
142
237
|
if (_config?.userId) req.headers["x-user-id"] = _config.userId;
|
|
@@ -146,18 +241,54 @@ function initApiClient(config, tokenStore) {
|
|
|
146
241
|
else if (_config.avatar.url) req.headers["x-avatar-url"] = _config.avatar.url;
|
|
147
242
|
_avatarSent = true;
|
|
148
243
|
}
|
|
244
|
+
await waitForTransitReady();
|
|
245
|
+
if (isTransitEnabled()) {
|
|
246
|
+
const sessionId = getSessionId();
|
|
247
|
+
const key = getSessionKey();
|
|
248
|
+
if (sessionId && key) {
|
|
249
|
+
req.headers["x-transit-session"] = sessionId;
|
|
250
|
+
if (req.data !== void 0 && req.data !== null) {
|
|
251
|
+
const envelope = await encryptPayload(req.data, key);
|
|
252
|
+
req.data = envelope;
|
|
253
|
+
req.headers["x-transit-encrypted"] = "1";
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
149
257
|
return req;
|
|
150
258
|
});
|
|
151
259
|
let isRefreshing = false;
|
|
152
260
|
let refreshQueue = [];
|
|
153
261
|
client.interceptors.response.use(
|
|
154
|
-
(response) => {
|
|
262
|
+
async (response) => {
|
|
263
|
+
if (isTransitEnabled()) {
|
|
264
|
+
const key = getSessionKey();
|
|
265
|
+
if (key) {
|
|
266
|
+
if (isTransitEnvelope(response.data)) {
|
|
267
|
+
response.data = await decryptPayload(response.data, key);
|
|
268
|
+
} else if (response.data?.data && isTransitEnvelope(response.data.data)) {
|
|
269
|
+
response.data.data = await decryptPayload(response.data.data, key);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
155
273
|
if (response.data && typeof response.data === "object" && "success" in response.data && "data" in response.data) {
|
|
156
274
|
response.data = response.data.data;
|
|
157
275
|
}
|
|
158
276
|
return response;
|
|
159
277
|
},
|
|
160
278
|
async (error) => {
|
|
279
|
+
if (isTransitEnabled() && error.response?.data) {
|
|
280
|
+
const key = getSessionKey();
|
|
281
|
+
if (key) {
|
|
282
|
+
try {
|
|
283
|
+
if (isTransitEnvelope(error.response.data)) {
|
|
284
|
+
error.response.data = await decryptPayload(error.response.data, key);
|
|
285
|
+
} else if (error.response.data?.data && isTransitEnvelope(error.response.data.data)) {
|
|
286
|
+
error.response.data.data = await decryptPayload(error.response.data.data, key);
|
|
287
|
+
}
|
|
288
|
+
} catch {
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
161
292
|
const original = error.config;
|
|
162
293
|
if (error.response?.status === 401 && !original._retry) {
|
|
163
294
|
const refreshToken = _tokenStore?.getRefreshToken();
|
|
@@ -412,10 +543,10 @@ var conversationsApi = {
|
|
|
412
543
|
async delete(conversationId) {
|
|
413
544
|
await getApiClient().delete(`/conversations/${conversationId}`);
|
|
414
545
|
},
|
|
415
|
-
async addParticipants(conversationId, userIds) {
|
|
546
|
+
async addParticipants(conversationId, userIds, role) {
|
|
416
547
|
const { data } = await getApiClient().post(
|
|
417
548
|
`/conversations/${conversationId}/participants`,
|
|
418
|
-
{ userIds }
|
|
549
|
+
{ userIds, ...role && { role } }
|
|
419
550
|
);
|
|
420
551
|
return normalizeConversation(data);
|
|
421
552
|
},
|
|
@@ -535,7 +666,14 @@ async function uploadBatch(files, platformUploadFn, conversationId, onProgress,
|
|
|
535
666
|
filename: f.name,
|
|
536
667
|
mimeType: f.type,
|
|
537
668
|
size: f.size,
|
|
538
|
-
conversationId
|
|
669
|
+
conversationId,
|
|
670
|
+
...f.compressed && {
|
|
671
|
+
metadata: {
|
|
672
|
+
compressed: f.compressed,
|
|
673
|
+
originalSize: f.originalSize,
|
|
674
|
+
compressionAlgorithm: f.compressionAlgorithm
|
|
675
|
+
}
|
|
676
|
+
}
|
|
539
677
|
}));
|
|
540
678
|
const { urls, errors: requestErrors } = await storageApi.requestPresignedUrlBatch(requests);
|
|
541
679
|
const progressMap = {};
|
|
@@ -632,6 +770,95 @@ var usersApi = {
|
|
|
632
770
|
|
|
633
771
|
// src/socket/socket.ts
|
|
634
772
|
import { io } from "socket.io-client";
|
|
773
|
+
|
|
774
|
+
// src/crypto/detect.ts
|
|
775
|
+
var _cached = null;
|
|
776
|
+
async function detectTransitAlgo() {
|
|
777
|
+
if (_cached) return _cached;
|
|
778
|
+
try {
|
|
779
|
+
await globalThis.crypto.subtle.generateKey(
|
|
780
|
+
{ name: "X25519" },
|
|
781
|
+
false,
|
|
782
|
+
["deriveKey"]
|
|
783
|
+
);
|
|
784
|
+
_cached = "x25519";
|
|
785
|
+
} catch {
|
|
786
|
+
_cached = "p256";
|
|
787
|
+
}
|
|
788
|
+
return _cached;
|
|
789
|
+
}
|
|
790
|
+
function resetAlgoCache() {
|
|
791
|
+
_cached = null;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/crypto/handshake.ts
|
|
795
|
+
async function fetchServerKeys(apiUrl) {
|
|
796
|
+
const res = await fetch(`${apiUrl}/crypto/pubkey`);
|
|
797
|
+
if (!res.ok) throw new Error(`[AntzChat] Failed to fetch server public key: ${res.status}`);
|
|
798
|
+
const body = await res.json();
|
|
799
|
+
return body?.data ?? body;
|
|
800
|
+
}
|
|
801
|
+
async function performHandshake(algo, serverKeys, socketHandshakeAuth) {
|
|
802
|
+
const ephemeral = await globalThis.crypto.subtle.generateKey(
|
|
803
|
+
algo === "x25519" ? { name: "X25519" } : { name: "ECDH", namedCurve: "P-256" },
|
|
804
|
+
false,
|
|
805
|
+
["deriveBits"]
|
|
806
|
+
);
|
|
807
|
+
const pubRaw = await globalThis.crypto.subtle.exportKey("raw", ephemeral.publicKey);
|
|
808
|
+
socketHandshakeAuth["transitEphemeralPub"] = bufToB642(pubRaw);
|
|
809
|
+
socketHandshakeAuth["transitAlgo"] = algo;
|
|
810
|
+
const ephemeralPriv = ephemeral.privateKey;
|
|
811
|
+
return (sessionId) => deriveSessionKey(ephemeralPriv, algo, serverKeys, sessionId);
|
|
812
|
+
}
|
|
813
|
+
async function deriveSessionKey(ephemeralPriv, algo, serverKeys, sessionId) {
|
|
814
|
+
const serverPubB64 = algo === "x25519" ? serverKeys.x25519 : serverKeys.p256;
|
|
815
|
+
const serverPubRaw = b64ToBuf2(serverPubB64);
|
|
816
|
+
const keyAlgoParams = algo === "x25519" ? { name: "X25519" } : { name: "ECDH", namedCurve: "P-256" };
|
|
817
|
+
const serverPubKey = await globalThis.crypto.subtle.importKey(
|
|
818
|
+
"raw",
|
|
819
|
+
serverPubRaw,
|
|
820
|
+
keyAlgoParams,
|
|
821
|
+
false,
|
|
822
|
+
[]
|
|
823
|
+
);
|
|
824
|
+
const sharedBits = await globalThis.crypto.subtle.deriveBits(
|
|
825
|
+
{ name: algo === "x25519" ? "X25519" : "ECDH", public: serverPubKey },
|
|
826
|
+
ephemeralPriv,
|
|
827
|
+
256
|
|
828
|
+
);
|
|
829
|
+
const hkdfKey = await globalThis.crypto.subtle.importKey(
|
|
830
|
+
"raw",
|
|
831
|
+
sharedBits,
|
|
832
|
+
"HKDF",
|
|
833
|
+
false,
|
|
834
|
+
["deriveKey"]
|
|
835
|
+
);
|
|
836
|
+
const salt = new TextEncoder().encode(sessionId);
|
|
837
|
+
const info = new TextEncoder().encode("antz-transit-v1");
|
|
838
|
+
return globalThis.crypto.subtle.deriveKey(
|
|
839
|
+
{ name: "HKDF", hash: "SHA-256", salt, info },
|
|
840
|
+
hkdfKey,
|
|
841
|
+
{ name: "AES-GCM", length: 256 },
|
|
842
|
+
false,
|
|
843
|
+
["encrypt", "decrypt"]
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
function bufToB642(buf) {
|
|
847
|
+
const bytes = new Uint8Array(buf);
|
|
848
|
+
let str = "";
|
|
849
|
+
bytes.forEach((b) => {
|
|
850
|
+
str += String.fromCharCode(b);
|
|
851
|
+
});
|
|
852
|
+
return btoa(str);
|
|
853
|
+
}
|
|
854
|
+
function b64ToBuf2(b64) {
|
|
855
|
+
const bin = atob(b64);
|
|
856
|
+
const buf = new Uint8Array(bin.length);
|
|
857
|
+
for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
|
|
858
|
+
return buf.buffer;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// src/socket/socket.ts
|
|
635
862
|
var _socket = null;
|
|
636
863
|
var _status = "disconnected";
|
|
637
864
|
var _statusListeners = /* @__PURE__ */ new Set();
|
|
@@ -656,23 +883,96 @@ function onSocketStatus(listener) {
|
|
|
656
883
|
_statusListeners.add(listener);
|
|
657
884
|
return () => _statusListeners.delete(listener);
|
|
658
885
|
}
|
|
886
|
+
async function secureEmit(socket, event, payload, ack) {
|
|
887
|
+
if (isTransitEnabled()) {
|
|
888
|
+
const key = getSessionKey();
|
|
889
|
+
if (key) {
|
|
890
|
+
const envelope = await encryptPayload(payload, key);
|
|
891
|
+
if (ack) {
|
|
892
|
+
socket.emit(event, envelope, async (encryptedResponse) => {
|
|
893
|
+
if (isTransitEnvelope(encryptedResponse)) {
|
|
894
|
+
try {
|
|
895
|
+
const decrypted = await decryptPayload(encryptedResponse, key);
|
|
896
|
+
ack(decrypted);
|
|
897
|
+
} catch {
|
|
898
|
+
ack(encryptedResponse);
|
|
899
|
+
}
|
|
900
|
+
} else {
|
|
901
|
+
ack(encryptedResponse);
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
} else {
|
|
905
|
+
socket.emit(event, envelope);
|
|
906
|
+
}
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
if (ack) {
|
|
911
|
+
socket.emit(event, payload, ack);
|
|
912
|
+
} else {
|
|
913
|
+
socket.emit(event, payload);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
function secureOn(socket, event, handler) {
|
|
917
|
+
socket.on(event, async (raw) => {
|
|
918
|
+
if (isTransitEnabled() && isTransitEnvelope(raw)) {
|
|
919
|
+
const key = getSessionKey();
|
|
920
|
+
if (key) {
|
|
921
|
+
try {
|
|
922
|
+
const data = await decryptPayload(raw, key);
|
|
923
|
+
handler(data);
|
|
924
|
+
return;
|
|
925
|
+
} catch {
|
|
926
|
+
console.error(`[AntzChat] Transit decryption failed for event: ${event}`);
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
handler(raw);
|
|
932
|
+
});
|
|
933
|
+
}
|
|
659
934
|
async function connectSocket(config, getToken) {
|
|
660
935
|
if (_socket && !_socket.disconnected) return _socket;
|
|
661
936
|
_getToken = getToken;
|
|
662
937
|
_userId = config.userId;
|
|
663
938
|
_tenantId = config.tenantId;
|
|
664
939
|
const token = getToken();
|
|
940
|
+
const socketHandshakeAuth = {
|
|
941
|
+
token: token ? `Bearer ${token}` : "",
|
|
942
|
+
...config.userId && { userId: config.userId },
|
|
943
|
+
...config.tenantId && { tenantId: config.tenantId },
|
|
944
|
+
...config.avatar?.url && { avatarUrl: config.avatar.url },
|
|
945
|
+
...config.avatar?.base64 && { avatarBase64: config.avatar.base64 }
|
|
946
|
+
};
|
|
947
|
+
let boundDeriveSessionKey = null;
|
|
948
|
+
if (config.transitEncryption) {
|
|
949
|
+
try {
|
|
950
|
+
const serverKeys = await fetchServerKeys(config.apiUrl);
|
|
951
|
+
if (!serverKeys.enabled) {
|
|
952
|
+
throw new Error(
|
|
953
|
+
"[AntzChat] Transit encryption mismatch: SDK has transitEncryption=true but server has TRANSIT_ENCRYPTION_ENABLED=false. Align the config on both sides."
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
const algo = await detectTransitAlgo();
|
|
957
|
+
boundDeriveSessionKey = await performHandshake(algo, serverKeys, socketHandshakeAuth);
|
|
958
|
+
} catch (err) {
|
|
959
|
+
if (err.message?.startsWith("[AntzChat] Transit encryption mismatch")) throw err;
|
|
960
|
+
console.warn("[AntzChat] Transit handshake setup failed, connecting without transit encryption:", err.message);
|
|
961
|
+
}
|
|
962
|
+
} else {
|
|
963
|
+
try {
|
|
964
|
+
const serverKeys = await fetchServerKeys(config.apiUrl);
|
|
965
|
+
if (serverKeys.enabled) {
|
|
966
|
+
throw new Error(
|
|
967
|
+
"[AntzChat] Transit encryption mismatch: SDK has transitEncryption=false but server has TRANSIT_ENCRYPTION_ENABLED=true. Align the config on both sides."
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
} catch (err) {
|
|
971
|
+
if (err.message?.startsWith("[AntzChat] Transit encryption mismatch")) throw err;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
665
974
|
_socket = io(`${config.socketOrigin}/chat`, {
|
|
666
|
-
auth:
|
|
667
|
-
token: token ? `Bearer ${token}` : "",
|
|
668
|
-
...config.userId && { userId: config.userId },
|
|
669
|
-
...config.tenantId && { tenantId: config.tenantId },
|
|
670
|
-
...config.avatar?.url && { avatarUrl: config.avatar.url },
|
|
671
|
-
...config.avatar?.base64 && { avatarBase64: config.avatar.base64 }
|
|
672
|
-
},
|
|
673
|
-
// path must match SOCKET_IO_PATH on the server (default '/socket.io').
|
|
674
|
-
// Set socketPath in SDK config when the server is behind a reverse proxy
|
|
675
|
-
// that adds a path prefix (e.g. '/chat-api/socket.io' for UAT).
|
|
975
|
+
auth: socketHandshakeAuth,
|
|
676
976
|
path: config.socketPath,
|
|
677
977
|
transports: ["websocket", "polling"],
|
|
678
978
|
reconnection: true,
|
|
@@ -683,32 +983,80 @@ async function connectSocket(config, getToken) {
|
|
|
683
983
|
});
|
|
684
984
|
setStatus("connecting");
|
|
685
985
|
_socket.on("connect", () => setStatus("connected"));
|
|
686
|
-
_socket.on("disconnect", () =>
|
|
986
|
+
_socket.on("disconnect", () => {
|
|
987
|
+
setStatus("disconnected");
|
|
988
|
+
clearTransitSession();
|
|
989
|
+
});
|
|
687
990
|
_socket.on("connect_error", (err) => {
|
|
688
991
|
console.error("[AntzChat] Socket connect_error:", err?.message, err?.data);
|
|
689
992
|
setStatus("error");
|
|
690
993
|
});
|
|
691
994
|
_socket.on("reconnecting", () => setStatus("reconnecting"));
|
|
692
995
|
_socket.on("reconnect", () => setStatus("connected"));
|
|
693
|
-
|
|
996
|
+
if (config.transitEncryption && boundDeriveSessionKey) {
|
|
997
|
+
await new Promise((resolve) => {
|
|
998
|
+
const done = () => {
|
|
999
|
+
clearTimeout(timeout);
|
|
1000
|
+
resolve();
|
|
1001
|
+
};
|
|
1002
|
+
const timeout = setTimeout(() => {
|
|
1003
|
+
console.warn("[AntzChat] transit_session timeout \u2014 unblocking HTTP without transit encryption");
|
|
1004
|
+
configureTransit(false);
|
|
1005
|
+
done();
|
|
1006
|
+
}, 5e3);
|
|
1007
|
+
_socket.on("transit_session", async ({ sessionId }) => {
|
|
1008
|
+
try {
|
|
1009
|
+
if (boundDeriveSessionKey) {
|
|
1010
|
+
const sessionKey = await boundDeriveSessionKey(sessionId);
|
|
1011
|
+
const algo = socketHandshakeAuth["transitAlgo"] ?? "p256";
|
|
1012
|
+
setTransitSession({ sessionKey, algo, sessionId, enabled: true });
|
|
1013
|
+
}
|
|
1014
|
+
} catch (err) {
|
|
1015
|
+
console.error("[AntzChat] Failed to derive transit session key:", err.message);
|
|
1016
|
+
}
|
|
1017
|
+
done();
|
|
1018
|
+
});
|
|
1019
|
+
_socket.on("connect_error", () => done());
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
secureOn(_socket, "read_receipt", (event) => {
|
|
694
1023
|
import("./chat.store-PY3YVYGN.js").then(({ useChatStore: useChatStore2 }) => {
|
|
695
|
-
|
|
1024
|
+
const e = event;
|
|
1025
|
+
useChatStore2.getState().setLastRead(e.conversationId, e.messageId, e.readAt);
|
|
696
1026
|
});
|
|
697
1027
|
});
|
|
698
|
-
_socket
|
|
1028
|
+
secureOn(_socket, "user_online", (event) => {
|
|
699
1029
|
import("./chat.store-PY3YVYGN.js").then(({ useChatStore: useChatStore2 }) => {
|
|
700
|
-
|
|
701
|
-
store.setUserOnline(event.userId);
|
|
1030
|
+
useChatStore2.getState().setUserOnline(event.userId);
|
|
702
1031
|
});
|
|
703
1032
|
});
|
|
704
|
-
_socket
|
|
1033
|
+
secureOn(_socket, "user_offline", (event) => {
|
|
705
1034
|
import("./chat.store-PY3YVYGN.js").then(({ useChatStore: useChatStore2 }) => {
|
|
1035
|
+
const e = event;
|
|
706
1036
|
const store = useChatStore2.getState();
|
|
707
|
-
store.setUserOffline(
|
|
708
|
-
if (
|
|
1037
|
+
store.setUserOffline(e.userId);
|
|
1038
|
+
if (e.lastSeenAt) store.setLastSeen(e.userId, e.lastSeenAt);
|
|
709
1039
|
});
|
|
710
1040
|
});
|
|
711
|
-
return _socket;
|
|
1041
|
+
return createSecureSocketProxy(_socket);
|
|
1042
|
+
}
|
|
1043
|
+
function createSecureSocketProxy(socket) {
|
|
1044
|
+
return new Proxy(socket, {
|
|
1045
|
+
get(target, prop) {
|
|
1046
|
+
if (prop === "on") {
|
|
1047
|
+
return (event, handler) => {
|
|
1048
|
+
const internal = ["connect", "disconnect", "connect_error", "reconnect", "reconnecting", "error"];
|
|
1049
|
+
if (internal.includes(event)) {
|
|
1050
|
+
return target.on(event, handler);
|
|
1051
|
+
}
|
|
1052
|
+
secureOn(target, event, handler);
|
|
1053
|
+
return socket;
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
const val = target[prop];
|
|
1057
|
+
return typeof val === "function" ? val.bind(target) : val;
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
712
1060
|
}
|
|
713
1061
|
function disconnectSocket() {
|
|
714
1062
|
if (_socket) {
|
|
@@ -716,6 +1064,8 @@ function disconnectSocket() {
|
|
|
716
1064
|
_socket = null;
|
|
717
1065
|
setStatus("disconnected");
|
|
718
1066
|
}
|
|
1067
|
+
clearTransitSession();
|
|
1068
|
+
resetAlgoCache();
|
|
719
1069
|
_getToken = null;
|
|
720
1070
|
_userId = void 0;
|
|
721
1071
|
_tenantId = void 0;
|
|
@@ -773,7 +1123,7 @@ async function withAck(event, payload) {
|
|
|
773
1123
|
if (!socket) return Promise.reject(new Error(`[AntzChat] Socket not connected (event: ${event})`));
|
|
774
1124
|
return new Promise((resolve, reject) => {
|
|
775
1125
|
const timer = setTimeout(() => reject(new Error(`Socket ack timeout: ${event}`)), ACK_TIMEOUT);
|
|
776
|
-
socket
|
|
1126
|
+
secureEmit(socket, event, payload, (response) => {
|
|
777
1127
|
clearTimeout(timer);
|
|
778
1128
|
resolve(response);
|
|
779
1129
|
});
|
|
@@ -782,7 +1132,7 @@ async function withAck(event, payload) {
|
|
|
782
1132
|
function fireAndForget(event, payload) {
|
|
783
1133
|
const socket = tryGetSocket();
|
|
784
1134
|
if (!socket) return;
|
|
785
|
-
socket
|
|
1135
|
+
secureEmit(socket, event, payload);
|
|
786
1136
|
}
|
|
787
1137
|
var socketEmit = {
|
|
788
1138
|
joinRoom(conversationId) {
|
|
@@ -827,7 +1177,7 @@ var socketEmit = {
|
|
|
827
1177
|
if (!socket) return Promise.resolve([]);
|
|
828
1178
|
return new Promise((resolve, reject) => {
|
|
829
1179
|
const timer = setTimeout(() => reject(new Error("get_online_users timeout")), ACK_TIMEOUT);
|
|
830
|
-
socket
|
|
1180
|
+
secureEmit(socket, "get_online_users", { userIds }, (response) => {
|
|
831
1181
|
clearTimeout(timer);
|
|
832
1182
|
if (response && typeof response === "object" && "onlineStatus" in response) {
|
|
833
1183
|
const status = response.onlineStatus;
|
|
@@ -1005,6 +1355,7 @@ export {
|
|
|
1005
1355
|
resetAuthStore,
|
|
1006
1356
|
resolveConfig,
|
|
1007
1357
|
setApiClientInstance,
|
|
1358
|
+
setTransitSession,
|
|
1008
1359
|
socketEmit,
|
|
1009
1360
|
storageApi,
|
|
1010
1361
|
tryGetSocket,
|