@peers-app/peers-sdk 0.30.3 → 0.30.4
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 +26 -5
- package/dist/device/connection.d.ts +6 -1
- package/dist/device/connection.js +11 -4
- package/dist/device/socket-io-binary-peer.d.ts +1 -0
- package/dist/device/socket-io-binary-peer.js +21 -5
- package/dist/types/peer-device.d.ts +5 -1
- package/package.json +1 -1
|
@@ -5,20 +5,30 @@ export type MeshDialReason = "own-device" | "group-network-info" | "broadcast-an
|
|
|
5
5
|
/** Bounded path categories used for lifetime counters; attempts also retain the address. */
|
|
6
6
|
export type MeshPath = "websocket" | "webrtc" | "cloud" | "discovery";
|
|
7
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";
|
|
8
|
+
export type MeshFailureReason = "ttl0" | "no-route" | "signaling-timeout" | "socket-error" | "socket-timeout" | "handshake" | "rejected" | "closed" | "superseded" | "unsupported" | "sender-not-known" | "trust-refused" | "identity-mismatch" | "cancelled" | "capacity";
|
|
9
9
|
/** Observed stage; timestamps are offsets in milliseconds from the attempt's queued time. */
|
|
10
10
|
export type MeshStage = "queued" | "started" | "authenticated" | "admitted" | "ready" | "firstSync";
|
|
11
|
-
/**
|
|
11
|
+
/** Local dial metadata and cancellation; never serialized into device messages. */
|
|
12
12
|
export interface IMeshDial {
|
|
13
|
+
/** Cancels pending transport work; an admitted winner is retained. */
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
/** Scheduler priority: bootstrap, coverage, repair, then redundancy. */
|
|
16
|
+
priority?: 1 | 2 | 3 | 4;
|
|
13
17
|
attemptId?: string;
|
|
14
18
|
reason?: MeshDialReason;
|
|
15
19
|
targetDeviceId?: string;
|
|
20
|
+
/** One-based retry number; absent for the initial dial. */
|
|
21
|
+
retry?: number;
|
|
16
22
|
}
|
|
17
23
|
/** One dial or inbound connection. Missing stages have not been observed. */
|
|
18
24
|
export interface IMeshAttempt {
|
|
19
25
|
attemptId: string;
|
|
20
26
|
targetDeviceId?: string;
|
|
21
27
|
reason: MeshDialReason;
|
|
28
|
+
/** One-based retry number; absent for initial and incoming attempts. */
|
|
29
|
+
retry?: number;
|
|
30
|
+
/** Scheduler priority when this was planned locally. */
|
|
31
|
+
priority?: 1 | 2 | 3 | 4;
|
|
22
32
|
direction: "outgoing" | "incoming";
|
|
23
33
|
path: MeshPath;
|
|
24
34
|
address?: string;
|
|
@@ -33,7 +43,7 @@ export interface IMeshAttempt {
|
|
|
33
43
|
};
|
|
34
44
|
ready: Record<string, number>;
|
|
35
45
|
firstSync: Record<string, number>;
|
|
36
|
-
outcome: "pending" | "admitted" | "failed" | "unfinished";
|
|
46
|
+
outcome: "pending" | "admitted" | "failed" | "cancelled" | "unfinished";
|
|
37
47
|
endReason?: "buffer-evicted" | "disposed";
|
|
38
48
|
endedAt?: number;
|
|
39
49
|
durationMs?: number;
|
|
@@ -60,6 +70,10 @@ export interface IMeshContextCoverage {
|
|
|
60
70
|
possibleSourceDeviceIds: string[];
|
|
61
71
|
usableDeviceIds: string[];
|
|
62
72
|
excluded: boolean;
|
|
73
|
+
/** True when an unmet context cannot currently fit at the connection cap. */
|
|
74
|
+
limitedByCapacity?: boolean;
|
|
75
|
+
/** Planner target; zero means no possible source. */
|
|
76
|
+
target?: number;
|
|
63
77
|
}
|
|
64
78
|
/** One connectivity episode, independent of any other episode in flight. */
|
|
65
79
|
export interface IMeshEpisode {
|
|
@@ -69,8 +83,8 @@ export interface IMeshEpisode {
|
|
|
69
83
|
startedAt: number;
|
|
70
84
|
endedAt?: number;
|
|
71
85
|
durationMs?: number;
|
|
72
|
-
outcome: "active" | "covered" | "unfinished";
|
|
73
|
-
endReason?: "covered" | "deadline" | "buffer-evicted" | "disposed";
|
|
86
|
+
outcome: "active" | "covered" | "unfinished" | "not-measured";
|
|
87
|
+
endReason?: "covered" | "deadline" | "buffer-evicted" | "disposed" | "page-hidden" | "still-covered";
|
|
74
88
|
metrics: IMeshDurations;
|
|
75
89
|
contexts: IMeshContextCoverage[];
|
|
76
90
|
}
|
|
@@ -121,6 +135,11 @@ export interface IMeshStats {
|
|
|
121
135
|
probes: number;
|
|
122
136
|
connected: number;
|
|
123
137
|
};
|
|
138
|
+
/** Lifetime episodes excluded from latency percentiles, by reason. */
|
|
139
|
+
unmeasuredEpisodes?: {
|
|
140
|
+
pageHidden: number;
|
|
141
|
+
stillCovered: number;
|
|
142
|
+
};
|
|
124
143
|
unfinishedAttempts: number;
|
|
125
144
|
doubleDials: number;
|
|
126
145
|
offlineDials: number;
|
|
@@ -137,6 +156,8 @@ export interface IMeshStats {
|
|
|
137
156
|
};
|
|
138
157
|
recentAttempts: IMeshAttempt[];
|
|
139
158
|
recentBroadcasts: IMeshBroadcast[];
|
|
159
|
+
/** Latest planner coverage, including zero targets and capacity limitations. */
|
|
160
|
+
coverage?: IMeshContextCoverage[];
|
|
140
161
|
}
|
|
141
162
|
/** A discovery broadcast and its observed reply, bounded with the attempt history. */
|
|
142
163
|
export interface IMeshBroadcast {
|
|
@@ -42,6 +42,8 @@ export declare class Connection {
|
|
|
42
42
|
private pendingSendArm;
|
|
43
43
|
/** Bumped by every {@link Connection.reset}, so a handshake can tell it was cut short. */
|
|
44
44
|
private handshakeGeneration;
|
|
45
|
+
/** Local close intent, retained through reset so retry owners do not penalize orderly shedding. */
|
|
46
|
+
closeIntent?: "remote" | "policy";
|
|
45
47
|
constructor(socket: ISocket, localDevice: Device, localDeviceServerAddresses?: string[] | undefined, getTrustLevel?: GetTrustLevel);
|
|
46
48
|
/** True once the remote identity has been authenticated by the handshake. */
|
|
47
49
|
get verified(): boolean;
|
|
@@ -151,5 +153,8 @@ export declare class Connection {
|
|
|
151
153
|
*/
|
|
152
154
|
close(): Promise<void>;
|
|
153
155
|
}
|
|
154
|
-
/**
|
|
156
|
+
/** Lowercases a peer address and removes a trailing slash; preserves its scheme. */
|
|
155
157
|
export declare function normalizeAddress(address: string): string;
|
|
158
|
+
/** Local handshake refusal. Its existing Error text remains compatible with older peers. */
|
|
159
|
+
export declare class ConnectionTrustError extends Error {
|
|
160
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Connection = void 0;
|
|
3
|
+
exports.ConnectionTrustError = exports.Connection = void 0;
|
|
4
4
|
exports.normalizeAddress = normalizeAddress;
|
|
5
5
|
const user_context_singleton_1 = require("../context/user-context-singleton");
|
|
6
6
|
const data_1 = require("../data");
|
|
@@ -62,6 +62,8 @@ class Connection {
|
|
|
62
62
|
pendingSendArm;
|
|
63
63
|
/** Bumped by every {@link Connection.reset}, so a handshake can tell it was cut short. */
|
|
64
64
|
handshakeGeneration = 0;
|
|
65
|
+
/** Local close intent, retained through reset so retry owners do not penalize orderly shedding. */
|
|
66
|
+
closeIntent;
|
|
65
67
|
constructor(socket, localDevice, localDeviceServerAddresses, getTrustLevel = () => Promise.resolve(socket_type_1.TrustLevel.Unknown)) {
|
|
66
68
|
this.localDevice = localDevice;
|
|
67
69
|
this.localDeviceServerAddresses = localDeviceServerAddresses;
|
|
@@ -76,6 +78,7 @@ class Connection {
|
|
|
76
78
|
socket.setPlaintextStreamsAllowed?.(false);
|
|
77
79
|
// Only the server side of a connection should expose these methods
|
|
78
80
|
this.exposeRPC("close", () => {
|
|
81
|
+
this.closeIntent ??= "remote";
|
|
79
82
|
(0, utils_1.sleep)(100).then(() => {
|
|
80
83
|
this.closeLocal();
|
|
81
84
|
});
|
|
@@ -404,7 +407,7 @@ class Connection {
|
|
|
404
407
|
this.assertHandshakeCurrent(generation);
|
|
405
408
|
if (trustLevel < socket_type_1.TrustLevel.Unknown) {
|
|
406
409
|
this.resetRemote();
|
|
407
|
-
throw new
|
|
410
|
+
throw new ConnectionTrustError("Untrusted connection");
|
|
408
411
|
}
|
|
409
412
|
// The client seals everything after it reads our response, so receive
|
|
410
413
|
// can arm now; send arms after the response frame goes out.
|
|
@@ -466,7 +469,7 @@ class Connection {
|
|
|
466
469
|
this.trustLevel = trustLevel;
|
|
467
470
|
if (this.trustLevel < socket_type_1.TrustLevel.Unknown) {
|
|
468
471
|
this.resetRemote();
|
|
469
|
-
throw new
|
|
472
|
+
throw new ConnectionTrustError("Untrusted connection");
|
|
470
473
|
}
|
|
471
474
|
return handshakeResponse;
|
|
472
475
|
}
|
|
@@ -613,7 +616,11 @@ async function noteHandshakeContest(contest) {
|
|
|
613
616
|
console.warn("Could not apply a key contest from the handshake", err);
|
|
614
617
|
}
|
|
615
618
|
}
|
|
616
|
-
/**
|
|
619
|
+
/** Lowercases a peer address and removes a trailing slash; preserves its scheme. */
|
|
617
620
|
function normalizeAddress(address) {
|
|
618
621
|
return address.toLowerCase().replace(/\/$/, "");
|
|
619
622
|
}
|
|
623
|
+
/** Local handshake refusal. Its existing Error text remains compatible with older peers. */
|
|
624
|
+
class ConnectionTrustError extends Error {
|
|
625
|
+
}
|
|
626
|
+
exports.ConnectionTrustError = ConnectionTrustError;
|
|
@@ -14,6 +14,7 @@ import type { IBinaryPeer } from "./binary-peer-connection-v2";
|
|
|
14
14
|
interface ISocketIO {
|
|
15
15
|
id?: string;
|
|
16
16
|
on(event: string, handler: (...args: any[]) => void): void;
|
|
17
|
+
off(event: string, handler: (...args: any[]) => void): void;
|
|
17
18
|
emit(event: string, ...args: any[]): void;
|
|
18
19
|
removeAllListeners(event?: string): void;
|
|
19
20
|
disconnect(): void;
|
|
@@ -22,8 +22,11 @@ function wrapSocketIOAsBinaryPeer(socket) {
|
|
|
22
22
|
const closeHandlers = [];
|
|
23
23
|
const drainHandlers = [];
|
|
24
24
|
let pendingBytes = 0;
|
|
25
|
+
let closed = false;
|
|
25
26
|
// Listen for binary data
|
|
26
|
-
|
|
27
|
+
const onData = (data) => {
|
|
28
|
+
if (closed)
|
|
29
|
+
return;
|
|
27
30
|
// Ensure we have a proper Uint8Array
|
|
28
31
|
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
29
32
|
dataHandlers.forEach((handler) => {
|
|
@@ -34,9 +37,16 @@ function wrapSocketIOAsBinaryPeer(socket) {
|
|
|
34
37
|
console.error("[socket-io-binary] Error in data handler:", err);
|
|
35
38
|
}
|
|
36
39
|
});
|
|
37
|
-
}
|
|
40
|
+
};
|
|
38
41
|
// Listen for disconnect
|
|
39
|
-
|
|
42
|
+
const onDisconnect = () => {
|
|
43
|
+
if (closed)
|
|
44
|
+
return;
|
|
45
|
+
closed = true;
|
|
46
|
+
// This adapter belongs to one connection incarnation. A delayed close from it
|
|
47
|
+
// must never disconnect a reconnected Socket.IO socket or remove its listeners.
|
|
48
|
+
socket.off(BINARY_EVENT, onData);
|
|
49
|
+
socket.off("disconnect", onDisconnect);
|
|
40
50
|
closeHandlers.forEach((handler) => {
|
|
41
51
|
try {
|
|
42
52
|
handler();
|
|
@@ -45,7 +55,9 @@ function wrapSocketIOAsBinaryPeer(socket) {
|
|
|
45
55
|
console.error("[socket-io-binary] Error in close handler:", err);
|
|
46
56
|
}
|
|
47
57
|
});
|
|
48
|
-
}
|
|
58
|
+
};
|
|
59
|
+
socket.on(BINARY_EVENT, onData);
|
|
60
|
+
socket.on("disconnect", onDisconnect);
|
|
49
61
|
return {
|
|
50
62
|
on(event, handler) {
|
|
51
63
|
if (event === "data") {
|
|
@@ -59,6 +71,8 @@ function wrapSocketIOAsBinaryPeer(socket) {
|
|
|
59
71
|
}
|
|
60
72
|
},
|
|
61
73
|
send(data) {
|
|
74
|
+
if (closed)
|
|
75
|
+
return;
|
|
62
76
|
const bytes = typeof data === "string"
|
|
63
77
|
? Buffer.from(data)
|
|
64
78
|
: data instanceof Uint8Array
|
|
@@ -79,7 +93,9 @@ function wrapSocketIOAsBinaryPeer(socket) {
|
|
|
79
93
|
});
|
|
80
94
|
},
|
|
81
95
|
destroy() {
|
|
82
|
-
|
|
96
|
+
if (closed)
|
|
97
|
+
return;
|
|
98
|
+
onDisconnect();
|
|
83
99
|
socket.disconnect();
|
|
84
100
|
},
|
|
85
101
|
getBufferedAmount() {
|
|
@@ -26,7 +26,11 @@ export interface IDeviceMessage<T = any> {
|
|
|
26
26
|
deviceMessageId: string;
|
|
27
27
|
/** Device that originated the message. */
|
|
28
28
|
fromDeviceId: string;
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Signed source identity. Lets a receiver accept a device it hasn't met yet when the device
|
|
31
|
+
* belongs to the receiver's own user (personal context) or to a counted member of the
|
|
32
|
+
* message's group.
|
|
33
|
+
*/
|
|
30
34
|
fromDeviceInfo?: ISignedObject<IDeviceInfo>;
|
|
31
35
|
/** Intended destination device. */
|
|
32
36
|
toDeviceId: string;
|