use-everywhere 0.13.0 → 1.0.1
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/devtools/index.cjs +436 -436
- package/dist/devtools/index.d.cts +19 -18
- package/dist/devtools/index.d.ts +19 -18
- package/dist/devtools/index.js +434 -327
- package/dist/index.cjs +665 -672
- package/dist/index.d.cts +177 -191
- package/dist/index.d.ts +177 -191
- package/dist/index.js +449 -382
- package/dist/shared-worker.cjs +12 -30
- package/dist/shared-worker.d.cts +3 -2
- package/dist/shared-worker.d.ts +3 -2
- package/dist/shared-worker.js +2 -7
- package/dist/testing.cjs +12 -30
- package/dist/testing.d.cts +3 -1
- package/dist/testing.d.ts +3 -1
- package/dist/testing.js +1 -6
- package/dist/use-peers-DtpHGzXE.js +315 -0
- package/dist/use-peers-RhIB77TV.cjs +380 -0
- package/package.json +7 -6
- package/dist/chunk-HZ5EI66W.js +0 -266
package/dist/index.cjs
CHANGED
|
@@ -1,695 +1,688 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
"
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
-
|
|
21
|
-
// src/index.ts
|
|
22
|
-
var src_exports = {};
|
|
23
|
-
__export(src_exports, {
|
|
24
|
-
BroadcastChannelTransport: () => import_core4.BroadcastChannelTransport,
|
|
25
|
-
CID_PARAM: () => import_core4.CID_PARAM,
|
|
26
|
-
DEFAULT_NAME: () => import_core.DEFAULT_NAME,
|
|
27
|
-
HandshakeTimeoutError: () => import_core4.HandshakeTimeoutError,
|
|
28
|
-
NoopTransport: () => import_core4.NoopTransport,
|
|
29
|
-
SharedWorkerTransport: () => import_core4.SharedWorkerTransport,
|
|
30
|
-
StorageTransport: () => import_core4.StorageTransport,
|
|
31
|
-
WIRE_VERSION: () => import_core4.WIRE_VERSION,
|
|
32
|
-
WindowClosedError: () => import_core4.WindowClosedError,
|
|
33
|
-
connectToOpener: () => import_core4.connectToOpener,
|
|
34
|
-
createChannel: () => import_core4.createChannel,
|
|
35
|
-
createLeader: () => import_core4.createLeader,
|
|
36
|
-
createNamespace: () => createNamespace,
|
|
37
|
-
createPresence: () => import_core4.createPresence,
|
|
38
|
-
createSharedReducer: () => import_core4.createSharedReducer,
|
|
39
|
-
createSharedStore: () => import_core4.createSharedStore,
|
|
40
|
-
createStoreHooks: () => createStoreHooks,
|
|
41
|
-
defaultTransport: () => import_core4.defaultTransport,
|
|
42
|
-
defineChannel: () => defineChannel,
|
|
43
|
-
defineStore: () => defineStore,
|
|
44
|
-
enableDebug: () => import_core4.enableDebug,
|
|
45
|
-
getBusNames: () => import_core4.getBusNames,
|
|
46
|
-
getLeader: () => getLeader,
|
|
47
|
-
getSharedStore: () => getSharedStore,
|
|
48
|
-
getTransportKind: () => import_core4.getTransportKind,
|
|
49
|
-
getWireSkew: () => import_core4.getWireSkew,
|
|
50
|
-
indexedDbAdapter: () => import_core4.indexedDbAdapter,
|
|
51
|
-
isBroadcastChannelAvailable: () => import_core4.isBroadcastChannelAvailable,
|
|
52
|
-
isSharedWorkerAvailable: () => import_core4.isSharedWorkerAvailable,
|
|
53
|
-
isStorageEventAvailable: () => import_core4.isStorageEventAvailable,
|
|
54
|
-
jsonSerializer: () => import_core4.jsonSerializer,
|
|
55
|
-
localStorageAdapter: () => import_core4.localStorageAdapter,
|
|
56
|
-
newer: () => import_core4.newer,
|
|
57
|
-
observeBus: () => import_core4.observeBus,
|
|
58
|
-
openWindow: () => import_core4.openWindow,
|
|
59
|
-
sessionStorageAdapter: () => import_core4.sessionStorageAdapter,
|
|
60
|
-
shallowEqual: () => shallowEqual,
|
|
61
|
-
useAnswer: () => useAnswer,
|
|
62
|
-
useAsk: () => useAsk,
|
|
63
|
-
useChannel: () => useChannel,
|
|
64
|
-
useClientId: () => useClientId,
|
|
65
|
-
useHydrated: () => useHydrated,
|
|
66
|
-
useIsLeader: () => useIsLeader,
|
|
67
|
-
useLeader: () => useLeader,
|
|
68
|
-
useLeaderEffect: () => useLeaderEffect,
|
|
69
|
-
useMessage: () => useMessage,
|
|
70
|
-
useOnMessage: () => useOnMessage,
|
|
71
|
-
useOpenedWindow: () => useOpenedWindow,
|
|
72
|
-
usePeers: () => usePeers,
|
|
73
|
-
usePresenceMetadata: () => usePresenceMetadata,
|
|
74
|
-
useSend: () => useSend,
|
|
75
|
-
useSharedReducer: () => useSharedReducer,
|
|
76
|
-
useSharedSelector: () => useSharedSelector,
|
|
77
|
-
useSharedState: () => useSharedState,
|
|
78
|
-
useSharedStore: () => useSharedStore,
|
|
79
|
-
useWindowResult: () => useWindowResult,
|
|
80
|
-
webStorageAdapter: () => import_core4.webStorageAdapter
|
|
81
|
-
});
|
|
82
|
-
module.exports = __toCommonJS(src_exports);
|
|
83
|
-
|
|
84
|
-
// src/use-shared-state.ts
|
|
85
|
-
var import_react = require("react");
|
|
86
|
-
|
|
87
|
-
// src/dev.ts
|
|
88
|
-
var inDev = false;
|
|
89
|
-
try {
|
|
90
|
-
inDev = process.env.NODE_ENV !== "production";
|
|
91
|
-
} catch {
|
|
92
|
-
}
|
|
93
|
-
var warned = /* @__PURE__ */ new Set();
|
|
94
|
-
var DOCS = "https://rxova.org/packages/use-everywhere/errors";
|
|
95
|
-
function diagnostic(code, message) {
|
|
96
|
-
return `[use-everywhere] ${code}: ${message}
|
|
97
|
-
\u2192 ${DOCS}/#${code.toLowerCase()}`;
|
|
98
|
-
}
|
|
99
|
-
function devWarn(code, message) {
|
|
100
|
-
if (!inDev) return;
|
|
101
|
-
const line = diagnostic(code, message);
|
|
102
|
-
if (warned.has(line)) return;
|
|
103
|
-
warned.add(line);
|
|
104
|
-
console.warn(line);
|
|
105
|
-
}
|
|
106
|
-
function warnRenamed(from, to) {
|
|
107
|
-
devWarn(
|
|
108
|
-
"UE2005",
|
|
109
|
-
`\`${from}\` was renamed to \`${to}\` and is removed in 1.0. Run \`npx use-everywhere-codemod rename-1.0 src/\` to update, or rename it by hand \u2014 nothing else changes.`
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
var seenInitials = /* @__PURE__ */ new Map();
|
|
113
|
-
function warnOnInitialMismatch(storeName, key, initial) {
|
|
114
|
-
if (!inDev) return;
|
|
115
|
-
const id = `${storeName} ${key}`;
|
|
116
|
-
if (!seenInitials.has(id)) {
|
|
117
|
-
seenInitials.set(id, initial);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
const first = seenInitials.get(id);
|
|
121
|
-
const comparable = (v) => v === null || typeof v !== "object";
|
|
122
|
-
if (comparable(first) && comparable(initial) && !Object.is(first, initial)) {
|
|
123
|
-
devWarn(
|
|
124
|
-
"UE2001",
|
|
125
|
-
`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 createStoreHooks, or a shared constant.`
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// src/registry.ts
|
|
131
|
-
var import_core = require("@use-everywhere/core");
|
|
132
|
-
|
|
133
|
-
// src/server-stubs.ts
|
|
134
|
-
var SERVER_CLIENT_ID = "";
|
|
135
|
-
var EMPTY = Object.freeze({});
|
|
136
|
-
var NO_PEERS = Object.freeze([]);
|
|
137
|
-
var noop = () => {
|
|
138
|
-
};
|
|
139
|
-
var unsubscribe = () => noop;
|
|
140
|
-
var INERT = Object.freeze({
|
|
141
|
-
clientId: SERVER_CLIENT_ID,
|
|
142
|
-
// store
|
|
143
|
-
//
|
|
144
|
-
// Resolved, unlike the leader's `waitForLeadership` below. A server render
|
|
145
|
-
// has nothing to restore and is documented to render defaults, so `await
|
|
146
|
-
// store.hydrated` must proceed — a never-settling promise would hang the
|
|
147
|
-
// render rather than describe it honestly.
|
|
148
|
-
hydrated: Promise.resolve(),
|
|
149
|
-
state: EMPTY,
|
|
150
|
-
getVersions: () => EMPTY,
|
|
151
|
-
set: noop,
|
|
152
|
-
subscribeKey: unsubscribe,
|
|
153
|
-
registerKey: noop,
|
|
154
|
-
// store + leader share getSnapshot; presence has getPeers
|
|
155
|
-
getSnapshot: () => EMPTY,
|
|
156
|
-
getPeers: () => NO_PEERS,
|
|
157
|
-
// presence
|
|
158
|
-
setMetadata: noop,
|
|
159
|
-
// leader
|
|
160
|
-
resign: noop,
|
|
161
|
-
setEligible: noop,
|
|
162
|
-
// common
|
|
163
|
-
subscribe: unsubscribe,
|
|
164
|
-
close: noop
|
|
165
|
-
});
|
|
166
|
-
var createServerStore = () => INERT;
|
|
167
|
-
var createServerPresence = () => INERT;
|
|
168
|
-
var NO_LEADER = Object.freeze({ leaderId: null, isLeader: false });
|
|
169
|
-
var NEVER = () => new Promise(() => {
|
|
170
|
-
});
|
|
171
|
-
var createServerLeader = () => ({
|
|
172
|
-
...INERT,
|
|
173
|
-
strategy: "heartbeat",
|
|
174
|
-
getSnapshot: () => NO_LEADER,
|
|
175
|
-
waitForLeadership: NEVER
|
|
176
|
-
});
|
|
177
|
-
var createServerReducer = (initial) => ({
|
|
178
|
-
...INERT,
|
|
179
|
-
getSnapshot: () => initial,
|
|
180
|
-
dispatch: noop,
|
|
181
|
-
pendingCount: () => 0
|
|
182
|
-
});
|
|
183
|
-
var createServerChannel = (name) => ({ ...INERT, name, post: noop, on: unsubscribe });
|
|
184
|
-
|
|
185
|
-
// src/registry.ts
|
|
186
|
-
var stores = /* @__PURE__ */ new Map();
|
|
187
|
-
var presences = /* @__PURE__ */ new Map();
|
|
188
|
-
var channels = /* @__PURE__ */ new Map();
|
|
189
|
-
var leaders = /* @__PURE__ */ new Map();
|
|
190
|
-
var storeConfig = /* @__PURE__ */ new Map();
|
|
191
|
-
var isServer = () => typeof window === "undefined";
|
|
192
|
-
var scopeOptions = {
|
|
193
|
-
everywhere: {},
|
|
194
|
-
tabs: { accept: (meta) => meta.kind !== "worker" },
|
|
195
|
-
tab: { transport: () => new import_core.NoopTransport() }
|
|
196
|
-
};
|
|
197
|
-
function getSharedStore(name = import_core.DEFAULT_NAME, scope = "everywhere") {
|
|
198
|
-
return getStore(name, scope);
|
|
199
|
-
}
|
|
200
|
-
function configSignature(options) {
|
|
201
|
-
const persist = options?.persist;
|
|
202
|
-
if (!persist) return "none";
|
|
203
|
-
return `persist:${persist.keys?.join(",") ?? "*"}:${persist.debounceMs ?? "default"}:v${persist.version ?? 0}`;
|
|
204
|
-
}
|
|
205
|
-
function configureStore(name, scope, options) {
|
|
206
|
-
const key = `${scope} ${name}`;
|
|
207
|
-
if (stores.has(key)) {
|
|
208
|
-
if (configSignature(storeConfig.get(key)) === configSignature(options)) return;
|
|
209
|
-
if (process.env.NODE_ENV !== "production") {
|
|
210
|
-
devWarn(
|
|
211
|
-
"UE2002",
|
|
212
|
-
`createStoreHooks('${name}') ran after that store was already created, with different options. The live store keeps the configuration it was built with. Move createStoreHooks to module scope, before any component reads the store.`
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
storeConfig.set(key, options);
|
|
218
|
-
}
|
|
219
|
-
function getStore(name, scope = "everywhere") {
|
|
220
|
-
const key = `${scope} ${name}`;
|
|
221
|
-
let store = stores.get(key);
|
|
222
|
-
if (!store) {
|
|
223
|
-
store = isServer() ? createServerStore() : (0, import_core.createSharedStore)(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
|
|
224
|
-
stores.set(key, store);
|
|
225
|
-
}
|
|
226
|
-
return store;
|
|
227
|
-
}
|
|
228
|
-
function getPresence(name, includeSelf = false) {
|
|
229
|
-
const key = includeSelf ? `self ${name}` : name;
|
|
230
|
-
let presence = presences.get(key);
|
|
231
|
-
if (!presence) {
|
|
232
|
-
presence = isServer() ? createServerPresence() : (0, import_core.createPresence)(name, { includeSelf });
|
|
233
|
-
presences.set(key, presence);
|
|
234
|
-
}
|
|
235
|
-
return presence;
|
|
236
|
-
}
|
|
237
|
-
function getLeader(name, options) {
|
|
238
|
-
let leader = leaders.get(name);
|
|
239
|
-
if (!leader) {
|
|
240
|
-
leader = isServer() ? createServerLeader() : (0, import_core.createLeader)(name, options);
|
|
241
|
-
leaders.set(name, leader);
|
|
242
|
-
if (options) leaderOptions.set(name, options);
|
|
243
|
-
} else if (options) {
|
|
244
|
-
warnOnLeaderOptionConflict(name, options);
|
|
245
|
-
}
|
|
246
|
-
return leader;
|
|
247
|
-
}
|
|
248
|
-
var leaderOptions = /* @__PURE__ */ new Map();
|
|
249
|
-
function warnOnLeaderOptionConflict(name, options) {
|
|
250
|
-
const first = leaderOptions.get(name);
|
|
251
|
-
for (const key of ["heartbeatMs", "leaseMs"]) {
|
|
252
|
-
const requested = options[key];
|
|
253
|
-
if (requested !== void 0 && requested !== first?.[key]) {
|
|
254
|
-
if (process.env.NODE_ENV !== "production") {
|
|
255
|
-
devWarn(
|
|
256
|
-
"UE2003",
|
|
257
|
-
`leader "${name}": ${key} ignored \u2014 the first useLeader/getLeader call fixes the election timings for this tab.`
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
var channelConfig = /* @__PURE__ */ new Map();
|
|
264
|
-
function configureChannel(name, options) {
|
|
265
|
-
if (channels.has(name)) {
|
|
266
|
-
const before = Object.keys(channelConfig.get(name)?.schema ?? {}).sort();
|
|
267
|
-
const after = Object.keys(options.schema ?? {}).sort();
|
|
268
|
-
if (before.join() === after.join()) return;
|
|
269
|
-
if (process.env.NODE_ENV !== "production") {
|
|
270
|
-
devWarn(
|
|
271
|
-
"UE2004",
|
|
272
|
-
`defineChannel('${name}') ran after that channel was already created, with different options. The live channel keeps the configuration it was built with. Move defineChannel to module scope, before any component sends or receives on it.`
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
channelConfig.set(name, options);
|
|
278
|
-
}
|
|
279
|
-
var reducers = /* @__PURE__ */ new Map();
|
|
280
|
-
function getReducer(name, key, reducer, initial) {
|
|
281
|
-
const id = `${name} ${key}`;
|
|
282
|
-
let existing = reducers.get(id);
|
|
283
|
-
if (!existing) {
|
|
284
|
-
existing = isServer() ? createServerReducer(initial) : (0, import_core.createSharedReducer)(name, reducer, initial, { key, leader: getLeader(name) });
|
|
285
|
-
reducers.set(id, existing);
|
|
286
|
-
}
|
|
287
|
-
return existing;
|
|
288
|
-
}
|
|
289
|
-
function getChannel(name) {
|
|
290
|
-
let channel = channels.get(name);
|
|
291
|
-
if (!channel) {
|
|
292
|
-
channel = isServer() ? createServerChannel(name) : (0, import_core.createChannel)(name, channelConfig.get(name));
|
|
293
|
-
channels.set(name, channel);
|
|
294
|
-
}
|
|
295
|
-
return channel;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// src/use-shared-state.ts
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const require_use_peers = require("./use-peers-RhIB77TV.cjs");
|
|
4
|
+
let react = require("react");
|
|
5
|
+
let _use_everywhere_core = require("@use-everywhere/core");
|
|
6
|
+
//#region src/use-shared-state.ts
|
|
7
|
+
/**
|
|
8
|
+
* Like useState, but the value exists in every tab, window, and worker on
|
|
9
|
+
* this origin. Late-joining tabs hydrate to the current value; concurrent
|
|
10
|
+
* writes converge last-writer-wins. Pass options.scope to delimit how much
|
|
11
|
+
* is shared ('everywhere' | 'tabs' | 'tab').
|
|
12
|
+
*
|
|
13
|
+
* Note the convergence rule is last-writer-wins per key, not per operation:
|
|
14
|
+
* two tabs running `set(n => n + 1)` at the same moment agree on one value,
|
|
15
|
+
* and one of the increments is lost. For counters and other accumulating
|
|
16
|
+
* writes, drive them from a single tab (useLeaderEffect) until an op-based
|
|
17
|
+
* primitive lands.
|
|
18
|
+
*/
|
|
299
19
|
function useSharedState(key, initial, options) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
20
|
+
const storeName = options?.store ?? _use_everywhere_core.DEFAULT_NAME;
|
|
21
|
+
const store = require_use_peers.getStore(storeName, options?.scope ?? "everywhere");
|
|
22
|
+
store.registerKey(key, initial);
|
|
23
|
+
if (process.env.NODE_ENV !== "production") require_use_peers.warnOnInitialMismatch(storeName, key, initial);
|
|
24
|
+
return [(0, react.useSyncExternalStore)((0, react.useCallback)((onChange) => store.subscribeKey(key, onChange), [store, key]), () => store.getSnapshot()[key], () => initial), (0, react.useCallback)((next) => store.set(key, next), [store, key])];
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/use-shared-reducer.ts
|
|
28
|
+
/**
|
|
29
|
+
* Like `useReducer`, but every tab, window, and worker on this origin applies
|
|
30
|
+
* the same actions in the same order.
|
|
31
|
+
*
|
|
32
|
+
* ```tsx
|
|
33
|
+
* const [count, dispatch] = useSharedReducer((n, action) => n + action.by, 0);
|
|
34
|
+
* <button onClick={() => dispatch({ by: 1 })}>{count}</button>;
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* Reach for this instead of `useSharedState` whenever a write is *relative to
|
|
38
|
+
* what is already there* — a counter, a total, a list you append to. Shared
|
|
39
|
+
* state converges last-writer-wins on the value, so two tabs incrementing at
|
|
40
|
+
* once both write the same result and one increment vanishes. A reducer sends
|
|
41
|
+
* the action rather than the result, and two increments are two actions.
|
|
42
|
+
*
|
|
43
|
+
* For a plain register — a theme, a selection, a draft — `useSharedState` is
|
|
44
|
+
* still the right tool and the cheaper one.
|
|
45
|
+
*
|
|
46
|
+
* ## The rules
|
|
47
|
+
*
|
|
48
|
+
* **Actions must survive the wire.** They are structured-cloned to every peer,
|
|
49
|
+
* so no functions, no class instances, no DOM nodes.
|
|
50
|
+
*
|
|
51
|
+
* **The reducer must be pure**, and it must be the *same* reducer everywhere.
|
|
52
|
+
* Every client folds the same actions itself; a client whose fold differs gets
|
|
53
|
+
* a different answer, and nothing can detect that. The first caller's function
|
|
54
|
+
* is the one used for the life of the page — a re-render passes a new identity
|
|
55
|
+
* every time, and swapping the fold under a history already applied is exactly
|
|
56
|
+
* the divergence this exists to prevent.
|
|
57
|
+
*
|
|
58
|
+
* **Ordering needs a leader**, which this shares with `useLeader` on the same
|
|
59
|
+
* bus rather than electing a second one. Before a seat is filled — the first
|
|
60
|
+
* moments of the first tab — dispatches are held locally and ordered once it
|
|
61
|
+
* is. They are never lost, only pending.
|
|
62
|
+
*/
|
|
320
63
|
function useSharedReducer(reducer, initial, options) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
64
|
+
const engine = require_use_peers.getReducer(options?.name ?? _use_everywhere_core.DEFAULT_NAME, options?.key ?? "default", reducer, initial);
|
|
65
|
+
return [(0, react.useSyncExternalStore)((0, react.useCallback)((onChange) => engine.subscribe(onChange), [engine]), () => engine.getSnapshot(), () => initial), (0, react.useCallback)((action) => engine.dispatch(action), [engine])];
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/use-shared-selector.ts
|
|
69
|
+
/**
|
|
70
|
+
* Read a derived value from a shared store, re-rendering only when it changes.
|
|
71
|
+
*
|
|
72
|
+
* `useSharedState` subscribes to one key, which is the right shape for reading
|
|
73
|
+
* one thing. Reading *across* keys meant either one hook per key or a
|
|
74
|
+
* subscription to the whole store, and the latter re-renders on every write to
|
|
75
|
+
* anything in it.
|
|
76
|
+
*
|
|
77
|
+
* ```tsx
|
|
78
|
+
* const total = useSharedSelector<Cart, number>(
|
|
79
|
+
* (cart) => cart.items.length + cart.saved.length,
|
|
80
|
+
* );
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* The selector runs on every store change; the component only re-renders when
|
|
84
|
+
* its result changes by `equal`.
|
|
85
|
+
*
|
|
86
|
+
* ## It reads, it does not declare
|
|
87
|
+
*
|
|
88
|
+
* `useSharedState(key, initial)` registers `key` with a default. A selector
|
|
89
|
+
* does not — it sees whatever is in the store, so a key nothing has registered
|
|
90
|
+
* or written yet is `undefined`. Write selectors that tolerate that, or declare
|
|
91
|
+
* the defaults with `useSharedState` (or a store initial) somewhere that
|
|
92
|
+
* mounts first.
|
|
93
|
+
*
|
|
94
|
+
* The same applies on a server, where the store is inert and empty: the
|
|
95
|
+
* selector runs against `{}`, and its result is what the server-rendered markup
|
|
96
|
+
* shows.
|
|
97
|
+
*/
|
|
338
98
|
function useSharedSelector(selector, options) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
99
|
+
const store = require_use_peers.getStore(options?.store ?? _use_everywhere_core.DEFAULT_NAME, options?.scope ?? "everywhere");
|
|
100
|
+
const equal = options?.equal ?? Object.is;
|
|
101
|
+
const selectorRef = (0, react.useRef)(selector);
|
|
102
|
+
selectorRef.current = selector;
|
|
103
|
+
const equalRef = (0, react.useRef)(equal);
|
|
104
|
+
equalRef.current = equal;
|
|
105
|
+
/**
|
|
106
|
+
* The last selection, and what produced it.
|
|
107
|
+
*
|
|
108
|
+
* `useSyncExternalStore` re-renders whenever the snapshot getter returns a
|
|
109
|
+
* value it has not seen, so a selector building a fresh object each call
|
|
110
|
+
* would loop forever. Caching against the *store* snapshot answers the common
|
|
111
|
+
* case in one identity check; caching against the selector as well is what
|
|
112
|
+
* keeps a changed selector from returning last render's answer.
|
|
113
|
+
*/
|
|
114
|
+
const cache = (0, react.useRef)(null);
|
|
115
|
+
const getSelection = (0, react.useCallback)(() => {
|
|
116
|
+
const source = store.getSnapshot();
|
|
117
|
+
const select = selectorRef.current;
|
|
118
|
+
const previous = cache.current;
|
|
119
|
+
if (previous && previous.source === source && previous.select === select) return previous.value;
|
|
120
|
+
const next = select(source);
|
|
121
|
+
const value = previous && equalRef.current(previous.value, next) ? previous.value : next;
|
|
122
|
+
cache.current = {
|
|
123
|
+
source,
|
|
124
|
+
select,
|
|
125
|
+
value
|
|
126
|
+
};
|
|
127
|
+
return value;
|
|
128
|
+
}, [store]);
|
|
129
|
+
return (0, react.useSyncExternalStore)((0, react.useCallback)((onChange) => store.subscribe(onChange), [store]), getSelection, getSelection);
|
|
130
|
+
}
|
|
131
|
+
/** Shallow equality over an object or array, for selectors that build one. */
|
|
362
132
|
function shallowEqual(a, b) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
133
|
+
if (Object.is(a, b)) return true;
|
|
134
|
+
if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
|
|
135
|
+
const left = a;
|
|
136
|
+
const right = b;
|
|
137
|
+
const keys = Object.keys(left);
|
|
138
|
+
if (keys.length !== Object.keys(right).length) return false;
|
|
139
|
+
return keys.every((key) => Object.is(left[key], right[key]));
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/use-on-message.ts
|
|
143
|
+
/** Get the page-wide typed channel for `name` (one instance per name). */
|
|
374
144
|
function useChannel(name) {
|
|
375
|
-
|
|
145
|
+
return require_use_peers.getChannel(name);
|
|
376
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Subscribe to one message type. The handler is kept fresh without
|
|
149
|
+
* resubscribing, so it may close over render state.
|
|
150
|
+
*/
|
|
377
151
|
function useOnMessage(channel, type, handler, options) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
152
|
+
const handlerRef = (0, react.useRef)(handler);
|
|
153
|
+
(0, react.useEffect)(() => {
|
|
154
|
+
handlerRef.current = handler;
|
|
155
|
+
});
|
|
156
|
+
const enabled = options?.enabled ?? true;
|
|
157
|
+
const once = options?.once ?? false;
|
|
158
|
+
(0, react.useEffect)(() => {
|
|
159
|
+
if (!enabled) return;
|
|
160
|
+
return channel.on(type, (payload, meta) => handlerRef.current(payload, meta), { once });
|
|
161
|
+
}, [
|
|
162
|
+
channel,
|
|
163
|
+
type,
|
|
164
|
+
enabled,
|
|
165
|
+
once
|
|
166
|
+
]);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Answer `ask`s of one type for as long as this component is mounted.
|
|
170
|
+
*
|
|
171
|
+
* A hook rather than a bare `channel.answer` call because a responder is a
|
|
172
|
+
* subscription: registering one in a render would leave the last unmounted
|
|
173
|
+
* component answering for the page.
|
|
174
|
+
*
|
|
175
|
+
* One responder per type per channel — registering a second replaces the first,
|
|
176
|
+
* which is the same first-wins-then-replace rule `answer` has in core.
|
|
177
|
+
*/
|
|
389
178
|
function useAnswer(channel, type, responder, options) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
179
|
+
const responderRef = (0, react.useRef)(responder);
|
|
180
|
+
(0, react.useEffect)(() => {
|
|
181
|
+
responderRef.current = responder;
|
|
182
|
+
});
|
|
183
|
+
const enabled = options?.enabled ?? true;
|
|
184
|
+
(0, react.useEffect)(() => {
|
|
185
|
+
if (!enabled) return;
|
|
186
|
+
return channel.answer(type, (payload, meta) => responderRef.current(payload, meta));
|
|
187
|
+
}, [
|
|
188
|
+
channel,
|
|
189
|
+
type,
|
|
190
|
+
enabled
|
|
191
|
+
]);
|
|
192
|
+
}
|
|
193
|
+
/** The channel's post function (stable identity per channel). */
|
|
400
194
|
function useSend(channel) {
|
|
401
|
-
|
|
195
|
+
return channel.post;
|
|
402
196
|
}
|
|
197
|
+
/** The channel's ask function (stable identity per channel). */
|
|
403
198
|
function useAsk(channel) {
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
|
|
199
|
+
return channel.ask;
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/define-channel.ts
|
|
203
|
+
/**
|
|
204
|
+
* Bind a channel name and message map once, at module level, and get fully
|
|
205
|
+
* typed hooks back. Sugar over useChannel/useOnMessage/useSend — the same
|
|
206
|
+
* page-wide channel singleton is shared, so mixing bound and standalone
|
|
207
|
+
* hooks for one name is safe.
|
|
208
|
+
*
|
|
209
|
+
* Options are registered here and applied when the channel is first needed, so
|
|
210
|
+
* declaring a `schema` at module scope still constructs nothing on import.
|
|
211
|
+
*/
|
|
408
212
|
function defineChannel(name, options) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
213
|
+
if (options) require_use_peers.configureChannel(name, options);
|
|
214
|
+
const useBoundSend = () => useSend(useChannel(name));
|
|
215
|
+
const useBoundMessage = (type, handler) => useOnMessage(useChannel(name), type, handler);
|
|
216
|
+
return {
|
|
217
|
+
get: () => require_use_peers.getChannel(name),
|
|
218
|
+
useSend: useBoundSend,
|
|
219
|
+
useOnMessage: useBoundMessage
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/create-store-hooks.ts
|
|
224
|
+
/**
|
|
225
|
+
* Bind a store name — and optionally persistence — once, at module level.
|
|
226
|
+
*
|
|
227
|
+
* Like defineChannel, this does not construct anything: it registers the
|
|
228
|
+
* options the registry will use when the store is first needed, so importing
|
|
229
|
+
* the module has no side effect. The store stays a singleton per name, so
|
|
230
|
+
* `createStoreHooks('settings', { persist })` and a bare
|
|
231
|
+
* `useSharedState('theme', 'dark', { store: 'settings' })` elsewhere resolve to
|
|
232
|
+
* the same store, and both get persistence.
|
|
233
|
+
*
|
|
234
|
+
* Must run before that store exists. Module evaluation always precedes render,
|
|
235
|
+
* so intended usage is automatic; if it does run late it throws rather than
|
|
236
|
+
* quietly handing back a store with no persistence.
|
|
237
|
+
*/
|
|
425
238
|
function createStoreHooks(name, options = {}) {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
}
|
|
473
|
-
function usePresenceMetadata(metadata, options) {
|
|
474
|
-
const presence = getPresence(options?.name ?? import_core.DEFAULT_NAME, options?.includeSelf ?? false);
|
|
475
|
-
(0, import_react5.useEffect)(() => {
|
|
476
|
-
presence.setMetadata(metadata);
|
|
477
|
-
}, [presence, metadata]);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// src/use-hydrated.ts
|
|
481
|
-
var import_react6 = require("react");
|
|
239
|
+
const scope = options.scope ?? "everywhere";
|
|
240
|
+
if (options.persist) require_use_peers.configureStore(name, scope, { persist: {
|
|
241
|
+
adapter: options.persist,
|
|
242
|
+
...options.persistKeys ? { keys: options.persistKeys } : {},
|
|
243
|
+
...options.persistDebounceMs === void 0 ? {} : { debounceMs: options.persistDebounceMs },
|
|
244
|
+
...options.persistVersion === void 0 ? {} : { version: options.persistVersion },
|
|
245
|
+
...options.migrate ? { migrate: options.migrate } : {},
|
|
246
|
+
...options.onRestoreError ? { onRestoreError: options.onRestoreError } : {}
|
|
247
|
+
} });
|
|
248
|
+
return {
|
|
249
|
+
store: () => require_use_peers.getStore(name, scope),
|
|
250
|
+
useSharedState: (key, initial) => useSharedState(key, initial, {
|
|
251
|
+
store: name,
|
|
252
|
+
scope
|
|
253
|
+
})
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/use-hydrated.ts
|
|
258
|
+
/**
|
|
259
|
+
* Whether a persisted store has finished restoring.
|
|
260
|
+
*
|
|
261
|
+
* `false` on the first render, then `true` once the restore lands — including
|
|
262
|
+
* the cases where there is nothing to restore, which settle immediately.
|
|
263
|
+
*
|
|
264
|
+
* Deliberately `false` first even for a synchronous adapter that has already
|
|
265
|
+
* finished. The alternative is a value that differs between the server render
|
|
266
|
+
* and the browser's hydrating render, which is a hydration mismatch on every
|
|
267
|
+
* app that uses it — the same reason `useClientId` reports `''` until the
|
|
268
|
+
* commit after hydration.
|
|
269
|
+
*
|
|
270
|
+
* The gap this closes only exists for **async** adapters, and it is one
|
|
271
|
+
* last-writer-wins makes invisible: a keystroke landing before the restore
|
|
272
|
+
* writes at counter 1, the restore arrives holding counter 5, and the newer
|
|
273
|
+
* keystroke is correctly discarded. The behaviour is right; the surprise is
|
|
274
|
+
* total. Gate the input and there is no gap:
|
|
275
|
+
*
|
|
276
|
+
* ```tsx
|
|
277
|
+
* const ready = useHydrated({ store: 'settings' });
|
|
278
|
+
* return <input disabled={!ready} value={draft} onChange={…} />;
|
|
279
|
+
* ```
|
|
280
|
+
*
|
|
281
|
+
* A synchronous adapter — `localStorageAdapter` and friends — has already
|
|
282
|
+
* restored by the time the store is handed back, so there is no gap to guard
|
|
283
|
+
* and this simply flips to `true` in the commit after mount.
|
|
284
|
+
*/
|
|
482
285
|
function useHydrated(options) {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
286
|
+
const store = require_use_peers.getStore(options?.store ?? _use_everywhere_core.DEFAULT_NAME, options?.scope ?? "everywhere");
|
|
287
|
+
const [ready, setReady] = (0, react.useState)(false);
|
|
288
|
+
(0, react.useEffect)(() => {
|
|
289
|
+
let cancelled = false;
|
|
290
|
+
store.hydrated.then(() => {
|
|
291
|
+
if (!cancelled) setReady(true);
|
|
292
|
+
});
|
|
293
|
+
return () => {
|
|
294
|
+
cancelled = true;
|
|
295
|
+
};
|
|
296
|
+
}, [store]);
|
|
297
|
+
return ready;
|
|
298
|
+
}
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/use-leader.ts
|
|
301
|
+
const NO_LEADER = Object.freeze({
|
|
302
|
+
leaderId: null,
|
|
303
|
+
isLeader: false
|
|
304
|
+
});
|
|
305
|
+
/**
|
|
306
|
+
* Who currently holds the seat on this bus, and whether it is us. Exactly one
|
|
307
|
+
* tab leads; opening another does not steal it, and closing the leader hands it
|
|
308
|
+
* over at once.
|
|
309
|
+
*/
|
|
500
310
|
function useLeader(options) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
() => NO_LEADER2
|
|
511
|
-
);
|
|
512
|
-
}
|
|
311
|
+
const leader = require_use_peers.getLeader(options?.name ?? _use_everywhere_core.DEFAULT_NAME, options);
|
|
312
|
+
const eligible = options?.eligible;
|
|
313
|
+
(0, react.useEffect)(() => {
|
|
314
|
+
if (eligible === void 0) return;
|
|
315
|
+
leader.setEligible(eligible);
|
|
316
|
+
}, [leader, eligible]);
|
|
317
|
+
return (0, react.useSyncExternalStore)((0, react.useCallback)((onChange) => leader.subscribe(onChange), [leader]), () => leader.getSnapshot(), () => NO_LEADER);
|
|
318
|
+
}
|
|
319
|
+
/** Is this tab the leader? */
|
|
513
320
|
function useIsLeader(options) {
|
|
514
|
-
|
|
321
|
+
return useLeader(options).isLeader;
|
|
515
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* Run an effect only in the tab that holds the seat, and tear it down when the
|
|
325
|
+
* seat moves. The one place to put "exactly one tab owns the socket".
|
|
326
|
+
*/
|
|
516
327
|
function useLeaderEffect(effect, options) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
328
|
+
const { isLeader } = useLeader(options);
|
|
329
|
+
const effectRef = (0, react.useRef)(effect);
|
|
330
|
+
(0, react.useEffect)(() => {
|
|
331
|
+
effectRef.current = effect;
|
|
332
|
+
});
|
|
333
|
+
(0, react.useEffect)(() => {
|
|
334
|
+
if (!isLeader) return;
|
|
335
|
+
return effectRef.current();
|
|
336
|
+
}, [isLeader]);
|
|
337
|
+
}
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region src/namespace.ts
|
|
340
|
+
/**
|
|
341
|
+
* Namespaced hooks and factories, so two independently deployed apps on one
|
|
342
|
+
* origin cannot collide by both taking the defaults.
|
|
343
|
+
*
|
|
344
|
+
* ```ts
|
|
345
|
+
* // checkout/bus.ts
|
|
346
|
+
* export const checkout = createNamespace('checkout');
|
|
347
|
+
*
|
|
348
|
+
* // anywhere in the checkout app
|
|
349
|
+
* const [items, setItems] = checkout.useSharedState('items', []);
|
|
350
|
+
* ```
|
|
351
|
+
*
|
|
352
|
+
* Call it **at module scope**, like `createStoreHooks` and `defineChannel`. It
|
|
353
|
+
* builds a small object of bound functions, and rebuilding that on every render
|
|
354
|
+
* would hand React a new `useSharedState` identity each time — harmless for the
|
|
355
|
+
* hooks themselves, which key off the bus name, but pointless work and a
|
|
356
|
+
* confusing thing to see in a profile.
|
|
357
|
+
*
|
|
358
|
+
* See the core `createNamespace` for what a namespace does and does not
|
|
359
|
+
* guarantee. In short: it prevents collision, not access.
|
|
360
|
+
*/
|
|
530
361
|
function createNamespace(namespace) {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
362
|
+
const core = (0, _use_everywhere_core.createNamespace)(namespace);
|
|
363
|
+
const { busName } = core;
|
|
364
|
+
return {
|
|
365
|
+
...core,
|
|
366
|
+
createStoreHooks: (name, options) => createStoreHooks(busName(name), options),
|
|
367
|
+
defineChannel: (name, options) => defineChannel(busName(name), options),
|
|
368
|
+
getSharedStore: (name, scope) => require_use_peers.getSharedStore(busName(name), scope),
|
|
369
|
+
useSharedState: (key, initial, options) => useSharedState(key, initial, {
|
|
370
|
+
...options,
|
|
371
|
+
store: busName(options?.store)
|
|
372
|
+
}),
|
|
373
|
+
useSharedSelector: (selector, options) => useSharedSelector(selector, {
|
|
374
|
+
...options,
|
|
375
|
+
store: busName(options?.store)
|
|
376
|
+
}),
|
|
377
|
+
usePeers: (options) => require_use_peers.usePeers({
|
|
378
|
+
...options,
|
|
379
|
+
name: busName(options?.name)
|
|
380
|
+
}),
|
|
381
|
+
usePresenceMetadata: (metadata, options) => require_use_peers.usePresenceMetadata(metadata, {
|
|
382
|
+
...options,
|
|
383
|
+
name: busName(options?.name)
|
|
384
|
+
}),
|
|
385
|
+
useHydrated: (options) => useHydrated({
|
|
386
|
+
...options,
|
|
387
|
+
store: busName(options?.store)
|
|
388
|
+
}),
|
|
389
|
+
useClientId: (options) => require_use_peers.useClientId({ name: busName(options?.name) }),
|
|
390
|
+
useLeader: (options) => useLeader({
|
|
391
|
+
...options,
|
|
392
|
+
name: busName(options?.name)
|
|
393
|
+
}),
|
|
394
|
+
useIsLeader: (options) => useIsLeader({
|
|
395
|
+
...options,
|
|
396
|
+
name: busName(options?.name)
|
|
397
|
+
}),
|
|
398
|
+
useLeaderEffect: (effect, options) => useLeaderEffect(effect, {
|
|
399
|
+
...options,
|
|
400
|
+
name: busName(options?.name)
|
|
401
|
+
})
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region src/use-window-result.ts
|
|
406
|
+
const IDLE = Object.freeze({
|
|
407
|
+
status: "idle",
|
|
408
|
+
result: void 0,
|
|
409
|
+
error: void 0
|
|
566
410
|
});
|
|
411
|
+
/**
|
|
412
|
+
* Drive an openWindow() flow from a component: the factory is called on
|
|
413
|
+
* open(), and the child window's lifecycle is folded into render state.
|
|
414
|
+
* Reopening replaces the previous window; a stale window's outcome is ignored.
|
|
415
|
+
*
|
|
416
|
+
* The child window deliberately outlives an unmount: closing a payment page
|
|
417
|
+
* because a route changed would lose the user's in-flight transaction. Call
|
|
418
|
+
* close() if the component owns the window's lifetime.
|
|
419
|
+
*/
|
|
567
420
|
function useWindowResult(factory) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
421
|
+
const [state, setState] = (0, react.useState)(IDLE);
|
|
422
|
+
const current = (0, react.useRef)(null);
|
|
423
|
+
const factoryRef = (0, react.useRef)(factory);
|
|
424
|
+
(0, react.useEffect)(() => {
|
|
425
|
+
factoryRef.current = factory;
|
|
426
|
+
});
|
|
427
|
+
const open = (0, react.useCallback)(() => {
|
|
428
|
+
current.current?.close();
|
|
429
|
+
let opened;
|
|
430
|
+
try {
|
|
431
|
+
opened = factoryRef.current();
|
|
432
|
+
} catch (err) {
|
|
433
|
+
setState({
|
|
434
|
+
status: "error",
|
|
435
|
+
result: void 0,
|
|
436
|
+
error: err
|
|
437
|
+
});
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
current.current = opened;
|
|
441
|
+
setState({
|
|
442
|
+
status: "opening",
|
|
443
|
+
result: void 0,
|
|
444
|
+
error: void 0
|
|
445
|
+
});
|
|
446
|
+
const fresh = () => current.current === opened;
|
|
447
|
+
opened.ready.then(() => {
|
|
448
|
+
if (!fresh()) return;
|
|
449
|
+
setState((s) => s.status === "opening" ? {
|
|
450
|
+
status: "connected",
|
|
451
|
+
result: void 0,
|
|
452
|
+
error: void 0
|
|
453
|
+
} : s);
|
|
454
|
+
}, () => {});
|
|
455
|
+
opened.result.then((value) => {
|
|
456
|
+
if (fresh()) setState({
|
|
457
|
+
status: "done",
|
|
458
|
+
result: value,
|
|
459
|
+
error: void 0
|
|
460
|
+
});
|
|
461
|
+
}, (err) => {
|
|
462
|
+
if (!fresh()) return;
|
|
463
|
+
setState(err instanceof _use_everywhere_core.WindowClosedError ? {
|
|
464
|
+
status: "closed-early",
|
|
465
|
+
result: void 0,
|
|
466
|
+
error: err
|
|
467
|
+
} : {
|
|
468
|
+
status: "error",
|
|
469
|
+
result: void 0,
|
|
470
|
+
error: err
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
}, []);
|
|
474
|
+
const post = (0, react.useCallback)((type, payload) => {
|
|
475
|
+
current.current?.post(type, payload);
|
|
476
|
+
}, []);
|
|
477
|
+
const close = (0, react.useCallback)(() => current.current?.close(), []);
|
|
478
|
+
return {
|
|
479
|
+
...state,
|
|
480
|
+
open,
|
|
481
|
+
post,
|
|
482
|
+
close
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
//#endregion
|
|
486
|
+
Object.defineProperty(exports, "BroadcastChannelTransport", {
|
|
487
|
+
enumerable: true,
|
|
488
|
+
get: function() {
|
|
489
|
+
return _use_everywhere_core.BroadcastChannelTransport;
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
Object.defineProperty(exports, "CID_PARAM", {
|
|
493
|
+
enumerable: true,
|
|
494
|
+
get: function() {
|
|
495
|
+
return _use_everywhere_core.CID_PARAM;
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
exports.DEFAULT_NAME = _use_everywhere_core.DEFAULT_NAME;
|
|
499
|
+
Object.defineProperty(exports, "HandshakeTimeoutError", {
|
|
500
|
+
enumerable: true,
|
|
501
|
+
get: function() {
|
|
502
|
+
return _use_everywhere_core.HandshakeTimeoutError;
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
Object.defineProperty(exports, "NoopTransport", {
|
|
506
|
+
enumerable: true,
|
|
507
|
+
get: function() {
|
|
508
|
+
return _use_everywhere_core.NoopTransport;
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
Object.defineProperty(exports, "SharedWorkerTransport", {
|
|
512
|
+
enumerable: true,
|
|
513
|
+
get: function() {
|
|
514
|
+
return _use_everywhere_core.SharedWorkerTransport;
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
Object.defineProperty(exports, "StorageTransport", {
|
|
518
|
+
enumerable: true,
|
|
519
|
+
get: function() {
|
|
520
|
+
return _use_everywhere_core.StorageTransport;
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
Object.defineProperty(exports, "WIRE_VERSION", {
|
|
524
|
+
enumerable: true,
|
|
525
|
+
get: function() {
|
|
526
|
+
return _use_everywhere_core.WIRE_VERSION;
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
Object.defineProperty(exports, "WindowClosedError", {
|
|
530
|
+
enumerable: true,
|
|
531
|
+
get: function() {
|
|
532
|
+
return _use_everywhere_core.WindowClosedError;
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
Object.defineProperty(exports, "connectToOpener", {
|
|
536
|
+
enumerable: true,
|
|
537
|
+
get: function() {
|
|
538
|
+
return _use_everywhere_core.connectToOpener;
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
Object.defineProperty(exports, "createChannel", {
|
|
542
|
+
enumerable: true,
|
|
543
|
+
get: function() {
|
|
544
|
+
return _use_everywhere_core.createChannel;
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
Object.defineProperty(exports, "createLeader", {
|
|
548
|
+
enumerable: true,
|
|
549
|
+
get: function() {
|
|
550
|
+
return _use_everywhere_core.createLeader;
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
exports.createNamespace = createNamespace;
|
|
554
|
+
Object.defineProperty(exports, "createPresence", {
|
|
555
|
+
enumerable: true,
|
|
556
|
+
get: function() {
|
|
557
|
+
return _use_everywhere_core.createPresence;
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
Object.defineProperty(exports, "createSharedReducer", {
|
|
561
|
+
enumerable: true,
|
|
562
|
+
get: function() {
|
|
563
|
+
return _use_everywhere_core.createSharedReducer;
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
Object.defineProperty(exports, "createSharedStore", {
|
|
567
|
+
enumerable: true,
|
|
568
|
+
get: function() {
|
|
569
|
+
return _use_everywhere_core.createSharedStore;
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
exports.createStoreHooks = createStoreHooks;
|
|
573
|
+
Object.defineProperty(exports, "defaultTransport", {
|
|
574
|
+
enumerable: true,
|
|
575
|
+
get: function() {
|
|
576
|
+
return _use_everywhere_core.defaultTransport;
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
exports.defineChannel = defineChannel;
|
|
580
|
+
Object.defineProperty(exports, "enableDebug", {
|
|
581
|
+
enumerable: true,
|
|
582
|
+
get: function() {
|
|
583
|
+
return _use_everywhere_core.enableDebug;
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
Object.defineProperty(exports, "getBusNames", {
|
|
587
|
+
enumerable: true,
|
|
588
|
+
get: function() {
|
|
589
|
+
return _use_everywhere_core.getBusNames;
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
exports.getLeader = require_use_peers.getLeader;
|
|
593
|
+
exports.getSharedStore = require_use_peers.getSharedStore;
|
|
594
|
+
Object.defineProperty(exports, "getTransportKind", {
|
|
595
|
+
enumerable: true,
|
|
596
|
+
get: function() {
|
|
597
|
+
return _use_everywhere_core.getTransportKind;
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
Object.defineProperty(exports, "getWireSkew", {
|
|
601
|
+
enumerable: true,
|
|
602
|
+
get: function() {
|
|
603
|
+
return _use_everywhere_core.getWireSkew;
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
Object.defineProperty(exports, "indexedDbAdapter", {
|
|
607
|
+
enumerable: true,
|
|
608
|
+
get: function() {
|
|
609
|
+
return _use_everywhere_core.indexedDbAdapter;
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
Object.defineProperty(exports, "isBroadcastChannelAvailable", {
|
|
613
|
+
enumerable: true,
|
|
614
|
+
get: function() {
|
|
615
|
+
return _use_everywhere_core.isBroadcastChannelAvailable;
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
Object.defineProperty(exports, "isSharedWorkerAvailable", {
|
|
619
|
+
enumerable: true,
|
|
620
|
+
get: function() {
|
|
621
|
+
return _use_everywhere_core.isSharedWorkerAvailable;
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
Object.defineProperty(exports, "isStorageEventAvailable", {
|
|
625
|
+
enumerable: true,
|
|
626
|
+
get: function() {
|
|
627
|
+
return _use_everywhere_core.isStorageEventAvailable;
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
Object.defineProperty(exports, "jsonSerializer", {
|
|
631
|
+
enumerable: true,
|
|
632
|
+
get: function() {
|
|
633
|
+
return _use_everywhere_core.jsonSerializer;
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
Object.defineProperty(exports, "localStorageAdapter", {
|
|
637
|
+
enumerable: true,
|
|
638
|
+
get: function() {
|
|
639
|
+
return _use_everywhere_core.localStorageAdapter;
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
Object.defineProperty(exports, "newer", {
|
|
643
|
+
enumerable: true,
|
|
644
|
+
get: function() {
|
|
645
|
+
return _use_everywhere_core.newer;
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
Object.defineProperty(exports, "observeBus", {
|
|
649
|
+
enumerable: true,
|
|
650
|
+
get: function() {
|
|
651
|
+
return _use_everywhere_core.observeBus;
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
Object.defineProperty(exports, "openWindow", {
|
|
655
|
+
enumerable: true,
|
|
656
|
+
get: function() {
|
|
657
|
+
return _use_everywhere_core.openWindow;
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
Object.defineProperty(exports, "sessionStorageAdapter", {
|
|
661
|
+
enumerable: true,
|
|
662
|
+
get: function() {
|
|
663
|
+
return _use_everywhere_core.sessionStorageAdapter;
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
exports.shallowEqual = shallowEqual;
|
|
667
|
+
exports.useAnswer = useAnswer;
|
|
668
|
+
exports.useAsk = useAsk;
|
|
669
|
+
exports.useChannel = useChannel;
|
|
670
|
+
exports.useClientId = require_use_peers.useClientId;
|
|
671
|
+
exports.useHydrated = useHydrated;
|
|
672
|
+
exports.useIsLeader = useIsLeader;
|
|
673
|
+
exports.useLeader = useLeader;
|
|
674
|
+
exports.useLeaderEffect = useLeaderEffect;
|
|
675
|
+
exports.useOnMessage = useOnMessage;
|
|
676
|
+
exports.usePeers = require_use_peers.usePeers;
|
|
677
|
+
exports.usePresenceMetadata = require_use_peers.usePresenceMetadata;
|
|
678
|
+
exports.useSend = useSend;
|
|
679
|
+
exports.useSharedReducer = useSharedReducer;
|
|
680
|
+
exports.useSharedSelector = useSharedSelector;
|
|
681
|
+
exports.useSharedState = useSharedState;
|
|
682
|
+
exports.useWindowResult = useWindowResult;
|
|
683
|
+
Object.defineProperty(exports, "webStorageAdapter", {
|
|
684
|
+
enumerable: true,
|
|
685
|
+
get: function() {
|
|
686
|
+
return _use_everywhere_core.webStorageAdapter;
|
|
687
|
+
}
|
|
695
688
|
});
|