@supabase/realtime-js 2.99.2 → 2.100.0-rc.0
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/main/RealtimeChannel.d.ts +35 -28
- package/dist/main/RealtimeChannel.d.ts.map +1 -1
- package/dist/main/RealtimeChannel.js +140 -301
- package/dist/main/RealtimeChannel.js.map +1 -1
- package/dist/main/RealtimeClient.d.ts +38 -57
- package/dist/main/RealtimeClient.d.ts.map +1 -1
- package/dist/main/RealtimeClient.js +232 -520
- package/dist/main/RealtimeClient.js.map +1 -1
- package/dist/main/RealtimePresence.d.ts +8 -24
- package/dist/main/RealtimePresence.d.ts.map +1 -1
- package/dist/main/RealtimePresence.js +6 -202
- package/dist/main/RealtimePresence.js.map +1 -1
- package/dist/main/lib/constants.d.ts +39 -35
- package/dist/main/lib/constants.d.ts.map +1 -1
- package/dist/main/lib/constants.js +30 -35
- package/dist/main/lib/constants.js.map +1 -1
- package/dist/main/lib/version.d.ts +1 -1
- package/dist/main/lib/version.d.ts.map +1 -1
- package/dist/main/lib/version.js +1 -1
- package/dist/main/lib/version.js.map +1 -1
- package/dist/main/lib/websocket-factory.d.ts +0 -9
- package/dist/main/lib/websocket-factory.d.ts.map +1 -1
- package/dist/main/lib/websocket-factory.js +0 -12
- package/dist/main/lib/websocket-factory.js.map +1 -1
- package/dist/main/phoenix/channelAdapter.d.ts +32 -0
- package/dist/main/phoenix/channelAdapter.d.ts.map +1 -0
- package/dist/main/phoenix/channelAdapter.js +103 -0
- package/dist/main/phoenix/channelAdapter.js.map +1 -0
- package/dist/main/phoenix/presenceAdapter.d.ts +53 -0
- package/dist/main/phoenix/presenceAdapter.d.ts.map +1 -0
- package/dist/main/phoenix/presenceAdapter.js +93 -0
- package/dist/main/phoenix/presenceAdapter.js.map +1 -0
- package/dist/main/phoenix/socketAdapter.d.ts +38 -0
- package/dist/main/phoenix/socketAdapter.d.ts.map +1 -0
- package/dist/main/phoenix/socketAdapter.js +114 -0
- package/dist/main/phoenix/socketAdapter.js.map +1 -0
- package/dist/main/phoenix/types.d.ts +5 -0
- package/dist/main/phoenix/types.d.ts.map +1 -0
- package/dist/main/phoenix/types.js +3 -0
- package/dist/main/phoenix/types.js.map +1 -0
- package/dist/module/RealtimeChannel.d.ts +35 -28
- package/dist/module/RealtimeChannel.d.ts.map +1 -1
- package/dist/module/RealtimeChannel.js +141 -302
- package/dist/module/RealtimeChannel.js.map +1 -1
- package/dist/module/RealtimeClient.d.ts +38 -57
- package/dist/module/RealtimeClient.d.ts.map +1 -1
- package/dist/module/RealtimeClient.js +233 -521
- package/dist/module/RealtimeClient.js.map +1 -1
- package/dist/module/RealtimePresence.d.ts +8 -24
- package/dist/module/RealtimePresence.d.ts.map +1 -1
- package/dist/module/RealtimePresence.js +5 -202
- package/dist/module/RealtimePresence.js.map +1 -1
- package/dist/module/lib/constants.d.ts +39 -35
- package/dist/module/lib/constants.d.ts.map +1 -1
- package/dist/module/lib/constants.js +30 -35
- package/dist/module/lib/constants.js.map +1 -1
- package/dist/module/lib/version.d.ts +1 -1
- package/dist/module/lib/version.d.ts.map +1 -1
- package/dist/module/lib/version.js +1 -1
- package/dist/module/lib/version.js.map +1 -1
- package/dist/module/lib/websocket-factory.d.ts +0 -9
- package/dist/module/lib/websocket-factory.d.ts.map +1 -1
- package/dist/module/lib/websocket-factory.js +0 -12
- package/dist/module/lib/websocket-factory.js.map +1 -1
- package/dist/module/phoenix/channelAdapter.d.ts +32 -0
- package/dist/module/phoenix/channelAdapter.d.ts.map +1 -0
- package/dist/module/phoenix/channelAdapter.js +100 -0
- package/dist/module/phoenix/channelAdapter.js.map +1 -0
- package/dist/module/phoenix/presenceAdapter.d.ts +53 -0
- package/dist/module/phoenix/presenceAdapter.d.ts.map +1 -0
- package/dist/module/phoenix/presenceAdapter.js +90 -0
- package/dist/module/phoenix/presenceAdapter.js.map +1 -0
- package/dist/module/phoenix/socketAdapter.d.ts +38 -0
- package/dist/module/phoenix/socketAdapter.d.ts.map +1 -0
- package/dist/module/phoenix/socketAdapter.js +111 -0
- package/dist/module/phoenix/socketAdapter.js.map +1 -0
- package/dist/module/phoenix/types.d.ts +5 -0
- package/dist/module/phoenix/types.d.ts.map +1 -0
- package/dist/module/phoenix/types.js +2 -0
- package/dist/module/phoenix/types.js.map +1 -0
- package/dist/tsconfig.module.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/RealtimeChannel.ts +201 -364
- package/src/RealtimeClient.ts +296 -583
- package/src/RealtimePresence.ts +10 -287
- package/src/lib/constants.ts +50 -37
- package/src/lib/version.ts +1 -1
- package/src/lib/websocket-factory.ts +0 -13
- package/src/phoenix/channelAdapter.ts +147 -0
- package/src/phoenix/presenceAdapter.ts +116 -0
- package/src/phoenix/socketAdapter.ts +168 -0
- package/src/phoenix/types.ts +32 -0
- package/dist/main/lib/push.d.ts +0 -48
- package/dist/main/lib/push.d.ts.map +0 -1
- package/dist/main/lib/push.js +0 -102
- package/dist/main/lib/push.js.map +0 -1
- package/dist/main/lib/timer.d.ts +0 -22
- package/dist/main/lib/timer.d.ts.map +0 -1
- package/dist/main/lib/timer.js +0 -39
- package/dist/main/lib/timer.js.map +0 -1
- package/dist/module/lib/push.d.ts +0 -48
- package/dist/module/lib/push.d.ts.map +0 -1
- package/dist/module/lib/push.js +0 -99
- package/dist/module/lib/push.js.map +0 -1
- package/dist/module/lib/timer.d.ts +0 -22
- package/dist/module/lib/timer.d.ts.map +0 -1
- package/dist/module/lib/timer.js +0 -36
- package/dist/module/lib/timer.js.map +0 -1
- package/src/lib/push.ts +0 -121
- package/src/lib/timer.ts +0 -43
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import WebSocketFactory from './lib/websocket-factory';
|
|
2
|
-
import { CHANNEL_EVENTS, CONNECTION_STATE, DEFAULT_VERSION, DEFAULT_TIMEOUT,
|
|
2
|
+
import { CHANNEL_EVENTS, CONNECTION_STATE, DEFAULT_VERSION, DEFAULT_TIMEOUT, DEFAULT_VSN, VSN_1_0_0, VSN_2_0_0, } from './lib/constants';
|
|
3
3
|
import Serializer from './lib/serializer';
|
|
4
|
-
import Timer from './lib/timer';
|
|
5
4
|
import { httpEndpointURL } from './lib/transformers';
|
|
6
5
|
import RealtimeChannel from './RealtimeChannel';
|
|
7
|
-
|
|
6
|
+
import SocketAdapter from './phoenix/socketAdapter';
|
|
8
7
|
// Connection-related constants
|
|
9
8
|
const CONNECTION_TIMEOUTS = {
|
|
10
9
|
HEARTBEAT_INTERVAL: 25000,
|
|
@@ -20,6 +19,54 @@ const WORKER_SCRIPT = `
|
|
|
20
19
|
}
|
|
21
20
|
});`;
|
|
22
21
|
export default class RealtimeClient {
|
|
22
|
+
get endPoint() {
|
|
23
|
+
return this.socketAdapter.endPoint;
|
|
24
|
+
}
|
|
25
|
+
get timeout() {
|
|
26
|
+
return this.socketAdapter.timeout;
|
|
27
|
+
}
|
|
28
|
+
get transport() {
|
|
29
|
+
return this.socketAdapter.transport;
|
|
30
|
+
}
|
|
31
|
+
get heartbeatCallback() {
|
|
32
|
+
return this.socketAdapter.heartbeatCallback;
|
|
33
|
+
}
|
|
34
|
+
get heartbeatIntervalMs() {
|
|
35
|
+
return this.socketAdapter.heartbeatIntervalMs;
|
|
36
|
+
}
|
|
37
|
+
get heartbeatTimer() {
|
|
38
|
+
if (this.worker) {
|
|
39
|
+
return this._workerHeartbeatTimer;
|
|
40
|
+
}
|
|
41
|
+
return this.socketAdapter.heartbeatTimer;
|
|
42
|
+
}
|
|
43
|
+
get pendingHeartbeatRef() {
|
|
44
|
+
if (this.worker) {
|
|
45
|
+
return this._pendingWorkerHeartbeatRef;
|
|
46
|
+
}
|
|
47
|
+
return this.socketAdapter.pendingHeartbeatRef;
|
|
48
|
+
}
|
|
49
|
+
get reconnectTimer() {
|
|
50
|
+
return this.socketAdapter.reconnectTimer;
|
|
51
|
+
}
|
|
52
|
+
get vsn() {
|
|
53
|
+
return this.socketAdapter.vsn;
|
|
54
|
+
}
|
|
55
|
+
get encode() {
|
|
56
|
+
return this.socketAdapter.encode;
|
|
57
|
+
}
|
|
58
|
+
get decode() {
|
|
59
|
+
return this.socketAdapter.decode;
|
|
60
|
+
}
|
|
61
|
+
get reconnectAfterMs() {
|
|
62
|
+
return this.socketAdapter.reconnectAfterMs;
|
|
63
|
+
}
|
|
64
|
+
get sendBuffer() {
|
|
65
|
+
return this.socketAdapter.sendBuffer;
|
|
66
|
+
}
|
|
67
|
+
get stateChangeCallbacks() {
|
|
68
|
+
return this.socketAdapter.stateChangeCallbacks;
|
|
69
|
+
}
|
|
23
70
|
/**
|
|
24
71
|
* Initializes the Socket.
|
|
25
72
|
*
|
|
@@ -51,39 +98,20 @@ export default class RealtimeClient {
|
|
|
51
98
|
*/
|
|
52
99
|
constructor(endPoint, options) {
|
|
53
100
|
var _a;
|
|
101
|
+
this.channels = new Array();
|
|
54
102
|
this.accessTokenValue = null;
|
|
103
|
+
this.accessToken = null;
|
|
55
104
|
this.apiKey = null;
|
|
56
|
-
this._manuallySetToken = false;
|
|
57
|
-
this.channels = new Array();
|
|
58
|
-
this.endPoint = '';
|
|
59
105
|
this.httpEndpoint = '';
|
|
60
106
|
/** @deprecated headers cannot be set on websocket connections */
|
|
61
107
|
this.headers = {};
|
|
62
108
|
this.params = {};
|
|
63
|
-
this.timeout = DEFAULT_TIMEOUT;
|
|
64
|
-
this.transport = null;
|
|
65
|
-
this.heartbeatIntervalMs = CONNECTION_TIMEOUTS.HEARTBEAT_INTERVAL;
|
|
66
|
-
this.heartbeatTimer = undefined;
|
|
67
|
-
this.pendingHeartbeatRef = null;
|
|
68
|
-
this.heartbeatCallback = noop;
|
|
69
109
|
this.ref = 0;
|
|
70
|
-
this.reconnectTimer = null;
|
|
71
|
-
this.vsn = DEFAULT_VSN;
|
|
72
|
-
this.logger = noop;
|
|
73
|
-
this.conn = null;
|
|
74
|
-
this.sendBuffer = [];
|
|
75
110
|
this.serializer = new Serializer();
|
|
76
|
-
this.
|
|
77
|
-
open: [],
|
|
78
|
-
close: [],
|
|
79
|
-
error: [],
|
|
80
|
-
message: [],
|
|
81
|
-
};
|
|
82
|
-
this.accessToken = null;
|
|
83
|
-
this._connectionState = 'disconnected';
|
|
84
|
-
this._wasManualDisconnect = false;
|
|
111
|
+
this._manuallySetToken = false;
|
|
85
112
|
this._authPromise = null;
|
|
86
|
-
this.
|
|
113
|
+
this._workerHeartbeatTimer = undefined;
|
|
114
|
+
this._pendingWorkerHeartbeatRef = null;
|
|
87
115
|
/**
|
|
88
116
|
* Use either custom fetch, if provided, or default fetch to make HTTP requests
|
|
89
117
|
*
|
|
@@ -100,11 +128,9 @@ export default class RealtimeClient {
|
|
|
100
128
|
throw new Error('API key is required to connect to Realtime');
|
|
101
129
|
}
|
|
102
130
|
this.apiKey = options.params.apikey;
|
|
103
|
-
|
|
104
|
-
this.
|
|
131
|
+
const socketAdapterOptions = this._initializeOptions(options);
|
|
132
|
+
this.socketAdapter = new SocketAdapter(endPoint, socketAdapterOptions);
|
|
105
133
|
this.httpEndpoint = httpEndpointURL(endPoint);
|
|
106
|
-
this._initializeOptions(options);
|
|
107
|
-
this._setupReconnectionTimer();
|
|
108
134
|
this.fetch = this._resolveFetch(options === null || options === void 0 ? void 0 : options.fetch);
|
|
109
135
|
}
|
|
110
136
|
/**
|
|
@@ -112,55 +138,44 @@ export default class RealtimeClient {
|
|
|
112
138
|
*/
|
|
113
139
|
connect() {
|
|
114
140
|
// Skip if already connecting, disconnecting, or connected
|
|
115
|
-
if (this.isConnecting() ||
|
|
116
|
-
this.isDisconnecting() ||
|
|
117
|
-
(this.conn !== null && this.isConnected())) {
|
|
141
|
+
if (this.isConnecting() || this.isDisconnecting() || this.isConnected()) {
|
|
118
142
|
return;
|
|
119
143
|
}
|
|
120
|
-
this._setConnectionState('connecting');
|
|
121
144
|
// Trigger auth if needed and not already in progress
|
|
122
145
|
// This ensures auth is called for standalone RealtimeClient usage
|
|
123
146
|
// while avoiding race conditions with SupabaseClient's immediate setAuth call
|
|
124
147
|
if (this.accessToken && !this._authPromise) {
|
|
125
148
|
this._setAuthSafely('connect');
|
|
126
149
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
'Option 2: Install and provide the "ws" package:\n\n' +
|
|
146
|
-
' npm install ws\n\n' +
|
|
147
|
-
' import ws from "ws"\n' +
|
|
148
|
-
' const client = new RealtimeClient(url, {\n' +
|
|
149
|
-
' ...options,\n' +
|
|
150
|
-
' transport: ws\n' +
|
|
151
|
-
' })');
|
|
152
|
-
}
|
|
153
|
-
throw new Error(`WebSocket not available: ${errorMessage}`);
|
|
150
|
+
this._setupConnectionHandlers();
|
|
151
|
+
try {
|
|
152
|
+
this.socketAdapter.connect();
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
const errorMessage = error.message;
|
|
156
|
+
// Provide helpful error message based on environment
|
|
157
|
+
if (errorMessage.includes('Node.js')) {
|
|
158
|
+
throw new Error(`${errorMessage}\n\n` +
|
|
159
|
+
'To use Realtime in Node.js, you need to provide a WebSocket implementation:\n\n' +
|
|
160
|
+
'Option 1: Use Node.js 22+ which has native WebSocket support\n' +
|
|
161
|
+
'Option 2: Install and provide the "ws" package:\n\n' +
|
|
162
|
+
' npm install ws\n\n' +
|
|
163
|
+
' import ws from "ws"\n' +
|
|
164
|
+
' const client = new RealtimeClient(url, {\n' +
|
|
165
|
+
' ...options,\n' +
|
|
166
|
+
' transport: ws\n' +
|
|
167
|
+
' })');
|
|
154
168
|
}
|
|
169
|
+
throw new Error(`WebSocket not available: ${errorMessage}`);
|
|
155
170
|
}
|
|
156
|
-
this.
|
|
171
|
+
this._handleNodeJsRaceCondition();
|
|
157
172
|
}
|
|
158
173
|
/**
|
|
159
174
|
* Returns the URL of the websocket.
|
|
160
175
|
* @returns string The URL of the websocket.
|
|
161
176
|
*/
|
|
162
177
|
endpointURL() {
|
|
163
|
-
return this.
|
|
178
|
+
return this.socketAdapter.endPointURL();
|
|
164
179
|
}
|
|
165
180
|
/**
|
|
166
181
|
* Disconnects the socket.
|
|
@@ -168,34 +183,14 @@ export default class RealtimeClient {
|
|
|
168
183
|
* @param code A numeric status code to send on disconnect.
|
|
169
184
|
* @param reason A custom reason for the disconnect.
|
|
170
185
|
*/
|
|
171
|
-
disconnect(code, reason) {
|
|
186
|
+
async disconnect(code, reason) {
|
|
172
187
|
if (this.isDisconnecting()) {
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
this._setConnectionState('disconnecting', true);
|
|
176
|
-
if (this.conn) {
|
|
177
|
-
// Setup fallback timer to prevent hanging in disconnecting state
|
|
178
|
-
const fallbackTimer = setTimeout(() => {
|
|
179
|
-
this._setConnectionState('disconnected');
|
|
180
|
-
}, 100);
|
|
181
|
-
this.conn.onclose = () => {
|
|
182
|
-
clearTimeout(fallbackTimer);
|
|
183
|
-
this._setConnectionState('disconnected');
|
|
184
|
-
};
|
|
185
|
-
// Close the WebSocket connection if close method exists
|
|
186
|
-
if (typeof this.conn.close === 'function') {
|
|
187
|
-
if (code) {
|
|
188
|
-
this.conn.close(code, reason !== null && reason !== void 0 ? reason : '');
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
this.conn.close();
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
this._teardownConnection();
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
this._setConnectionState('disconnected');
|
|
188
|
+
return 'ok';
|
|
198
189
|
}
|
|
190
|
+
return await this.socketAdapter.disconnect(() => {
|
|
191
|
+
clearInterval(this._workerHeartbeatTimer);
|
|
192
|
+
this._terminateWorker();
|
|
193
|
+
}, code, reason);
|
|
199
194
|
}
|
|
200
195
|
/**
|
|
201
196
|
* Returns all created channels
|
|
@@ -204,65 +199,63 @@ export default class RealtimeClient {
|
|
|
204
199
|
return this.channels;
|
|
205
200
|
}
|
|
206
201
|
/**
|
|
207
|
-
* Unsubscribes and
|
|
202
|
+
* Unsubscribes, removes and tears down a single channel
|
|
208
203
|
* @param channel A RealtimeChannel instance
|
|
209
204
|
*/
|
|
210
205
|
async removeChannel(channel) {
|
|
211
206
|
const status = await channel.unsubscribe();
|
|
207
|
+
if (status === 'ok') {
|
|
208
|
+
channel.teardown();
|
|
209
|
+
}
|
|
212
210
|
if (this.channels.length === 0) {
|
|
213
211
|
this.disconnect();
|
|
214
212
|
}
|
|
215
213
|
return status;
|
|
216
214
|
}
|
|
217
215
|
/**
|
|
218
|
-
* Unsubscribes and
|
|
216
|
+
* Unsubscribes, removes and tears down all channels
|
|
219
217
|
*/
|
|
220
218
|
async removeAllChannels() {
|
|
221
|
-
const
|
|
222
|
-
|
|
219
|
+
const promises = this.channels.map(async (channel) => {
|
|
220
|
+
const result = await channel.unsubscribe();
|
|
221
|
+
channel.teardown();
|
|
222
|
+
return result;
|
|
223
|
+
});
|
|
224
|
+
const result = await Promise.all(promises);
|
|
223
225
|
this.disconnect();
|
|
224
|
-
return
|
|
226
|
+
return result;
|
|
225
227
|
}
|
|
226
228
|
/**
|
|
227
229
|
* Logs the message.
|
|
228
230
|
*
|
|
229
|
-
* For customized logging, `this.logger` can be overridden.
|
|
231
|
+
* For customized logging, `this.logger` can be overridden in Client constructor.
|
|
230
232
|
*/
|
|
231
233
|
log(kind, msg, data) {
|
|
232
|
-
this.
|
|
234
|
+
this.socketAdapter.log(kind, msg, data);
|
|
233
235
|
}
|
|
234
236
|
/**
|
|
235
237
|
* Returns the current state of the socket.
|
|
236
238
|
*/
|
|
237
239
|
connectionState() {
|
|
238
|
-
|
|
239
|
-
case SOCKET_STATES.connecting:
|
|
240
|
-
return CONNECTION_STATE.Connecting;
|
|
241
|
-
case SOCKET_STATES.open:
|
|
242
|
-
return CONNECTION_STATE.Open;
|
|
243
|
-
case SOCKET_STATES.closing:
|
|
244
|
-
return CONNECTION_STATE.Closing;
|
|
245
|
-
default:
|
|
246
|
-
return CONNECTION_STATE.Closed;
|
|
247
|
-
}
|
|
240
|
+
return this.socketAdapter.connectionState() || CONNECTION_STATE.closed;
|
|
248
241
|
}
|
|
249
242
|
/**
|
|
250
243
|
* Returns `true` is the connection is open.
|
|
251
244
|
*/
|
|
252
245
|
isConnected() {
|
|
253
|
-
return this.
|
|
246
|
+
return this.socketAdapter.isConnected();
|
|
254
247
|
}
|
|
255
248
|
/**
|
|
256
249
|
* Returns `true` if the connection is currently connecting.
|
|
257
250
|
*/
|
|
258
251
|
isConnecting() {
|
|
259
|
-
return this.
|
|
252
|
+
return this.socketAdapter.isConnecting();
|
|
260
253
|
}
|
|
261
254
|
/**
|
|
262
255
|
* Returns `true` if the connection is currently disconnecting.
|
|
263
256
|
*/
|
|
264
257
|
isDisconnecting() {
|
|
265
|
-
return this.
|
|
258
|
+
return this.socketAdapter.isDisconnecting();
|
|
266
259
|
}
|
|
267
260
|
/**
|
|
268
261
|
* Creates (or reuses) a {@link RealtimeChannel} for the provided topic.
|
|
@@ -289,20 +282,7 @@ export default class RealtimeClient {
|
|
|
289
282
|
* If the socket is not connected, the message gets enqueued within a local buffer, and sent out when a connection is next established.
|
|
290
283
|
*/
|
|
291
284
|
push(data) {
|
|
292
|
-
|
|
293
|
-
const callback = () => {
|
|
294
|
-
this.encode(data, (result) => {
|
|
295
|
-
var _a;
|
|
296
|
-
(_a = this.conn) === null || _a === void 0 ? void 0 : _a.send(result);
|
|
297
|
-
});
|
|
298
|
-
};
|
|
299
|
-
this.log('push', `${topic} ${event} (${ref})`, payload);
|
|
300
|
-
if (this.isConnected()) {
|
|
301
|
-
callback();
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
this.sendBuffer.push(callback);
|
|
305
|
-
}
|
|
285
|
+
this.socketAdapter.push(data);
|
|
306
286
|
}
|
|
307
287
|
/**
|
|
308
288
|
* Sets the JWT access token used for channel subscription authorization and Realtime RLS.
|
|
@@ -345,70 +325,14 @@ export default class RealtimeClient {
|
|
|
345
325
|
* Sends a heartbeat message if the socket is connected.
|
|
346
326
|
*/
|
|
347
327
|
async sendHeartbeat() {
|
|
348
|
-
|
|
349
|
-
if (!this.isConnected()) {
|
|
350
|
-
try {
|
|
351
|
-
this.heartbeatCallback('disconnected');
|
|
352
|
-
}
|
|
353
|
-
catch (e) {
|
|
354
|
-
this.log('error', 'error in heartbeat callback', e);
|
|
355
|
-
}
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
// Handle heartbeat timeout and force reconnection if needed
|
|
359
|
-
if (this.pendingHeartbeatRef) {
|
|
360
|
-
this.pendingHeartbeatRef = null;
|
|
361
|
-
this._heartbeatSentAt = null;
|
|
362
|
-
this.log('transport', 'heartbeat timeout. Attempting to re-establish connection');
|
|
363
|
-
try {
|
|
364
|
-
this.heartbeatCallback('timeout');
|
|
365
|
-
}
|
|
366
|
-
catch (e) {
|
|
367
|
-
this.log('error', 'error in heartbeat callback', e);
|
|
368
|
-
}
|
|
369
|
-
// Force reconnection after heartbeat timeout
|
|
370
|
-
this._wasManualDisconnect = false;
|
|
371
|
-
(_a = this.conn) === null || _a === void 0 ? void 0 : _a.close(WS_CLOSE_NORMAL, 'heartbeat timeout');
|
|
372
|
-
setTimeout(() => {
|
|
373
|
-
var _a;
|
|
374
|
-
if (!this.isConnected()) {
|
|
375
|
-
(_a = this.reconnectTimer) === null || _a === void 0 ? void 0 : _a.scheduleTimeout();
|
|
376
|
-
}
|
|
377
|
-
}, CONNECTION_TIMEOUTS.HEARTBEAT_TIMEOUT_FALLBACK);
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
// Send heartbeat message to server
|
|
381
|
-
this._heartbeatSentAt = Date.now();
|
|
382
|
-
this.pendingHeartbeatRef = this._makeRef();
|
|
383
|
-
this.push({
|
|
384
|
-
topic: 'phoenix',
|
|
385
|
-
event: 'heartbeat',
|
|
386
|
-
payload: {},
|
|
387
|
-
ref: this.pendingHeartbeatRef,
|
|
388
|
-
});
|
|
389
|
-
try {
|
|
390
|
-
this.heartbeatCallback('sent');
|
|
391
|
-
}
|
|
392
|
-
catch (e) {
|
|
393
|
-
this.log('error', 'error in heartbeat callback', e);
|
|
394
|
-
}
|
|
395
|
-
this._setAuthSafely('heartbeat');
|
|
328
|
+
this.socketAdapter.sendHeartbeat();
|
|
396
329
|
}
|
|
397
330
|
/**
|
|
398
331
|
* Sets a callback that receives lifecycle events for internal heartbeat messages.
|
|
399
332
|
* Useful for instrumenting connection health (e.g. sent/ok/timeout/disconnected).
|
|
400
333
|
*/
|
|
401
334
|
onHeartbeat(callback) {
|
|
402
|
-
this.heartbeatCallback = callback;
|
|
403
|
-
}
|
|
404
|
-
/**
|
|
405
|
-
* Flushes send buffer
|
|
406
|
-
*/
|
|
407
|
-
flushSendBuffer() {
|
|
408
|
-
if (this.isConnected() && this.sendBuffer.length > 0) {
|
|
409
|
-
this.sendBuffer.forEach((callback) => callback());
|
|
410
|
-
this.sendBuffer = [];
|
|
411
|
-
}
|
|
335
|
+
this.socketAdapter.heartbeatCallback = this._wrapHeartbeatCallback(callback);
|
|
412
336
|
}
|
|
413
337
|
/**
|
|
414
338
|
* Return the next message ref, accounting for overflows
|
|
@@ -416,29 +340,10 @@ export default class RealtimeClient {
|
|
|
416
340
|
* @internal
|
|
417
341
|
*/
|
|
418
342
|
_makeRef() {
|
|
419
|
-
|
|
420
|
-
if (newRef === this.ref) {
|
|
421
|
-
this.ref = 0;
|
|
422
|
-
}
|
|
423
|
-
else {
|
|
424
|
-
this.ref = newRef;
|
|
425
|
-
}
|
|
426
|
-
return this.ref.toString();
|
|
343
|
+
return this.socketAdapter.makeRef();
|
|
427
344
|
}
|
|
428
345
|
/**
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
* @internal
|
|
432
|
-
*/
|
|
433
|
-
_leaveOpenTopic(topic) {
|
|
434
|
-
let dupChannel = this.channels.find((c) => c.topic === topic && (c._isJoined() || c._isJoining()));
|
|
435
|
-
if (dupChannel) {
|
|
436
|
-
this.log('transport', `leaving duplicate topic "${topic}"`);
|
|
437
|
-
dupChannel.unsubscribe();
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* Removes a subscription from the socket.
|
|
346
|
+
* Removes a channel from RealtimeClient
|
|
442
347
|
*
|
|
443
348
|
* @param channel An open subscription.
|
|
444
349
|
*
|
|
@@ -447,262 +352,6 @@ export default class RealtimeClient {
|
|
|
447
352
|
_remove(channel) {
|
|
448
353
|
this.channels = this.channels.filter((c) => c.topic !== channel.topic);
|
|
449
354
|
}
|
|
450
|
-
/** @internal */
|
|
451
|
-
_onConnMessage(rawMessage) {
|
|
452
|
-
this.decode(rawMessage.data, (msg) => {
|
|
453
|
-
// Handle heartbeat responses
|
|
454
|
-
if (msg.topic === 'phoenix' &&
|
|
455
|
-
msg.event === 'phx_reply' &&
|
|
456
|
-
msg.ref &&
|
|
457
|
-
msg.ref === this.pendingHeartbeatRef) {
|
|
458
|
-
const latency = this._heartbeatSentAt ? Date.now() - this._heartbeatSentAt : undefined;
|
|
459
|
-
try {
|
|
460
|
-
this.heartbeatCallback(msg.payload.status === 'ok' ? 'ok' : 'error', latency);
|
|
461
|
-
}
|
|
462
|
-
catch (e) {
|
|
463
|
-
this.log('error', 'error in heartbeat callback', e);
|
|
464
|
-
}
|
|
465
|
-
this._heartbeatSentAt = null;
|
|
466
|
-
this.pendingHeartbeatRef = null;
|
|
467
|
-
}
|
|
468
|
-
// Log incoming message
|
|
469
|
-
const { topic, event, payload, ref } = msg;
|
|
470
|
-
const refString = ref ? `(${ref})` : '';
|
|
471
|
-
const status = payload.status || '';
|
|
472
|
-
this.log('receive', `${status} ${topic} ${event} ${refString}`.trim(), payload);
|
|
473
|
-
// Route message to appropriate channels
|
|
474
|
-
this.channels
|
|
475
|
-
.filter((channel) => channel._isMember(topic))
|
|
476
|
-
.forEach((channel) => channel._trigger(event, payload, ref));
|
|
477
|
-
this._triggerStateCallbacks('message', msg);
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
/**
|
|
481
|
-
* Clear specific timer
|
|
482
|
-
* @internal
|
|
483
|
-
*/
|
|
484
|
-
_clearTimer(timer) {
|
|
485
|
-
var _a;
|
|
486
|
-
if (timer === 'heartbeat' && this.heartbeatTimer) {
|
|
487
|
-
clearInterval(this.heartbeatTimer);
|
|
488
|
-
this.heartbeatTimer = undefined;
|
|
489
|
-
}
|
|
490
|
-
else if (timer === 'reconnect') {
|
|
491
|
-
(_a = this.reconnectTimer) === null || _a === void 0 ? void 0 : _a.reset();
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* Clear all timers
|
|
496
|
-
* @internal
|
|
497
|
-
*/
|
|
498
|
-
_clearAllTimers() {
|
|
499
|
-
this._clearTimer('heartbeat');
|
|
500
|
-
this._clearTimer('reconnect');
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* Setup connection handlers for WebSocket events
|
|
504
|
-
* @internal
|
|
505
|
-
*/
|
|
506
|
-
_setupConnectionHandlers() {
|
|
507
|
-
if (!this.conn)
|
|
508
|
-
return;
|
|
509
|
-
// Set binary type if supported (browsers and most WebSocket implementations)
|
|
510
|
-
if ('binaryType' in this.conn) {
|
|
511
|
-
;
|
|
512
|
-
this.conn.binaryType = 'arraybuffer';
|
|
513
|
-
}
|
|
514
|
-
this.conn.onopen = () => this._onConnOpen();
|
|
515
|
-
this.conn.onerror = (error) => this._onConnError(error);
|
|
516
|
-
this.conn.onmessage = (event) => this._onConnMessage(event);
|
|
517
|
-
this.conn.onclose = (event) => this._onConnClose(event);
|
|
518
|
-
if (this.conn.readyState === SOCKET_STATES.open) {
|
|
519
|
-
this._onConnOpen();
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* Teardown connection and cleanup resources
|
|
524
|
-
* @internal
|
|
525
|
-
*/
|
|
526
|
-
_teardownConnection() {
|
|
527
|
-
if (this.conn) {
|
|
528
|
-
if (this.conn.readyState === SOCKET_STATES.open ||
|
|
529
|
-
this.conn.readyState === SOCKET_STATES.connecting) {
|
|
530
|
-
try {
|
|
531
|
-
this.conn.close();
|
|
532
|
-
}
|
|
533
|
-
catch (e) {
|
|
534
|
-
this.log('error', 'Error closing connection', e);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
this.conn.onopen = null;
|
|
538
|
-
this.conn.onerror = null;
|
|
539
|
-
this.conn.onmessage = null;
|
|
540
|
-
this.conn.onclose = null;
|
|
541
|
-
this.conn = null;
|
|
542
|
-
}
|
|
543
|
-
this._clearAllTimers();
|
|
544
|
-
this._terminateWorker();
|
|
545
|
-
this.channels.forEach((channel) => channel.teardown());
|
|
546
|
-
}
|
|
547
|
-
/** @internal */
|
|
548
|
-
_onConnOpen() {
|
|
549
|
-
this._setConnectionState('connected');
|
|
550
|
-
this.log('transport', `connected to ${this.endpointURL()}`);
|
|
551
|
-
// Wait for any pending auth operations before flushing send buffer
|
|
552
|
-
// This ensures channel join messages include the correct access token
|
|
553
|
-
const authPromise = this._authPromise ||
|
|
554
|
-
(this.accessToken && !this.accessTokenValue ? this.setAuth() : Promise.resolve());
|
|
555
|
-
authPromise
|
|
556
|
-
.then(() => {
|
|
557
|
-
// When subscribe() is called before the accessToken callback has
|
|
558
|
-
// resolved (common on React Native / Expo where token storage is
|
|
559
|
-
// async), the phx_join payload captured at subscribe()-time will
|
|
560
|
-
// have no access_token. By this point auth has settled and
|
|
561
|
-
// this.accessTokenValue holds the real JWT.
|
|
562
|
-
//
|
|
563
|
-
// The stale join messages sitting in sendBuffer captured the old
|
|
564
|
-
// (token-less) payload in a closure, so we cannot simply flush
|
|
565
|
-
// them. Instead we:
|
|
566
|
-
// 1. Patch each channel's joinPush payload with the real token
|
|
567
|
-
// 2. Drop the stale buffered messages
|
|
568
|
-
// 3. Re-send the join for any channel still in "joining" state
|
|
569
|
-
//
|
|
570
|
-
// On browsers this is a harmless no-op: accessTokenValue was
|
|
571
|
-
// already set synchronously before subscribe() ran, so the join
|
|
572
|
-
// payload already had the correct token.
|
|
573
|
-
if (this.accessTokenValue) {
|
|
574
|
-
this.channels.forEach((channel) => {
|
|
575
|
-
channel.updateJoinPayload({ access_token: this.accessTokenValue });
|
|
576
|
-
});
|
|
577
|
-
this.sendBuffer = [];
|
|
578
|
-
this.channels.forEach((channel) => {
|
|
579
|
-
if (channel._isJoining()) {
|
|
580
|
-
channel.joinPush.sent = false;
|
|
581
|
-
channel.joinPush.send();
|
|
582
|
-
}
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
this.flushSendBuffer();
|
|
586
|
-
})
|
|
587
|
-
.catch((e) => {
|
|
588
|
-
this.log('error', 'error waiting for auth on connect', e);
|
|
589
|
-
// Proceed anyway to avoid hanging connections
|
|
590
|
-
this.flushSendBuffer();
|
|
591
|
-
});
|
|
592
|
-
this._clearTimer('reconnect');
|
|
593
|
-
if (!this.worker) {
|
|
594
|
-
this._startHeartbeat();
|
|
595
|
-
}
|
|
596
|
-
else {
|
|
597
|
-
if (!this.workerRef) {
|
|
598
|
-
this._startWorkerHeartbeat();
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
this._triggerStateCallbacks('open');
|
|
602
|
-
}
|
|
603
|
-
/** @internal */
|
|
604
|
-
_startHeartbeat() {
|
|
605
|
-
this.heartbeatTimer && clearInterval(this.heartbeatTimer);
|
|
606
|
-
this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), this.heartbeatIntervalMs);
|
|
607
|
-
}
|
|
608
|
-
/** @internal */
|
|
609
|
-
_startWorkerHeartbeat() {
|
|
610
|
-
if (this.workerUrl) {
|
|
611
|
-
this.log('worker', `starting worker for from ${this.workerUrl}`);
|
|
612
|
-
}
|
|
613
|
-
else {
|
|
614
|
-
this.log('worker', `starting default worker`);
|
|
615
|
-
}
|
|
616
|
-
const objectUrl = this._workerObjectUrl(this.workerUrl);
|
|
617
|
-
this.workerRef = new Worker(objectUrl);
|
|
618
|
-
this.workerRef.onerror = (error) => {
|
|
619
|
-
this.log('worker', 'worker error', error.message);
|
|
620
|
-
this._terminateWorker();
|
|
621
|
-
};
|
|
622
|
-
this.workerRef.onmessage = (event) => {
|
|
623
|
-
if (event.data.event === 'keepAlive') {
|
|
624
|
-
this.sendHeartbeat();
|
|
625
|
-
}
|
|
626
|
-
};
|
|
627
|
-
this.workerRef.postMessage({
|
|
628
|
-
event: 'start',
|
|
629
|
-
interval: this.heartbeatIntervalMs,
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
/**
|
|
633
|
-
* Terminate the Web Worker and clear the reference
|
|
634
|
-
* @internal
|
|
635
|
-
*/
|
|
636
|
-
_terminateWorker() {
|
|
637
|
-
if (this.workerRef) {
|
|
638
|
-
this.log('worker', 'terminating worker');
|
|
639
|
-
this.workerRef.terminate();
|
|
640
|
-
this.workerRef = undefined;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
/** @internal */
|
|
644
|
-
_onConnClose(event) {
|
|
645
|
-
var _a;
|
|
646
|
-
this._setConnectionState('disconnected');
|
|
647
|
-
this.log('transport', 'close', event);
|
|
648
|
-
this._triggerChanError();
|
|
649
|
-
this._clearTimer('heartbeat');
|
|
650
|
-
// Only schedule reconnection if it wasn't a manual disconnect
|
|
651
|
-
if (!this._wasManualDisconnect) {
|
|
652
|
-
(_a = this.reconnectTimer) === null || _a === void 0 ? void 0 : _a.scheduleTimeout();
|
|
653
|
-
}
|
|
654
|
-
this._triggerStateCallbacks('close', event);
|
|
655
|
-
}
|
|
656
|
-
/** @internal */
|
|
657
|
-
_onConnError(error) {
|
|
658
|
-
this._setConnectionState('disconnected');
|
|
659
|
-
this.log('transport', `${error}`);
|
|
660
|
-
this._triggerChanError();
|
|
661
|
-
this._triggerStateCallbacks('error', error);
|
|
662
|
-
try {
|
|
663
|
-
this.heartbeatCallback('error');
|
|
664
|
-
}
|
|
665
|
-
catch (e) {
|
|
666
|
-
this.log('error', 'error in heartbeat callback', e);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
/** @internal */
|
|
670
|
-
_triggerChanError() {
|
|
671
|
-
this.channels.forEach((channel) => channel._trigger(CHANNEL_EVENTS.error));
|
|
672
|
-
}
|
|
673
|
-
/** @internal */
|
|
674
|
-
_appendParams(url, params) {
|
|
675
|
-
if (Object.keys(params).length === 0) {
|
|
676
|
-
return url;
|
|
677
|
-
}
|
|
678
|
-
const prefix = url.match(/\?/) ? '&' : '?';
|
|
679
|
-
const query = new URLSearchParams(params);
|
|
680
|
-
return `${url}${prefix}${query}`;
|
|
681
|
-
}
|
|
682
|
-
_workerObjectUrl(url) {
|
|
683
|
-
let result_url;
|
|
684
|
-
if (url) {
|
|
685
|
-
result_url = url;
|
|
686
|
-
}
|
|
687
|
-
else {
|
|
688
|
-
const blob = new Blob([WORKER_SCRIPT], { type: 'application/javascript' });
|
|
689
|
-
result_url = URL.createObjectURL(blob);
|
|
690
|
-
}
|
|
691
|
-
return result_url;
|
|
692
|
-
}
|
|
693
|
-
/**
|
|
694
|
-
* Set connection state with proper state management
|
|
695
|
-
* @internal
|
|
696
|
-
*/
|
|
697
|
-
_setConnectionState(state, manual = false) {
|
|
698
|
-
this._connectionState = state;
|
|
699
|
-
if (state === 'connecting') {
|
|
700
|
-
this._wasManualDisconnect = false;
|
|
701
|
-
}
|
|
702
|
-
else if (state === 'disconnecting') {
|
|
703
|
-
this._wasManualDisconnect = manual;
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
355
|
/**
|
|
707
356
|
* Perform the actual auth operation
|
|
708
357
|
* @internal
|
|
@@ -745,8 +394,8 @@ export default class RealtimeClient {
|
|
|
745
394
|
version: DEFAULT_VERSION,
|
|
746
395
|
};
|
|
747
396
|
tokenToSend && channel.updateJoinPayload(payload);
|
|
748
|
-
if (channel.joinedOnce && channel.
|
|
749
|
-
channel.
|
|
397
|
+
if (channel.joinedOnce && channel.channelAdapter.isJoined()) {
|
|
398
|
+
channel.channelAdapter.push(CHANNEL_EVENTS.access_token, {
|
|
750
399
|
access_token: tokenToSend,
|
|
751
400
|
});
|
|
752
401
|
}
|
|
@@ -774,85 +423,139 @@ export default class RealtimeClient {
|
|
|
774
423
|
});
|
|
775
424
|
}
|
|
776
425
|
}
|
|
777
|
-
/**
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
try {
|
|
785
|
-
callback(data);
|
|
786
|
-
}
|
|
787
|
-
catch (e) {
|
|
788
|
-
this.log('error', `error in ${event} callback`, e);
|
|
789
|
-
}
|
|
426
|
+
/** @internal */
|
|
427
|
+
_setupConnectionHandlers() {
|
|
428
|
+
this.socketAdapter.onOpen(() => {
|
|
429
|
+
const authPromise = this._authPromise ||
|
|
430
|
+
(this.accessToken && !this.accessTokenValue ? this.setAuth() : Promise.resolve());
|
|
431
|
+
authPromise.catch((e) => {
|
|
432
|
+
this.log('error', 'error waiting for auth on connect', e);
|
|
790
433
|
});
|
|
434
|
+
if (this.worker && !this.workerRef) {
|
|
435
|
+
this._startWorkerHeartbeat();
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
this.socketAdapter.onClose(() => {
|
|
439
|
+
if (this.worker && this.workerRef) {
|
|
440
|
+
this._terminateWorker();
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
this.socketAdapter.onMessage((message) => {
|
|
444
|
+
if (message.ref && message.ref === this._pendingWorkerHeartbeatRef) {
|
|
445
|
+
this._pendingWorkerHeartbeatRef = null;
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
/** @internal */
|
|
450
|
+
_handleNodeJsRaceCondition() {
|
|
451
|
+
if (this.socketAdapter.isConnected()) {
|
|
452
|
+
// hack: ensure onConnOpen is called
|
|
453
|
+
this.socketAdapter.getSocket().onConnOpen();
|
|
791
454
|
}
|
|
792
|
-
|
|
793
|
-
|
|
455
|
+
}
|
|
456
|
+
/** @internal */
|
|
457
|
+
_wrapHeartbeatCallback(heartbeatCallback) {
|
|
458
|
+
return (status, latency) => {
|
|
459
|
+
if (status == 'sent')
|
|
460
|
+
this._setAuthSafely();
|
|
461
|
+
if (heartbeatCallback)
|
|
462
|
+
heartbeatCallback(status, latency);
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
/** @internal */
|
|
466
|
+
_startWorkerHeartbeat() {
|
|
467
|
+
if (this.workerUrl) {
|
|
468
|
+
this.log('worker', `starting worker for from ${this.workerUrl}`);
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
this.log('worker', `starting default worker`);
|
|
794
472
|
}
|
|
473
|
+
const objectUrl = this._workerObjectUrl(this.workerUrl);
|
|
474
|
+
this.workerRef = new Worker(objectUrl);
|
|
475
|
+
this.workerRef.onerror = (error) => {
|
|
476
|
+
this.log('worker', 'worker error', error.message);
|
|
477
|
+
this._terminateWorker();
|
|
478
|
+
this.disconnect();
|
|
479
|
+
};
|
|
480
|
+
this.workerRef.onmessage = (event) => {
|
|
481
|
+
if (event.data.event === 'keepAlive') {
|
|
482
|
+
this.sendHeartbeat();
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
this.workerRef.postMessage({
|
|
486
|
+
event: 'start',
|
|
487
|
+
interval: this.heartbeatIntervalMs,
|
|
488
|
+
});
|
|
795
489
|
}
|
|
796
490
|
/**
|
|
797
|
-
*
|
|
491
|
+
* Terminate the Web Worker and clear the reference
|
|
798
492
|
* @internal
|
|
799
493
|
*/
|
|
800
|
-
|
|
801
|
-
this.
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
494
|
+
_terminateWorker() {
|
|
495
|
+
if (this.workerRef) {
|
|
496
|
+
this.log('worker', 'terminating worker');
|
|
497
|
+
this.workerRef.terminate();
|
|
498
|
+
this.workerRef = undefined;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
/** @internal */
|
|
502
|
+
_workerObjectUrl(url) {
|
|
503
|
+
let result_url;
|
|
504
|
+
if (url) {
|
|
505
|
+
result_url = url;
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
const blob = new Blob([WORKER_SCRIPT], { type: 'application/javascript' });
|
|
509
|
+
result_url = URL.createObjectURL(blob);
|
|
510
|
+
}
|
|
511
|
+
return result_url;
|
|
809
512
|
}
|
|
810
513
|
/**
|
|
811
|
-
* Initialize
|
|
514
|
+
* Initialize socket options with defaults
|
|
812
515
|
* @internal
|
|
813
516
|
*/
|
|
814
517
|
_initializeOptions(options) {
|
|
815
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j
|
|
816
|
-
|
|
817
|
-
this.
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
if ((options === null || options === void 0 ? void 0 : options.logLevel) || (options === null || options === void 0 ? void 0 : options.log_level)) {
|
|
831
|
-
this.logLevel = options.logLevel || options.log_level;
|
|
832
|
-
this.params = Object.assign(Object.assign({}, this.params), { log_level: this.logLevel });
|
|
833
|
-
}
|
|
834
|
-
// Set up functions with defaults
|
|
835
|
-
this.reconnectAfterMs =
|
|
836
|
-
(_h = options === null || options === void 0 ? void 0 : options.reconnectAfterMs) !== null && _h !== void 0 ? _h : ((tries) => {
|
|
518
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
519
|
+
this.worker = (_a = options === null || options === void 0 ? void 0 : options.worker) !== null && _a !== void 0 ? _a : false;
|
|
520
|
+
this.accessToken = (_b = options === null || options === void 0 ? void 0 : options.accessToken) !== null && _b !== void 0 ? _b : null;
|
|
521
|
+
const result = {};
|
|
522
|
+
result.timeout = (_c = options === null || options === void 0 ? void 0 : options.timeout) !== null && _c !== void 0 ? _c : DEFAULT_TIMEOUT;
|
|
523
|
+
result.heartbeatIntervalMs =
|
|
524
|
+
(_d = options === null || options === void 0 ? void 0 : options.heartbeatIntervalMs) !== null && _d !== void 0 ? _d : CONNECTION_TIMEOUTS.HEARTBEAT_INTERVAL;
|
|
525
|
+
result.vsn = (_e = options === null || options === void 0 ? void 0 : options.vsn) !== null && _e !== void 0 ? _e : DEFAULT_VSN;
|
|
526
|
+
// @ts-ignore - mismatch between phoenix and supabase
|
|
527
|
+
result.transport = (_f = options === null || options === void 0 ? void 0 : options.transport) !== null && _f !== void 0 ? _f : WebSocketFactory.getWebSocketConstructor();
|
|
528
|
+
result.params = options === null || options === void 0 ? void 0 : options.params;
|
|
529
|
+
result.logger = options === null || options === void 0 ? void 0 : options.logger;
|
|
530
|
+
result.heartbeatCallback = this._wrapHeartbeatCallback(options === null || options === void 0 ? void 0 : options.heartbeatCallback);
|
|
531
|
+
result.reconnectAfterMs =
|
|
532
|
+
(_g = options === null || options === void 0 ? void 0 : options.reconnectAfterMs) !== null && _g !== void 0 ? _g : ((tries) => {
|
|
837
533
|
return RECONNECT_INTERVALS[tries - 1] || DEFAULT_RECONNECT_FALLBACK;
|
|
838
534
|
});
|
|
839
|
-
|
|
535
|
+
let defaultEncode;
|
|
536
|
+
let defaultDecode;
|
|
537
|
+
switch (result.vsn) {
|
|
840
538
|
case VSN_1_0_0:
|
|
841
|
-
|
|
842
|
-
(
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
return callback(JSON.parse(payload));
|
|
848
|
-
});
|
|
539
|
+
defaultEncode = (payload, callback) => {
|
|
540
|
+
return callback(JSON.stringify(payload));
|
|
541
|
+
};
|
|
542
|
+
defaultDecode = (payload, callback) => {
|
|
543
|
+
return callback(JSON.parse(payload));
|
|
544
|
+
};
|
|
849
545
|
break;
|
|
850
546
|
case VSN_2_0_0:
|
|
851
|
-
|
|
852
|
-
|
|
547
|
+
defaultEncode = this.serializer.encode.bind(this.serializer);
|
|
548
|
+
defaultDecode = this.serializer.decode.bind(this.serializer);
|
|
853
549
|
break;
|
|
854
550
|
default:
|
|
855
|
-
throw new Error(`Unsupported serializer version: ${
|
|
551
|
+
throw new Error(`Unsupported serializer version: ${result.vsn}`);
|
|
552
|
+
}
|
|
553
|
+
result.encode = (_h = options === null || options === void 0 ? void 0 : options.encode) !== null && _h !== void 0 ? _h : defaultEncode;
|
|
554
|
+
result.decode = (_j = options === null || options === void 0 ? void 0 : options.decode) !== null && _j !== void 0 ? _j : defaultDecode;
|
|
555
|
+
result.beforeReconnect = this._reconnectAuth.bind(this);
|
|
556
|
+
if ((options === null || options === void 0 ? void 0 : options.logLevel) || (options === null || options === void 0 ? void 0 : options.log_level)) {
|
|
557
|
+
this.logLevel = options.logLevel || options.log_level;
|
|
558
|
+
result.params = Object.assign(Object.assign({}, result.params), { log_level: this.logLevel });
|
|
856
559
|
}
|
|
857
560
|
// Handle worker setup
|
|
858
561
|
if (this.worker) {
|
|
@@ -860,6 +563,15 @@ export default class RealtimeClient {
|
|
|
860
563
|
throw new Error('Web Worker is not supported');
|
|
861
564
|
}
|
|
862
565
|
this.workerUrl = options === null || options === void 0 ? void 0 : options.workerUrl;
|
|
566
|
+
result.autoSendHeartbeat = !this.worker;
|
|
567
|
+
}
|
|
568
|
+
return result;
|
|
569
|
+
}
|
|
570
|
+
/** @internal */
|
|
571
|
+
async _reconnectAuth() {
|
|
572
|
+
await this._waitForAuthIfNeeded();
|
|
573
|
+
if (!this.isConnected()) {
|
|
574
|
+
this.connect();
|
|
863
575
|
}
|
|
864
576
|
}
|
|
865
577
|
}
|