@typexim/web-sdk 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +2 -1
  2. package/dist/types/enums/sdk.d.ts +0 -1
  3. package/dist/types/modules/ChatSDK.d.ts +3 -2
  4. package/dist/types/modules/ReConnectWebsocket.d.ts +3 -0
  5. package/dist/types/modules/TabSessionCoordinator.d.ts +9 -5
  6. package/dist/types/utils/websocket.d.ts +4 -0
  7. package/dist/web-sdk.esm.js +1 -1
  8. package/dist/web-sdk.js +1 -1
  9. package/dist-test/enums/business.js +139 -0
  10. package/dist-test/enums/index.js +2 -0
  11. package/dist-test/enums/sdk.js +24 -0
  12. package/dist-test/events.js +50 -0
  13. package/dist-test/index.js +26 -0
  14. package/dist-test/interfaces/business.js +1 -0
  15. package/dist-test/interfaces/index.js +3 -0
  16. package/dist-test/interfaces/pushMessage.js +1 -0
  17. package/dist-test/interfaces/sdk/api.js +1 -0
  18. package/dist-test/interfaces/sdk/common.js +1 -0
  19. package/dist-test/interfaces/sdk/index.js +2 -0
  20. package/dist-test/modules/Base.js +70 -0
  21. package/dist-test/modules/BusinessWebsocket.js +68 -0
  22. package/dist-test/modules/ChatSDK.js +698 -0
  23. package/dist-test/modules/FeedCache.js +211 -0
  24. package/dist-test/modules/HttpClient.js +104 -0
  25. package/dist-test/modules/Logger.js +73 -0
  26. package/dist-test/modules/PushMessageProcessor.js +437 -0
  27. package/dist-test/modules/ReConnectWebsocket.js +223 -0
  28. package/dist-test/modules/Storage.js +27 -0
  29. package/dist-test/modules/TabSessionCoordinator.js +289 -0
  30. package/dist-test/modules/Version.js +20 -0
  31. package/dist-test/utils/date.js +4 -0
  32. package/dist-test/utils/http.js +13 -0
  33. package/dist-test/utils/makeSign.js +79 -0
  34. package/dist-test/utils/message.js +146 -0
  35. package/dist-test/utils/misc.js +13 -0
  36. package/dist-test/utils/promise.js +10 -0
  37. package/dist-test/utils/sleep.js +3 -0
  38. package/dist-test/utils/websocket.js +24 -0
  39. package/package.json +1 -1
  40. package/test/{chat-sdk-restore-login.test.mjs → chat-sdk-independent-login.test.mjs} +50 -62
  41. package/test/chat-sdk-session-restore.test.mjs +331 -0
  42. package/test/tab-session-coordinator.test.mjs +65 -64
  43. package/test/websocket-url.test.mjs +24 -0
  44. package/dist/types/modules/SingletonLock.d.ts +0 -12
  45. package/test/singleton-lock.test.mjs +0 -75
