@peers-app/peers-sdk 0.20.4 → 0.20.6

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.
@@ -1,41 +1,77 @@
1
- import type { GroupMemberRole, IChangeRecord } from "../data";
1
+ import type { GroupMemberRole, IChangeRecord, IDeviceInfo } from "../data";
2
2
  import type { DataFilter, IDataQueryParams } from "../data/orm";
3
+ import type { ISignedObject } from "../keys";
4
+ /** A remotely addressable Peers device that can exchange data and mesh messages. */
3
5
  export interface IPeerDevice {
6
+ /** Stable identifier for this device installation. */
4
7
  deviceId: string;
8
+ /** User identity that owns this device. */
5
9
  userId: string;
10
+ /** Role used when this device participates in a shared group. */
6
11
  role: GroupMemberRole;
12
+ /** Lists change records available from this device. */
7
13
  listChanges(filter?: DataFilter<IChangeRecord>, opts?: IDataQueryParams<IChangeRecord>): Promise<IChangeRecord[]>;
14
+ /** Returns current connectivity and resource information for this device. */
8
15
  getNetworkInfo(): Promise<INetworkInfo>;
16
+ /** Reports the latest change timestamp applied from another device. */
9
17
  notifyOfChanges(deviceId: string, timestampLastApplied: number): Promise<void>;
18
+ /** Sends a routed message to another device. */
10
19
  sendDeviceMessage(message: IDeviceMessage): Promise<any>;
11
20
  }
21
+ /** A signed, routable message exchanged through the Peers device mesh. */
12
22
  export interface IDeviceMessage<T = any> {
23
+ /** Unique identifier used for deduplication and retention. */
13
24
  deviceMessageId: string;
25
+ /** Device that originated the message. */
14
26
  fromDeviceId: string;
27
+ /** Signed source identity used to bootstrap fresh same-user devices. */
28
+ fromDeviceInfo?: ISignedObject<IDeviceInfo>;
29
+ /** Intended destination device. */
15
30
  toDeviceId: string;
31
+ /** Personal or group data context in which the message is valid. */
16
32
  dataContextId: string;
33
+ /** Remaining forwarding hops. */
17
34
  ttl: number;
35
+ /** Signed or boxed application payload while in transit. */
18
36
  payload: T;
37
+ /** Device IDs through which the message has already passed. */
19
38
  hops: string[];
39
+ /** Optional preferred route supplied by routing intelligence. */
20
40
  suggestedPath?: string[];
21
41
  }
42
+ /** Connectivity, synchronization, and resource snapshot reported by a device. */
22
43
  export interface INetworkInfo {
44
+ /** Device that produced this snapshot. */
23
45
  deviceId: string;
46
+ /** Latest change timestamp applied by the reporting device. */
24
47
  timestampLastApplied: number;
48
+ /** Current direct device connections. */
25
49
  connections: IDeviceConnection[];
50
+ /** Device IDs this device prefers to connect to. */
26
51
  preferredDeviceIds: string[];
52
+ /** Current CPU utilization percentage. */
27
53
  cpuPercent: number;
54
+ /** Current memory utilization percentage. */
28
55
  memPercent: number;
56
+ /** Number of additional direct connections available. */
29
57
  connectionSlotsAvailable: number;
30
58
  }
59
+ /** Health and synchronization metadata for one direct device connection. */
31
60
  export interface IDeviceConnection {
61
+ /** Connected remote device. */
32
62
  deviceId: string;
63
+ /** Observed round-trip latency in milliseconds. */
33
64
  latencyMs: number;
65
+ /** Observed request error rate. */
34
66
  errorRate: number;
67
+ /** Latest change timestamp applied by the remote device. */
35
68
  timestampLastApplied: number;
69
+ /** Optional cleanup callback for closing the connection. */
36
70
  onClose?(): Promise<void>;
71
+ /** Whether the connection has already closed. */
37
72
  closed?: boolean;
38
73
  }
