@use-everywhere/core 0.4.1 → 0.5.0
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/index.cjs +176 -38
- package/dist/index.d.cts +15 -5
- package/dist/index.d.ts +15 -5
- package/dist/index.js +176 -38
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -55,7 +55,13 @@ function emitBusEvent(name, direction, wire) {
|
|
|
55
55
|
const set = observers.get(name);
|
|
56
56
|
if (!set) return;
|
|
57
57
|
const event = { name, direction, wire };
|
|
58
|
-
for (const fn of set)
|
|
58
|
+
for (const fn of set) {
|
|
59
|
+
try {
|
|
60
|
+
fn(event);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error(`[use-everywhere] a bus observer for "${name}" threw`, error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
59
65
|
}
|
|
60
66
|
function observeBus(name, fn) {
|
|
61
67
|
let set = observers.get(name);
|
|
@@ -80,12 +86,41 @@ function enableDebug(options = {}) {
|
|
|
80
86
|
});
|
|
81
87
|
}
|
|
82
88
|
|
|
89
|
+
// src/dev.ts
|
|
90
|
+
var inDev = false;
|
|
91
|
+
try {
|
|
92
|
+
inDev = process.env.NODE_ENV !== "production";
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
var warned = /* @__PURE__ */ new Set();
|
|
96
|
+
function devWarn(message) {
|
|
97
|
+
if (!inDev || warned.has(message)) return;
|
|
98
|
+
warned.add(message);
|
|
99
|
+
console.warn(message);
|
|
100
|
+
}
|
|
101
|
+
|
|
83
102
|
// src/ids.ts
|
|
103
|
+
function randomHex(bytes) {
|
|
104
|
+
const crypto = globalThis.crypto;
|
|
105
|
+
if (crypto?.getRandomValues) {
|
|
106
|
+
const buf = crypto.getRandomValues(new Uint8Array(bytes));
|
|
107
|
+
let out2 = "";
|
|
108
|
+
for (const b of buf) out2 += b.toString(16).padStart(2, "0");
|
|
109
|
+
return out2;
|
|
110
|
+
}
|
|
111
|
+
devWarn(
|
|
112
|
+
"[use-everywhere] crypto.getRandomValues is unavailable; falling back to Math.random for ids. Cross-origin window nonces are not cryptographically strong in this environment."
|
|
113
|
+
);
|
|
114
|
+
let out = "";
|
|
115
|
+
while (out.length < bytes * 2)
|
|
116
|
+
out += Math.floor(Math.random() * 256).toString(16).padStart(2, "0");
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
84
119
|
function newClientId() {
|
|
85
|
-
return
|
|
120
|
+
return randomHex(8);
|
|
86
121
|
}
|
|
87
122
|
function newMsgId() {
|
|
88
|
-
return
|
|
123
|
+
return randomHex(16);
|
|
89
124
|
}
|
|
90
125
|
|
|
91
126
|
// src/transport/broadcast-channel-transport.ts
|
|
@@ -165,12 +200,20 @@ function createBus(name, options, onShutdown) {
|
|
|
165
200
|
options.heartbeatMs ?? 2e3
|
|
166
201
|
);
|
|
167
202
|
const sayBye = () => post({ v: 1, scope: "presence", type: "bye", clientId, kind });
|
|
203
|
+
const onPageShow = (event) => {
|
|
204
|
+
if (!event.persisted) return;
|
|
205
|
+
post({ v: 1, scope: "presence", type: "hello", clientId, kind });
|
|
206
|
+
};
|
|
168
207
|
const hasWindow = typeof document !== "undefined" && typeof addEventListener === "function";
|
|
169
|
-
if (hasWindow)
|
|
208
|
+
if (hasWindow) {
|
|
209
|
+
addEventListener("pagehide", sayBye);
|
|
210
|
+
addEventListener("pageshow", onPageShow);
|
|
211
|
+
}
|
|
170
212
|
return {
|
|
171
213
|
name,
|
|
172
214
|
clientId,
|
|
173
215
|
kind,
|
|
216
|
+
heartbeatMs: options.heartbeatMs ?? 2e3,
|
|
174
217
|
post,
|
|
175
218
|
subscribe(fn) {
|
|
176
219
|
listeners.add(fn);
|
|
@@ -180,12 +223,16 @@ function createBus(name, options, onShutdown) {
|
|
|
180
223
|
refs++;
|
|
181
224
|
},
|
|
182
225
|
release() {
|
|
226
|
+
if (closed) return;
|
|
183
227
|
refs--;
|
|
184
228
|
if (refs > 0) return;
|
|
185
229
|
sayBye();
|
|
186
230
|
closed = true;
|
|
187
231
|
clearInterval(heartbeat);
|
|
188
|
-
if (hasWindow)
|
|
232
|
+
if (hasWindow) {
|
|
233
|
+
removeEventListener("pagehide", sayBye);
|
|
234
|
+
removeEventListener("pageshow", onPageShow);
|
|
235
|
+
}
|
|
189
236
|
unsubscribe();
|
|
190
237
|
transport.close();
|
|
191
238
|
listeners.clear();
|
|
@@ -207,6 +254,15 @@ function getBus(name, options = {}) {
|
|
|
207
254
|
if (!bus) {
|
|
208
255
|
bus = createBus(name, options, () => registry.delete(name));
|
|
209
256
|
registry.set(name, bus);
|
|
257
|
+
} else {
|
|
258
|
+
if (options.heartbeatMs !== void 0 && options.heartbeatMs !== bus.heartbeatMs) {
|
|
259
|
+
devWarn(
|
|
260
|
+
`[use-everywhere] bus "${name}": heartbeatMs ignored \u2014 the first creator fixes bus options`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
if (options.kind !== void 0 && options.kind !== bus.kind) {
|
|
264
|
+
devWarn(`[use-everywhere] bus "${name}": kind ignored \u2014 the first creator fixes bus options`);
|
|
265
|
+
}
|
|
210
266
|
}
|
|
211
267
|
bus.acquire();
|
|
212
268
|
return bus;
|
|
@@ -223,6 +279,7 @@ function createChannel(name, options = {}) {
|
|
|
223
279
|
const meta = { clientId: wire.clientId, kind: wire.kind, self: false };
|
|
224
280
|
for (const fn of set) fn(wire.payload, meta);
|
|
225
281
|
});
|
|
282
|
+
let closed = false;
|
|
226
283
|
return {
|
|
227
284
|
name,
|
|
228
285
|
clientId: bus.clientId,
|
|
@@ -247,6 +304,8 @@ function createChannel(name, options = {}) {
|
|
|
247
304
|
return () => set.delete(handler);
|
|
248
305
|
},
|
|
249
306
|
close() {
|
|
307
|
+
if (closed) return;
|
|
308
|
+
closed = true;
|
|
250
309
|
unsubscribe();
|
|
251
310
|
handlers.clear();
|
|
252
311
|
bus.release();
|
|
@@ -260,25 +319,48 @@ function newer(a, b) {
|
|
|
260
319
|
}
|
|
261
320
|
|
|
262
321
|
// src/dev-freeze.ts
|
|
263
|
-
var
|
|
322
|
+
var inDev2 = false;
|
|
264
323
|
try {
|
|
265
|
-
|
|
324
|
+
inDev2 = process.env.NODE_ENV !== "production";
|
|
266
325
|
} catch {
|
|
267
326
|
}
|
|
268
327
|
function deepFreeze(value) {
|
|
269
328
|
if (value === null || typeof value !== "object" || Object.isFrozen(value)) return;
|
|
329
|
+
if (ArrayBuffer.isView(value)) return;
|
|
270
330
|
Object.freeze(value);
|
|
331
|
+
if (value instanceof Map) {
|
|
332
|
+
for (const [k, v] of value) {
|
|
333
|
+
deepFreeze(k);
|
|
334
|
+
deepFreeze(v);
|
|
335
|
+
}
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (value instanceof Set) {
|
|
339
|
+
for (const entry of value) deepFreeze(entry);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
271
342
|
for (const key of Object.keys(value)) {
|
|
272
343
|
deepFreeze(value[key]);
|
|
273
344
|
}
|
|
274
345
|
}
|
|
275
346
|
function freezeShared(value) {
|
|
276
|
-
if (
|
|
347
|
+
if (inDev2) deepFreeze(value);
|
|
277
348
|
return value;
|
|
278
349
|
}
|
|
279
350
|
|
|
280
351
|
// src/shared-store.ts
|
|
352
|
+
var liveStores = /* @__PURE__ */ new Map();
|
|
281
353
|
function createSharedStore(name, initial, options = {}) {
|
|
354
|
+
const onSharedBus = !options.transport;
|
|
355
|
+
if (onSharedBus) {
|
|
356
|
+
const live = liveStores.get(name) ?? 0;
|
|
357
|
+
if (live > 0) {
|
|
358
|
+
devWarn(
|
|
359
|
+
`[use-everywhere] second shared store for "${name}" in this tab \u2014 stores on one page never hear each other and will diverge. Reuse one per name.`
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
liveStores.set(name, live + 1);
|
|
363
|
+
}
|
|
282
364
|
const bus = getBus(name, options);
|
|
283
365
|
const clientId = bus.clientId;
|
|
284
366
|
const accept = options.accept;
|
|
@@ -332,18 +414,25 @@ function createSharedStore(name, initial, options = {}) {
|
|
|
332
414
|
});
|
|
333
415
|
function setKey(key, value) {
|
|
334
416
|
const version = [(versions[key]?.[0] ?? 0) + 1, clientId];
|
|
417
|
+
try {
|
|
418
|
+
bus.post({
|
|
419
|
+
v: 1,
|
|
420
|
+
scope: "state",
|
|
421
|
+
type: "patch",
|
|
422
|
+
key,
|
|
423
|
+
value,
|
|
424
|
+
version,
|
|
425
|
+
clientId,
|
|
426
|
+
kind: bus.kind
|
|
427
|
+
});
|
|
428
|
+
} catch (error) {
|
|
429
|
+
throw new TypeError(
|
|
430
|
+
`use-everywhere: the value for key "${key}" cannot cross the wire (structured clone failed); the write was not applied. ${String(error)}`,
|
|
431
|
+
{ cause: error }
|
|
432
|
+
);
|
|
433
|
+
}
|
|
335
434
|
versions[key] = version;
|
|
336
435
|
state[key] = freezeShared(value);
|
|
337
|
-
bus.post({
|
|
338
|
-
v: 1,
|
|
339
|
-
scope: "state",
|
|
340
|
-
type: "patch",
|
|
341
|
-
key,
|
|
342
|
-
value,
|
|
343
|
-
version,
|
|
344
|
-
clientId,
|
|
345
|
-
kind: bus.kind
|
|
346
|
-
});
|
|
347
436
|
notify(key, value, { clientId, kind: bus.kind, self: true });
|
|
348
437
|
}
|
|
349
438
|
const proxy = new Proxy(state, {
|
|
@@ -406,7 +495,15 @@ function createSharedStore(name, initial, options = {}) {
|
|
|
406
495
|
addEventListener("pagehide", flushPersist);
|
|
407
496
|
}
|
|
408
497
|
}
|
|
409
|
-
bus.post({ v: 1, scope: "state", type: "hello", clientId, kind: bus.kind });
|
|
498
|
+
const sayHello = () => bus.post({ v: 1, scope: "state", type: "hello", clientId, kind: bus.kind });
|
|
499
|
+
const onPageShow = (event) => {
|
|
500
|
+
if (!event.persisted) return;
|
|
501
|
+
sayHello();
|
|
502
|
+
};
|
|
503
|
+
const hasWindow = typeof document !== "undefined" && typeof addEventListener === "function";
|
|
504
|
+
if (hasWindow) addEventListener("pageshow", onPageShow);
|
|
505
|
+
sayHello();
|
|
506
|
+
let storeClosed = false;
|
|
410
507
|
return {
|
|
411
508
|
clientId,
|
|
412
509
|
state: proxy,
|
|
@@ -427,16 +524,27 @@ function createSharedStore(name, initial, options = {}) {
|
|
|
427
524
|
keyListeners.set(key, set);
|
|
428
525
|
}
|
|
429
526
|
set.add(fn);
|
|
430
|
-
return () =>
|
|
527
|
+
return () => {
|
|
528
|
+
set.delete(fn);
|
|
529
|
+
if (set.size === 0) keyListeners.delete(key);
|
|
530
|
+
};
|
|
431
531
|
},
|
|
432
532
|
registerKey(key, initialValue) {
|
|
433
533
|
if (key in versions) return;
|
|
434
534
|
versions[key] = [0, clientId];
|
|
435
|
-
state[key] = initialValue;
|
|
535
|
+
state[key] = freezeShared(initialValue);
|
|
436
536
|
snapshot = Object.freeze({ ...state });
|
|
437
537
|
versionsSnapshot = Object.freeze({ ...versions });
|
|
438
538
|
},
|
|
439
539
|
close() {
|
|
540
|
+
if (storeClosed) return;
|
|
541
|
+
storeClosed = true;
|
|
542
|
+
if (onSharedBus) {
|
|
543
|
+
const live = (liveStores.get(name) ?? 1) - 1;
|
|
544
|
+
if (live > 0) liveStores.set(name, live);
|
|
545
|
+
else liveStores.delete(name);
|
|
546
|
+
}
|
|
547
|
+
if (hasWindow) removeEventListener("pageshow", onPageShow);
|
|
440
548
|
if (flushPersist) {
|
|
441
549
|
flushPersist();
|
|
442
550
|
if (typeof document !== "undefined" && typeof removeEventListener === "function") {
|
|
@@ -485,6 +593,7 @@ function createPresence(name, options = {}) {
|
|
|
485
593
|
},
|
|
486
594
|
Math.max(500, Math.floor(pruneAfterMs / 2))
|
|
487
595
|
);
|
|
596
|
+
let closed = false;
|
|
488
597
|
return {
|
|
489
598
|
clientId: bus.clientId,
|
|
490
599
|
getPeers: () => snapshot,
|
|
@@ -493,6 +602,8 @@ function createPresence(name, options = {}) {
|
|
|
493
602
|
return () => listeners.delete(fn);
|
|
494
603
|
},
|
|
495
604
|
close() {
|
|
605
|
+
if (closed) return;
|
|
606
|
+
closed = true;
|
|
496
607
|
clearInterval(prune);
|
|
497
608
|
unsubscribe();
|
|
498
609
|
listeners.clear();
|
|
@@ -581,10 +692,19 @@ function createLeader(name, options = {}) {
|
|
|
581
692
|
bus.post({ v: 1, scope: "leader", type: "resign", term: resigning, clientId, kind: bus.kind });
|
|
582
693
|
armLease(leaseMs);
|
|
583
694
|
}
|
|
695
|
+
const sayHello = () => bus.post({ v: 1, scope: "leader", type: "hello", clientId, kind: bus.kind });
|
|
584
696
|
const hasWindow = typeof document !== "undefined" && typeof addEventListener === "function";
|
|
585
697
|
const onPageHide = () => resign();
|
|
586
|
-
|
|
587
|
-
|
|
698
|
+
const onPageShow = (event) => {
|
|
699
|
+
if (!event.persisted) return;
|
|
700
|
+
sayHello();
|
|
701
|
+
armLease(heartbeatMs);
|
|
702
|
+
};
|
|
703
|
+
if (hasWindow) {
|
|
704
|
+
addEventListener("pagehide", onPageHide);
|
|
705
|
+
addEventListener("pageshow", onPageShow);
|
|
706
|
+
}
|
|
707
|
+
sayHello();
|
|
588
708
|
armLease(heartbeatMs);
|
|
589
709
|
return {
|
|
590
710
|
clientId,
|
|
@@ -604,11 +724,15 @@ function createLeader(name, options = {}) {
|
|
|
604
724
|
if (leaderId === null) armLease(0);
|
|
605
725
|
},
|
|
606
726
|
close() {
|
|
727
|
+
if (closed) return;
|
|
607
728
|
closed = true;
|
|
608
729
|
resign();
|
|
609
730
|
clearInterval(beat);
|
|
610
731
|
clearTimeout(lease);
|
|
611
|
-
if (hasWindow)
|
|
732
|
+
if (hasWindow) {
|
|
733
|
+
removeEventListener("pagehide", onPageHide);
|
|
734
|
+
removeEventListener("pageshow", onPageShow);
|
|
735
|
+
}
|
|
612
736
|
unsubscribe();
|
|
613
737
|
listeners.clear();
|
|
614
738
|
bus.release();
|
|
@@ -622,44 +746,54 @@ function isPersisted(value) {
|
|
|
622
746
|
const candidate = value;
|
|
623
747
|
return candidate.v === 1 && typeof candidate.state === "object" && typeof candidate.versions === "object";
|
|
624
748
|
}
|
|
625
|
-
function webStorageAdapter(storage, key) {
|
|
626
|
-
const
|
|
749
|
+
function webStorageAdapter(storage, key, options = {}) {
|
|
750
|
+
const report = (error, operation) => {
|
|
627
751
|
try {
|
|
628
|
-
|
|
752
|
+
options.onError?.(error, operation);
|
|
629
753
|
} catch {
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
const resolve = (operation) => {
|
|
757
|
+
try {
|
|
758
|
+
return typeof storage === "function" ? storage() : storage;
|
|
759
|
+
} catch (error) {
|
|
760
|
+
report(error, operation);
|
|
630
761
|
return void 0;
|
|
631
762
|
}
|
|
632
763
|
};
|
|
633
764
|
return {
|
|
634
765
|
read() {
|
|
635
766
|
try {
|
|
636
|
-
const raw = resolve()?.getItem(key);
|
|
767
|
+
const raw = resolve("read")?.getItem(key);
|
|
637
768
|
if (!raw) return void 0;
|
|
638
769
|
const parsed = JSON.parse(raw);
|
|
639
770
|
return isPersisted(parsed) ? parsed : void 0;
|
|
640
|
-
} catch {
|
|
771
|
+
} catch (error) {
|
|
772
|
+
report(error, "read");
|
|
641
773
|
return void 0;
|
|
642
774
|
}
|
|
643
775
|
},
|
|
644
776
|
write(snapshot) {
|
|
645
777
|
try {
|
|
646
|
-
resolve()?.setItem(key, JSON.stringify(snapshot));
|
|
647
|
-
} catch {
|
|
778
|
+
resolve("write")?.setItem(key, JSON.stringify(snapshot));
|
|
779
|
+
} catch (error) {
|
|
780
|
+
report(error, "write");
|
|
648
781
|
}
|
|
649
782
|
},
|
|
650
783
|
remove() {
|
|
651
784
|
try {
|
|
652
|
-
resolve()?.removeItem(key);
|
|
653
|
-
} catch {
|
|
785
|
+
resolve("remove")?.removeItem(key);
|
|
786
|
+
} catch (error) {
|
|
787
|
+
report(error, "remove");
|
|
654
788
|
}
|
|
655
789
|
}
|
|
656
790
|
};
|
|
657
791
|
}
|
|
658
|
-
function localStorageAdapter(key) {
|
|
659
|
-
return webStorageAdapter(() => globalThis.localStorage, key);
|
|
792
|
+
function localStorageAdapter(key, options) {
|
|
793
|
+
return webStorageAdapter(() => globalThis.localStorage, key, options);
|
|
660
794
|
}
|
|
661
|
-
function sessionStorageAdapter(key) {
|
|
662
|
-
return webStorageAdapter(() => globalThis.sessionStorage, key);
|
|
795
|
+
function sessionStorageAdapter(key, options) {
|
|
796
|
+
return webStorageAdapter(() => globalThis.sessionStorage, key, options);
|
|
663
797
|
}
|
|
664
798
|
|
|
665
799
|
// src/errors/handshake-timeout-error.ts
|
|
@@ -786,6 +920,7 @@ function openWindow(url, options) {
|
|
|
786
920
|
}, 400);
|
|
787
921
|
const readyTimer = setTimeout(() => {
|
|
788
922
|
if (isReady || closedSettled) return;
|
|
923
|
+
localWindow.removeEventListener("message", onMessage);
|
|
789
924
|
const err = new HandshakeTimeoutError();
|
|
790
925
|
rejectReady(err);
|
|
791
926
|
if (!resultSettled) {
|
|
@@ -839,6 +974,7 @@ function connectToOpener(options) {
|
|
|
839
974
|
markHandled(ready);
|
|
840
975
|
const onMessage = (event) => {
|
|
841
976
|
if (!allowAnyOrigin && event.origin !== peerOrigin) return;
|
|
977
|
+
if (event.source !== opener) return;
|
|
842
978
|
if (!isWindowWire(event.data, cid)) return;
|
|
843
979
|
const wire = event.data;
|
|
844
980
|
if (wire.t === "ready-ack") {
|
|
@@ -934,9 +1070,11 @@ var MemoryHub = class {
|
|
|
934
1070
|
}
|
|
935
1071
|
/** @internal */
|
|
936
1072
|
broadcast(from, data) {
|
|
1073
|
+
structuredClone(data);
|
|
937
1074
|
for (const transport of this.transports) {
|
|
938
1075
|
if (transport !== from) {
|
|
939
|
-
|
|
1076
|
+
const copy = structuredClone(data);
|
|
1077
|
+
queueMicrotask(() => transport.deliver(copy));
|
|
940
1078
|
}
|
|
941
1079
|
}
|
|
942
1080
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -230,6 +230,15 @@ interface Leader {
|
|
|
230
230
|
declare function createLeader(name: string, options?: LeaderOptions): Leader;
|
|
231
231
|
|
|
232
232
|
type StorageLike = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
|
|
233
|
+
interface WebStorageAdapterOptions {
|
|
234
|
+
/**
|
|
235
|
+
* Called when a storage operation fails: blocked storage, corrupt JSON, a
|
|
236
|
+
* full quota. Persistence stays best-effort either way — this is the
|
|
237
|
+
* observability seam (telemetry, a "your changes may not be saved" notice),
|
|
238
|
+
* not a recovery path. Errors thrown by the callback itself are swallowed.
|
|
239
|
+
*/
|
|
240
|
+
onError?: (error: unknown, operation: 'read' | 'write' | 'remove') => void;
|
|
241
|
+
}
|
|
233
242
|
/**
|
|
234
243
|
* Persist to any Storage-shaped thing.
|
|
235
244
|
*
|
|
@@ -240,13 +249,14 @@ type StorageLike = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
|
|
|
240
249
|
* Behind a thunk, every access happens inside one.
|
|
241
250
|
*
|
|
242
251
|
* Blocked storage, corrupt JSON, a foreign schema, or a full quota all degrade
|
|
243
|
-
* to a silent no-op
|
|
252
|
+
* to a silent no-op — persistence is best-effort; it must never break the
|
|
253
|
+
* store. Pass `onError` to observe the failures anyway.
|
|
244
254
|
*/
|
|
245
|
-
declare function webStorageAdapter(storage: StorageLike | (() => StorageLike | undefined), key: string): PersistAdapter;
|
|
255
|
+
declare function webStorageAdapter(storage: StorageLike | (() => StorageLike | undefined), key: string, options?: WebStorageAdapterOptions): PersistAdapter;
|
|
246
256
|
/** Survives closing every tab. */
|
|
247
|
-
declare function localStorageAdapter(key: string): PersistAdapter;
|
|
257
|
+
declare function localStorageAdapter(key: string, options?: WebStorageAdapterOptions): PersistAdapter;
|
|
248
258
|
/** Survives reloads, but dies with the tab. */
|
|
249
|
-
declare function sessionStorageAdapter(key: string): PersistAdapter;
|
|
259
|
+
declare function sessionStorageAdapter(key: string, options?: WebStorageAdapterOptions): PersistAdapter;
|
|
250
260
|
|
|
251
261
|
interface MessageEventLike {
|
|
252
262
|
data: unknown;
|
|
@@ -442,4 +452,4 @@ declare class MemoryHub {
|
|
|
442
452
|
disconnect(transport: MemoryTransport): void;
|
|
443
453
|
}
|
|
444
454
|
|
|
445
|
-
export { BroadcastChannelTransport, type BusEvent, type BusObserver, type BusWire, CID_PARAM, type Channel, type CommonOptions, type ConnectToOpenerOptions, DEFAULT_NAME, type DebugOptions, HandshakeTimeoutError, type Leader, type LeaderOptions, type LeaderSnapshot, MemoryHub, MemoryTransport, type MessageEventLike, type MessageMap, type MessageMeta, NoopTransport, type OpenWindowOptions, type OpenedWindow, type OpenerConnection, type Peer, type PeerKind, type PersistAdapter, type PersistOptions, type Persisted, type Presence, type PresenceOptions, type SharedStore, type SharedStoreOptions, type StorageLike, type Transport, type Version, WindowClosedError, type WindowEventTarget, type WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedStore, defaultTransport, enableDebug, getBusNames, isBroadcastChannelAvailable, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter };
|
|
455
|
+
export { BroadcastChannelTransport, type BusEvent, type BusObserver, type BusWire, CID_PARAM, type Channel, type CommonOptions, type ConnectToOpenerOptions, DEFAULT_NAME, type DebugOptions, HandshakeTimeoutError, type Leader, type LeaderOptions, type LeaderSnapshot, MemoryHub, MemoryTransport, type MessageEventLike, type MessageMap, type MessageMeta, NoopTransport, type OpenWindowOptions, type OpenedWindow, type OpenerConnection, type Peer, type PeerKind, type PersistAdapter, type PersistOptions, type Persisted, type Presence, type PresenceOptions, type SharedStore, type SharedStoreOptions, type StorageLike, type Transport, type Version, type WebStorageAdapterOptions, WindowClosedError, type WindowEventTarget, type WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedStore, defaultTransport, enableDebug, getBusNames, isBroadcastChannelAvailable, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter };
|
package/dist/index.d.ts
CHANGED
|
@@ -230,6 +230,15 @@ interface Leader {
|
|
|
230
230
|
declare function createLeader(name: string, options?: LeaderOptions): Leader;
|
|
231
231
|
|
|
232
232
|
type StorageLike = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
|
|
233
|
+
interface WebStorageAdapterOptions {
|
|
234
|
+
/**
|
|
235
|
+
* Called when a storage operation fails: blocked storage, corrupt JSON, a
|
|
236
|
+
* full quota. Persistence stays best-effort either way — this is the
|
|
237
|
+
* observability seam (telemetry, a "your changes may not be saved" notice),
|
|
238
|
+
* not a recovery path. Errors thrown by the callback itself are swallowed.
|
|
239
|
+
*/
|
|
240
|
+
onError?: (error: unknown, operation: 'read' | 'write' | 'remove') => void;
|
|
241
|
+
}
|
|
233
242
|
/**
|
|
234
243
|
* Persist to any Storage-shaped thing.
|
|
235
244
|
*
|
|
@@ -240,13 +249,14 @@ type StorageLike = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
|
|
|
240
249
|
* Behind a thunk, every access happens inside one.
|
|
241
250
|
*
|
|
242
251
|
* Blocked storage, corrupt JSON, a foreign schema, or a full quota all degrade
|
|
243
|
-
* to a silent no-op
|
|
252
|
+
* to a silent no-op — persistence is best-effort; it must never break the
|
|
253
|
+
* store. Pass `onError` to observe the failures anyway.
|
|
244
254
|
*/
|
|
245
|
-
declare function webStorageAdapter(storage: StorageLike | (() => StorageLike | undefined), key: string): PersistAdapter;
|
|
255
|
+
declare function webStorageAdapter(storage: StorageLike | (() => StorageLike | undefined), key: string, options?: WebStorageAdapterOptions): PersistAdapter;
|
|
246
256
|
/** Survives closing every tab. */
|
|
247
|
-
declare function localStorageAdapter(key: string): PersistAdapter;
|
|
257
|
+
declare function localStorageAdapter(key: string, options?: WebStorageAdapterOptions): PersistAdapter;
|
|
248
258
|
/** Survives reloads, but dies with the tab. */
|
|
249
|
-
declare function sessionStorageAdapter(key: string): PersistAdapter;
|
|
259
|
+
declare function sessionStorageAdapter(key: string, options?: WebStorageAdapterOptions): PersistAdapter;
|
|
250
260
|
|
|
251
261
|
interface MessageEventLike {
|
|
252
262
|
data: unknown;
|
|
@@ -442,4 +452,4 @@ declare class MemoryHub {
|
|
|
442
452
|
disconnect(transport: MemoryTransport): void;
|
|
443
453
|
}
|
|
444
454
|
|
|
445
|
-
export { BroadcastChannelTransport, type BusEvent, type BusObserver, type BusWire, CID_PARAM, type Channel, type CommonOptions, type ConnectToOpenerOptions, DEFAULT_NAME, type DebugOptions, HandshakeTimeoutError, type Leader, type LeaderOptions, type LeaderSnapshot, MemoryHub, MemoryTransport, type MessageEventLike, type MessageMap, type MessageMeta, NoopTransport, type OpenWindowOptions, type OpenedWindow, type OpenerConnection, type Peer, type PeerKind, type PersistAdapter, type PersistOptions, type Persisted, type Presence, type PresenceOptions, type SharedStore, type SharedStoreOptions, type StorageLike, type Transport, type Version, WindowClosedError, type WindowEventTarget, type WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedStore, defaultTransport, enableDebug, getBusNames, isBroadcastChannelAvailable, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter };
|
|
455
|
+
export { BroadcastChannelTransport, type BusEvent, type BusObserver, type BusWire, CID_PARAM, type Channel, type CommonOptions, type ConnectToOpenerOptions, DEFAULT_NAME, type DebugOptions, HandshakeTimeoutError, type Leader, type LeaderOptions, type LeaderSnapshot, MemoryHub, MemoryTransport, type MessageEventLike, type MessageMap, type MessageMeta, NoopTransport, type OpenWindowOptions, type OpenedWindow, type OpenerConnection, type Peer, type PeerKind, type PersistAdapter, type PersistOptions, type Persisted, type Presence, type PresenceOptions, type SharedStore, type SharedStoreOptions, type StorageLike, type Transport, type Version, type WebStorageAdapterOptions, WindowClosedError, type WindowEventTarget, type WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedStore, defaultTransport, enableDebug, getBusNames, isBroadcastChannelAvailable, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter };
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,13 @@ function emitBusEvent(name, direction, wire) {
|
|
|
7
7
|
const set = observers.get(name);
|
|
8
8
|
if (!set) return;
|
|
9
9
|
const event = { name, direction, wire };
|
|
10
|
-
for (const fn of set)
|
|
10
|
+
for (const fn of set) {
|
|
11
|
+
try {
|
|
12
|
+
fn(event);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error(`[use-everywhere] a bus observer for "${name}" threw`, error);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
11
17
|
}
|
|
12
18
|
function observeBus(name, fn) {
|
|
13
19
|
let set = observers.get(name);
|
|
@@ -32,12 +38,41 @@ function enableDebug(options = {}) {
|
|
|
32
38
|
});
|
|
33
39
|
}
|
|
34
40
|
|
|
41
|
+
// src/dev.ts
|
|
42
|
+
var inDev = false;
|
|
43
|
+
try {
|
|
44
|
+
inDev = process.env.NODE_ENV !== "production";
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
var warned = /* @__PURE__ */ new Set();
|
|
48
|
+
function devWarn(message) {
|
|
49
|
+
if (!inDev || warned.has(message)) return;
|
|
50
|
+
warned.add(message);
|
|
51
|
+
console.warn(message);
|
|
52
|
+
}
|
|
53
|
+
|
|
35
54
|
// src/ids.ts
|
|
55
|
+
function randomHex(bytes) {
|
|
56
|
+
const crypto = globalThis.crypto;
|
|
57
|
+
if (crypto?.getRandomValues) {
|
|
58
|
+
const buf = crypto.getRandomValues(new Uint8Array(bytes));
|
|
59
|
+
let out2 = "";
|
|
60
|
+
for (const b of buf) out2 += b.toString(16).padStart(2, "0");
|
|
61
|
+
return out2;
|
|
62
|
+
}
|
|
63
|
+
devWarn(
|
|
64
|
+
"[use-everywhere] crypto.getRandomValues is unavailable; falling back to Math.random for ids. Cross-origin window nonces are not cryptographically strong in this environment."
|
|
65
|
+
);
|
|
66
|
+
let out = "";
|
|
67
|
+
while (out.length < bytes * 2)
|
|
68
|
+
out += Math.floor(Math.random() * 256).toString(16).padStart(2, "0");
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
36
71
|
function newClientId() {
|
|
37
|
-
return
|
|
72
|
+
return randomHex(8);
|
|
38
73
|
}
|
|
39
74
|
function newMsgId() {
|
|
40
|
-
return
|
|
75
|
+
return randomHex(16);
|
|
41
76
|
}
|
|
42
77
|
|
|
43
78
|
// src/transport/broadcast-channel-transport.ts
|
|
@@ -117,12 +152,20 @@ function createBus(name, options, onShutdown) {
|
|
|
117
152
|
options.heartbeatMs ?? 2e3
|
|
118
153
|
);
|
|
119
154
|
const sayBye = () => post({ v: 1, scope: "presence", type: "bye", clientId, kind });
|
|
155
|
+
const onPageShow = (event) => {
|
|
156
|
+
if (!event.persisted) return;
|
|
157
|
+
post({ v: 1, scope: "presence", type: "hello", clientId, kind });
|
|
158
|
+
};
|
|
120
159
|
const hasWindow = typeof document !== "undefined" && typeof addEventListener === "function";
|
|
121
|
-
if (hasWindow)
|
|
160
|
+
if (hasWindow) {
|
|
161
|
+
addEventListener("pagehide", sayBye);
|
|
162
|
+
addEventListener("pageshow", onPageShow);
|
|
163
|
+
}
|
|
122
164
|
return {
|
|
123
165
|
name,
|
|
124
166
|
clientId,
|
|
125
167
|
kind,
|
|
168
|
+
heartbeatMs: options.heartbeatMs ?? 2e3,
|
|
126
169
|
post,
|
|
127
170
|
subscribe(fn) {
|
|
128
171
|
listeners.add(fn);
|
|
@@ -132,12 +175,16 @@ function createBus(name, options, onShutdown) {
|
|
|
132
175
|
refs++;
|
|
133
176
|
},
|
|
134
177
|
release() {
|
|
178
|
+
if (closed) return;
|
|
135
179
|
refs--;
|
|
136
180
|
if (refs > 0) return;
|
|
137
181
|
sayBye();
|
|
138
182
|
closed = true;
|
|
139
183
|
clearInterval(heartbeat);
|
|
140
|
-
if (hasWindow)
|
|
184
|
+
if (hasWindow) {
|
|
185
|
+
removeEventListener("pagehide", sayBye);
|
|
186
|
+
removeEventListener("pageshow", onPageShow);
|
|
187
|
+
}
|
|
141
188
|
unsubscribe();
|
|
142
189
|
transport.close();
|
|
143
190
|
listeners.clear();
|
|
@@ -159,6 +206,15 @@ function getBus(name, options = {}) {
|
|
|
159
206
|
if (!bus) {
|
|
160
207
|
bus = createBus(name, options, () => registry.delete(name));
|
|
161
208
|
registry.set(name, bus);
|
|
209
|
+
} else {
|
|
210
|
+
if (options.heartbeatMs !== void 0 && options.heartbeatMs !== bus.heartbeatMs) {
|
|
211
|
+
devWarn(
|
|
212
|
+
`[use-everywhere] bus "${name}": heartbeatMs ignored \u2014 the first creator fixes bus options`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
if (options.kind !== void 0 && options.kind !== bus.kind) {
|
|
216
|
+
devWarn(`[use-everywhere] bus "${name}": kind ignored \u2014 the first creator fixes bus options`);
|
|
217
|
+
}
|
|
162
218
|
}
|
|
163
219
|
bus.acquire();
|
|
164
220
|
return bus;
|
|
@@ -175,6 +231,7 @@ function createChannel(name, options = {}) {
|
|
|
175
231
|
const meta = { clientId: wire.clientId, kind: wire.kind, self: false };
|
|
176
232
|
for (const fn of set) fn(wire.payload, meta);
|
|
177
233
|
});
|
|
234
|
+
let closed = false;
|
|
178
235
|
return {
|
|
179
236
|
name,
|
|
180
237
|
clientId: bus.clientId,
|
|
@@ -199,6 +256,8 @@ function createChannel(name, options = {}) {
|
|
|
199
256
|
return () => set.delete(handler);
|
|
200
257
|
},
|
|
201
258
|
close() {
|
|
259
|
+
if (closed) return;
|
|
260
|
+
closed = true;
|
|
202
261
|
unsubscribe();
|
|
203
262
|
handlers.clear();
|
|
204
263
|
bus.release();
|
|
@@ -212,25 +271,48 @@ function newer(a, b) {
|
|
|
212
271
|
}
|
|
213
272
|
|
|
214
273
|
// src/dev-freeze.ts
|
|
215
|
-
var
|
|
274
|
+
var inDev2 = false;
|
|
216
275
|
try {
|
|
217
|
-
|
|
276
|
+
inDev2 = process.env.NODE_ENV !== "production";
|
|
218
277
|
} catch {
|
|
219
278
|
}
|
|
220
279
|
function deepFreeze(value) {
|
|
221
280
|
if (value === null || typeof value !== "object" || Object.isFrozen(value)) return;
|
|
281
|
+
if (ArrayBuffer.isView(value)) return;
|
|
222
282
|
Object.freeze(value);
|
|
283
|
+
if (value instanceof Map) {
|
|
284
|
+
for (const [k, v] of value) {
|
|
285
|
+
deepFreeze(k);
|
|
286
|
+
deepFreeze(v);
|
|
287
|
+
}
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (value instanceof Set) {
|
|
291
|
+
for (const entry of value) deepFreeze(entry);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
223
294
|
for (const key of Object.keys(value)) {
|
|
224
295
|
deepFreeze(value[key]);
|
|
225
296
|
}
|
|
226
297
|
}
|
|
227
298
|
function freezeShared(value) {
|
|
228
|
-
if (
|
|
299
|
+
if (inDev2) deepFreeze(value);
|
|
229
300
|
return value;
|
|
230
301
|
}
|
|
231
302
|
|
|
232
303
|
// src/shared-store.ts
|
|
304
|
+
var liveStores = /* @__PURE__ */ new Map();
|
|
233
305
|
function createSharedStore(name, initial, options = {}) {
|
|
306
|
+
const onSharedBus = !options.transport;
|
|
307
|
+
if (onSharedBus) {
|
|
308
|
+
const live = liveStores.get(name) ?? 0;
|
|
309
|
+
if (live > 0) {
|
|
310
|
+
devWarn(
|
|
311
|
+
`[use-everywhere] second shared store for "${name}" in this tab \u2014 stores on one page never hear each other and will diverge. Reuse one per name.`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
liveStores.set(name, live + 1);
|
|
315
|
+
}
|
|
234
316
|
const bus = getBus(name, options);
|
|
235
317
|
const clientId = bus.clientId;
|
|
236
318
|
const accept = options.accept;
|
|
@@ -284,18 +366,25 @@ function createSharedStore(name, initial, options = {}) {
|
|
|
284
366
|
});
|
|
285
367
|
function setKey(key, value) {
|
|
286
368
|
const version = [(versions[key]?.[0] ?? 0) + 1, clientId];
|
|
369
|
+
try {
|
|
370
|
+
bus.post({
|
|
371
|
+
v: 1,
|
|
372
|
+
scope: "state",
|
|
373
|
+
type: "patch",
|
|
374
|
+
key,
|
|
375
|
+
value,
|
|
376
|
+
version,
|
|
377
|
+
clientId,
|
|
378
|
+
kind: bus.kind
|
|
379
|
+
});
|
|
380
|
+
} catch (error) {
|
|
381
|
+
throw new TypeError(
|
|
382
|
+
`use-everywhere: the value for key "${key}" cannot cross the wire (structured clone failed); the write was not applied. ${String(error)}`,
|
|
383
|
+
{ cause: error }
|
|
384
|
+
);
|
|
385
|
+
}
|
|
287
386
|
versions[key] = version;
|
|
288
387
|
state[key] = freezeShared(value);
|
|
289
|
-
bus.post({
|
|
290
|
-
v: 1,
|
|
291
|
-
scope: "state",
|
|
292
|
-
type: "patch",
|
|
293
|
-
key,
|
|
294
|
-
value,
|
|
295
|
-
version,
|
|
296
|
-
clientId,
|
|
297
|
-
kind: bus.kind
|
|
298
|
-
});
|
|
299
388
|
notify(key, value, { clientId, kind: bus.kind, self: true });
|
|
300
389
|
}
|
|
301
390
|
const proxy = new Proxy(state, {
|
|
@@ -358,7 +447,15 @@ function createSharedStore(name, initial, options = {}) {
|
|
|
358
447
|
addEventListener("pagehide", flushPersist);
|
|
359
448
|
}
|
|
360
449
|
}
|
|
361
|
-
bus.post({ v: 1, scope: "state", type: "hello", clientId, kind: bus.kind });
|
|
450
|
+
const sayHello = () => bus.post({ v: 1, scope: "state", type: "hello", clientId, kind: bus.kind });
|
|
451
|
+
const onPageShow = (event) => {
|
|
452
|
+
if (!event.persisted) return;
|
|
453
|
+
sayHello();
|
|
454
|
+
};
|
|
455
|
+
const hasWindow = typeof document !== "undefined" && typeof addEventListener === "function";
|
|
456
|
+
if (hasWindow) addEventListener("pageshow", onPageShow);
|
|
457
|
+
sayHello();
|
|
458
|
+
let storeClosed = false;
|
|
362
459
|
return {
|
|
363
460
|
clientId,
|
|
364
461
|
state: proxy,
|
|
@@ -379,16 +476,27 @@ function createSharedStore(name, initial, options = {}) {
|
|
|
379
476
|
keyListeners.set(key, set);
|
|
380
477
|
}
|
|
381
478
|
set.add(fn);
|
|
382
|
-
return () =>
|
|
479
|
+
return () => {
|
|
480
|
+
set.delete(fn);
|
|
481
|
+
if (set.size === 0) keyListeners.delete(key);
|
|
482
|
+
};
|
|
383
483
|
},
|
|
384
484
|
registerKey(key, initialValue) {
|
|
385
485
|
if (key in versions) return;
|
|
386
486
|
versions[key] = [0, clientId];
|
|
387
|
-
state[key] = initialValue;
|
|
487
|
+
state[key] = freezeShared(initialValue);
|
|
388
488
|
snapshot = Object.freeze({ ...state });
|
|
389
489
|
versionsSnapshot = Object.freeze({ ...versions });
|
|
390
490
|
},
|
|
391
491
|
close() {
|
|
492
|
+
if (storeClosed) return;
|
|
493
|
+
storeClosed = true;
|
|
494
|
+
if (onSharedBus) {
|
|
495
|
+
const live = (liveStores.get(name) ?? 1) - 1;
|
|
496
|
+
if (live > 0) liveStores.set(name, live);
|
|
497
|
+
else liveStores.delete(name);
|
|
498
|
+
}
|
|
499
|
+
if (hasWindow) removeEventListener("pageshow", onPageShow);
|
|
392
500
|
if (flushPersist) {
|
|
393
501
|
flushPersist();
|
|
394
502
|
if (typeof document !== "undefined" && typeof removeEventListener === "function") {
|
|
@@ -437,6 +545,7 @@ function createPresence(name, options = {}) {
|
|
|
437
545
|
},
|
|
438
546
|
Math.max(500, Math.floor(pruneAfterMs / 2))
|
|
439
547
|
);
|
|
548
|
+
let closed = false;
|
|
440
549
|
return {
|
|
441
550
|
clientId: bus.clientId,
|
|
442
551
|
getPeers: () => snapshot,
|
|
@@ -445,6 +554,8 @@ function createPresence(name, options = {}) {
|
|
|
445
554
|
return () => listeners.delete(fn);
|
|
446
555
|
},
|
|
447
556
|
close() {
|
|
557
|
+
if (closed) return;
|
|
558
|
+
closed = true;
|
|
448
559
|
clearInterval(prune);
|
|
449
560
|
unsubscribe();
|
|
450
561
|
listeners.clear();
|
|
@@ -533,10 +644,19 @@ function createLeader(name, options = {}) {
|
|
|
533
644
|
bus.post({ v: 1, scope: "leader", type: "resign", term: resigning, clientId, kind: bus.kind });
|
|
534
645
|
armLease(leaseMs);
|
|
535
646
|
}
|
|
647
|
+
const sayHello = () => bus.post({ v: 1, scope: "leader", type: "hello", clientId, kind: bus.kind });
|
|
536
648
|
const hasWindow = typeof document !== "undefined" && typeof addEventListener === "function";
|
|
537
649
|
const onPageHide = () => resign();
|
|
538
|
-
|
|
539
|
-
|
|
650
|
+
const onPageShow = (event) => {
|
|
651
|
+
if (!event.persisted) return;
|
|
652
|
+
sayHello();
|
|
653
|
+
armLease(heartbeatMs);
|
|
654
|
+
};
|
|
655
|
+
if (hasWindow) {
|
|
656
|
+
addEventListener("pagehide", onPageHide);
|
|
657
|
+
addEventListener("pageshow", onPageShow);
|
|
658
|
+
}
|
|
659
|
+
sayHello();
|
|
540
660
|
armLease(heartbeatMs);
|
|
541
661
|
return {
|
|
542
662
|
clientId,
|
|
@@ -556,11 +676,15 @@ function createLeader(name, options = {}) {
|
|
|
556
676
|
if (leaderId === null) armLease(0);
|
|
557
677
|
},
|
|
558
678
|
close() {
|
|
679
|
+
if (closed) return;
|
|
559
680
|
closed = true;
|
|
560
681
|
resign();
|
|
561
682
|
clearInterval(beat);
|
|
562
683
|
clearTimeout(lease);
|
|
563
|
-
if (hasWindow)
|
|
684
|
+
if (hasWindow) {
|
|
685
|
+
removeEventListener("pagehide", onPageHide);
|
|
686
|
+
removeEventListener("pageshow", onPageShow);
|
|
687
|
+
}
|
|
564
688
|
unsubscribe();
|
|
565
689
|
listeners.clear();
|
|
566
690
|
bus.release();
|
|
@@ -574,44 +698,54 @@ function isPersisted(value) {
|
|
|
574
698
|
const candidate = value;
|
|
575
699
|
return candidate.v === 1 && typeof candidate.state === "object" && typeof candidate.versions === "object";
|
|
576
700
|
}
|
|
577
|
-
function webStorageAdapter(storage, key) {
|
|
578
|
-
const
|
|
701
|
+
function webStorageAdapter(storage, key, options = {}) {
|
|
702
|
+
const report = (error, operation) => {
|
|
579
703
|
try {
|
|
580
|
-
|
|
704
|
+
options.onError?.(error, operation);
|
|
581
705
|
} catch {
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
const resolve = (operation) => {
|
|
709
|
+
try {
|
|
710
|
+
return typeof storage === "function" ? storage() : storage;
|
|
711
|
+
} catch (error) {
|
|
712
|
+
report(error, operation);
|
|
582
713
|
return void 0;
|
|
583
714
|
}
|
|
584
715
|
};
|
|
585
716
|
return {
|
|
586
717
|
read() {
|
|
587
718
|
try {
|
|
588
|
-
const raw = resolve()?.getItem(key);
|
|
719
|
+
const raw = resolve("read")?.getItem(key);
|
|
589
720
|
if (!raw) return void 0;
|
|
590
721
|
const parsed = JSON.parse(raw);
|
|
591
722
|
return isPersisted(parsed) ? parsed : void 0;
|
|
592
|
-
} catch {
|
|
723
|
+
} catch (error) {
|
|
724
|
+
report(error, "read");
|
|
593
725
|
return void 0;
|
|
594
726
|
}
|
|
595
727
|
},
|
|
596
728
|
write(snapshot) {
|
|
597
729
|
try {
|
|
598
|
-
resolve()?.setItem(key, JSON.stringify(snapshot));
|
|
599
|
-
} catch {
|
|
730
|
+
resolve("write")?.setItem(key, JSON.stringify(snapshot));
|
|
731
|
+
} catch (error) {
|
|
732
|
+
report(error, "write");
|
|
600
733
|
}
|
|
601
734
|
},
|
|
602
735
|
remove() {
|
|
603
736
|
try {
|
|
604
|
-
resolve()?.removeItem(key);
|
|
605
|
-
} catch {
|
|
737
|
+
resolve("remove")?.removeItem(key);
|
|
738
|
+
} catch (error) {
|
|
739
|
+
report(error, "remove");
|
|
606
740
|
}
|
|
607
741
|
}
|
|
608
742
|
};
|
|
609
743
|
}
|
|
610
|
-
function localStorageAdapter(key) {
|
|
611
|
-
return webStorageAdapter(() => globalThis.localStorage, key);
|
|
744
|
+
function localStorageAdapter(key, options) {
|
|
745
|
+
return webStorageAdapter(() => globalThis.localStorage, key, options);
|
|
612
746
|
}
|
|
613
|
-
function sessionStorageAdapter(key) {
|
|
614
|
-
return webStorageAdapter(() => globalThis.sessionStorage, key);
|
|
747
|
+
function sessionStorageAdapter(key, options) {
|
|
748
|
+
return webStorageAdapter(() => globalThis.sessionStorage, key, options);
|
|
615
749
|
}
|
|
616
750
|
|
|
617
751
|
// src/errors/handshake-timeout-error.ts
|
|
@@ -738,6 +872,7 @@ function openWindow(url, options) {
|
|
|
738
872
|
}, 400);
|
|
739
873
|
const readyTimer = setTimeout(() => {
|
|
740
874
|
if (isReady || closedSettled) return;
|
|
875
|
+
localWindow.removeEventListener("message", onMessage);
|
|
741
876
|
const err = new HandshakeTimeoutError();
|
|
742
877
|
rejectReady(err);
|
|
743
878
|
if (!resultSettled) {
|
|
@@ -791,6 +926,7 @@ function connectToOpener(options) {
|
|
|
791
926
|
markHandled(ready);
|
|
792
927
|
const onMessage = (event) => {
|
|
793
928
|
if (!allowAnyOrigin && event.origin !== peerOrigin) return;
|
|
929
|
+
if (event.source !== opener) return;
|
|
794
930
|
if (!isWindowWire(event.data, cid)) return;
|
|
795
931
|
const wire = event.data;
|
|
796
932
|
if (wire.t === "ready-ack") {
|
|
@@ -886,9 +1022,11 @@ var MemoryHub = class {
|
|
|
886
1022
|
}
|
|
887
1023
|
/** @internal */
|
|
888
1024
|
broadcast(from, data) {
|
|
1025
|
+
structuredClone(data);
|
|
889
1026
|
for (const transport of this.transports) {
|
|
890
1027
|
if (transport !== from) {
|
|
891
|
-
|
|
1028
|
+
const copy = structuredClone(data);
|
|
1029
|
+
queueMicrotask(() => transport.deliver(copy));
|
|
892
1030
|
}
|
|
893
1031
|
}
|
|
894
1032
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@use-everywhere/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Cross-tab shared state, events, presence, and cross-origin window channels",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jonatan Kruszewski <jonakrusze@gmail.com>",
|
|
@@ -48,43 +48,43 @@
|
|
|
48
48
|
"name": "everything (import *)",
|
|
49
49
|
"path": "dist/index.js",
|
|
50
50
|
"import": "*",
|
|
51
|
-
"limit": "
|
|
51
|
+
"limit": "5 kB"
|
|
52
52
|
},
|
|
53
53
|
{
|
|
54
54
|
"name": "createLeader",
|
|
55
55
|
"path": "dist/index.js",
|
|
56
56
|
"import": "{ createLeader }",
|
|
57
|
-
"limit": "1.
|
|
57
|
+
"limit": "1.6 kB"
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
60
|
"name": "createSharedStore",
|
|
61
61
|
"path": "dist/index.js",
|
|
62
62
|
"import": "{ createSharedStore }",
|
|
63
|
-
"limit": "
|
|
63
|
+
"limit": "2.25 kB"
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
"name": "createChannel",
|
|
67
67
|
"path": "dist/index.js",
|
|
68
68
|
"import": "{ createChannel }",
|
|
69
|
-
"limit": "
|
|
69
|
+
"limit": "1.25 kB"
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
"name": "createPresence",
|
|
73
73
|
"path": "dist/index.js",
|
|
74
74
|
"import": "{ createPresence }",
|
|
75
|
-
"limit": "1 kB"
|
|
75
|
+
"limit": "1.35 kB"
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
"name": "openWindow (opener side)",
|
|
79
79
|
"path": "dist/index.js",
|
|
80
80
|
"import": "{ openWindow }",
|
|
81
|
-
"limit": "1.
|
|
81
|
+
"limit": "1.3 kB"
|
|
82
82
|
},
|
|
83
83
|
{
|
|
84
84
|
"name": "connectToOpener (child side)",
|
|
85
85
|
"path": "dist/index.js",
|
|
86
86
|
"import": "{ connectToOpener }",
|
|
87
|
-
"limit": "1 kB"
|
|
87
|
+
"limit": "1.15 kB"
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
"name": "MemoryHub (test transport)",
|