@peers-app/peers-sdk 0.23.12 → 0.23.13

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.
@@ -7,7 +7,12 @@
7
7
  */
8
8
  import { z } from "zod";
9
9
  import { Emitter, type Event } from "../events";
10
+ import { HybridLogicalClock } from "../hlc";
10
11
  import { type DataFilter, type IDataQueryParams, type ISqlDb, type ITableMetaData, SQLDataSource } from "./orm";
12
+ /**
13
+ * Zod schema for a single granular change-log row in `ChangeTrackingV2`.
14
+ * `appliedAt` is assigned locally at insert time; incoming values are replaced.
15
+ */
11
16
  export declare const changeRecordSchema: z.ZodObject<{
12
17
  changeId: z.ZodEffects<z.ZodString, string, string>;
13
18
  transactionId: z.ZodOptional<z.ZodString>;
@@ -42,13 +47,57 @@ export declare const changeRecordSchema: z.ZodObject<{
42
47
  transactionId?: string | undefined;
43
48
  supersededAt?: number | undefined;
44
49
  }>;
50
+ /**
51
+ * One JSON-patch operation (or root snapshot / delete) stored for synchronization.
52
+ */
45
53
  export type IChangeRecord = z.infer<typeof changeRecordSchema>;
54
+ /**
55
+ * Local-only change log used for peer sync.
56
+ *
57
+ * Owns two {@link HybridLogicalClock} instances for this database:
58
+ * - {@link clock} stamps `createdAt` / `supersededAt` (cross-device LWW)
59
+ * - `appliedClock` stamps `appliedAt` at insert time (per-device sync watermark)
60
+ */
46
61
  export declare class ChangeTrackingTable extends SQLDataSource<IChangeRecord> {
47
62
  readonly dataChangedEmitter: Emitter<IChangeRecord>;
48
63
  readonly dataChanged: Event<IChangeRecord>;
49
- constructor({ db }: {
64
+ /**
65
+ * Hybrid logical clock for author write times (`createdAt`, `supersededAt`).
66
+ * Observes accepted incoming `createdAt` values so later local writes order after them.
67
+ */
68
+ readonly clock: HybridLogicalClock;
69
+ private readonly appliedClock;
70
+ private clockInit;
71
+ /**
72
+ * @param db - SQLite database that stores `ChangeTrackingV2`
73
+ * @param now - Optional physical-time source shared by both clocks (defaults to {@link getTimestamp})
74
+ */
75
+ constructor({ db, now }: {
50
76
  db: ISqlDb;
77
+ now?: () => number;
51
78
  });
79
+ /**
80
+ * Ensures the table exists, seeds both clocks from durable history, and
81
+ * rewrites any far-future `createdAt` rows this device still owns.
82
+ *
83
+ * @param force - Re-run table migration and clock seeding
84
+ */
85
+ initTable(force?: boolean): Promise<boolean>;
86
+ /**
87
+ * Drop the change-tracking table and clear memoized clock seeding so the next
88
+ * {@link initTable} reseeds from an empty (or newly created) table.
89
+ */
90
+ dropTableIfExists(): Promise<void>;
91
+ private seedClocksAndRepair;
92
+ /**
93
+ * Rewrite local rows whose `createdAt` is beyond the change-timestamp bound.
94
+ * `createdAt` is restamped from {@link clock} (near wall time) and `appliedAt`
95
+ * from the unbounded apply clock so peers whose watermark already passed the
96
+ * old value will pull the repaired rows.
97
+ *
98
+ * @returns Number of rows rewritten
99
+ */
100
+ repairFutureTimestamps(): Promise<number>;
52
101
  /**
53
102
  * Efficiently find which recordIds have been deleted.
54
103
  *
@@ -79,6 +128,10 @@ export declare class ChangeTrackingTable extends SQLDataSource<IChangeRecord> {
79
128
  * @returns Array of changeIds with no change records
80
129
  */
81
130
  filterToMissingChangeIds(changeIds: string[]): Promise<string[]>;
131
+ /**
132
+ * Mark every still-active change for `recordId` whose `createdAt` is older
133
+ * than `supersededAt`. Used after a root snapshot, delete, or restore.
134
+ */
82
135
  markAllPriorChangesSuperseded(tableName: string, recordId: string, supersededAt: number): Promise<void>;
83
136
  /**
84
137
  * Batch update supersededAt timestamps for multiple changes.
@@ -99,7 +152,8 @@ export declare class ChangeTrackingTable extends SQLDataSource<IChangeRecord> {
99
152
  */
100
153
  get(id: string): Promise<IChangeRecord | undefined>;
101
154
  /**
102
- * Override to serialize value field to JSON string
155
+ * Insert a change row, assigning `appliedAt` from the local apply clock
156
+ * immediately before the SQL statement so watermarks stay monotonic.
103
157
  */
104
158
  insert(record: IChangeRecord): Promise<IChangeRecord>;
105
159
  /**
@@ -109,6 +163,7 @@ export declare class ChangeTrackingTable extends SQLDataSource<IChangeRecord> {
109
163
  /**
110
164
  * Synchronous insert for use within transactions.
111
165
  * Table must be initialized before calling this method.
166
+ * Assigns `appliedAt` from the local apply clock immediately before the SQL statement.
112
167
  */
113
168
  insertSync(record: IChangeRecord): IChangeRecord;
114
169
  /**
@@ -10,10 +10,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.ChangeTrackingTable = exports.changeRecordSchema = void 0;
11
11
  const zod_1 = require("zod");
12
12
  const events_1 = require("../events");
13
+ const hlc_1 = require("../hlc");
13
14
  const serial_json_1 = require("../serial-json");
15
+ const peer_device_1 = require("../types/peer-device");
14
16
  const zod_types_1 = require("../types/zod-types");
15
17
  const utils_1 = require("../utils");
16
18
  const orm_1 = require("./orm");
19
+ /**
20
+ * Zod schema for a single granular change-log row in `ChangeTrackingV2`.
21
+ * `appliedAt` is assigned locally at insert time; incoming values are replaced.
22
+ */
17
23
  exports.changeRecordSchema = zod_1.z.object({
18
24
  changeId: zod_types_1.zodPeerId,
19
25
  transactionId: zod_1.z.string().optional(), // not used for now
@@ -25,9 +31,11 @@ exports.changeRecordSchema = zod_1.z.object({
25
31
  .any()
26
32
  .nullish()
27
33
  .describe("JSON value - can be any type (object, string, number, boolean, array, null, or undefined)"),
28
- createdAt: zod_1.z.number(),
29
- appliedAt: zod_1.z.number(),
30
- supersededAt: zod_1.z.number().optional(),
34
+ createdAt: zod_1.z.number().describe("Author write time from the hybrid logical clock (LWW key)"),
35
+ appliedAt: zod_1.z
36
+ .number()
37
+ .describe("Local apply / sync-visibility time; assigned at insert, incoming values replaced"),
38
+ supersededAt: zod_1.z.number().optional().describe("When this change was superseded; unset if active"),
31
39
  });
32
40
  const changeTrackingV2TableMetaData = {
33
41
  name: "ChangeTrackingV2",
@@ -47,15 +55,103 @@ const changeTrackingV2TableMetaData = {
47
55
  ],
48
56
  localOnly: true,
49
57
  };
58
+ /**
59
+ * Local-only change log used for peer sync.
60
+ *
61
+ * Owns two {@link HybridLogicalClock} instances for this database:
62
+ * - {@link clock} stamps `createdAt` / `supersededAt` (cross-device LWW)
63
+ * - `appliedClock` stamps `appliedAt` at insert time (per-device sync watermark)
64
+ */
50
65
  class ChangeTrackingTable extends orm_1.SQLDataSource {
51
66
  dataChangedEmitter;
52
67
  dataChanged;
53
- constructor({ db }) {
68
+ /**
69
+ * Hybrid logical clock for author write times (`createdAt`, `supersededAt`).
70
+ * Observes accepted incoming `createdAt` values so later local writes order after them.
71
+ */
72
+ clock;
73
+ appliedClock;
74
+ clockInit;
75
+ /**
76
+ * @param db - SQLite database that stores `ChangeTrackingV2`
77
+ * @param now - Optional physical-time source shared by both clocks (defaults to {@link getTimestamp})
78
+ */
79
+ constructor({ db, now }) {
54
80
  super(db, changeTrackingV2TableMetaData, exports.changeRecordSchema);
81
+ const physicalNow = now ?? utils_1.getTimestamp;
82
+ this.clock = new hlc_1.HybridLogicalClock({ now: physicalNow });
83
+ this.appliedClock = new hlc_1.HybridLogicalClock({
84
+ now: physicalNow,
85
+ maxSkewMs: Number.POSITIVE_INFINITY,
86
+ });
55
87
  const tableName = changeTrackingV2TableMetaData.name;
56
88
  this.dataChangedEmitter = new events_1.Emitter(`${tableName}_DataChanged_${(0, utils_1.newid)()}`);
57
89
  this.dataChanged = this.dataChangedEmitter.event;
58
90
  }
91
+ /**
92
+ * Ensures the table exists, seeds both clocks from durable history, and
93
+ * rewrites any far-future `createdAt` rows this device still owns.
94
+ *
95
+ * @param force - Re-run table migration and clock seeding
96
+ */
97
+ async initTable(force = false) {
98
+ const created = await super.initTable(force);
99
+ if (!this.clockInit || force) {
100
+ this.clockInit = this.seedClocksAndRepair();
101
+ }
102
+ await this.clockInit;
103
+ return created;
104
+ }
105
+ /**
106
+ * Drop the change-tracking table and clear memoized clock seeding so the next
107
+ * {@link initTable} reseeds from an empty (or newly created) table.
108
+ */
109
+ async dropTableIfExists() {
110
+ await super.dropTableIfExists();
111
+ this.clockInit = undefined;
112
+ }
113
+ async seedClocksAndRepair() {
114
+ const physicalNow = this.clock.now();
115
+ const cutoff = physicalNow + peer_device_1.PeerDeviceConsts.CHANGE_TIMESTAMP_TOLERANCE_MS;
116
+ const bounds = await this.db.get(`SELECT
117
+ MAX(CASE WHEN createdAt <= ? THEN createdAt END) as maxCreatedAt,
118
+ MAX(appliedAt) as maxAppliedAt
119
+ FROM "${this.tableName}"`, [cutoff]);
120
+ if (bounds?.maxAppliedAt != null) {
121
+ this.appliedClock.seed(Number(bounds.maxAppliedAt));
122
+ }
123
+ if (bounds?.maxCreatedAt != null) {
124
+ this.clock.seed(Number(bounds.maxCreatedAt));
125
+ }
126
+ await this.repairFutureTimestamps();
127
+ }
128
+ /**
129
+ * Rewrite local rows whose `createdAt` is beyond the change-timestamp bound.
130
+ * `createdAt` is restamped from {@link clock} (near wall time) and `appliedAt`
131
+ * from the unbounded apply clock so peers whose watermark already passed the
132
+ * old value will pull the repaired rows.
133
+ *
134
+ * @returns Number of rows rewritten
135
+ */
136
+ async repairFutureTimestamps() {
137
+ await super.initTable();
138
+ const cutoff = this.clock.now() + peer_device_1.PeerDeviceConsts.CHANGE_TIMESTAMP_TOLERANCE_MS;
139
+ const inBoundMax = await this.db.get(`SELECT MAX(createdAt) as maxCreatedAt FROM "${this.tableName}" WHERE createdAt <= ?`, [cutoff]);
140
+ if (inBoundMax?.maxCreatedAt != null) {
141
+ this.clock.seed(Number(inBoundMax.maxCreatedAt));
142
+ }
143
+ const rows = await this.db.all(`SELECT changeId FROM "${this.tableName}" WHERE createdAt > ? ORDER BY createdAt ASC`, [cutoff]);
144
+ if (rows.length === 0) {
145
+ return 0;
146
+ }
147
+ for (const row of rows) {
148
+ const createdAt = this.clock.tick();
149
+ const appliedAt = this.appliedClock.tick();
150
+ await this.db.exec(`UPDATE "${this.tableName}" SET createdAt = ?, appliedAt = ? WHERE changeId = ?`, [createdAt, appliedAt, row.changeId]);
151
+ }
152
+ console.log(`Repaired ${rows.length} change tracking row(s) with timestamps too far in the future`);
153
+ return rows.length;
154
+ }
59
155
  /**
60
156
  * Efficiently find which recordIds have been deleted.
61
157
  *
@@ -144,6 +240,10 @@ class ChangeTrackingTable extends orm_1.SQLDataSource {
144
240
  `, changeIds);
145
241
  return missingChangeIds.map((d) => d.changeId);
146
242
  }
243
+ /**
244
+ * Mark every still-active change for `recordId` whose `createdAt` is older
245
+ * than `supersededAt`. Used after a root snapshot, delete, or restore.
246
+ */
147
247
  async markAllPriorChangesSuperseded(tableName, recordId, supersededAt) {
148
248
  await this.db.exec(`
149
249
  UPDATE "${this.tableName}"
@@ -193,9 +293,12 @@ class ChangeTrackingTable extends orm_1.SQLDataSource {
193
293
  return record ? this.deserializeValue(record) : undefined;
194
294
  }
195
295
  /**
196
- * Override to serialize value field to JSON string
296
+ * Insert a change row, assigning `appliedAt` from the local apply clock
297
+ * immediately before the SQL statement so watermarks stay monotonic.
197
298
  */
198
299
  async insert(record) {
300
+ await this.initTable();
301
+ record.appliedAt = this.appliedClock.tick();
199
302
  if (record.value !== undefined) {
200
303
  record.value = (0, serial_json_1.toJSONString)(record.value);
201
304
  }
@@ -216,8 +319,10 @@ class ChangeTrackingTable extends orm_1.SQLDataSource {
216
319
  /**
217
320
  * Synchronous insert for use within transactions.
218
321
  * Table must be initialized before calling this method.
322
+ * Assigns `appliedAt` from the local apply clock immediately before the SQL statement.
219
323
  */
220
324
  insertSync(record) {
325
+ record.appliedAt = this.appliedClock.tick();
221
326
  if (record.value !== undefined) {
222
327
  record = { ...record, value: (0, serial_json_1.toJSONString)(record.value) };
223
328
  }
@@ -4,9 +4,9 @@ exports.Connection = void 0;
4
4
  exports.normalizeAddress = normalizeAddress;
5
5
  const data_1 = require("../data");
6
6
  const keys_1 = require("../keys");
7
+ const peer_device_1 = require("../types/peer-device");
7
8
  const utils_1 = require("../utils");
8
9
  const socket_type_1 = require("./socket.type");
9
- const HANDSHAKE_TIMESTAMP_TOLERANCE_MS = 300_000; // 5 minutes
10
10
  /** Authenticated RPC connection between two Peers devices. */
11
11
  class Connection {
12
12
  localDevice;
@@ -26,7 +26,7 @@ class Connection {
26
26
  onHandshakeComplete;
27
27
  timeoutMs = 60_000; // 60 seconds
28
28
  maxChunkSize = socket_type_1.DEFAULT_MAX_CHUNK_SIZE;
29
- handshakeTimestampToleranceMs = HANDSHAKE_TIMESTAMP_TOLERANCE_MS;
29
+ handshakeTimestampToleranceMs = peer_device_1.PeerDeviceConsts.CLOCK_SKEW_TOLERANCE_MS;
30
30
  _connectionAddress;
31
31
  get connectionAddress() {
32
32
  return this._connectionAddress;
package/dist/hlc.d.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Options for {@link HybridLogicalClock}.
3
+ */
4
+ export interface IHybridLogicalClockOptions {
5
+ /**
6
+ * Physical-time source in milliseconds. Defaults to {@link getTimestamp}
7
+ * so HLC ticks stay on the same clock as existing change-log rows and tests.
8
+ * (`Date.now` and `performance.timeOrigin + performance.now` drift apart
9
+ * in long-running processes.) Injected in tests to simulate clock skew.
10
+ */
11
+ now?: () => number;
12
+ /**
13
+ * Incoming timestamps more than this many ms ahead of {@link HybridLogicalClock.now}
14
+ * are ignored by {@link HybridLogicalClock.observe}. Defaults to
15
+ * {@link PeerDeviceConsts.CHANGE_TIMESTAMP_TOLERANCE_MS}.
16
+ */
17
+ maxSkewMs?: number;
18
+ }
19
+ /**
20
+ * Compact hybrid logical clock stored as a single millisecond number.
21
+ *
22
+ * `tick()` is `max(now(), last + 1)` so local timestamps stay close to wall time
23
+ * while remaining strictly monotonic even when the system clock jumps backward.
24
+ * `observe(ts)` raises `last` to a received timestamp so a later local tick
25
+ * always orders after a change this device has already seen.
26
+ */
27
+ export declare class HybridLogicalClock {
28
+ private last;
29
+ private readonly getNow;
30
+ private readonly maxSkewMs;
31
+ /**
32
+ * @param opts - Optional physical-time source and observe-skew bound
33
+ */
34
+ constructor(opts?: IHybridLogicalClockOptions);
35
+ /**
36
+ * Current physical time from the injected `now` function. Does not advance the clock.
37
+ */
38
+ now(): number;
39
+ /**
40
+ * Last assigned or observed value, or `0` before the first tick/seed/observe.
41
+ */
42
+ get value(): number;
43
+ /**
44
+ * Assign the next timestamp: `last = max(now(), last + 1)`.
45
+ *
46
+ * @returns The newly assigned timestamp
47
+ */
48
+ tick(): number;
49
+ /**
50
+ * Incorporate a timestamp received from another device.
51
+ * Values that are not finite, or that exceed `now() + maxSkewMs`, are ignored
52
+ * so one misconfigured peer cannot push this clock into the far future.
53
+ *
54
+ * @param ts - Received timestamp
55
+ * @returns `true` if the clock absorbed `ts`
56
+ */
57
+ observe(ts: number): boolean;
58
+ /**
59
+ * Raise `last` to at least `ts` without consulting the skew bound.
60
+ * Used when seeding from durable local history on startup.
61
+ *
62
+ * @param ts - Known-good timestamp from local storage
63
+ */
64
+ seed(ts: number): void;
65
+ }
package/dist/hlc.js ADDED
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HybridLogicalClock = void 0;
4
+ const peer_device_1 = require("./types/peer-device");
5
+ const utils_1 = require("./utils");
6
+ /**
7
+ * Compact hybrid logical clock stored as a single millisecond number.
8
+ *
9
+ * `tick()` is `max(now(), last + 1)` so local timestamps stay close to wall time
10
+ * while remaining strictly monotonic even when the system clock jumps backward.
11
+ * `observe(ts)` raises `last` to a received timestamp so a later local tick
12
+ * always orders after a change this device has already seen.
13
+ */
14
+ class HybridLogicalClock {
15
+ last = 0;
16
+ getNow;
17
+ maxSkewMs;
18
+ /**
19
+ * @param opts - Optional physical-time source and observe-skew bound
20
+ */
21
+ constructor(opts) {
22
+ this.getNow = opts?.now ?? utils_1.getTimestamp;
23
+ this.maxSkewMs = opts?.maxSkewMs ?? peer_device_1.PeerDeviceConsts.CHANGE_TIMESTAMP_TOLERANCE_MS;
24
+ }
25
+ /**
26
+ * Current physical time from the injected `now` function. Does not advance the clock.
27
+ */
28
+ now() {
29
+ return this.getNow();
30
+ }
31
+ /**
32
+ * Last assigned or observed value, or `0` before the first tick/seed/observe.
33
+ */
34
+ get value() {
35
+ return this.last;
36
+ }
37
+ /**
38
+ * Assign the next timestamp: `last = max(now(), last + 1)`.
39
+ *
40
+ * @returns The newly assigned timestamp
41
+ */
42
+ tick() {
43
+ this.last = Math.max(this.getNow(), this.last + 1);
44
+ return this.last;
45
+ }
46
+ /**
47
+ * Incorporate a timestamp received from another device.
48
+ * Values that are not finite, or that exceed `now() + maxSkewMs`, are ignored
49
+ * so one misconfigured peer cannot push this clock into the far future.
50
+ *
51
+ * @param ts - Received timestamp
52
+ * @returns `true` if the clock absorbed `ts`
53
+ */
54
+ observe(ts) {
55
+ if (!Number.isFinite(ts)) {
56
+ return false;
57
+ }
58
+ if (ts > this.getNow() + this.maxSkewMs) {
59
+ return false;
60
+ }
61
+ this.last = Math.max(this.last, ts);
62
+ return true;
63
+ }
64
+ /**
65
+ * Raise `last` to at least `ts` without consulting the skew bound.
66
+ * Used when seeding from durable local history on startup.
67
+ *
68
+ * @param ts - Known-good timestamp from local storage
69
+ */
70
+ seed(ts) {
71
+ if (!Number.isFinite(ts)) {
72
+ return;
73
+ }
74
+ this.last = Math.max(this.last, ts);
75
+ }
76
+ }
77
+ exports.HybridLogicalClock = HybridLogicalClock;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const hlc_1 = require("./hlc");
4
+ const peer_device_1 = require("./types/peer-device");
5
+ describe("HybridLogicalClock", () => {
6
+ it("is monotonic when now goes backward", () => {
7
+ let t = 1000;
8
+ const clock = new hlc_1.HybridLogicalClock({ now: () => t });
9
+ const first = clock.tick();
10
+ t = 100;
11
+ const second = clock.tick();
12
+ expect(second).toBe(first + 1);
13
+ });
14
+ it("advances by 1 on the same millisecond", () => {
15
+ const clock = new hlc_1.HybridLogicalClock({ now: () => 5000 });
16
+ expect(clock.tick()).toBe(5000);
17
+ expect(clock.tick()).toBe(5001);
18
+ expect(clock.tick()).toBe(5002);
19
+ });
20
+ it("observe advances last so the next tick is after the observed time", () => {
21
+ const clock = new hlc_1.HybridLogicalClock({ now: () => 1000 });
22
+ expect(clock.observe(2500)).toBe(true);
23
+ expect(clock.tick()).toBe(2501);
24
+ });
25
+ it("observe ignores timestamps beyond the skew bound", () => {
26
+ const now = 10_000;
27
+ const clock = new hlc_1.HybridLogicalClock({ now: () => now });
28
+ const future = now + peer_device_1.PeerDeviceConsts.CHANGE_TIMESTAMP_TOLERANCE_MS + 1;
29
+ expect(clock.observe(future)).toBe(false);
30
+ expect(clock.tick()).toBe(now);
31
+ });
32
+ it("seed raises last without going backward", () => {
33
+ const clock = new hlc_1.HybridLogicalClock({ now: () => 1000 });
34
+ clock.seed(2000);
35
+ clock.seed(1500);
36
+ expect(clock.tick()).toBe(2001);
37
+ });
38
+ it("now peeks physical time without advancing", () => {
39
+ const clock = new hlc_1.HybridLogicalClock({ now: () => 42 });
40
+ expect(clock.now()).toBe(42);
41
+ expect(clock.now()).toBe(42);
42
+ expect(clock.value).toBe(0);
43
+ expect(clock.tick()).toBe(42);
44
+ });
45
+ });
package/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ export * from "./device/tx-encoding";
16
16
  export * from "./device-pairing";
17
17
  export * from "./events";
18
18
  export * from "./group-invite";
19
+ export * from "./hlc";
19
20
  export * from "./keys";
20
21
  export * from "./logging";
21
22
  export * from "./mentions";
package/dist/index.js CHANGED
@@ -37,6 +37,7 @@ __exportStar(require("./device/tx-encoding"), exports);
37
37
  __exportStar(require("./device-pairing"), exports);
38
38
  __exportStar(require("./events"), exports);
39
39
  __exportStar(require("./group-invite"), exports);
40
+ __exportStar(require("./hlc"), exports);
40
41
  __exportStar(require("./keys"), exports);
41
42
  __exportStar(require("./logging"), exports);
42
43
  __exportStar(require("./mentions"), exports);
@@ -81,6 +81,14 @@ export declare const PeerDeviceConsts: Readonly<{
81
81
  readonly TIMEOUT_MIN: 3000;
82
82
  readonly TIMEOUT_MAX: 30000;
83
83
  readonly NETWORK_INFO_CACHE_TIME: 1000;
84
+ /** Handshake rejects peers whose wall clock differs by more than this. */
85
+ readonly CLOCK_SKEW_TOLERANCE_MS: 300000;
86
+ /**
87
+ * Incoming change `createdAt` values more than this far ahead of local now
88
+ * are skipped. 2× the handshake bound so multi-hop HLC lead plus allowed
89
+ * skew does not reject legitimate changes.
90
+ */
91
+ readonly CHANGE_TIMESTAMP_TOLERANCE_MS: 600000;
84
92
  }>;
85
93
  /** Availability and queue information for a requested file chunk. */
86
94
  export type IFileChunkInfo = {
@@ -13,4 +13,12 @@ exports.PeerDeviceConsts = Object.freeze({
13
13
  TIMEOUT_MIN: 3_000,
14
14
  TIMEOUT_MAX: 30_000,
15
15
  NETWORK_INFO_CACHE_TIME: 1000,
16
+ /** Handshake rejects peers whose wall clock differs by more than this. */
17
+ CLOCK_SKEW_TOLERANCE_MS: 300_000,
18
+ /**
19
+ * Incoming change `createdAt` values more than this far ahead of local now
20
+ * are skipped. 2× the handshake bound so multi-hop HLC lead plus allowed
21
+ * skew does not reject legitimate changes.
22
+ */
23
+ CHANGE_TIMESTAMP_TOLERANCE_MS: 600_000,
16
24
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-sdk",
3
- "version": "0.23.12",
3
+ "version": "0.23.13",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-sdk.git"