@aurabase/realtime 0.6.1 → 0.7.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.
package/dist/index.cjs CHANGED
@@ -309,6 +309,11 @@ var AuraChannel = class {
309
309
  * Retourne une fonction de désabonnement direct `() => void` qui proxy également l'instance du canal pour le chaînage fluide.
310
310
  */
311
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
+ }
312
317
  const id = ++this._listenerIdCounter;
313
318
  this._listeners.push({
314
319
  id,
@@ -892,6 +897,9 @@ var RealtimeConnectionError = class _RealtimeConnectionError extends Error {
892
897
  };
893
898
 
894
899
  // src/HeartbeatManager.ts
900
+ function isUnrefable(value) {
901
+ return typeof value === "object" && value !== null && "unref" in value && typeof value.unref === "function";
902
+ }
895
903
  var HeartbeatManager = class _HeartbeatManager {
896
904
  _worker = null;
897
905
  _workerUrl = null;
@@ -962,6 +970,9 @@ var HeartbeatManager = class _HeartbeatManager {
962
970
  this._interval = setInterval(() => {
963
971
  this._sendFn?.();
964
972
  }, intervalMs);
973
+ if (isUnrefable(this._interval)) {
974
+ this._interval.unref();
975
+ }
965
976
  }
966
977
  _clearWatchdog() {
967
978
  if (this._watchdog) {
@@ -1044,6 +1055,9 @@ function getTokenProjectId(token) {
1044
1055
  var HEARTBEAT_INTERVAL_MS = 25e3;
1045
1056
  var MAX_TIMEOUT_MS = 2147483647;
1046
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
+ }
1047
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}:/;
1048
1062
  var LIVENESS_MAX_SILENCE_MS = 9e4;
1049
1063
  var RealtimeService = class _RealtimeService {
@@ -1243,11 +1257,11 @@ var RealtimeService = class _RealtimeService {
1243
1257
  this._emitError(
1244
1258
  opened ? new RealtimeConnectionError(
1245
1259
  "connection",
1246
- "Realtime : connexion WebSocket ferm\xE9e de fa\xE7on inattendue.",
1260
+ "Realtime: WebSocket connection closed unexpectedly.",
1247
1261
  { code: evt?.code, reason: evt?.reason }
1248
1262
  ) : new RealtimeConnectionError(
1249
1263
  "handshake",
1250
- "Realtime : \xE9chec du handshake WebSocket (ferm\xE9 avant ouverture). Causes possibles, par ordre de fr\xE9quence : authentification refus\xE9e (HTTP 401/403 \u2014 token JWT absent, expir\xE9 ou invalide, apikey rejet\xE9e), quota de connexions atteint (HTTP 429), ou serveur injoignable / en red\xE9marrage / bloqu\xE9 par un proxy ou TLS. La sp\xE9cification WebSocket n'expose pas le statut HTTP du handshake au client : le SDK ne peut pas trancher entre ces causes.",
1264
+ "Realtime: WebSocket handshake failed (closed before opening). Possible causes, in order of likelihood: authentication refused (HTTP 401/403: JWT missing, expired or invalid, apikey rejected), connection quota reached (HTTP 429), or server unreachable / restarting / blocked by a proxy or TLS. The WebSocket specification does not expose the handshake HTTP status to the client: the SDK cannot tell these causes apart.",
1251
1265
  { code: evt?.code ?? 1006, reason: evt?.reason }
1252
1266
  )
1253
1267
  );
@@ -1295,9 +1309,12 @@ var RealtimeService = class _RealtimeService {
1295
1309
  const silence = Date.now() - this._lastServerMessageAt;
1296
1310
  if (silence < LIVENESS_MAX_SILENCE_MS) return;
1297
1311
  this._handleDeadConnection(
1298
- `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.`
1312
+ `Realtime: no message from the server for ${Math.round(silence / 1e3)} s although it pushes one every 30 s: the connection is considered dead. Reconnecting.`
1299
1313
  );
1300
1314
  }, LIVENESS_GUARD_INTERVAL_MS);
1315
+ if (isUnrefable2(this._livenessGuard)) {
1316
+ this._livenessGuard.unref();
1317
+ }
1301
1318
  }
1302
1319
  _stopLivenessGuard() {
1303
1320
  if (this._livenessGuard) {
@@ -1347,13 +1364,13 @@ var RealtimeService = class _RealtimeService {
1347
1364
  if (ws !== this._ws) return;
1348
1365
  if (ws.readyState !== WebSocket.OPEN) {
1349
1366
  this._handleDeadConnection(
1350
- "Realtime : la connexion WebSocket n\u2019est plus ouverte mais aucun \xE9v\xE9nement de fermeture n\u2019est arriv\xE9 (socket fig\xE9). Reconnexion."
1367
+ "Realtime: the WebSocket connection is no longer open but no close event arrived (stuck socket). Reconnecting."
1351
1368
  );
1352
1369
  return;
1353
1370
  }
1354
1371
  if (this._awaitingPong) {
1355
1372
  this._handleDeadConnection(
1356
- `Realtime : connexion inactive \u2014 aucune r\xE9ponse du serveur dans les ${HEARTBEAT_INTERVAL_MS} ms suivant un ping (connexion probablement morte). Reconnexion.`
1373
+ `Realtime: idle connection, no server response within ${HEARTBEAT_INTERVAL_MS} ms after a ping (connection probably dead). Reconnecting.`
1357
1374
  );
1358
1375
  return;
1359
1376
  }
@@ -1507,7 +1524,7 @@ var RealtimeService = class _RealtimeService {
1507
1524
  error: new core.AuraError(0, {
1508
1525
  error: {
1509
1526
  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)."
1527
+ message: "httpBroadcast: the payload contains an `event` key, which is reserved for the event name (it would overwrite it for subscribers). Rename it (`eventName`, `kind`\u2026)."
1511
1528
  }
1512
1529
  })
1513
1530
  });
@@ -1528,7 +1545,7 @@ var RealtimeService = class _RealtimeService {
1528
1545
  error: new core.AuraError(0, {
1529
1546
  error: {
1530
1547
  code: "realtime_channel_non_qualifie",
1531
- message: `httpBroadcast: le canal "${channel}" n'est pas qualifi\xE9 et aucune session n'est ouverte \u2014 le SDK n'a donc aucun \`project_id\` \xE0 lui pr\xE9fixer, alors que le serveur l'EXIGE (403 \xAB Le canal n'appartient pas \xE0 ce projet \xBB). Passez le canal sous la forme "<project_id>:` + channel + `" ou sp\xE9cifiez l'option { projectId: "<uuid>" } \u2014 c'est la forme attendue c\xF4t\xE9 serveur/edge, o\xF9 il n'y a pas de session utilisateur.`
1548
+ message: `httpBroadcast: the channel "${channel}" is not qualified and no session is open, so the SDK has no \`project_id\` to prefix it with, while the server REQUIRES it (403 "The channel does not belong to this project"). Pass the channel as "<project_id>:` + channel + '" or specify the option { projectId: "<uuid>" }, which is the expected form server-side/edge, where there is no user session.'
1532
1549
  }
1533
1550
  })
1534
1551
  });
@@ -1694,7 +1711,7 @@ var RealtimeService = class _RealtimeService {
1694
1711
  if (msg.type === "error") {
1695
1712
  if (typeof console !== "undefined") {
1696
1713
  console.warn(
1697
- "[aura-realtime] Erreur serveur pour un canal inconnu de ce client \u2014 ignor\xE9e (aucun canal local concern\xE9) :",
1714
+ "[aura-realtime] Server error for a channel unknown to this client: ignored (no local channel concerned):",
1698
1715
  { channel: channelFull, message: msg.message }
1699
1716
  );
1700
1717
  }
@@ -1731,6 +1748,11 @@ var RealtimeService = class _RealtimeService {
1731
1748
  for (const ch of this._channels.values()) {
1732
1749
  ch._onMessage(msg);
1733
1750
  }
1751
+ const text = msg.message ?? "";
1752
+ if (text.startsWith("SESSION_REVOKED") && !this._customAuthToken) {
1753
+ this._reconnectAttempts = this._maxReconnectAttempts;
1754
+ this.client.authStore.clear();
1755
+ }
1734
1756
  }
1735
1757
  }
1736
1758
  };
package/dist/index.d.cts CHANGED
@@ -354,7 +354,6 @@ declare class RealtimeConnectionError extends Error {
354
354
  * Le chien de garde porte sur le `ready`, PAS sur le premier battement : mesuré
355
355
  * en Chromium réel, le démarrage d'un Worker peut dépasser 1,5 intervalle quand
356
356
  * l'intervalle est court, ce qui faisait abandonner à tort un Worker
357
- * parfaitement sain — et perdre silencieusement l'immunité au throttling, la
358
357
  * seule raison d'être de ce Worker.
359
358
  */
360
359
  declare class HeartbeatManager {
package/dist/index.d.ts CHANGED
@@ -354,7 +354,6 @@ declare class RealtimeConnectionError extends Error {
354
354
  * Le chien de garde porte sur le `ready`, PAS sur le premier battement : mesuré
355
355
  * en Chromium réel, le démarrage d'un Worker peut dépasser 1,5 intervalle quand
356
356
  * l'intervalle est court, ce qui faisait abandonner à tort un Worker
357
- * parfaitement sain — et perdre silencieusement l'immunité au throttling, la
358
357
  * seule raison d'être de ce Worker.
359
358
  */
360
359
  declare class HeartbeatManager {
package/dist/index.js CHANGED
@@ -307,6 +307,11 @@ var AuraChannel = class {
307
307
  * Retourne une fonction de désabonnement direct `() => void` qui proxy également l'instance du canal pour le chaînage fluide.
308
308
  */
309
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
+ }
310
315
  const id = ++this._listenerIdCounter;
311
316
  this._listeners.push({
312
317
  id,
@@ -890,6 +895,9 @@ var RealtimeConnectionError = class _RealtimeConnectionError extends Error {
890
895
  };
891
896
 
892
897
  // src/HeartbeatManager.ts
898
+ function isUnrefable(value) {
899
+ return typeof value === "object" && value !== null && "unref" in value && typeof value.unref === "function";
900
+ }
893
901
  var HeartbeatManager = class _HeartbeatManager {
894
902
  _worker = null;
895
903
  _workerUrl = null;
@@ -960,6 +968,9 @@ var HeartbeatManager = class _HeartbeatManager {
960
968
  this._interval = setInterval(() => {
961
969
  this._sendFn?.();
962
970
  }, intervalMs);
971
+ if (isUnrefable(this._interval)) {
972
+ this._interval.unref();
973
+ }
963
974
  }
964
975
  _clearWatchdog() {
965
976
  if (this._watchdog) {
@@ -1042,6 +1053,9 @@ function getTokenProjectId(token) {
1042
1053
  var HEARTBEAT_INTERVAL_MS = 25e3;
1043
1054
  var MAX_TIMEOUT_MS = 2147483647;
1044
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
+ }
1045
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}:/;
1046
1060
  var LIVENESS_MAX_SILENCE_MS = 9e4;
1047
1061
  var RealtimeService = class _RealtimeService {
@@ -1241,11 +1255,11 @@ var RealtimeService = class _RealtimeService {
1241
1255
  this._emitError(
1242
1256
  opened ? new RealtimeConnectionError(
1243
1257
  "connection",
1244
- "Realtime : connexion WebSocket ferm\xE9e de fa\xE7on inattendue.",
1258
+ "Realtime: WebSocket connection closed unexpectedly.",
1245
1259
  { code: evt?.code, reason: evt?.reason }
1246
1260
  ) : new RealtimeConnectionError(
1247
1261
  "handshake",
1248
- "Realtime : \xE9chec du handshake WebSocket (ferm\xE9 avant ouverture). Causes possibles, par ordre de fr\xE9quence : authentification refus\xE9e (HTTP 401/403 \u2014 token JWT absent, expir\xE9 ou invalide, apikey rejet\xE9e), quota de connexions atteint (HTTP 429), ou serveur injoignable / en red\xE9marrage / bloqu\xE9 par un proxy ou TLS. La sp\xE9cification WebSocket n'expose pas le statut HTTP du handshake au client : le SDK ne peut pas trancher entre ces causes.",
1262
+ "Realtime: WebSocket handshake failed (closed before opening). Possible causes, in order of likelihood: authentication refused (HTTP 401/403: JWT missing, expired or invalid, apikey rejected), connection quota reached (HTTP 429), or server unreachable / restarting / blocked by a proxy or TLS. The WebSocket specification does not expose the handshake HTTP status to the client: the SDK cannot tell these causes apart.",
1249
1263
  { code: evt?.code ?? 1006, reason: evt?.reason }
1250
1264
  )
1251
1265
  );
@@ -1293,9 +1307,12 @@ var RealtimeService = class _RealtimeService {
1293
1307
  const silence = Date.now() - this._lastServerMessageAt;
1294
1308
  if (silence < LIVENESS_MAX_SILENCE_MS) return;
1295
1309
  this._handleDeadConnection(
1296
- `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.`
1310
+ `Realtime: no message from the server for ${Math.round(silence / 1e3)} s although it pushes one every 30 s: the connection is considered dead. Reconnecting.`
1297
1311
  );
1298
1312
  }, LIVENESS_GUARD_INTERVAL_MS);
1313
+ if (isUnrefable2(this._livenessGuard)) {
1314
+ this._livenessGuard.unref();
1315
+ }
1299
1316
  }
1300
1317
  _stopLivenessGuard() {
1301
1318
  if (this._livenessGuard) {
@@ -1345,13 +1362,13 @@ var RealtimeService = class _RealtimeService {
1345
1362
  if (ws !== this._ws) return;
1346
1363
  if (ws.readyState !== WebSocket.OPEN) {
1347
1364
  this._handleDeadConnection(
1348
- "Realtime : la connexion WebSocket n\u2019est plus ouverte mais aucun \xE9v\xE9nement de fermeture n\u2019est arriv\xE9 (socket fig\xE9). Reconnexion."
1365
+ "Realtime: the WebSocket connection is no longer open but no close event arrived (stuck socket). Reconnecting."
1349
1366
  );
1350
1367
  return;
1351
1368
  }
1352
1369
  if (this._awaitingPong) {
1353
1370
  this._handleDeadConnection(
1354
- `Realtime : connexion inactive \u2014 aucune r\xE9ponse du serveur dans les ${HEARTBEAT_INTERVAL_MS} ms suivant un ping (connexion probablement morte). Reconnexion.`
1371
+ `Realtime: idle connection, no server response within ${HEARTBEAT_INTERVAL_MS} ms after a ping (connection probably dead). Reconnecting.`
1355
1372
  );
1356
1373
  return;
1357
1374
  }
@@ -1505,7 +1522,7 @@ var RealtimeService = class _RealtimeService {
1505
1522
  error: new AuraError(0, {
1506
1523
  error: {
1507
1524
  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)."
1525
+ message: "httpBroadcast: the payload contains an `event` key, which is reserved for the event name (it would overwrite it for subscribers). Rename it (`eventName`, `kind`\u2026)."
1509
1526
  }
1510
1527
  })
1511
1528
  });
@@ -1526,7 +1543,7 @@ var RealtimeService = class _RealtimeService {
1526
1543
  error: new AuraError(0, {
1527
1544
  error: {
1528
1545
  code: "realtime_channel_non_qualifie",
1529
- message: `httpBroadcast: le canal "${channel}" n'est pas qualifi\xE9 et aucune session n'est ouverte \u2014 le SDK n'a donc aucun \`project_id\` \xE0 lui pr\xE9fixer, alors que le serveur l'EXIGE (403 \xAB Le canal n'appartient pas \xE0 ce projet \xBB). Passez le canal sous la forme "<project_id>:` + channel + `" ou sp\xE9cifiez l'option { projectId: "<uuid>" } \u2014 c'est la forme attendue c\xF4t\xE9 serveur/edge, o\xF9 il n'y a pas de session utilisateur.`
1546
+ message: `httpBroadcast: the channel "${channel}" is not qualified and no session is open, so the SDK has no \`project_id\` to prefix it with, while the server REQUIRES it (403 "The channel does not belong to this project"). Pass the channel as "<project_id>:` + channel + '" or specify the option { projectId: "<uuid>" }, which is the expected form server-side/edge, where there is no user session.'
1530
1547
  }
1531
1548
  })
1532
1549
  });
@@ -1692,7 +1709,7 @@ var RealtimeService = class _RealtimeService {
1692
1709
  if (msg.type === "error") {
1693
1710
  if (typeof console !== "undefined") {
1694
1711
  console.warn(
1695
- "[aura-realtime] Erreur serveur pour un canal inconnu de ce client \u2014 ignor\xE9e (aucun canal local concern\xE9) :",
1712
+ "[aura-realtime] Server error for a channel unknown to this client: ignored (no local channel concerned):",
1696
1713
  { channel: channelFull, message: msg.message }
1697
1714
  );
1698
1715
  }
@@ -1729,6 +1746,11 @@ var RealtimeService = class _RealtimeService {
1729
1746
  for (const ch of this._channels.values()) {
1730
1747
  ch._onMessage(msg);
1731
1748
  }
1749
+ const text = msg.message ?? "";
1750
+ if (text.startsWith("SESSION_REVOKED") && !this._customAuthToken) {
1751
+ this._reconnectAttempts = this._maxReconnectAttempts;
1752
+ this.client.authStore.clear();
1753
+ }
1732
1754
  }
1733
1755
  }
1734
1756
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurabase/realtime",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
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.1"
48
+ "@aurabase/core": "0.7.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "20.14.15",