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 +27 -2
- package/dist/observe.js +27 -2
- package/dist/reporters.d.ts +2 -0
- package/package.json +1 -1
- package/src/reporters.ts +24 -1
- package/src/wrap-client.ts +13 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/reporters.d.ts
CHANGED
|
@@ -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.
|
|
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;
|
|
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
|
},
|
package/src/wrap-client.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
/**
|