@syncular/transport-ws 0.0.1 → 0.0.2-127
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 +36 -0
- package/dist/index.d.ts +44 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +91 -16
- package/dist/index.js.map +1 -1
- package/package.json +27 -5
- package/src/index.test.ts +183 -0
- package/src/index.ts +145 -22
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @syncular/transport-ws
|
|
2
|
+
|
|
3
|
+
WebSocket transport for Syncular realtime wake-ups and presence.
|
|
4
|
+
|
|
5
|
+
WebSockets are used as a wake-up mechanism; data still flows over HTTP via `@syncular/transport-http` (clients pull after being notified).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @syncular/transport-ws
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createWebSocketTransport } from '@syncular/transport-ws';
|
|
17
|
+
|
|
18
|
+
const transport = createWebSocketTransport({
|
|
19
|
+
baseUrl: 'https://api.example.com',
|
|
20
|
+
getHeaders: () => ({ Authorization: `Bearer ${token}` }),
|
|
21
|
+
// In browsers, WebSocket auth is typically cookie-based (same-origin).
|
|
22
|
+
// If needed, use getRealtimeParams to add non-sensitive query params.
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Documentation
|
|
27
|
+
|
|
28
|
+
- Realtime wake-ups: https://syncular.dev/docs/build/realtime
|
|
29
|
+
- Presence: https://syncular.dev/docs/build/presence
|
|
30
|
+
|
|
31
|
+
## Links
|
|
32
|
+
|
|
33
|
+
- GitHub: https://github.com/syncular/syncular
|
|
34
|
+
- Issues: https://github.com/syncular/syncular/issues
|
|
35
|
+
|
|
36
|
+
> Status: Alpha. APIs and storage layouts may change between releases.
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Browsers' `WebSocket` cannot attach custom headers.
|
|
9
9
|
* - Use cookie auth (same-origin) or a query-param token for the realtime URL.
|
|
10
10
|
*/
|
|
11
|
-
import type { SyncTransport } from '@syncular/core';
|
|
11
|
+
import type { SyncPushRequest, SyncPushResponse, SyncTransport } from '@syncular/core';
|
|
12
12
|
import { type ClientOptions } from '@syncular/transport-http';
|
|
13
13
|
/**
|
|
14
14
|
* WebSocket connection state
|
|
@@ -30,15 +30,42 @@ export interface PresenceEventData {
|
|
|
30
30
|
metadata?: Record<string, unknown>;
|
|
31
31
|
}>;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Push response data received from the server over WS
|
|
35
|
+
*/
|
|
36
|
+
export interface WsPushResponseData {
|
|
37
|
+
requestId: string;
|
|
38
|
+
ok: boolean;
|
|
39
|
+
status: string;
|
|
40
|
+
commitSeq?: number;
|
|
41
|
+
results: Array<{
|
|
42
|
+
opIndex: number;
|
|
43
|
+
status: string;
|
|
44
|
+
[k: string]: unknown;
|
|
45
|
+
}>;
|
|
46
|
+
timestamp: number;
|
|
47
|
+
}
|
|
33
48
|
/**
|
|
34
49
|
* WebSocket event from the server
|
|
35
50
|
*/
|
|
36
51
|
export interface WebSocketEvent {
|
|
37
|
-
event: 'sync' | 'heartbeat' | 'error' | 'presence';
|
|
52
|
+
event: 'sync' | 'heartbeat' | 'error' | 'presence' | 'push-response';
|
|
38
53
|
data: {
|
|
39
54
|
cursor?: number;
|
|
55
|
+
/** Inline change data for small payloads (WS data delivery) */
|
|
56
|
+
changes?: unknown[];
|
|
40
57
|
error?: string;
|
|
41
58
|
presence?: PresenceEventData;
|
|
59
|
+
/** Push response fields (for push-response events) */
|
|
60
|
+
requestId?: string;
|
|
61
|
+
ok?: boolean;
|
|
62
|
+
status?: string;
|
|
63
|
+
commitSeq?: number;
|
|
64
|
+
results?: Array<{
|
|
65
|
+
opIndex: number;
|
|
66
|
+
status: string;
|
|
67
|
+
[k: string]: unknown;
|
|
68
|
+
}>;
|
|
42
69
|
timestamp: number;
|
|
43
70
|
};
|
|
44
71
|
}
|
|
@@ -52,16 +79,17 @@ export type WebSocketEventCallback = (event: WebSocketEvent) => void;
|
|
|
52
79
|
export type WebSocketStateCallback = (state: WebSocketConnectionState) => void;
|
|
53
80
|
export interface WebSocketTransportOptions extends ClientOptions {
|
|
54
81
|
/**
|
|
55
|
-
* WebSocket endpoint URL. If not provided, uses `${baseUrl}/realtime`
|
|
56
|
-
* `http(s)` -> `ws(s)` conversion when possible.
|
|
82
|
+
* WebSocket endpoint URL. If not provided, uses `${baseUrl}/sync/realtime`
|
|
83
|
+
* with `http(s)` -> `ws(s)` conversion when possible.
|
|
57
84
|
*/
|
|
58
85
|
wsUrl?: string;
|
|
59
86
|
/**
|
|
60
87
|
* Additional query params for the realtime URL (e.g. `{ token }`).
|
|
61
88
|
*
|
|
62
89
|
* ⚠️ SECURITY WARNING: Query parameters may be logged by proxies, CDNs, and
|
|
63
|
-
* browser history. Do NOT pass sensitive tokens here.
|
|
64
|
-
*
|
|
90
|
+
* browser history. Do NOT pass sensitive tokens here. Prefer cookie-based
|
|
91
|
+
* auth. Use `authToken` only with servers that explicitly support first-message
|
|
92
|
+
* WebSocket authentication.
|
|
65
93
|
*/
|
|
66
94
|
getRealtimeParams?: (args: {
|
|
67
95
|
clientId: string;
|
|
@@ -103,6 +131,11 @@ export interface WebSocketTransportOptions extends ClientOptions {
|
|
|
103
131
|
* Optional WebSocket implementation override (useful for non-browser runtimes).
|
|
104
132
|
*/
|
|
105
133
|
WebSocketImpl?: typeof WebSocket;
|
|
134
|
+
/**
|
|
135
|
+
* Timeout for waiting on WS push responses before falling back to HTTP push.
|
|
136
|
+
* Default: 1500ms
|
|
137
|
+
*/
|
|
138
|
+
wsPushTimeoutMs?: number;
|
|
106
139
|
/**
|
|
107
140
|
* Transport path telemetry sent to the server for push/pull and realtime.
|
|
108
141
|
* Defaults to 'relay' for this transport.
|
|
@@ -126,6 +159,11 @@ export interface WebSocketTransport extends SyncTransport {
|
|
|
126
159
|
sendPresenceLeave(scopeKey: string): void;
|
|
127
160
|
sendPresenceUpdate(scopeKey: string, metadata: Record<string, unknown>): void;
|
|
128
161
|
onPresenceEvent(callback: PresenceEventCallback): () => void;
|
|
162
|
+
/**
|
|
163
|
+
* Push a commit via WebSocket (bypasses HTTP).
|
|
164
|
+
* Returns `null` if WS is not connected or times out (caller should fall back to HTTP).
|
|
165
|
+
*/
|
|
166
|
+
pushViaWs(request: SyncPushRequest): Promise<SyncPushResponse | null>;
|
|
129
167
|
}
|
|
130
168
|
export declare function createWebSocketTransport(options: WebSocketTransportOptions): WebSocketTransport;
|
|
131
169
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ function defaultWsUrl(baseUrl) {
|
|
|
16
16
|
? new URL(baseUrl)
|
|
17
17
|
: new URL(baseUrl, location.origin);
|
|
18
18
|
resolved.protocol = resolved.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
19
|
-
resolved.pathname = `${resolved.pathname.replace(/\/$/, '')}/realtime`;
|
|
19
|
+
resolved.pathname = `${resolved.pathname.replace(/\/$/, '')}/sync/realtime`;
|
|
20
20
|
return resolved.toString();
|
|
21
21
|
}
|
|
22
22
|
catch {
|
|
@@ -32,10 +32,12 @@ export function createWebSocketTransport(options) {
|
|
|
32
32
|
transportPath: telemetryTransportPath,
|
|
33
33
|
});
|
|
34
34
|
const { baseUrl, wsUrl = options.wsUrl ?? defaultWsUrl(baseUrl), getRealtimeParams, authToken, initialReconnectDelay = 1000, maxReconnectDelay = 30_000, reconnectBackoffFactor = 2, heartbeatTimeout = 60_000, WebSocketImpl = typeof WebSocket !== 'undefined' ? WebSocket : undefined, } = options;
|
|
35
|
-
// Warn about security risk of using getRealtimeParams with sensitive data
|
|
36
|
-
if
|
|
35
|
+
// Warn about security risk of using getRealtimeParams with sensitive data,
|
|
36
|
+
// but only if the consumer hasn't also provided getHeaders or authToken
|
|
37
|
+
// (which indicates intentional auth handling with query-param fallback).
|
|
38
|
+
if (getRealtimeParams && !authToken && !options.getHeaders) {
|
|
37
39
|
console.warn('[transport-ws] getRealtimeParams sends data in URL query parameters, ' +
|
|
38
|
-
'which may be logged by proxies and CDNs.
|
|
40
|
+
'which may be logged by proxies and CDNs. Prefer cookie-based auth when possible.');
|
|
39
41
|
}
|
|
40
42
|
if (!wsUrl) {
|
|
41
43
|
throw new Error('@syncular/transport-ws: wsUrl is required when baseUrl cannot be converted');
|
|
@@ -56,6 +58,9 @@ export function createWebSocketTransport(options) {
|
|
|
56
58
|
// Presence state
|
|
57
59
|
const activePresenceScopes = new Map();
|
|
58
60
|
const presenceCallbacks = new Set();
|
|
61
|
+
// Pending WS push requests (requestId -> resolver)
|
|
62
|
+
const pendingPushRequests = new Map();
|
|
63
|
+
const wsPushTimeoutMs = Math.max(1, options.wsPushTimeoutMs ?? 1_500);
|
|
59
64
|
function setConnectionState(state) {
|
|
60
65
|
if (connectionState === state)
|
|
61
66
|
return;
|
|
@@ -103,6 +108,12 @@ export function createWebSocketTransport(options) {
|
|
|
103
108
|
function doDisconnect() {
|
|
104
109
|
clearHeartbeatTimer();
|
|
105
110
|
clearReconnectTimer();
|
|
111
|
+
// Resolve all pending WS push requests as null (triggers HTTP fallback)
|
|
112
|
+
for (const [, pending] of pendingPushRequests) {
|
|
113
|
+
clearTimeout(pending.timer);
|
|
114
|
+
pending.resolve(null);
|
|
115
|
+
}
|
|
116
|
+
pendingPushRequests.clear();
|
|
106
117
|
if (ws) {
|
|
107
118
|
try {
|
|
108
119
|
ws.onopen = null;
|
|
@@ -128,6 +139,25 @@ export function createWebSocketTransport(options) {
|
|
|
128
139
|
const data = raw.data;
|
|
129
140
|
if (!data || typeof data !== 'object')
|
|
130
141
|
return;
|
|
142
|
+
// Route push-response events to pending request resolvers
|
|
143
|
+
if (event === 'push-response') {
|
|
144
|
+
const d = data;
|
|
145
|
+
const requestId = typeof d.requestId === 'string' ? d.requestId : '';
|
|
146
|
+
const pending = pendingPushRequests.get(requestId);
|
|
147
|
+
if (pending) {
|
|
148
|
+
pendingPushRequests.delete(requestId);
|
|
149
|
+
clearTimeout(pending.timer);
|
|
150
|
+
pending.resolve({
|
|
151
|
+
ok: true,
|
|
152
|
+
status: d.status ?? 'rejected',
|
|
153
|
+
commitSeq: typeof d.commitSeq === 'number' ? d.commitSeq : undefined,
|
|
154
|
+
results: Array.isArray(d.results)
|
|
155
|
+
? d.results
|
|
156
|
+
: [],
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
131
161
|
// Route presence events to dedicated callbacks
|
|
132
162
|
if (event === 'presence') {
|
|
133
163
|
const presenceData = data.presence;
|
|
@@ -146,10 +176,11 @@ export function createWebSocketTransport(options) {
|
|
|
146
176
|
return;
|
|
147
177
|
currentEventCallback?.({ event, data });
|
|
148
178
|
}
|
|
149
|
-
function sendPresenceMessage(msg) {
|
|
150
|
-
|
|
179
|
+
function sendPresenceMessage(msg, socketArg) {
|
|
180
|
+
const target = socketArg ?? ws;
|
|
181
|
+
if (!target || target.readyState !== WebSocketImpl.OPEN)
|
|
151
182
|
return;
|
|
152
|
-
|
|
183
|
+
target.send(JSON.stringify({ type: 'presence', ...msg }));
|
|
153
184
|
}
|
|
154
185
|
async function buildUrl(clientId) {
|
|
155
186
|
if (!wsUrl)
|
|
@@ -195,23 +226,30 @@ export function createWebSocketTransport(options) {
|
|
|
195
226
|
return;
|
|
196
227
|
if (!WebSocketImpl)
|
|
197
228
|
throw new Error('WebSocketImpl is required');
|
|
229
|
+
let socket;
|
|
198
230
|
try {
|
|
199
|
-
|
|
231
|
+
socket = new WebSocketImpl(url);
|
|
200
232
|
}
|
|
201
233
|
catch {
|
|
202
234
|
doDisconnect();
|
|
203
235
|
scheduleReconnect();
|
|
204
236
|
return;
|
|
205
237
|
}
|
|
206
|
-
ws
|
|
238
|
+
ws = socket;
|
|
239
|
+
socket.onopen = async () => {
|
|
207
240
|
if (nonce !== connectNonce)
|
|
208
241
|
return;
|
|
242
|
+
if (socket !== ws)
|
|
243
|
+
return;
|
|
209
244
|
// Send auth token if provided (more secure than query params)
|
|
210
|
-
if (authToken
|
|
245
|
+
if (authToken) {
|
|
211
246
|
try {
|
|
212
247
|
const token = typeof authToken === 'function' ? await authToken() : authToken;
|
|
213
|
-
if (token &&
|
|
214
|
-
|
|
248
|
+
if (token &&
|
|
249
|
+
nonce === connectNonce &&
|
|
250
|
+
socket === ws &&
|
|
251
|
+
socket.readyState === WebSocketImpl.OPEN) {
|
|
252
|
+
socket.send(JSON.stringify({ type: 'auth', token }));
|
|
215
253
|
}
|
|
216
254
|
}
|
|
217
255
|
catch {
|
|
@@ -221,17 +259,23 @@ export function createWebSocketTransport(options) {
|
|
|
221
259
|
}
|
|
222
260
|
if (nonce !== connectNonce)
|
|
223
261
|
return;
|
|
262
|
+
if (socket !== ws)
|
|
263
|
+
return;
|
|
264
|
+
if (socket.readyState !== WebSocketImpl.OPEN)
|
|
265
|
+
return;
|
|
224
266
|
setConnectionState('connected');
|
|
225
267
|
reconnectAttempts = 0;
|
|
226
268
|
resetHeartbeatTimer();
|
|
227
269
|
// Re-join all active presence scopes on reconnect
|
|
228
270
|
for (const [scopeKey, metadata] of activePresenceScopes) {
|
|
229
|
-
sendPresenceMessage({ action: 'join', scopeKey, metadata });
|
|
271
|
+
sendPresenceMessage({ action: 'join', scopeKey, metadata }, socket);
|
|
230
272
|
}
|
|
231
273
|
};
|
|
232
|
-
|
|
274
|
+
socket.onmessage = (evt) => {
|
|
233
275
|
if (nonce !== connectNonce)
|
|
234
276
|
return;
|
|
277
|
+
if (socket !== ws)
|
|
278
|
+
return;
|
|
235
279
|
resetHeartbeatTimer();
|
|
236
280
|
if (typeof evt.data === 'string') {
|
|
237
281
|
try {
|
|
@@ -242,15 +286,19 @@ export function createWebSocketTransport(options) {
|
|
|
242
286
|
}
|
|
243
287
|
}
|
|
244
288
|
};
|
|
245
|
-
|
|
289
|
+
socket.onerror = () => {
|
|
246
290
|
if (nonce !== connectNonce)
|
|
247
291
|
return;
|
|
292
|
+
if (socket !== ws)
|
|
293
|
+
return;
|
|
248
294
|
doDisconnect();
|
|
249
295
|
scheduleReconnect();
|
|
250
296
|
};
|
|
251
|
-
|
|
297
|
+
socket.onclose = () => {
|
|
252
298
|
if (nonce !== connectNonce)
|
|
253
299
|
return;
|
|
300
|
+
if (socket !== ws)
|
|
301
|
+
return;
|
|
254
302
|
doDisconnect();
|
|
255
303
|
scheduleReconnect();
|
|
256
304
|
};
|
|
@@ -303,6 +351,33 @@ export function createWebSocketTransport(options) {
|
|
|
303
351
|
presenceCallbacks.delete(callback);
|
|
304
352
|
};
|
|
305
353
|
},
|
|
354
|
+
pushViaWs(request) {
|
|
355
|
+
if (!ws || ws.readyState !== WebSocketImpl.OPEN) {
|
|
356
|
+
return Promise.resolve(null);
|
|
357
|
+
}
|
|
358
|
+
const requestId = crypto.randomUUID();
|
|
359
|
+
return new Promise((resolve) => {
|
|
360
|
+
const timer = setTimeout(() => {
|
|
361
|
+
pendingPushRequests.delete(requestId);
|
|
362
|
+
resolve(null);
|
|
363
|
+
}, wsPushTimeoutMs);
|
|
364
|
+
pendingPushRequests.set(requestId, { resolve, timer });
|
|
365
|
+
try {
|
|
366
|
+
ws.send(JSON.stringify({
|
|
367
|
+
type: 'push',
|
|
368
|
+
requestId,
|
|
369
|
+
clientCommitId: request.clientCommitId,
|
|
370
|
+
operations: request.operations,
|
|
371
|
+
schemaVersion: request.schemaVersion,
|
|
372
|
+
}));
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
pendingPushRequests.delete(requestId);
|
|
376
|
+
clearTimeout(timer);
|
|
377
|
+
resolve(null);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
},
|
|
306
381
|
};
|
|
307
382
|
}
|
|
308
383
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/transport-ws",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2-127",
|
|
4
|
+
"description": "WebSocket transport for Syncular real-time sync",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Benjamin Kniffler",
|
|
7
|
+
"homepage": "https://syncular.dev",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/syncular/syncular.git",
|
|
11
|
+
"directory": "packages/transport-ws"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/syncular/syncular/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"sync",
|
|
18
|
+
"offline-first",
|
|
19
|
+
"realtime",
|
|
20
|
+
"database",
|
|
21
|
+
"typescript",
|
|
22
|
+
"websocket",
|
|
23
|
+
"realtime"
|
|
24
|
+
],
|
|
4
25
|
"private": false,
|
|
5
26
|
"publishConfig": {
|
|
6
27
|
"access": "public"
|
|
@@ -17,14 +38,15 @@
|
|
|
17
38
|
},
|
|
18
39
|
"scripts": {
|
|
19
40
|
"tsgo": "tsgo --noEmit",
|
|
20
|
-
"build": "
|
|
41
|
+
"build": "tsgo",
|
|
42
|
+
"release": "bunx syncular-publish"
|
|
21
43
|
},
|
|
22
44
|
"dependencies": {
|
|
23
|
-
"@syncular/core": "
|
|
24
|
-
"@syncular/transport-http": "
|
|
45
|
+
"@syncular/core": "0.0.2-127",
|
|
46
|
+
"@syncular/transport-http": "0.0.2-127"
|
|
25
47
|
},
|
|
26
48
|
"devDependencies": {
|
|
27
|
-
"@syncular/config": "
|
|
49
|
+
"@syncular/config": "0.0.0"
|
|
28
50
|
},
|
|
29
51
|
"files": [
|
|
30
52
|
"dist",
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { createWebSocketTransport } from './index';
|
|
3
|
+
|
|
4
|
+
class MockWebSocket {
|
|
5
|
+
static readonly CONNECTING = 0;
|
|
6
|
+
static readonly OPEN = 1;
|
|
7
|
+
static readonly CLOSING = 2;
|
|
8
|
+
static readonly CLOSED = 3;
|
|
9
|
+
static instances: MockWebSocket[] = [];
|
|
10
|
+
|
|
11
|
+
readonly url: string;
|
|
12
|
+
readonly sent: string[] = [];
|
|
13
|
+
readyState = MockWebSocket.CONNECTING;
|
|
14
|
+
onopen: ((ev: Event) => unknown) | null = null;
|
|
15
|
+
onmessage: ((ev: MessageEvent<string>) => unknown) | null = null;
|
|
16
|
+
onerror: ((ev: Event) => unknown) | null = null;
|
|
17
|
+
onclose: ((ev: Event) => unknown) | null = null;
|
|
18
|
+
|
|
19
|
+
constructor(url: string | URL, _protocols?: string | string[]) {
|
|
20
|
+
this.url = String(url);
|
|
21
|
+
MockWebSocket.instances.push(this);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
send(data: string): void {
|
|
25
|
+
if (this.readyState !== MockWebSocket.OPEN) {
|
|
26
|
+
throw new Error('Socket is not open');
|
|
27
|
+
}
|
|
28
|
+
this.sent.push(data);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
close(): void {
|
|
32
|
+
this.readyState = MockWebSocket.CLOSED;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async triggerOpen(): Promise<void> {
|
|
36
|
+
this.readyState = MockWebSocket.OPEN;
|
|
37
|
+
const handler = this.onopen;
|
|
38
|
+
if (!handler) return;
|
|
39
|
+
await handler(new Event('open'));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
triggerClose(): void {
|
|
43
|
+
this.readyState = MockWebSocket.CLOSED;
|
|
44
|
+
const handler = this.onclose;
|
|
45
|
+
if (!handler) return;
|
|
46
|
+
handler(new Event('close'));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function clearMockSockets(): void {
|
|
51
|
+
MockWebSocket.instances.length = 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function waitForSocket(): Promise<MockWebSocket> {
|
|
55
|
+
for (let i = 0; i < 10; i++) {
|
|
56
|
+
const socket = MockWebSocket.instances[0];
|
|
57
|
+
if (socket) return socket;
|
|
58
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
59
|
+
}
|
|
60
|
+
throw new Error('Expected a websocket instance to be created');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createDeferred<T>(): {
|
|
64
|
+
promise: Promise<T>;
|
|
65
|
+
resolve: (value: T) => void;
|
|
66
|
+
} {
|
|
67
|
+
let resolve!: (value: T) => void;
|
|
68
|
+
const promise = new Promise<T>((r) => {
|
|
69
|
+
resolve = r;
|
|
70
|
+
});
|
|
71
|
+
return { promise, resolve };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
describe('createWebSocketTransport auth flow', () => {
|
|
75
|
+
test('derives default wsUrl from baseUrl as /sync/realtime', async () => {
|
|
76
|
+
clearMockSockets();
|
|
77
|
+
const transport = createWebSocketTransport({
|
|
78
|
+
baseUrl: 'http://localhost:3000/api',
|
|
79
|
+
WebSocketImpl: MockWebSocket as typeof WebSocket,
|
|
80
|
+
reconnectJitter: 0,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const disconnect = transport.connect(
|
|
84
|
+
{ clientId: 'client-default-url' },
|
|
85
|
+
() => {}
|
|
86
|
+
);
|
|
87
|
+
const socket = await waitForSocket();
|
|
88
|
+
|
|
89
|
+
const url = new URL(socket.url);
|
|
90
|
+
expect(url.protocol).toBe('ws:');
|
|
91
|
+
expect(url.pathname).toBe('/api/sync/realtime');
|
|
92
|
+
expect(url.searchParams.get('clientId')).toBe('client-default-url');
|
|
93
|
+
expect(url.searchParams.get('transportPath')).toBe('relay');
|
|
94
|
+
|
|
95
|
+
disconnect();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('sends first-message auth token after open', async () => {
|
|
99
|
+
clearMockSockets();
|
|
100
|
+
const transport = createWebSocketTransport({
|
|
101
|
+
baseUrl: 'http://localhost:3000/api',
|
|
102
|
+
WebSocketImpl: MockWebSocket as typeof WebSocket,
|
|
103
|
+
authToken: 'token-1',
|
|
104
|
+
reconnectJitter: 0,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const disconnect = transport.connect({ clientId: 'client-1' }, () => {});
|
|
108
|
+
const socket = await waitForSocket();
|
|
109
|
+
|
|
110
|
+
await socket.triggerOpen();
|
|
111
|
+
|
|
112
|
+
expect(transport.getConnectionState()).toBe('connected');
|
|
113
|
+
expect(socket.sent).toContain(
|
|
114
|
+
JSON.stringify({ type: 'auth', token: 'token-1' })
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
disconnect();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('does not become connected when socket closes while waiting for auth token', async () => {
|
|
121
|
+
clearMockSockets();
|
|
122
|
+
const token = createDeferred<string>();
|
|
123
|
+
const transport = createWebSocketTransport({
|
|
124
|
+
baseUrl: 'http://localhost:3000/api',
|
|
125
|
+
WebSocketImpl: MockWebSocket as typeof WebSocket,
|
|
126
|
+
authToken: () => token.promise,
|
|
127
|
+
reconnectJitter: 0,
|
|
128
|
+
initialReconnectDelay: 1_000_000,
|
|
129
|
+
maxReconnectDelay: 1_000_000,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const disconnect = transport.connect({ clientId: 'client-2' }, () => {});
|
|
133
|
+
const socket = await waitForSocket();
|
|
134
|
+
|
|
135
|
+
const openPromise = socket.triggerOpen();
|
|
136
|
+
expect(transport.getConnectionState()).toBe('connecting');
|
|
137
|
+
|
|
138
|
+
socket.triggerClose();
|
|
139
|
+
expect(transport.getConnectionState()).toBe('disconnected');
|
|
140
|
+
|
|
141
|
+
token.resolve('token-2');
|
|
142
|
+
await openPromise;
|
|
143
|
+
|
|
144
|
+
expect(transport.getConnectionState()).toBe('disconnected');
|
|
145
|
+
expect(socket.sent).toEqual([]);
|
|
146
|
+
|
|
147
|
+
disconnect();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('times out ws push quickly when configured', async () => {
|
|
151
|
+
clearMockSockets();
|
|
152
|
+
const transport = createWebSocketTransport({
|
|
153
|
+
baseUrl: 'http://localhost:3000/api',
|
|
154
|
+
WebSocketImpl: MockWebSocket as typeof WebSocket,
|
|
155
|
+
reconnectJitter: 0,
|
|
156
|
+
wsPushTimeoutMs: 20,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const disconnect = transport.connect({ clientId: 'client-3' }, () => {});
|
|
160
|
+
const socket = await waitForSocket();
|
|
161
|
+
await socket.triggerOpen();
|
|
162
|
+
|
|
163
|
+
const startedAt = Date.now();
|
|
164
|
+
const response = await transport.pushViaWs({
|
|
165
|
+
clientId: 'client-3',
|
|
166
|
+
clientCommitId: 'commit-1',
|
|
167
|
+
operations: [],
|
|
168
|
+
schemaVersion: 1,
|
|
169
|
+
});
|
|
170
|
+
const elapsedMs = Date.now() - startedAt;
|
|
171
|
+
|
|
172
|
+
expect(response).toBeNull();
|
|
173
|
+
expect(elapsedMs).toBeGreaterThanOrEqual(15);
|
|
174
|
+
expect(elapsedMs).toBeLessThan(500);
|
|
175
|
+
|
|
176
|
+
const pushMessage = socket.sent.find((msg) =>
|
|
177
|
+
msg.includes('"type":"push"')
|
|
178
|
+
);
|
|
179
|
+
expect(pushMessage).toBeDefined();
|
|
180
|
+
|
|
181
|
+
disconnect();
|
|
182
|
+
});
|
|
183
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
* - Use cookie auth (same-origin) or a query-param token for the realtime URL.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
SyncPushRequest,
|
|
14
|
+
SyncPushResponse,
|
|
15
|
+
SyncTransport,
|
|
16
|
+
} from '@syncular/core';
|
|
13
17
|
import {
|
|
14
18
|
type ClientOptions,
|
|
15
19
|
createHttpTransport,
|
|
@@ -40,15 +44,35 @@ export interface PresenceEventData {
|
|
|
40
44
|
}>;
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Push response data received from the server over WS
|
|
49
|
+
*/
|
|
50
|
+
export interface WsPushResponseData {
|
|
51
|
+
requestId: string;
|
|
52
|
+
ok: boolean;
|
|
53
|
+
status: string;
|
|
54
|
+
commitSeq?: number;
|
|
55
|
+
results: Array<{ opIndex: number; status: string; [k: string]: unknown }>;
|
|
56
|
+
timestamp: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
43
59
|
/**
|
|
44
60
|
* WebSocket event from the server
|
|
45
61
|
*/
|
|
46
62
|
export interface WebSocketEvent {
|
|
47
|
-
event: 'sync' | 'heartbeat' | 'error' | 'presence';
|
|
63
|
+
event: 'sync' | 'heartbeat' | 'error' | 'presence' | 'push-response';
|
|
48
64
|
data: {
|
|
49
65
|
cursor?: number;
|
|
66
|
+
/** Inline change data for small payloads (WS data delivery) */
|
|
67
|
+
changes?: unknown[];
|
|
50
68
|
error?: string;
|
|
51
69
|
presence?: PresenceEventData;
|
|
70
|
+
/** Push response fields (for push-response events) */
|
|
71
|
+
requestId?: string;
|
|
72
|
+
ok?: boolean;
|
|
73
|
+
status?: string;
|
|
74
|
+
commitSeq?: number;
|
|
75
|
+
results?: Array<{ opIndex: number; status: string; [k: string]: unknown }>;
|
|
52
76
|
timestamp: number;
|
|
53
77
|
};
|
|
54
78
|
}
|
|
@@ -65,16 +89,17 @@ export type WebSocketStateCallback = (state: WebSocketConnectionState) => void;
|
|
|
65
89
|
|
|
66
90
|
export interface WebSocketTransportOptions extends ClientOptions {
|
|
67
91
|
/**
|
|
68
|
-
* WebSocket endpoint URL. If not provided, uses `${baseUrl}/realtime`
|
|
69
|
-
* `http(s)` -> `ws(s)` conversion when possible.
|
|
92
|
+
* WebSocket endpoint URL. If not provided, uses `${baseUrl}/sync/realtime`
|
|
93
|
+
* with `http(s)` -> `ws(s)` conversion when possible.
|
|
70
94
|
*/
|
|
71
95
|
wsUrl?: string;
|
|
72
96
|
/**
|
|
73
97
|
* Additional query params for the realtime URL (e.g. `{ token }`).
|
|
74
98
|
*
|
|
75
99
|
* ⚠️ SECURITY WARNING: Query parameters may be logged by proxies, CDNs, and
|
|
76
|
-
* browser history. Do NOT pass sensitive tokens here.
|
|
77
|
-
*
|
|
100
|
+
* browser history. Do NOT pass sensitive tokens here. Prefer cookie-based
|
|
101
|
+
* auth. Use `authToken` only with servers that explicitly support first-message
|
|
102
|
+
* WebSocket authentication.
|
|
78
103
|
*/
|
|
79
104
|
getRealtimeParams?: (args: {
|
|
80
105
|
clientId: string;
|
|
@@ -116,6 +141,11 @@ export interface WebSocketTransportOptions extends ClientOptions {
|
|
|
116
141
|
* Optional WebSocket implementation override (useful for non-browser runtimes).
|
|
117
142
|
*/
|
|
118
143
|
WebSocketImpl?: typeof WebSocket;
|
|
144
|
+
/**
|
|
145
|
+
* Timeout for waiting on WS push responses before falling back to HTTP push.
|
|
146
|
+
* Default: 1500ms
|
|
147
|
+
*/
|
|
148
|
+
wsPushTimeoutMs?: number;
|
|
119
149
|
/**
|
|
120
150
|
* Transport path telemetry sent to the server for push/pull and realtime.
|
|
121
151
|
* Defaults to 'relay' for this transport.
|
|
@@ -143,6 +173,11 @@ export interface WebSocketTransport extends SyncTransport {
|
|
|
143
173
|
sendPresenceLeave(scopeKey: string): void;
|
|
144
174
|
sendPresenceUpdate(scopeKey: string, metadata: Record<string, unknown>): void;
|
|
145
175
|
onPresenceEvent(callback: PresenceEventCallback): () => void;
|
|
176
|
+
/**
|
|
177
|
+
* Push a commit via WebSocket (bypasses HTTP).
|
|
178
|
+
* Returns `null` if WS is not connected or times out (caller should fall back to HTTP).
|
|
179
|
+
*/
|
|
180
|
+
pushViaWs(request: SyncPushRequest): Promise<SyncPushResponse | null>;
|
|
146
181
|
}
|
|
147
182
|
|
|
148
183
|
function defaultWsUrl(baseUrl: string): string | null {
|
|
@@ -154,7 +189,7 @@ function defaultWsUrl(baseUrl: string): string | null {
|
|
|
154
189
|
: new URL(baseUrl, location.origin);
|
|
155
190
|
|
|
156
191
|
resolved.protocol = resolved.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
157
|
-
resolved.pathname = `${resolved.pathname.replace(/\/$/, '')}/realtime`;
|
|
192
|
+
resolved.pathname = `${resolved.pathname.replace(/\/$/, '')}/sync/realtime`;
|
|
158
193
|
return resolved.toString();
|
|
159
194
|
} catch {
|
|
160
195
|
return null;
|
|
@@ -184,11 +219,13 @@ export function createWebSocketTransport(
|
|
|
184
219
|
WebSocketImpl = typeof WebSocket !== 'undefined' ? WebSocket : undefined,
|
|
185
220
|
} = options;
|
|
186
221
|
|
|
187
|
-
// Warn about security risk of using getRealtimeParams with sensitive data
|
|
188
|
-
if
|
|
222
|
+
// Warn about security risk of using getRealtimeParams with sensitive data,
|
|
223
|
+
// but only if the consumer hasn't also provided getHeaders or authToken
|
|
224
|
+
// (which indicates intentional auth handling with query-param fallback).
|
|
225
|
+
if (getRealtimeParams && !authToken && !options.getHeaders) {
|
|
189
226
|
console.warn(
|
|
190
227
|
'[transport-ws] getRealtimeParams sends data in URL query parameters, ' +
|
|
191
|
-
'which may be logged by proxies and CDNs.
|
|
228
|
+
'which may be logged by proxies and CDNs. Prefer cookie-based auth when possible.'
|
|
192
229
|
);
|
|
193
230
|
}
|
|
194
231
|
|
|
@@ -223,6 +260,16 @@ export function createWebSocketTransport(
|
|
|
223
260
|
>();
|
|
224
261
|
const presenceCallbacks = new Set<PresenceEventCallback>();
|
|
225
262
|
|
|
263
|
+
// Pending WS push requests (requestId -> resolver)
|
|
264
|
+
const pendingPushRequests = new Map<
|
|
265
|
+
string,
|
|
266
|
+
{
|
|
267
|
+
resolve: (value: SyncPushResponse | null) => void;
|
|
268
|
+
timer: ReturnType<typeof setTimeout>;
|
|
269
|
+
}
|
|
270
|
+
>();
|
|
271
|
+
const wsPushTimeoutMs = Math.max(1, options.wsPushTimeoutMs ?? 1_500);
|
|
272
|
+
|
|
226
273
|
function setConnectionState(state: WebSocketConnectionState): void {
|
|
227
274
|
if (connectionState === state) return;
|
|
228
275
|
connectionState = state;
|
|
@@ -278,6 +325,13 @@ export function createWebSocketTransport(
|
|
|
278
325
|
clearHeartbeatTimer();
|
|
279
326
|
clearReconnectTimer();
|
|
280
327
|
|
|
328
|
+
// Resolve all pending WS push requests as null (triggers HTTP fallback)
|
|
329
|
+
for (const [, pending] of pendingPushRequests) {
|
|
330
|
+
clearTimeout(pending.timer);
|
|
331
|
+
pending.resolve(null);
|
|
332
|
+
}
|
|
333
|
+
pendingPushRequests.clear();
|
|
334
|
+
|
|
281
335
|
if (ws) {
|
|
282
336
|
try {
|
|
283
337
|
ws.onopen = null;
|
|
@@ -303,6 +357,26 @@ export function createWebSocketTransport(
|
|
|
303
357
|
const data = (raw as { data: unknown }).data;
|
|
304
358
|
if (!data || typeof data !== 'object') return;
|
|
305
359
|
|
|
360
|
+
// Route push-response events to pending request resolvers
|
|
361
|
+
if (event === 'push-response') {
|
|
362
|
+
const d = data as Record<string, unknown>;
|
|
363
|
+
const requestId = typeof d.requestId === 'string' ? d.requestId : '';
|
|
364
|
+
const pending = pendingPushRequests.get(requestId);
|
|
365
|
+
if (pending) {
|
|
366
|
+
pendingPushRequests.delete(requestId);
|
|
367
|
+
clearTimeout(pending.timer);
|
|
368
|
+
pending.resolve({
|
|
369
|
+
ok: true as const,
|
|
370
|
+
status: (d.status as 'applied' | 'cached' | 'rejected') ?? 'rejected',
|
|
371
|
+
commitSeq: typeof d.commitSeq === 'number' ? d.commitSeq : undefined,
|
|
372
|
+
results: Array.isArray(d.results)
|
|
373
|
+
? (d.results as SyncPushResponse['results'])
|
|
374
|
+
: [],
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
306
380
|
// Route presence events to dedicated callbacks
|
|
307
381
|
if (event === 'presence') {
|
|
308
382
|
const presenceData = (data as { presence?: unknown }).presence;
|
|
@@ -322,9 +396,13 @@ export function createWebSocketTransport(
|
|
|
322
396
|
currentEventCallback?.({ event, data } as WebSocketEvent);
|
|
323
397
|
}
|
|
324
398
|
|
|
325
|
-
function sendPresenceMessage(
|
|
326
|
-
|
|
327
|
-
|
|
399
|
+
function sendPresenceMessage(
|
|
400
|
+
msg: Record<string, unknown>,
|
|
401
|
+
socketArg?: WebSocket
|
|
402
|
+
): void {
|
|
403
|
+
const target = socketArg ?? ws;
|
|
404
|
+
if (!target || target.readyState !== WebSocketImpl!.OPEN) return;
|
|
405
|
+
target.send(JSON.stringify({ type: 'presence', ...msg }));
|
|
328
406
|
}
|
|
329
407
|
|
|
330
408
|
async function buildUrl(clientId: string): Promise<string> {
|
|
@@ -371,24 +449,32 @@ export function createWebSocketTransport(
|
|
|
371
449
|
|
|
372
450
|
if (!WebSocketImpl) throw new Error('WebSocketImpl is required');
|
|
373
451
|
|
|
452
|
+
let socket: WebSocket;
|
|
374
453
|
try {
|
|
375
|
-
|
|
454
|
+
socket = new WebSocketImpl(url);
|
|
376
455
|
} catch {
|
|
377
456
|
doDisconnect();
|
|
378
457
|
scheduleReconnect();
|
|
379
458
|
return;
|
|
380
459
|
}
|
|
460
|
+
ws = socket;
|
|
381
461
|
|
|
382
|
-
|
|
462
|
+
socket.onopen = async () => {
|
|
383
463
|
if (nonce !== connectNonce) return;
|
|
464
|
+
if (socket !== ws) return;
|
|
384
465
|
|
|
385
466
|
// Send auth token if provided (more secure than query params)
|
|
386
|
-
if (authToken
|
|
467
|
+
if (authToken) {
|
|
387
468
|
try {
|
|
388
469
|
const token =
|
|
389
470
|
typeof authToken === 'function' ? await authToken() : authToken;
|
|
390
|
-
if (
|
|
391
|
-
|
|
471
|
+
if (
|
|
472
|
+
token &&
|
|
473
|
+
nonce === connectNonce &&
|
|
474
|
+
socket === ws &&
|
|
475
|
+
socket.readyState === WebSocketImpl.OPEN
|
|
476
|
+
) {
|
|
477
|
+
socket.send(JSON.stringify({ type: 'auth', token }));
|
|
392
478
|
}
|
|
393
479
|
} catch {
|
|
394
480
|
// Auth token failed, but connection is still open
|
|
@@ -397,18 +483,21 @@ export function createWebSocketTransport(
|
|
|
397
483
|
}
|
|
398
484
|
|
|
399
485
|
if (nonce !== connectNonce) return;
|
|
486
|
+
if (socket !== ws) return;
|
|
487
|
+
if (socket.readyState !== WebSocketImpl.OPEN) return;
|
|
400
488
|
setConnectionState('connected');
|
|
401
489
|
reconnectAttempts = 0;
|
|
402
490
|
resetHeartbeatTimer();
|
|
403
491
|
|
|
404
492
|
// Re-join all active presence scopes on reconnect
|
|
405
493
|
for (const [scopeKey, metadata] of activePresenceScopes) {
|
|
406
|
-
sendPresenceMessage({ action: 'join', scopeKey, metadata });
|
|
494
|
+
sendPresenceMessage({ action: 'join', scopeKey, metadata }, socket);
|
|
407
495
|
}
|
|
408
496
|
};
|
|
409
497
|
|
|
410
|
-
|
|
498
|
+
socket.onmessage = (evt) => {
|
|
411
499
|
if (nonce !== connectNonce) return;
|
|
500
|
+
if (socket !== ws) return;
|
|
412
501
|
resetHeartbeatTimer();
|
|
413
502
|
|
|
414
503
|
if (typeof evt.data === 'string') {
|
|
@@ -420,14 +509,16 @@ export function createWebSocketTransport(
|
|
|
420
509
|
}
|
|
421
510
|
};
|
|
422
511
|
|
|
423
|
-
|
|
512
|
+
socket.onerror = () => {
|
|
424
513
|
if (nonce !== connectNonce) return;
|
|
514
|
+
if (socket !== ws) return;
|
|
425
515
|
doDisconnect();
|
|
426
516
|
scheduleReconnect();
|
|
427
517
|
};
|
|
428
518
|
|
|
429
|
-
|
|
519
|
+
socket.onclose = () => {
|
|
430
520
|
if (nonce !== connectNonce) return;
|
|
521
|
+
if (socket !== ws) return;
|
|
431
522
|
doDisconnect();
|
|
432
523
|
scheduleReconnect();
|
|
433
524
|
};
|
|
@@ -480,5 +571,37 @@ export function createWebSocketTransport(
|
|
|
480
571
|
presenceCallbacks.delete(callback);
|
|
481
572
|
};
|
|
482
573
|
},
|
|
574
|
+
pushViaWs(request: SyncPushRequest): Promise<SyncPushResponse | null> {
|
|
575
|
+
if (!ws || ws.readyState !== WebSocketImpl!.OPEN) {
|
|
576
|
+
return Promise.resolve(null);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const requestId = crypto.randomUUID();
|
|
580
|
+
|
|
581
|
+
return new Promise<SyncPushResponse | null>((resolve) => {
|
|
582
|
+
const timer = setTimeout(() => {
|
|
583
|
+
pendingPushRequests.delete(requestId);
|
|
584
|
+
resolve(null);
|
|
585
|
+
}, wsPushTimeoutMs);
|
|
586
|
+
|
|
587
|
+
pendingPushRequests.set(requestId, { resolve, timer });
|
|
588
|
+
|
|
589
|
+
try {
|
|
590
|
+
ws!.send(
|
|
591
|
+
JSON.stringify({
|
|
592
|
+
type: 'push',
|
|
593
|
+
requestId,
|
|
594
|
+
clientCommitId: request.clientCommitId,
|
|
595
|
+
operations: request.operations,
|
|
596
|
+
schemaVersion: request.schemaVersion,
|
|
597
|
+
})
|
|
598
|
+
);
|
|
599
|
+
} catch {
|
|
600
|
+
pendingPushRequests.delete(requestId);
|
|
601
|
+
clearTimeout(timer);
|
|
602
|
+
resolve(null);
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
},
|
|
483
606
|
};
|
|
484
607
|
}
|