74
+ /** Shared tuning constants for device synchronization and routing. */
39
75
  export declare const PeerDeviceConsts: Readonly<{
40
76
  readonly MAX_CONNECTIONS: 30;
41
77
  readonly RESYNC_INTERVAL: 300000;
@@ -46,6 +82,7 @@ export declare const PeerDeviceConsts: Readonly<{
46
82
  readonly TIMEOUT_MAX: 30000;
47
83
  readonly NETWORK_INFO_CACHE_TIME: 1000;
48
84
  }>;
85
+ /** Availability and queue information for a requested file chunk. */
49
86
  export type IFileChunkInfo = {
50
87
  hasChunk: false;
51
88
  } | {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PeerDeviceConsts = void 0;
4
+ /** Shared tuning constants for device synchronization and routing. */
4
5
  exports.PeerDeviceConsts = Object.freeze({
5
6
  // TODO set this based on device type (desktops and servers should be able to handle 100 connections)
6
7
  MAX_CONNECTIONS: 30,
@@ -1,3 +1,20 @@
1
- export declare const userConnectStatus: import("../data/persistent-vars").PersistentVar<any>;
1
+ import { type UserConnectStatus } from "./user-connect.types";
2
+ /**
3
+ * Shared lifetime for connection codes, aliases, invite intents, and UI countdowns.
4
+ * Ten minutes from creation or capture.
5
+ */
6
+ export declare const USER_CONNECT_TTL_MS = 600000;
7
+ /**
8
+ * Current structured status of the local user-connect ceremony.
9
+ * Prefer reading through {@link normalizeUserConnectStatus} when consuming
10
+ * values that may have been persisted before the structured type existed.
11
+ */
12
+ export declare const userConnectStatus: import("../data/persistent-vars").PersistentVar<UserConnectStatus>;
13
+ /**
14
+ * Active offer connection code (12 Crockford chars). Cleared after {@link USER_CONNECT_TTL_MS}.
15
+ */
2
16
  export declare const userConnectCodeOffer: import("../data/persistent-vars").PersistentVar<string>;
17
+ /**
18
+ * Active answer connection code (12 Crockford chars). Cleared after {@link USER_CONNECT_TTL_MS}.
19
+ */
3
20
  export declare const userConnectCodeAnswer: import("../data/persistent-vars").PersistentVar<string>;
@@ -1,35 +1,63 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.userConnectCodeAnswer = exports.userConnectCodeOffer = exports.userConnectStatus = void 0;
3
+ exports.userConnectCodeAnswer = exports.userConnectCodeOffer = exports.userConnectStatus = exports.USER_CONNECT_TTL_MS = void 0;
4
4
  const persistent_vars_1 = require("../data/persistent-vars");
5
- exports.userConnectStatus = (0, persistent_vars_1.deviceVar)("userConnectStatus", { defaultValue: undefined });
5
+ const user_connect_types_1 = require("./user-connect.types");
6
+ /**
7
+ * Shared lifetime for connection codes, aliases, invite intents, and UI countdowns.
8
+ * Ten minutes from creation or capture.
9
+ */
10
+ exports.USER_CONNECT_TTL_MS = 600_000;
11
+ /**
12
+ * Current structured status of the local user-connect ceremony.
13
+ * Prefer reading through {@link normalizeUserConnectStatus} when consuming
14
+ * values that may have been persisted before the structured type existed.
15
+ */
16
+ exports.userConnectStatus = (0, persistent_vars_1.deviceVar)("userConnectStatus", {
17
+ defaultValue: user_connect_types_1.USER_CONNECT_IDLE_STATUS,
18
+ });
19
+ /**
20
+ * Active offer connection code (12 Crockford chars). Cleared after {@link USER_CONNECT_TTL_MS}.
21
+ */
6
22
  exports.userConnectCodeOffer = (0, persistent_vars_1.deviceVar)("userConnectCodeOffer", { defaultValue: "" });
7
23
  exports.userConnectCodeOffer.loadingPromise.then(() => {
8
24
  let codeTimeout;
9
- (0, exports.userConnectStatus)("");
25
+ if ((0, exports.userConnectCodeOffer)()) {
26
+ (0, exports.userConnectCodeOffer)("");
27
+ }
28
+ (0, exports.userConnectStatus)(user_connect_types_1.USER_CONNECT_IDLE_STATUS);
10
29
  exports.userConnectCodeOffer.subscribe(() => {
11
- (0, exports.userConnectStatus)("");
30
+ (0, exports.userConnectStatus)(user_connect_types_1.USER_CONNECT_IDLE_STATUS);
12
31
  clearTimeout(codeTimeout);
13
32
  if ((0, exports.userConnectCodeOffer)()) {
14
33
  codeTimeout = setTimeout(() => {
15
34
  (0, exports.userConnectCodeOffer)("");
16
- }, 600_000); // 10 minutes
35
+ }, exports.USER_CONNECT_TTL_MS);
17
36
  }
18
37
  });
19
38
  });
39
+ /**
40
+ * Active answer connection code (12 Crockford chars). Cleared after {@link USER_CONNECT_TTL_MS}.
41
+ */
20
42
  exports.userConnectCodeAnswer = (0, persistent_vars_1.deviceVar)("userConnectCodeAnswer", {
21
43
  defaultValue: "",
22
44
  });
23
45
  exports.userConnectCodeAnswer.loadingPromise.then(() => {
24
46
  let codeTimeout;
25
- (0, exports.userConnectStatus)("");
47
+ if ((0, exports.userConnectCodeAnswer)()) {
48
+ (0, exports.userConnectCodeAnswer)("");
49
+ }
50
+ (0, exports.userConnectStatus)(user_connect_types_1.USER_CONNECT_IDLE_STATUS);
26
51
  exports.userConnectCodeAnswer.subscribe(() => {
27
- (0, exports.userConnectStatus)("");
52
+ (0, exports.userConnectStatus)(user_connect_types_1.USER_CONNECT_IDLE_STATUS);
28
53
  clearTimeout(codeTimeout);
29
54
  if ((0, exports.userConnectCodeAnswer)()) {
30
55
  codeTimeout = setTimeout(() => {
31
56
  (0, exports.userConnectCodeAnswer)("");
32
- }, 600_000); // 10 minutes
57
+ }, exports.USER_CONNECT_TTL_MS);
33
58
  }
34
59
  });
35
60
  });
