@aerostack/sdk-node 0.6.6 → 0.6.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.
- package/dist/commonjs/sdk/realtime.d.ts +40 -19
- package/dist/commonjs/sdk/realtime.d.ts.map +1 -1
- package/dist/commonjs/sdk/realtime.js +96 -38
- package/dist/commonjs/sdk/realtime.js.map +1 -1
- package/dist/esm/sdk/realtime.d.ts +40 -19
- package/dist/esm/sdk/realtime.d.ts.map +1 -1
- package/dist/esm/sdk/realtime.js +96 -38
- package/dist/esm/sdk/realtime.js.map +1 -1
- package/package.json +2 -2
- package/src/sdk/realtime.ts +117 -63
|
@@ -1,41 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Aerostack Realtime Client for Node.js SDK
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* This is NOT auto-generated — it is a hand-written extension for the Node SDK.
|
|
3
|
+
* Production-hardened WebSocket client for server-side usage.
|
|
6
4
|
*/
|
|
7
|
-
type RealtimeEvent = 'INSERT' | 'UPDATE' | 'DELETE' | '*';
|
|
8
|
-
interface
|
|
5
|
+
export type RealtimeEvent = 'INSERT' | 'UPDATE' | 'DELETE' | '*';
|
|
6
|
+
export interface RealtimeMessage {
|
|
7
|
+
type: string;
|
|
8
|
+
topic: string;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
}
|
|
11
|
+
export interface RealtimeSubscriptionOptions {
|
|
9
12
|
event?: RealtimeEvent;
|
|
10
13
|
filter?: Record<string, any>;
|
|
11
14
|
}
|
|
12
|
-
type RealtimeCallback = (payload:
|
|
13
|
-
interface
|
|
14
|
-
|
|
15
|
+
export type RealtimeCallback<T = any> = (payload: RealtimePayload<T>) => void;
|
|
16
|
+
export interface RealtimePayload<T = any> {
|
|
17
|
+
type: 'db_change' | 'chat_message';
|
|
18
|
+
topic: string;
|
|
19
|
+
operation: RealtimeEvent;
|
|
20
|
+
data: T;
|
|
21
|
+
old?: T;
|
|
22
|
+
timestamp?: string;
|
|
23
|
+
[key: string]: any;
|
|
24
|
+
}
|
|
25
|
+
export interface NodeRealtimeOptions {
|
|
15
26
|
serverUrl: string;
|
|
16
|
-
/** Project ID to subscribe to */
|
|
17
27
|
projectId: string;
|
|
18
|
-
/** API Key for authentication (recommended for server-side) */
|
|
19
28
|
apiKey?: string | undefined;
|
|
20
|
-
/** User ID (optional) */
|
|
21
29
|
userId?: string | undefined;
|
|
22
|
-
/** Bearer token (optional, for user-context connections) */
|
|
23
30
|
token?: string | undefined;
|
|
31
|
+
maxReconnectAttempts?: number;
|
|
24
32
|
}
|
|
25
|
-
export declare class RealtimeSubscription {
|
|
33
|
+
export declare class RealtimeSubscription<T = any> {
|
|
26
34
|
private client;
|
|
27
35
|
private topic;
|
|
28
36
|
private options;
|
|
29
37
|
private callbacks;
|
|
30
38
|
private _isSubscribed;
|
|
31
39
|
constructor(client: NodeRealtimeClient, topic: string, options?: RealtimeSubscriptionOptions);
|
|
32
|
-
on(
|
|
40
|
+
on(event: RealtimeEvent, callback: RealtimeCallback<T>): this;
|
|
33
41
|
subscribe(): this;
|
|
34
42
|
unsubscribe(): void;
|
|
35
43
|
get isSubscribed(): boolean;
|
|
36
44
|
/** @internal */
|
|
37
|
-
_emit(payload:
|
|
45
|
+
_emit(payload: RealtimePayload<T>): void;
|
|
38
46
|
}
|
|
47
|
+
export type RealtimeStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected';
|
|
39
48
|
export declare class NodeRealtimeClient {
|
|
40
49
|
private wsUrl;
|
|
41
50
|
private projectId;
|
|
@@ -47,20 +56,32 @@ export declare class NodeRealtimeClient {
|
|
|
47
56
|
private reconnectTimer;
|
|
48
57
|
private heartbeatTimer;
|
|
49
58
|
private reconnectAttempts;
|
|
50
|
-
private
|
|
59
|
+
private _sendQueue;
|
|
60
|
+
private _connectingPromise;
|
|
61
|
+
private _status;
|
|
62
|
+
private _statusListeners;
|
|
63
|
+
private _lastPong;
|
|
64
|
+
private _maxReconnectAttempts;
|
|
65
|
+
private _maxRetriesListeners;
|
|
51
66
|
constructor(options: NodeRealtimeOptions);
|
|
67
|
+
get status(): RealtimeStatus;
|
|
52
68
|
get connected(): boolean;
|
|
69
|
+
onStatusChange(cb: (status: RealtimeStatus) => void): () => void;
|
|
70
|
+
onMaxRetriesExceeded(cb: () => void): () => void;
|
|
71
|
+
private _setStatus;
|
|
72
|
+
setToken(newToken: string): void;
|
|
53
73
|
connect(): Promise<void>;
|
|
74
|
+
private _doConnect;
|
|
54
75
|
disconnect(): void;
|
|
55
|
-
channel(topic: string, options?: RealtimeSubscriptionOptions): RealtimeSubscription
|
|
76
|
+
channel<T = any>(topic: string, options?: RealtimeSubscriptionOptions): RealtimeSubscription<T>;
|
|
77
|
+
sendChat(roomId: string, text: string): void;
|
|
78
|
+
chatRoom(roomId: string): RealtimeSubscription;
|
|
56
79
|
/** @internal */
|
|
57
80
|
_send(data: any): void;
|
|
58
81
|
private handleMessage;
|
|
59
82
|
private startHeartbeat;
|
|
60
83
|
private stopHeartbeat;
|
|
61
|
-
/** Exponential backoff with jitter */
|
|
62
84
|
private scheduleReconnect;
|
|
63
85
|
private stopReconnect;
|
|
64
86
|
}
|
|
65
|
-
export {};
|
|
66
87
|
//# sourceMappingURL=realtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../../../src/sdk/realtime.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../../../src/sdk/realtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Aerostack Realtime Client for Node.js SDK
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* This is NOT auto-generated — it is a hand-written extension for the Node SDK.
|
|
4
|
+
* Production-hardened WebSocket client for server-side usage.
|
|
7
5
|
*/
|
|
8
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
7
|
exports.NodeRealtimeClient = exports.RealtimeSubscription = void 0;
|
|
10
|
-
// Constants for exponential backoff
|
|
11
8
|
const BASE_RECONNECT_MS = 1000;
|
|
12
9
|
const MAX_RECONNECT_MS = 30000;
|
|
13
10
|
const JITTER_FACTOR = 0.3;
|
|
14
11
|
class RealtimeSubscription {
|
|
15
12
|
constructor(client, topic, options = {}) {
|
|
16
|
-
this.callbacks = new
|
|
13
|
+
this.callbacks = new Map();
|
|
17
14
|
this._isSubscribed = false;
|
|
18
15
|
this.client = client;
|
|
19
16
|
this.topic = topic;
|
|
20
17
|
this.options = options;
|
|
21
18
|
}
|
|
22
|
-
on(
|
|
23
|
-
this.callbacks.
|
|
19
|
+
on(event, callback) {
|
|
20
|
+
if (!this.callbacks.has(event)) {
|
|
21
|
+
this.callbacks.set(event, new Set());
|
|
22
|
+
}
|
|
23
|
+
this.callbacks.get(event).add(callback);
|
|
24
24
|
return this;
|
|
25
25
|
}
|
|
26
26
|
subscribe() {
|
|
@@ -48,41 +48,76 @@ class RealtimeSubscription {
|
|
|
48
48
|
/** @internal */
|
|
49
49
|
_emit(payload) {
|
|
50
50
|
const event = payload.operation;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
cb(payload);
|
|
56
|
-
}
|
|
57
|
-
catch (e) {
|
|
58
|
-
console.error('Realtime callback error:', e);
|
|
59
|
-
}
|
|
51
|
+
this.callbacks.get(event)?.forEach(cb => {
|
|
52
|
+
try {
|
|
53
|
+
cb(payload);
|
|
60
54
|
}
|
|
61
|
-
|
|
55
|
+
catch (e) {
|
|
56
|
+
console.error('Realtime callback error:', e);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
this.callbacks.get('*')?.forEach(cb => {
|
|
60
|
+
try {
|
|
61
|
+
cb(payload);
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
console.error('Realtime callback error:', e);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
62
67
|
}
|
|
63
68
|
}
|
|
64
69
|
exports.RealtimeSubscription = RealtimeSubscription;
|
|
65
70
|
class NodeRealtimeClient {
|
|
66
71
|
constructor(options) {
|
|
67
|
-
this.ws = null;
|
|
72
|
+
this.ws = null;
|
|
68
73
|
this.subscriptions = new Map();
|
|
69
74
|
this.reconnectTimer = null;
|
|
70
75
|
this.heartbeatTimer = null;
|
|
71
76
|
this.reconnectAttempts = 0;
|
|
72
|
-
this.
|
|
73
|
-
|
|
77
|
+
this._sendQueue = [];
|
|
78
|
+
this._connectingPromise = null;
|
|
79
|
+
this._status = 'idle';
|
|
80
|
+
this._statusListeners = new Set();
|
|
81
|
+
this._lastPong = 0;
|
|
82
|
+
this._maxRetriesListeners = new Set();
|
|
74
83
|
const base = options.serverUrl.replace(/\/v1\/?$/, '').replace(/^http/, 'ws');
|
|
75
84
|
this.wsUrl = `${base}/api/realtime`;
|
|
76
85
|
this.projectId = options.projectId;
|
|
77
86
|
this.apiKey = options.apiKey;
|
|
78
87
|
this.userId = options.userId;
|
|
79
88
|
this.token = options.token;
|
|
89
|
+
this._maxReconnectAttempts = options.maxReconnectAttempts ?? Infinity;
|
|
90
|
+
}
|
|
91
|
+
get status() { return this._status; }
|
|
92
|
+
get connected() { return this._status === 'connected'; }
|
|
93
|
+
onStatusChange(cb) {
|
|
94
|
+
this._statusListeners.add(cb);
|
|
95
|
+
return () => this._statusListeners.delete(cb);
|
|
96
|
+
}
|
|
97
|
+
onMaxRetriesExceeded(cb) {
|
|
98
|
+
this._maxRetriesListeners.add(cb);
|
|
99
|
+
return () => this._maxRetriesListeners.delete(cb);
|
|
100
|
+
}
|
|
101
|
+
_setStatus(s) {
|
|
102
|
+
this._status = s;
|
|
103
|
+
this._statusListeners.forEach(cb => cb(s));
|
|
104
|
+
}
|
|
105
|
+
setToken(newToken) {
|
|
106
|
+
this.token = newToken;
|
|
107
|
+
this._send({ type: 'auth', token: newToken });
|
|
80
108
|
}
|
|
81
|
-
get connected() { return this._connected; }
|
|
82
109
|
async connect() {
|
|
83
|
-
if (this.ws)
|
|
110
|
+
if (this.ws && this._status === 'connected')
|
|
84
111
|
return;
|
|
85
|
-
|
|
112
|
+
if (this._connectingPromise)
|
|
113
|
+
return this._connectingPromise;
|
|
114
|
+
this._connectingPromise = this._doConnect().finally(() => {
|
|
115
|
+
this._connectingPromise = null;
|
|
116
|
+
});
|
|
117
|
+
return this._connectingPromise;
|
|
118
|
+
}
|
|
119
|
+
async _doConnect() {
|
|
120
|
+
this._setStatus('connecting');
|
|
86
121
|
const url = new URL(this.wsUrl);
|
|
87
122
|
if (this.apiKey) {
|
|
88
123
|
url.searchParams.set('apiKey', this.apiKey);
|
|
@@ -96,8 +131,6 @@ class NodeRealtimeClient {
|
|
|
96
131
|
url.searchParams.set('token', this.token);
|
|
97
132
|
return new Promise(async (resolve, reject) => {
|
|
98
133
|
try {
|
|
99
|
-
// Try native WebSocket first (Bun, Deno, modern Node 22+)
|
|
100
|
-
// Fall back to 'ws' package for older Node versions
|
|
101
134
|
let WsClass;
|
|
102
135
|
if (typeof globalThis.WebSocket !== 'undefined') {
|
|
103
136
|
WsClass = globalThis.WebSocket;
|
|
@@ -108,15 +141,18 @@ class NodeRealtimeClient {
|
|
|
108
141
|
WsClass = ws.default || ws;
|
|
109
142
|
}
|
|
110
143
|
catch {
|
|
111
|
-
throw new Error('WebSocket not available. Install
|
|
144
|
+
throw new Error('WebSocket not available. Install "ws" package.');
|
|
112
145
|
}
|
|
113
146
|
}
|
|
114
147
|
this.ws = new WsClass(url.toString());
|
|
115
148
|
this.ws.onopen = () => {
|
|
116
|
-
this.
|
|
117
|
-
this.reconnectAttempts = 0;
|
|
149
|
+
this._setStatus('connected');
|
|
150
|
+
this.reconnectAttempts = 0;
|
|
151
|
+
this._lastPong = Date.now();
|
|
118
152
|
this.startHeartbeat();
|
|
119
|
-
|
|
153
|
+
while (this._sendQueue.length > 0) {
|
|
154
|
+
this.ws.send(JSON.stringify(this._sendQueue.shift()));
|
|
155
|
+
}
|
|
120
156
|
for (const sub of this.subscriptions.values()) {
|
|
121
157
|
if (sub.isSubscribed)
|
|
122
158
|
sub.subscribe();
|
|
@@ -134,30 +170,32 @@ class NodeRealtimeClient {
|
|
|
134
170
|
}
|
|
135
171
|
};
|
|
136
172
|
this.ws.onclose = () => {
|
|
137
|
-
this.
|
|
173
|
+
this._setStatus('reconnecting');
|
|
138
174
|
this.stopHeartbeat();
|
|
139
175
|
this.ws = null;
|
|
140
176
|
this.scheduleReconnect();
|
|
141
177
|
};
|
|
142
178
|
this.ws.onerror = (err) => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
179
|
+
console.error('Realtime connection error:', err);
|
|
180
|
+
this._setStatus('disconnected');
|
|
181
|
+
reject(err);
|
|
146
182
|
};
|
|
147
183
|
}
|
|
148
184
|
catch (e) {
|
|
185
|
+
this._setStatus('disconnected');
|
|
149
186
|
reject(e);
|
|
150
187
|
}
|
|
151
188
|
});
|
|
152
189
|
}
|
|
153
190
|
disconnect() {
|
|
191
|
+
this._setStatus('disconnected');
|
|
154
192
|
this.stopReconnect();
|
|
155
193
|
this.stopHeartbeat();
|
|
156
194
|
if (this.ws) {
|
|
157
195
|
this.ws.close();
|
|
158
196
|
this.ws = null;
|
|
159
197
|
}
|
|
160
|
-
this.
|
|
198
|
+
this._sendQueue = [];
|
|
161
199
|
}
|
|
162
200
|
channel(topic, options = {}) {
|
|
163
201
|
const fullTopic = topic.includes('/') ? topic : `table/${topic}/${this.projectId}`;
|
|
@@ -168,13 +206,26 @@ class NodeRealtimeClient {
|
|
|
168
206
|
}
|
|
169
207
|
return sub;
|
|
170
208
|
}
|
|
209
|
+
sendChat(roomId, text) {
|
|
210
|
+
this._send({ type: 'chat', roomId, text });
|
|
211
|
+
}
|
|
212
|
+
chatRoom(roomId) {
|
|
213
|
+
return this.channel(`chat/${roomId}/${this.projectId}`);
|
|
214
|
+
}
|
|
171
215
|
/** @internal */
|
|
172
216
|
_send(data) {
|
|
173
|
-
if (this.ws && this.
|
|
217
|
+
if (this.ws && this._status === 'connected') {
|
|
174
218
|
this.ws.send(JSON.stringify(data));
|
|
175
219
|
}
|
|
220
|
+
else {
|
|
221
|
+
this._sendQueue.push(data);
|
|
222
|
+
}
|
|
176
223
|
}
|
|
177
224
|
handleMessage(data) {
|
|
225
|
+
if (data.type === 'pong') {
|
|
226
|
+
this._lastPong = Date.now();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
178
229
|
if (data.type === 'db_change' || data.type === 'chat_message') {
|
|
179
230
|
const sub = this.subscriptions.get(data.topic);
|
|
180
231
|
if (sub)
|
|
@@ -184,6 +235,10 @@ class NodeRealtimeClient {
|
|
|
184
235
|
startHeartbeat() {
|
|
185
236
|
this.heartbeatTimer = setInterval(() => {
|
|
186
237
|
this._send({ type: 'ping' });
|
|
238
|
+
if (this._lastPong > 0 && Date.now() - this._lastPong > 70000) {
|
|
239
|
+
console.warn('Realtime: no pong received, forcing reconnect');
|
|
240
|
+
this.ws?.close();
|
|
241
|
+
}
|
|
187
242
|
}, 30000);
|
|
188
243
|
}
|
|
189
244
|
stopHeartbeat() {
|
|
@@ -192,16 +247,19 @@ class NodeRealtimeClient {
|
|
|
192
247
|
this.heartbeatTimer = null;
|
|
193
248
|
}
|
|
194
249
|
}
|
|
195
|
-
/** Exponential backoff with jitter */
|
|
196
250
|
scheduleReconnect() {
|
|
197
251
|
this.stopReconnect();
|
|
252
|
+
if (this.reconnectAttempts >= this._maxReconnectAttempts) {
|
|
253
|
+
this._setStatus('disconnected');
|
|
254
|
+
this._maxRetriesListeners.forEach(cb => cb());
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
198
257
|
const delay = Math.min(BASE_RECONNECT_MS * Math.pow(2, this.reconnectAttempts), MAX_RECONNECT_MS);
|
|
199
258
|
const jitter = delay * JITTER_FACTOR * Math.random();
|
|
200
|
-
const finalDelay = delay + jitter;
|
|
201
259
|
this.reconnectAttempts++;
|
|
202
260
|
this.reconnectTimer = setTimeout(() => {
|
|
203
261
|
this.connect().catch(() => { });
|
|
204
|
-
},
|
|
262
|
+
}, delay + jitter);
|
|
205
263
|
}
|
|
206
264
|
stopReconnect() {
|
|
207
265
|
if (this.reconnectTimer) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime.js","sourceRoot":"","sources":["../../../src/sdk/realtime.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"realtime.js","sourceRoot":"","sources":["../../../src/sdk/realtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,41 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Aerostack Realtime Client for Node.js SDK
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* This is NOT auto-generated — it is a hand-written extension for the Node SDK.
|
|
3
|
+
* Production-hardened WebSocket client for server-side usage.
|
|
6
4
|
*/
|
|
7
|
-
type RealtimeEvent = 'INSERT' | 'UPDATE' | 'DELETE' | '*';
|
|
8
|
-
interface
|
|
5
|
+
export type RealtimeEvent = 'INSERT' | 'UPDATE' | 'DELETE' | '*';
|
|
6
|
+
export interface RealtimeMessage {
|
|
7
|
+
type: string;
|
|
8
|
+
topic: string;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
}
|
|
11
|
+
export interface RealtimeSubscriptionOptions {
|
|
9
12
|
event?: RealtimeEvent;
|
|
10
13
|
filter?: Record<string, any>;
|
|
11
14
|
}
|
|
12
|
-
type RealtimeCallback = (payload:
|
|
13
|
-
interface
|
|
14
|
-
|
|
15
|
+
export type RealtimeCallback<T = any> = (payload: RealtimePayload<T>) => void;
|
|
16
|
+
export interface RealtimePayload<T = any> {
|
|
17
|
+
type: 'db_change' | 'chat_message';
|
|
18
|
+
topic: string;
|
|
19
|
+
operation: RealtimeEvent;
|
|
20
|
+
data: T;
|
|
21
|
+
old?: T;
|
|
22
|
+
timestamp?: string;
|
|
23
|
+
[key: string]: any;
|
|
24
|
+
}
|
|
25
|
+
export interface NodeRealtimeOptions {
|
|
15
26
|
serverUrl: string;
|
|
16
|
-
/** Project ID to subscribe to */
|
|
17
27
|
projectId: string;
|
|
18
|
-
/** API Key for authentication (recommended for server-side) */
|
|
19
28
|
apiKey?: string | undefined;
|
|
20
|
-
/** User ID (optional) */
|
|
21
29
|
userId?: string | undefined;
|
|
22
|
-
/** Bearer token (optional, for user-context connections) */
|
|
23
30
|
token?: string | undefined;
|
|
31
|
+
maxReconnectAttempts?: number;
|
|
24
32
|
}
|
|
25
|
-
export declare class RealtimeSubscription {
|
|
33
|
+
export declare class RealtimeSubscription<T = any> {
|
|
26
34
|
private client;
|
|
27
35
|
private topic;
|
|
28
36
|
private options;
|
|
29
37
|
private callbacks;
|
|
30
38
|
private _isSubscribed;
|
|
31
39
|
constructor(client: NodeRealtimeClient, topic: string, options?: RealtimeSubscriptionOptions);
|
|
32
|
-
on(
|
|
40
|
+
on(event: RealtimeEvent, callback: RealtimeCallback<T>): this;
|
|
33
41
|
subscribe(): this;
|
|
34
42
|
unsubscribe(): void;
|
|
35
43
|
get isSubscribed(): boolean;
|
|
36
44
|
/** @internal */
|
|
37
|
-
_emit(payload:
|
|
45
|
+
_emit(payload: RealtimePayload<T>): void;
|
|
38
46
|
}
|
|
47
|
+
export type RealtimeStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected';
|
|
39
48
|
export declare class NodeRealtimeClient {
|
|
40
49
|
private wsUrl;
|
|
41
50
|
private projectId;
|
|
@@ -47,20 +56,32 @@ export declare class NodeRealtimeClient {
|
|
|
47
56
|
private reconnectTimer;
|
|
48
57
|
private heartbeatTimer;
|
|
49
58
|
private reconnectAttempts;
|
|
50
|
-
private
|
|
59
|
+
private _sendQueue;
|
|
60
|
+
private _connectingPromise;
|
|
61
|
+
private _status;
|
|
62
|
+
private _statusListeners;
|
|
63
|
+
private _lastPong;
|
|
64
|
+
private _maxReconnectAttempts;
|
|
65
|
+
private _maxRetriesListeners;
|
|
51
66
|
constructor(options: NodeRealtimeOptions);
|
|
67
|
+
get status(): RealtimeStatus;
|
|
52
68
|
get connected(): boolean;
|
|
69
|
+
onStatusChange(cb: (status: RealtimeStatus) => void): () => void;
|
|
70
|
+
onMaxRetriesExceeded(cb: () => void): () => void;
|
|
71
|
+
private _setStatus;
|
|
72
|
+
setToken(newToken: string): void;
|
|
53
73
|
connect(): Promise<void>;
|
|
74
|
+
private _doConnect;
|
|
54
75
|
disconnect(): void;
|
|
55
|
-
channel(topic: string, options?: RealtimeSubscriptionOptions): RealtimeSubscription
|
|
76
|
+
channel<T = any>(topic: string, options?: RealtimeSubscriptionOptions): RealtimeSubscription<T>;
|
|
77
|
+
sendChat(roomId: string, text: string): void;
|
|
78
|
+
chatRoom(roomId: string): RealtimeSubscription;
|
|
56
79
|
/** @internal */
|
|
57
80
|
_send(data: any): void;
|
|
58
81
|
private handleMessage;
|
|
59
82
|
private startHeartbeat;
|
|
60
83
|
private stopHeartbeat;
|
|
61
|
-
/** Exponential backoff with jitter */
|
|
62
84
|
private scheduleReconnect;
|
|
63
85
|
private stopReconnect;
|
|
64
86
|
}
|
|
65
|
-
export {};
|
|
66
87
|
//# sourceMappingURL=realtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../../../src/sdk/realtime.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../../../src/sdk/realtime.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/sdk/realtime.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Aerostack Realtime Client for Node.js SDK
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* This is NOT auto-generated — it is a hand-written extension for the Node SDK.
|
|
3
|
+
* Production-hardened WebSocket client for server-side usage.
|
|
6
4
|
*/
|
|
7
|
-
// Constants for exponential backoff
|
|
8
5
|
const BASE_RECONNECT_MS = 1000;
|
|
9
6
|
const MAX_RECONNECT_MS = 30000;
|
|
10
7
|
const JITTER_FACTOR = 0.3;
|
|
11
8
|
export class RealtimeSubscription {
|
|
12
9
|
constructor(client, topic, options = {}) {
|
|
13
|
-
this.callbacks = new
|
|
10
|
+
this.callbacks = new Map();
|
|
14
11
|
this._isSubscribed = false;
|
|
15
12
|
this.client = client;
|
|
16
13
|
this.topic = topic;
|
|
17
14
|
this.options = options;
|
|
18
15
|
}
|
|
19
|
-
on(
|
|
20
|
-
this.callbacks.
|
|
16
|
+
on(event, callback) {
|
|
17
|
+
if (!this.callbacks.has(event)) {
|
|
18
|
+
this.callbacks.set(event, new Set());
|
|
19
|
+
}
|
|
20
|
+
this.callbacks.get(event).add(callback);
|
|
21
21
|
return this;
|
|
22
22
|
}
|
|
23
23
|
subscribe() {
|
|
@@ -45,40 +45,75 @@ export class RealtimeSubscription {
|
|
|
45
45
|
/** @internal */
|
|
46
46
|
_emit(payload) {
|
|
47
47
|
const event = payload.operation;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
cb(payload);
|
|
53
|
-
}
|
|
54
|
-
catch (e) {
|
|
55
|
-
console.error('Realtime callback error:', e);
|
|
56
|
-
}
|
|
48
|
+
this.callbacks.get(event)?.forEach(cb => {
|
|
49
|
+
try {
|
|
50
|
+
cb(payload);
|
|
57
51
|
}
|
|
58
|
-
|
|
52
|
+
catch (e) {
|
|
53
|
+
console.error('Realtime callback error:', e);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
this.callbacks.get('*')?.forEach(cb => {
|
|
57
|
+
try {
|
|
58
|
+
cb(payload);
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
console.error('Realtime callback error:', e);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
66
|
export class NodeRealtimeClient {
|
|
62
67
|
constructor(options) {
|
|
63
|
-
this.ws = null;
|
|
68
|
+
this.ws = null;
|
|
64
69
|
this.subscriptions = new Map();
|
|
65
70
|
this.reconnectTimer = null;
|
|
66
71
|
this.heartbeatTimer = null;
|
|
67
72
|
this.reconnectAttempts = 0;
|
|
68
|
-
this.
|
|
69
|
-
|
|
73
|
+
this._sendQueue = [];
|
|
74
|
+
this._connectingPromise = null;
|
|
75
|
+
this._status = 'idle';
|
|
76
|
+
this._statusListeners = new Set();
|
|
77
|
+
this._lastPong = 0;
|
|
78
|
+
this._maxRetriesListeners = new Set();
|
|
70
79
|
const base = options.serverUrl.replace(/\/v1\/?$/, '').replace(/^http/, 'ws');
|
|
71
80
|
this.wsUrl = `${base}/api/realtime`;
|
|
72
81
|
this.projectId = options.projectId;
|
|
73
82
|
this.apiKey = options.apiKey;
|
|
74
83
|
this.userId = options.userId;
|
|
75
84
|
this.token = options.token;
|
|
85
|
+
this._maxReconnectAttempts = options.maxReconnectAttempts ?? Infinity;
|
|
86
|
+
}
|
|
87
|
+
get status() { return this._status; }
|
|
88
|
+
get connected() { return this._status === 'connected'; }
|
|
89
|
+
onStatusChange(cb) {
|
|
90
|
+
this._statusListeners.add(cb);
|
|
91
|
+
return () => this._statusListeners.delete(cb);
|
|
92
|
+
}
|
|
93
|
+
onMaxRetriesExceeded(cb) {
|
|
94
|
+
this._maxRetriesListeners.add(cb);
|
|
95
|
+
return () => this._maxRetriesListeners.delete(cb);
|
|
96
|
+
}
|
|
97
|
+
_setStatus(s) {
|
|
98
|
+
this._status = s;
|
|
99
|
+
this._statusListeners.forEach(cb => cb(s));
|
|
100
|
+
}
|
|
101
|
+
setToken(newToken) {
|
|
102
|
+
this.token = newToken;
|
|
103
|
+
this._send({ type: 'auth', token: newToken });
|
|
76
104
|
}
|
|
77
|
-
get connected() { return this._connected; }
|
|
78
105
|
async connect() {
|
|
79
|
-
if (this.ws)
|
|
106
|
+
if (this.ws && this._status === 'connected')
|
|
80
107
|
return;
|
|
81
|
-
|
|
108
|
+
if (this._connectingPromise)
|
|
109
|
+
return this._connectingPromise;
|
|
110
|
+
this._connectingPromise = this._doConnect().finally(() => {
|
|
111
|
+
this._connectingPromise = null;
|
|
112
|
+
});
|
|
113
|
+
return this._connectingPromise;
|
|
114
|
+
}
|
|
115
|
+
async _doConnect() {
|
|
116
|
+
this._setStatus('connecting');
|
|
82
117
|
const url = new URL(this.wsUrl);
|
|
83
118
|
if (this.apiKey) {
|
|
84
119
|
url.searchParams.set('apiKey', this.apiKey);
|
|
@@ -92,8 +127,6 @@ export class NodeRealtimeClient {
|
|
|
92
127
|
url.searchParams.set('token', this.token);
|
|
93
128
|
return new Promise(async (resolve, reject) => {
|
|
94
129
|
try {
|
|
95
|
-
// Try native WebSocket first (Bun, Deno, modern Node 22+)
|
|
96
|
-
// Fall back to 'ws' package for older Node versions
|
|
97
130
|
let WsClass;
|
|
98
131
|
if (typeof globalThis.WebSocket !== 'undefined') {
|
|
99
132
|
WsClass = globalThis.WebSocket;
|
|
@@ -104,15 +137,18 @@ export class NodeRealtimeClient {
|
|
|
104
137
|
WsClass = ws.default || ws;
|
|
105
138
|
}
|
|
106
139
|
catch {
|
|
107
|
-
throw new Error('WebSocket not available. Install
|
|
140
|
+
throw new Error('WebSocket not available. Install "ws" package.');
|
|
108
141
|
}
|
|
109
142
|
}
|
|
110
143
|
this.ws = new WsClass(url.toString());
|
|
111
144
|
this.ws.onopen = () => {
|
|
112
|
-
this.
|
|
113
|
-
this.reconnectAttempts = 0;
|
|
145
|
+
this._setStatus('connected');
|
|
146
|
+
this.reconnectAttempts = 0;
|
|
147
|
+
this._lastPong = Date.now();
|
|
114
148
|
this.startHeartbeat();
|
|
115
|
-
|
|
149
|
+
while (this._sendQueue.length > 0) {
|
|
150
|
+
this.ws.send(JSON.stringify(this._sendQueue.shift()));
|
|
151
|
+
}
|
|
116
152
|
for (const sub of this.subscriptions.values()) {
|
|
117
153
|
if (sub.isSubscribed)
|
|
118
154
|
sub.subscribe();
|
|
@@ -130,30 +166,32 @@ export class NodeRealtimeClient {
|
|
|
130
166
|
}
|
|
131
167
|
};
|
|
132
168
|
this.ws.onclose = () => {
|
|
133
|
-
this.
|
|
169
|
+
this._setStatus('reconnecting');
|
|
134
170
|
this.stopHeartbeat();
|
|
135
171
|
this.ws = null;
|
|
136
172
|
this.scheduleReconnect();
|
|
137
173
|
};
|
|
138
174
|
this.ws.onerror = (err) => {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
175
|
+
console.error('Realtime connection error:', err);
|
|
176
|
+
this._setStatus('disconnected');
|
|
177
|
+
reject(err);
|
|
142
178
|
};
|
|
143
179
|
}
|
|
144
180
|
catch (e) {
|
|
181
|
+
this._setStatus('disconnected');
|
|
145
182
|
reject(e);
|
|
146
183
|
}
|
|
147
184
|
});
|
|
148
185
|
}
|
|
149
186
|
disconnect() {
|
|
187
|
+
this._setStatus('disconnected');
|
|
150
188
|
this.stopReconnect();
|
|
151
189
|
this.stopHeartbeat();
|
|
152
190
|
if (this.ws) {
|
|
153
191
|
this.ws.close();
|
|
154
192
|
this.ws = null;
|
|
155
193
|
}
|
|
156
|
-
this.
|
|
194
|
+
this._sendQueue = [];
|
|
157
195
|
}
|
|
158
196
|
channel(topic, options = {}) {
|
|
159
197
|
const fullTopic = topic.includes('/') ? topic : `table/${topic}/${this.projectId}`;
|
|
@@ -164,13 +202,26 @@ export class NodeRealtimeClient {
|
|
|
164
202
|
}
|
|
165
203
|
return sub;
|
|
166
204
|
}
|
|
205
|
+
sendChat(roomId, text) {
|
|
206
|
+
this._send({ type: 'chat', roomId, text });
|
|
207
|
+
}
|
|
208
|
+
chatRoom(roomId) {
|
|
209
|
+
return this.channel(`chat/${roomId}/${this.projectId}`);
|
|
210
|
+
}
|
|
167
211
|
/** @internal */
|
|
168
212
|
_send(data) {
|
|
169
|
-
if (this.ws && this.
|
|
213
|
+
if (this.ws && this._status === 'connected') {
|
|
170
214
|
this.ws.send(JSON.stringify(data));
|
|
171
215
|
}
|
|
216
|
+
else {
|
|
217
|
+
this._sendQueue.push(data);
|
|
218
|
+
}
|
|
172
219
|
}
|
|
173
220
|
handleMessage(data) {
|
|
221
|
+
if (data.type === 'pong') {
|
|
222
|
+
this._lastPong = Date.now();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
174
225
|
if (data.type === 'db_change' || data.type === 'chat_message') {
|
|
175
226
|
const sub = this.subscriptions.get(data.topic);
|
|
176
227
|
if (sub)
|
|
@@ -180,6 +231,10 @@ export class NodeRealtimeClient {
|
|
|
180
231
|
startHeartbeat() {
|
|
181
232
|
this.heartbeatTimer = setInterval(() => {
|
|
182
233
|
this._send({ type: 'ping' });
|
|
234
|
+
if (this._lastPong > 0 && Date.now() - this._lastPong > 70000) {
|
|
235
|
+
console.warn('Realtime: no pong received, forcing reconnect');
|
|
236
|
+
this.ws?.close();
|
|
237
|
+
}
|
|
183
238
|
}, 30000);
|
|
184
239
|
}
|
|
185
240
|
stopHeartbeat() {
|
|
@@ -188,16 +243,19 @@ export class NodeRealtimeClient {
|
|
|
188
243
|
this.heartbeatTimer = null;
|
|
189
244
|
}
|
|
190
245
|
}
|
|
191
|
-
/** Exponential backoff with jitter */
|
|
192
246
|
scheduleReconnect() {
|
|
193
247
|
this.stopReconnect();
|
|
248
|
+
if (this.reconnectAttempts >= this._maxReconnectAttempts) {
|
|
249
|
+
this._setStatus('disconnected');
|
|
250
|
+
this._maxRetriesListeners.forEach(cb => cb());
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
194
253
|
const delay = Math.min(BASE_RECONNECT_MS * Math.pow(2, this.reconnectAttempts), MAX_RECONNECT_MS);
|
|
195
254
|
const jitter = delay * JITTER_FACTOR * Math.random();
|
|
196
|
-
const finalDelay = delay + jitter;
|
|
197
255
|
this.reconnectAttempts++;
|
|
198
256
|
this.reconnectTimer = setTimeout(() => {
|
|
199
257
|
this.connect().catch(() => { });
|
|
200
|
-
},
|
|
258
|
+
}, delay + jitter);
|
|
201
259
|
}
|
|
202
260
|
stopReconnect() {
|
|
203
261
|
if (this.reconnectTimer) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime.js","sourceRoot":"","sources":["../../../src/sdk/realtime.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"realtime.js","sourceRoot":"","sources":["../../../src/sdk/realtime.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aerostack/sdk-node",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8",
|
|
4
4
|
"author": "Aerostack",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"tshy": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"ws": "^8.19.0"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@aerostack/core": "^0.6.
|
|
39
|
+
"@aerostack/core": "^0.6.8",
|
|
40
40
|
"zod": "^3.25.0 || ^4.0.0"
|
|
41
41
|
},
|
|
42
42
|
"exports": {
|
package/src/sdk/realtime.ts
CHANGED
|
@@ -1,48 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Aerostack Realtime Client for Node.js SDK
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* This is NOT auto-generated — it is a hand-written extension for the Node SDK.
|
|
3
|
+
* Production-hardened WebSocket client for server-side usage.
|
|
6
4
|
*/
|
|
7
5
|
|
|
8
|
-
type RealtimeEvent = 'INSERT' | 'UPDATE' | 'DELETE' | '*';
|
|
6
|
+
export type RealtimeEvent = 'INSERT' | 'UPDATE' | 'DELETE' | '*';
|
|
9
7
|
|
|
10
|
-
interface RealtimeMessage {
|
|
8
|
+
export interface RealtimeMessage {
|
|
11
9
|
type: string;
|
|
12
10
|
topic: string;
|
|
13
11
|
[key: string]: any;
|
|
14
12
|
}
|
|
15
13
|
|
|
16
|
-
interface RealtimeSubscriptionOptions {
|
|
14
|
+
export interface RealtimeSubscriptionOptions {
|
|
17
15
|
event?: RealtimeEvent;
|
|
18
16
|
filter?: Record<string, any>;
|
|
19
17
|
}
|
|
20
18
|
|
|
21
|
-
type RealtimeCallback = (payload:
|
|
19
|
+
export type RealtimeCallback<T = any> = (payload: RealtimePayload<T>) => void;
|
|
22
20
|
|
|
23
|
-
interface
|
|
24
|
-
|
|
21
|
+
export interface RealtimePayload<T = any> {
|
|
22
|
+
type: 'db_change' | 'chat_message';
|
|
23
|
+
topic: string;
|
|
24
|
+
operation: RealtimeEvent;
|
|
25
|
+
data: T;
|
|
26
|
+
old?: T;
|
|
27
|
+
timestamp?: string;
|
|
28
|
+
[key: string]: any;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface NodeRealtimeOptions {
|
|
25
32
|
serverUrl: string;
|
|
26
|
-
/** Project ID to subscribe to */
|
|
27
33
|
projectId: string;
|
|
28
|
-
/** API Key for authentication (recommended for server-side) */
|
|
29
34
|
apiKey?: string | undefined;
|
|
30
|
-
/** User ID (optional) */
|
|
31
35
|
userId?: string | undefined;
|
|
32
|
-
/** Bearer token (optional, for user-context connections) */
|
|
33
36
|
token?: string | undefined;
|
|
37
|
+
maxReconnectAttempts?: number;
|
|
34
38
|
}
|
|
35
39
|
|
|
36
|
-
// Constants for exponential backoff
|
|
37
40
|
const BASE_RECONNECT_MS = 1000;
|
|
38
41
|
const MAX_RECONNECT_MS = 30000;
|
|
39
42
|
const JITTER_FACTOR = 0.3;
|
|
40
43
|
|
|
41
|
-
export class RealtimeSubscription {
|
|
44
|
+
export class RealtimeSubscription<T = any> {
|
|
42
45
|
private client: NodeRealtimeClient;
|
|
43
46
|
private topic: string;
|
|
44
47
|
private options: RealtimeSubscriptionOptions;
|
|
45
|
-
private callbacks: Set<RealtimeCallback
|
|
48
|
+
private callbacks: Map<RealtimeEvent, Set<RealtimeCallback<T>>> = new Map();
|
|
46
49
|
private _isSubscribed: boolean = false;
|
|
47
50
|
|
|
48
51
|
constructor(client: NodeRealtimeClient, topic: string, options: RealtimeSubscriptionOptions = {}) {
|
|
@@ -51,8 +54,11 @@ export class RealtimeSubscription {
|
|
|
51
54
|
this.options = options;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
on(
|
|
55
|
-
this.callbacks.
|
|
57
|
+
on(event: RealtimeEvent, callback: RealtimeCallback<T>): this {
|
|
58
|
+
if (!this.callbacks.has(event)) {
|
|
59
|
+
this.callbacks.set(event, new Set());
|
|
60
|
+
}
|
|
61
|
+
this.callbacks.get(event)!.add(callback);
|
|
56
62
|
return this;
|
|
57
63
|
}
|
|
58
64
|
|
|
@@ -80,51 +86,82 @@ export class RealtimeSubscription {
|
|
|
80
86
|
get isSubscribed() { return this._isSubscribed; }
|
|
81
87
|
|
|
82
88
|
/** @internal */
|
|
83
|
-
_emit(payload:
|
|
89
|
+
_emit(payload: RealtimePayload<T>): void {
|
|
84
90
|
const event = payload.operation as RealtimeEvent;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
} catch (e) {
|
|
92
|
-
console.error('Realtime callback error:', e);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
91
|
+
this.callbacks.get(event)?.forEach(cb => {
|
|
92
|
+
try { cb(payload); } catch (e) { console.error('Realtime callback error:', e); }
|
|
93
|
+
});
|
|
94
|
+
this.callbacks.get('*')?.forEach(cb => {
|
|
95
|
+
try { cb(payload); } catch (e) { console.error('Realtime callback error:', e); }
|
|
96
|
+
});
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
export type RealtimeStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected';
|
|
101
|
+
|
|
99
102
|
export class NodeRealtimeClient {
|
|
100
103
|
private wsUrl: string;
|
|
101
104
|
private projectId: string;
|
|
102
105
|
private apiKey: string | undefined;
|
|
103
106
|
private userId: string | undefined;
|
|
104
107
|
private token: string | undefined;
|
|
105
|
-
private ws: any = null;
|
|
108
|
+
private ws: any = null;
|
|
106
109
|
private subscriptions: Map<string, RealtimeSubscription> = new Map();
|
|
107
110
|
private reconnectTimer: any = null;
|
|
108
111
|
private heartbeatTimer: any = null;
|
|
109
112
|
private reconnectAttempts: number = 0;
|
|
110
|
-
private
|
|
113
|
+
private _sendQueue: any[] = [];
|
|
114
|
+
private _connectingPromise: Promise<void> | null = null;
|
|
115
|
+
private _status: RealtimeStatus = 'idle';
|
|
116
|
+
private _statusListeners: Set<(s: RealtimeStatus) => void> = new Set();
|
|
117
|
+
private _lastPong: number = 0;
|
|
118
|
+
private _maxReconnectAttempts: number;
|
|
119
|
+
private _maxRetriesListeners: Set<() => void> = new Set();
|
|
111
120
|
|
|
112
121
|
constructor(options: NodeRealtimeOptions) {
|
|
113
|
-
// Convert HTTP URL to WS URL
|
|
114
122
|
const base = options.serverUrl.replace(/\/v1\/?$/, '').replace(/^http/, 'ws');
|
|
115
123
|
this.wsUrl = `${base}/api/realtime`;
|
|
116
124
|
this.projectId = options.projectId;
|
|
117
125
|
this.apiKey = options.apiKey;
|
|
118
126
|
this.userId = options.userId;
|
|
119
127
|
this.token = options.token;
|
|
128
|
+
this._maxReconnectAttempts = options.maxReconnectAttempts ?? Infinity;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
get status(): RealtimeStatus { return this._status; }
|
|
132
|
+
get connected(): boolean { return this._status === 'connected'; }
|
|
133
|
+
|
|
134
|
+
onStatusChange(cb: (status: RealtimeStatus) => void): () => void {
|
|
135
|
+
this._statusListeners.add(cb);
|
|
136
|
+
return () => this._statusListeners.delete(cb);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
onMaxRetriesExceeded(cb: () => void): () => void {
|
|
140
|
+
this._maxRetriesListeners.add(cb);
|
|
141
|
+
return () => this._maxRetriesListeners.delete(cb);
|
|
120
142
|
}
|
|
121
143
|
|
|
122
|
-
|
|
144
|
+
private _setStatus(s: RealtimeStatus) {
|
|
145
|
+
this._status = s;
|
|
146
|
+
this._statusListeners.forEach(cb => cb(s));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
setToken(newToken: string): void {
|
|
150
|
+
this.token = newToken;
|
|
151
|
+
this._send({ type: 'auth', token: newToken });
|
|
152
|
+
}
|
|
123
153
|
|
|
124
154
|
async connect(): Promise<void> {
|
|
125
|
-
if (this.ws) return;
|
|
155
|
+
if (this.ws && this._status === 'connected') return;
|
|
156
|
+
if (this._connectingPromise) return this._connectingPromise;
|
|
157
|
+
this._connectingPromise = this._doConnect().finally(() => {
|
|
158
|
+
this._connectingPromise = null;
|
|
159
|
+
});
|
|
160
|
+
return this._connectingPromise;
|
|
161
|
+
}
|
|
126
162
|
|
|
127
|
-
|
|
163
|
+
private async _doConnect(): Promise<void> {
|
|
164
|
+
this._setStatus('connecting');
|
|
128
165
|
const url = new URL(this.wsUrl);
|
|
129
166
|
if (this.apiKey) {
|
|
130
167
|
url.searchParams.set('apiKey', this.apiKey);
|
|
@@ -136,8 +173,6 @@ export class NodeRealtimeClient {
|
|
|
136
173
|
|
|
137
174
|
return new Promise(async (resolve, reject) => {
|
|
138
175
|
try {
|
|
139
|
-
// Try native WebSocket first (Bun, Deno, modern Node 22+)
|
|
140
|
-
// Fall back to 'ws' package for older Node versions
|
|
141
176
|
let WsClass: any;
|
|
142
177
|
if (typeof globalThis.WebSocket !== 'undefined') {
|
|
143
178
|
WsClass = globalThis.WebSocket;
|
|
@@ -146,19 +181,20 @@ export class NodeRealtimeClient {
|
|
|
146
181
|
const ws = await import('ws');
|
|
147
182
|
WsClass = ws.default || ws;
|
|
148
183
|
} catch {
|
|
149
|
-
throw new Error(
|
|
150
|
-
'WebSocket not available. Install the "ws" package: npm install ws'
|
|
151
|
-
);
|
|
184
|
+
throw new Error('WebSocket not available. Install "ws" package.');
|
|
152
185
|
}
|
|
153
186
|
}
|
|
154
187
|
|
|
155
188
|
this.ws = new WsClass(url.toString());
|
|
156
189
|
|
|
157
190
|
this.ws.onopen = () => {
|
|
158
|
-
this.
|
|
159
|
-
this.reconnectAttempts = 0;
|
|
191
|
+
this._setStatus('connected');
|
|
192
|
+
this.reconnectAttempts = 0;
|
|
193
|
+
this._lastPong = Date.now();
|
|
160
194
|
this.startHeartbeat();
|
|
161
|
-
|
|
195
|
+
while (this._sendQueue.length > 0) {
|
|
196
|
+
this.ws.send(JSON.stringify(this._sendQueue.shift()));
|
|
197
|
+
}
|
|
162
198
|
for (const sub of this.subscriptions.values()) {
|
|
163
199
|
if (sub.isSubscribed) sub.subscribe();
|
|
164
200
|
}
|
|
@@ -176,61 +212,80 @@ export class NodeRealtimeClient {
|
|
|
176
212
|
};
|
|
177
213
|
|
|
178
214
|
this.ws.onclose = () => {
|
|
179
|
-
this.
|
|
215
|
+
this._setStatus('reconnecting');
|
|
180
216
|
this.stopHeartbeat();
|
|
181
217
|
this.ws = null;
|
|
182
218
|
this.scheduleReconnect();
|
|
183
219
|
};
|
|
184
220
|
|
|
185
221
|
this.ws.onerror = (err: any) => {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
222
|
+
console.error('Realtime connection error:', err);
|
|
223
|
+
this._setStatus('disconnected');
|
|
224
|
+
reject(err);
|
|
189
225
|
};
|
|
190
226
|
} catch (e) {
|
|
227
|
+
this._setStatus('disconnected');
|
|
191
228
|
reject(e);
|
|
192
229
|
}
|
|
193
230
|
});
|
|
194
231
|
}
|
|
195
232
|
|
|
196
233
|
disconnect(): void {
|
|
234
|
+
this._setStatus('disconnected');
|
|
197
235
|
this.stopReconnect();
|
|
198
236
|
this.stopHeartbeat();
|
|
199
237
|
if (this.ws) {
|
|
200
238
|
this.ws.close();
|
|
201
239
|
this.ws = null;
|
|
202
240
|
}
|
|
203
|
-
this.
|
|
241
|
+
this._sendQueue = [];
|
|
204
242
|
}
|
|
205
243
|
|
|
206
|
-
channel(topic: string, options: RealtimeSubscriptionOptions = {}): RealtimeSubscription {
|
|
244
|
+
channel<T = any>(topic: string, options: RealtimeSubscriptionOptions = {}): RealtimeSubscription<T> {
|
|
207
245
|
const fullTopic = topic.includes('/') ? topic : `table/${topic}/${this.projectId}`;
|
|
208
|
-
|
|
209
246
|
let sub = this.subscriptions.get(fullTopic);
|
|
210
247
|
if (!sub) {
|
|
211
|
-
sub = new RealtimeSubscription(this, fullTopic, options);
|
|
248
|
+
sub = new RealtimeSubscription<T>(this, fullTopic, options);
|
|
212
249
|
this.subscriptions.set(fullTopic, sub);
|
|
213
250
|
}
|
|
214
|
-
return sub
|
|
251
|
+
return sub as RealtimeSubscription<T>;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
sendChat(roomId: string, text: string): void {
|
|
255
|
+
this._send({ type: 'chat', roomId, text });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
chatRoom(roomId: string): RealtimeSubscription {
|
|
259
|
+
return this.channel(`chat/${roomId}/${this.projectId}`);
|
|
215
260
|
}
|
|
216
261
|
|
|
217
262
|
/** @internal */
|
|
218
263
|
_send(data: any): void {
|
|
219
|
-
if (this.ws && this.
|
|
264
|
+
if (this.ws && this._status === 'connected') {
|
|
220
265
|
this.ws.send(JSON.stringify(data));
|
|
266
|
+
} else {
|
|
267
|
+
this._sendQueue.push(data);
|
|
221
268
|
}
|
|
222
269
|
}
|
|
223
270
|
|
|
224
271
|
private handleMessage(data: RealtimeMessage): void {
|
|
272
|
+
if (data.type === 'pong') {
|
|
273
|
+
this._lastPong = Date.now();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
225
276
|
if (data.type === 'db_change' || data.type === 'chat_message') {
|
|
226
277
|
const sub = this.subscriptions.get(data.topic);
|
|
227
|
-
if (sub) sub._emit(data);
|
|
278
|
+
if (sub) sub._emit(data as any);
|
|
228
279
|
}
|
|
229
280
|
}
|
|
230
281
|
|
|
231
282
|
private startHeartbeat(): void {
|
|
232
283
|
this.heartbeatTimer = setInterval(() => {
|
|
233
284
|
this._send({ type: 'ping' });
|
|
285
|
+
if (this._lastPong > 0 && Date.now() - this._lastPong > 70000) {
|
|
286
|
+
console.warn('Realtime: no pong received, forcing reconnect');
|
|
287
|
+
this.ws?.close();
|
|
288
|
+
}
|
|
234
289
|
}, 30000);
|
|
235
290
|
}
|
|
236
291
|
|
|
@@ -241,20 +296,19 @@ export class NodeRealtimeClient {
|
|
|
241
296
|
}
|
|
242
297
|
}
|
|
243
298
|
|
|
244
|
-
/** Exponential backoff with jitter */
|
|
245
299
|
private scheduleReconnect(): void {
|
|
246
300
|
this.stopReconnect();
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
301
|
+
if (this.reconnectAttempts >= this._maxReconnectAttempts) {
|
|
302
|
+
this._setStatus('disconnected');
|
|
303
|
+
this._maxRetriesListeners.forEach(cb => cb());
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const delay = Math.min(BASE_RECONNECT_MS * Math.pow(2, this.reconnectAttempts), MAX_RECONNECT_MS);
|
|
251
307
|
const jitter = delay * JITTER_FACTOR * Math.random();
|
|
252
|
-
const finalDelay = delay + jitter;
|
|
253
|
-
|
|
254
308
|
this.reconnectAttempts++;
|
|
255
309
|
this.reconnectTimer = setTimeout(() => {
|
|
256
310
|
this.connect().catch(() => { });
|
|
257
|
-
},
|
|
311
|
+
}, delay + jitter);
|
|
258
312
|
}
|
|
259
313
|
|
|
260
314
|
private stopReconnect(): void {
|