@supabase/realtime-js 2.108.2-beta.0 → 2.108.2-beta.5

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.
@@ -0,0 +1,88 @@
1
+ # `httpSend()` server version requirement
2
+
3
+ **Since:** v2.107.0
4
+ **Applies to:** anyone calling `RealtimeChannel.httpSend()` (or `RealtimeChannel.broadcast()` configured to use HTTP)
5
+
6
+ `httpSend()` sends broadcast events through a per-event REST endpoint:
7
+
8
+ ```
9
+ POST /api/broadcast/:topic/events/:event
10
+ ```
11
+
12
+ This endpoint was added in **Realtime server v2.97.0** ([supabase/realtime#1864](https://github.com/supabase/realtime/pull/1864)). It also enables binary broadcast payloads (`application/octet-stream`), which the older batch endpoint does not support.
13
+
14
+ ## What changed
15
+
16
+ - `httpSend()` calls the new per-event endpoint and expects HTTP `202`.
17
+ - On `404`, the client now rejects with a message that names the requirement and the escape hatches (see below) instead of returning the generic `Not Found` text from the server.
18
+ - The original `RealtimeChannel.send()` (and its REST fallback) still target the older `POST /api/broadcast` batch endpoint and are unaffected.
19
+
20
+ ## Who is affected
21
+
22
+ You are affected if **all** of the following are true:
23
+
24
+ 1. You upgraded `@supabase/realtime-js` (or `@supabase/supabase-js`) to **v2.107.0 or later**, **and**
25
+ 2. You call `httpSend()` (or `broadcast()` with `type: 'broadcast'` routed through HTTP), **and**
26
+ 3. Your Realtime server is **older than v2.97.0**.
27
+
28
+ If you only use the WebSocket `send()` API, or your Realtime server is already on v2.97.0+, nothing changes for you.
29
+
30
+ ## How to verify the server version
31
+
32
+ The Realtime server does not currently emit a version header, so the easiest checks are:
33
+
34
+ - **Hosted Supabase:** version is rolled forward continuously and is on v2.97.0+.
35
+ - **Local development with the Supabase CLI:** recent CLI versions bundle Realtime v2.97.0+. Update the CLI to the latest stable.
36
+ - **Self-hosted:** check the `image:` tag of the Realtime container in your `docker-compose.yml`.
37
+
38
+ ## What to do
39
+
40
+ ### If you are on a recent Supabase CLI
41
+
42
+ You should already be on a compatible Realtime version. If you still see the 404 error, update the CLI:
43
+
44
+ ```bash
45
+ # macOS / Homebrew
46
+ brew upgrade supabase/tap/supabase
47
+
48
+ # npm
49
+ npm install -g supabase
50
+
51
+ # scoop / etc — see https://supabase.com/docs/guides/local-development
52
+ ```
53
+
54
+ Then restart the local Supabase stack.
55
+
56
+ ### If you need to pin a specific Realtime version locally
57
+
58
+ The CLI honors a per-project pin file. Create the file with the desired image tag:
59
+
60
+ ```bash
61
+ mkdir -p supabase/.temp
62
+ echo "v2.97.3" > supabase/.temp/realtime-version
63
+ supabase stop && supabase start
64
+ ```
65
+
66
+ (This is the same mechanism the `@supabase/supabase-js` test harness uses internally.)
67
+
68
+ ### If you self-host
69
+
70
+ Bump the Realtime image in your deployment to `v2.97.3` or newer:
71
+
72
+ ```yaml
73
+ services:
74
+ realtime:
75
+ image: supabase/realtime:v2.97.3
76
+ ```
77
+
78
+ ### If you cannot update the server right now
79
+
80
+ Downgrade `@supabase/realtime-js` (and `@supabase/supabase-js`) back to **v2.106.x**, which only uses the older `POST /api/broadcast` batch endpoint:
81
+
82
+ ```bash
83
+ npm install @supabase/supabase-js@2.106.2
84
+ ```
85
+
86
+ ## Why the message is more specific now
87
+
88
+ Prior to this release, a 404 from the new endpoint surfaced as a plain `Not Found` error, which was hard to act on. The client now rejects with a message that points at this migration file and the escape hatches above.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supabase/realtime-js",
3
- "version": "2.108.2-beta.0",
3
+ "version": "2.108.2-beta.5",
4
4
  "description": "Listen to realtime updates to your PostgreSQL database",
5
5
  "keywords": [
6
6
  "realtime",
@@ -179,6 +179,9 @@ export default class RealtimeChannel {
179
179
  broadcastEndpointURL: string
180
180
  private: boolean
181
181
  presence: RealtimePresence
182
+ private _postgresChangesSystemReady = false
183
+ private _subscribeCallback: ((status: REALTIME_SUBSCRIBE_STATES, err?: Error) => void) | null =
184
+ null
182
185
  /** @internal */
183
186
  channelAdapter: ChannelAdapter
184
187
 
@@ -253,8 +256,22 @@ export default class RealtimeChannel {
253
256
 
254
257
  this.channelAdapter = new ChannelAdapter(this.socket.socketAdapter, topic, this.params)
255
258
  this.presence = new RealtimePresence(this)
259
+ this.channelAdapter.on(REALTIME_LISTEN_TYPES.SYSTEM, (payload: unknown) => {
260
+ if (
261
+ payload &&
262
+ typeof payload === 'object' &&
263
+ 'extension' in payload &&
264
+ payload.extension === REALTIME_LISTEN_TYPES.POSTGRES_CHANGES &&
265
+ 'status' in payload &&
266
+ payload.status === 'ok'
267
+ ) {
268
+ this._postgresChangesSystemReady = true
269
+ this._emitSubscribed()
270
+ }
271
+ })
256
272
 
257
273
  this._onClose(() => {
274
+ this._resetSubscribeState()
258
275
  this.socket._remove(this)
259
276
  })
260
277
 
@@ -277,6 +294,10 @@ export default class RealtimeChannel {
277
294
  * Log the full `err` so its `cause`, `name`, and any structured fields aren't hidden
278
295
  * behind `err.message`.
279
296
  *
297
+ * For channels that only bind `postgres_changes` callbacks, `SUBSCRIBED` is emitted after the
298
+ * server confirms the Postgres changefeed is ready. Channels with `broadcast` or `presence`
299
+ * callbacks keep the existing `SUBSCRIBED` timing.
300
+ *
280
301
  * @category Realtime
281
302
  *
282
303
  * @example Handling errors
@@ -297,6 +318,7 @@ export default class RealtimeChannel {
297
318
  this.socket.connect()
298
319
  }
299
320
  if (this.channelAdapter.isClosed()) {
321
+ this._resetSubscribeState()
300
322
  const {
301
323
  config: { broadcast, presence, private: isPrivate },
302
324
  } = this.params
@@ -344,11 +366,13 @@ export default class RealtimeChannel {
344
366
  this._updatePostgresBindings(postgres_changes, callback)
345
367
  })
346
368
  .receive('error', (error: { [key: string]: any }) => {
369
+ this._resetSubscribeState()
347
370
  this.state = CHANNEL_STATES.errored
348
371
  const message = Object.values(error).join(', ') || 'error'
349
372
  callback?.(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, new Error(message, { cause: error }))
350
373
  })
351
374
  .receive('timeout', () => {
375
+ this._resetSubscribeState()
352
376
  callback?.(REALTIME_SUBSCRIBE_STATES.TIMED_OUT)
353
377
  })
354
378
  }
@@ -396,7 +420,7 @@ export default class RealtimeChannel {
396
420
  this.bindings.postgres_changes = newPostgresBindings
397
421
 
398
422
  if (this.state != CHANNEL_STATES.errored && callback) {
399
- callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED)
423
+ this._onSubscribeOk(callback)
400
424
  }
401
425
  }
402
426
 
@@ -803,6 +827,16 @@ export default class RealtimeChannel {
803
827
  return { success: true }
804
828
  }
805
829
 
830
+ if (response.status === 404) {
831
+ return Promise.reject(
832
+ new Error(
833
+ 'httpSend() requires Realtime server v2.97.0 or newer; the endpoint returned 404. ' +
834
+ 'Update your Supabase CLI to a recent version, or upgrade the Realtime server in your self-hosted setup. ' +
835
+ 'See https://github.com/supabase/supabase-js/blob/master/packages/core/realtime-js/migrations/httpsend-server-version.md'
836
+ )
837
+ )
838
+ }
839
+
806
840
  let errorMessage = response.statusText
807
841
  try {
808
842
  const errorBody = await response.json()
@@ -1121,6 +1155,39 @@ export default class RealtimeChannel {
1121
1155
  return normalizedServer === normalizedClient
1122
1156
  }
1123
1157
 
1158
+ private _onSubscribeOk(callback: (status: REALTIME_SUBSCRIBE_STATES, err?: Error) => void) {
1159
+ if (!this._shouldWaitForPostgresChangesSystem()) {
1160
+ callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED)
1161
+ return
1162
+ }
1163
+
1164
+ this._subscribeCallback = callback
1165
+ this._emitSubscribed()
1166
+ }
1167
+
1168
+ private _emitSubscribed() {
1169
+ if (!this._subscribeCallback || !this._postgresChangesSystemReady) {
1170
+ return
1171
+ }
1172
+
1173
+ const callback = this._subscribeCallback
1174
+ this._subscribeCallback = null
1175
+ callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED)
1176
+ }
1177
+
1178
+ private _resetSubscribeState() {
1179
+ this._postgresChangesSystemReady = false
1180
+ this._subscribeCallback = null
1181
+ }
1182
+
1183
+ private _shouldWaitForPostgresChangesSystem() {
1184
+ return (
1185
+ (this.bindings.postgres_changes?.length ?? 0) > 0 &&
1186
+ (this.bindings.broadcast?.length ?? 0) === 0 &&
1187
+ (this.bindings.presence?.length ?? 0) === 0
1188
+ )
1189
+ }
1190
+
1124
1191
  /** @internal */
1125
1192
  private _getPayloadRecords(payload: any) {
1126
1193
  const records = {
@@ -4,4 +4,4 @@
4
4
  // - Debugging and support (identifying which version is running)
5
5
  // - Telemetry and logging (version reporting in errors/analytics)
6
6
  // - Ensuring build artifacts match the published package version
7
- export const version = '2.108.2-beta.0'
7
+ export const version = '2.108.2-beta.5'