61
+ exports.userConnectStatus.loadingPromise.then(() => {
62
+ (0, exports.userConnectStatus)(user_connect_types_1.USER_CONNECT_IDLE_STATUS);
63
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const user_connect_types_1 = require("./user-connect.types");
4
+ describe("normalizeUserConnectStatus", () => {
5
+ it("clears legacy string and malformed persisted statuses", () => {
6
+ expect((0, user_connect_types_1.normalizeUserConnectStatus)("")).toEqual(user_connect_types_1.USER_CONNECT_IDLE_STATUS);
7
+ expect((0, user_connect_types_1.normalizeUserConnectStatus)("Error: old error")).toEqual(user_connect_types_1.USER_CONNECT_IDLE_STATUS);
8
+ expect((0, user_connect_types_1.normalizeUserConnectStatus)("old-remote-user-id")).toEqual(user_connect_types_1.USER_CONNECT_IDLE_STATUS);
9
+ expect((0, user_connect_types_1.normalizeUserConnectStatus)(null)).toEqual(user_connect_types_1.USER_CONNECT_IDLE_STATUS);
10
+ expect((0, user_connect_types_1.normalizeUserConnectStatus)({ kind: "connected" })).toEqual(user_connect_types_1.USER_CONNECT_IDLE_STATUS);
11
+ expect((0, user_connect_types_1.normalizeUserConnectStatus)({ kind: "error", code: "unknown" })).toEqual(user_connect_types_1.USER_CONNECT_IDLE_STATUS);
12
+ });
13
+ it("preserves valid structured statuses", () => {
14
+ expect((0, user_connect_types_1.normalizeUserConnectStatus)({ kind: "offer-ready" })).toEqual({
15
+ kind: "offer-ready",
16
+ });
17
+ expect((0, user_connect_types_1.normalizeUserConnectStatus)({ kind: "connecting", attempt: 3 })).toEqual({
18
+ kind: "connecting",
19
+ attempt: 3,
20
+ });
21
+ expect((0, user_connect_types_1.normalizeUserConnectStatus)({
22
+ kind: "connected",
23
+ remoteUserId: "remote-user",
24
+ remoteDeviceId: "remote-device",
25
+ })).toEqual({
26
+ kind: "connected",
27
+ remoteUserId: "remote-user",
28
+ remoteDeviceId: "remote-device",
29
+ });
30
+ expect((0, user_connect_types_1.normalizeUserConnectStatus)({
31
+ kind: "error",
32
+ code: "remote-not-waiting",
33
+ detail: "diagnostic",
34
+ })).toEqual({
35
+ kind: "error",
36
+ code: "remote-not-waiting",
37
+ detail: "diagnostic",
38
+ });
39
+ });
40
+ it("normalizes invalid connection attempt numbers to the first attempt", () => {
41
+ expect((0, user_connect_types_1.normalizeUserConnectStatus)({ kind: "connecting", attempt: 0 })).toEqual({
42
+ kind: "connecting",
43
+ attempt: 1,
44
+ });
45
+ });
46
+ });
@@ -4,6 +4,42 @@
4
4
  * This flow allows two users to securely exchange user information
5
5
  * using a 12-character code (4-char rendezvous alias + 8-char shared secret).
6
6
  */
7
+ /**
8
+ * Stable protocol error codes for the user-connect exchange.
9
+ * The UI owns presentation text; the device layer reports these codes.
10
+ */
11
+ export type UserConnectErrorCode = "invalid-code" | "no-route" | "not-found-or-expired" | "remote-not-waiting" | "canceled" | "protocol-error";
12
+ /**
13
+ * Discriminated status published by the device layer during a user-connect ceremony.
14
+ */
15
+ export type UserConnectStatus = {
16
+ kind: "idle";
17
+ } | {
18
+ kind: "offer-registering";
19
+ } | {
20
+ kind: "offer-ready";
21
+ } | {
22
+ kind: "connecting";
23
+ attempt: number;
24
+ } | {
25
+ kind: "connected";
26
+ remoteUserId: string;
27
+ remoteDeviceId: string;
28
+ } | {
29
+ kind: "error";
30
+ code: UserConnectErrorCode;
31
+ detail?: string;
32
+ };
33
+ /** Default idle status for a quiet connection ceremony. */
34
+ export declare const USER_CONNECT_IDLE_STATUS: UserConnectStatus;
35
+ /**
36
+ * Normalize a persisted or in-memory status value to the structured shape.
37
+ * Clears legacy string statuses (`""`, `"Error: …"`, bare user IDs) to idle so
38
+ * callers never treat stale strings as protocol facts.
39
+ * @param value Raw pvar value
40
+ * @returns A valid {@link UserConnectStatus}
41
+ */
42
+ export declare function normalizeUserConnectStatus(value: unknown): UserConnectStatus;
7
43
  /**
8
44
  * User information exchanged during the connection flow.
9
45
  */
@@ -6,3 +6,63 @@
6
6
  * using a 12-character code (4-char rendezvous alias + 8-char shared secret).
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.USER_CONNECT_IDLE_STATUS = void 0;
10
+ exports.normalizeUserConnectStatus = normalizeUserConnectStatus;
11
+ /** Default idle status for a quiet connection ceremony. */
12
+ exports.USER_CONNECT_IDLE_STATUS = { kind: "idle" };
13
+ /**
14
+ * Normalize a persisted or in-memory status value to the structured shape.
15
+ * Clears legacy string statuses (`""`, `"Error: …"`, bare user IDs) to idle so
16
+ * callers never treat stale strings as protocol facts.
17
+ * @param value Raw pvar value
18
+ * @returns A valid {@link UserConnectStatus}
19
+ */
20
+ function normalizeUserConnectStatus(value) {
21
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
22
+ return exports.USER_CONNECT_IDLE_STATUS;
23
+ }
24
+ const candidate = value;
25
+ switch (candidate.kind) {
26
+ case "idle":
27
+ case "offer-registering":
28
+ case "offer-ready":
29
+ return { kind: candidate.kind };
30
+ case "connecting": {
31
+ const attempt = value.attempt;
32
+ return {
33
+ kind: "connecting",
34
+ attempt: typeof attempt === "number" && attempt > 0 ? attempt : 1,
35
+ };
36
+ }
37
+ case "connected": {
38
+ const remoteUserId = value.remoteUserId;
39
+ const remoteDeviceId = value.remoteDeviceId;
40
+ if (typeof remoteUserId === "string" &&
41
+ remoteUserId.length > 0 &&
42
+ typeof remoteDeviceId === "string" &&
43
+ remoteDeviceId.length > 0) {
44
+ return { kind: "connected", remoteUserId, remoteDeviceId };
45
+ }
46
+ return exports.USER_CONNECT_IDLE_STATUS;
47
+ }
48
+ case "error": {
49
+ const code = value.code;
50
+ const detail = value.detail;
51
+ if (code === "invalid-code" ||
52
+ code === "no-route" ||
53
+ code === "not-found-or-expired" ||
54
+ code === "remote-not-waiting" ||
55
+ code === "canceled" ||
56
+ code === "protocol-error") {
57
+ return {
58
+ kind: "error",
59
+ code,
60
+ detail: typeof detail === "string" ? detail : undefined,
61
+ };
62
+ }
63
+ return exports.USER_CONNECT_IDLE_STATUS;
64
+ }
65
+ default:
66
+ return exports.USER_CONNECT_IDLE_STATUS;
67
+ }
68
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-sdk",
3
- "version": "0.20.4",
3
+ "version": "0.20.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-sdk.git"