deepspace 0.17.0 → 0.18.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.js CHANGED
@@ -1786,6 +1786,8 @@ function parseServerError(error) {
1786
1786
  // src/client/storage/record-socket.ts
1787
1787
  var WS_CONNECTING = 0;
1788
1788
  var WS_OPEN = 1;
1789
+ var PING_EVERY_MS = 15e3;
1790
+ var PONG_TIMEOUT_MS = 3e4;
1789
1791
  var RecordSocket = class {
1790
1792
  constructor(config) {
1791
1793
  this.config = config;
@@ -1801,6 +1803,14 @@ var RecordSocket = class {
1801
1803
  pendingRequests = /* @__PURE__ */ new Map();
1802
1804
  reconnectAttempt = 0;
1803
1805
  reconnectTimeout = null;
1806
+ heartbeat = null;
1807
+ /** Any inbound frame proves the peer is answering. */
1808
+ lastInboundAt = 0;
1809
+ /** Monotonic time of the one probe awaiting an inbound answer. */
1810
+ outstandingPingAt = null;
1811
+ /** Monotonic time of the prior heartbeat callback. A long gap means the
1812
+ * scheduler was suspended, so a pre-suspension probe cannot be judged. */
1813
+ lastHeartbeatCheckAt = 0;
1804
1814
  // Bumped by connect() (on entry) and disconnect()/destroy(). connect()
1805
1815
  // captures it before awaiting the auth token and re-checks after; a
1806
1816
  // mismatch means a teardown (or a newer connect) raced in during the
@@ -1849,10 +1859,12 @@ var RecordSocket = class {
1849
1859
  ws.onopen = () => {
1850
1860
  config.log?.("connected", config.roomId);
1851
1861
  config.listeners.onStatus("connected");
1862
+ this.startHeartbeat(ws);
1852
1863
  this.resubscribeAll(ws);
1853
1864
  };
1854
1865
  ws.onmessage = (event) => this.handleMessage(event);
1855
1866
  ws.onclose = () => {
1867
+ this.stopHeartbeat();
1856
1868
  config.log?.("disconnected", config.roomId);
1857
1869
  config.listeners.onStatus("disconnected");
1858
1870
  config.listeners.onReady(false);
@@ -1907,8 +1919,48 @@ var RecordSocket = class {
1907
1919
  this.reconnectTimeout = null;
1908
1920
  }
1909
1921
  }
1922
+ /**
1923
+ * Turn a peer that stopped answering into a `close` event. That is the whole
1924
+ * job — `onclose` already reports 'disconnected', marks every query stale,
1925
+ * and reconnects with backoff. Without this it is never called, because
1926
+ * nothing about the connection is actually broken.
1927
+ */
1928
+ startHeartbeat(ws) {
1929
+ this.stopHeartbeat();
1930
+ this.lastInboundAt = performance.now();
1931
+ this.lastHeartbeatCheckAt = this.lastInboundAt;
1932
+ this.outstandingPingAt = null;
1933
+ this.heartbeat = setInterval(() => {
1934
+ if (ws.readyState !== WS_OPEN) return;
1935
+ const now = performance.now();
1936
+ const callbackGap = now - this.lastHeartbeatCheckAt;
1937
+ this.lastHeartbeatCheckAt = now;
1938
+ if (this.outstandingPingAt !== null) {
1939
+ if (now - this.outstandingPingAt >= PONG_TIMEOUT_MS) {
1940
+ if (callbackGap >= PONG_TIMEOUT_MS) {
1941
+ ws.send("ping");
1942
+ this.outstandingPingAt = now;
1943
+ } else {
1944
+ ws.close();
1945
+ }
1946
+ }
1947
+ return;
1948
+ }
1949
+ if (now - this.lastInboundAt >= PING_EVERY_MS) {
1950
+ ws.send("ping");
1951
+ this.outstandingPingAt = now;
1952
+ }
1953
+ }, PING_EVERY_MS);
1954
+ }
1955
+ stopHeartbeat() {
1956
+ if (this.heartbeat) clearInterval(this.heartbeat);
1957
+ this.heartbeat = null;
1958
+ this.outstandingPingAt = null;
1959
+ this.lastHeartbeatCheckAt = 0;
1960
+ }
1910
1961
  /** Close without firing onclose — prevents the zombie-reconnect. */
1911
1962
  teardownSocket() {
1963
+ this.stopHeartbeat();
1912
1964
  const ws = this.ws;
1913
1965
  if (!ws) return;
1914
1966
  this.config.log?.("closing", this.config.roomId);
@@ -1977,6 +2029,9 @@ var RecordSocket = class {
1977
2029
  }
1978
2030
  }
1979
2031
  handleMessage(event) {
2032
+ this.lastInboundAt = performance.now();
2033
+ this.outstandingPingAt = null;
2034
+ if (event.data === "pong") return;
1980
2035
  if (event.data instanceof ArrayBuffer) {
1981
2036
  this.binaryHandlers.forEach((h) => h(event.data));
1982
2037
  return;