@peers-app/peers-sdk 0.30.2 → 0.30.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.
@@ -0,0 +1,148 @@
1
+ /** Trigger for an overlapping, at most five-minute connectivity measurement. */
2
+ export type MeshEpisodeReason = "startup" | "resume" | "network-change" | "connection-loss";
3
+ /** Provenance of a dial; explicit requests and group joins are also distinguished. */
4
+ export type MeshDialReason = "own-device" | "group-network-info" | "broadcast-answer" | "configured-server" | "lan-scan" | "websocket-upgrade" | "incoming" | "direct-request" | "group-join";
5
+ /** Bounded path categories used for lifetime counters; attempts also retain the address. */
6
+ export type MeshPath = "websocket" | "webrtc" | "cloud" | "discovery";
7
+ /** Failure categories, independent of free-form transport error messages. */
8
+ export type MeshFailureReason = "ttl0" | "no-route" | "signaling-timeout" | "socket-error" | "socket-timeout" | "handshake" | "rejected" | "closed" | "superseded" | "unsupported";
9
+ /** Observed stage; timestamps are offsets in milliseconds from the attempt's queued time. */
10
+ export type MeshStage = "queued" | "started" | "authenticated" | "admitted" | "ready" | "firstSync";
11
+ /** Optional observational metadata passed through a dial without affecting its result. */
12
+ export interface IMeshDial {
13
+ attemptId?: string;
14
+ reason?: MeshDialReason;
15
+ targetDeviceId?: string;
16
+ }
17
+ /** One dial or inbound connection. Missing stages have not been observed. */
18
+ export interface IMeshAttempt {
19
+ attemptId: string;
20
+ targetDeviceId?: string;
21
+ reason: MeshDialReason;
22
+ direction: "outgoing" | "incoming";
23
+ path: MeshPath;
24
+ address?: string;
25
+ queuedAt: number;
26
+ stages: Partial<Record<MeshStage, number>>;
27
+ answeredDeviceId?: string;
28
+ expectedIdentity?: boolean;
29
+ answered: boolean;
30
+ lastSignalFailure?: {
31
+ reason: "ttl0" | "no-route";
32
+ detail?: string;
33
+ };
34
+ ready: Record<string, number>;
35
+ firstSync: Record<string, number>;
36
+ outcome: "pending" | "admitted" | "failed" | "unfinished";
37
+ endReason?: "buffer-evicted" | "disposed";
38
+ endedAt?: number;
39
+ durationMs?: number;
40
+ failure?: {
41
+ stage: MeshStage;
42
+ reason: MeshFailureReason;
43
+ detail?: string;
44
+ };
45
+ doubleDial: boolean;
46
+ offline: boolean;
47
+ outOfOrder: boolean;
48
+ }
49
+ /** Millisecond durations derived from one episode; absent means not observed. */
50
+ export interface IMeshDurations {
51
+ T_first?: number;
52
+ T_own?: number;
53
+ T_cover?: number;
54
+ T_resume?: number;
55
+ repairStart?: number;
56
+ }
57
+ /** Coverage at the latest observation (at completion for a finished episode). */
58
+ export interface IMeshContextCoverage {
59
+ dataContextId: string;
60
+ possibleSourceDeviceIds: string[];
61
+ usableDeviceIds: string[];
62
+ excluded: boolean;
63
+ }
64
+ /** One connectivity episode, independent of any other episode in flight. */
65
+ export interface IMeshEpisode {
66
+ episodeId: string;
67
+ reason: MeshEpisodeReason;
68
+ trigger?: string;
69
+ startedAt: number;
70
+ endedAt?: number;
71
+ durationMs?: number;
72
+ outcome: "active" | "covered" | "unfinished";
73
+ endReason?: "covered" | "deadline" | "buffer-evicted" | "disposed";
74
+ metrics: IMeshDurations;
75
+ contexts: IMeshContextCoverage[];
76
+ }
77
+ /** Nearest-rank percentiles of observed values only; count exposes the denominator. */
78
+ export interface IMeshPercentiles {
79
+ count: number;
80
+ p50?: number;
81
+ p90?: number;
82
+ p95?: number;
83
+ p99?: number;
84
+ }
85
+ /** Lifetime dial totals. Incoming connections are separate from the outgoing-dial ratio. */
86
+ export interface IMeshPathCounters {
87
+ dials: number;
88
+ connections: number;
89
+ incomingConnections: number;
90
+ dialsPerConnection: number | null;
91
+ failures: Partial<Record<MeshFailureReason, number>>;
92
+ }
93
+ /** Shared relay totals and current gauges, also exposed on non-Services hosts. */
94
+ export interface IMeshServicesCounters {
95
+ directRelays: number;
96
+ floodsForwarded: number;
97
+ floodsPruned: number;
98
+ pendingEmits: number;
99
+ concurrentConnections: number;
100
+ connects: number;
101
+ disconnects: number;
102
+ }
103
+ /** In-memory diagnostics snapshot. No history survives a runtime restart. */
104
+ export interface IMeshStats {
105
+ startedAt: number;
106
+ sampledAt: number;
107
+ uptimeMs: number;
108
+ readyToDialMs?: number;
109
+ limits: {
110
+ episodes: number;
111
+ attempts: number;
112
+ episodeMs: number;
113
+ };
114
+ currentEpisodes: IMeshEpisode[];
115
+ recentEpisodes: IMeshEpisode[];
116
+ percentiles: Record<keyof IMeshDurations, IMeshPercentiles>;
117
+ counters: {
118
+ paths: Record<MeshPath, IMeshPathCounters>;
119
+ lanScan: {
120
+ scans: number;
121
+ probes: number;
122
+ connected: number;
123
+ };
124
+ unfinishedAttempts: number;
125
+ doubleDials: number;
126
+ offlineDials: number;
127
+ offlineDialsPerHour: number;
128
+ broadcasts: {
129
+ sent: number;
130
+ answered: number;
131
+ ttl0: number;
132
+ failed: number;
133
+ pending: number;
134
+ perHour: number;
135
+ };
136
+ services: IMeshServicesCounters;
137
+ };
138
+ recentAttempts: IMeshAttempt[];
139
+ recentBroadcasts: IMeshBroadcast[];
140
+ }
141
+ /** A discovery broadcast and its observed reply, bounded with the attempt history. */
142
+ export interface IMeshBroadcast {
143
+ broadcastId: string;
144
+ dataContextId?: string;
145
+ sentAt: number;
146
+ durationMs?: number;
147
+ outcome: "pending" | "answered" | "ttl0" | "failed";
148
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -4,12 +4,15 @@ import type { Observable } from "../../observable";
4
4
  import type { INetworkInfo } from "../../types/peer-device";
5
5
  import type { IContractConsumerBase } from "../contract-proxy";
6
6
  import type { IContractDefinition } from "../types";
7
+ import type { IMeshStats } from "./mesh-metrics-types";
8
+ export type * from "./mesh-metrics-types";
7
9
  /** Version of the built-in System Network Diagnostics contract. */
8
10
  export declare const NETWORK_DIAGNOSTICS_CONTRACT_VERSION = 1;
9
11
  /** Version of the built-in System Network Control contract. */
10
12
  export declare const NETWORK_CONTROL_CONTRACT_VERSION = 1;
11
13
  /** Stable IDs for tools in System Network Diagnostics v1. */
12
14
  export declare const networkDiagnosticsToolIds: {
15
+ readonly getMeshStats: string;
13
16
  readonly getOverview: "00ms1yb50ym353ads0gel5jpv";
14
17
  readonly getConnections: "00ms1yb50ykny9kdgiclpa1t4";
15
18
  readonly getGroupSyncStatus: "00ms1yb50yp2q7jr0jglzsztq";
@@ -241,6 +244,11 @@ export interface INetworkDiagnosticsConsumer extends IContractConsumerBase {
241
244
  getOverview(): Promise<{
242
245
  overview: INetworkOverview;
243
246
  }>;
247
+ getMeshStats(input?: {
248
+ limit?: number;
249
+ }): Promise<{
250
+ stats: IMeshStats;
251
+ }>;
244
252
  getConnections(): Promise<{
245
253
  connections: INetworkConnectionInfo[];
246
254
  }>;
@@ -6,12 +6,18 @@ exports.getNetworkControlContractDefinition = getNetworkControlContractDefinitio
6
6
  const system_ids_1 = require("../../system-ids");
7
7
  const field_type_1 = require("../../types/field-type");
8
8
  const system_contracts_1 = require("../system-contracts");
9
+ const tool_catalog_identity_1 = require("../tool-catalog-identity");
9
10
  /** Version of the built-in System Network Diagnostics contract. */
10
11
  exports.NETWORK_DIAGNOSTICS_CONTRACT_VERSION = 1;
11
12
  /** Version of the built-in System Network Control contract. */
12
13
  exports.NETWORK_CONTROL_CONTRACT_VERSION = 1;
13
14
  /** Stable IDs for tools in System Network Diagnostics v1. */
14
15
  exports.networkDiagnosticsToolIds = {
16
+ getMeshStats: (0, tool_catalog_identity_1.deriveToolCatalogId)({
17
+ contractId: system_ids_1.networkDiagnosticsContractId,
18
+ version: exports.NETWORK_DIAGNOSTICS_CONTRACT_VERSION,
19
+ toolName: "getMeshStats",
20
+ }),
15
21
  getOverview: "00ms1yb50ym353ads0gel5jpv",
16
22
  getConnections: "00ms1yb50ykny9kdgiclpa1t4",
17
23
  getGroupSyncStatus: "00ms1yb50yp2q7jr0jglzsztq",
@@ -46,6 +52,16 @@ function getNetworkDiagnosticsContractDefinition() {
46
52
  description: "Portable local network, connection, and synchronization diagnostics.",
47
53
  tables: [],
48
54
  tools: [
55
+ {
56
+ name: "getMeshStats",
57
+ usageDescription: "Read bounded in-memory connectivity episodes, attempts, percentiles and relay counters.",
58
+ inputFields: [
59
+ field("limit", field_type_1.FieldType.number, "Maximum recent attempts and broadcasts (default 50, range 0–500). Episodes and counters are always complete.", { optional: true }),
60
+ ],
61
+ outputFields: [
62
+ field("stats", field_type_1.FieldType.object, "Connectivity measurements since runtime startup."),
63
+ ],
64
+ },
49
65
  {
50
66
  name: "getOverview",
51
67
  usageDescription: "Return aggregate network and group information.",
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FilesTable = void 0;
4
4
  exports.Files = Files;
5
5
  const user_context_singleton_1 = require("../../context/user-context-singleton");
6
- const rpc_types_1 = require("../../rpc-types");
7
6
  const orm_1 = require("../orm");
8
7
  const table_definitions_system_1 = require("../orm/table-definitions.system");
9
8
  const chunk_store_1 = require("./chunk-store");
@@ -166,18 +165,3 @@ exports.FilesTable = FilesTable;
166
165
  function Files(dataContext) {
167
166
  return (0, user_context_singleton_1.getTableContainer)(dataContext).getTable(file_types_1.filesMetaData, file_types_1.fileSchema);
168
167
  }
169
- // Deprecated RPCs, kept only for Family Hub 0.3.x until its port is Home's version. New code
170
- // uses System Files, which is bound to the caller's own data context.
171
- rpc_types_1.rpcServerCalls.getFileContentsBase64 = async (fileId) => {
172
- const data = await Files().getFileContents(fileId);
173
- if (data === null) {
174
- throw new Error(`File not found: ${fileId}`);
175
- }
176
- return Buffer.from(data).toString("base64");
177
- };
178
- rpc_types_1.rpcServerCalls.saveFile = async (input) => {
179
- const encoding = input.encoding || "utf8";
180
- const dataBuffer = encoding === "base64" ? Buffer.from(input.data, "base64") : Buffer.from(input.data, "utf8");
181
- // Kept: Family Hub 0.3.x doesn't declare its file fields, so nothing would reference these.
182
- return await Files().saveFile({ name: input.name, mimeType: input.mimeType, keep: true }, new Uint8Array(dataBuffer));
183
- };
@@ -21,6 +21,9 @@ export declare class Connection {
21
21
  private _remoteDeviceInfo;
22
22
  /** Trust assigned to the remote identity once the handshake completes. */
23
23
  trustLevel: TrustLevel;
24
+ /** Synchronous diagnostics only; observer errors cannot affect handshake results. */
25
+ onHandshakeObserved?: (stage: "authenticated" | "failed", error?: unknown) => void;
26
+ private observeHandshake;
24
27
  /** Server-side hook fired shortly after the successful handshake reply has been sent. */
25
28
  onHandshakeComplete: ((deviceInfo: IDeviceInfo) => void) | undefined;
26
29
  /** RPC timeout for {@link Connection.emit}. */
@@ -32,6 +32,16 @@ class Connection {
32
32
  };
33
33
  /** Trust assigned to the remote identity once the handshake completes. */
34
34
  trustLevel = socket_type_1.TrustLevel.Unknown;
35
+ /** Synchronous diagnostics only; observer errors cannot affect handshake results. */
36
+ onHandshakeObserved;
37
+ observeHandshake(stage, error) {
38
+ try {
39
+ this.onHandshakeObserved?.(stage, error);
40
+ }
41
+ catch {
42
+ /* diagnostics never affect the handshake */
43
+ }
44
+ }
35
45
  /** Server-side hook fired shortly after the successful handshake reply has been sent. */
36
46
  onHandshakeComplete;
37
47
  /** RPC timeout for {@link Connection.emit}. */
@@ -133,6 +143,7 @@ class Connection {
133
143
  }
134
144
  }
135
145
  catch (e) {
146
+ this.observeHandshake("failed", e);
136
147
  console.error("Error handling completeHandshake", e);
137
148
  callback({ error: e?.message ?? String(e), errorType: "RPC_ERROR" });
138
149
  }
@@ -412,6 +423,7 @@ class Connection {
412
423
  };
413
424
  this.trustLevel = trustLevel;
414
425
  this._verified = true;
426
+ this.observeHandshake("authenticated");
415
427
  return handshakeResponse;
416
428
  }
417
429
  /**
@@ -459,6 +471,7 @@ class Connection {
459
471
  return handshakeResponse;
460
472
  }
461
473
  catch (e) {
474
+ this.observeHandshake("failed", e);
462
475
  // A failed or timed-out attempt must neither retain its ephemeral secret nor let a
463
476
  // late reply authenticate it. Do not clear state belonging to a newer handshake.
464
477
  if (generation === this.handshakeGeneration)
@@ -522,6 +535,7 @@ class Connection {
522
535
  : remoteDeviceInfo.deviceName,
523
536
  };
524
537
  this._verified = true;
538
+ this.observeHandshake("authenticated");
525
539
  this._connectionAddress = remoteAddress;
526
540
  return handshakeResponse;
527
541
  }
@@ -29,28 +29,6 @@ export interface IRemoteLogsQueryInput {
29
29
  export declare const rpcServerCalls: {
30
30
  encryptData: (value: string, groupId?: string) => Promise<string>;
31
31
  tableMethodCall: (dataContextId: string, tableName: string, methodName: string, ...args: any[]) => Promise<any>;
32
- /**
33
- * @deprecated Use System Files (`SystemFiles`, `readWholeFile`). Kept only for Family Hub
34
- * 0.3.x; deleted once its port is Home's version.
35
- */
36
- getFileContentsBase64: (fileId: string) => Promise<string>;
37
- /**
38
- * @deprecated Use System Files (`SystemFiles`). Kept only for Family Hub 0.3.x; deleted once
39
- * its port is Home's version.
40
- */
41
- saveFile: (input: {
42
- name: string;
43
- mimeType?: string;
44
- data: string;
45
- encoding?: "utf8" | "base64";
46
- fileId?: string;
47
- }) => Promise<{
48
- fileId: string;
49
- name: string;
50
- fileSize: number;
51
- fileHash: string;
52
- mimeType?: string;
53
- }>;
54
32
  deleteLocalDatabase: () => Promise<void>;
55
33
  /** Returns info about the pre-migration data directory, or `null` if already cleaned up. */
56
34
  getOldDataDirInfo: () => Promise<{
package/dist/rpc-types.js CHANGED
@@ -14,16 +14,6 @@ function rpcStub(rpcName) {
14
14
  exports.rpcServerCalls = {
15
15
  encryptData: rpcStub("encryptData"),
16
16
  tableMethodCall: rpcStub("tableMethodCall"),
17
- /**
18
- * @deprecated Use System Files (`SystemFiles`, `readWholeFile`). Kept only for Family Hub
19
- * 0.3.x; deleted once its port is Home's version.
20
- */
21
- getFileContentsBase64: rpcStub("getFileContentsBase64"),
22
- /**
23
- * @deprecated Use System Files (`SystemFiles`). Kept only for Family Hub 0.3.x; deleted once
24
- * its port is Home's version.
25
- */
26
- saveFile: rpcStub("saveFile"),
27
17
  deleteLocalDatabase: rpcStub("deleteLocalDatabase"),
28
18
  /** Returns info about the pre-migration data directory, or `null` if already cleaned up. */
29
19
  getOldDataDirInfo: rpcStub("getOldDataDirInfo"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-sdk",
3
- "version": "0.30.2",
3
+ "version": "0.30.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-sdk.git"