use-everywhere 0.4.1 → 0.6.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/README.md +1 -1
- package/dist/chunk-YOXUZYKA.js +187 -0
- package/dist/devtools/index.cjs +36 -4
- package/dist/devtools/index.js +1 -1
- package/dist/index.cjs +170 -32
- package/dist/index.d.cts +60 -10
- package/dist/index.d.ts +60 -10
- package/dist/index.js +70 -22
- package/dist/testing.cjs +33 -0
- package/dist/testing.d.cts +1 -0
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +8 -0
- package/package.json +28 -9
- package/dist/chunk-PNFJW7QK.js +0 -92
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ function PayButton() {
|
|
|
103
103
|
}),
|
|
104
104
|
);
|
|
105
105
|
|
|
106
|
-
if (pay.status === 'done') return <p>Paid — receipt {pay.result
|
|
106
|
+
if (pay.status === 'done') return <p>Paid — receipt {pay.result.receiptId}</p>;
|
|
107
107
|
if (pay.status === 'closed-early') return <p>Payment window was closed.</p>;
|
|
108
108
|
|
|
109
109
|
return (
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// src/registry.ts
|
|
4
|
+
import {
|
|
5
|
+
createChannel,
|
|
6
|
+
createLeader,
|
|
7
|
+
createPresence,
|
|
8
|
+
createSharedStore,
|
|
9
|
+
DEFAULT_NAME,
|
|
10
|
+
NoopTransport
|
|
11
|
+
} from "@use-everywhere/core";
|
|
12
|
+
|
|
13
|
+
// src/dev.ts
|
|
14
|
+
var inDev = false;
|
|
15
|
+
try {
|
|
16
|
+
inDev = process.env.NODE_ENV !== "production";
|
|
17
|
+
} catch {
|
|
18
|
+
}
|
|
19
|
+
var warned = /* @__PURE__ */ new Set();
|
|
20
|
+
function devWarn(message) {
|
|
21
|
+
if (!inDev || warned.has(message)) return;
|
|
22
|
+
warned.add(message);
|
|
23
|
+
console.warn(message);
|
|
24
|
+
}
|
|
25
|
+
var seenInitials = /* @__PURE__ */ new Map();
|
|
26
|
+
function warnOnInitialMismatch(storeName, key, initial) {
|
|
27
|
+
if (!inDev) return;
|
|
28
|
+
const id = `${storeName} ${key}`;
|
|
29
|
+
if (!seenInitials.has(id)) {
|
|
30
|
+
seenInitials.set(id, initial);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const first = seenInitials.get(id);
|
|
34
|
+
const comparable = (v) => v === null || typeof v !== "object";
|
|
35
|
+
if (comparable(first) && comparable(initial) && !Object.is(first, initial)) {
|
|
36
|
+
devWarn(
|
|
37
|
+
`[use-everywhere] useSharedState('${key}') was called with different initial values (${String(first)} and ${String(initial)}). The first registration wins, so the second is ignored. Define the default once \u2014 defineStore, or a shared constant.`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/server-stubs.ts
|
|
43
|
+
var SERVER_CLIENT_ID = "";
|
|
44
|
+
var EMPTY = Object.freeze({});
|
|
45
|
+
var NO_PEERS = Object.freeze([]);
|
|
46
|
+
var noop = () => {
|
|
47
|
+
};
|
|
48
|
+
var unsubscribe = () => noop;
|
|
49
|
+
var INERT = Object.freeze({
|
|
50
|
+
clientId: SERVER_CLIENT_ID,
|
|
51
|
+
// store
|
|
52
|
+
state: EMPTY,
|
|
53
|
+
getVersions: () => EMPTY,
|
|
54
|
+
set: noop,
|
|
55
|
+
subscribeKey: unsubscribe,
|
|
56
|
+
registerKey: noop,
|
|
57
|
+
// store + leader share getSnapshot; presence has getPeers
|
|
58
|
+
getSnapshot: () => EMPTY,
|
|
59
|
+
getPeers: () => NO_PEERS,
|
|
60
|
+
// leader
|
|
61
|
+
resign: noop,
|
|
62
|
+
setEligible: noop,
|
|
63
|
+
// common
|
|
64
|
+
subscribe: unsubscribe,
|
|
65
|
+
close: noop
|
|
66
|
+
});
|
|
67
|
+
var createServerStore = () => INERT;
|
|
68
|
+
var createServerPresence = () => INERT;
|
|
69
|
+
var NO_LEADER = Object.freeze({ leaderId: null, isLeader: false });
|
|
70
|
+
var createServerLeader = () => ({ ...INERT, getSnapshot: () => NO_LEADER });
|
|
71
|
+
var createServerChannel = (name) => ({ ...INERT, name, post: noop, on: unsubscribe });
|
|
72
|
+
|
|
73
|
+
// src/registry.ts
|
|
74
|
+
var stores = /* @__PURE__ */ new Map();
|
|
75
|
+
var presences = /* @__PURE__ */ new Map();
|
|
76
|
+
var channels = /* @__PURE__ */ new Map();
|
|
77
|
+
var leaders = /* @__PURE__ */ new Map();
|
|
78
|
+
var storeConfig = /* @__PURE__ */ new Map();
|
|
79
|
+
var isServer = () => typeof window === "undefined";
|
|
80
|
+
var scopeOptions = {
|
|
81
|
+
everywhere: {},
|
|
82
|
+
tabs: { accept: (meta) => meta.kind !== "worker" },
|
|
83
|
+
tab: { transport: () => new NoopTransport() }
|
|
84
|
+
};
|
|
85
|
+
function getSharedStore(name = DEFAULT_NAME, scope = "everywhere") {
|
|
86
|
+
return getStore(name, scope);
|
|
87
|
+
}
|
|
88
|
+
function configSignature(options) {
|
|
89
|
+
const persist = options?.persist;
|
|
90
|
+
if (!persist) return "none";
|
|
91
|
+
return `persist:${persist.keys?.join(",") ?? "*"}:${persist.debounceMs ?? "default"}`;
|
|
92
|
+
}
|
|
93
|
+
function configureStore(name, scope, options) {
|
|
94
|
+
const key = `${scope} ${name}`;
|
|
95
|
+
if (stores.has(key)) {
|
|
96
|
+
if (configSignature(storeConfig.get(key)) === configSignature(options)) return;
|
|
97
|
+
devWarn(
|
|
98
|
+
`[use-everywhere] defineStore('${name}') ran after that store was already created, with different options. The live store keeps the configuration it was built with. Move defineStore to module scope, before any component reads the store.`
|
|
99
|
+
);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
storeConfig.set(key, options);
|
|
103
|
+
}
|
|
104
|
+
function getStore(name, scope = "everywhere") {
|
|
105
|
+
const key = `${scope} ${name}`;
|
|
106
|
+
let store = stores.get(key);
|
|
107
|
+
if (!store) {
|
|
108
|
+
store = isServer() ? createServerStore() : createSharedStore(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
|
|
109
|
+
stores.set(key, store);
|
|
110
|
+
}
|
|
111
|
+
return store;
|
|
112
|
+
}
|
|
113
|
+
function getPresence(name) {
|
|
114
|
+
let presence = presences.get(name);
|
|
115
|
+
if (!presence) {
|
|
116
|
+
presence = isServer() ? createServerPresence() : createPresence(name);
|
|
117
|
+
presences.set(name, presence);
|
|
118
|
+
}
|
|
119
|
+
return presence;
|
|
120
|
+
}
|
|
121
|
+
function getLeader(name, options) {
|
|
122
|
+
let leader = leaders.get(name);
|
|
123
|
+
if (!leader) {
|
|
124
|
+
leader = isServer() ? createServerLeader() : createLeader(name, options);
|
|
125
|
+
leaders.set(name, leader);
|
|
126
|
+
if (options) leaderOptions.set(name, options);
|
|
127
|
+
} else if (options) {
|
|
128
|
+
warnOnLeaderOptionConflict(name, options);
|
|
129
|
+
}
|
|
130
|
+
return leader;
|
|
131
|
+
}
|
|
132
|
+
var leaderOptions = /* @__PURE__ */ new Map();
|
|
133
|
+
function warnOnLeaderOptionConflict(name, options) {
|
|
134
|
+
const first = leaderOptions.get(name);
|
|
135
|
+
for (const key of ["heartbeatMs", "leaseMs"]) {
|
|
136
|
+
const requested = options[key];
|
|
137
|
+
if (requested !== void 0 && requested !== first?.[key]) {
|
|
138
|
+
devWarn(
|
|
139
|
+
`[use-everywhere] leader "${name}": ${key} ignored \u2014 the first useLeader/getLeader call fixes the election timings for this tab.`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function getChannel(name) {
|
|
145
|
+
let channel = channels.get(name);
|
|
146
|
+
if (!channel) {
|
|
147
|
+
channel = isServer() ? createServerChannel(name) : createChannel(name);
|
|
148
|
+
channels.set(name, channel);
|
|
149
|
+
}
|
|
150
|
+
return channel;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/use-peers.ts
|
|
154
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
155
|
+
var NO_PEERS2 = Object.freeze([]);
|
|
156
|
+
function usePeers(options) {
|
|
157
|
+
const presence = getPresence(options?.name ?? DEFAULT_NAME);
|
|
158
|
+
return useSyncExternalStore(
|
|
159
|
+
useCallback((onChange) => presence.subscribe(onChange), [presence]),
|
|
160
|
+
() => presence.getPeers(),
|
|
161
|
+
() => NO_PEERS2
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
function useClientId(options) {
|
|
165
|
+
const presence = getPresence(options?.name ?? DEFAULT_NAME);
|
|
166
|
+
return useSyncExternalStore(
|
|
167
|
+
// The id never changes for the life of a presence engine, so there is
|
|
168
|
+
// nothing to subscribe to — but uSES still needs the post-hydration read,
|
|
169
|
+
// which it performs when it attaches this subscription.
|
|
170
|
+
useCallback(() => () => {
|
|
171
|
+
}, [presence]),
|
|
172
|
+
() => presence.clientId,
|
|
173
|
+
() => SERVER_CLIENT_ID
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export {
|
|
178
|
+
warnOnInitialMismatch,
|
|
179
|
+
DEFAULT_NAME,
|
|
180
|
+
getSharedStore,
|
|
181
|
+
configureStore,
|
|
182
|
+
getStore,
|
|
183
|
+
getLeader,
|
|
184
|
+
getChannel,
|
|
185
|
+
usePeers,
|
|
186
|
+
useClientId
|
|
187
|
+
};
|
package/dist/devtools/index.cjs
CHANGED
|
@@ -31,9 +31,41 @@ var import_core2 = require("@use-everywhere/core");
|
|
|
31
31
|
|
|
32
32
|
// src/registry.ts
|
|
33
33
|
var import_core = require("@use-everywhere/core");
|
|
34
|
+
|
|
35
|
+
// src/server-stubs.ts
|
|
36
|
+
var SERVER_CLIENT_ID = "";
|
|
37
|
+
var EMPTY = Object.freeze({});
|
|
38
|
+
var NO_PEERS = Object.freeze([]);
|
|
39
|
+
var noop = () => {
|
|
40
|
+
};
|
|
41
|
+
var unsubscribe = () => noop;
|
|
42
|
+
var INERT = Object.freeze({
|
|
43
|
+
clientId: SERVER_CLIENT_ID,
|
|
44
|
+
// store
|
|
45
|
+
state: EMPTY,
|
|
46
|
+
getVersions: () => EMPTY,
|
|
47
|
+
set: noop,
|
|
48
|
+
subscribeKey: unsubscribe,
|
|
49
|
+
registerKey: noop,
|
|
50
|
+
// store + leader share getSnapshot; presence has getPeers
|
|
51
|
+
getSnapshot: () => EMPTY,
|
|
52
|
+
getPeers: () => NO_PEERS,
|
|
53
|
+
// leader
|
|
54
|
+
resign: noop,
|
|
55
|
+
setEligible: noop,
|
|
56
|
+
// common
|
|
57
|
+
subscribe: unsubscribe,
|
|
58
|
+
close: noop
|
|
59
|
+
});
|
|
60
|
+
var createServerStore = () => INERT;
|
|
61
|
+
var createServerPresence = () => INERT;
|
|
62
|
+
var NO_LEADER = Object.freeze({ leaderId: null, isLeader: false });
|
|
63
|
+
|
|
64
|
+
// src/registry.ts
|
|
34
65
|
var stores = /* @__PURE__ */ new Map();
|
|
35
66
|
var presences = /* @__PURE__ */ new Map();
|
|
36
67
|
var storeConfig = /* @__PURE__ */ new Map();
|
|
68
|
+
var isServer = () => typeof window === "undefined";
|
|
37
69
|
var scopeOptions = {
|
|
38
70
|
everywhere: {},
|
|
39
71
|
tabs: { accept: (meta) => meta.kind !== "worker" },
|
|
@@ -46,7 +78,7 @@ function getStore(name, scope = "everywhere") {
|
|
|
46
78
|
const key = `${scope} ${name}`;
|
|
47
79
|
let store = stores.get(key);
|
|
48
80
|
if (!store) {
|
|
49
|
-
store = (0, import_core.createSharedStore)(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
|
|
81
|
+
store = isServer() ? createServerStore() : (0, import_core.createSharedStore)(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
|
|
50
82
|
stores.set(key, store);
|
|
51
83
|
}
|
|
52
84
|
return store;
|
|
@@ -54,7 +86,7 @@ function getStore(name, scope = "everywhere") {
|
|
|
54
86
|
function getPresence(name) {
|
|
55
87
|
let presence = presences.get(name);
|
|
56
88
|
if (!presence) {
|
|
57
|
-
presence = (0, import_core.createPresence)(name);
|
|
89
|
+
presence = isServer() ? createServerPresence() : (0, import_core.createPresence)(name);
|
|
58
90
|
presences.set(name, presence);
|
|
59
91
|
}
|
|
60
92
|
return presence;
|
|
@@ -62,13 +94,13 @@ function getPresence(name) {
|
|
|
62
94
|
|
|
63
95
|
// src/use-peers.ts
|
|
64
96
|
var import_react = require("react");
|
|
65
|
-
var
|
|
97
|
+
var NO_PEERS2 = Object.freeze([]);
|
|
66
98
|
function usePeers(options) {
|
|
67
99
|
const presence = getPresence(options?.name ?? import_core.DEFAULT_NAME);
|
|
68
100
|
return (0, import_react.useSyncExternalStore)(
|
|
69
101
|
(0, import_react.useCallback)((onChange) => presence.subscribe(onChange), [presence]),
|
|
70
102
|
() => presence.getPeers(),
|
|
71
|
-
() =>
|
|
103
|
+
() => NO_PEERS2
|
|
72
104
|
);
|
|
73
105
|
}
|
|
74
106
|
|
package/dist/devtools/index.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -16,17 +16,35 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
16
|
}
|
|
17
17
|
return to;
|
|
18
18
|
};
|
|
19
|
-
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
20
19
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
20
|
|
|
22
21
|
// src/index.ts
|
|
23
22
|
var src_exports = {};
|
|
24
23
|
__export(src_exports, {
|
|
24
|
+
BroadcastChannelTransport: () => import_core3.BroadcastChannelTransport,
|
|
25
|
+
CID_PARAM: () => import_core3.CID_PARAM,
|
|
25
26
|
DEFAULT_NAME: () => import_core.DEFAULT_NAME,
|
|
27
|
+
HandshakeTimeoutError: () => import_core3.HandshakeTimeoutError,
|
|
28
|
+
NoopTransport: () => import_core3.NoopTransport,
|
|
29
|
+
WindowClosedError: () => import_core3.WindowClosedError,
|
|
30
|
+
connectToOpener: () => import_core3.connectToOpener,
|
|
31
|
+
createChannel: () => import_core3.createChannel,
|
|
32
|
+
createLeader: () => import_core3.createLeader,
|
|
33
|
+
createPresence: () => import_core3.createPresence,
|
|
34
|
+
createSharedStore: () => import_core3.createSharedStore,
|
|
35
|
+
defaultTransport: () => import_core3.defaultTransport,
|
|
26
36
|
defineChannel: () => defineChannel,
|
|
27
37
|
defineStore: () => defineStore,
|
|
38
|
+
enableDebug: () => import_core3.enableDebug,
|
|
39
|
+
getBusNames: () => import_core3.getBusNames,
|
|
28
40
|
getLeader: () => getLeader,
|
|
29
41
|
getSharedStore: () => getSharedStore,
|
|
42
|
+
isBroadcastChannelAvailable: () => import_core3.isBroadcastChannelAvailable,
|
|
43
|
+
localStorageAdapter: () => import_core3.localStorageAdapter,
|
|
44
|
+
newer: () => import_core3.newer,
|
|
45
|
+
observeBus: () => import_core3.observeBus,
|
|
46
|
+
openWindow: () => import_core3.openWindow,
|
|
47
|
+
sessionStorageAdapter: () => import_core3.sessionStorageAdapter,
|
|
30
48
|
useChannel: () => useChannel,
|
|
31
49
|
useClientId: () => useClientId,
|
|
32
50
|
useIsLeader: () => useIsLeader,
|
|
@@ -36,20 +54,84 @@ __export(src_exports, {
|
|
|
36
54
|
useOpenedWindow: () => useOpenedWindow,
|
|
37
55
|
usePeers: () => usePeers,
|
|
38
56
|
useSend: () => useSend,
|
|
39
|
-
useSharedState: () => useSharedState
|
|
57
|
+
useSharedState: () => useSharedState,
|
|
58
|
+
webStorageAdapter: () => import_core3.webStorageAdapter
|
|
40
59
|
});
|
|
41
60
|
module.exports = __toCommonJS(src_exports);
|
|
42
61
|
|
|
43
62
|
// src/use-shared-state.ts
|
|
44
63
|
var import_react = require("react");
|
|
45
64
|
|
|
65
|
+
// src/dev.ts
|
|
66
|
+
var inDev = false;
|
|
67
|
+
try {
|
|
68
|
+
inDev = process.env.NODE_ENV !== "production";
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
var warned = /* @__PURE__ */ new Set();
|
|
72
|
+
function devWarn(message) {
|
|
73
|
+
if (!inDev || warned.has(message)) return;
|
|
74
|
+
warned.add(message);
|
|
75
|
+
console.warn(message);
|
|
76
|
+
}
|
|
77
|
+
var seenInitials = /* @__PURE__ */ new Map();
|
|
78
|
+
function warnOnInitialMismatch(storeName, key, initial) {
|
|
79
|
+
if (!inDev) return;
|
|
80
|
+
const id = `${storeName} ${key}`;
|
|
81
|
+
if (!seenInitials.has(id)) {
|
|
82
|
+
seenInitials.set(id, initial);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const first = seenInitials.get(id);
|
|
86
|
+
const comparable = (v) => v === null || typeof v !== "object";
|
|
87
|
+
if (comparable(first) && comparable(initial) && !Object.is(first, initial)) {
|
|
88
|
+
devWarn(
|
|
89
|
+
`[use-everywhere] useSharedState('${key}') was called with different initial values (${String(first)} and ${String(initial)}). The first registration wins, so the second is ignored. Define the default once \u2014 defineStore, or a shared constant.`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
46
94
|
// src/registry.ts
|
|
47
95
|
var import_core = require("@use-everywhere/core");
|
|
96
|
+
|
|
97
|
+
// src/server-stubs.ts
|
|
98
|
+
var SERVER_CLIENT_ID = "";
|
|
99
|
+
var EMPTY = Object.freeze({});
|
|
100
|
+
var NO_PEERS = Object.freeze([]);
|
|
101
|
+
var noop = () => {
|
|
102
|
+
};
|
|
103
|
+
var unsubscribe = () => noop;
|
|
104
|
+
var INERT = Object.freeze({
|
|
105
|
+
clientId: SERVER_CLIENT_ID,
|
|
106
|
+
// store
|
|
107
|
+
state: EMPTY,
|
|
108
|
+
getVersions: () => EMPTY,
|
|
109
|
+
set: noop,
|
|
110
|
+
subscribeKey: unsubscribe,
|
|
111
|
+
registerKey: noop,
|
|
112
|
+
// store + leader share getSnapshot; presence has getPeers
|
|
113
|
+
getSnapshot: () => EMPTY,
|
|
114
|
+
getPeers: () => NO_PEERS,
|
|
115
|
+
// leader
|
|
116
|
+
resign: noop,
|
|
117
|
+
setEligible: noop,
|
|
118
|
+
// common
|
|
119
|
+
subscribe: unsubscribe,
|
|
120
|
+
close: noop
|
|
121
|
+
});
|
|
122
|
+
var createServerStore = () => INERT;
|
|
123
|
+
var createServerPresence = () => INERT;
|
|
124
|
+
var NO_LEADER = Object.freeze({ leaderId: null, isLeader: false });
|
|
125
|
+
var createServerLeader = () => ({ ...INERT, getSnapshot: () => NO_LEADER });
|
|
126
|
+
var createServerChannel = (name) => ({ ...INERT, name, post: noop, on: unsubscribe });
|
|
127
|
+
|
|
128
|
+
// src/registry.ts
|
|
48
129
|
var stores = /* @__PURE__ */ new Map();
|
|
49
130
|
var presences = /* @__PURE__ */ new Map();
|
|
50
131
|
var channels = /* @__PURE__ */ new Map();
|
|
51
132
|
var leaders = /* @__PURE__ */ new Map();
|
|
52
133
|
var storeConfig = /* @__PURE__ */ new Map();
|
|
134
|
+
var isServer = () => typeof window === "undefined";
|
|
53
135
|
var scopeOptions = {
|
|
54
136
|
everywhere: {},
|
|
55
137
|
tabs: { accept: (meta) => meta.kind !== "worker" },
|
|
@@ -58,12 +140,19 @@ var scopeOptions = {
|
|
|
58
140
|
function getSharedStore(name = import_core.DEFAULT_NAME, scope = "everywhere") {
|
|
59
141
|
return getStore(name, scope);
|
|
60
142
|
}
|
|
143
|
+
function configSignature(options) {
|
|
144
|
+
const persist = options?.persist;
|
|
145
|
+
if (!persist) return "none";
|
|
146
|
+
return `persist:${persist.keys?.join(",") ?? "*"}:${persist.debounceMs ?? "default"}`;
|
|
147
|
+
}
|
|
61
148
|
function configureStore(name, scope, options) {
|
|
62
149
|
const key = `${scope} ${name}`;
|
|
63
150
|
if (stores.has(key)) {
|
|
64
|
-
|
|
65
|
-
|
|
151
|
+
if (configSignature(storeConfig.get(key)) === configSignature(options)) return;
|
|
152
|
+
devWarn(
|
|
153
|
+
`[use-everywhere] defineStore('${name}') ran after that store was already created, with different options. The live store keeps the configuration it was built with. Move defineStore to module scope, before any component reads the store.`
|
|
66
154
|
);
|
|
155
|
+
return;
|
|
67
156
|
}
|
|
68
157
|
storeConfig.set(key, options);
|
|
69
158
|
}
|
|
@@ -71,7 +160,7 @@ function getStore(name, scope = "everywhere") {
|
|
|
71
160
|
const key = `${scope} ${name}`;
|
|
72
161
|
let store = stores.get(key);
|
|
73
162
|
if (!store) {
|
|
74
|
-
store = (0, import_core.createSharedStore)(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
|
|
163
|
+
store = isServer() ? createServerStore() : (0, import_core.createSharedStore)(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
|
|
75
164
|
stores.set(key, store);
|
|
76
165
|
}
|
|
77
166
|
return store;
|
|
@@ -79,7 +168,7 @@ function getStore(name, scope = "everywhere") {
|
|
|
79
168
|
function getPresence(name) {
|
|
80
169
|
let presence = presences.get(name);
|
|
81
170
|
if (!presence) {
|
|
82
|
-
presence = (0, import_core.createPresence)(name);
|
|
171
|
+
presence = isServer() ? createServerPresence() : (0, import_core.createPresence)(name);
|
|
83
172
|
presences.set(name, presence);
|
|
84
173
|
}
|
|
85
174
|
return presence;
|
|
@@ -87,15 +176,30 @@ function getPresence(name) {
|
|
|
87
176
|
function getLeader(name, options) {
|
|
88
177
|
let leader = leaders.get(name);
|
|
89
178
|
if (!leader) {
|
|
90
|
-
leader = (0, import_core.createLeader)(name, options);
|
|
179
|
+
leader = isServer() ? createServerLeader() : (0, import_core.createLeader)(name, options);
|
|
91
180
|
leaders.set(name, leader);
|
|
181
|
+
if (options) leaderOptions.set(name, options);
|
|
182
|
+
} else if (options) {
|
|
183
|
+
warnOnLeaderOptionConflict(name, options);
|
|
92
184
|
}
|
|
93
185
|
return leader;
|
|
94
186
|
}
|
|
187
|
+
var leaderOptions = /* @__PURE__ */ new Map();
|
|
188
|
+
function warnOnLeaderOptionConflict(name, options) {
|
|
189
|
+
const first = leaderOptions.get(name);
|
|
190
|
+
for (const key of ["heartbeatMs", "leaseMs"]) {
|
|
191
|
+
const requested = options[key];
|
|
192
|
+
if (requested !== void 0 && requested !== first?.[key]) {
|
|
193
|
+
devWarn(
|
|
194
|
+
`[use-everywhere] leader "${name}": ${key} ignored \u2014 the first useLeader/getLeader call fixes the election timings for this tab.`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
95
199
|
function getChannel(name) {
|
|
96
200
|
let channel = channels.get(name);
|
|
97
201
|
if (!channel) {
|
|
98
|
-
channel = (0, import_core.createChannel)(name);
|
|
202
|
+
channel = isServer() ? createServerChannel(name) : (0, import_core.createChannel)(name);
|
|
99
203
|
channels.set(name, channel);
|
|
100
204
|
}
|
|
101
205
|
return channel;
|
|
@@ -103,8 +207,10 @@ function getChannel(name) {
|
|
|
103
207
|
|
|
104
208
|
// src/use-shared-state.ts
|
|
105
209
|
function useSharedState(key, initial, options) {
|
|
106
|
-
const
|
|
210
|
+
const storeName = options?.store ?? import_core.DEFAULT_NAME;
|
|
211
|
+
const store = getStore(storeName, options?.scope ?? "everywhere");
|
|
107
212
|
store.registerKey(key, initial);
|
|
213
|
+
warnOnInitialMismatch(storeName, key, initial);
|
|
108
214
|
const value = (0, import_react.useSyncExternalStore)(
|
|
109
215
|
(0, import_react.useCallback)((onChange) => store.subscribeKey(key, onChange), [store, key]),
|
|
110
216
|
() => store.getSnapshot()[key],
|
|
@@ -167,22 +273,31 @@ function defineStore(name, options = {}) {
|
|
|
167
273
|
|
|
168
274
|
// src/use-peers.ts
|
|
169
275
|
var import_react3 = require("react");
|
|
170
|
-
var
|
|
276
|
+
var NO_PEERS2 = Object.freeze([]);
|
|
171
277
|
function usePeers(options) {
|
|
172
278
|
const presence = getPresence(options?.name ?? import_core.DEFAULT_NAME);
|
|
173
279
|
return (0, import_react3.useSyncExternalStore)(
|
|
174
280
|
(0, import_react3.useCallback)((onChange) => presence.subscribe(onChange), [presence]),
|
|
175
281
|
() => presence.getPeers(),
|
|
176
|
-
() =>
|
|
282
|
+
() => NO_PEERS2
|
|
177
283
|
);
|
|
178
284
|
}
|
|
179
285
|
function useClientId(options) {
|
|
180
|
-
|
|
286
|
+
const presence = getPresence(options?.name ?? import_core.DEFAULT_NAME);
|
|
287
|
+
return (0, import_react3.useSyncExternalStore)(
|
|
288
|
+
// The id never changes for the life of a presence engine, so there is
|
|
289
|
+
// nothing to subscribe to — but uSES still needs the post-hydration read,
|
|
290
|
+
// which it performs when it attaches this subscription.
|
|
291
|
+
(0, import_react3.useCallback)(() => () => {
|
|
292
|
+
}, [presence]),
|
|
293
|
+
() => presence.clientId,
|
|
294
|
+
() => SERVER_CLIENT_ID
|
|
295
|
+
);
|
|
181
296
|
}
|
|
182
297
|
|
|
183
298
|
// src/use-leader.ts
|
|
184
299
|
var import_react4 = require("react");
|
|
185
|
-
var
|
|
300
|
+
var NO_LEADER2 = Object.freeze({ leaderId: null, isLeader: false });
|
|
186
301
|
function useLeader(options) {
|
|
187
302
|
const leader = getLeader(options?.name ?? import_core.DEFAULT_NAME, options);
|
|
188
303
|
const eligible = options?.eligible;
|
|
@@ -193,7 +308,7 @@ function useLeader(options) {
|
|
|
193
308
|
return (0, import_react4.useSyncExternalStore)(
|
|
194
309
|
(0, import_react4.useCallback)((onChange) => leader.subscribe(onChange), [leader]),
|
|
195
310
|
() => leader.getSnapshot(),
|
|
196
|
-
() =>
|
|
311
|
+
() => NO_LEADER2
|
|
197
312
|
);
|
|
198
313
|
}
|
|
199
314
|
function useIsLeader(options) {
|
|
@@ -214,31 +329,36 @@ function useLeaderEffect(effect, options) {
|
|
|
214
329
|
// src/use-opened-window.ts
|
|
215
330
|
var import_react5 = require("react");
|
|
216
331
|
var import_core2 = require("@use-everywhere/core");
|
|
332
|
+
var IDLE = Object.freeze({
|
|
333
|
+
status: "idle",
|
|
334
|
+
result: void 0,
|
|
335
|
+
error: void 0
|
|
336
|
+
});
|
|
217
337
|
function useOpenedWindow(factory) {
|
|
218
|
-
const [
|
|
219
|
-
const [result, setResult] = (0, import_react5.useState)(void 0);
|
|
220
|
-
const [error, setError] = (0, import_react5.useState)(void 0);
|
|
338
|
+
const [state, setState] = (0, import_react5.useState)(IDLE);
|
|
221
339
|
const current = (0, import_react5.useRef)(null);
|
|
222
340
|
const factoryRef = (0, import_react5.useRef)(factory);
|
|
223
|
-
|
|
341
|
+
(0, import_react5.useEffect)(() => {
|
|
342
|
+
factoryRef.current = factory;
|
|
343
|
+
});
|
|
224
344
|
const open = (0, import_react5.useCallback)(() => {
|
|
225
345
|
current.current?.close();
|
|
226
346
|
let opened;
|
|
227
347
|
try {
|
|
228
348
|
opened = factoryRef.current();
|
|
229
349
|
} catch (err) {
|
|
230
|
-
|
|
231
|
-
setError(err);
|
|
350
|
+
setState({ status: "error", result: void 0, error: err });
|
|
232
351
|
return;
|
|
233
352
|
}
|
|
234
353
|
current.current = opened;
|
|
235
|
-
|
|
236
|
-
setResult(void 0);
|
|
237
|
-
setError(void 0);
|
|
354
|
+
setState({ status: "opening", result: void 0, error: void 0 });
|
|
238
355
|
const fresh = () => current.current === opened;
|
|
239
356
|
opened.ready.then(
|
|
240
357
|
() => {
|
|
241
|
-
if (fresh())
|
|
358
|
+
if (!fresh()) return;
|
|
359
|
+
setState(
|
|
360
|
+
(s) => s.status === "opening" ? { status: "connected", result: void 0, error: void 0 } : s
|
|
361
|
+
);
|
|
242
362
|
},
|
|
243
363
|
() => {
|
|
244
364
|
}
|
|
@@ -246,14 +366,13 @@ function useOpenedWindow(factory) {
|
|
|
246
366
|
);
|
|
247
367
|
opened.result.then(
|
|
248
368
|
(value) => {
|
|
249
|
-
if (
|
|
250
|
-
setResult(value);
|
|
251
|
-
setStatus("done");
|
|
369
|
+
if (fresh()) setState({ status: "done", result: value, error: void 0 });
|
|
252
370
|
},
|
|
253
371
|
(err) => {
|
|
254
372
|
if (!fresh()) return;
|
|
255
|
-
|
|
256
|
-
|
|
373
|
+
setState(
|
|
374
|
+
err instanceof import_core2.WindowClosedError ? { status: "closed-early", result: void 0, error: err } : { status: "error", result: void 0, error: err }
|
|
375
|
+
);
|
|
257
376
|
}
|
|
258
377
|
);
|
|
259
378
|
}, []);
|
|
@@ -261,18 +380,37 @@ function useOpenedWindow(factory) {
|
|
|
261
380
|
current.current?.post(type, payload);
|
|
262
381
|
}, []);
|
|
263
382
|
const close = (0, import_react5.useCallback)(() => current.current?.close(), []);
|
|
264
|
-
return {
|
|
383
|
+
return { ...state, open, post, close };
|
|
265
384
|
}
|
|
266
385
|
|
|
267
386
|
// src/index.ts
|
|
268
|
-
|
|
387
|
+
var import_core3 = require("@use-everywhere/core");
|
|
269
388
|
// Annotate the CommonJS export names for ESM import in node:
|
|
270
389
|
0 && (module.exports = {
|
|
390
|
+
BroadcastChannelTransport,
|
|
391
|
+
CID_PARAM,
|
|
271
392
|
DEFAULT_NAME,
|
|
393
|
+
HandshakeTimeoutError,
|
|
394
|
+
NoopTransport,
|
|
395
|
+
WindowClosedError,
|
|
396
|
+
connectToOpener,
|
|
397
|
+
createChannel,
|
|
398
|
+
createLeader,
|
|
399
|
+
createPresence,
|
|
400
|
+
createSharedStore,
|
|
401
|
+
defaultTransport,
|
|
272
402
|
defineChannel,
|
|
273
403
|
defineStore,
|
|
404
|
+
enableDebug,
|
|
405
|
+
getBusNames,
|
|
274
406
|
getLeader,
|
|
275
407
|
getSharedStore,
|
|
408
|
+
isBroadcastChannelAvailable,
|
|
409
|
+
localStorageAdapter,
|
|
410
|
+
newer,
|
|
411
|
+
observeBus,
|
|
412
|
+
openWindow,
|
|
413
|
+
sessionStorageAdapter,
|
|
276
414
|
useChannel,
|
|
277
415
|
useClientId,
|
|
278
416
|
useIsLeader,
|
|
@@ -283,5 +421,5 @@ __reExport(src_exports, require("@use-everywhere/core"), module.exports);
|
|
|
283
421
|
usePeers,
|
|
284
422
|
useSend,
|
|
285
423
|
useSharedState,
|
|
286
|
-
|
|
424
|
+
webStorageAdapter
|
|
287
425
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { SharedStore, LeaderOptions, Leader, MessageMap, Channel, MessageMeta, PersistAdapter, Peer, LeaderSnapshot, OpenedWindow } from '@use-everywhere/core';
|
|
2
|
-
export
|
|
3
|
-
export { DEFAULT_NAME } from '@use-everywhere/core';
|
|
1
|
+
import { SharedStore, LeaderOptions, Leader, MessageMap, Channel, MessageMeta, PersistAdapter, Peer, LeaderSnapshot, OpenedWindow, WindowClosedError } from '@use-everywhere/core';
|
|
2
|
+
export { BroadcastChannelTransport, BusEvent, BusObserver, BusWire, CID_PARAM, Channel, CommonOptions, ConnectToOpenerOptions, DEFAULT_NAME, DebugOptions, HandshakeTimeoutError, Leader, LeaderOptions, LeaderSnapshot, MessageEventLike, MessageMap, MessageMeta, NoopTransport, OpenWindowOptions, OpenedWindow, OpenerConnection, Peer, PeerKind, PersistAdapter, PersistOptions, Persisted, Presence, PresenceOptions, SharedStore, SharedStoreOptions, StorageLike, Transport, Version, WebStorageAdapterOptions, WindowClosedError, WindowEventTarget, WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedStore, defaultTransport, enableDebug, getBusNames, isBroadcastChannelAvailable, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter } from '@use-everywhere/core';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* How far a shared value travels:
|
|
@@ -21,6 +20,12 @@ interface UseSharedStateOptions {
|
|
|
21
20
|
* this origin. Late-joining tabs hydrate to the current value; concurrent
|
|
22
21
|
* writes converge last-writer-wins. Pass options.scope to delimit how much
|
|
23
22
|
* is shared ('everywhere' | 'tabs' | 'tab').
|
|
23
|
+
*
|
|
24
|
+
* Note the convergence rule is last-writer-wins per key, not per operation:
|
|
25
|
+
* two tabs running `set(n => n + 1)` at the same moment agree on one value,
|
|
26
|
+
* and one of the increments is lost. For counters and other accumulating
|
|
27
|
+
* writes, drive them from a single tab (useLeaderEffect) until an op-based
|
|
28
|
+
* primitive lands.
|
|
24
29
|
*/
|
|
25
30
|
declare function useSharedState<T>(key: string, initial: T, options?: UseSharedStateOptions): [T, (next: T | ((prev: T) => T)) => void];
|
|
26
31
|
|
|
@@ -105,7 +110,16 @@ declare function defineStore<S extends Record<string, unknown> = Record<string,
|
|
|
105
110
|
declare function usePeers(options?: {
|
|
106
111
|
name?: string;
|
|
107
112
|
}): readonly Peer[];
|
|
108
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* This client's own id on the presence bus (matches patch origin ids).
|
|
115
|
+
*
|
|
116
|
+
* Read through useSyncExternalStore rather than returned straight from render,
|
|
117
|
+
* because the id is minted per environment: a server would render one value and
|
|
118
|
+
* the browser a different one, and any component that puts it in the DOM would
|
|
119
|
+
* mismatch on hydration. The server snapshot is a constant empty string, which
|
|
120
|
+
* React also uses for the client's hydrating render, so markup matches; the
|
|
121
|
+
* real id arrives in the commit straight after. Treat `''` as "not known yet".
|
|
122
|
+
*/
|
|
109
123
|
declare function useClientId(options?: {
|
|
110
124
|
name?: string;
|
|
111
125
|
}): string;
|
|
@@ -130,23 +144,59 @@ declare function useIsLeader(options?: UseLeaderOptions): boolean;
|
|
|
130
144
|
declare function useLeaderEffect(effect: () => void | (() => void), options?: UseLeaderOptions): void;
|
|
131
145
|
|
|
132
146
|
type OpenedWindowStatus = 'idle' | 'opening' | 'connected' | 'done' | 'closed-early' | 'error';
|
|
133
|
-
|
|
147
|
+
/**
|
|
148
|
+
* The flow's state as a discriminated union, so `status` narrows `result` and
|
|
149
|
+
* `error` for you. Three independent fields could not: `result` was `R |
|
|
150
|
+
* undefined` even in the 'done' branch, which is why reading it needed a
|
|
151
|
+
* non-null assertion at every call site.
|
|
152
|
+
*/
|
|
153
|
+
type OpenedWindowState<R> = {
|
|
154
|
+
status: 'idle';
|
|
155
|
+
result: undefined;
|
|
156
|
+
error: undefined;
|
|
157
|
+
} | {
|
|
158
|
+
status: 'opening';
|
|
159
|
+
result: undefined;
|
|
160
|
+
error: undefined;
|
|
161
|
+
} | {
|
|
162
|
+
status: 'connected';
|
|
163
|
+
result: undefined;
|
|
164
|
+
error: undefined;
|
|
165
|
+
} | {
|
|
166
|
+
status: 'done';
|
|
167
|
+
result: R;
|
|
168
|
+
error: undefined;
|
|
169
|
+
}
|
|
170
|
+
/** The child window went away before calling finish(). */
|
|
171
|
+
| {
|
|
172
|
+
status: 'closed-early';
|
|
173
|
+
result: undefined;
|
|
174
|
+
error: WindowClosedError;
|
|
175
|
+
}
|
|
176
|
+
/** The handshake timed out, the popup was blocked, or the factory threw. */
|
|
177
|
+
| {
|
|
178
|
+
status: 'error';
|
|
179
|
+
result: undefined;
|
|
180
|
+
error: unknown;
|
|
181
|
+
};
|
|
182
|
+
interface OpenedWindowControls<Out extends MessageMap, In extends MessageMap, R> {
|
|
134
183
|
/** Call from a click handler (popup blockers require a user gesture). */
|
|
135
184
|
open: () => void;
|
|
136
|
-
status: OpenedWindowStatus;
|
|
137
|
-
/** The child's finish() value, once status is 'done'. */
|
|
138
|
-
result: R | undefined;
|
|
139
|
-
error: unknown;
|
|
140
185
|
/** Post to the child; no-op while nothing is open. */
|
|
141
186
|
post: OpenedWindow<Out, In, R>['post'];
|
|
142
187
|
close: () => void;
|
|
143
188
|
}
|
|
189
|
+
type UseOpenedWindow<Out extends MessageMap, In extends MessageMap, R> = OpenedWindowState<R> & OpenedWindowControls<Out, In, R>;
|
|
144
190
|
|
|
145
191
|
/**
|
|
146
192
|
* Drive an openWindow() flow from a component: the factory is called on
|
|
147
193
|
* open(), and the child window's lifecycle is folded into render state.
|
|
148
194
|
* Reopening replaces the previous window; a stale window's outcome is ignored.
|
|
195
|
+
*
|
|
196
|
+
* The child window deliberately outlives an unmount: closing a payment page
|
|
197
|
+
* because a route changed would lose the user's in-flight transaction. Call
|
|
198
|
+
* close() if the component owns the window's lifetime.
|
|
149
199
|
*/
|
|
150
200
|
declare function useOpenedWindow<Out extends MessageMap, In extends MessageMap, R = unknown>(factory: () => OpenedWindow<Out, In, R>): UseOpenedWindow<Out, In, R>;
|
|
151
201
|
|
|
152
|
-
export { type AnyStore, type ChannelHooks, type DefineStoreOptions, type OpenedWindowStatus, type ShareScope, type StoreHooks, type UseLeaderOptions, type UseOpenedWindow, type UseSharedStateOptions, defineChannel, defineStore, getLeader, getSharedStore, useChannel, useClientId, useIsLeader, useLeader, useLeaderEffect, useMessage, useOpenedWindow, usePeers, useSend, useSharedState };
|
|
202
|
+
export { type AnyStore, type ChannelHooks, type DefineStoreOptions, type OpenedWindowControls, type OpenedWindowState, type OpenedWindowStatus, type ShareScope, type StoreHooks, type UseLeaderOptions, type UseOpenedWindow, type UseSharedStateOptions, defineChannel, defineStore, getLeader, getSharedStore, useChannel, useClientId, useIsLeader, useLeader, useLeaderEffect, useMessage, useOpenedWindow, usePeers, useSend, useSharedState };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { SharedStore, LeaderOptions, Leader, MessageMap, Channel, MessageMeta, PersistAdapter, Peer, LeaderSnapshot, OpenedWindow } from '@use-everywhere/core';
|
|
2
|
-
export
|
|
3
|
-
export { DEFAULT_NAME } from '@use-everywhere/core';
|
|
1
|
+
import { SharedStore, LeaderOptions, Leader, MessageMap, Channel, MessageMeta, PersistAdapter, Peer, LeaderSnapshot, OpenedWindow, WindowClosedError } from '@use-everywhere/core';
|
|
2
|
+
export { BroadcastChannelTransport, BusEvent, BusObserver, BusWire, CID_PARAM, Channel, CommonOptions, ConnectToOpenerOptions, DEFAULT_NAME, DebugOptions, HandshakeTimeoutError, Leader, LeaderOptions, LeaderSnapshot, MessageEventLike, MessageMap, MessageMeta, NoopTransport, OpenWindowOptions, OpenedWindow, OpenerConnection, Peer, PeerKind, PersistAdapter, PersistOptions, Persisted, Presence, PresenceOptions, SharedStore, SharedStoreOptions, StorageLike, Transport, Version, WebStorageAdapterOptions, WindowClosedError, WindowEventTarget, WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedStore, defaultTransport, enableDebug, getBusNames, isBroadcastChannelAvailable, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter } from '@use-everywhere/core';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* How far a shared value travels:
|
|
@@ -21,6 +20,12 @@ interface UseSharedStateOptions {
|
|
|
21
20
|
* this origin. Late-joining tabs hydrate to the current value; concurrent
|
|
22
21
|
* writes converge last-writer-wins. Pass options.scope to delimit how much
|
|
23
22
|
* is shared ('everywhere' | 'tabs' | 'tab').
|
|
23
|
+
*
|
|
24
|
+
* Note the convergence rule is last-writer-wins per key, not per operation:
|
|
25
|
+
* two tabs running `set(n => n + 1)` at the same moment agree on one value,
|
|
26
|
+
* and one of the increments is lost. For counters and other accumulating
|
|
27
|
+
* writes, drive them from a single tab (useLeaderEffect) until an op-based
|
|
28
|
+
* primitive lands.
|
|
24
29
|
*/
|
|
25
30
|
declare function useSharedState<T>(key: string, initial: T, options?: UseSharedStateOptions): [T, (next: T | ((prev: T) => T)) => void];
|
|
26
31
|
|
|
@@ -105,7 +110,16 @@ declare function defineStore<S extends Record<string, unknown> = Record<string,
|
|
|
105
110
|
declare function usePeers(options?: {
|
|
106
111
|
name?: string;
|
|
107
112
|
}): readonly Peer[];
|
|
108
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* This client's own id on the presence bus (matches patch origin ids).
|
|
115
|
+
*
|
|
116
|
+
* Read through useSyncExternalStore rather than returned straight from render,
|
|
117
|
+
* because the id is minted per environment: a server would render one value and
|
|
118
|
+
* the browser a different one, and any component that puts it in the DOM would
|
|
119
|
+
* mismatch on hydration. The server snapshot is a constant empty string, which
|
|
120
|
+
* React also uses for the client's hydrating render, so markup matches; the
|
|
121
|
+
* real id arrives in the commit straight after. Treat `''` as "not known yet".
|
|
122
|
+
*/
|
|
109
123
|
declare function useClientId(options?: {
|
|
110
124
|
name?: string;
|
|
111
125
|
}): string;
|
|
@@ -130,23 +144,59 @@ declare function useIsLeader(options?: UseLeaderOptions): boolean;
|
|
|
130
144
|
declare function useLeaderEffect(effect: () => void | (() => void), options?: UseLeaderOptions): void;
|
|
131
145
|
|
|
132
146
|
type OpenedWindowStatus = 'idle' | 'opening' | 'connected' | 'done' | 'closed-early' | 'error';
|
|
133
|
-
|
|
147
|
+
/**
|
|
148
|
+
* The flow's state as a discriminated union, so `status` narrows `result` and
|
|
149
|
+
* `error` for you. Three independent fields could not: `result` was `R |
|
|
150
|
+
* undefined` even in the 'done' branch, which is why reading it needed a
|
|
151
|
+
* non-null assertion at every call site.
|
|
152
|
+
*/
|
|
153
|
+
type OpenedWindowState<R> = {
|
|
154
|
+
status: 'idle';
|
|
155
|
+
result: undefined;
|
|
156
|
+
error: undefined;
|
|
157
|
+
} | {
|
|
158
|
+
status: 'opening';
|
|
159
|
+
result: undefined;
|
|
160
|
+
error: undefined;
|
|
161
|
+
} | {
|
|
162
|
+
status: 'connected';
|
|
163
|
+
result: undefined;
|
|
164
|
+
error: undefined;
|
|
165
|
+
} | {
|
|
166
|
+
status: 'done';
|
|
167
|
+
result: R;
|
|
168
|
+
error: undefined;
|
|
169
|
+
}
|
|
170
|
+
/** The child window went away before calling finish(). */
|
|
171
|
+
| {
|
|
172
|
+
status: 'closed-early';
|
|
173
|
+
result: undefined;
|
|
174
|
+
error: WindowClosedError;
|
|
175
|
+
}
|
|
176
|
+
/** The handshake timed out, the popup was blocked, or the factory threw. */
|
|
177
|
+
| {
|
|
178
|
+
status: 'error';
|
|
179
|
+
result: undefined;
|
|
180
|
+
error: unknown;
|
|
181
|
+
};
|
|
182
|
+
interface OpenedWindowControls<Out extends MessageMap, In extends MessageMap, R> {
|
|
134
183
|
/** Call from a click handler (popup blockers require a user gesture). */
|
|
135
184
|
open: () => void;
|
|
136
|
-
status: OpenedWindowStatus;
|
|
137
|
-
/** The child's finish() value, once status is 'done'. */
|
|
138
|
-
result: R | undefined;
|
|
139
|
-
error: unknown;
|
|
140
185
|
/** Post to the child; no-op while nothing is open. */
|
|
141
186
|
post: OpenedWindow<Out, In, R>['post'];
|
|
142
187
|
close: () => void;
|
|
143
188
|
}
|
|
189
|
+
type UseOpenedWindow<Out extends MessageMap, In extends MessageMap, R> = OpenedWindowState<R> & OpenedWindowControls<Out, In, R>;
|
|
144
190
|
|
|
145
191
|
/**
|
|
146
192
|
* Drive an openWindow() flow from a component: the factory is called on
|
|
147
193
|
* open(), and the child window's lifecycle is folded into render state.
|
|
148
194
|
* Reopening replaces the previous window; a stale window's outcome is ignored.
|
|
195
|
+
*
|
|
196
|
+
* The child window deliberately outlives an unmount: closing a payment page
|
|
197
|
+
* because a route changed would lose the user's in-flight transaction. Call
|
|
198
|
+
* close() if the component owns the window's lifetime.
|
|
149
199
|
*/
|
|
150
200
|
declare function useOpenedWindow<Out extends MessageMap, In extends MessageMap, R = unknown>(factory: () => OpenedWindow<Out, In, R>): UseOpenedWindow<Out, In, R>;
|
|
151
201
|
|
|
152
|
-
export { type AnyStore, type ChannelHooks, type DefineStoreOptions, type OpenedWindowStatus, type ShareScope, type StoreHooks, type UseLeaderOptions, type UseOpenedWindow, type UseSharedStateOptions, defineChannel, defineStore, getLeader, getSharedStore, useChannel, useClientId, useIsLeader, useLeader, useLeaderEffect, useMessage, useOpenedWindow, usePeers, useSend, useSharedState };
|
|
202
|
+
export { type AnyStore, type ChannelHooks, type DefineStoreOptions, type OpenedWindowControls, type OpenedWindowState, type OpenedWindowStatus, type ShareScope, type StoreHooks, type UseLeaderOptions, type UseOpenedWindow, type UseSharedStateOptions, defineChannel, defineStore, getLeader, getSharedStore, useChannel, useClientId, useIsLeader, useLeader, useLeaderEffect, useMessage, useOpenedWindow, usePeers, useSend, useSharedState };
|
package/dist/index.js
CHANGED
|
@@ -7,14 +7,17 @@ import {
|
|
|
7
7
|
getSharedStore,
|
|
8
8
|
getStore,
|
|
9
9
|
useClientId,
|
|
10
|
-
usePeers
|
|
11
|
-
|
|
10
|
+
usePeers,
|
|
11
|
+
warnOnInitialMismatch
|
|
12
|
+
} from "./chunk-YOXUZYKA.js";
|
|
12
13
|
|
|
13
14
|
// src/use-shared-state.ts
|
|
14
15
|
import { useCallback, useSyncExternalStore } from "react";
|
|
15
16
|
function useSharedState(key, initial, options) {
|
|
16
|
-
const
|
|
17
|
+
const storeName = options?.store ?? DEFAULT_NAME;
|
|
18
|
+
const store = getStore(storeName, options?.scope ?? "everywhere");
|
|
17
19
|
store.registerKey(key, initial);
|
|
20
|
+
warnOnInitialMismatch(storeName, key, initial);
|
|
18
21
|
const value = useSyncExternalStore(
|
|
19
22
|
useCallback((onChange) => store.subscribeKey(key, onChange), [store, key]),
|
|
20
23
|
() => store.getSnapshot()[key],
|
|
@@ -107,33 +110,38 @@ function useLeaderEffect(effect, options) {
|
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
// src/use-opened-window.ts
|
|
110
|
-
import { useCallback as useCallback3, useRef as useRef3, useState } from "react";
|
|
113
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState } from "react";
|
|
111
114
|
import { WindowClosedError } from "@use-everywhere/core";
|
|
115
|
+
var IDLE = Object.freeze({
|
|
116
|
+
status: "idle",
|
|
117
|
+
result: void 0,
|
|
118
|
+
error: void 0
|
|
119
|
+
});
|
|
112
120
|
function useOpenedWindow(factory) {
|
|
113
|
-
const [
|
|
114
|
-
const [result, setResult] = useState(void 0);
|
|
115
|
-
const [error, setError] = useState(void 0);
|
|
121
|
+
const [state, setState] = useState(IDLE);
|
|
116
122
|
const current = useRef3(null);
|
|
117
123
|
const factoryRef = useRef3(factory);
|
|
118
|
-
|
|
124
|
+
useEffect3(() => {
|
|
125
|
+
factoryRef.current = factory;
|
|
126
|
+
});
|
|
119
127
|
const open = useCallback3(() => {
|
|
120
128
|
current.current?.close();
|
|
121
129
|
let opened;
|
|
122
130
|
try {
|
|
123
131
|
opened = factoryRef.current();
|
|
124
132
|
} catch (err) {
|
|
125
|
-
|
|
126
|
-
setError(err);
|
|
133
|
+
setState({ status: "error", result: void 0, error: err });
|
|
127
134
|
return;
|
|
128
135
|
}
|
|
129
136
|
current.current = opened;
|
|
130
|
-
|
|
131
|
-
setResult(void 0);
|
|
132
|
-
setError(void 0);
|
|
137
|
+
setState({ status: "opening", result: void 0, error: void 0 });
|
|
133
138
|
const fresh = () => current.current === opened;
|
|
134
139
|
opened.ready.then(
|
|
135
140
|
() => {
|
|
136
|
-
if (fresh())
|
|
141
|
+
if (!fresh()) return;
|
|
142
|
+
setState(
|
|
143
|
+
(s) => s.status === "opening" ? { status: "connected", result: void 0, error: void 0 } : s
|
|
144
|
+
);
|
|
137
145
|
},
|
|
138
146
|
() => {
|
|
139
147
|
}
|
|
@@ -141,14 +149,13 @@ function useOpenedWindow(factory) {
|
|
|
141
149
|
);
|
|
142
150
|
opened.result.then(
|
|
143
151
|
(value) => {
|
|
144
|
-
if (
|
|
145
|
-
setResult(value);
|
|
146
|
-
setStatus("done");
|
|
152
|
+
if (fresh()) setState({ status: "done", result: value, error: void 0 });
|
|
147
153
|
},
|
|
148
154
|
(err) => {
|
|
149
155
|
if (!fresh()) return;
|
|
150
|
-
|
|
151
|
-
|
|
156
|
+
setState(
|
|
157
|
+
err instanceof WindowClosedError ? { status: "closed-early", result: void 0, error: err } : { status: "error", result: void 0, error: err }
|
|
158
|
+
);
|
|
152
159
|
}
|
|
153
160
|
);
|
|
154
161
|
}, []);
|
|
@@ -156,17 +163,57 @@ function useOpenedWindow(factory) {
|
|
|
156
163
|
current.current?.post(type, payload);
|
|
157
164
|
}, []);
|
|
158
165
|
const close = useCallback3(() => current.current?.close(), []);
|
|
159
|
-
return {
|
|
166
|
+
return { ...state, open, post, close };
|
|
160
167
|
}
|
|
161
168
|
|
|
162
169
|
// src/index.ts
|
|
163
|
-
|
|
170
|
+
import {
|
|
171
|
+
createChannel,
|
|
172
|
+
createSharedStore,
|
|
173
|
+
createPresence,
|
|
174
|
+
createLeader,
|
|
175
|
+
webStorageAdapter,
|
|
176
|
+
localStorageAdapter,
|
|
177
|
+
sessionStorageAdapter,
|
|
178
|
+
openWindow,
|
|
179
|
+
connectToOpener,
|
|
180
|
+
CID_PARAM,
|
|
181
|
+
WindowClosedError as WindowClosedError2,
|
|
182
|
+
HandshakeTimeoutError,
|
|
183
|
+
newer,
|
|
184
|
+
observeBus,
|
|
185
|
+
enableDebug,
|
|
186
|
+
getBusNames,
|
|
187
|
+
BroadcastChannelTransport,
|
|
188
|
+
NoopTransport,
|
|
189
|
+
defaultTransport,
|
|
190
|
+
isBroadcastChannelAvailable
|
|
191
|
+
} from "@use-everywhere/core";
|
|
164
192
|
export {
|
|
193
|
+
BroadcastChannelTransport,
|
|
194
|
+
CID_PARAM,
|
|
165
195
|
DEFAULT_NAME,
|
|
196
|
+
HandshakeTimeoutError,
|
|
197
|
+
NoopTransport,
|
|
198
|
+
WindowClosedError2 as WindowClosedError,
|
|
199
|
+
connectToOpener,
|
|
200
|
+
createChannel,
|
|
201
|
+
createLeader,
|
|
202
|
+
createPresence,
|
|
203
|
+
createSharedStore,
|
|
204
|
+
defaultTransport,
|
|
166
205
|
defineChannel,
|
|
167
206
|
defineStore,
|
|
207
|
+
enableDebug,
|
|
208
|
+
getBusNames,
|
|
168
209
|
getLeader,
|
|
169
210
|
getSharedStore,
|
|
211
|
+
isBroadcastChannelAvailable,
|
|
212
|
+
localStorageAdapter,
|
|
213
|
+
newer,
|
|
214
|
+
observeBus,
|
|
215
|
+
openWindow,
|
|
216
|
+
sessionStorageAdapter,
|
|
170
217
|
useChannel,
|
|
171
218
|
useClientId,
|
|
172
219
|
useIsLeader,
|
|
@@ -176,5 +223,6 @@ export {
|
|
|
176
223
|
useOpenedWindow,
|
|
177
224
|
usePeers,
|
|
178
225
|
useSend,
|
|
179
|
-
useSharedState
|
|
226
|
+
useSharedState,
|
|
227
|
+
webStorageAdapter
|
|
180
228
|
};
|
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/testing.ts
|
|
22
|
+
var testing_exports = {};
|
|
23
|
+
__export(testing_exports, {
|
|
24
|
+
MemoryHub: () => import_testing.MemoryHub,
|
|
25
|
+
MemoryTransport: () => import_testing.MemoryTransport
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(testing_exports);
|
|
28
|
+
var import_testing = require("@use-everywhere/core/testing");
|
|
29
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
30
|
+
0 && (module.exports = {
|
|
31
|
+
MemoryHub,
|
|
32
|
+
MemoryTransport
|
|
33
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MemoryHub, MemoryTransport } from '@use-everywhere/core/testing';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MemoryHub, MemoryTransport } from '@use-everywhere/core/testing';
|
package/dist/testing.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "use-everywhere",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "React hooks for state and messages shared across tabs, windows, and workers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jonatan Kruszewski <jonakrusze@gmail.com>",
|
|
@@ -49,6 +49,16 @@
|
|
|
49
49
|
"types": "./dist/devtools/index.d.cts",
|
|
50
50
|
"default": "./dist/devtools/index.cjs"
|
|
51
51
|
}
|
|
52
|
+
},
|
|
53
|
+
"./testing": {
|
|
54
|
+
"import": {
|
|
55
|
+
"types": "./dist/testing.d.ts",
|
|
56
|
+
"default": "./dist/testing.js"
|
|
57
|
+
},
|
|
58
|
+
"require": {
|
|
59
|
+
"types": "./dist/testing.d.cts",
|
|
60
|
+
"default": "./dist/testing.cjs"
|
|
61
|
+
}
|
|
52
62
|
}
|
|
53
63
|
},
|
|
54
64
|
"files": [
|
|
@@ -59,53 +69,59 @@
|
|
|
59
69
|
"name": "everything (import *)",
|
|
60
70
|
"path": "dist/index.js",
|
|
61
71
|
"import": "*",
|
|
62
|
-
"limit": "
|
|
72
|
+
"limit": "6.6 kB"
|
|
63
73
|
},
|
|
64
74
|
{
|
|
65
75
|
"name": "useSharedState",
|
|
66
76
|
"path": "dist/index.js",
|
|
67
77
|
"import": "{ useSharedState }",
|
|
68
|
-
"limit": "
|
|
78
|
+
"limit": "2.85 kB"
|
|
69
79
|
},
|
|
70
80
|
{
|
|
71
81
|
"name": "useChannel + useMessage + useSend",
|
|
72
82
|
"path": "dist/index.js",
|
|
73
83
|
"import": "{ useChannel, useMessage, useSend }",
|
|
74
|
-
"limit": "1 kB"
|
|
84
|
+
"limit": "1.6 kB"
|
|
75
85
|
},
|
|
76
86
|
{
|
|
77
87
|
"name": "usePeers + useClientId",
|
|
78
88
|
"path": "dist/index.js",
|
|
79
89
|
"import": "{ usePeers, useClientId }",
|
|
80
|
-
"limit": "1.
|
|
90
|
+
"limit": "1.7 kB"
|
|
81
91
|
},
|
|
82
92
|
{
|
|
83
93
|
"name": "useOpenedWindow + openWindow",
|
|
84
94
|
"path": "dist/index.js",
|
|
85
95
|
"import": "{ useOpenedWindow, openWindow }",
|
|
86
|
-
"limit": "1.
|
|
96
|
+
"limit": "1.5 kB"
|
|
87
97
|
},
|
|
88
98
|
{
|
|
89
99
|
"name": "useLeader + useLeaderEffect",
|
|
90
100
|
"path": "dist/index.js",
|
|
91
101
|
"import": "{ useLeader, useLeaderEffect }",
|
|
92
|
-
"limit": "
|
|
102
|
+
"limit": "2.15 kB"
|
|
93
103
|
},
|
|
94
104
|
{
|
|
95
105
|
"name": "defineStore (persisted)",
|
|
96
106
|
"path": "dist/index.js",
|
|
97
107
|
"import": "{ defineStore, localStorageAdapter }",
|
|
98
|
-
"limit": "
|
|
108
|
+
"limit": "3.3 kB"
|
|
99
109
|
},
|
|
100
110
|
{
|
|
101
111
|
"name": "Inspector (devtools subpath)",
|
|
102
112
|
"path": "dist/devtools/index.js",
|
|
103
113
|
"import": "{ Inspector }",
|
|
104
114
|
"limit": "5 kB"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "MemoryHub (testing subpath)",
|
|
118
|
+
"path": "dist/testing.js",
|
|
119
|
+
"import": "{ MemoryHub }",
|
|
120
|
+
"limit": "400 B"
|
|
105
121
|
}
|
|
106
122
|
],
|
|
107
123
|
"dependencies": {
|
|
108
|
-
"@use-everywhere/core": "0.
|
|
124
|
+
"@use-everywhere/core": "0.6.0"
|
|
109
125
|
},
|
|
110
126
|
"peerDependencies": {
|
|
111
127
|
"react": ">=18"
|
|
@@ -130,6 +146,9 @@
|
|
|
130
146
|
"*": {
|
|
131
147
|
"devtools": [
|
|
132
148
|
"./dist/devtools/index.d.ts"
|
|
149
|
+
],
|
|
150
|
+
"testing": [
|
|
151
|
+
"./dist/testing.d.ts"
|
|
133
152
|
]
|
|
134
153
|
}
|
|
135
154
|
},
|
package/dist/chunk-PNFJW7QK.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
// src/registry.ts
|
|
4
|
-
import {
|
|
5
|
-
createChannel,
|
|
6
|
-
createLeader,
|
|
7
|
-
createPresence,
|
|
8
|
-
createSharedStore,
|
|
9
|
-
DEFAULT_NAME,
|
|
10
|
-
NoopTransport
|
|
11
|
-
} from "@use-everywhere/core";
|
|
12
|
-
var stores = /* @__PURE__ */ new Map();
|
|
13
|
-
var presences = /* @__PURE__ */ new Map();
|
|
14
|
-
var channels = /* @__PURE__ */ new Map();
|
|
15
|
-
var leaders = /* @__PURE__ */ new Map();
|
|
16
|
-
var storeConfig = /* @__PURE__ */ new Map();
|
|
17
|
-
var scopeOptions = {
|
|
18
|
-
everywhere: {},
|
|
19
|
-
tabs: { accept: (meta) => meta.kind !== "worker" },
|
|
20
|
-
tab: { transport: () => new NoopTransport() }
|
|
21
|
-
};
|
|
22
|
-
function getSharedStore(name = DEFAULT_NAME, scope = "everywhere") {
|
|
23
|
-
return getStore(name, scope);
|
|
24
|
-
}
|
|
25
|
-
function configureStore(name, scope, options) {
|
|
26
|
-
const key = `${scope} ${name}`;
|
|
27
|
-
if (stores.has(key)) {
|
|
28
|
-
throw new Error(
|
|
29
|
-
`defineStore('${name}') ran after that store was already created. Move it to module scope \u2014 configuring a live store would silently hand you one without persistence.`
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
storeConfig.set(key, options);
|
|
33
|
-
}
|
|
34
|
-
function getStore(name, scope = "everywhere") {
|
|
35
|
-
const key = `${scope} ${name}`;
|
|
36
|
-
let store = stores.get(key);
|
|
37
|
-
if (!store) {
|
|
38
|
-
store = createSharedStore(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
|
|
39
|
-
stores.set(key, store);
|
|
40
|
-
}
|
|
41
|
-
return store;
|
|
42
|
-
}
|
|
43
|
-
function getPresence(name) {
|
|
44
|
-
let presence = presences.get(name);
|
|
45
|
-
if (!presence) {
|
|
46
|
-
presence = createPresence(name);
|
|
47
|
-
presences.set(name, presence);
|
|
48
|
-
}
|
|
49
|
-
return presence;
|
|
50
|
-
}
|
|
51
|
-
function getLeader(name, options) {
|
|
52
|
-
let leader = leaders.get(name);
|
|
53
|
-
if (!leader) {
|
|
54
|
-
leader = createLeader(name, options);
|
|
55
|
-
leaders.set(name, leader);
|
|
56
|
-
}
|
|
57
|
-
return leader;
|
|
58
|
-
}
|
|
59
|
-
function getChannel(name) {
|
|
60
|
-
let channel = channels.get(name);
|
|
61
|
-
if (!channel) {
|
|
62
|
-
channel = createChannel(name);
|
|
63
|
-
channels.set(name, channel);
|
|
64
|
-
}
|
|
65
|
-
return channel;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// src/use-peers.ts
|
|
69
|
-
import { useCallback, useSyncExternalStore } from "react";
|
|
70
|
-
var NO_PEERS = Object.freeze([]);
|
|
71
|
-
function usePeers(options) {
|
|
72
|
-
const presence = getPresence(options?.name ?? DEFAULT_NAME);
|
|
73
|
-
return useSyncExternalStore(
|
|
74
|
-
useCallback((onChange) => presence.subscribe(onChange), [presence]),
|
|
75
|
-
() => presence.getPeers(),
|
|
76
|
-
() => NO_PEERS
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
function useClientId(options) {
|
|
80
|
-
return getPresence(options?.name ?? DEFAULT_NAME).clientId;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export {
|
|
84
|
-
DEFAULT_NAME,
|
|
85
|
-
getSharedStore,
|
|
86
|
-
configureStore,
|
|
87
|
-
getStore,
|
|
88
|
-
getLeader,
|
|
89
|
-
getChannel,
|
|
90
|
-
usePeers,
|
|
91
|
-
useClientId
|
|
92
|
-
};
|