@scp-dev/hub 0.3.1 → 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 CHANGED
@@ -593,6 +593,26 @@ interface HubGraph {
593
593
  /** the default and widest window of message edges; the core keeps an hour of sender and recipient pairs */
594
594
  declare const GRAPH_MINUTES = 15;
595
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;
596
616
  interface HubSocket {
597
617
  conn: HubConn;
598
618
  send(text: string): void;
@@ -611,6 +631,8 @@ interface HubDeps {
611
631
  save(socket: HubSocket): void;
612
632
  /** structured log line, e.g. relay_action; never bodies or tokens */
613
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;
614
636
  /** overrides of {@link HUB_LIMITS}; `Infinity` disables a cap, count or byte limit, durations stay finite */
615
637
  limits?: Partial<HubLimits>;
616
638
  /** the link to other accounts' relays; without it an address of another account is not found */
@@ -793,6 +815,7 @@ declare class RelayHub {
793
815
  /** the machines of other members than the grant's, which a bridge shows under the account handle */
794
816
  private othersOf;
795
817
  private fleet;
818
+ private dash;
796
819
  private write;
797
820
  private save;
798
821
  private waitMs;
@@ -842,4 +865,4 @@ declare class RelayHub {
842
865
  xnotice(endpoint: string, text: string): void;
843
866
  }
844
867
 
845
- 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
@@ -2012,6 +2012,20 @@ function heldResult(id, params) {
2012
2012
  // src/hub.ts
2013
2013
  var GRAPH_MINUTES = 15;
2014
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
+ }
2015
2029
  var HUB_LIMITS = {
2016
2030
  machines: 16,
2017
2031
  endpoints: 512,
@@ -3170,6 +3184,7 @@ var RelayHub = class _RelayHub {
3170
3184
  this.fleet({ type: "presence", machine, endpoint: view.address, presence: view.presence, harness: view.harness, labels: view.labels });
3171
3185
  if (change.status && !change.added) this.fleet({ type: "status", machine, endpoint: view.address, status: view.status });
3172
3186
  }
3187
+ if (full || changes.some((c) => c.added || c.was !== void 0)) this.dash({ type: "endpoints", at: new Date(this.now()).toISOString(), machine });
3173
3188
  }
3174
3189
  /** a direct client's endpoint changed: the other bridges mirror it */
3175
3190
  announceEndpoint(endpoint) {
@@ -3194,6 +3209,15 @@ var RelayHub = class _RelayHub {
3194
3209
  fleet(event) {
3195
3210
  const at = new Date(this.now()).toISOString();
3196
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
+ }
3197
3221
  }
3198
3222
  write(socket, value) {
3199
3223
  if (this.gone.has(socket.conn.id)) return;
@@ -3386,6 +3410,7 @@ var RelayHub = class _RelayHub {
3386
3410
  });
3387
3411
  this.stored = null;
3388
3412
  this.toBridges(name, { machine: name, endpoints: [], full: true });
3413
+ this.dash({ type: "endpoints", at: new Date(this.now()).toISOString(), machine: name });
3389
3414
  this.deps.log?.("relay_action", { account: this.account, machine: name, method: "machine_release", code: "ok" });
3390
3415
  return { removed: name, endpoints: dropped };
3391
3416
  }
@@ -3498,5 +3523,6 @@ export {
3498
3523
  JUDGE_LIMITS,
3499
3524
  JudgeGate,
3500
3525
  MACHINE_NAME,
3501
- RelayHub
3526
+ RelayHub,
3527
+ dashboardEvent
3502
3528
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scp-dev/hub",
3
- "version": "0.3.1",
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",
package/src/hub.ts CHANGED
@@ -123,6 +123,29 @@ export interface HubGraph {
123
123
  export const GRAPH_MINUTES = 15
124
124
  export const GRAPH_MINUTES_MAX = 60
125
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
+
126
149
  export interface HubSocket {
127
150
  conn: HubConn
128
151
  send(text: string): void
@@ -142,6 +165,8 @@ export interface HubDeps {
142
165
  save(socket: HubSocket): void
143
166
  /** structured log line, e.g. relay_action; never bodies or tokens */
144
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
145
170
  /** overrides of {@link HUB_LIMITS}; `Infinity` disables a cap, count or byte limit, durations stay finite */
146
171
  limits?: Partial<HubLimits>
147
172
  /** the link to other accounts' relays; without it an address of another account is not found */
@@ -1431,6 +1456,7 @@ export class RelayHub {
1431
1456
  this.fleet({ type: "presence", machine, endpoint: view.address, presence: view.presence, harness: view.harness, labels: view.labels })
1432
1457
  if (change.status && !change.added) this.fleet({ type: "status", machine, endpoint: view.address, status: view.status })
1433
1458
  }
1459
+ if (full || changes.some((c) => c.added || c.was !== undefined)) this.dash({ type: "endpoints", at: new Date(this.now()).toISOString(), machine })
1434
1460
  }
1435
1461
 
1436
1462
  /** a direct client's endpoint changed: the other bridges mirror it */
@@ -1461,6 +1487,16 @@ export class RelayHub {
1461
1487
  private fleet(event: Record<string, unknown>): void {
1462
1488
  const at = new Date(this.now()).toISOString()
1463
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
+ }
1464
1500
  }
1465
1501
 
1466
1502
  private write(socket: HubSocket, value: unknown): void {
@@ -1673,6 +1709,7 @@ export class RelayHub {
1673
1709
  })
1674
1710
  this.stored = null
1675
1711
  this.toBridges(name, { machine: name, endpoints: [], full: true })
1712
+ this.dash({ type: "endpoints", at: new Date(this.now()).toISOString(), machine: name })
1676
1713
  this.deps.log?.("relay_action", { account: this.account, machine: name, method: "machine_release", code: "ok" })
1677
1714
  return { removed: name, endpoints: dropped }
1678
1715
  }
package/src/index.ts CHANGED
@@ -13,6 +13,8 @@ export {
13
13
  type XEndpoint,
14
14
  } from "./federation"
15
15
  export {
16
+ type DashboardEvent,
17
+ dashboardEvent,
16
18
  GRAPH_MINUTES,
17
19
  GRAPH_MINUTES_MAX,
18
20
  type GraphEdge,