@scp-dev/hub 0.3.0 → 0.4.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.d.ts +28 -3
- package/dist/index.js +32 -3
- package/package.json +2 -2
- package/src/federation.ts +8 -1
- package/src/hub.ts +40 -2
- package/src/index.ts +2 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _scp_dev_core from '@scp-dev/core';
|
|
2
|
-
import { Envelope, RelayCore, CoreOptions, EndpointRow, PolicySubject, ScopeSubject, ScopeNode, ScopeDirection, Admission, MessageRow, EndpointView, ScopeCap, ScopeEntry, Claim, SendParams, SendResult, EndpointKind, Presence, StatusState, Verdict, JudgeModelConfig, JudgeClient, Sql, JudgePolicy, CrossAccountLink, XDelivered, XListed, ActivityRow, XRelease } from '@scp-dev/core';
|
|
2
|
+
import { Envelope, RelayCore, CoreOptions, EndpointRow, PolicySubject, ScopeSubject, ScopeNode, ScopeDirection, Admission, MessageRow, EndpointView, ScopeCap, ScopeEntry, Claim, SendParams, SendResult, EndpointKind, Presence, StatusState, Verdict, JudgeModelConfig, JudgeClient, Sql, JudgePolicy, Wake, CrossAccountLink, XDelivered, XListed, ActivityRow, XRelease } from '@scp-dev/core';
|
|
3
3
|
|
|
4
4
|
type RelayAuth = Envelope["from"]["auth"];
|
|
5
5
|
/** a machine name: one relay host of an account */
|
|
@@ -59,6 +59,8 @@ interface MirrorView extends MirrorScope {
|
|
|
59
59
|
presence?: Presence;
|
|
60
60
|
last_seen?: string | null;
|
|
61
61
|
title?: string | null;
|
|
62
|
+
/** an agent's {@link Wake}; a value this hub does not know is kept as none */
|
|
63
|
+
wake?: string | null;
|
|
62
64
|
/** the handle the endpoint had before a rename: its mirror is renamed in place */
|
|
63
65
|
was?: string;
|
|
64
66
|
}
|
|
@@ -567,7 +569,7 @@ interface GraphNode {
|
|
|
567
569
|
status: EndpointView["status"];
|
|
568
570
|
last_seen: string | null;
|
|
569
571
|
parent: string | null;
|
|
570
|
-
wake?:
|
|
572
|
+
wake?: Wake | null;
|
|
571
573
|
}
|
|
572
574
|
/** `spawn`: parent to child; `message`: sender to recipient, with the count and newest send in the graph's window */
|
|
573
575
|
type GraphEdge = {
|
|
@@ -591,6 +593,26 @@ interface HubGraph {
|
|
|
591
593
|
/** the default and widest window of message edges; the core keeps an hour of sender and recipient pairs */
|
|
592
594
|
declare const GRAPH_MINUTES = 15;
|
|
593
595
|
declare const GRAPH_MINUTES_MAX = 60;
|
|
596
|
+
/** what a read-only dashboard stream sees as it happens: addresses, kinds and presence, never a body, note, summary or label */
|
|
597
|
+
type DashboardEvent = {
|
|
598
|
+
type: "presence";
|
|
599
|
+
at: string;
|
|
600
|
+
machine: string | null;
|
|
601
|
+
endpoint: string;
|
|
602
|
+
presence: Presence;
|
|
603
|
+
} | {
|
|
604
|
+
type: "message";
|
|
605
|
+
at: string;
|
|
606
|
+
kind: string;
|
|
607
|
+
from: string;
|
|
608
|
+
to: string[];
|
|
609
|
+
} | {
|
|
610
|
+
type: "endpoints";
|
|
611
|
+
at: string;
|
|
612
|
+
machine: string;
|
|
613
|
+
};
|
|
614
|
+
/** a watcher's fleet event as a dashboard sees it, field by field; null for the kinds a dashboard does not stream */
|
|
615
|
+
declare function dashboardEvent(event: Record<string, unknown>, at: string): DashboardEvent | null;
|
|
594
616
|
interface HubSocket {
|
|
595
617
|
conn: HubConn;
|
|
596
618
|
send(text: string): void;
|
|
@@ -609,6 +631,8 @@ interface HubDeps {
|
|
|
609
631
|
save(socket: HubSocket): void;
|
|
610
632
|
/** structured log line, e.g. relay_action; never bodies or tokens */
|
|
611
633
|
log?: (event: string, fields: Record<string, unknown>) => void;
|
|
634
|
+
/** live presence, endpoint and message metadata for a read-only dashboard; never a body */
|
|
635
|
+
dashboard?: (event: DashboardEvent) => void;
|
|
612
636
|
/** overrides of {@link HUB_LIMITS}; `Infinity` disables a cap, count or byte limit, durations stay finite */
|
|
613
637
|
limits?: Partial<HubLimits>;
|
|
614
638
|
/** the link to other accounts' relays; without it an address of another account is not found */
|
|
@@ -791,6 +815,7 @@ declare class RelayHub {
|
|
|
791
815
|
/** the machines of other members than the grant's, which a bridge shows under the account handle */
|
|
792
816
|
private othersOf;
|
|
793
817
|
private fleet;
|
|
818
|
+
private dash;
|
|
794
819
|
private write;
|
|
795
820
|
private save;
|
|
796
821
|
private waitMs;
|
|
@@ -840,4 +865,4 @@ declare class RelayHub {
|
|
|
840
865
|
xnotice(endpoint: string, text: string): void;
|
|
841
866
|
}
|
|
842
867
|
|
|
843
|
-
export { type Deliver, type Delivery, FederatedCore, type FederatedOptions, GRAPH_MINUTES, GRAPH_MINUTES_MAX, type GraphEdge, type GraphNode, HUB_LIMITS, type HoldView, type HubAuth, type HubConn, type HubDeps, type HubGrant, type HubGraph, type HubJudgeOptions, type HubLimits, type HubRole, type HubSocket, JUDGE_LIMITS, type JudgeClientFactory, JudgeGate, type JudgeGateOptions, type JudgeLimits, type Origin as JudgeOrigin, MACHINE_NAME, type MachineRow, type MirrorCaps, type MirrorChange, type MirrorRules, type MirrorScope, type MirrorView, type RelayAuth, RelayHub, type RemoteView, type XEndpoint };
|
|
868
|
+
export { type DashboardEvent, type Deliver, type Delivery, FederatedCore, type FederatedOptions, GRAPH_MINUTES, GRAPH_MINUTES_MAX, type GraphEdge, type GraphNode, HUB_LIMITS, type HoldView, type HubAuth, type HubConn, type HubDeps, type HubGrant, type HubGraph, type HubJudgeOptions, type HubLimits, type HubRole, type HubSocket, JUDGE_LIMITS, type JudgeClientFactory, JudgeGate, type JudgeGateOptions, type JudgeLimits, type Origin as JudgeOrigin, MACHINE_NAME, type MachineRow, type MirrorCaps, type MirrorChange, type MirrorRules, type MirrorScope, type MirrorView, type RelayAuth, RelayHub, type RemoteView, type XEndpoint, dashboardEvent };
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,8 @@ import {
|
|
|
22
22
|
unreachable,
|
|
23
23
|
validHandle,
|
|
24
24
|
validHarness,
|
|
25
|
-
validSelector
|
|
25
|
+
validSelector,
|
|
26
|
+
WAKES
|
|
26
27
|
} from "@scp-dev/core";
|
|
27
28
|
var MACHINE_NAME = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
28
29
|
var REMOTE = /^[a-z0-9][a-z0-9._-]{0,63}\/([a-z0-9][a-z0-9._-]{0,62})?$/;
|
|
@@ -1283,7 +1284,7 @@ var FederatedCore = class extends RelayCore {
|
|
|
1283
1284
|
const was = this.view(before);
|
|
1284
1285
|
const presence = was.presence !== view.presence;
|
|
1285
1286
|
const status = before.status_state !== after.status_state || before.status_note !== after.status_note;
|
|
1286
|
-
const other = ["kind", "harness", "model", "cwd", "labels", "title", "tree", "parent_ep", "depth", "rules", "caps", "incarnation"].some(
|
|
1287
|
+
const other = ["kind", "harness", "model", "cwd", "labels", "title", "wake", "tree", "parent_ep", "depth", "rules", "caps", "incarnation"].some(
|
|
1287
1288
|
(field) => before[field] !== after[field]
|
|
1288
1289
|
);
|
|
1289
1290
|
return presence || status || other ? { view, added: false, presence, status } : null;
|
|
@@ -1351,6 +1352,7 @@ var FederatedCore = class extends RelayCore {
|
|
|
1351
1352
|
rules: rules === void 0 ? row.rules : rules && (rules.in || rules.out) ? JSON.stringify(rules) : null,
|
|
1352
1353
|
caps,
|
|
1353
1354
|
title: scope.title !== void 0 ? scope.title : fresh ? null : row.title,
|
|
1355
|
+
wake: scope.wake !== void 0 ? scope.wake : fresh ? null : row.wake,
|
|
1354
1356
|
incarnation: scope.incarnation ?? row.incarnation
|
|
1355
1357
|
};
|
|
1356
1358
|
}
|
|
@@ -1504,6 +1506,7 @@ function parseScope(view) {
|
|
|
1504
1506
|
throw new RelayError("too_large", `an endpoint's rules and caps are more than ${SCOPE_BYTES} bytes`);
|
|
1505
1507
|
if (given("title"))
|
|
1506
1508
|
out.title = view.title === null ? null : cleanText(typeof view.title === "string" ? view.title.replace(/\s+/g, " ") : view.title, "title", 64) || null;
|
|
1509
|
+
if (given("wake")) out.wake = typeof view.wake === "string" && view.wake !== "none" && WAKES.includes(view.wake) ? view.wake : null;
|
|
1507
1510
|
if (given("incarnation")) {
|
|
1508
1511
|
if (view.incarnation !== null && (typeof view.incarnation !== "string" || !INCARNATION.test(view.incarnation))) throw bad("incarnation");
|
|
1509
1512
|
out.incarnation = view.incarnation ?? null;
|
|
@@ -2009,6 +2012,20 @@ function heldResult(id, params) {
|
|
|
2009
2012
|
// src/hub.ts
|
|
2010
2013
|
var GRAPH_MINUTES = 15;
|
|
2011
2014
|
var GRAPH_MINUTES_MAX = 60;
|
|
2015
|
+
var str = (v) => typeof v === "string" ? v : null;
|
|
2016
|
+
function dashboardEvent(event, at) {
|
|
2017
|
+
if (event.type === "message") {
|
|
2018
|
+
const from = str(event.from);
|
|
2019
|
+
const to = Array.isArray(event.to) ? event.to.filter((a) => typeof a === "string") : [];
|
|
2020
|
+
return from && to.length ? { type: "message", at, kind: str(event.kind) ?? "message", from, to } : null;
|
|
2021
|
+
}
|
|
2022
|
+
if (event.type === "presence") {
|
|
2023
|
+
const endpoint = str(event.endpoint);
|
|
2024
|
+
const presence = event.presence === "online" || event.presence === "idle" || event.presence === "offline" ? event.presence : null;
|
|
2025
|
+
return endpoint && presence ? { type: "presence", at, machine: str(event.machine), endpoint, presence } : null;
|
|
2026
|
+
}
|
|
2027
|
+
return null;
|
|
2028
|
+
}
|
|
2012
2029
|
var HUB_LIMITS = {
|
|
2013
2030
|
machines: 16,
|
|
2014
2031
|
endpoints: 512,
|
|
@@ -3167,6 +3184,7 @@ var RelayHub = class _RelayHub {
|
|
|
3167
3184
|
this.fleet({ type: "presence", machine, endpoint: view.address, presence: view.presence, harness: view.harness, labels: view.labels });
|
|
3168
3185
|
if (change.status && !change.added) this.fleet({ type: "status", machine, endpoint: view.address, status: view.status });
|
|
3169
3186
|
}
|
|
3187
|
+
if (full || changes.some((c) => c.added || c.was !== void 0)) this.dash({ type: "endpoints", at: new Date(this.now()).toISOString(), machine });
|
|
3170
3188
|
}
|
|
3171
3189
|
/** a direct client's endpoint changed: the other bridges mirror it */
|
|
3172
3190
|
announceEndpoint(endpoint) {
|
|
@@ -3191,6 +3209,15 @@ var RelayHub = class _RelayHub {
|
|
|
3191
3209
|
fleet(event) {
|
|
3192
3210
|
const at = new Date(this.now()).toISOString();
|
|
3193
3211
|
for (const socket of this.deps.sockets()) if (socket.conn.watching) this.write(socket, { jsonrpc: "2.0", method: "fleet", params: { at, ...event } });
|
|
3212
|
+
const dash = this.deps.dashboard ? dashboardEvent(event, at) : null;
|
|
3213
|
+
if (dash) this.dash(dash);
|
|
3214
|
+
}
|
|
3215
|
+
dash(event) {
|
|
3216
|
+
try {
|
|
3217
|
+
this.deps.dashboard?.(event);
|
|
3218
|
+
} catch {
|
|
3219
|
+
this.deps.log?.("relay_dashboard_failed", { account: this.account });
|
|
3220
|
+
}
|
|
3194
3221
|
}
|
|
3195
3222
|
write(socket, value) {
|
|
3196
3223
|
if (this.gone.has(socket.conn.id)) return;
|
|
@@ -3383,6 +3410,7 @@ var RelayHub = class _RelayHub {
|
|
|
3383
3410
|
});
|
|
3384
3411
|
this.stored = null;
|
|
3385
3412
|
this.toBridges(name, { machine: name, endpoints: [], full: true });
|
|
3413
|
+
this.dash({ type: "endpoints", at: new Date(this.now()).toISOString(), machine: name });
|
|
3386
3414
|
this.deps.log?.("relay_action", { account: this.account, machine: name, method: "machine_release", code: "ok" });
|
|
3387
3415
|
return { removed: name, endpoints: dropped };
|
|
3388
3416
|
}
|
|
@@ -3495,5 +3523,6 @@ export {
|
|
|
3495
3523
|
JUDGE_LIMITS,
|
|
3496
3524
|
JudgeGate,
|
|
3497
3525
|
MACHINE_NAME,
|
|
3498
|
-
RelayHub
|
|
3526
|
+
RelayHub,
|
|
3527
|
+
dashboardEvent
|
|
3499
3528
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scp-dev/hub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Swarm Communication Protocol federation and account hub: mirrored endpoints across machines, bridges, direct clients and cross-account delivery.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://scp.dev",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"typecheck": "tsc -p ."
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@scp-dev/core": "0.1.
|
|
32
|
+
"@scp-dev/core": "0.1.4"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"tsup": "^8.5.1"
|
package/src/federation.ts
CHANGED
|
@@ -43,6 +43,8 @@ import {
|
|
|
43
43
|
validHandle,
|
|
44
44
|
validHarness,
|
|
45
45
|
validSelector,
|
|
46
|
+
type Wake,
|
|
47
|
+
WAKES,
|
|
46
48
|
} from "@scp-dev/core"
|
|
47
49
|
|
|
48
50
|
export type RelayAuth = Envelope["from"]["auth"]
|
|
@@ -121,6 +123,8 @@ export interface MirrorView extends MirrorScope {
|
|
|
121
123
|
presence?: Presence
|
|
122
124
|
last_seen?: string | null
|
|
123
125
|
title?: string | null
|
|
126
|
+
/** an agent's {@link Wake}; a value this hub does not know is kept as none */
|
|
127
|
+
wake?: string | null
|
|
124
128
|
/** the handle the endpoint had before a rename: its mirror is renamed in place */
|
|
125
129
|
was?: string
|
|
126
130
|
}
|
|
@@ -142,6 +146,7 @@ interface ParsedScope {
|
|
|
142
146
|
rules?: MirrorRules | null
|
|
143
147
|
caps?: ScopeCaps | null
|
|
144
148
|
title?: string | null
|
|
149
|
+
wake?: Wake | null
|
|
145
150
|
incarnation?: string | null
|
|
146
151
|
}
|
|
147
152
|
|
|
@@ -1609,7 +1614,7 @@ export class FederatedCore extends RelayCore {
|
|
|
1609
1614
|
const was = this.view(before)
|
|
1610
1615
|
const presence = was.presence !== view.presence
|
|
1611
1616
|
const status = before.status_state !== after.status_state || before.status_note !== after.status_note
|
|
1612
|
-
const other = (["kind", "harness", "model", "cwd", "labels", "title", "tree", "parent_ep", "depth", "rules", "caps", "incarnation"] as const).some(
|
|
1617
|
+
const other = (["kind", "harness", "model", "cwd", "labels", "title", "wake", "tree", "parent_ep", "depth", "rules", "caps", "incarnation"] as const).some(
|
|
1613
1618
|
(field) => before[field] !== (after as FederatedRow)[field],
|
|
1614
1619
|
)
|
|
1615
1620
|
return presence || status || other ? { view, added: false, presence, status } : null
|
|
@@ -1682,6 +1687,7 @@ export class FederatedCore extends RelayCore {
|
|
|
1682
1687
|
rules: rules === undefined ? row.rules : rules && (rules.in || rules.out) ? JSON.stringify(rules) : null,
|
|
1683
1688
|
caps,
|
|
1684
1689
|
title: scope.title !== undefined ? scope.title : fresh ? null : row.title,
|
|
1690
|
+
wake: scope.wake !== undefined ? scope.wake : fresh ? null : row.wake,
|
|
1685
1691
|
incarnation: scope.incarnation ?? row.incarnation,
|
|
1686
1692
|
}
|
|
1687
1693
|
}
|
|
@@ -1860,6 +1866,7 @@ function parseScope(view: Partial<MirrorView>): ParsedScope {
|
|
|
1860
1866
|
throw new RelayError("too_large", `an endpoint's rules and caps are more than ${SCOPE_BYTES} bytes`)
|
|
1861
1867
|
if (given("title"))
|
|
1862
1868
|
out.title = view.title === null ? null : cleanText(typeof view.title === "string" ? view.title.replace(/\s+/g, " ") : view.title, "title", 64) || null
|
|
1869
|
+
if (given("wake")) out.wake = typeof view.wake === "string" && view.wake !== "none" && WAKES.includes(view.wake as Wake) ? (view.wake as Wake) : null
|
|
1863
1870
|
if (given("incarnation")) {
|
|
1864
1871
|
if (view.incarnation !== null && (typeof view.incarnation !== "string" || !INCARNATION.test(view.incarnation))) throw bad("incarnation")
|
|
1865
1872
|
out.incarnation = view.incarnation ?? null
|
package/src/hub.ts
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
SYSTEM_HANDLE,
|
|
39
39
|
sameSecret,
|
|
40
40
|
untilTomorrow,
|
|
41
|
+
type Wake,
|
|
41
42
|
type XContact,
|
|
42
43
|
XCounters,
|
|
43
44
|
type XDelivered,
|
|
@@ -104,7 +105,7 @@ export interface GraphNode {
|
|
|
104
105
|
status: EndpointView["status"]
|
|
105
106
|
last_seen: string | null
|
|
106
107
|
parent: string | null
|
|
107
|
-
wake?:
|
|
108
|
+
wake?: Wake | null
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
/** `spawn`: parent to child; `message`: sender to recipient, with the count and newest send in the graph's window */
|
|
@@ -122,6 +123,29 @@ export interface HubGraph {
|
|
|
122
123
|
export const GRAPH_MINUTES = 15
|
|
123
124
|
export const GRAPH_MINUTES_MAX = 60
|
|
124
125
|
|
|
126
|
+
/** what a read-only dashboard stream sees as it happens: addresses, kinds and presence, never a body, note, summary or label */
|
|
127
|
+
export type DashboardEvent =
|
|
128
|
+
| { type: "presence"; at: string; machine: string | null; endpoint: string; presence: Presence }
|
|
129
|
+
| { type: "message"; at: string; kind: string; from: string; to: string[] }
|
|
130
|
+
| { type: "endpoints"; at: string; machine: string }
|
|
131
|
+
|
|
132
|
+
const str = (v: unknown): string | null => (typeof v === "string" ? v : null)
|
|
133
|
+
|
|
134
|
+
/** a watcher's fleet event as a dashboard sees it, field by field; null for the kinds a dashboard does not stream */
|
|
135
|
+
export function dashboardEvent(event: Record<string, unknown>, at: string): DashboardEvent | null {
|
|
136
|
+
if (event.type === "message") {
|
|
137
|
+
const from = str(event.from)
|
|
138
|
+
const to = Array.isArray(event.to) ? event.to.filter((a): a is string => typeof a === "string") : []
|
|
139
|
+
return from && to.length ? { type: "message", at, kind: str(event.kind) ?? "message", from, to } : null
|
|
140
|
+
}
|
|
141
|
+
if (event.type === "presence") {
|
|
142
|
+
const endpoint = str(event.endpoint)
|
|
143
|
+
const presence = event.presence === "online" || event.presence === "idle" || event.presence === "offline" ? event.presence : null
|
|
144
|
+
return endpoint && presence ? { type: "presence", at, machine: str(event.machine), endpoint, presence } : null
|
|
145
|
+
}
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
|
|
125
149
|
export interface HubSocket {
|
|
126
150
|
conn: HubConn
|
|
127
151
|
send(text: string): void
|
|
@@ -141,6 +165,8 @@ export interface HubDeps {
|
|
|
141
165
|
save(socket: HubSocket): void
|
|
142
166
|
/** structured log line, e.g. relay_action; never bodies or tokens */
|
|
143
167
|
log?: (event: string, fields: Record<string, unknown>) => void
|
|
168
|
+
/** live presence, endpoint and message metadata for a read-only dashboard; never a body */
|
|
169
|
+
dashboard?: (event: DashboardEvent) => void
|
|
144
170
|
/** overrides of {@link HUB_LIMITS}; `Infinity` disables a cap, count or byte limit, durations stay finite */
|
|
145
171
|
limits?: Partial<HubLimits>
|
|
146
172
|
/** the link to other accounts' relays; without it an address of another account is not found */
|
|
@@ -1430,6 +1456,7 @@ export class RelayHub {
|
|
|
1430
1456
|
this.fleet({ type: "presence", machine, endpoint: view.address, presence: view.presence, harness: view.harness, labels: view.labels })
|
|
1431
1457
|
if (change.status && !change.added) this.fleet({ type: "status", machine, endpoint: view.address, status: view.status })
|
|
1432
1458
|
}
|
|
1459
|
+
if (full || changes.some((c) => c.added || c.was !== undefined)) this.dash({ type: "endpoints", at: new Date(this.now()).toISOString(), machine })
|
|
1433
1460
|
}
|
|
1434
1461
|
|
|
1435
1462
|
/** a direct client's endpoint changed: the other bridges mirror it */
|
|
@@ -1460,6 +1487,16 @@ export class RelayHub {
|
|
|
1460
1487
|
private fleet(event: Record<string, unknown>): void {
|
|
1461
1488
|
const at = new Date(this.now()).toISOString()
|
|
1462
1489
|
for (const socket of this.deps.sockets()) if (socket.conn.watching) this.write(socket, { jsonrpc: "2.0", method: "fleet", params: { at, ...event } })
|
|
1490
|
+
const dash = this.deps.dashboard ? dashboardEvent(event, at) : null
|
|
1491
|
+
if (dash) this.dash(dash)
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
private dash(event: DashboardEvent): void {
|
|
1495
|
+
try {
|
|
1496
|
+
this.deps.dashboard?.(event)
|
|
1497
|
+
} catch {
|
|
1498
|
+
this.deps.log?.("relay_dashboard_failed", { account: this.account })
|
|
1499
|
+
}
|
|
1463
1500
|
}
|
|
1464
1501
|
|
|
1465
1502
|
private write(socket: HubSocket, value: unknown): void {
|
|
@@ -1593,7 +1630,7 @@ export class RelayHub {
|
|
|
1593
1630
|
/** a read-only graph for a dashboard: listed endpoints, spawn edges among them, and message counts per sender and recipient; never a body */
|
|
1594
1631
|
graph(minutes = GRAPH_MINUTES): HubGraph {
|
|
1595
1632
|
const window = Math.min(Math.max(1, Math.floor(Number.isFinite(minutes) ? minutes : GRAPH_MINUTES)), GRAPH_MINUTES_MAX)
|
|
1596
|
-
const endpoints = this.relay.list()
|
|
1633
|
+
const endpoints = this.relay.list()
|
|
1597
1634
|
const byId = new Map(endpoints.map((e) => [e.id, e.address]))
|
|
1598
1635
|
const listed = new Set(byId.values())
|
|
1599
1636
|
const nodes: GraphNode[] = endpoints.map((e) => ({
|
|
@@ -1672,6 +1709,7 @@ export class RelayHub {
|
|
|
1672
1709
|
})
|
|
1673
1710
|
this.stored = null
|
|
1674
1711
|
this.toBridges(name, { machine: name, endpoints: [], full: true })
|
|
1712
|
+
this.dash({ type: "endpoints", at: new Date(this.now()).toISOString(), machine: name })
|
|
1675
1713
|
this.deps.log?.("relay_action", { account: this.account, machine: name, method: "machine_release", code: "ok" })
|
|
1676
1714
|
return { removed: name, endpoints: dropped }
|
|
1677
1715
|
}
|