@interocitor/core 0.0.0-beta.6 → 0.0.0-beta.8
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/README.md +71 -4
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +23 -0
- package/dist/core/sync-engine.d.ts +14 -2
- package/dist/core/sync-engine.d.ts.map +1 -1
- package/dist/core/sync-engine.js +104 -12
- package/dist/handshake/qr-public.d.ts +3 -0
- package/dist/handshake/qr-public.d.ts.map +1 -0
- package/dist/handshake/qr-public.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/storage/reset.d.ts +10 -0
- package/dist/storage/reset.d.ts.map +1 -0
- package/dist/storage/reset.js +18 -0
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -163,6 +163,32 @@ What we do **not** guarantee:
|
|
|
163
163
|
manifest). See `docs/security-model.md`.
|
|
164
164
|
- Recovery if the passphrase is lost — see "New device / restore".
|
|
165
165
|
|
|
166
|
+
### Sync cadence
|
|
167
|
+
|
|
168
|
+
The engine adapts how often it polls the remote so it does useful work
|
|
169
|
+
when there is data to sync, and stays out of the way otherwise. This is
|
|
170
|
+
all automatic — there is no configuration knob.
|
|
171
|
+
|
|
172
|
+
- **Adaptive backoff.** Polling starts at `pollInterval` (30 s by
|
|
173
|
+
default). After every poll that merges zero entries the interval
|
|
174
|
+
doubles, capped at 60 s. Any poll that merges ≥ 1 entry resets the
|
|
175
|
+
interval back to base. The intent is to absorb idle bursts of clients
|
|
176
|
+
without hammering the remote, while still recovering immediately when
|
|
177
|
+
data starts flowing.
|
|
178
|
+
- **Tab visibility.** When the host page is hidden
|
|
179
|
+
(`document.visibilityState === 'hidden'`) the current poll interval is
|
|
180
|
+
multiplied by 10 — backgrounded tabs poll lazily. When the tab becomes
|
|
181
|
+
visible again the interval is reset to base **and** an immediate
|
|
182
|
+
`pull()` is fired so foregrounded data jumps in without waiting for
|
|
183
|
+
the next tick. This is a standard SWR‑style refresh‑on‑focus pattern.
|
|
184
|
+
The listener is wired during `connect()` and torn down by
|
|
185
|
+
`disconnect()`; environments without `document` (e.g. Node) skip it.
|
|
186
|
+
- **Push fallback.** Adapters that push invalidations (Cloudflare relay)
|
|
187
|
+
trigger an immediate pull and bypass the polling cadence entirely.
|
|
188
|
+
Polling is the safety net when the push channel is unavailable; the
|
|
189
|
+
WS connection itself uses bounded retries with a long cooldown after
|
|
190
|
+
repeated failed upgrades, so a misbehaving relay cannot DDoS itself.
|
|
191
|
+
|
|
166
192
|
## Setup lifecycle
|
|
167
193
|
|
|
168
194
|
```
|
|
@@ -210,15 +236,56 @@ Joining a new device to an existing mesh requires three things:
|
|
|
210
236
|
3. Access to the same **remote mailbox** (the same `remotePath` on a
|
|
211
237
|
storage backend the new device can reach).
|
|
212
238
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
239
|
+
A production app should keep CRUD, sync lifecycle, and pairing separate:
|
|
240
|
+
|
|
241
|
+
```text
|
|
242
|
+
lib/interocitor-db.ts engine, schema, local repository, credential primitives
|
|
243
|
+
lib/interocitor-sync.ts mesh id lifecycle, adapter, connect/disconnect, recovery
|
|
244
|
+
lib/interocitor-pairing.ts QR handshake only
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The pairing flow ships credentials over an ECDH relay handshake. The handshake relay base and sync adapter base are different concepts: a relay base is the temporary handshake-file path, such as `/Taska`; the Cloudflare adapter base URL is the concrete Worker route, such as `/sync/io/{meshId}`. The engine `remotePath` is still the mesh folder path used inside that adapter, such as `/Taska`.
|
|
248
|
+
|
|
249
|
+
### Pairing intents
|
|
250
|
+
|
|
251
|
+
**Join QR** is for a device that does not have credentials yet:
|
|
252
|
+
|
|
253
|
+
1. Joiner mints a fresh mesh id.
|
|
254
|
+
2. Joiner creates a Cloudflare adapter with base URL `/sync/io/{meshId}`.
|
|
255
|
+
3. Joiner calls `generateJoinQR()`.
|
|
256
|
+
4. Existing paired device scans and pushes credentials.
|
|
257
|
+
5. Joiner receives credentials from `credentials` on the result.
|
|
258
|
+
6. Joiner applies the passphrase and connects to the minted mesh.
|
|
259
|
+
|
|
260
|
+
**Share QR** is for an existing mesh member inviting a new device:
|
|
261
|
+
|
|
262
|
+
1. Existing device connects to the active mesh.
|
|
263
|
+
2. Existing device calls `generateShareQR()` with `remotePath` and `passphrase`.
|
|
264
|
+
3. New device scans and receives credentials from `handleScannedQR()`.
|
|
265
|
+
4. New device applies credentials and connects using the adapter config from the payload.
|
|
266
|
+
|
|
267
|
+
`handleScannedQR()` returns `null` for join intent because the scanner pushed its own credentials. It returns credentials for share intent because the scanner received credentials. Always handle the return value:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
import { decodeQRPayload, handleScannedQR, parseQRFromUrl } from '@interocitor/core';
|
|
271
|
+
// Raw QR payload decoder is also available from '@interocitor/core/handshake/qr'.
|
|
272
|
+
|
|
273
|
+
const payload = parseQRFromUrl(location.hash) ?? decodeQRPayload(rawPastedPayload);
|
|
274
|
+
const received = await handleScannedQR({ adapter, relayBase: '/Taska', payload });
|
|
275
|
+
|
|
276
|
+
if (received) {
|
|
277
|
+
if (received.passphrase) db.setPassphrase(received.passphrase);
|
|
278
|
+
await connectFromPayload(received.remotePath);
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
After the handshake the new device:
|
|
216
283
|
|
|
217
284
|
```ts
|
|
218
285
|
const db = new Interocitor(adapter, {
|
|
219
286
|
dbName: 'my-app',
|
|
220
287
|
appName: 'My App',
|
|
221
|
-
remotePath: '/
|
|
288
|
+
remotePath: '/Taska',
|
|
222
289
|
passphrase: 'base58-from-handshake',
|
|
223
290
|
encrypted: true,
|
|
224
291
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/adapters/cloudflare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAEtH,MAAM,WAAW,uBAAuB;IACtC,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAUD;;;;;;;;;;;;;GAaG;AACH,wFAAwF;AACxF,MAAM,WAAW,yBAAyB;IACxC,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,iBAAkB,YAAW,cAAc;IACtD,QAAQ,CAAC,IAAI,gBAAgB;IAE7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IACjD,OAAO,CAAC,aAAa,CAAS;IAM9B,OAAO,CAAC,cAAc,CAA0B;gBAEpC,MAAM,EAAE,uBAAuB;IAI3C,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,YAAY;IAWpB,OAAO,KAAK,SAAS,GAMpB;IAED,OAAO,KAAK,SAAS,GAWpB;IAED,OAAO,CAAC,KAAK;IAKb,OAAO,CAAC,OAAO;IAMT,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBnC;;;OAGG;IACH,kBAAkB,IAAI,MAAM;IAK5B,eAAe,IAAI,OAAO;IAI1B,wBAAwB,CACtB,YAAY,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,EAC1D,KAAK,CAAC,EAAE,uBAAuB,GAC9B,MAAM,IAAI;
|
|
1
|
+
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/adapters/cloudflare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAEtH,MAAM,WAAW,uBAAuB;IACtC,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAUD;;;;;;;;;;;;;GAaG;AACH,wFAAwF;AACxF,MAAM,WAAW,yBAAyB;IACxC,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,iBAAkB,YAAW,cAAc;IACtD,QAAQ,CAAC,IAAI,gBAAgB;IAE7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IACjD,OAAO,CAAC,aAAa,CAAS;IAM9B,OAAO,CAAC,cAAc,CAA0B;gBAEpC,MAAM,EAAE,uBAAuB;IAI3C,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,YAAY;IAWpB,OAAO,KAAK,SAAS,GAMpB;IAED,OAAO,KAAK,SAAS,GAWpB;IAED,OAAO,CAAC,KAAK;IAKb,OAAO,CAAC,OAAO;IAMT,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBnC;;;OAGG;IACH,kBAAkB,IAAI,MAAM;IAK5B,eAAe,IAAI,OAAO;IAI1B,wBAAwB,CACtB,YAAY,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,EAC1D,KAAK,CAAC,EAAE,uBAAuB,GAC9B,MAAM,IAAI;IAsFP,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAe/C;;0BAEsB;IACtB,gBAAgB,IAAI,IAAI;IAIlB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAqB7C,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAe5C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAa3C,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcjE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;CAsB/D"}
|
|
@@ -101,18 +101,29 @@ export class CloudflareAdapter {
|
|
|
101
101
|
let cancelled = false;
|
|
102
102
|
let backoffMs = 1000;
|
|
103
103
|
const MAX_BACKOFF_MS = 30000;
|
|
104
|
+
// Attempts where the socket closed before ever opening (failed upgrade).
|
|
105
|
+
// After MAX_FAILED_UPGRADES consecutive such failures we back off to a long
|
|
106
|
+
// cooldown interval rather than hammering (e.g. DO free-tier exhaustion).
|
|
107
|
+
// Once the cooldown elapses we try again — self-healing if the server recovers.
|
|
108
|
+
let failedUpgradeStreak = 0;
|
|
109
|
+
const MAX_FAILED_UPGRADES = 5;
|
|
110
|
+
const COOLDOWN_MS = 60 * 60 * 1000; // 1 hour
|
|
104
111
|
const connect = () => {
|
|
105
112
|
if (cancelled)
|
|
106
113
|
return;
|
|
114
|
+
let opened = false;
|
|
107
115
|
try {
|
|
108
116
|
ws = new WebSocket(this.notifyUrl);
|
|
109
117
|
}
|
|
110
118
|
catch (error) {
|
|
111
119
|
hooks?.onError?.(error);
|
|
120
|
+
failedUpgradeStreak++;
|
|
112
121
|
scheduleReconnect();
|
|
113
122
|
return;
|
|
114
123
|
}
|
|
115
124
|
ws.onopen = () => {
|
|
125
|
+
opened = true;
|
|
126
|
+
failedUpgradeStreak = 0;
|
|
116
127
|
backoffMs = 1000;
|
|
117
128
|
hooks?.onReady?.();
|
|
118
129
|
};
|
|
@@ -131,8 +142,20 @@ export class CloudflareAdapter {
|
|
|
131
142
|
hooks?.onError?.(event);
|
|
132
143
|
};
|
|
133
144
|
ws.onclose = () => {
|
|
145
|
+
if (!opened) {
|
|
146
|
+
// The upgrade itself failed (server returned non-101, e.g. 500/503).
|
|
147
|
+
failedUpgradeStreak++;
|
|
148
|
+
}
|
|
134
149
|
if (!cancelled) {
|
|
135
150
|
hooks?.onClose?.();
|
|
151
|
+
if (failedUpgradeStreak >= MAX_FAILED_UPGRADES) {
|
|
152
|
+
// Back off to a long cooldown then try again — self-healing if the
|
|
153
|
+
// server recovers (e.g. DO free-tier resets).
|
|
154
|
+
failedUpgradeStreak = 0;
|
|
155
|
+
backoffMs = 1000;
|
|
156
|
+
setTimeout(() => connect(), COOLDOWN_MS);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
136
159
|
scheduleReconnect();
|
|
137
160
|
}
|
|
138
161
|
};
|
|
@@ -69,6 +69,10 @@ export declare class Interocitor<S extends Record<string, Record<string, unknown
|
|
|
69
69
|
private batchTimer;
|
|
70
70
|
private pendingBatch;
|
|
71
71
|
private pollTimer;
|
|
72
|
+
private pollBaseIntervalMs;
|
|
73
|
+
private pollCurrentIntervalMs;
|
|
74
|
+
private pollGeneration;
|
|
75
|
+
private visibilityChangeListener;
|
|
72
76
|
private unsubscribeRemoteInvalidations;
|
|
73
77
|
private remoteInvalidationPullPromise;
|
|
74
78
|
private remoteInvalidationPullQueued;
|
|
@@ -176,6 +180,8 @@ export declare class Interocitor<S extends Record<string, Record<string, unknown
|
|
|
176
180
|
private jitterDelay;
|
|
177
181
|
private stopPolling;
|
|
178
182
|
private startPolling;
|
|
183
|
+
/** Called by emit() to adapt the poll interval based on pull activity. */
|
|
184
|
+
private adaptPollInterval;
|
|
179
185
|
private stopRemoteInvalidations;
|
|
180
186
|
private startRemoteInvalidations;
|
|
181
187
|
private getRowHlc;
|
|
@@ -202,8 +208,12 @@ export declare class Interocitor<S extends Record<string, Record<string, unknown
|
|
|
202
208
|
private applyInitialState;
|
|
203
209
|
configureMesh(state: SyncInitialState): void;
|
|
204
210
|
/**
|
|
205
|
-
* Set the mesh encryption passphrase.
|
|
206
|
-
*
|
|
211
|
+
* Set or replace the mesh encryption passphrase before connecting.
|
|
212
|
+
*
|
|
213
|
+
* Apps may call this before init(), after init(), or after credentials
|
|
214
|
+
* arrive from pairing. It intentionally does not rebuild the engine or
|
|
215
|
+
* change remotePath/deviceId; it only invalidates the derived key so the
|
|
216
|
+
* next connect/flush uses the new passphrase.
|
|
207
217
|
*/
|
|
208
218
|
setPassphrase(passphrase: string): void;
|
|
209
219
|
/**
|
|
@@ -250,6 +260,8 @@ export declare class Interocitor<S extends Record<string, Record<string, unknown
|
|
|
250
260
|
connect(): Promise<void>;
|
|
251
261
|
private tryConnectFastPath;
|
|
252
262
|
private doConnect;
|
|
263
|
+
private startVisibilityTracking;
|
|
264
|
+
private stopVisibilityTracking;
|
|
253
265
|
disconnect(): Promise<void>;
|
|
254
266
|
setRemoteStorage(adapter: StorageAdapter | null): Promise<void>;
|
|
255
267
|
setLocalStorage(local: LocalStoreAdapter): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-engine.d.ts","sourceRoot":"","sources":["../../src/core/sync-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,iBAAiB,EAEjB,QAAQ,EACR,GAAG,EAIH,iBAAiB,EAEjB,WAAW,EAGX,gBAAgB,EAEhB,eAAe,EACf,qBAAqB,EACrB,kBAAkB,EAClB,2BAA2B,EAC3B,aAAa,EACb,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,KAAK,EAAmB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"sync-engine.d.ts","sourceRoot":"","sources":["../../src/core/sync-engine.ts"],"names":[],"mappings":"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"}
|
package/dist/core/sync-engine.js
CHANGED
|
@@ -23,6 +23,7 @@ import { flushToAdapter } from "./flush.js";
|
|
|
23
23
|
import { pull as doPull } from "./pull.js";
|
|
24
24
|
import { compact as doCompact, rehydrate as doRehydrate } from "./compaction.js";
|
|
25
25
|
const DEFAULT_COMPACT_WARNING_THRESHOLD = 50;
|
|
26
|
+
const POLL_BACKGROUND_MULTIPLIER = 10;
|
|
26
27
|
const DEFAULT_COMPACT_AUTO_THRESHOLD = 50;
|
|
27
28
|
const DEFAULT_COMPACT_AUTO_SAMPLE_NUMERATOR = 10;
|
|
28
29
|
const DEFAULT_COMPACT_AUTO_DEVICE_COUNT = 1;
|
|
@@ -56,6 +57,10 @@ export class Interocitor {
|
|
|
56
57
|
this.pendingBatch = null;
|
|
57
58
|
// Poll / push invalidation management
|
|
58
59
|
this.pollTimer = null;
|
|
60
|
+
this.pollBaseIntervalMs = 0;
|
|
61
|
+
this.pollCurrentIntervalMs = 0;
|
|
62
|
+
this.pollGeneration = {};
|
|
63
|
+
this.visibilityChangeListener = null;
|
|
59
64
|
this.unsubscribeRemoteInvalidations = null;
|
|
60
65
|
this.remoteInvalidationPullPromise = null;
|
|
61
66
|
this.remoteInvalidationPullQueued = false;
|
|
@@ -570,15 +575,51 @@ export class Interocitor {
|
|
|
570
575
|
}
|
|
571
576
|
stopPolling() {
|
|
572
577
|
if (this.pollTimer) {
|
|
573
|
-
|
|
578
|
+
clearTimeout(this.pollTimer);
|
|
574
579
|
this.pollTimer = null;
|
|
575
580
|
}
|
|
581
|
+
this.pollGeneration = {}; // invalidate any in-flight pull's generation
|
|
582
|
+
this.pollBaseIntervalMs = 0;
|
|
576
583
|
}
|
|
577
584
|
startPolling(intervalMs = this.config.pollInterval) {
|
|
578
585
|
this.stopPolling();
|
|
579
|
-
this.
|
|
580
|
-
|
|
581
|
-
|
|
586
|
+
this.pollBaseIntervalMs = intervalMs;
|
|
587
|
+
this.pollCurrentIntervalMs = intervalMs;
|
|
588
|
+
// Each startPolling call gets its own generation token so that in-flight
|
|
589
|
+
// pulls from a previous polling session don't reschedule after stopPolling.
|
|
590
|
+
const generation = {};
|
|
591
|
+
this.pollGeneration = generation;
|
|
592
|
+
const schedule = () => {
|
|
593
|
+
this.pollTimer = setTimeout(() => {
|
|
594
|
+
this.pollTimer = null;
|
|
595
|
+
this.pull().catch(() => { }).finally(() => {
|
|
596
|
+
if (this.pollGeneration === generation) {
|
|
597
|
+
schedule();
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
}, this.pollCurrentIntervalMs);
|
|
601
|
+
};
|
|
602
|
+
schedule();
|
|
603
|
+
}
|
|
604
|
+
/** Called by emit() to adapt the poll interval based on pull activity. */
|
|
605
|
+
adaptPollInterval(entriesMerged) {
|
|
606
|
+
if (!this.pollBaseIntervalMs)
|
|
607
|
+
return; // not polling
|
|
608
|
+
const MAX_POLL_INTERVAL_MS = 60000;
|
|
609
|
+
if (entriesMerged > 0) {
|
|
610
|
+
// Activity — reset to base interval.
|
|
611
|
+
if (this.pollCurrentIntervalMs !== this.pollBaseIntervalMs) {
|
|
612
|
+
this.pollCurrentIntervalMs = this.pollBaseIntervalMs;
|
|
613
|
+
this.log('debug', '[interocitor:poll] activity detected, poll interval reset', { intervalMs: this.pollCurrentIntervalMs });
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
// Idle — back off toward max.
|
|
618
|
+
if (this.pollCurrentIntervalMs < MAX_POLL_INTERVAL_MS) {
|
|
619
|
+
this.pollCurrentIntervalMs = Math.min(this.pollCurrentIntervalMs * 2, MAX_POLL_INTERVAL_MS);
|
|
620
|
+
this.log('debug', '[interocitor:poll] idle, poll interval backed off', { intervalMs: this.pollCurrentIntervalMs });
|
|
621
|
+
}
|
|
622
|
+
}
|
|
582
623
|
}
|
|
583
624
|
stopRemoteInvalidations() {
|
|
584
625
|
if (this.unsubscribeRemoteInvalidations) {
|
|
@@ -777,6 +818,9 @@ export class Interocitor {
|
|
|
777
818
|
this.invalidateQueryCacheForTable(event.table);
|
|
778
819
|
this.invalidateRowCacheForTable(event.table);
|
|
779
820
|
}
|
|
821
|
+
if (event.type === 'sync:complete') {
|
|
822
|
+
this.adaptPollInterval(event.entriesMerged);
|
|
823
|
+
}
|
|
780
824
|
for (const listener of this.listeners) {
|
|
781
825
|
try {
|
|
782
826
|
listener(event);
|
|
@@ -953,11 +997,27 @@ export class Interocitor {
|
|
|
953
997
|
});
|
|
954
998
|
}
|
|
955
999
|
/**
|
|
956
|
-
* Set the mesh encryption passphrase.
|
|
957
|
-
*
|
|
1000
|
+
* Set or replace the mesh encryption passphrase before connecting.
|
|
1001
|
+
*
|
|
1002
|
+
* Apps may call this before init(), after init(), or after credentials
|
|
1003
|
+
* arrive from pairing. It intentionally does not rebuild the engine or
|
|
1004
|
+
* change remotePath/deviceId; it only invalidates the derived key so the
|
|
1005
|
+
* next connect/flush uses the new passphrase.
|
|
958
1006
|
*/
|
|
959
1007
|
setPassphrase(passphrase) {
|
|
960
|
-
this.
|
|
1008
|
+
if (this.connected)
|
|
1009
|
+
throw new Error('Cannot set passphrase while connected; disconnect first');
|
|
1010
|
+
this.passphrase = passphrase;
|
|
1011
|
+
this.encryptionKey = null;
|
|
1012
|
+
this.encrypted = true;
|
|
1013
|
+
this.emit({
|
|
1014
|
+
type: 'mesh:configured',
|
|
1015
|
+
dbName: this.dbName,
|
|
1016
|
+
remotePath: this.config.remotePath,
|
|
1017
|
+
deviceId: this.deviceId,
|
|
1018
|
+
encrypted: this.encrypted,
|
|
1019
|
+
hadPassphrase: true,
|
|
1020
|
+
});
|
|
961
1021
|
}
|
|
962
1022
|
/**
|
|
963
1023
|
* Get the current mesh passphrase.
|
|
@@ -1211,6 +1271,8 @@ export class Interocitor {
|
|
|
1211
1271
|
hasInFlight: !!this.connectPromise,
|
|
1212
1272
|
});
|
|
1213
1273
|
await this.ensureReady();
|
|
1274
|
+
if (this.encrypted && !this.encryptionKey)
|
|
1275
|
+
await this.resolveEncryption();
|
|
1214
1276
|
if (!this.config.remotePath)
|
|
1215
1277
|
throw new Error('connect() requires remotePath; configure mesh before connecting');
|
|
1216
1278
|
// Idempotent. If we are already connected to a live mesh on this
|
|
@@ -1449,12 +1511,41 @@ export class Interocitor {
|
|
|
1449
1511
|
this.startRemoteInvalidations(adapter);
|
|
1450
1512
|
this.connected = true;
|
|
1451
1513
|
this.log('info', 'connect() — connected', { remotePath: this.config.remotePath, deviceId: this.deviceId, pollInterval: this.config.pollInterval });
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1514
|
+
this.startVisibilityTracking();
|
|
1515
|
+
}
|
|
1516
|
+
startVisibilityTracking() {
|
|
1517
|
+
this.stopVisibilityTracking();
|
|
1518
|
+
if (typeof document === 'undefined')
|
|
1519
|
+
return;
|
|
1520
|
+
const listener = () => {
|
|
1521
|
+
if (document.visibilityState === 'hidden') {
|
|
1522
|
+
// Tab backgrounded: flush pending writes and slow down polling 10×.
|
|
1523
|
+
this.doFlush().catch(() => { });
|
|
1524
|
+
if (this.pollBaseIntervalMs) {
|
|
1525
|
+
this.pollCurrentIntervalMs = Math.min(this.pollCurrentIntervalMs * POLL_BACKGROUND_MULTIPLIER, this.pollBaseIntervalMs * POLL_BACKGROUND_MULTIPLIER);
|
|
1526
|
+
this.log('debug', '[interocitor:poll] tab hidden, poll interval slowed', { intervalMs: this.pollCurrentIntervalMs });
|
|
1456
1527
|
}
|
|
1457
|
-
}
|
|
1528
|
+
}
|
|
1529
|
+
else {
|
|
1530
|
+
// Tab foregrounded: pull immediately then reset to base interval.
|
|
1531
|
+
this.log('debug', '[interocitor:poll] tab visible, forcing pull and resetting interval');
|
|
1532
|
+
if (this.pollBaseIntervalMs) {
|
|
1533
|
+
this.pollCurrentIntervalMs = this.pollBaseIntervalMs;
|
|
1534
|
+
}
|
|
1535
|
+
if (this.connected) {
|
|
1536
|
+
this.pull().catch(() => { });
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
};
|
|
1540
|
+
this.visibilityChangeListener = listener;
|
|
1541
|
+
document.addEventListener('visibilitychange', listener);
|
|
1542
|
+
}
|
|
1543
|
+
stopVisibilityTracking() {
|
|
1544
|
+
if (this.visibilityChangeListener) {
|
|
1545
|
+
if (typeof document !== 'undefined') {
|
|
1546
|
+
document.removeEventListener('visibilitychange', this.visibilityChangeListener);
|
|
1547
|
+
}
|
|
1548
|
+
this.visibilityChangeListener = null;
|
|
1458
1549
|
}
|
|
1459
1550
|
}
|
|
1460
1551
|
async disconnect() {
|
|
@@ -1463,6 +1554,7 @@ export class Interocitor {
|
|
|
1463
1554
|
// poll/push/flush touches the adapter while we are killing the session.
|
|
1464
1555
|
this.stopPolling();
|
|
1465
1556
|
this.stopRemoteInvalidations();
|
|
1557
|
+
this.stopVisibilityTracking();
|
|
1466
1558
|
this.clearScheduledFlush();
|
|
1467
1559
|
this.clearCompactTimers();
|
|
1468
1560
|
this.clearBatchTimer();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qr-public.d.ts","sourceRoot":"","sources":["../../src/handshake/qr-public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,eAAe,EACf,eAAe,EACf,cAAc,GACf,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,eAAe,EACf,kBAAkB,GACnB,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { buildPairUrl, decodeQRPayload, encodeQRPayload, parseQRFromUrl, } from "./qr.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ export { generateShareQR, generateJoinQR, handleScannedQR, buildPairUrl, parseQR
|
|
|
36
36
|
export type { HandshakeCredentials, HandshakeQRPayload, HandshakeIntent, GenerateShareQROptions, GenerateShareQRResult, GenerateJoinQROptions, GenerateJoinQRResult, HandleScannedQROptions, } from './handshake/index.ts';
|
|
37
37
|
export { Interocitor, type InterocitorInitContext, } from './core/sync-engine.ts';
|
|
38
38
|
export { LocalStore } from './storage/local-store.ts';
|
|
39
|
+
export { resetLocalDatabase } from './storage/reset.ts';
|
|
39
40
|
export { type CredentialStore, type StoredCredentials, LocalStorageCredentialStore, WebAuthnCredentialStore, createCredentialStore, } from './storage/credential-store.ts';
|
|
40
41
|
export { Table, QueryResult, RowResult } from './core/table.ts';
|
|
41
42
|
export { types } from './core/schema-types.ts';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH,OAAO,EACL,eAAe,EACf,cAAc,EACd,eAAe,EACf,YAAY,EACZ,cAAc,EACd,eAAe,EACf,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EACL,WAAW,EACX,KAAK,sBAAsB,GAC5B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,2BAA2B,EAC3B,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAI/C,OAAO,EACL,2BAA2B,EAC3B,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,MAAM,EACN,cAAc,EACd,eAAe,EACf,WAAW,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAI3D,YAAY,EAEV,cAAc,EACd,SAAS,EAGT,iBAAiB,EACjB,iBAAiB,EAGjB,UAAU,EACV,gBAAgB,EAChB,QAAQ,EACR,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,EACf,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,wBAAwB,EACxB,WAAW,EACX,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,2BAA2B,EAC3B,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,cAAc,EACd,aAAa,EAGb,SAAS,EACT,iBAAiB,EAGjB,GAAG,EAGH,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,UAAU,EACV,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,aAAa,GACd,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH,OAAO,EACL,eAAe,EACf,cAAc,EACd,eAAe,EACf,YAAY,EACZ,cAAc,EACd,eAAe,EACf,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EACL,WAAW,EACX,KAAK,sBAAsB,GAC5B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,2BAA2B,EAC3B,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAI/C,OAAO,EACL,2BAA2B,EAC3B,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,MAAM,EACN,cAAc,EACd,eAAe,EACf,WAAW,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAI3D,YAAY,EAEV,cAAc,EACd,SAAS,EAGT,iBAAiB,EACjB,iBAAiB,EAGjB,UAAU,EACV,gBAAgB,EAChB,QAAQ,EACR,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,EACf,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,wBAAwB,EACxB,WAAW,EACX,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,2BAA2B,EAC3B,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,cAAc,EACd,aAAa,EAGb,SAAS,EACT,iBAAiB,EAGjB,GAAG,EAGH,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,UAAU,EACV,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,aAAa,GACd,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,7 @@ export { generateShareQR, generateJoinQR, handleScannedQR, buildPairUrl, parseQR
|
|
|
37
37
|
// ─── Engine ───────────────────────────────────────────────────────────
|
|
38
38
|
export { Interocitor, } from "./core/sync-engine.js";
|
|
39
39
|
export { LocalStore } from "./storage/local-store.js";
|
|
40
|
+
export { resetLocalDatabase } from "./storage/reset.js";
|
|
40
41
|
export { LocalStorageCredentialStore, WebAuthnCredentialStore, createCredentialStore, } from "./storage/credential-store.js";
|
|
41
42
|
export { Table, QueryResult, RowResult } from "./core/table.js";
|
|
42
43
|
export { types } from "./core/schema-types.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delete an Interocitor IndexedDB database after callers have disconnected
|
|
3
|
+
* and cleared credentials.
|
|
4
|
+
*
|
|
5
|
+
* This is intentionally low-level: it only deletes the local IndexedDB
|
|
6
|
+
* database named by `dbName`. Apps should call it as the final destructive
|
|
7
|
+
* local reset step, then reload before creating or joining a new mesh.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resetLocalDatabase(dbName?: string): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=reset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reset.d.ts","sourceRoot":"","sources":["../../src/storage/reset.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CASxE"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delete an Interocitor IndexedDB database after callers have disconnected
|
|
3
|
+
* and cleared credentials.
|
|
4
|
+
*
|
|
5
|
+
* This is intentionally low-level: it only deletes the local IndexedDB
|
|
6
|
+
* database named by `dbName`. Apps should call it as the final destructive
|
|
7
|
+
* local reset step, then reload before creating or joining a new mesh.
|
|
8
|
+
*/
|
|
9
|
+
export function resetLocalDatabase(dbName = 'interocitor') {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const req = indexedDB.deleteDatabase(dbName);
|
|
12
|
+
req.onsuccess = () => resolve();
|
|
13
|
+
req.onerror = () => reject(req.error ?? new Error(`Failed to delete IndexedDB database "${dbName}"`));
|
|
14
|
+
req.onblocked = () => {
|
|
15
|
+
reject(new Error(`Cannot delete IndexedDB database "${dbName}" while another connection is open`));
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@interocitor/core",
|
|
3
|
-
"version": "0.0.0-beta.
|
|
3
|
+
"version": "0.0.0-beta.8",
|
|
4
4
|
"description": "Encrypted local-first CRDT database that syncs over cloud storage without a purpose-built backend.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,6 +30,14 @@
|
|
|
30
30
|
"import": "./dist/storage/local-store.js",
|
|
31
31
|
"types": "./dist/storage/local-store.d.ts"
|
|
32
32
|
},
|
|
33
|
+
"./storage/reset": {
|
|
34
|
+
"import": "./dist/storage/reset.js",
|
|
35
|
+
"types": "./dist/storage/reset.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"./handshake/qr": {
|
|
38
|
+
"import": "./dist/handshake/qr-public.js",
|
|
39
|
+
"types": "./dist/handshake/qr-public.d.ts"
|
|
40
|
+
},
|
|
33
41
|
"./crypto/keys": {
|
|
34
42
|
"import": "./dist/crypto/keys.js",
|
|
35
43
|
"types": "./dist/crypto/keys.d.ts"
|