@aurabase/realtime 0.6.0 → 0.6.2

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,11 +289,31 @@ 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.
294
310
  */
295
311
  on(type, filter, callback) {
312
+ if (type !== "postgres_changes" && type !== "broadcast" && type !== "presence") {
313
+ throw new TypeError(
314
+ `[aurabase] channel.on(): unknown listen type "${String(type)}". For table changes use .on('postgres_changes', { event: 'INSERT', schema: 'public', table: '<table>' }, callback); other types: 'broadcast', 'presence'.`
315
+ );
316
+ }
296
317
  const id = ++this._listenerIdCounter;
297
318
  this._listeners.push({
298
319
  id,
@@ -359,9 +380,11 @@ var AuraChannel = class {
359
380
  * with the full config (postgres_changes, broadcast, presence).
360
381
  *
361
382
  * 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 ;
383
+ * - `SUBSCRIBED` — le serveur a confirmé l'abonnement ; émis de nouveau quand une
384
+ * écoute `postgres_changes` perdue (voir `CHANNEL_ERROR`) est rétablie ;
385
+ * - `CHANNEL_ERROR` — refus serveur (RLS, rate limit…), échec de handshake, coupure
386
+ * inattendue de la connexion, ou écoute `postgres_changes` du projet
387
+ * perdue (l'abonnement est conservé, `SUBSCRIBED` suit sa reprise) ;
365
388
  * - `TIMED_OUT` — aucune réponse du serveur dans `timeoutMs` après l'envoi ;
366
389
  * - `CLOSED` — le canal a été fermé volontairement (`unsubscribe()`,
367
390
  * `removeChannel()`, `removeAllChannels()`, `disconnect()`).
@@ -510,6 +533,10 @@ var AuraChannel = class {
510
533
  */
511
534
  send(message) {
512
535
  return new Promise((resolve) => {
536
+ if (message.payload && Object.prototype.hasOwnProperty.call(message.payload, "event")) {
537
+ resolve("invalid_payload");
538
+ return;
539
+ }
513
540
  if (!this._sendFn) {
514
541
  resolve("not_connected");
515
542
  return;
@@ -642,7 +669,7 @@ var AuraChannel = class {
642
669
  case "error": {
643
670
  const errMsg = msg;
644
671
  const errChannel = errMsg.channel;
645
- if (errChannel && errChannel !== this.fullName && errChannel !== this.name) {
672
+ if (errChannel && !this._isOwnServerChannel(errChannel)) {
646
673
  break;
647
674
  }
648
675
  this._clearSubscribeTimeout();
@@ -655,6 +682,12 @@ var AuraChannel = class {
655
682
  }
656
683
  break;
657
684
  }
685
+ case "channel_recovered": {
686
+ if (!this._isOwnServerChannel(msg.channel)) break;
687
+ if (this._serverState !== "subscribed") break;
688
+ this._emitStatus("SUBSCRIBED");
689
+ break;
690
+ }
658
691
  case "token_refreshed":
659
692
  break;
660
693
  case "broadcast_ack": {
@@ -864,6 +897,9 @@ var RealtimeConnectionError = class _RealtimeConnectionError extends Error {
864
897
  };
865
898
 
866
899
  // src/HeartbeatManager.ts
900
+ function isUnrefable(value) {
901
+ return typeof value === "object" && value !== null && "unref" in value && typeof value.unref === "function";
902
+ }
867
903
  var HeartbeatManager = class _HeartbeatManager {
868
904
  _worker = null;
869
905
  _workerUrl = null;
@@ -934,6 +970,9 @@ var HeartbeatManager = class _HeartbeatManager {
934
970
  this._interval = setInterval(() => {
935
971
  this._sendFn?.();
936
972
  }, intervalMs);
973
+ if (isUnrefable(this._interval)) {
974
+ this._interval.unref();
975
+ }
937
976
  }
938
977
  _clearWatchdog() {
939
978
  if (this._watchdog) {
@@ -1016,6 +1055,9 @@ function getTokenProjectId(token) {
1016
1055
  var HEARTBEAT_INTERVAL_MS = 25e3;
1017
1056
  var MAX_TIMEOUT_MS = 2147483647;
1018
1057
  var LIVENESS_GUARD_INTERVAL_MS = 3e4;
1058
+ function isUnrefable2(value) {
1059
+ return typeof value === "object" && value !== null && "unref" in value && typeof value.unref === "function";
1060
+ }
1019
1061
  var CANAL_QUALIFIE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:/;
1020
1062
  var LIVENESS_MAX_SILENCE_MS = 9e4;
1021
1063
  var RealtimeService = class _RealtimeService {
@@ -1270,6 +1312,9 @@ var RealtimeService = class _RealtimeService {
1270
1312
  `Realtime : aucun message du serveur depuis ${Math.round(silence / 1e3)} s alors qu'il en pousse un toutes les 30 s \u2014 connexion consid\xE9r\xE9e morte. Reconnexion.`
1271
1313
  );
1272
1314
  }, LIVENESS_GUARD_INTERVAL_MS);
1315
+ if (isUnrefable2(this._livenessGuard)) {
1316
+ this._livenessGuard.unref();
1317
+ }
1273
1318
  }
1274
1319
  _stopLivenessGuard() {
1275
1320
  if (this._livenessGuard) {
@@ -1473,6 +1518,17 @@ var RealtimeService = class _RealtimeService {
1473
1518
  * - ou déjà qualifié (`'<project_id>:room-1'`).
1474
1519
  */
1475
1520
  httpBroadcast(channel, event, payload = {}, options) {
1521
+ if (Object.prototype.hasOwnProperty.call(payload, "event")) {
1522
+ return Promise.resolve({
1523
+ data: null,
1524
+ error: new core.AuraError(0, {
1525
+ error: {
1526
+ code: "realtime_payload_event_reserved",
1527
+ 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)."
1528
+ }
1529
+ })
1530
+ });
1531
+ }
1476
1532
  const explicitProjectId = options?.projectId ?? this.client.projectId;
1477
1533
  let prefix = this._projectPrefix();
1478
1534
  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()`).
@@ -339,7 +354,6 @@ declare class RealtimeConnectionError extends Error {
339
354
  * Le chien de garde porte sur le `ready`, PAS sur le premier battement : mesuré
340
355
  * en Chromium réel, le démarrage d'un Worker peut dépasser 1,5 intervalle quand
341
356
  * l'intervalle est court, ce qui faisait abandonner à tort un Worker
342
- * parfaitement sain — et perdre silencieusement l'immunité au throttling, la
343
357
  * seule raison d'être de ce Worker.
344
358
  */
345
359
  declare class HeartbeatManager {
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()`).
@@ -339,7 +354,6 @@ declare class RealtimeConnectionError extends Error {
339
354
  * Le chien de garde porte sur le `ready`, PAS sur le premier battement : mesuré
340
355
  * en Chromium réel, le démarrage d'un Worker peut dépasser 1,5 intervalle quand
341
356
  * l'intervalle est court, ce qui faisait abandonner à tort un Worker
342
- * parfaitement sain — et perdre silencieusement l'immunité au throttling, la
343
357
  * seule raison d'être de ce Worker.
344
358
  */
345
359
  declare class HeartbeatManager {
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,11 +287,31 @@ 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.
292
308
  */
293
309
  on(type, filter, callback) {
310
+ if (type !== "postgres_changes" && type !== "broadcast" && type !== "presence") {
311
+ throw new TypeError(
312
+ `[aurabase] channel.on(): unknown listen type "${String(type)}". For table changes use .on('postgres_changes', { event: 'INSERT', schema: 'public', table: '<table>' }, callback); other types: 'broadcast', 'presence'.`
313
+ );
314
+ }
294
315
  const id = ++this._listenerIdCounter;
295
316
  this._listeners.push({
296
317
  id,
@@ -357,9 +378,11 @@ var AuraChannel = class {
357
378
  * with the full config (postgres_changes, broadcast, presence).
358
379
  *
359
380
  * 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 ;
381
+ * - `SUBSCRIBED` — le serveur a confirmé l'abonnement ; émis de nouveau quand une
382
+ * écoute `postgres_changes` perdue (voir `CHANNEL_ERROR`) est rétablie ;
383
+ * - `CHANNEL_ERROR` — refus serveur (RLS, rate limit…), échec de handshake, coupure
384
+ * inattendue de la connexion, ou écoute `postgres_changes` du projet
385
+ * perdue (l'abonnement est conservé, `SUBSCRIBED` suit sa reprise) ;
363
386
  * - `TIMED_OUT` — aucune réponse du serveur dans `timeoutMs` après l'envoi ;
364
387
  * - `CLOSED` — le canal a été fermé volontairement (`unsubscribe()`,
365
388
  * `removeChannel()`, `removeAllChannels()`, `disconnect()`).
@@ -508,6 +531,10 @@ var AuraChannel = class {
508
531
  */
509
532
  send(message) {
510
533
  return new Promise((resolve) => {
534
+ if (message.payload && Object.prototype.hasOwnProperty.call(message.payload, "event")) {
535
+ resolve("invalid_payload");
536
+ return;
537
+ }
511
538
  if (!this._sendFn) {
512
539
  resolve("not_connected");
513
540
  return;
@@ -640,7 +667,7 @@ var AuraChannel = class {
640
667
  case "error": {
641
668
  const errMsg = msg;
642
669
  const errChannel = errMsg.channel;
643
- if (errChannel && errChannel !== this.fullName && errChannel !== this.name) {
670
+ if (errChannel && !this._isOwnServerChannel(errChannel)) {
644
671
  break;
645
672
  }
646
673
  this._clearSubscribeTimeout();
@@ -653,6 +680,12 @@ var AuraChannel = class {
653
680
  }
654
681
  break;
655
682
  }
683
+ case "channel_recovered": {
684
+ if (!this._isOwnServerChannel(msg.channel)) break;
685
+ if (this._serverState !== "subscribed") break;
686
+ this._emitStatus("SUBSCRIBED");
687
+ break;
688
+ }
656
689
  case "token_refreshed":
657
690
  break;
658
691
  case "broadcast_ack": {
@@ -862,6 +895,9 @@ var RealtimeConnectionError = class _RealtimeConnectionError extends Error {
862
895
  };
863
896
 
864
897
  // src/HeartbeatManager.ts
898
+ function isUnrefable(value) {
899
+ return typeof value === "object" && value !== null && "unref" in value && typeof value.unref === "function";
900
+ }
865
901
  var HeartbeatManager = class _HeartbeatManager {
866
902
  _worker = null;
867
903
  _workerUrl = null;
@@ -932,6 +968,9 @@ var HeartbeatManager = class _HeartbeatManager {
932
968
  this._interval = setInterval(() => {
933
969
  this._sendFn?.();
934
970
  }, intervalMs);
971
+ if (isUnrefable(this._interval)) {
972
+ this._interval.unref();
973
+ }
935
974
  }
936
975
  _clearWatchdog() {
937
976
  if (this._watchdog) {
@@ -1014,6 +1053,9 @@ function getTokenProjectId(token) {
1014
1053
  var HEARTBEAT_INTERVAL_MS = 25e3;
1015
1054
  var MAX_TIMEOUT_MS = 2147483647;
1016
1055
  var LIVENESS_GUARD_INTERVAL_MS = 3e4;
1056
+ function isUnrefable2(value) {
1057
+ return typeof value === "object" && value !== null && "unref" in value && typeof value.unref === "function";
1058
+ }
1017
1059
  var CANAL_QUALIFIE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:/;
1018
1060
  var LIVENESS_MAX_SILENCE_MS = 9e4;
1019
1061
  var RealtimeService = class _RealtimeService {
@@ -1268,6 +1310,9 @@ var RealtimeService = class _RealtimeService {
1268
1310
  `Realtime : aucun message du serveur depuis ${Math.round(silence / 1e3)} s alors qu'il en pousse un toutes les 30 s \u2014 connexion consid\xE9r\xE9e morte. Reconnexion.`
1269
1311
  );
1270
1312
  }, LIVENESS_GUARD_INTERVAL_MS);
1313
+ if (isUnrefable2(this._livenessGuard)) {
1314
+ this._livenessGuard.unref();
1315
+ }
1271
1316
  }
1272
1317
  _stopLivenessGuard() {
1273
1318
  if (this._livenessGuard) {
@@ -1471,6 +1516,17 @@ var RealtimeService = class _RealtimeService {
1471
1516
  * - ou déjà qualifié (`'<project_id>:room-1'`).
1472
1517
  */
1473
1518
  httpBroadcast(channel, event, payload = {}, options) {
1519
+ if (Object.prototype.hasOwnProperty.call(payload, "event")) {
1520
+ return Promise.resolve({
1521
+ data: null,
1522
+ error: new AuraError(0, {
1523
+ error: {
1524
+ code: "realtime_payload_event_reserved",
1525
+ 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)."
1526
+ }
1527
+ })
1528
+ });
1529
+ }
1474
1530
  const explicitProjectId = options?.projectId ?? this.client.projectId;
1475
1531
  let prefix = this._projectPrefix();
1476
1532
  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.2",
4
4
  "description": "Service temps réel du SDK Aurabase (postgres_changes, broadcast, presence)",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -31,12 +31,12 @@
31
31
  "author": "Aurabase",
32
32
  "repository": {
33
33
  "type": "git",
34
- "url": "git+https://github.com/daylami555/aurabase.git",
34
+ "url": "git+https://github.com/Aurabase-cloud/Aurabase.git",
35
35
  "directory": "aurabase-js/packages/realtime"
36
36
  },
37
37
  "homepage": "https://aurabase.cloud",
38
38
  "bugs": {
39
- "url": "https://github.com/daylami555/aurabase/issues"
39
+ "url": "https://github.com/Aurabase-cloud/Aurabase/issues"
40
40
  },
41
41
  "keywords": [
42
42
  "aurabase",
@@ -45,7 +45,7 @@
45
45
  "realtime"
46
46
  ],
47
47
  "dependencies": {
48
- "@aurabase/core": "0.6.0"
48
+ "@aurabase/core": "0.6.2"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "20.14.15",