@aerostack/sdk-web 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 +43 -13
- package/dist/commonjs/sdk/realtime.d.ts.map +1 -1
- package/dist/commonjs/sdk/realtime.js +115 -25
- package/dist/commonjs/sdk/realtime.js.map +1 -1
- package/dist/esm/sdk/realtime.d.ts +43 -13
- package/dist/esm/sdk/realtime.d.ts.map +1 -1
- package/dist/esm/sdk/realtime.js +115 -25
- package/dist/esm/sdk/realtime.js.map +1 -1
- package/package.json +2 -2
- package/src/sdk/realtime.ts +158 -36
|
@@ -8,19 +8,37 @@ export interface RealtimeSubscriptionOptions {
|
|
|
8
8
|
event?: RealtimeEvent;
|
|
9
9
|
filter?: Record<string, any>;
|
|
10
10
|
}
|
|
11
|
-
export type RealtimeCallback = (payload:
|
|
12
|
-
export
|
|
11
|
+
export type RealtimeCallback<T = any> = (payload: RealtimePayload<T>) => void;
|
|
12
|
+
export interface RealtimePayload<T = any> {
|
|
13
|
+
type: 'db_change' | 'chat_message';
|
|
14
|
+
topic: string;
|
|
15
|
+
operation: RealtimeEvent;
|
|
16
|
+
data: T;
|
|
17
|
+
old?: T;
|
|
18
|
+
timestamp?: string;
|
|
19
|
+
[key: string]: any;
|
|
20
|
+
}
|
|
21
|
+
export declare class RealtimeSubscription<T = any> {
|
|
13
22
|
private client;
|
|
14
23
|
private topic;
|
|
15
24
|
private options;
|
|
16
25
|
private callbacks;
|
|
17
26
|
private isSubscribed;
|
|
18
27
|
constructor(client: RealtimeClient, topic: string, options?: RealtimeSubscriptionOptions);
|
|
19
|
-
on(event: RealtimeEvent, callback: RealtimeCallback): this;
|
|
28
|
+
on(event: RealtimeEvent, callback: RealtimeCallback<T>): this;
|
|
20
29
|
subscribe(): this;
|
|
21
30
|
unsubscribe(): void;
|
|
22
31
|
/** @internal */
|
|
23
|
-
_emit(payload:
|
|
32
|
+
_emit(payload: RealtimePayload<T>): void;
|
|
33
|
+
}
|
|
34
|
+
export type RealtimeStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected';
|
|
35
|
+
export interface RealtimeClientOptions {
|
|
36
|
+
baseUrl: string;
|
|
37
|
+
projectId: string;
|
|
38
|
+
token?: string;
|
|
39
|
+
userId?: string;
|
|
40
|
+
apiKey?: string;
|
|
41
|
+
maxReconnectAttempts?: number;
|
|
24
42
|
}
|
|
25
43
|
export declare class RealtimeClient {
|
|
26
44
|
private baseUrl;
|
|
@@ -33,23 +51,35 @@ export declare class RealtimeClient {
|
|
|
33
51
|
private reconnectTimer;
|
|
34
52
|
private heartbeatTimer;
|
|
35
53
|
private reconnectAttempts;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
private _sendQueue;
|
|
55
|
+
private _connectingPromise;
|
|
56
|
+
private _status;
|
|
57
|
+
private _statusListeners;
|
|
58
|
+
private _lastPong;
|
|
59
|
+
private _maxReconnectAttempts;
|
|
60
|
+
private _maxRetriesListeners;
|
|
61
|
+
constructor(options: RealtimeClientOptions);
|
|
62
|
+
get status(): RealtimeStatus;
|
|
63
|
+
onStatusChange(cb: (status: RealtimeStatus) => void): () => void;
|
|
64
|
+
onMaxRetriesExceeded(cb: () => void): () => void;
|
|
65
|
+
private _setStatus;
|
|
66
|
+
setToken(newToken: string): void;
|
|
43
67
|
connect(): Promise<void>;
|
|
68
|
+
private _doConnect;
|
|
44
69
|
disconnect(): void;
|
|
45
|
-
channel(topic: string, options?: RealtimeSubscriptionOptions): RealtimeSubscription
|
|
70
|
+
channel<T = any>(topic: string, options?: RealtimeSubscriptionOptions): RealtimeSubscription<T>;
|
|
71
|
+
sendChat(roomId: string, text: string): void;
|
|
72
|
+
chatRoom(roomId: string): RealtimeSubscription;
|
|
46
73
|
/** @internal */
|
|
47
74
|
_send(data: any): void;
|
|
48
75
|
private handleMessage;
|
|
49
76
|
private startHeartbeat;
|
|
50
77
|
private stopHeartbeat;
|
|
51
|
-
/** Exponential backoff with jitter: 1s → 2s → 4s → ... → 30s */
|
|
52
78
|
private scheduleReconnect;
|
|
53
79
|
private stopReconnect;
|
|
80
|
+
private _handleOnline;
|
|
81
|
+
private _handleOffline;
|
|
82
|
+
private _setupOfflineDetection;
|
|
83
|
+
private _teardownOfflineDetection;
|
|
54
84
|
}
|
|
55
85
|
//# sourceMappingURL=realtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../../../src/sdk/realtime.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,GAAG,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB;AAED,MAAM,WAAW,2BAA2B;IACxC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../../../src/sdk/realtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -40,13 +40,8 @@ class RealtimeSubscription {
|
|
|
40
40
|
/** @internal */
|
|
41
41
|
_emit(payload) {
|
|
42
42
|
const event = payload.operation;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// Emit to specific event listeners
|
|
46
|
-
this.callbacks.get(event)?.forEach(cb => cb(payload));
|
|
47
|
-
// Emit to catch-all listeners
|
|
48
|
-
this.callbacks.get('*')?.forEach(cb => cb(payload));
|
|
49
|
-
}
|
|
43
|
+
this.callbacks.get(event)?.forEach(cb => cb(payload));
|
|
44
|
+
this.callbacks.get('*')?.forEach(cb => cb(payload));
|
|
50
45
|
}
|
|
51
46
|
}
|
|
52
47
|
exports.RealtimeSubscription = RealtimeSubscription;
|
|
@@ -57,7 +52,24 @@ class RealtimeClient {
|
|
|
57
52
|
this.reconnectTimer = null;
|
|
58
53
|
this.heartbeatTimer = null;
|
|
59
54
|
this.reconnectAttempts = 0;
|
|
60
|
-
this.
|
|
55
|
+
this._sendQueue = [];
|
|
56
|
+
this._connectingPromise = null;
|
|
57
|
+
this._status = 'idle';
|
|
58
|
+
this._statusListeners = new Set();
|
|
59
|
+
this._lastPong = 0;
|
|
60
|
+
this._maxRetriesListeners = new Set();
|
|
61
|
+
this._handleOnline = () => {
|
|
62
|
+
if (this._status !== 'connected') {
|
|
63
|
+
this.reconnectAttempts = 0;
|
|
64
|
+
this.connect().catch(() => { });
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
this._handleOffline = () => {
|
|
68
|
+
this.stopReconnect();
|
|
69
|
+
this._setStatus('disconnected');
|
|
70
|
+
};
|
|
71
|
+
const wsBase = options.baseUrl.replace(/\/v1\/?$/, '').replace(/^http/, 'ws');
|
|
72
|
+
this.baseUrl = `${wsBase}/api/realtime`;
|
|
61
73
|
this.projectId = options.projectId || '';
|
|
62
74
|
if (options.token)
|
|
63
75
|
this.token = options.token;
|
|
@@ -65,28 +77,64 @@ class RealtimeClient {
|
|
|
65
77
|
this.userId = options.userId;
|
|
66
78
|
if (options.apiKey)
|
|
67
79
|
this.apiKey = options.apiKey;
|
|
80
|
+
this._maxReconnectAttempts = options.maxReconnectAttempts ?? Infinity;
|
|
81
|
+
}
|
|
82
|
+
get status() { return this._status; }
|
|
83
|
+
onStatusChange(cb) {
|
|
84
|
+
this._statusListeners.add(cb);
|
|
85
|
+
return () => this._statusListeners.delete(cb);
|
|
86
|
+
}
|
|
87
|
+
onMaxRetriesExceeded(cb) {
|
|
88
|
+
this._maxRetriesListeners.add(cb);
|
|
89
|
+
return () => this._maxRetriesListeners.delete(cb);
|
|
90
|
+
}
|
|
91
|
+
_setStatus(s) {
|
|
92
|
+
this._status = s;
|
|
93
|
+
this._statusListeners.forEach(cb => cb(s));
|
|
94
|
+
}
|
|
95
|
+
setToken(newToken) {
|
|
96
|
+
this.token = newToken;
|
|
97
|
+
this._send({ type: 'auth', token: newToken });
|
|
68
98
|
}
|
|
69
99
|
connect() {
|
|
70
|
-
if (this.ws)
|
|
100
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN)
|
|
71
101
|
return Promise.resolve();
|
|
102
|
+
if (this._connectingPromise)
|
|
103
|
+
return this._connectingPromise;
|
|
104
|
+
this._connectingPromise = this._doConnect().finally(() => {
|
|
105
|
+
this._connectingPromise = null;
|
|
106
|
+
});
|
|
107
|
+
return this._connectingPromise;
|
|
108
|
+
}
|
|
109
|
+
_doConnect() {
|
|
110
|
+
this._setStatus('connecting');
|
|
72
111
|
return new Promise((resolve, reject) => {
|
|
73
112
|
const url = new URL(this.baseUrl);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
url.searchParams.set('projectId', this.projectId);
|
|
79
|
-
}
|
|
113
|
+
// Always include projectId in URL for routing (not auth)
|
|
114
|
+
url.searchParams.set('projectId', this.projectId);
|
|
80
115
|
if (this.userId)
|
|
81
116
|
url.searchParams.set('userId', this.userId);
|
|
82
117
|
if (this.token)
|
|
83
118
|
url.searchParams.set('token', this.token);
|
|
84
|
-
|
|
119
|
+
// SECURITY: Pass API key via Sec-WebSocket-Protocol header, NOT as a URL query param.
|
|
120
|
+
// URL query params appear in CDN logs, browser history, and Referer headers.
|
|
121
|
+
// Protocol format: "aerostack-key.<base64-encoded-key>"
|
|
122
|
+
const protocols = [];
|
|
123
|
+
if (this.apiKey) {
|
|
124
|
+
protocols.push(`aerostack-key.${this.apiKey}`);
|
|
125
|
+
}
|
|
126
|
+
this.ws = protocols.length > 0
|
|
127
|
+
? new WebSocket(url.toString(), protocols)
|
|
128
|
+
: new WebSocket(url.toString());
|
|
85
129
|
this.ws.onopen = () => {
|
|
86
|
-
|
|
130
|
+
this._setStatus('connected');
|
|
87
131
|
this.reconnectAttempts = 0;
|
|
132
|
+
this._lastPong = Date.now();
|
|
88
133
|
this.startHeartbeat();
|
|
89
|
-
|
|
134
|
+
this._setupOfflineDetection();
|
|
135
|
+
while (this._sendQueue.length > 0) {
|
|
136
|
+
this.ws.send(JSON.stringify(this._sendQueue.shift()));
|
|
137
|
+
}
|
|
90
138
|
for (const sub of this.subscriptions.values()) {
|
|
91
139
|
sub.subscribe();
|
|
92
140
|
}
|
|
@@ -102,22 +150,28 @@ class RealtimeClient {
|
|
|
102
150
|
}
|
|
103
151
|
};
|
|
104
152
|
this.ws.onclose = () => {
|
|
105
|
-
|
|
153
|
+
this._setStatus('reconnecting');
|
|
106
154
|
this.stopHeartbeat();
|
|
107
155
|
this.ws = null;
|
|
108
156
|
this.scheduleReconnect();
|
|
109
157
|
};
|
|
110
158
|
this.ws.onerror = (err) => {
|
|
111
159
|
console.error('Realtime connection error:', err);
|
|
160
|
+
this._setStatus('disconnected');
|
|
112
161
|
reject(err);
|
|
113
162
|
};
|
|
114
163
|
});
|
|
115
164
|
}
|
|
116
165
|
disconnect() {
|
|
166
|
+
this._setStatus('disconnected');
|
|
167
|
+
this.stopReconnect();
|
|
168
|
+
this.stopHeartbeat();
|
|
169
|
+
this._teardownOfflineDetection();
|
|
117
170
|
if (this.ws) {
|
|
118
171
|
this.ws.close();
|
|
172
|
+
this.ws = null;
|
|
119
173
|
}
|
|
120
|
-
this.
|
|
174
|
+
this._sendQueue = [];
|
|
121
175
|
}
|
|
122
176
|
channel(topic, options = {}) {
|
|
123
177
|
const fullTopic = topic.includes('/') ? topic : `table/${topic}/${this.projectId}`;
|
|
@@ -128,32 +182,54 @@ class RealtimeClient {
|
|
|
128
182
|
}
|
|
129
183
|
return sub;
|
|
130
184
|
}
|
|
185
|
+
sendChat(roomId, text) {
|
|
186
|
+
this._send({ type: 'chat', roomId, text });
|
|
187
|
+
}
|
|
188
|
+
chatRoom(roomId) {
|
|
189
|
+
return this.channel(`chat/${roomId}/${this.projectId}`);
|
|
190
|
+
}
|
|
131
191
|
/** @internal */
|
|
132
192
|
_send(data) {
|
|
133
193
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
134
194
|
this.ws.send(JSON.stringify(data));
|
|
135
195
|
}
|
|
196
|
+
else {
|
|
197
|
+
this._sendQueue.push(data);
|
|
198
|
+
}
|
|
136
199
|
}
|
|
137
200
|
handleMessage(data) {
|
|
201
|
+
if (data.type === 'pong') {
|
|
202
|
+
this._lastPong = Date.now();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
138
205
|
if (data.type === 'db_change' || data.type === 'chat_message') {
|
|
139
206
|
const sub = this.subscriptions.get(data.topic);
|
|
140
|
-
if (sub)
|
|
207
|
+
if (sub)
|
|
141
208
|
sub._emit(data);
|
|
142
|
-
}
|
|
143
209
|
}
|
|
144
210
|
}
|
|
145
211
|
startHeartbeat() {
|
|
146
212
|
this.heartbeatTimer = setInterval(() => {
|
|
147
213
|
this._send({ type: 'ping' });
|
|
214
|
+
if (this._lastPong > 0 && Date.now() - this._lastPong > 70000) {
|
|
215
|
+
console.warn('Realtime: no pong received, forcing reconnect');
|
|
216
|
+
this.ws?.close();
|
|
217
|
+
}
|
|
148
218
|
}, 30000);
|
|
149
219
|
}
|
|
150
220
|
stopHeartbeat() {
|
|
151
|
-
if (this.heartbeatTimer)
|
|
221
|
+
if (this.heartbeatTimer) {
|
|
152
222
|
clearInterval(this.heartbeatTimer);
|
|
223
|
+
this.heartbeatTimer = null;
|
|
224
|
+
}
|
|
153
225
|
}
|
|
154
|
-
/** Exponential backoff with jitter: 1s → 2s → 4s → ... → 30s */
|
|
155
226
|
scheduleReconnect() {
|
|
156
227
|
this.stopReconnect();
|
|
228
|
+
if (this.reconnectAttempts >= this._maxReconnectAttempts) {
|
|
229
|
+
this._setStatus('disconnected');
|
|
230
|
+
this._maxRetriesListeners.forEach(cb => cb());
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
157
233
|
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
|
|
158
234
|
const jitter = delay * 0.3 * Math.random();
|
|
159
235
|
this.reconnectAttempts++;
|
|
@@ -162,8 +238,22 @@ class RealtimeClient {
|
|
|
162
238
|
}, delay + jitter);
|
|
163
239
|
}
|
|
164
240
|
stopReconnect() {
|
|
165
|
-
if (this.reconnectTimer)
|
|
241
|
+
if (this.reconnectTimer) {
|
|
166
242
|
clearTimeout(this.reconnectTimer);
|
|
243
|
+
this.reconnectTimer = null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
_setupOfflineDetection() {
|
|
247
|
+
if (typeof window !== 'undefined') {
|
|
248
|
+
window.addEventListener('online', this._handleOnline);
|
|
249
|
+
window.addEventListener('offline', this._handleOffline);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
_teardownOfflineDetection() {
|
|
253
|
+
if (typeof window !== 'undefined') {
|
|
254
|
+
window.removeEventListener('online', this._handleOnline);
|
|
255
|
+
window.removeEventListener('offline', this._handleOffline);
|
|
256
|
+
}
|
|
167
257
|
}
|
|
168
258
|
}
|
|
169
259
|
exports.RealtimeClient = RealtimeClient;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime.js","sourceRoot":"","sources":["../../../src/sdk/realtime.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"realtime.js","sourceRoot":"","sources":["../../../src/sdk/realtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,19 +8,37 @@ export interface RealtimeSubscriptionOptions {
|
|
|
8
8
|
event?: RealtimeEvent;
|
|
9
9
|
filter?: Record<string, any>;
|
|
10
10
|
}
|
|
11
|
-
export type RealtimeCallback = (payload:
|
|
12
|
-
export
|
|
11
|
+
export type RealtimeCallback<T = any> = (payload: RealtimePayload<T>) => void;
|
|
12
|
+
export interface RealtimePayload<T = any> {
|
|
13
|
+
type: 'db_change' | 'chat_message';
|
|
14
|
+
topic: string;
|
|
15
|
+
operation: RealtimeEvent;
|
|
16
|
+
data: T;
|
|
17
|
+
old?: T;
|
|
18
|
+
timestamp?: string;
|
|
19
|
+
[key: string]: any;
|
|
20
|
+
}
|
|
21
|
+
export declare class RealtimeSubscription<T = any> {
|
|
13
22
|
private client;
|
|
14
23
|
private topic;
|
|
15
24
|
private options;
|
|
16
25
|
private callbacks;
|
|
17
26
|
private isSubscribed;
|
|
18
27
|
constructor(client: RealtimeClient, topic: string, options?: RealtimeSubscriptionOptions);
|
|
19
|
-
on(event: RealtimeEvent, callback: RealtimeCallback): this;
|
|
28
|
+
on(event: RealtimeEvent, callback: RealtimeCallback<T>): this;
|
|
20
29
|
subscribe(): this;
|
|
21
30
|
unsubscribe(): void;
|
|
22
31
|
/** @internal */
|
|
23
|
-
_emit(payload:
|
|
32
|
+
_emit(payload: RealtimePayload<T>): void;
|
|
33
|
+
}
|
|
34
|
+
export type RealtimeStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected';
|
|
35
|
+
export interface RealtimeClientOptions {
|
|
36
|
+
baseUrl: string;
|
|
37
|
+
projectId: string;
|
|
38
|
+
token?: string;
|
|
39
|
+
userId?: string;
|
|
40
|
+
apiKey?: string;
|
|
41
|
+
maxReconnectAttempts?: number;
|
|
24
42
|
}
|
|
25
43
|
export declare class RealtimeClient {
|
|
26
44
|
private baseUrl;
|
|
@@ -33,23 +51,35 @@ export declare class RealtimeClient {
|
|
|
33
51
|
private reconnectTimer;
|
|
34
52
|
private heartbeatTimer;
|
|
35
53
|
private reconnectAttempts;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
private _sendQueue;
|
|
55
|
+
private _connectingPromise;
|
|
56
|
+
private _status;
|
|
57
|
+
private _statusListeners;
|
|
58
|
+
private _lastPong;
|
|
59
|
+
private _maxReconnectAttempts;
|
|
60
|
+
private _maxRetriesListeners;
|
|
61
|
+
constructor(options: RealtimeClientOptions);
|
|
62
|
+
get status(): RealtimeStatus;
|
|
63
|
+
onStatusChange(cb: (status: RealtimeStatus) => void): () => void;
|
|
64
|
+
onMaxRetriesExceeded(cb: () => void): () => void;
|
|
65
|
+
private _setStatus;
|
|
66
|
+
setToken(newToken: string): void;
|
|
43
67
|
connect(): Promise<void>;
|
|
68
|
+
private _doConnect;
|
|
44
69
|
disconnect(): void;
|
|
45
|
-
channel(topic: string, options?: RealtimeSubscriptionOptions): RealtimeSubscription
|
|
70
|
+
channel<T = any>(topic: string, options?: RealtimeSubscriptionOptions): RealtimeSubscription<T>;
|
|
71
|
+
sendChat(roomId: string, text: string): void;
|
|
72
|
+
chatRoom(roomId: string): RealtimeSubscription;
|
|
46
73
|
/** @internal */
|
|
47
74
|
_send(data: any): void;
|
|
48
75
|
private handleMessage;
|
|
49
76
|
private startHeartbeat;
|
|
50
77
|
private stopHeartbeat;
|
|
51
|
-
/** Exponential backoff with jitter: 1s → 2s → 4s → ... → 30s */
|
|
52
78
|
private scheduleReconnect;
|
|
53
79
|
private stopReconnect;
|
|
80
|
+
private _handleOnline;
|
|
81
|
+
private _handleOffline;
|
|
82
|
+
private _setupOfflineDetection;
|
|
83
|
+
private _teardownOfflineDetection;
|
|
54
84
|
}
|
|
55
85
|
//# sourceMappingURL=realtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../../../src/sdk/realtime.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,GAAG,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB;AAED,MAAM,WAAW,2BAA2B;IACxC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,CAAC;
|
|
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
|
@@ -37,13 +37,8 @@ export class RealtimeSubscription {
|
|
|
37
37
|
/** @internal */
|
|
38
38
|
_emit(payload) {
|
|
39
39
|
const event = payload.operation;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// Emit to specific event listeners
|
|
43
|
-
this.callbacks.get(event)?.forEach(cb => cb(payload));
|
|
44
|
-
// Emit to catch-all listeners
|
|
45
|
-
this.callbacks.get('*')?.forEach(cb => cb(payload));
|
|
46
|
-
}
|
|
40
|
+
this.callbacks.get(event)?.forEach(cb => cb(payload));
|
|
41
|
+
this.callbacks.get('*')?.forEach(cb => cb(payload));
|
|
47
42
|
}
|
|
48
43
|
}
|
|
49
44
|
export class RealtimeClient {
|
|
@@ -53,7 +48,24 @@ export class RealtimeClient {
|
|
|
53
48
|
this.reconnectTimer = null;
|
|
54
49
|
this.heartbeatTimer = null;
|
|
55
50
|
this.reconnectAttempts = 0;
|
|
56
|
-
this.
|
|
51
|
+
this._sendQueue = [];
|
|
52
|
+
this._connectingPromise = null;
|
|
53
|
+
this._status = 'idle';
|
|
54
|
+
this._statusListeners = new Set();
|
|
55
|
+
this._lastPong = 0;
|
|
56
|
+
this._maxRetriesListeners = new Set();
|
|
57
|
+
this._handleOnline = () => {
|
|
58
|
+
if (this._status !== 'connected') {
|
|
59
|
+
this.reconnectAttempts = 0;
|
|
60
|
+
this.connect().catch(() => { });
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
this._handleOffline = () => {
|
|
64
|
+
this.stopReconnect();
|
|
65
|
+
this._setStatus('disconnected');
|
|
66
|
+
};
|
|
67
|
+
const wsBase = options.baseUrl.replace(/\/v1\/?$/, '').replace(/^http/, 'ws');
|
|
68
|
+
this.baseUrl = `${wsBase}/api/realtime`;
|
|
57
69
|
this.projectId = options.projectId || '';
|
|
58
70
|
if (options.token)
|
|
59
71
|
this.token = options.token;
|
|
@@ -61,28 +73,64 @@ export class RealtimeClient {
|
|
|
61
73
|
this.userId = options.userId;
|
|
62
74
|
if (options.apiKey)
|
|
63
75
|
this.apiKey = options.apiKey;
|
|
76
|
+
this._maxReconnectAttempts = options.maxReconnectAttempts ?? Infinity;
|
|
77
|
+
}
|
|
78
|
+
get status() { return this._status; }
|
|
79
|
+
onStatusChange(cb) {
|
|
80
|
+
this._statusListeners.add(cb);
|
|
81
|
+
return () => this._statusListeners.delete(cb);
|
|
82
|
+
}
|
|
83
|
+
onMaxRetriesExceeded(cb) {
|
|
84
|
+
this._maxRetriesListeners.add(cb);
|
|
85
|
+
return () => this._maxRetriesListeners.delete(cb);
|
|
86
|
+
}
|
|
87
|
+
_setStatus(s) {
|
|
88
|
+
this._status = s;
|
|
89
|
+
this._statusListeners.forEach(cb => cb(s));
|
|
90
|
+
}
|
|
91
|
+
setToken(newToken) {
|
|
92
|
+
this.token = newToken;
|
|
93
|
+
this._send({ type: 'auth', token: newToken });
|
|
64
94
|
}
|
|
65
95
|
connect() {
|
|
66
|
-
if (this.ws)
|
|
96
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN)
|
|
67
97
|
return Promise.resolve();
|
|
98
|
+
if (this._connectingPromise)
|
|
99
|
+
return this._connectingPromise;
|
|
100
|
+
this._connectingPromise = this._doConnect().finally(() => {
|
|
101
|
+
this._connectingPromise = null;
|
|
102
|
+
});
|
|
103
|
+
return this._connectingPromise;
|
|
104
|
+
}
|
|
105
|
+
_doConnect() {
|
|
106
|
+
this._setStatus('connecting');
|
|
68
107
|
return new Promise((resolve, reject) => {
|
|
69
108
|
const url = new URL(this.baseUrl);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
url.searchParams.set('projectId', this.projectId);
|
|
75
|
-
}
|
|
109
|
+
// Always include projectId in URL for routing (not auth)
|
|
110
|
+
url.searchParams.set('projectId', this.projectId);
|
|
76
111
|
if (this.userId)
|
|
77
112
|
url.searchParams.set('userId', this.userId);
|
|
78
113
|
if (this.token)
|
|
79
114
|
url.searchParams.set('token', this.token);
|
|
80
|
-
|
|
115
|
+
// SECURITY: Pass API key via Sec-WebSocket-Protocol header, NOT as a URL query param.
|
|
116
|
+
// URL query params appear in CDN logs, browser history, and Referer headers.
|
|
117
|
+
// Protocol format: "aerostack-key.<base64-encoded-key>"
|
|
118
|
+
const protocols = [];
|
|
119
|
+
if (this.apiKey) {
|
|
120
|
+
protocols.push(`aerostack-key.${this.apiKey}`);
|
|
121
|
+
}
|
|
122
|
+
this.ws = protocols.length > 0
|
|
123
|
+
? new WebSocket(url.toString(), protocols)
|
|
124
|
+
: new WebSocket(url.toString());
|
|
81
125
|
this.ws.onopen = () => {
|
|
82
|
-
|
|
126
|
+
this._setStatus('connected');
|
|
83
127
|
this.reconnectAttempts = 0;
|
|
128
|
+
this._lastPong = Date.now();
|
|
84
129
|
this.startHeartbeat();
|
|
85
|
-
|
|
130
|
+
this._setupOfflineDetection();
|
|
131
|
+
while (this._sendQueue.length > 0) {
|
|
132
|
+
this.ws.send(JSON.stringify(this._sendQueue.shift()));
|
|
133
|
+
}
|
|
86
134
|
for (const sub of this.subscriptions.values()) {
|
|
87
135
|
sub.subscribe();
|
|
88
136
|
}
|
|
@@ -98,22 +146,28 @@ export class RealtimeClient {
|
|
|
98
146
|
}
|
|
99
147
|
};
|
|
100
148
|
this.ws.onclose = () => {
|
|
101
|
-
|
|
149
|
+
this._setStatus('reconnecting');
|
|
102
150
|
this.stopHeartbeat();
|
|
103
151
|
this.ws = null;
|
|
104
152
|
this.scheduleReconnect();
|
|
105
153
|
};
|
|
106
154
|
this.ws.onerror = (err) => {
|
|
107
155
|
console.error('Realtime connection error:', err);
|
|
156
|
+
this._setStatus('disconnected');
|
|
108
157
|
reject(err);
|
|
109
158
|
};
|
|
110
159
|
});
|
|
111
160
|
}
|
|
112
161
|
disconnect() {
|
|
162
|
+
this._setStatus('disconnected');
|
|
163
|
+
this.stopReconnect();
|
|
164
|
+
this.stopHeartbeat();
|
|
165
|
+
this._teardownOfflineDetection();
|
|
113
166
|
if (this.ws) {
|
|
114
167
|
this.ws.close();
|
|
168
|
+
this.ws = null;
|
|
115
169
|
}
|
|
116
|
-
this.
|
|
170
|
+
this._sendQueue = [];
|
|
117
171
|
}
|
|
118
172
|
channel(topic, options = {}) {
|
|
119
173
|
const fullTopic = topic.includes('/') ? topic : `table/${topic}/${this.projectId}`;
|
|
@@ -124,32 +178,54 @@ export class RealtimeClient {
|
|
|
124
178
|
}
|
|
125
179
|
return sub;
|
|
126
180
|
}
|
|
181
|
+
sendChat(roomId, text) {
|
|
182
|
+
this._send({ type: 'chat', roomId, text });
|
|
183
|
+
}
|
|
184
|
+
chatRoom(roomId) {
|
|
185
|
+
return this.channel(`chat/${roomId}/${this.projectId}`);
|
|
186
|
+
}
|
|
127
187
|
/** @internal */
|
|
128
188
|
_send(data) {
|
|
129
189
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
130
190
|
this.ws.send(JSON.stringify(data));
|
|
131
191
|
}
|
|
192
|
+
else {
|
|
193
|
+
this._sendQueue.push(data);
|
|
194
|
+
}
|
|
132
195
|
}
|
|
133
196
|
handleMessage(data) {
|
|
197
|
+
if (data.type === 'pong') {
|
|
198
|
+
this._lastPong = Date.now();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
134
201
|
if (data.type === 'db_change' || data.type === 'chat_message') {
|
|
135
202
|
const sub = this.subscriptions.get(data.topic);
|
|
136
|
-
if (sub)
|
|
203
|
+
if (sub)
|
|
137
204
|
sub._emit(data);
|
|
138
|
-
}
|
|
139
205
|
}
|
|
140
206
|
}
|
|
141
207
|
startHeartbeat() {
|
|
142
208
|
this.heartbeatTimer = setInterval(() => {
|
|
143
209
|
this._send({ type: 'ping' });
|
|
210
|
+
if (this._lastPong > 0 && Date.now() - this._lastPong > 70000) {
|
|
211
|
+
console.warn('Realtime: no pong received, forcing reconnect');
|
|
212
|
+
this.ws?.close();
|
|
213
|
+
}
|
|
144
214
|
}, 30000);
|
|
145
215
|
}
|
|
146
216
|
stopHeartbeat() {
|
|
147
|
-
if (this.heartbeatTimer)
|
|
217
|
+
if (this.heartbeatTimer) {
|
|
148
218
|
clearInterval(this.heartbeatTimer);
|
|
219
|
+
this.heartbeatTimer = null;
|
|
220
|
+
}
|
|
149
221
|
}
|
|
150
|
-
/** Exponential backoff with jitter: 1s → 2s → 4s → ... → 30s */
|
|
151
222
|
scheduleReconnect() {
|
|
152
223
|
this.stopReconnect();
|
|
224
|
+
if (this.reconnectAttempts >= this._maxReconnectAttempts) {
|
|
225
|
+
this._setStatus('disconnected');
|
|
226
|
+
this._maxRetriesListeners.forEach(cb => cb());
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
153
229
|
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
|
|
154
230
|
const jitter = delay * 0.3 * Math.random();
|
|
155
231
|
this.reconnectAttempts++;
|
|
@@ -158,8 +234,22 @@ export class RealtimeClient {
|
|
|
158
234
|
}, delay + jitter);
|
|
159
235
|
}
|
|
160
236
|
stopReconnect() {
|
|
161
|
-
if (this.reconnectTimer)
|
|
237
|
+
if (this.reconnectTimer) {
|
|
162
238
|
clearTimeout(this.reconnectTimer);
|
|
239
|
+
this.reconnectTimer = null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
_setupOfflineDetection() {
|
|
243
|
+
if (typeof window !== 'undefined') {
|
|
244
|
+
window.addEventListener('online', this._handleOnline);
|
|
245
|
+
window.addEventListener('offline', this._handleOffline);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
_teardownOfflineDetection() {
|
|
249
|
+
if (typeof window !== 'undefined') {
|
|
250
|
+
window.removeEventListener('online', this._handleOnline);
|
|
251
|
+
window.removeEventListener('offline', this._handleOffline);
|
|
252
|
+
}
|
|
163
253
|
}
|
|
164
254
|
}
|
|
165
255
|
//# sourceMappingURL=realtime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime.js","sourceRoot":"","sources":["../../../src/sdk/realtime.ts"],"names":[],"mappings":"
|
|
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-web",
|
|
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
|
@@ -11,13 +11,23 @@ export interface RealtimeSubscriptionOptions {
|
|
|
11
11
|
filter?: Record<string, any>;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export type RealtimeCallback = (payload:
|
|
14
|
+
export type RealtimeCallback<T = any> = (payload: RealtimePayload<T>) => void;
|
|
15
15
|
|
|
16
|
-
export
|
|
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
|
+
|
|
26
|
+
export class RealtimeSubscription<T = any> {
|
|
17
27
|
private client: RealtimeClient;
|
|
18
28
|
private topic: string;
|
|
19
29
|
private options: RealtimeSubscriptionOptions;
|
|
20
|
-
private callbacks: Map<RealtimeEvent, Set<RealtimeCallback
|
|
30
|
+
private callbacks: Map<RealtimeEvent, Set<RealtimeCallback<T>>> = new Map();
|
|
21
31
|
private isSubscribed: boolean = false;
|
|
22
32
|
|
|
23
33
|
constructor(client: RealtimeClient, topic: string, options: RealtimeSubscriptionOptions = {}) {
|
|
@@ -26,7 +36,7 @@ export class RealtimeSubscription {
|
|
|
26
36
|
this.options = options;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
|
-
on(event: RealtimeEvent, callback: RealtimeCallback): this {
|
|
39
|
+
on(event: RealtimeEvent, callback: RealtimeCallback<T>): this {
|
|
30
40
|
if (!this.callbacks.has(event)) {
|
|
31
41
|
this.callbacks.set(event, new Set());
|
|
32
42
|
}
|
|
@@ -56,19 +66,24 @@ export class RealtimeSubscription {
|
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
/** @internal */
|
|
59
|
-
_emit(payload:
|
|
69
|
+
_emit(payload: RealtimePayload<T>): void {
|
|
60
70
|
const event = payload.operation as RealtimeEvent;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (requestedEvent === '*' || requestedEvent === event) {
|
|
64
|
-
// Emit to specific event listeners
|
|
65
|
-
this.callbacks.get(event)?.forEach(cb => cb(payload));
|
|
66
|
-
// Emit to catch-all listeners
|
|
67
|
-
this.callbacks.get('*')?.forEach(cb => cb(payload));
|
|
68
|
-
}
|
|
71
|
+
this.callbacks.get(event)?.forEach(cb => cb(payload));
|
|
72
|
+
this.callbacks.get('*')?.forEach(cb => cb(payload));
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
|
|
76
|
+
export type RealtimeStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected';
|
|
77
|
+
|
|
78
|
+
export interface RealtimeClientOptions {
|
|
79
|
+
baseUrl: string;
|
|
80
|
+
projectId: string;
|
|
81
|
+
token?: string;
|
|
82
|
+
userId?: string;
|
|
83
|
+
apiKey?: string;
|
|
84
|
+
maxReconnectAttempts?: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
72
87
|
export class RealtimeClient {
|
|
73
88
|
private baseUrl: string;
|
|
74
89
|
private projectId: string;
|
|
@@ -80,35 +95,85 @@ export class RealtimeClient {
|
|
|
80
95
|
private reconnectTimer: any = null;
|
|
81
96
|
private heartbeatTimer: any = null;
|
|
82
97
|
private reconnectAttempts: number = 0;
|
|
98
|
+
private _sendQueue: any[] = [];
|
|
99
|
+
private _connectingPromise: Promise<void> | null = null;
|
|
100
|
+
private _status: RealtimeStatus = 'idle';
|
|
101
|
+
private _statusListeners: Set<(s: RealtimeStatus) => void> = new Set();
|
|
102
|
+
private _lastPong: number = 0;
|
|
103
|
+
private _maxReconnectAttempts: number;
|
|
104
|
+
private _maxRetriesListeners: Set<() => void> = new Set();
|
|
83
105
|
|
|
84
|
-
constructor(options:
|
|
85
|
-
|
|
106
|
+
constructor(options: RealtimeClientOptions) {
|
|
107
|
+
const wsBase = options.baseUrl.replace(/\/v1\/?$/, '').replace(/^http/, 'ws');
|
|
108
|
+
this.baseUrl = `${wsBase}/api/realtime`;
|
|
86
109
|
this.projectId = options.projectId || '';
|
|
87
110
|
if (options.token) this.token = options.token;
|
|
88
111
|
if (options.userId) this.userId = options.userId;
|
|
89
112
|
if (options.apiKey) this.apiKey = options.apiKey;
|
|
113
|
+
this._maxReconnectAttempts = options.maxReconnectAttempts ?? Infinity;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get status(): RealtimeStatus { return this._status; }
|
|
117
|
+
|
|
118
|
+
onStatusChange(cb: (status: RealtimeStatus) => void): () => void {
|
|
119
|
+
this._statusListeners.add(cb);
|
|
120
|
+
return () => this._statusListeners.delete(cb);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
onMaxRetriesExceeded(cb: () => void): () => void {
|
|
124
|
+
this._maxRetriesListeners.add(cb);
|
|
125
|
+
return () => this._maxRetriesListeners.delete(cb);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private _setStatus(s: RealtimeStatus) {
|
|
129
|
+
this._status = s;
|
|
130
|
+
this._statusListeners.forEach(cb => cb(s));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
setToken(newToken: string): void {
|
|
134
|
+
this.token = newToken;
|
|
135
|
+
this._send({ type: 'auth', token: newToken });
|
|
90
136
|
}
|
|
91
137
|
|
|
92
138
|
connect(): Promise<void> {
|
|
93
|
-
if (this.ws) return Promise.resolve();
|
|
139
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) return Promise.resolve();
|
|
140
|
+
if (this._connectingPromise) return this._connectingPromise;
|
|
141
|
+
this._connectingPromise = this._doConnect().finally(() => {
|
|
142
|
+
this._connectingPromise = null;
|
|
143
|
+
});
|
|
144
|
+
return this._connectingPromise;
|
|
145
|
+
}
|
|
94
146
|
|
|
147
|
+
private _doConnect(): Promise<void> {
|
|
148
|
+
this._setStatus('connecting');
|
|
95
149
|
return new Promise((resolve, reject) => {
|
|
96
150
|
const url = new URL(this.baseUrl);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
} else {
|
|
100
|
-
url.searchParams.set('projectId', this.projectId);
|
|
101
|
-
}
|
|
151
|
+
// Always include projectId in URL for routing (not auth)
|
|
152
|
+
url.searchParams.set('projectId', this.projectId);
|
|
102
153
|
if (this.userId) url.searchParams.set('userId', this.userId);
|
|
103
154
|
if (this.token) url.searchParams.set('token', this.token);
|
|
104
155
|
|
|
105
|
-
|
|
156
|
+
// SECURITY: Pass API key via Sec-WebSocket-Protocol header, NOT as a URL query param.
|
|
157
|
+
// URL query params appear in CDN logs, browser history, and Referer headers.
|
|
158
|
+
// Protocol format: "aerostack-key.<base64-encoded-key>"
|
|
159
|
+
const protocols: string[] = [];
|
|
160
|
+
if (this.apiKey) {
|
|
161
|
+
protocols.push(`aerostack-key.${this.apiKey}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.ws = protocols.length > 0
|
|
165
|
+
? new WebSocket(url.toString(), protocols)
|
|
166
|
+
: new WebSocket(url.toString());
|
|
106
167
|
|
|
107
168
|
this.ws.onopen = () => {
|
|
108
|
-
|
|
169
|
+
this._setStatus('connected');
|
|
109
170
|
this.reconnectAttempts = 0;
|
|
171
|
+
this._lastPong = Date.now();
|
|
110
172
|
this.startHeartbeat();
|
|
111
|
-
|
|
173
|
+
this._setupOfflineDetection();
|
|
174
|
+
while (this._sendQueue.length > 0) {
|
|
175
|
+
this.ws!.send(JSON.stringify(this._sendQueue.shift()));
|
|
176
|
+
}
|
|
112
177
|
for (const sub of this.subscriptions.values()) {
|
|
113
178
|
sub.subscribe();
|
|
114
179
|
}
|
|
@@ -125,7 +190,7 @@ export class RealtimeClient {
|
|
|
125
190
|
};
|
|
126
191
|
|
|
127
192
|
this.ws.onclose = () => {
|
|
128
|
-
|
|
193
|
+
this._setStatus('reconnecting');
|
|
129
194
|
this.stopHeartbeat();
|
|
130
195
|
this.ws = null;
|
|
131
196
|
this.scheduleReconnect();
|
|
@@ -133,58 +198,86 @@ export class RealtimeClient {
|
|
|
133
198
|
|
|
134
199
|
this.ws.onerror = (err) => {
|
|
135
200
|
console.error('Realtime connection error:', err);
|
|
201
|
+
this._setStatus('disconnected');
|
|
136
202
|
reject(err);
|
|
137
203
|
};
|
|
138
204
|
});
|
|
139
205
|
}
|
|
140
206
|
|
|
141
207
|
disconnect() {
|
|
208
|
+
this._setStatus('disconnected');
|
|
209
|
+
this.stopReconnect();
|
|
210
|
+
this.stopHeartbeat();
|
|
211
|
+
this._teardownOfflineDetection();
|
|
142
212
|
if (this.ws) {
|
|
143
213
|
this.ws.close();
|
|
214
|
+
this.ws = null;
|
|
144
215
|
}
|
|
145
|
-
this.
|
|
216
|
+
this._sendQueue = [];
|
|
146
217
|
}
|
|
147
218
|
|
|
148
|
-
channel(topic: string, options: RealtimeSubscriptionOptions = {}): RealtimeSubscription {
|
|
219
|
+
channel<T = any>(topic: string, options: RealtimeSubscriptionOptions = {}): RealtimeSubscription<T> {
|
|
149
220
|
const fullTopic = topic.includes('/') ? topic : `table/${topic}/${this.projectId}`;
|
|
150
|
-
|
|
151
221
|
let sub = this.subscriptions.get(fullTopic);
|
|
152
222
|
if (!sub) {
|
|
153
|
-
sub = new RealtimeSubscription(this, fullTopic, options);
|
|
223
|
+
sub = new RealtimeSubscription<T>(this, fullTopic, options);
|
|
154
224
|
this.subscriptions.set(fullTopic, sub);
|
|
155
225
|
}
|
|
156
|
-
return sub
|
|
226
|
+
return sub as RealtimeSubscription<T>;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
sendChat(roomId: string, text: string): void {
|
|
230
|
+
this._send({ type: 'chat', roomId, text });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
chatRoom(roomId: string): RealtimeSubscription {
|
|
234
|
+
return this.channel(`chat/${roomId}/${this.projectId}`);
|
|
157
235
|
}
|
|
158
236
|
|
|
159
237
|
/** @internal */
|
|
160
238
|
_send(data: any) {
|
|
161
239
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
162
240
|
this.ws.send(JSON.stringify(data));
|
|
241
|
+
} else {
|
|
242
|
+
this._sendQueue.push(data);
|
|
163
243
|
}
|
|
164
244
|
}
|
|
165
245
|
|
|
166
246
|
private handleMessage(data: RealtimeMessage) {
|
|
247
|
+
if (data.type === 'pong') {
|
|
248
|
+
this._lastPong = Date.now();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
167
251
|
if (data.type === 'db_change' || data.type === 'chat_message') {
|
|
168
252
|
const sub = this.subscriptions.get(data.topic);
|
|
169
|
-
if (sub)
|
|
170
|
-
sub._emit(data);
|
|
171
|
-
}
|
|
253
|
+
if (sub) sub._emit(data as any);
|
|
172
254
|
}
|
|
173
255
|
}
|
|
174
256
|
|
|
175
257
|
private startHeartbeat() {
|
|
176
258
|
this.heartbeatTimer = setInterval(() => {
|
|
177
259
|
this._send({ type: 'ping' });
|
|
260
|
+
if (this._lastPong > 0 && Date.now() - this._lastPong > 70000) {
|
|
261
|
+
console.warn('Realtime: no pong received, forcing reconnect');
|
|
262
|
+
this.ws?.close();
|
|
263
|
+
}
|
|
178
264
|
}, 30000);
|
|
179
265
|
}
|
|
180
266
|
|
|
181
267
|
private stopHeartbeat() {
|
|
182
|
-
if (this.heartbeatTimer)
|
|
268
|
+
if (this.heartbeatTimer) {
|
|
269
|
+
clearInterval(this.heartbeatTimer);
|
|
270
|
+
this.heartbeatTimer = null;
|
|
271
|
+
}
|
|
183
272
|
}
|
|
184
273
|
|
|
185
|
-
/** Exponential backoff with jitter: 1s → 2s → 4s → ... → 30s */
|
|
186
274
|
private scheduleReconnect() {
|
|
187
275
|
this.stopReconnect();
|
|
276
|
+
if (this.reconnectAttempts >= this._maxReconnectAttempts) {
|
|
277
|
+
this._setStatus('disconnected');
|
|
278
|
+
this._maxRetriesListeners.forEach(cb => cb());
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
188
281
|
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
|
|
189
282
|
const jitter = delay * 0.3 * Math.random();
|
|
190
283
|
this.reconnectAttempts++;
|
|
@@ -194,6 +287,35 @@ export class RealtimeClient {
|
|
|
194
287
|
}
|
|
195
288
|
|
|
196
289
|
private stopReconnect() {
|
|
197
|
-
if (this.reconnectTimer)
|
|
290
|
+
if (this.reconnectTimer) {
|
|
291
|
+
clearTimeout(this.reconnectTimer);
|
|
292
|
+
this.reconnectTimer = null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private _handleOnline = () => {
|
|
297
|
+
if (this._status !== 'connected') {
|
|
298
|
+
this.reconnectAttempts = 0;
|
|
299
|
+
this.connect().catch(() => { });
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
private _handleOffline = () => {
|
|
304
|
+
this.stopReconnect();
|
|
305
|
+
this._setStatus('disconnected');
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
private _setupOfflineDetection() {
|
|
309
|
+
if (typeof window !== 'undefined') {
|
|
310
|
+
window.addEventListener('online', this._handleOnline);
|
|
311
|
+
window.addEventListener('offline', this._handleOffline);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private _teardownOfflineDetection() {
|
|
316
|
+
if (typeof window !== 'undefined') {
|
|
317
|
+
window.removeEventListener('online', this._handleOnline);
|
|
318
|
+
window.removeEventListener('offline', this._handleOffline);
|
|
319
|
+
}
|
|
198
320
|
}
|
|
199
321
|
}
|