@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 +1 -0
- package/dist/index.cjs +60 -4
- package/dist/index.d.cts +20 -6
- package/dist/index.d.ts +20 -6
- package/dist/index.js +60 -4
- package/package.json +4 -4
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
|
-
*
|
|
364
|
-
*
|
|
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 &&
|
|
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
|
-
*
|
|
162
|
-
*
|
|
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
|
-
*
|
|
162
|
-
*
|
|
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
|
-
*
|
|
362
|
-
*
|
|
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 &&
|
|
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.
|
|
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/
|
|
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/
|
|
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.
|
|
48
|
+
"@aurabase/core": "0.6.2"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "20.14.15",
|