@@ -0,0 +1,289 @@
1
+ const CHANNEL_NAME = 'typex-sdk-tab-session';
2
+ const STORAGE_KEY = '__typex_sdk_tab_session_message__';
3
+ function createTabId() {
4
+ if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
5
+ return crypto.randomUUID();
6
+ }
7
+ return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
8
+ }
9
+ function isCoordinatorMessage(value) {
10
+ if (!value || typeof value !== 'object') {
11
+ return false;
12
+ }
13
+ const message = value;
14
+ if (typeof message.senderTabId !== 'string') {
15
+ return false;
16
+ }
17
+ if (message.type === 'session-request') {
18
+ return (typeof message.requestId === 'string' &&
19
+ typeof message.createdAt === 'number');
20
+ }
21
+ if (message.type === 'session-response') {
22
+ return (typeof message.requesterTabId === 'string' &&
23
+ typeof message.requestId === 'string' &&
24
+ typeof message.createdAt === 'number' &&
25
+ isTransferableLoginSession(message.session));
26
+ }
27
+ if (message.type === 'logout') {
28
+ return (typeof message.logoutId === 'string' &&
29
+ typeof message.createdAt === 'number');
30
+ }
31
+ if (message.type === 'login') {
32
+ return (typeof message.loginId === 'string' &&
33
+ typeof message.createdAt === 'number' &&
34
+ isTransferableLoginSession(message.session));
35
+ }
36
+ return false;
37
+ }
38
+ function isTransferableLoginSession(value) {
39
+ if (!value || typeof value !== 'object') {
40
+ return false;
41
+ }
42
+ const session = value;
43
+ const loginUser = session.loginUser;
44
+ return ((session.deviceId === null || typeof session.deviceId === 'string') &&
45
+ !!loginUser &&
46
+ typeof loginUser.userId === 'string' &&
47
+ typeof loginUser.secretKey === 'string' &&
48
+ typeof loginUser.sessionId === 'string');
49
+ }
50
+ export default class TabSessionCoordinator {
51
+ constructor(config = {}) {
52
+ Object.defineProperty(this, "tabId", {
53
+ enumerable: true,
54
+ configurable: true,
55
+ writable: true,
56
+ value: void 0
57
+ });
58
+ Object.defineProperty(this, "channel", {
59
+ enumerable: true,
60
+ configurable: true,
61
+ writable: true,
62
+ value: void 0
63
+ });
64
+ Object.defineProperty(this, "closed", {
65
+ enumerable: true,
66
+ configurable: true,
67
+ writable: true,
68
+ value: void 0
69
+ });
70
+ Object.defineProperty(this, "handledSessionRequestIds", {
71
+ enumerable: true,
72
+ configurable: true,
73
+ writable: true,
74
+ value: void 0
75
+ });
76
+ Object.defineProperty(this, "handledLogoutIds", {
77
+ enumerable: true,
78
+ configurable: true,
79
+ writable: true,
80
+ value: void 0
81
+ });
82
+ Object.defineProperty(this, "handledLoginIds", {
83
+ enumerable: true,
84
+ configurable: true,
85
+ writable: true,
86
+ value: void 0
87
+ });
88
+ Object.defineProperty(this, "pendingSessionRequests", {
89
+ enumerable: true,
90
+ configurable: true,
91
+ writable: true,
92
+ value: void 0
93
+ });
94
+ Object.defineProperty(this, "getCurrentSession", {
95
+ enumerable: true,
96
+ configurable: true,
97
+ writable: true,
98
+ value: void 0
99
+ });
100
+ Object.defineProperty(this, "onLogoutBroadcast", {
101
+ enumerable: true,
102
+ configurable: true,
103
+ writable: true,
104
+ value: void 0
105
+ });
106
+ Object.defineProperty(this, "onLoginBroadcast", {
107
+ enumerable: true,
108
+ configurable: true,
109
+ writable: true,
110
+ value: void 0
111
+ });
112
+ Object.defineProperty(this, "logger", {
113
+ enumerable: true,
114
+ configurable: true,
115
+ writable: true,
116
+ value: void 0
117
+ });
118
+ this.tabId = createTabId();
119
+ this.channel = null;
120
+ this.closed = false;
121
+ this.handledSessionRequestIds = new Set();
122
+ this.handledLogoutIds = new Set();
123
+ this.handledLoginIds = new Set();
124
+ this.pendingSessionRequests = new Map();
125
+ this.getCurrentSession = config.getCurrentSession ?? null;
126
+ this.onLogoutBroadcast = config.onLogoutBroadcast ?? null;
127
+ this.onLoginBroadcast = config.onLoginBroadcast ?? null;
128
+ this.logger = config.logger ?? null;
129
+ this.handleBroadcastMessage = this.handleBroadcastMessage.bind(this);
130
+ this.handleStorageMessage = this.handleStorageMessage.bind(this);
131
+ if (typeof BroadcastChannel !== 'undefined') {
132
+ this.channel = new BroadcastChannel(CHANNEL_NAME);
133
+ this.channel.onmessage = this.handleBroadcastMessage;
134
+ }
135
+ if (typeof window !== 'undefined') {
136
+ window.addEventListener('storage', this.handleStorageMessage);
137
+ }
138
+ }
139
+ publishLogout() {
140
+ this.publish({
141
+ type: 'logout',
142
+ senderTabId: this.tabId,
143
+ logoutId: createTabId(),
144
+ createdAt: Date.now(),
145
+ });
146
+ }
147
+ publishLogin(session) {
148
+ this.publish({
149
+ type: 'login',
150
+ senderTabId: this.tabId,
151
+ loginId: createTabId(),
152
+ createdAt: Date.now(),
153
+ session,
154
+ });
155
+ }
156
+ requestCurrentSession(timeoutMs = 500) {
157
+ if (this.closed) {
158
+ return Promise.resolve(null);
159
+ }
160
+ const requestId = createTabId();
161
+ return new Promise((resolve) => {
162
+ const timeout = setTimeout(() => {
163
+ this.pendingSessionRequests.delete(requestId);
164
+ resolve(null);
165
+ }, timeoutMs);
166
+ this.pendingSessionRequests.set(requestId, {
167
+ resolve,
168
+ timeout,
169
+ });
170
+ this.publish({
171
+ type: 'session-request',
172
+ senderTabId: this.tabId,
173
+ requestId,
174
+ createdAt: Date.now(),
175
+ });
176
+ });
177
+ }
178
+ destroy() {
179
+ if (this.closed) {
180
+ return;
181
+ }
182
+ this.closed = true;
183
+ this.pendingSessionRequests.forEach((request) => {
184
+ clearTimeout(request.timeout);
185
+ request.resolve(null);
186
+ });
187
+ this.pendingSessionRequests.clear();
188
+ if (this.channel) {
189
+ this.channel.close();
190
+ this.channel = null;
191
+ }
192
+ if (typeof window !== 'undefined') {
193
+ window.removeEventListener('storage', this.handleStorageMessage);
194
+ }
195
+ }
196
+ publish(message) {
197
+ if (this.closed) {
198
+ return;
199
+ }
200
+ if (this.channel) {
201
+ this.channel.postMessage(message);
202
+ }
203
+ // Never persist messages that contain secretKey to localStorage.
204
+ if (message.type === 'session-response' || message.type === 'login') {
205
+ return;
206
+ }
207
+ if (typeof localStorage !== 'undefined') {
208
+ try {
209
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({
210
+ ...message,
211
+ storageNonce: createTabId(),
212
+ }));
213
+ }
214
+ catch (e) {
215
+ this.logger?.warn('TabSessionCoordinator storage publish failed', e);
216
+ }
217
+ }
218
+ }
219
+ handleBroadcastMessage(event) {
220
+ this.handleMessage(event.data);
221
+ }
222
+ handleStorageMessage(event) {
223
+ if (event.key !== STORAGE_KEY || !event.newValue) {
224
+ return;
225
+ }
226
+ try {
227
+ this.handleMessage(JSON.parse(event.newValue));
228
+ }
229
+ catch (e) {
230
+ this.logger?.warn('TabSessionCoordinator storage parse failed', e);
231
+ }
232
+ }
233
+ handleMessage(message) {
234
+ if (this.closed || !isCoordinatorMessage(message)) {
235
+ return;
236
+ }
237
+ if (message.senderTabId === this.tabId) {
238
+ return;
239
+ }
240
+ if (message.type === 'logout') {
241
+ if (this.handledLogoutIds.has(message.logoutId)) {
242
+ return;
243
+ }
244
+ this.handledLogoutIds.add(message.logoutId);
245
+ void this.onLogoutBroadcast?.();
246
+ return;
247
+ }
248
+ if (message.type === 'login') {
249
+ if (this.handledLoginIds.has(message.loginId)) {
250
+ return;
251
+ }
252
+ this.handledLoginIds.add(message.loginId);
253
+ void this.onLoginBroadcast?.(message.session);
254
+ return;
255
+ }
256
+ if (message.type === 'session-request') {
257
+ const sessionRequestKey = `${message.senderTabId}:${message.requestId}`;
258
+ if (this.handledSessionRequestIds.has(sessionRequestKey)) {
259
+ return;
260
+ }
261
+ this.handledSessionRequestIds.add(sessionRequestKey);
262
+ const session = this.getCurrentSession?.();
263
+ if (!session) {
264
+ return;
265
+ }
266
+ this.publish({
267
+ type: 'session-response',
268
+ senderTabId: this.tabId,
269
+ requesterTabId: message.senderTabId,
270
+ requestId: message.requestId,
271
+ createdAt: Date.now(),
272
+ session,
273
+ });
274
+ return;
275
+ }
276
+ if (message.type === 'session-response') {
277
+ if (message.requesterTabId !== this.tabId) {
278
+ return;
279
+ }
280
+ const pendingRequest = this.pendingSessionRequests.get(message.requestId);
281
+ if (!pendingRequest) {
282
+ return;
283
+ }
284
+ clearTimeout(pendingRequest.timeout);
285
+ this.pendingSessionRequests.delete(message.requestId);
286
+ pendingRequest.resolve(message.session);
287
+ }
288
+ }
289
+ }
@@ -0,0 +1,20 @@
1
+ export default class Version {
2
+ constructor() {
3
+ Object.defineProperty(this, "version", {
4
+ enumerable: true,
5
+ configurable: true,
6
+ writable: true,
7
+ value: void 0
8
+ });
9
+ this.version = 0;
10
+ }
11
+ increment() {
12
+ this.version++;
13
+ }
14
+ get() {
15
+ return this.version;
16
+ }
17
+ check(curVersion) {
18
+ return curVersion === this.version;
19
+ }
20
+ }
@@ -0,0 +1,4 @@
1
+ export function getCurrentISOString() {
2
+ const d = new Date();
3
+ return d.toISOString();
4
+ }
@@ -0,0 +1,13 @@
1
+ export function createErrorResponse(code, msg) {
2
+ return {
3
+ ok: false,
4
+ code,
5
+ msg,
6
+ };
7
+ }
8
+ export function createOkResponse(data) {
9
+ return {
10
+ ok: true,
11
+ data,
12
+ };
13
+ }
@@ -0,0 +1,79 @@
1
+ import CryptoJS from 'crypto-js';
2
+ /**
3
+ * 将字符串转换为 ArrayBuffer
4
+ * @param str 输入字符串
5
+ * @returns ArrayBuffer
6
+ */
7
+ function stringToArrayBuffer(str) {
8
+ return new TextEncoder().encode(str).buffer;
9
+ }
10
+ async function generateSignatureWebCrypto(message, secretKey) {
11
+ const keyBuffer = stringToArrayBuffer(secretKey);
12
+ const messageBuffer = stringToArrayBuffer(message);
13
+ // 导入密钥
14
+ const key = await crypto.subtle.importKey('raw', keyBuffer, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']);
15
+ // 生成签名
16
+ const signature = await crypto.subtle.sign('HMAC', key, messageBuffer);
17
+ // 转换签名为十六进制字符串
18
+ return Array.from(new Uint8Array(signature))
19
+ .map((b) => b.toString(16).padStart(2, '0'))
20
+ .join('');
21
+ }
22
+ /**
23
+ * 使用 CryptoJS 生成 HMAC 签名(降级方案)
24
+ * @param message 待签名的消息
25
+ * @param secretKey 密钥
26
+ * @returns 签名字符串(十六进制格式)
27
+ */
28
+ function generateSignatureCryptoJS(message, secretKey) {
29
+ const hash = CryptoJS.HmacSHA256(message, secretKey);
30
+ return hash.toString(CryptoJS.enc.Hex);
31
+ }
32
+ /**
33
+ * 生成 HMAC 签名(兼容降级)
34
+ * @param message 待签名的消息
35
+ * @returns 签名字符串(十六进制格式)
36
+ */
37
+ async function generateSignature(message, secretKey) {
38
+ if (window.crypto && window.crypto.subtle) {
39
+ // 优先使用 Web Crypto API
40
+ return await generateSignatureWebCrypto(message, secretKey);
41
+ }
42
+ // 降级到 CryptoJS
43
+ return generateSignatureCryptoJS(message, secretKey);
44
+ }
45
+ const sortedJSON = (obj) => {
46
+ if (obj === null || obj === undefined) {
47
+ return '';
48
+ }
49
+ if (typeof obj === 'object' && !Array.isArray(obj)) {
50
+ // 处理对象 (map)
51
+ const keys = Object.keys(obj).sort(); // 对键进行字典序排序
52
+ let str = '';
53
+ for (const key of keys) {
54
+ str += sortedJSON(obj[key]);
55
+ }
56
+ return str;
57
+ }
58
+ else if (Array.isArray(obj)) {
59
+ // 处理数组
60
+ let str = '';
61
+ for (const item of obj) {
62
+ str += sortedJSON(item);
63
+ }
64
+ return str;
65
+ }
66
+ else if (typeof obj === 'string') {
67
+ // 处理字符串
68
+ return obj;
69
+ }
70
+ else {
71
+ // 处理其他基础类型 (数字、布尔值等)
72
+ return String(obj);
73
+ }
74
+ };
75
+ const makeSign = async (data, secretKey) => {
76
+ const message = await sortedJSON(data);
77
+ return await generateSignature(message, secretKey);
78
+ };
79
+ export default makeSign;
@@ -0,0 +1,146 @@
1
+ import { RequestDirection } from '../enums/index.js';
2
+ import { jsonParse } from './misc.js';
3
+ export const makePositions = (config) => {
4
+ const positions = [];
5
+ if (config.direction === RequestDirection.up) {
6
+ let curPosition = config.startPosition ?? config.maxPosition;
7
+ while (positions.length < config.limit &&
8
+ curPosition >= config.minPosition) {
9
+ positions.push(curPosition);
10
+ curPosition--;
11
+ }
12
+ const hasMore = curPosition >= config.minPosition;
13
+ let nextPosition = undefined;
14
+ if (hasMore) {
15
+ nextPosition = curPosition;
16
+ }
17
+ return {
18
+ positions,
19
+ nextPosition,
20
+ hasMore,
21
+ };
22
+ }
23
+ else {
24
+ let curPosition = config.startPosition ?? config.minPosition;
25
+ while (positions.length < config.limit &&
26
+ curPosition <= config.maxPosition) {
27
+ positions.push(curPosition);
28
+ curPosition++;
29
+ }
30
+ const hasMore = curPosition <= config.maxPosition;
31
+ let nextPosition = undefined;
32
+ if (hasMore) {
33
+ nextPosition = curPosition;
34
+ }
35
+ return {
36
+ positions,
37
+ nextPosition,
38
+ hasMore,
39
+ };
40
+ }
41
+ };
42
+ export const stringifyCustomMessageContent = (payload) => {
43
+ return JSON.stringify({
44
+ client_msg_id: payload.client_msg_id,
45
+ content: JSON.stringify(payload.content),
46
+ });
47
+ };
48
+ /**
49
+ * 解析message_type_id为MessageType.custom的消息内容
50
+ */
51
+ export const parseCustomMessageContent = (str) => {
52
+ const json = jsonParse(str);
53
+ if (json !== null) {
54
+ let parsedContent = undefined;
55
+ if (json.content != null) {
56
+ parsedContent = jsonParse(json.content) ?? undefined;
57
+ }
58
+ return {
59
+ client_msg_id: json.client_msg_id,
60
+ content: parsedContent,
61
+ };
62
+ }
63
+ return {};
64
+ };
65
+ /**
66
+ * 解析message_kind_id为MessageKind.system,message_type_id为MessageType.text的消息内容
67
+ */
68
+ export const parseSystemMessageContent = (str) => {
69
+ const json = jsonParse(str);
70
+ if (json !== null) {
71
+ return {
72
+ text: json.text,
73
+ text_i18n: json.text_i18n,
74
+ };
75
+ }
76
+ return {};
77
+ };
78
+ /**
79
+ * 解析message_kind_id为MessageKind.text,message_type_id为MessageType.text的消息内容
80
+ */
81
+ export const parseTextMessageContent = (str) => {
82
+ const json = jsonParse(str);
83
+ if (json !== null) {
84
+ return {
85
+ text: json.text,
86
+ client_msg_id: json.client_msg_id,
87
+ };
88
+ }
89
+ return {};
90
+ };
91
+ const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
92
+ /**
93
+ * 向URL添加查询参数
94
+ * @param url
95
+ * @param key
96
+ * @param value
97
+ * @returns
98
+ */
99
+ export const appendQueryParam = (url, key, value) => {
100
+ const encodedKey = encodeURIComponent(key);
101
+ const encodedValue = encodeURIComponent(value);
102
+ const hashIndex = url.indexOf('#');
103
+ const baseUrl = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
104
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : '';
105
+ const existsReg = new RegExp(`([?&])${escapeRegExp(encodedKey)}=[^&#]*`);
106
+ if (existsReg.test(baseUrl)) {
107
+ return baseUrl.replace(existsReg, `$1${encodedKey}=${encodedValue}`) + hash;
108
+ }
109
+ const separator = baseUrl.includes('?') ? '&' : '?';
110
+ return `${baseUrl}${separator}${encodedKey}=${encodedValue}${hash}`;
111
+ };
112
+ /**
113
+ * 按域名匹配 JSON 字符串中的 URL
114
+ * 同时支持:
115
+ * - https://api-coco.typex.im/...
116
+ * - https:\/\/api-coco.typex.im\/...
117
+ */
118
+ export const createDomainUrlRegex = (domain) => new RegExp(`https?:\\\\?/\\\\?/${escapeRegExp(domain)}(?:\\\\.|[^"\\\\\\s])*`, 'g');
119
+ const decodeJsonString = (encoded) => {
120
+ try {
121
+ return JSON.parse(`"${encoded}"`);
122
+ }
123
+ catch {
124
+ return encoded;
125
+ }
126
+ };
127
+ const encodeJsonString = (decoded) => {
128
+ return JSON.stringify(decoded).slice(1, -1);
129
+ };
130
+ /**
131
+ * 替换消息 content(JSON 字符串)里指定域名的 URL
132
+ */
133
+ export const replaceDomainUrlsInContent = (content, domain, replaceUrl) => {
134
+ if (!content || typeof content !== 'string') {
135
+ return content;
136
+ }
137
+ const regex = createDomainUrlRegex(domain);
138
+ return content.replace(regex, (rawUrl) => {
139
+ const currentUrl = decodeJsonString(rawUrl);
140
+ const nextUrl = replaceUrl(currentUrl);
141
+ if (typeof nextUrl !== 'string' || nextUrl === currentUrl) {
142
+ return rawUrl;
143
+ }
144
+ return encodeJsonString(nextUrl);
145
+ });
146
+ };
@@ -0,0 +1,13 @@
1
+ export const jsonParse = (content) => {
2
+ try {
3
+ if (content == null ||
4
+ typeof content !== 'string' ||
5
+ content.length === 0) {
6
+ return null;
7
+ }
8
+ return JSON.parse(content);
9
+ }
10
+ catch {
11
+ return null;
12
+ }
13
+ };
@@ -0,0 +1,10 @@
1
+ export const withResolvers = () => {
2
+ let resolver = () => { };
3
+ const promise = new Promise((resolve) => {
4
+ resolver = resolve;
5
+ });
6
+ return {
7
+ promise,
8
+ resolve: resolver,
9
+ };
10
+ };
@@ -0,0 +1,3 @@
1
+ export default (ms) => {
2
+ return new Promise((resolve) => setTimeout(resolve, ms));
3
+ };
@@ -0,0 +1,24 @@
1
+ export const WEBSOCKET_AUTH_PROTOCOL = 'typex.im.auth.v1';
2
+ export function encodeTokenForWebSocketProtocol(token) {
3
+ const bytes = new TextEncoder().encode(token);
4
+ let binary = '';
5
+ for (const byte of bytes) {
6
+ binary += String.fromCharCode(byte);
7
+ }
8
+ return btoa(binary)
9
+ .replace(/\+/g, '-')
10
+ .replace(/\//g, '_')
11
+ .replace(/=+$/, '');
12
+ }
13
+ export function buildWebSocketAuthProtocols(token) {
14
+ return [
15
+ WEBSOCKET_AUTH_PROTOCOL,
16
+ `typex.im.auth.b64url.${encodeTokenForWebSocketProtocol(token)}`,
17
+ ];
18
+ }
19
+ export function buildWebSocketConnectUrl(url, options = {}) {
20
+ const wsUrl = new URL(url);
21
+ wsUrl.searchParams.set('platform', options.platform ?? 'web');
22
+ wsUrl.searchParams.set('ws_route', options.wsRoute ?? '');
23
+ return wsUrl.toString();
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typexim/web-sdk",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",