@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.
- package/dist/contracts/system/mesh-metrics-types.d.ts +148 -0
- package/dist/contracts/system/mesh-metrics-types.js +2 -0
- package/dist/contracts/system/network-contracts.d.ts +8 -0
- package/dist/contracts/system/network-contracts.js +16 -0
- package/dist/data/files/files.js +0 -16
- package/dist/device/connection.d.ts +3 -0
- package/dist/device/connection.js +14 -0
- package/dist/rpc-types.d.ts +0 -22
- package/dist/rpc-types.js +0 -10
- package/package.json +1 -1
|
@@ -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
|
+
}
|
|
@@ -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.",
|
package/dist/data/files/files.js
CHANGED
|
@@ -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
|
}
|
package/dist/rpc-types.d.ts
CHANGED
|
@@ -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"),
|