@aurabase/realtime 0.6.0 → 0.6.1

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/README.md CHANGED
@@ -174,6 +174,7 @@ const status = await room.send({ type: 'broadcast', event: 'cursor-pos', payload
174
174
  | `'timeout'` | `ack` demandé sur un canal abonné, aucun accusé reçu en 5 s : l'accusé s'est **perdu** (lien coupé, serveur saturé). | oui |
175
175
  | `'not_connected'` | Aucun transport lié, ou socket non-OPEN — le message a été **jeté**. | **non** |
176
176
  | `'requires_subscription'` | Le serveur n'a **pas d'abonnement** pour ce canal : jamais souscrit, souscription **refusée** (RLS, `private_only`, quota) ou **révoquée** depuis (voir ci-dessous). | **non** |
177
+ | `'invalid_payload'` | Le `payload` porte lui-même une clé `event` : elle écraserait le nom de l'événement (replié dans le payload du fil). Renommez-la (`kind`…). | **non** |
177
178
  | `'error'` | L'envoi a levé (sérialisation, transport en erreur). | non |
178
179
 
179
180
  Avant la remédiation C18/N24, ces trois derniers cas rendaient tous `'error'` (et le
package/dist/index.cjs CHANGED
@@ -223,6 +223,7 @@ function matchesColumnFilter(filter, record) {
223
223
  var DEFAULT_SUBSCRIBE_TIMEOUT_MS = 1e4;
224
224
  var PERTE_D_ABONNEMENT = /Non abonné au canal|Accès révoqué au channel/;
225
225
  var AuraChannel = class {
226
+ /** Nom du canal tel que passé à `realtime.channel(name)`. */
226
227
  name;
227
228
  _projectPrefix;
228
229
  _listeners = [];
@@ -288,6 +289,21 @@ var AuraChannel = class {
288
289
  get fullName() {
289
290
  return `${this._projectPrefix()}${this.name}`;
290
291
  }
292
+ /**
293
+ * Le nom de canal porté par un message serveur désigne-t-il CE canal ?
294
+ *
295
+ * Le serveur nomme toujours le canal qualifié `<project_id>:<nom>`. Quand le SDK connaît le
296
+ * projet (session), `fullName` est déjà qualifié : égalité stricte. Sans session (clé API seule),
297
+ * le SDK émet le canal nu et la passerelle le qualifie : on retire alors exactement UN segment
298
+ * de projet (sans `:`) devant le nom. Un nom qui ne fait que se terminer par `:<nom>`
299
+ * (`<pid>:x:<nom>`) désigne un AUTRE canal.
300
+ */
301
+ _isOwnServerChannel(serverChannel) {
302
+ if (serverChannel === this.fullName || serverChannel === this.name) return true;
303
+ if (this.fullName !== this.name) return false;
304
+ const sep = serverChannel.indexOf(":");
305
+ return sep > 0 && serverChannel.slice(sep + 1) === this.name;
306
+ }
291
307
  /**
292
308
  * Signature d'implémentation de `.on()`.
293
309
  * Retourne une fonction de désabonnement direct `() => void` qui proxy également l'instance du canal pour le chaînage fluide.
@@ -359,9 +375,11 @@ var AuraChannel = class {
359
375
  * with the full config (postgres_changes, broadcast, presence).
360
376
  *
361
377
  * Le callback reçoit les mêmes statuts que `@supabase/realtime-js` :
362
- * - `SUBSCRIBED` — le serveur a confirmé l'abonnement ;
363
- * - `CHANNEL_ERROR` — refus serveur (RLS, rate limit…), échec de handshake ou
364
- * coupure inattendue de la connexion ;
378
+ * - `SUBSCRIBED` — le serveur a confirmé l'abonnement ; émis de nouveau quand une
379
+ * écoute `postgres_changes` perdue (voir `CHANNEL_ERROR`) est rétablie ;
380
+ * - `CHANNEL_ERROR` — refus serveur (RLS, rate limit…), échec de handshake, coupure
381
+ * inattendue de la connexion, ou écoute `postgres_changes` du projet
382
+ * perdue (l'abonnement est conservé, `SUBSCRIBED` suit sa reprise) ;
365
383
  * - `TIMED_OUT` — aucune réponse du serveur dans `timeoutMs` après l'envoi ;
366
384
  * - `CLOSED` — le canal a été fermé volontairement (`unsubscribe()`,
367
385
  * `removeChannel()`, `removeAllChannels()`, `disconnect()`).
@@ -510,6 +528,10 @@ var AuraChannel = class {
510
528
  */
511
529
  send(message) {
512
530
  return new Promise((resolve) => {
531
+ if (message.payload && Object.prototype.hasOwnProperty.call(message.payload, "event")) {
532
+ resolve("invalid_payload");
533
+ return;
534
+ }
513
535
  if (!this._sendFn) {
514
536
  resolve("not_connected");
515
537
  return;
@@ -642,7 +664,7 @@ var AuraChannel = class {
642
664
  case "error": {
643
665
  const errMsg = msg;
644
666
  const errChannel = errMsg.channel;
645
- if (errChannel && errChannel !== this.fullName && errChannel !== this.name) {
667
+ if (errChannel && !this._isOwnServerChannel(errChannel)) {
646
668
  break;
647
669
  }
648
670
  this._clearSubscribeTimeout();
@@ -655,6 +677,12 @@ var AuraChannel = class {
655
677
  }
656
678
  break;
657
679
  }
680
+ case "channel_recovered": {
681
+ if (!this._isOwnServerChannel(msg.channel)) break;
682
+ if (this._serverState !== "subscribed") break;
683
+ this._emitStatus("SUBSCRIBED");
684
+ break;
685
+ }
658
686
  case "token_refreshed":
659
687
  break;
660
688
  case "broadcast_ack": {
@@ -1473,6 +1501,17 @@ var RealtimeService = class _RealtimeService {
1473
1501
  * - ou déjà qualifié (`'<project_id>:room-1'`).
1474
1502
  */
1475
1503
  httpBroadcast(channel, event, payload = {}, options) {
1504
+ if (Object.prototype.hasOwnProperty.call(payload, "event")) {
1505
+ return Promise.resolve({
1506
+ data: null,
1507
+ error: new core.AuraError(0, {
1508
+ error: {
1509
+ code: "realtime_payload_event_reserved",
1510
+ message: "httpBroadcast: le payload contient une cl\xE9 `event`, r\xE9serv\xE9e au nom de l'\xE9v\xE9nement (elle \xE9craserait celui-ci chez les abonn\xE9s). Renommez-la (`eventName`, `kind`\u2026)."
1511
+ }
1512
+ })
1513
+ });
1514
+ }
1476
1515
  const explicitProjectId = options?.projectId ?? this.client.projectId;
1477
1516
  let prefix = this._projectPrefix();
1478
1517
  if (!prefix && explicitProjectId) {
package/dist/index.d.cts CHANGED
@@ -56,6 +56,7 @@ declare const DEFAULT_SUBSCRIBE_TIMEOUT_MS = 10000;
56
56
  * ```
57
57
  */
58
58
  declare class AuraChannel {
59
+ /** Nom du canal tel que passé à `realtime.channel(name)`. */
59
60
  readonly name: string;
60
61
  private readonly _projectPrefix;
61
62
  private readonly _listeners;
@@ -108,6 +109,16 @@ declare class AuraChannel {
108
109
  private _nextRef;
109
110
  /** Full channel name with project prefix (for server protocol) */
110
111
  get fullName(): string;
112
+ /**
113
+ * Le nom de canal porté par un message serveur désigne-t-il CE canal ?
114
+ *
115
+ * Le serveur nomme toujours le canal qualifié `<project_id>:<nom>`. Quand le SDK connaît le
116
+ * projet (session), `fullName` est déjà qualifié : égalité stricte. Sans session (clé API seule),
117
+ * le SDK émet le canal nu et la passerelle le qualifie : on retire alors exactement UN segment
118
+ * de projet (sans `:`) devant le nom. Un nom qui ne fait que se terminer par `:<nom>`
119
+ * (`<pid>:x:<nom>`) désigne un AUTRE canal.
120
+ */
121
+ _isOwnServerChannel(serverChannel: string): boolean;
111
122
  /**
112
123
  * Enregistre un listener pour les événements PostgreSQL (CDC) avec filtrage INSERT.
113
124
  * Typé spécifiquement avec `new: TRow` et `old: null`.
@@ -117,14 +128,16 @@ declare class AuraChannel {
117
128
  }, callback: (payload: RealtimePostgresInsertPayload<TRow>) => void): AuraChannelSubscription;
118
129
  /**
119
130
  * Enregistre un listener pour les événements PostgreSQL (CDC) avec filtrage UPDATE.
120
- * Typé spécifiquement avec `new: TRow` et `old: Partial<TRow>`.
131
+ * Typé spécifiquement avec `new: TRow` et `old: Partial<TRow>` : `old` ne porte que
132
+ * la clé primaire de l'ancienne ligne, jamais ses autres valeurs.
121
133
  */
122
134
  on<TRow = Record<string, unknown>>(type: 'postgres_changes', filter: PostgresChangesFilter & {
123
135
  event: 'UPDATE';
124
136
  }, callback: (payload: RealtimePostgresUpdatePayload<TRow>) => void): AuraChannelSubscription;
125
137
  /**
126
138
  * Enregistre un listener pour les événements PostgreSQL (CDC) avec filtrage DELETE.
127
- * Typé spécifiquement avec `new: null` et `old: Partial<TRow> | null`.
139
+ * Typé spécifiquement avec `new: null` et `old: Partial<TRow> | null` : `old` ne porte
140
+ * que la clé primaire de la ligne supprimée.
128
141
  */
129
142
  on<TRow = Record<string, unknown>>(type: 'postgres_changes', filter: PostgresChangesFilter & {
130
143
  event: 'DELETE';
@@ -157,9 +170,11 @@ declare class AuraChannel {
157
170
  * with the full config (postgres_changes, broadcast, presence).
158
171
  *
159
172
  * Le callback reçoit les mêmes statuts que `@supabase/realtime-js` :
160
- * - `SUBSCRIBED` — le serveur a confirmé l'abonnement ;
161
- * - `CHANNEL_ERROR` — refus serveur (RLS, rate limit…), échec de handshake ou
162
- * coupure inattendue de la connexion ;
173
+ * - `SUBSCRIBED` — le serveur a confirmé l'abonnement ; émis de nouveau quand une
174
+ * écoute `postgres_changes` perdue (voir `CHANNEL_ERROR`) est rétablie ;
175
+ * - `CHANNEL_ERROR` — refus serveur (RLS, rate limit…), échec de handshake, coupure
176
+ * inattendue de la connexion, ou écoute `postgres_changes` du projet
177
+ * perdue (l'abonnement est conservé, `SUBSCRIBED` suit sa reprise) ;
163
178
  * - `TIMED_OUT` — aucune réponse du serveur dans `timeoutMs` après l'envoi ;
164
179
  * - `CLOSED` — le canal a été fermé volontairement (`unsubscribe()`,
165
180
  * `removeChannel()`, `removeAllChannels()`, `disconnect()`).
package/dist/index.d.ts CHANGED
@@ -56,6 +56,7 @@ declare const DEFAULT_SUBSCRIBE_TIMEOUT_MS = 10000;
56
56
  * ```
57
57
  */
58
58
  declare class AuraChannel {
59
+ /** Nom du canal tel que passé à `realtime.channel(name)`. */
59
60
  readonly name: string;
60
61
  private readonly _projectPrefix;
61
62
  private readonly _listeners;
@@ -108,6 +109,16 @@ declare class AuraChannel {
108
109
  private _nextRef;
109
110
  /** Full channel name with project prefix (for server protocol) */
110
111
  get fullName(): string;
112
+ /**
113
+ * Le nom de canal porté par un message serveur désigne-t-il CE canal ?
114
+ *
115
+ * Le serveur nomme toujours le canal qualifié `<project_id>:<nom>`. Quand le SDK connaît le
116
+ * projet (session), `fullName` est déjà qualifié : égalité stricte. Sans session (clé API seule),
117
+ * le SDK émet le canal nu et la passerelle le qualifie : on retire alors exactement UN segment
118
+ * de projet (sans `:`) devant le nom. Un nom qui ne fait que se terminer par `:<nom>`
119
+ * (`<pid>:x:<nom>`) désigne un AUTRE canal.
120
+ */
121
+ _isOwnServerChannel(serverChannel: string): boolean;
111
122
  /**
112
123
  * Enregistre un listener pour les événements PostgreSQL (CDC) avec filtrage INSERT.
113
124
  * Typé spécifiquement avec `new: TRow` et `old: null`.
@@ -117,14 +128,16 @@ declare class AuraChannel {
117
128
  }, callback: (payload: RealtimePostgresInsertPayload<TRow>) => void): AuraChannelSubscription;
118
129
  /**
119
130
  * Enregistre un listener pour les événements PostgreSQL (CDC) avec filtrage UPDATE.
120
- * Typé spécifiquement avec `new: TRow` et `old: Partial<TRow>`.
131
+ * Typé spécifiquement avec `new: TRow` et `old: Partial<TRow>` : `old` ne porte que
132
+ * la clé primaire de l'ancienne ligne, jamais ses autres valeurs.
121
133
  */
122
134
  on<TRow = Record<string, unknown>>(type: 'postgres_changes', filter: PostgresChangesFilter & {
123
135
  event: 'UPDATE';
124
136
  }, callback: (payload: RealtimePostgresUpdatePayload<TRow>) => void): AuraChannelSubscription;
125
137
  /**
126
138
  * Enregistre un listener pour les événements PostgreSQL (CDC) avec filtrage DELETE.
127
- * Typé spécifiquement avec `new: null` et `old: Partial<TRow> | null`.
139
+ * Typé spécifiquement avec `new: null` et `old: Partial<TRow> | null` : `old` ne porte
140
+ * que la clé primaire de la ligne supprimée.
128
141
  */
129
142
  on<TRow = Record<string, unknown>>(type: 'postgres_changes', filter: PostgresChangesFilter & {
130
143
  event: 'DELETE';
@@ -157,9 +170,11 @@ declare class AuraChannel {
157
170
  * with the full config (postgres_changes, broadcast, presence).
158
171
  *
159
172
  * Le callback reçoit les mêmes statuts que `@supabase/realtime-js` :
160
- * - `SUBSCRIBED` — le serveur a confirmé l'abonnement ;
161
- * - `CHANNEL_ERROR` — refus serveur (RLS, rate limit…), échec de handshake ou
162
- * coupure inattendue de la connexion ;
173
+ * - `SUBSCRIBED` — le serveur a confirmé l'abonnement ; émis de nouveau quand une
174
+ * écoute `postgres_changes` perdue (voir `CHANNEL_ERROR`) est rétablie ;
175
+ * - `CHANNEL_ERROR` — refus serveur (RLS, rate limit…), échec de handshake, coupure
176
+ * inattendue de la connexion, ou écoute `postgres_changes` du projet
177
+ * perdue (l'abonnement est conservé, `SUBSCRIBED` suit sa reprise) ;
163
178
  * - `TIMED_OUT` — aucune réponse du serveur dans `timeoutMs` après l'envoi ;
164
179
  * - `CLOSED` — le canal a été fermé volontairement (`unsubscribe()`,
165
180
  * `removeChannel()`, `removeAllChannels()`, `disconnect()`).
package/dist/index.js CHANGED
@@ -221,6 +221,7 @@ function matchesColumnFilter(filter, record) {
221
221
  var DEFAULT_SUBSCRIBE_TIMEOUT_MS = 1e4;
222
222
  var PERTE_D_ABONNEMENT = /Non abonné au canal|Accès révoqué au channel/;
223
223
  var AuraChannel = class {
224
+ /** Nom du canal tel que passé à `realtime.channel(name)`. */
224
225
  name;
225
226
  _projectPrefix;
226
227
  _listeners = [];
@@ -286,6 +287,21 @@ var AuraChannel = class {
286
287
  get fullName() {
287
288
  return `${this._projectPrefix()}${this.name}`;
288
289
  }
290
+ /**
291
+ * Le nom de canal porté par un message serveur désigne-t-il CE canal ?
292
+ *
293
+ * Le serveur nomme toujours le canal qualifié `<project_id>:<nom>`. Quand le SDK connaît le
294
+ * projet (session), `fullName` est déjà qualifié : égalité stricte. Sans session (clé API seule),
295
+ * le SDK émet le canal nu et la passerelle le qualifie : on retire alors exactement UN segment
296
+ * de projet (sans `:`) devant le nom. Un nom qui ne fait que se terminer par `:<nom>`
297
+ * (`<pid>:x:<nom>`) désigne un AUTRE canal.
298
+ */
299
+ _isOwnServerChannel(serverChannel) {
300
+ if (serverChannel === this.fullName || serverChannel === this.name) return true;
301
+ if (this.fullName !== this.name) return false;
302
+ const sep = serverChannel.indexOf(":");
303
+ return sep > 0 && serverChannel.slice(sep + 1) === this.name;
304
+ }
289
305
  /**
290
306
  * Signature d'implémentation de `.on()`.
291
307
  * Retourne une fonction de désabonnement direct `() => void` qui proxy également l'instance du canal pour le chaînage fluide.
@@ -357,9 +373,11 @@ var AuraChannel = class {
357
373
  * with the full config (postgres_changes, broadcast, presence).
358
374
  *
359
375
  * Le callback reçoit les mêmes statuts que `@supabase/realtime-js` :
360
- * - `SUBSCRIBED` — le serveur a confirmé l'abonnement ;
361
- * - `CHANNEL_ERROR` — refus serveur (RLS, rate limit…), échec de handshake ou
362
- * coupure inattendue de la connexion ;
376
+ * - `SUBSCRIBED` — le serveur a confirmé l'abonnement ; émis de nouveau quand une
377
+ * écoute `postgres_changes` perdue (voir `CHANNEL_ERROR`) est rétablie ;
378
+ * - `CHANNEL_ERROR` — refus serveur (RLS, rate limit…), échec de handshake, coupure
379
+ * inattendue de la connexion, ou écoute `postgres_changes` du projet
380
+ * perdue (l'abonnement est conservé, `SUBSCRIBED` suit sa reprise) ;
363
381
  * - `TIMED_OUT` — aucune réponse du serveur dans `timeoutMs` après l'envoi ;
364
382
  * - `CLOSED` — le canal a été fermé volontairement (`unsubscribe()`,
365
383
  * `removeChannel()`, `removeAllChannels()`, `disconnect()`).
@@ -508,6 +526,10 @@ var AuraChannel = class {
508
526
  */
509
527
  send(message) {
510
528
  return new Promise((resolve) => {
529
+ if (message.payload && Object.prototype.hasOwnProperty.call(message.payload, "event")) {
530
+ resolve("invalid_payload");
531
+ return;
532
+ }
511
533
  if (!this._sendFn) {
512
534
  resolve("not_connected");
513
535
  return;
@@ -640,7 +662,7 @@ var AuraChannel = class {
640
662
  case "error": {
641
663
  const errMsg = msg;
642
664
  const errChannel = errMsg.channel;
643
- if (errChannel && errChannel !== this.fullName && errChannel !== this.name) {
665
+ if (errChannel && !this._isOwnServerChannel(errChannel)) {
644
666
  break;
645
667
  }
646
668
  this._clearSubscribeTimeout();
@@ -653,6 +675,12 @@ var AuraChannel = class {
653
675
  }
654
676
  break;
655
677
  }
678
+ case "channel_recovered": {
679
+ if (!this._isOwnServerChannel(msg.channel)) break;
680
+ if (this._serverState !== "subscribed") break;
681
+ this._emitStatus("SUBSCRIBED");
682
+ break;
683
+ }
656
684
  case "token_refreshed":
657
685
  break;
658
686
  case "broadcast_ack": {
@@ -1471,6 +1499,17 @@ var RealtimeService = class _RealtimeService {
1471
1499
  * - ou déjà qualifié (`'<project_id>:room-1'`).
1472
1500
  */
1473
1501
  httpBroadcast(channel, event, payload = {}, options) {
1502
+ if (Object.prototype.hasOwnProperty.call(payload, "event")) {
1503
+ return Promise.resolve({
1504
+ data: null,
1505
+ error: new AuraError(0, {
1506
+ error: {
1507
+ code: "realtime_payload_event_reserved",
1508
+ message: "httpBroadcast: le payload contient une cl\xE9 `event`, r\xE9serv\xE9e au nom de l'\xE9v\xE9nement (elle \xE9craserait celui-ci chez les abonn\xE9s). Renommez-la (`eventName`, `kind`\u2026)."
1509
+ }
1510
+ })
1511
+ });
1512
+ }
1474
1513
  const explicitProjectId = options?.projectId ?? this.client.projectId;
1475
1514
  let prefix = this._projectPrefix();
1476
1515
  if (!prefix && explicitProjectId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurabase/realtime",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Service temps réel du SDK Aurabase (postgres_changes, broadcast, presence)",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -45,7 +45,7 @@
45
45
  "realtime"
46
46
  ],
47
47
  "dependencies": {
48
- "@aurabase/core": "0.6.0"
48
+ "@aurabase/core": "0.6.1"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "20.14.15",