hrpc-inspector 0.1.2 → 0.1.3

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
@@ -28,9 +28,12 @@ function createWebSocketReporter(url, opts = {}) {
28
28
  const maxQueue = opts.maxQueue ?? 1e3;
29
29
  const reconnectMs = opts.reconnectMs ?? 3e3;
30
30
  const OPEN = 1;
31
+ const maxReconnectMs = opts.maxReconnectMs ?? 3e4;
31
32
  let ws = null;
32
33
  let queue = [];
33
34
  let closed = false;
35
+ let retryTimer = null;
36
+ let backoffMs = reconnectMs;
34
37
  function enqueue(ev) {
35
38
  queue.push(ev);
36
39
  if (queue.length > maxQueue) queue.shift();
@@ -42,12 +45,23 @@ function createWebSocketReporter(url, opts = {}) {
42
45
  enqueue(ev);
43
46
  }
44
47
  }
48
+ function scheduleRetry() {
49
+ if (closed || retryTimer) return;
50
+ retryTimer = setTimeout(() => {
51
+ retryTimer = null;
52
+ ensure();
53
+ }, backoffMs);
54
+ backoffMs = Math.min(backoffMs * 2, maxReconnectMs);
55
+ if (typeof retryTimer?.unref === "function") retryTimer.unref();
56
+ }
45
57
  function ensure() {
46
58
  if (closed || !WS) return;
47
59
  if (ws && (ws.readyState === 0 || ws.readyState === 1)) return;
60
+ if (retryTimer) return;
48
61
  try {
49
62
  ws = new WS(url);
50
63
  ws.onopen = () => {
64
+ backoffMs = reconnectMs;
51
65
  if (opts.announce !== void 0) rawSend({ __source: opts.announce });
52
66
  const q = queue;
53
67
  queue = [];
@@ -55,7 +69,7 @@ function createWebSocketReporter(url, opts = {}) {
55
69
  };
56
70
  ws.onclose = () => {
57
71
  ws = null;
58
- if (!closed) setTimeout(ensure, reconnectMs);
72
+ scheduleRetry();
59
73
  };
60
74
  ws.onerror = () => {
61
75
  };
@@ -67,6 +81,7 @@ function createWebSocketReporter(url, opts = {}) {
67
81
  };
68
82
  } catch {
69
83
  ws = null;
84
+ scheduleRetry();
70
85
  }
71
86
  }
72
87
  return {
@@ -80,6 +95,10 @@ function createWebSocketReporter(url, opts = {}) {
80
95
  },
81
96
  close() {
82
97
  closed = true;
98
+ if (retryTimer) {
99
+ clearTimeout(retryTimer);
100
+ retryTimer = null;
101
+ }
83
102
  try {
84
103
  ws?.close();
85
104
  } catch {
@@ -137,7 +156,7 @@ function monitorStream(report, endpoint, corrId, stream, t0, preview, tag) {
137
156
  }
138
157
  }
139
158
  function wrapFn(report, endpoint, fn, self, preview, monitorStreams, onCall) {
140
- return function observed(...args) {
159
+ const wrapped = function observed(...args) {
141
160
  const corrId = "rpc-" + ++seq;
142
161
  let tag = {};
143
162
  if (invokeCtx && invokeCtx.method === endpoint) {
@@ -171,6 +190,12 @@ function wrapFn(report, endpoint, fn, self, preview, monitorStreams, onCall) {
171
190
  }
172
191
  return result;
173
192
  };
193
+ try {
194
+ Object.defineProperty(wrapped, "name", { value: fn.name, configurable: true });
195
+ Object.defineProperty(wrapped, "length", { value: fn.length, configurable: true });
196
+ } catch {
197
+ }
198
+ return wrapped;
174
199
  }
175
200
  function wrapClient(client, report, opts = {}) {
176
201
  if (!client || typeof client !== "object") return client;
package/dist/observe.js CHANGED
@@ -28,9 +28,12 @@ function createWebSocketReporter(url, opts = {}) {
28
28
  const maxQueue = opts.maxQueue ?? 1e3;
29
29
  const reconnectMs = opts.reconnectMs ?? 3e3;
30
30
  const OPEN = 1;
31
+ const maxReconnectMs = opts.maxReconnectMs ?? 3e4;
31
32
  let ws = null;
32
33
  let queue = [];
33
34
  let closed = false;
35
+ let retryTimer = null;
36
+ let backoffMs = reconnectMs;
34
37
  function enqueue(ev) {
35
38
  queue.push(ev);
36
39
  if (queue.length > maxQueue) queue.shift();
@@ -42,12 +45,23 @@ function createWebSocketReporter(url, opts = {}) {
42
45
  enqueue(ev);
43
46
  }
44
47
  }
48
+ function scheduleRetry() {
49
+ if (closed || retryTimer) return;
50
+ retryTimer = setTimeout(() => {
51
+ retryTimer = null;
52
+ ensure();
53
+ }, backoffMs);
54
+ backoffMs = Math.min(backoffMs * 2, maxReconnectMs);
55
+ if (typeof retryTimer?.unref === "function") retryTimer.unref();
56
+ }
45
57
  function ensure() {
46
58
  if (closed || !WS) return;
47
59
  if (ws && (ws.readyState === 0 || ws.readyState === 1)) return;
60
+ if (retryTimer) return;
48
61
  try {
49
62
  ws = new WS(url);
50
63
  ws.onopen = () => {
64
+ backoffMs = reconnectMs;
51
65
  if (opts.announce !== void 0) rawSend({ __source: opts.announce });
52
66
  const q = queue;
53
67
  queue = [];
@@ -55,7 +69,7 @@ function createWebSocketReporter(url, opts = {}) {
55
69
  };
56
70
  ws.onclose = () => {
57
71
  ws = null;
58
- if (!closed) setTimeout(ensure, reconnectMs);
72
+ scheduleRetry();
59
73
  };
60
74
  ws.onerror = () => {
61
75
  };
@@ -67,6 +81,7 @@ function createWebSocketReporter(url, opts = {}) {
67
81
  };
68
82
  } catch {
69
83
  ws = null;
84
+ scheduleRetry();
70
85
  }
71
86
  }
72
87
  return {
@@ -80,6 +95,10 @@ function createWebSocketReporter(url, opts = {}) {
80
95
  },
81
96
  close() {
82
97
  closed = true;
98
+ if (retryTimer) {
99
+ clearTimeout(retryTimer);
100
+ retryTimer = null;
101
+ }
83
102
  try {
84
103
  ws?.close();
85
104
  } catch {
@@ -137,7 +156,7 @@ function monitorStream(report, endpoint, corrId, stream, t0, preview, tag) {
137
156
  }
138
157
  }
139
158
  function wrapFn(report, endpoint, fn, self, preview, monitorStreams, onCall) {
140
- return function observed(...args) {
159
+ const wrapped = function observed(...args) {
141
160
  const corrId = "rpc-" + ++seq;
142
161
  let tag = {};
143
162
  if (invokeCtx && invokeCtx.method === endpoint) {
@@ -171,6 +190,12 @@ function wrapFn(report, endpoint, fn, self, preview, monitorStreams, onCall) {
171
190
  }
172
191
  return result;
173
192
  };
193
+ try {
194
+ Object.defineProperty(wrapped, "name", { value: fn.name, configurable: true });
195
+ Object.defineProperty(wrapped, "length", { value: fn.length, configurable: true });
196
+ } catch {
197
+ }
198
+ return wrapped;
174
199
  }
175
200
  function wrapClient(client, report, opts = {}) {
176
201
  if (!client || typeof client !== "object") return client;
@@ -17,6 +17,8 @@ export interface WebSocketReporterOptions {
17
17
  * replay/invoke path); leaving it undefined keeps the reporter strictly send-only, as before.
18
18
  */
19
19
  onMessage?: (msg: any) => void;
20
+ /** Ceiling for the reconnect backoff, in ms. Default 30000. */
21
+ maxReconnectMs?: number;
20
22
  }
21
23
  /**
22
24
  * Send each event as one JSON WebSocket message to `url` (e.g. ws://127.0.0.1:9420/ws).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hrpc-inspector",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "hrpc based debugging and observability for Bare. Tap an RPC client or a raw transport and read endpoint, request, response, latency and live subscription streams in a local GUI, or merge multi-peer timelines over a Hyperswarm hub.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/reporters.ts CHANGED
@@ -25,6 +25,8 @@ export interface WebSocketReporterOptions {
25
25
  * replay/invoke path); leaving it undefined keeps the reporter strictly send-only, as before.
26
26
  */
27
27
  onMessage?: (msg: any) => void;
28
+ /** Ceiling for the reconnect backoff, in ms. Default 30000. */
29
+ maxReconnectMs?: number;
28
30
  }
29
31
 
30
32
  /**
@@ -37,9 +39,17 @@ export function createWebSocketReporter(url: string, opts: WebSocketReporterOpti
37
39
  const reconnectMs = opts.reconnectMs ?? 3000;
38
40
  const OPEN = 1;
39
41
 
42
+ const maxReconnectMs = opts.maxReconnectMs ?? 30000;
43
+
40
44
  let ws: any = null;
41
45
  let queue: unknown[] = [];
42
46
  let closed = false;
47
+ // A reconnect already scheduled. Without this, export() — which runs on every flush, i.e. every
48
+ // ~200ms — would open a fresh socket the moment the previous one failed, defeating the backoff
49
+ // entirely. A browser logs EVERY refused WebSocket connect to the console itself, before any
50
+ // onerror handler runs, so that turned "no GUI running" into a console flooded at 5 errors/sec.
51
+ let retryTimer: any = null;
52
+ let backoffMs = reconnectMs;
43
53
 
44
54
  function enqueue(ev: unknown) {
45
55
  queue.push(ev);
@@ -48,21 +58,33 @@ export function createWebSocketReporter(url: string, opts: WebSocketReporterOpti
48
58
  function rawSend(ev: unknown) {
49
59
  try { ws.send(JSON.stringify(ev)); } catch { enqueue(ev); }
50
60
  }
61
+ function scheduleRetry() {
62
+ if (closed || retryTimer) return;
63
+ retryTimer = setTimeout(() => { retryTimer = null; ensure(); }, backoffMs);
64
+ // Widen the gap each time the viewer stays absent, so a dev session with no GUI settles at one
65
+ // attempt per maxReconnectMs instead of one per flush. Reset to `reconnectMs` on a real open.
66
+ backoffMs = Math.min(backoffMs * 2, maxReconnectMs);
67
+ if (typeof retryTimer?.unref === 'function') retryTimer.unref();
68
+ }
69
+
51
70
  function ensure() {
52
71
  if (closed || !WS) return;
53
72
  if (ws && (ws.readyState === 0 || ws.readyState === 1)) return;
73
+ if (retryTimer) return; // a reconnect is already pending — do not stack another socket on top
54
74
  try {
55
75
  ws = new WS(url);
56
76
  ws.onopen = () => {
77
+ backoffMs = reconnectMs; // the viewer is back; retry promptly if it goes away again
57
78
  if (opts.announce !== undefined) rawSend({ __source: opts.announce }); // identify this device first
58
79
  const q = queue; queue = []; for (const e of q) rawSend(e);
59
80
  };
60
- ws.onclose = () => { ws = null; if (!closed) setTimeout(ensure, reconnectMs); };
81
+ ws.onclose = () => { ws = null; scheduleRetry(); };
61
82
  ws.onerror = () => {};
62
83
  // Bidirectional only when a handler was supplied (dev/replay path). Never throws inward.
63
84
  if (opts.onMessage) ws.onmessage = (m: any) => { try { opts.onMessage!(JSON.parse(m.data)); } catch { /* ignore malformed inbound */ } };
64
85
  } catch {
65
86
  ws = null;
87
+ scheduleRetry();
66
88
  }
67
89
  }
68
90
 
@@ -77,6 +99,7 @@ export function createWebSocketReporter(url: string, opts: WebSocketReporterOpti
77
99
  },
78
100
  close() {
79
101
  closed = true;
102
+ if (retryTimer) { clearTimeout(retryTimer); retryTimer = null; }
80
103
  try { ws?.close(); } catch {}
81
104
  ws = null;
82
105
  },
@@ -101,7 +101,7 @@ function monitorStream(report: Report, endpoint: string, corrId: string, stream:
101
101
  }
102
102
 
103
103
  function wrapFn(report: Report, endpoint: string, fn: any, self: any, preview: (v: unknown) => unknown, monitorStreams: boolean, onCall?: (corrId: string, method: string, args: unknown[]) => void): any {
104
- return function observed(this: unknown, ...args: unknown[]) {
104
+ const wrapped = function observed(this: unknown, ...args: unknown[]) {
105
105
  const corrId = 'rpc-' + ++seq;
106
106
  // Consume the invoke tag ONCE, and only for the exact method being replayed.
107
107
  let tag: Record<string, unknown> = {};
@@ -131,6 +131,18 @@ function wrapFn(report: Report, endpoint: string, fn: any, self: any, preview: (
131
131
  }
132
132
  return result;
133
133
  };
134
+
135
+ // Carry the original identity across. An app may read `fn.name` — keet's own System Log does
136
+ // (`method: apiFn.name`), and every entry showed "observed" instead of the endpoint. `length`
137
+ // goes too, since arity is the other thing reflection commonly checks. Both are configurable
138
+ // on a function, so this cannot throw; guarded anyway, because the tap must never break a client.
139
+ try {
140
+ Object.defineProperty(wrapped, 'name', { value: fn.name, configurable: true });
141
+ Object.defineProperty(wrapped, 'length', { value: fn.length, configurable: true });
142
+ } catch {
143
+ /* identity is a nicety; never fail the wrap over it */
144
+ }
145
+ return wrapped;
134
146
  }
135
147
 
136
148
  /**