@koolbase/core 12.11.0 → 12.11.1
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/cjs/realtime.d.ts +12 -0
- package/dist/cjs/realtime.js +81 -7
- package/dist/esm/realtime.d.ts +12 -0
- package/dist/esm/realtime.js +81 -7
- package/package.json +1 -1
package/dist/cjs/realtime.d.ts
CHANGED
|
@@ -9,6 +9,11 @@ export declare class KoolbaseRealtime {
|
|
|
9
9
|
private reconnectAttempts;
|
|
10
10
|
private reconnectTimer;
|
|
11
11
|
private connecting;
|
|
12
|
+
private idleTimer;
|
|
13
|
+
/** How long an unused connection stays open, in ms: a screen that comes
|
|
14
|
+
* straight back (navigating back, React's development double start)
|
|
15
|
+
* reuses it instead of opening another. */
|
|
16
|
+
static idleGraceMs: number;
|
|
12
17
|
/**
|
|
13
18
|
* Identifies whose cache a seen record belongs in.
|
|
14
19
|
*
|
|
@@ -39,6 +44,13 @@ export declare class KoolbaseRealtime {
|
|
|
39
44
|
* does not accumulate delay.
|
|
40
45
|
*/
|
|
41
46
|
private scheduleReconnect;
|
|
47
|
+
/**
|
|
48
|
+
* Closes the connection once nobody is subscribed -- after a moment, not at
|
|
49
|
+
* once: a screen that unmounts and mounts again keeps the connection it had.
|
|
50
|
+
* A subscribe within the moment cancels the close. Nothing reconnects after
|
|
51
|
+
* it: reconnecting needs a subscriber.
|
|
52
|
+
*/
|
|
53
|
+
private scheduleIdleClose;
|
|
42
54
|
disconnect(): void;
|
|
43
55
|
}
|
|
44
56
|
export {};
|
package/dist/cjs/realtime.js
CHANGED
|
@@ -41,6 +41,9 @@ class KoolbaseRealtime {
|
|
|
41
41
|
this.reconnectAttempts = 0;
|
|
42
42
|
this.reconnectTimer = null;
|
|
43
43
|
this.connecting = false;
|
|
44
|
+
// Pending close of an unused connection (scheduleIdleClose); null while
|
|
45
|
+
// anyone is subscribed.
|
|
46
|
+
this.idleTimer = null;
|
|
44
47
|
this.config = config;
|
|
45
48
|
this.getToken = getToken;
|
|
46
49
|
this.getUserId = getUserId;
|
|
@@ -61,6 +64,10 @@ class KoolbaseRealtime {
|
|
|
61
64
|
await (0, cache_store_js_1.removeCachedRecord)(userId, recordId);
|
|
62
65
|
}
|
|
63
66
|
subscribe(collection, callback) {
|
|
67
|
+
if (this.idleTimer) {
|
|
68
|
+
clearTimeout(this.idleTimer);
|
|
69
|
+
this.idleTimer = null;
|
|
70
|
+
}
|
|
64
71
|
if (!this.listeners.has(collection))
|
|
65
72
|
this.listeners.set(collection, []);
|
|
66
73
|
this.listeners.get(collection).push(callback);
|
|
@@ -71,13 +78,16 @@ class KoolbaseRealtime {
|
|
|
71
78
|
void this.connect();
|
|
72
79
|
}
|
|
73
80
|
return () => {
|
|
74
|
-
const callbacks = this.listeners.get(collection)
|
|
75
|
-
const i = callbacks.indexOf(callback);
|
|
76
|
-
if (i
|
|
77
|
-
|
|
81
|
+
const callbacks = this.listeners.get(collection);
|
|
82
|
+
const i = callbacks ? callbacks.indexOf(callback) : -1;
|
|
83
|
+
if (!callbacks || i === -1)
|
|
84
|
+
return; // already unsubscribed: a second call does nothing
|
|
85
|
+
callbacks.splice(i, 1);
|
|
78
86
|
if (callbacks.length === 0) {
|
|
79
87
|
this.listeners.delete(collection);
|
|
80
88
|
this.sendUnsubscribe(collection);
|
|
89
|
+
if (this.listeners.size === 0)
|
|
90
|
+
this.scheduleIdleClose();
|
|
81
91
|
}
|
|
82
92
|
};
|
|
83
93
|
}
|
|
@@ -86,15 +96,39 @@ class KoolbaseRealtime {
|
|
|
86
96
|
return;
|
|
87
97
|
if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING))
|
|
88
98
|
return;
|
|
89
|
-
|
|
99
|
+
// Claimed BEFORE the token is awaited. Claimed after it, every subscribe
|
|
100
|
+
// arriving meanwhile -- two live lists on one screen, React's development
|
|
101
|
+
// double start -- found nobody connecting and opened its own socket; only
|
|
102
|
+
// the last was remembered, and the others stayed open for good.
|
|
103
|
+
this.connecting = true;
|
|
104
|
+
let token;
|
|
105
|
+
try {
|
|
106
|
+
token = await this.getToken();
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
token = null;
|
|
110
|
+
}
|
|
111
|
+
if (this.listeners.size === 0) {
|
|
112
|
+
// Everyone left while the token was on its way: nothing to connect for.
|
|
113
|
+
this.connecting = false;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
90
116
|
if (!token) {
|
|
117
|
+
this.connecting = false;
|
|
91
118
|
this.scheduleReconnect(); // sign-in may be in flight
|
|
92
119
|
return;
|
|
93
120
|
}
|
|
94
121
|
this.projectId = projectIdFromToken(token);
|
|
95
|
-
this.connecting = true;
|
|
96
122
|
const wsUrl = this.config.baseUrl.replace('https://', 'wss://').replace('http://', 'ws://');
|
|
97
|
-
|
|
123
|
+
let ws;
|
|
124
|
+
try {
|
|
125
|
+
ws = new WebSocket(`${wsUrl}/v1/realtime/ws?token=${encodeURIComponent(token)}`);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
this.connecting = false;
|
|
129
|
+
this.scheduleReconnect();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
98
132
|
this.ws = ws;
|
|
99
133
|
ws.onopen = () => {
|
|
100
134
|
this.connecting = false;
|
|
@@ -187,11 +221,47 @@ class KoolbaseRealtime {
|
|
|
187
221
|
}, delay);
|
|
188
222
|
this.reconnectTimer.unref?.();
|
|
189
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Closes the connection once nobody is subscribed -- after a moment, not at
|
|
226
|
+
* once: a screen that unmounts and mounts again keeps the connection it had.
|
|
227
|
+
* A subscribe within the moment cancels the close. Nothing reconnects after
|
|
228
|
+
* it: reconnecting needs a subscriber.
|
|
229
|
+
*/
|
|
230
|
+
scheduleIdleClose() {
|
|
231
|
+
if (this.idleTimer)
|
|
232
|
+
clearTimeout(this.idleTimer);
|
|
233
|
+
this.idleTimer = setTimeout(() => {
|
|
234
|
+
this.idleTimer = null;
|
|
235
|
+
if (this.listeners.size > 0)
|
|
236
|
+
return;
|
|
237
|
+
if (this.reconnectTimer) {
|
|
238
|
+
clearTimeout(this.reconnectTimer);
|
|
239
|
+
this.reconnectTimer = null;
|
|
240
|
+
}
|
|
241
|
+
const ws = this.ws;
|
|
242
|
+
this.ws = null;
|
|
243
|
+
this.connecting = false;
|
|
244
|
+
if (ws) {
|
|
245
|
+
// Detached first: this close is ours, not a dropped connection.
|
|
246
|
+
ws.onopen = null;
|
|
247
|
+
ws.onmessage = null;
|
|
248
|
+
ws.onclose = null;
|
|
249
|
+
ws.onerror = null;
|
|
250
|
+
ws.close();
|
|
251
|
+
}
|
|
252
|
+
}, KoolbaseRealtime.idleGraceMs);
|
|
253
|
+
this.idleTimer.unref?.();
|
|
254
|
+
}
|
|
190
255
|
disconnect() {
|
|
256
|
+
if (this.idleTimer) {
|
|
257
|
+
clearTimeout(this.idleTimer);
|
|
258
|
+
this.idleTimer = null;
|
|
259
|
+
}
|
|
191
260
|
if (this.reconnectTimer) {
|
|
192
261
|
clearTimeout(this.reconnectTimer);
|
|
193
262
|
this.reconnectTimer = null;
|
|
194
263
|
}
|
|
264
|
+
this.connecting = false;
|
|
195
265
|
this.ws?.close();
|
|
196
266
|
this.ws = null;
|
|
197
267
|
this.projectId = null;
|
|
@@ -199,3 +269,7 @@ class KoolbaseRealtime {
|
|
|
199
269
|
}
|
|
200
270
|
}
|
|
201
271
|
exports.KoolbaseRealtime = KoolbaseRealtime;
|
|
272
|
+
/** How long an unused connection stays open, in ms: a screen that comes
|
|
273
|
+
* straight back (navigating back, React's development double start)
|
|
274
|
+
* reuses it instead of opening another. */
|
|
275
|
+
KoolbaseRealtime.idleGraceMs = 1000;
|
package/dist/esm/realtime.d.ts
CHANGED
|
@@ -9,6 +9,11 @@ export declare class KoolbaseRealtime {
|
|
|
9
9
|
private reconnectAttempts;
|
|
10
10
|
private reconnectTimer;
|
|
11
11
|
private connecting;
|
|
12
|
+
private idleTimer;
|
|
13
|
+
/** How long an unused connection stays open, in ms: a screen that comes
|
|
14
|
+
* straight back (navigating back, React's development double start)
|
|
15
|
+
* reuses it instead of opening another. */
|
|
16
|
+
static idleGraceMs: number;
|
|
12
17
|
/**
|
|
13
18
|
* Identifies whose cache a seen record belongs in.
|
|
14
19
|
*
|
|
@@ -39,6 +44,13 @@ export declare class KoolbaseRealtime {
|
|
|
39
44
|
* does not accumulate delay.
|
|
40
45
|
*/
|
|
41
46
|
private scheduleReconnect;
|
|
47
|
+
/**
|
|
48
|
+
* Closes the connection once nobody is subscribed -- after a moment, not at
|
|
49
|
+
* once: a screen that unmounts and mounts again keeps the connection it had.
|
|
50
|
+
* A subscribe within the moment cancels the close. Nothing reconnects after
|
|
51
|
+
* it: reconnecting needs a subscriber.
|
|
52
|
+
*/
|
|
53
|
+
private scheduleIdleClose;
|
|
42
54
|
disconnect(): void;
|
|
43
55
|
}
|
|
44
56
|
export {};
|
package/dist/esm/realtime.js
CHANGED
|
@@ -38,6 +38,9 @@ export class KoolbaseRealtime {
|
|
|
38
38
|
this.reconnectAttempts = 0;
|
|
39
39
|
this.reconnectTimer = null;
|
|
40
40
|
this.connecting = false;
|
|
41
|
+
// Pending close of an unused connection (scheduleIdleClose); null while
|
|
42
|
+
// anyone is subscribed.
|
|
43
|
+
this.idleTimer = null;
|
|
41
44
|
this.config = config;
|
|
42
45
|
this.getToken = getToken;
|
|
43
46
|
this.getUserId = getUserId;
|
|
@@ -58,6 +61,10 @@ export class KoolbaseRealtime {
|
|
|
58
61
|
await removeCachedRecord(userId, recordId);
|
|
59
62
|
}
|
|
60
63
|
subscribe(collection, callback) {
|
|
64
|
+
if (this.idleTimer) {
|
|
65
|
+
clearTimeout(this.idleTimer);
|
|
66
|
+
this.idleTimer = null;
|
|
67
|
+
}
|
|
61
68
|
if (!this.listeners.has(collection))
|
|
62
69
|
this.listeners.set(collection, []);
|
|
63
70
|
this.listeners.get(collection).push(callback);
|
|
@@ -68,13 +75,16 @@ export class KoolbaseRealtime {
|
|
|
68
75
|
void this.connect();
|
|
69
76
|
}
|
|
70
77
|
return () => {
|
|
71
|
-
const callbacks = this.listeners.get(collection)
|
|
72
|
-
const i = callbacks.indexOf(callback);
|
|
73
|
-
if (i
|
|
74
|
-
|
|
78
|
+
const callbacks = this.listeners.get(collection);
|
|
79
|
+
const i = callbacks ? callbacks.indexOf(callback) : -1;
|
|
80
|
+
if (!callbacks || i === -1)
|
|
81
|
+
return; // already unsubscribed: a second call does nothing
|
|
82
|
+
callbacks.splice(i, 1);
|
|
75
83
|
if (callbacks.length === 0) {
|
|
76
84
|
this.listeners.delete(collection);
|
|
77
85
|
this.sendUnsubscribe(collection);
|
|
86
|
+
if (this.listeners.size === 0)
|
|
87
|
+
this.scheduleIdleClose();
|
|
78
88
|
}
|
|
79
89
|
};
|
|
80
90
|
}
|
|
@@ -83,15 +93,39 @@ export class KoolbaseRealtime {
|
|
|
83
93
|
return;
|
|
84
94
|
if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING))
|
|
85
95
|
return;
|
|
86
|
-
|
|
96
|
+
// Claimed BEFORE the token is awaited. Claimed after it, every subscribe
|
|
97
|
+
// arriving meanwhile -- two live lists on one screen, React's development
|
|
98
|
+
// double start -- found nobody connecting and opened its own socket; only
|
|
99
|
+
// the last was remembered, and the others stayed open for good.
|
|
100
|
+
this.connecting = true;
|
|
101
|
+
let token;
|
|
102
|
+
try {
|
|
103
|
+
token = await this.getToken();
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
token = null;
|
|
107
|
+
}
|
|
108
|
+
if (this.listeners.size === 0) {
|
|
109
|
+
// Everyone left while the token was on its way: nothing to connect for.
|
|
110
|
+
this.connecting = false;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
87
113
|
if (!token) {
|
|
114
|
+
this.connecting = false;
|
|
88
115
|
this.scheduleReconnect(); // sign-in may be in flight
|
|
89
116
|
return;
|
|
90
117
|
}
|
|
91
118
|
this.projectId = projectIdFromToken(token);
|
|
92
|
-
this.connecting = true;
|
|
93
119
|
const wsUrl = this.config.baseUrl.replace('https://', 'wss://').replace('http://', 'ws://');
|
|
94
|
-
|
|
120
|
+
let ws;
|
|
121
|
+
try {
|
|
122
|
+
ws = new WebSocket(`${wsUrl}/v1/realtime/ws?token=${encodeURIComponent(token)}`);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
this.connecting = false;
|
|
126
|
+
this.scheduleReconnect();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
95
129
|
this.ws = ws;
|
|
96
130
|
ws.onopen = () => {
|
|
97
131
|
this.connecting = false;
|
|
@@ -184,14 +218,54 @@ export class KoolbaseRealtime {
|
|
|
184
218
|
}, delay);
|
|
185
219
|
this.reconnectTimer.unref?.();
|
|
186
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Closes the connection once nobody is subscribed -- after a moment, not at
|
|
223
|
+
* once: a screen that unmounts and mounts again keeps the connection it had.
|
|
224
|
+
* A subscribe within the moment cancels the close. Nothing reconnects after
|
|
225
|
+
* it: reconnecting needs a subscriber.
|
|
226
|
+
*/
|
|
227
|
+
scheduleIdleClose() {
|
|
228
|
+
if (this.idleTimer)
|
|
229
|
+
clearTimeout(this.idleTimer);
|
|
230
|
+
this.idleTimer = setTimeout(() => {
|
|
231
|
+
this.idleTimer = null;
|
|
232
|
+
if (this.listeners.size > 0)
|
|
233
|
+
return;
|
|
234
|
+
if (this.reconnectTimer) {
|
|
235
|
+
clearTimeout(this.reconnectTimer);
|
|
236
|
+
this.reconnectTimer = null;
|
|
237
|
+
}
|
|
238
|
+
const ws = this.ws;
|
|
239
|
+
this.ws = null;
|
|
240
|
+
this.connecting = false;
|
|
241
|
+
if (ws) {
|
|
242
|
+
// Detached first: this close is ours, not a dropped connection.
|
|
243
|
+
ws.onopen = null;
|
|
244
|
+
ws.onmessage = null;
|
|
245
|
+
ws.onclose = null;
|
|
246
|
+
ws.onerror = null;
|
|
247
|
+
ws.close();
|
|
248
|
+
}
|
|
249
|
+
}, KoolbaseRealtime.idleGraceMs);
|
|
250
|
+
this.idleTimer.unref?.();
|
|
251
|
+
}
|
|
187
252
|
disconnect() {
|
|
253
|
+
if (this.idleTimer) {
|
|
254
|
+
clearTimeout(this.idleTimer);
|
|
255
|
+
this.idleTimer = null;
|
|
256
|
+
}
|
|
188
257
|
if (this.reconnectTimer) {
|
|
189
258
|
clearTimeout(this.reconnectTimer);
|
|
190
259
|
this.reconnectTimer = null;
|
|
191
260
|
}
|
|
261
|
+
this.connecting = false;
|
|
192
262
|
this.ws?.close();
|
|
193
263
|
this.ws = null;
|
|
194
264
|
this.projectId = null;
|
|
195
265
|
this.listeners.clear();
|
|
196
266
|
}
|
|
197
267
|
}
|
|
268
|
+
/** How long an unused connection stays open, in ms: a screen that comes
|
|
269
|
+
* straight back (navigating back, React's development double start)
|
|
270
|
+
* reuses it instead of opening another. */
|
|
271
|
+
KoolbaseRealtime.idleGraceMs = 1000;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koolbase/core",
|
|
3
|
-
"version": "12.11.
|
|
3
|
+
"version": "12.11.1",
|
|
4
4
|
"description": "Koolbase SDK core \u2014 shared behaviour behind @koolbase/react-native and @koolbase/js. Install one of those, not this.",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"types": "./dist/esm/index.d.ts",
|