@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.
Files changed (111) hide show
  1. package/dist/main/RealtimeChannel.d.ts +35 -28
  2. package/dist/main/RealtimeChannel.d.ts.map +1 -1
  3. package/dist/main/RealtimeChannel.js +140 -301
  4. package/dist/main/RealtimeChannel.js.map +1 -1
  5. package/dist/main/RealtimeClient.d.ts +38 -57
  6. package/dist/main/RealtimeClient.d.ts.map +1 -1
  7. package/dist/main/RealtimeClient.js +232 -520
  8. package/dist/main/RealtimeClient.js.map +1 -1
  9. package/dist/main/RealtimePresence.d.ts +8 -24
  10. package/dist/main/RealtimePresence.d.ts.map +1 -1
  11. package/dist/main/RealtimePresence.js +6 -202
  12. package/dist/main/RealtimePresence.js.map +1 -1
  13. package/dist/main/lib/constants.d.ts +39 -35
  14. package/dist/main/lib/constants.d.ts.map +1 -1
  15. package/dist/main/lib/constants.js +30 -35
  16. package/dist/main/lib/constants.js.map +1 -1
  17. package/dist/main/lib/version.d.ts +1 -1
  18. package/dist/main/lib/version.d.ts.map +1 -1
  19. package/dist/main/lib/version.js +1 -1
  20. package/dist/main/lib/version.js.map +1 -1
  21. package/dist/main/lib/websocket-factory.d.ts +0 -9
  22. package/dist/main/lib/websocket-factory.d.ts.map +1 -1
  23. package/dist/main/lib/websocket-factory.js +0 -12
  24. package/dist/main/lib/websocket-factory.js.map +1 -1
  25. package/dist/main/phoenix/channelAdapter.d.ts +32 -0
  26. package/dist/main/phoenix/channelAdapter.d.ts.map +1 -0
  27. package/dist/main/phoenix/channelAdapter.js +103 -0
  28. package/dist/main/phoenix/channelAdapter.js.map +1 -0
  29. package/dist/main/phoenix/presenceAdapter.d.ts +53 -0
  30. package/dist/main/phoenix/presenceAdapter.d.ts.map +1 -0
  31. package/dist/main/phoenix/presenceAdapter.js +93 -0
  32. package/dist/main/phoenix/presenceAdapter.js.map +1 -0
  33. package/dist/main/phoenix/socketAdapter.d.ts +38 -0
  34. package/dist/main/phoenix/socketAdapter.d.ts.map +1 -0
  35. package/dist/main/phoenix/socketAdapter.js +114 -0
  36. package/dist/main/phoenix/socketAdapter.js.map +1 -0
  37. package/dist/main/phoenix/types.d.ts +5 -0
  38. package/dist/main/phoenix/types.d.ts.map +1 -0
  39. package/dist/main/phoenix/types.js +3 -0
  40. package/dist/main/phoenix/types.js.map +1 -0
  41. package/dist/module/RealtimeChannel.d.ts +35 -28
  42. package/dist/module/RealtimeChannel.d.ts.map +1 -1
  43. package/dist/module/RealtimeChannel.js +141 -302
  44. package/dist/module/RealtimeChannel.js.map +1 -1
  45. package/dist/module/RealtimeClient.d.ts +38 -57
  46. package/dist/module/RealtimeClient.d.ts.map +1 -1
  47. package/dist/module/RealtimeClient.js +233 -521
  48. package/dist/module/RealtimeClient.js.map +1 -1
  49. package/dist/module/RealtimePresence.d.ts +8 -24
  50. package/dist/module/RealtimePresence.d.ts.map +1 -1
  51. package/dist/module/RealtimePresence.js +5 -202
  52. package/dist/module/RealtimePresence.js.map +1 -1
  53. package/dist/module/lib/constants.d.ts +39 -35
  54. package/dist/module/lib/constants.d.ts.map +1 -1
  55. package/dist/module/lib/constants.js +30 -35
  56. package/dist/module/lib/constants.js.map +1 -1
  57. package/dist/module/lib/version.d.ts +1 -1
  58. package/dist/module/lib/version.d.ts.map +1 -1
  59. package/dist/module/lib/version.js +1 -1
  60. package/dist/module/lib/version.js.map +1 -1
  61. package/dist/module/lib/websocket-factory.d.ts +0 -9
  62. package/dist/module/lib/websocket-factory.d.ts.map +1 -1
  63. package/dist/module/lib/websocket-factory.js +0 -12
  64. package/dist/module/lib/websocket-factory.js.map +1 -1
  65. package/dist/module/phoenix/channelAdapter.d.ts +32 -0
  66. package/dist/module/phoenix/channelAdapter.d.ts.map +1 -0
  67. package/dist/module/phoenix/channelAdapter.js +100 -0
  68. package/dist/module/phoenix/channelAdapter.js.map +1 -0
  69. package/dist/module/phoenix/presenceAdapter.d.ts +53 -0
  70. package/dist/module/phoenix/presenceAdapter.d.ts.map +1 -0
  71. package/dist/module/phoenix/presenceAdapter.js +90 -0
  72. package/dist/module/phoenix/presenceAdapter.js.map +1 -0
  73. package/dist/module/phoenix/socketAdapter.d.ts +38 -0
  74. package/dist/module/phoenix/socketAdapter.d.ts.map +1 -0
  75. package/dist/module/phoenix/socketAdapter.js +111 -0
  76. package/dist/module/phoenix/socketAdapter.js.map +1 -0
  77. package/dist/module/phoenix/types.d.ts +5 -0
  78. package/dist/module/phoenix/types.d.ts.map +1 -0
  79. package/dist/module/phoenix/types.js +2 -0
  80. package/dist/module/phoenix/types.js.map +1 -0
  81. package/dist/tsconfig.module.tsbuildinfo +1 -1
  82. package/dist/tsconfig.tsbuildinfo +1 -1
  83. package/package.json +2 -2
  84. package/src/RealtimeChannel.ts +201 -364
  85. package/src/RealtimeClient.ts +296 -583
  86. package/src/RealtimePresence.ts +10 -287
  87. package/src/lib/constants.ts +50 -37
  88. package/src/lib/version.ts +1 -1
  89. package/src/lib/websocket-factory.ts +0 -13
  90. package/src/phoenix/channelAdapter.ts +147 -0
  91. package/src/phoenix/presenceAdapter.ts +116 -0
  92. package/src/phoenix/socketAdapter.ts +168 -0
  93. package/src/phoenix/types.ts +32 -0
  94. package/dist/main/lib/push.d.ts +0 -48
  95. package/dist/main/lib/push.d.ts.map +0 -1
  96. package/dist/main/lib/push.js +0 -102
  97. package/dist/main/lib/push.js.map +0 -1
  98. package/dist/main/lib/timer.d.ts +0 -22
  99. package/dist/main/lib/timer.d.ts.map +0 -1
  100. package/dist/main/lib/timer.js +0 -39
  101. package/dist/main/lib/timer.js.map +0 -1
  102. package/dist/module/lib/push.d.ts +0 -48
  103. package/dist/module/lib/push.d.ts.map +0 -1
  104. package/dist/module/lib/push.js +0 -99
  105. package/dist/module/lib/push.js.map +0 -1
  106. package/dist/module/lib/timer.d.ts +0 -22
  107. package/dist/module/lib/timer.d.ts.map +0 -1
  108. package/dist/module/lib/timer.js +0 -36
  109. package/dist/module/lib/timer.js.map +0 -1
  110. package/src/lib/push.ts +0 -121
  111. 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, SOCKET_STATES, TRANSPORTS, DEFAULT_VSN, VSN_1_0_0, VSN_2_0_0, WS_CLOSE_NORMAL, } from './lib/constants';
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
- const noop = () => { };
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.stateChangeCallbacks = {
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._heartbeatSentAt = null;
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
- // Initialize endpoint URLs
104
- this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`;
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
- // Establish WebSocket connection
128
- if (this.transport) {
129
- // Use custom transport if provided
130
- this.conn = new this.transport(this.endpointURL());
131
- }
132
- else {
133
- // Try to use native WebSocket
134
- try {
135
- this.conn = WebSocketFactory.createWebSocket(this.endpointURL());
136
- }
137
- catch (error) {
138
- this._setConnectionState('disconnected');
139
- const errorMessage = error.message;
140
- // Provide helpful error message based on environment
141
- if (errorMessage.includes('Node.js')) {
142
- throw new Error(`${errorMessage}\n\n` +
143
- 'To use Realtime in Node.js, you need to provide a WebSocket implementation:\n\n' +
144
- 'Option 1: Use Node.js 22+ which has native WebSocket support\n' +
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._setupConnectionHandlers();
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._appendParams(this.endPoint, Object.assign({}, this.params, { vsn: this.vsn }));
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 removes a single channel
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 removes all channels
216
+ * Unsubscribes, removes and tears down all channels
219
217
  */
220
218
  async removeAllChannels() {
221
- const values_1 = await Promise.all(this.channels.map((channel) => channel.unsubscribe()));
222
- this.channels = [];
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 values_1;
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.logger(kind, msg, data);
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
- switch (this.conn && this.conn.readyState) {
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.connectionState() === CONNECTION_STATE.Open;
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._connectionState === 'connecting';
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._connectionState === 'disconnecting';
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
- const { topic, event, payload, ref } = data;
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
- var _a;
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
- let newRef = this.ref + 1;
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
- * Unsubscribe from channels with the specified topic.
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._isJoined()) {
749
- channel._push(CHANNEL_EVENTS.access_token, {
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
- * Trigger state change callbacks with proper error handling
779
- * @internal
780
- */
781
- _triggerStateCallbacks(event, data) {
782
- try {
783
- this.stateChangeCallbacks[event].forEach((callback) => {
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
- catch (e) {
793
- this.log('error', `error triggering ${event} callbacks`, e);
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
- * Setup reconnection timer with proper configuration
491
+ * Terminate the Web Worker and clear the reference
798
492
  * @internal
799
493
  */
800
- _setupReconnectionTimer() {
801
- this.reconnectTimer = new Timer(async () => {
802
- setTimeout(async () => {
803
- await this._waitForAuthIfNeeded();
804
- if (!this.isConnected()) {
805
- this.connect();
806
- }
807
- }, CONNECTION_TIMEOUTS.RECONNECT_DELAY);
808
- }, this.reconnectAfterMs);
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 client options with defaults
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, _k, _l, _m;
816
- // Set defaults
817
- this.transport = (_a = options === null || options === void 0 ? void 0 : options.transport) !== null && _a !== void 0 ? _a : null;
818
- this.timeout = (_b = options === null || options === void 0 ? void 0 : options.timeout) !== null && _b !== void 0 ? _b : DEFAULT_TIMEOUT;
819
- this.heartbeatIntervalMs =
820
- (_c = options === null || options === void 0 ? void 0 : options.heartbeatIntervalMs) !== null && _c !== void 0 ? _c : CONNECTION_TIMEOUTS.HEARTBEAT_INTERVAL;
821
- this.worker = (_d = options === null || options === void 0 ? void 0 : options.worker) !== null && _d !== void 0 ? _d : false;
822
- this.accessToken = (_e = options === null || options === void 0 ? void 0 : options.accessToken) !== null && _e !== void 0 ? _e : null;
823
- this.heartbeatCallback = (_f = options === null || options === void 0 ? void 0 : options.heartbeatCallback) !== null && _f !== void 0 ? _f : noop;
824
- this.vsn = (_g = options === null || options === void 0 ? void 0 : options.vsn) !== null && _g !== void 0 ? _g : DEFAULT_VSN;
825
- // Handle special cases
826
- if (options === null || options === void 0 ? void 0 : options.params)
827
- this.params = options.params;
828
- if (options === null || options === void 0 ? void 0 : options.logger)
829
- this.logger = options.logger;
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
- switch (this.vsn) {
535
+ let defaultEncode;
536
+ let defaultDecode;
537
+ switch (result.vsn) {
840
538
  case VSN_1_0_0:
841
- this.encode =
842
- (_j = options === null || options === void 0 ? void 0 : options.encode) !== null && _j !== void 0 ? _j : ((payload, callback) => {
843
- return callback(JSON.stringify(payload));
844
- });
845
- this.decode =
846
- (_k = options === null || options === void 0 ? void 0 : options.decode) !== null && _k !== void 0 ? _k : ((payload, callback) => {
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
- this.encode = (_l = options === null || options === void 0 ? void 0 : options.encode) !== null && _l !== void 0 ? _l : this.serializer.encode.bind(this.serializer);
852
- this.decode = (_m = options === null || options === void 0 ? void 0 : options.decode) !== null && _m !== void 0 ? _m : this.serializer.decode.bind(this.serializer);
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: ${this.vsn}`);
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
  }