castle-web-sdk 0.4.7 → 0.4.9
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 +56 -5
- package/dist/castle.d.ts +4 -1
- package/dist/castle.js +2 -1
- package/dist/commands.d.ts +16 -0
- package/dist/context.d.ts +4 -0
- package/dist/portal.d.ts +7 -0
- package/dist/portal.js +35 -0
- package/dist/runtime.d.ts +20 -0
- package/dist/runtime.js +109 -21
- package/dist/transport.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ import { setup, initCard, Storage, Leaderboard } from "castle-web-sdk";
|
|
|
19
19
|
- [Time](#time)
|
|
20
20
|
- [User](#user)
|
|
21
21
|
- [Pass](#pass)
|
|
22
|
+
- [Portal](#portal)
|
|
22
23
|
- [Lifecycle](#lifecycle)
|
|
23
24
|
- [Setup](#setup)
|
|
24
25
|
- [CastleError](#castleerror)
|
|
@@ -224,6 +225,50 @@ if (status === "purchased" || status === "alreadyOwned") {
|
|
|
224
225
|
}
|
|
225
226
|
```
|
|
226
227
|
|
|
228
|
+
## Portal
|
|
229
|
+
|
|
230
|
+
A portal sends the player from this deck to another Castle deck,
|
|
231
|
+
referred to by its deck id.
|
|
232
|
+
|
|
233
|
+
### `Portal.open(deckId): Promise<PortalOpenResult>`
|
|
234
|
+
|
|
235
|
+
Sends the player to `deckId`: the Castle app swipes the feed to it, the
|
|
236
|
+
website opens its page. There's nowhere to go in the editor or dev server,
|
|
237
|
+
so there it resolves `unavailable`. `PortalOpenResult` has a `status`:
|
|
238
|
+
|
|
239
|
+
- `'navigating'` — the host is moving to the target deck. Treat `open` as
|
|
240
|
+
final: this deck is torn down as it navigates away, so don't rely on code
|
|
241
|
+
running afterward.
|
|
242
|
+
- `'unavailable'` — nowhere to navigate here (the editor or dev server).
|
|
243
|
+
|
|
244
|
+
```js
|
|
245
|
+
portalButton.onclick = async () => {
|
|
246
|
+
const { status } = await Portal.open(nextDeckId);
|
|
247
|
+
if (status === "unavailable") {
|
|
248
|
+
// No feed to navigate here — fall back to your own affordance.
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### `Portal.prefetch(deckId): Promise<PortalPrefetchResult>`
|
|
254
|
+
|
|
255
|
+
A best-effort hint that the player may soon `open(deckId)`. In the Castle app
|
|
256
|
+
the host warms that deck (fetching it now so a later `open` transitions without
|
|
257
|
+
a cold load); everywhere else it's a no-op. `PortalPrefetchResult`
|
|
258
|
+
has a `status`:
|
|
259
|
+
|
|
260
|
+
- `'prefetching'` — the host accepted the hint (or the deck was already warm).
|
|
261
|
+
- `'rejected'` — the host declined, e.g. this deck has already prefetched its
|
|
262
|
+
limit of upcoming decks. Prefetch a few likely destinations, not everything.
|
|
263
|
+
- `'unavailable'` — the host doesn't prefetch here (the website, editor, or dev
|
|
264
|
+
server).
|
|
265
|
+
|
|
266
|
+
```js
|
|
267
|
+
link.addEventListener("pointerenter", () => {
|
|
268
|
+
Portal.prefetch(link.dataset.deckId);
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
227
272
|
## Lifecycle
|
|
228
273
|
|
|
229
274
|
`Lifecycle` tells the host when the deck has painted its first frame, so
|
|
@@ -256,8 +301,9 @@ Startup, editor-mode check, and a file-write call for editor UI.
|
|
|
256
301
|
|
|
257
302
|
Call this once at the start of the deck, before any other SDK call.
|
|
258
303
|
`setup()` initializes the SDK so the rest of the API is usable and
|
|
259
|
-
mounts
|
|
260
|
-
|
|
304
|
+
mounts a 5:7 Castle card around whatever the deck renders into `#root`
|
|
305
|
+
in play mode. The SDK preserves the card aspect ratio; Castle hosts own
|
|
306
|
+
the card's max size, placement, and surrounding padding.
|
|
261
307
|
While running locally with `castle-web serve`, it also forwards
|
|
262
308
|
`console` output to the CLI and reloads the page when `castle-web
|
|
263
309
|
restart` runs.
|
|
@@ -271,9 +317,10 @@ setup();
|
|
|
271
317
|
### `initCard(): HTMLDivElement`
|
|
272
318
|
|
|
273
319
|
Use this when the deck draws into a `<canvas>` (or anything else)
|
|
274
|
-
rather than into the React tree at `#root`. Returns a
|
|
275
|
-
|
|
276
|
-
|
|
320
|
+
rather than into the React tree at `#root`. Returns a `<div>` with the
|
|
321
|
+
standard Castle 5:7 card aspect ratio. The div resizes itself when the
|
|
322
|
+
window resizes. In Castle-hosted iframes/WebViews it fills the host's
|
|
323
|
+
available card frame.
|
|
277
324
|
|
|
278
325
|
```js
|
|
279
326
|
import { setup, initCard } from "castle-web-sdk";
|
|
@@ -289,6 +336,10 @@ card.appendChild(canvas);
|
|
|
289
336
|
If the deck mounts a React tree into `#root` instead, you don't need
|
|
290
337
|
`initCard()` — `setup()` already wraps `#root`'s children in a card.
|
|
291
338
|
|
|
339
|
+
Host-specific layout flags such as `CastleEmbed.feed` are deprecated.
|
|
340
|
+
New code should let Castle hosts provide the frame and let the SDK infer
|
|
341
|
+
whether it is running standalone or embedded.
|
|
342
|
+
|
|
292
343
|
### `CARD_RATIO`
|
|
293
344
|
|
|
294
345
|
The card aspect ratio (`5 / 7`). Use this if you need to size something
|
package/dist/castle.d.ts
CHANGED
|
@@ -6,7 +6,10 @@ export { Lifecycle } from "./lifecycle";
|
|
|
6
6
|
export type { CastleLifecycleApi } from "./lifecycle";
|
|
7
7
|
export { Pass } from "./passes";
|
|
8
8
|
export type { CastlePassApi, PassOfferResult, PassOfferStatus, } from "./passes";
|
|
9
|
-
export {
|
|
9
|
+
export { Portal } from "./portal";
|
|
10
|
+
export type { CastlePortalApi, PortalOpenResult, PortalOpenStatus, PortalPrefetchResult, PortalPrefetchStatus, } from "./portal";
|
|
11
|
+
export { CARD_RATIO, initCard, onBeforeRestart, onFilesChanged, onSaveReloadState, requestReload, setup, takeReloadState, writeFile, } from "./runtime";
|
|
12
|
+
export type { FileChange, FilesChangedEvent } from "./runtime";
|
|
10
13
|
export { SharedStorage, Storage } from "./storage";
|
|
11
14
|
export { Time } from "./time";
|
|
12
15
|
export type { CastleClockZone, CastleDateParts, CastleTimeApi } from "./time";
|
package/dist/castle.js
CHANGED
|
@@ -4,7 +4,8 @@ export { CastleError } from "./errors";
|
|
|
4
4
|
export { Leaderboard } from "./leaderboard";
|
|
5
5
|
export { Lifecycle } from "./lifecycle";
|
|
6
6
|
export { Pass } from "./passes";
|
|
7
|
-
export {
|
|
7
|
+
export { Portal } from "./portal";
|
|
8
|
+
export { CARD_RATIO, initCard, onBeforeRestart, onFilesChanged, onSaveReloadState, requestReload, setup, takeReloadState, writeFile, } from "./runtime";
|
|
8
9
|
export { SharedStorage, Storage } from "./storage";
|
|
9
10
|
export { Time } from "./time";
|
|
10
11
|
export { User } from "./user";
|
package/dist/commands.d.ts
CHANGED
|
@@ -24,6 +24,14 @@ export type PassOfferStatus = "purchased" | "alreadyOwned" | "cancelled" | "unav
|
|
|
24
24
|
export interface PassOfferResult {
|
|
25
25
|
status: PassOfferStatus;
|
|
26
26
|
}
|
|
27
|
+
export type PortalOpenStatus = "navigating" | "unavailable";
|
|
28
|
+
export interface PortalOpenResult {
|
|
29
|
+
status: PortalOpenStatus;
|
|
30
|
+
}
|
|
31
|
+
export type PortalPrefetchStatus = "prefetching" | "rejected" | "unavailable";
|
|
32
|
+
export interface PortalPrefetchResult {
|
|
33
|
+
status: PortalPrefetchStatus;
|
|
34
|
+
}
|
|
27
35
|
export interface CommandParams {
|
|
28
36
|
"deckStorage.load": Record<string, never>;
|
|
29
37
|
"deckStorage.update": {
|
|
@@ -57,6 +65,12 @@ export interface CommandParams {
|
|
|
57
65
|
"pass.offer": {
|
|
58
66
|
passId: string;
|
|
59
67
|
};
|
|
68
|
+
"portal.open": {
|
|
69
|
+
targetDeckId: string;
|
|
70
|
+
};
|
|
71
|
+
"portal.prefetch": {
|
|
72
|
+
targetDeckId: string;
|
|
73
|
+
};
|
|
60
74
|
}
|
|
61
75
|
export interface CommandResult {
|
|
62
76
|
"deckStorage.load": {
|
|
@@ -93,6 +107,8 @@ export interface CommandResult {
|
|
|
93
107
|
hasPass: boolean;
|
|
94
108
|
};
|
|
95
109
|
"pass.offer": PassOfferResult;
|
|
110
|
+
"portal.open": PortalOpenResult;
|
|
111
|
+
"portal.prefetch": PortalPrefetchResult;
|
|
96
112
|
}
|
|
97
113
|
export type CommandName = keyof CommandParams;
|
|
98
114
|
export interface SerializedCommandError {
|
package/dist/context.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export type CastleHost = "web" | "mobile" | "dev";
|
|
2
2
|
export interface CastleEmbed {
|
|
3
3
|
edit?: boolean;
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated Hosts should not set this for new behavior. The SDK now infers
|
|
6
|
+
* host-framed vs standalone presentation from its runtime context.
|
|
7
|
+
*/
|
|
4
8
|
feed?: boolean;
|
|
5
9
|
host?: CastleHost;
|
|
6
10
|
}
|
package/dist/portal.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PortalOpenResult, PortalPrefetchResult } from "./commands";
|
|
2
|
+
export type { PortalOpenResult, PortalOpenStatus, PortalPrefetchResult, PortalPrefetchStatus, } from "./commands";
|
|
3
|
+
export interface CastlePortalApi {
|
|
4
|
+
open(deckId: string): Promise<PortalOpenResult>;
|
|
5
|
+
prefetch(deckId: string): Promise<PortalPrefetchResult>;
|
|
6
|
+
}
|
|
7
|
+
export declare const Portal: CastlePortalApi;
|
package/dist/portal.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Portal — a deck-facing capability for sending the player to another Castle
|
|
2
|
+
// deck ("following a portal"). The host owns the transition: in the Castle feed
|
|
3
|
+
// it swipes to the target deck (which may itself be a web deck or a normal
|
|
4
|
+
// engine deck); elsewhere (the website, the dev server, the editor) there's no
|
|
5
|
+
// feed to navigate, so it resolves `unavailable` and nothing happens.
|
|
6
|
+
//
|
|
7
|
+
// Navigation is effectively fire-and-forget: once the host accepts it, the
|
|
8
|
+
// source deck is torn down as the feed moves to the target, so a deck should
|
|
9
|
+
// not rely on code running after a successful `open`.
|
|
10
|
+
import { CastleError } from "./errors";
|
|
11
|
+
import { hostRequest } from "./transport";
|
|
12
|
+
export const Portal = {
|
|
13
|
+
open,
|
|
14
|
+
prefetch,
|
|
15
|
+
};
|
|
16
|
+
async function open(deckId) {
|
|
17
|
+
if (typeof deckId !== "string" || deckId.length === 0) {
|
|
18
|
+
throw new CastleError({
|
|
19
|
+
code: "INVALID_ARGUMENT",
|
|
20
|
+
message: "Portal.open requires a deckId.",
|
|
21
|
+
operation: "Portal.open",
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return hostRequest("portal.open", { targetDeckId: deckId });
|
|
25
|
+
}
|
|
26
|
+
async function prefetch(deckId) {
|
|
27
|
+
if (typeof deckId !== "string" || deckId.length === 0) {
|
|
28
|
+
throw new CastleError({
|
|
29
|
+
code: "INVALID_ARGUMENT",
|
|
30
|
+
message: "Portal.prefetch requires a deckId.",
|
|
31
|
+
operation: "Portal.prefetch",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return hostRequest("portal.prefetch", { targetDeckId: deckId });
|
|
35
|
+
}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -7,9 +7,29 @@ interface LocalResponse {
|
|
|
7
7
|
error?: string;
|
|
8
8
|
[key: string]: unknown;
|
|
9
9
|
}
|
|
10
|
+
export interface FileChange {
|
|
11
|
+
/** Deck-relative POSIX path. */
|
|
12
|
+
path: string;
|
|
13
|
+
event: "add" | "change" | "delete";
|
|
14
|
+
/**
|
|
15
|
+
* Deck-relative paths affected via the Vite import graph: the changed file
|
|
16
|
+
* itself (when imported by anything) plus every transitive importer. Empty
|
|
17
|
+
* when the file isn't part of the module graph.
|
|
18
|
+
*/
|
|
19
|
+
affected: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface FilesChangedEvent {
|
|
22
|
+
changes: FileChange[];
|
|
23
|
+
/** Union of every change's path + affected set, deduped. */
|
|
24
|
+
affected: string[];
|
|
25
|
+
}
|
|
10
26
|
export declare function setup(): void;
|
|
11
27
|
export declare function writeFile(path: string, contents: string): Promise<LocalResponse>;
|
|
12
28
|
export declare function initCard(): HTMLDivElement;
|
|
13
29
|
export declare function sendLocalCommand<C extends CommandName>(command: C, params: CommandParams[C]): Promise<CommandResponseEnvelope>;
|
|
30
|
+
export declare function onFilesChanged(listener: (event: FilesChangedEvent) => void): () => void;
|
|
14
31
|
export declare function onBeforeRestart(hook: () => void | Promise<void>): () => void;
|
|
32
|
+
export declare function onSaveReloadState(key: string, save: () => unknown): () => void;
|
|
33
|
+
export declare function takeReloadState<T = unknown>(key: string): T | null;
|
|
34
|
+
export declare function requestReload(): void;
|
|
15
35
|
export {};
|
package/dist/runtime.js
CHANGED
|
@@ -48,11 +48,6 @@ export function initCard() {
|
|
|
48
48
|
card.id = "castle-card";
|
|
49
49
|
document.body.appendChild(card);
|
|
50
50
|
function resize() {
|
|
51
|
-
if (getCastleEmbed()?.feed === true) {
|
|
52
|
-
card.style.width = "100vw";
|
|
53
|
-
card.style.height = "100vh";
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
51
|
const { w, h } = computeCardSize();
|
|
57
52
|
card.style.width = w + "px";
|
|
58
53
|
card.style.height = h + "px";
|
|
@@ -61,14 +56,11 @@ export function initCard() {
|
|
|
61
56
|
window.addEventListener("resize", resize);
|
|
62
57
|
return card;
|
|
63
58
|
}
|
|
64
|
-
// Constrains whatever the deck renders into #root to a
|
|
65
|
-
//
|
|
66
|
-
// full viewport, so the card shell applies only to standalone play.
|
|
59
|
+
// Constrains whatever the deck renders into #root to a 5:7 card in play mode.
|
|
60
|
+
// Hosts own max size and padding; the SDK only preserves the card aspect ratio.
|
|
67
61
|
function initPlayCard() {
|
|
68
62
|
if (isEdit())
|
|
69
63
|
return;
|
|
70
|
-
if (getCastleEmbed()?.feed === true)
|
|
71
|
-
return;
|
|
72
64
|
const style = document.createElement("style");
|
|
73
65
|
style.textContent = `
|
|
74
66
|
html, body { background: #000; }
|
|
@@ -94,23 +86,27 @@ function initPlayCard() {
|
|
|
94
86
|
window.addEventListener("resize", resize);
|
|
95
87
|
}
|
|
96
88
|
function computeCardSize() {
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const aw = window.innerWidth - pad * 2;
|
|
101
|
-
const ah = window.innerHeight - pad * 2;
|
|
89
|
+
const pad = shouldUseStandaloneChrome() ? 20 : 0;
|
|
90
|
+
const aw = Math.max(1, window.innerWidth - pad * 2);
|
|
91
|
+
const ah = Math.max(1, window.innerHeight - pad * 2);
|
|
102
92
|
let w;
|
|
103
93
|
let h;
|
|
104
94
|
if (aw / ah < CARD_RATIO) {
|
|
105
|
-
w =
|
|
95
|
+
w = aw;
|
|
106
96
|
h = w / CARD_RATIO;
|
|
107
97
|
}
|
|
108
98
|
else {
|
|
109
|
-
h =
|
|
99
|
+
h = ah;
|
|
110
100
|
w = h * CARD_RATIO;
|
|
111
101
|
}
|
|
112
102
|
return { w, h };
|
|
113
103
|
}
|
|
104
|
+
function shouldUseStandaloneChrome() {
|
|
105
|
+
if (window.parent !== window)
|
|
106
|
+
return false;
|
|
107
|
+
const embed = getCastleEmbed();
|
|
108
|
+
return embed?.host === "dev";
|
|
109
|
+
}
|
|
114
110
|
function sendMsg(msg) {
|
|
115
111
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
116
112
|
ws.send(JSON.stringify(msg));
|
|
@@ -346,7 +342,10 @@ function handleLocalMessage(msg) {
|
|
|
346
342
|
});
|
|
347
343
|
}
|
|
348
344
|
else if (msg.type === "restart") {
|
|
349
|
-
|
|
345
|
+
scheduleReload(RESTART_DEBOUNCE_MS);
|
|
346
|
+
}
|
|
347
|
+
else if (msg.type === "files_changed") {
|
|
348
|
+
dispatchFilesChanged(msg);
|
|
350
349
|
}
|
|
351
350
|
else if (msg.type === "write_file_response") {
|
|
352
351
|
resolveLocalRequest(msg);
|
|
@@ -355,18 +354,106 @@ function handleLocalMessage(msg) {
|
|
|
355
354
|
resolveLocalCommand(msg);
|
|
356
355
|
}
|
|
357
356
|
}
|
|
357
|
+
// Files-changed listeners, registered by consumers (the kit engine, editors).
|
|
358
|
+
// The SDK only delivers the event; deciding what to re-read or reload is the
|
|
359
|
+
// consumer's job.
|
|
360
|
+
const filesChangedListeners = new Set();
|
|
361
|
+
export function onFilesChanged(listener) {
|
|
362
|
+
filesChangedListeners.add(listener);
|
|
363
|
+
return () => filesChangedListeners.delete(listener);
|
|
364
|
+
}
|
|
365
|
+
function dispatchFilesChanged(msg) {
|
|
366
|
+
const rawChanges = Array.isArray(msg.changes) ? msg.changes : [];
|
|
367
|
+
const changes = [];
|
|
368
|
+
for (const raw of rawChanges) {
|
|
369
|
+
if (typeof raw?.path !== "string")
|
|
370
|
+
continue;
|
|
371
|
+
const event = raw.event;
|
|
372
|
+
if (event !== "add" && event !== "change" && event !== "delete")
|
|
373
|
+
continue;
|
|
374
|
+
changes.push({
|
|
375
|
+
path: raw.path,
|
|
376
|
+
event,
|
|
377
|
+
affected: Array.isArray(raw.affected)
|
|
378
|
+
? raw.affected.filter((p) => typeof p === "string")
|
|
379
|
+
: [],
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
if (changes.length === 0)
|
|
383
|
+
return;
|
|
384
|
+
const affected = Array.isArray(msg.affected)
|
|
385
|
+
? msg.affected.filter((p) => typeof p === "string")
|
|
386
|
+
: [];
|
|
387
|
+
const event = { changes, affected };
|
|
388
|
+
for (const listener of filesChangedListeners) {
|
|
389
|
+
try {
|
|
390
|
+
listener(event);
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
// one consumer's failure shouldn't starve the others
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
358
397
|
// Restart (from `castle-web restart` / task agents) is debounced so a burst
|
|
359
398
|
// of reload requests -- several tasks finishing close together -- produces
|
|
360
399
|
// one reload. Before reloading, registered hooks run (the kit editor flushes
|
|
361
|
-
// its debounced unsaved edits there
|
|
400
|
+
// its debounced unsaved edits there, save-state hooks stash their state) so
|
|
401
|
+
// in-flight work isn't lost.
|
|
362
402
|
const RESTART_DEBOUNCE_MS = 1500;
|
|
403
|
+
// Consumer-requested reloads (a code file changed) coalesce on a short window:
|
|
404
|
+
// the serve already batched the fs events, so this only folds multiple
|
|
405
|
+
// consumers requesting at once.
|
|
406
|
+
const REQUEST_RELOAD_DEBOUNCE_MS = 250;
|
|
363
407
|
let restartTimer = null;
|
|
364
408
|
const beforeRestartHooks = new Set();
|
|
365
409
|
export function onBeforeRestart(hook) {
|
|
366
410
|
beforeRestartHooks.add(hook);
|
|
367
411
|
return () => beforeRestartHooks.delete(hook);
|
|
368
412
|
}
|
|
369
|
-
|
|
413
|
+
// Save-state hooks: consumers register a saver keyed by a stable string; right
|
|
414
|
+
// before a reload every saver runs and its value is stashed in sessionStorage.
|
|
415
|
+
// After the reload the consumer calls takeReloadState(key) to pick it back up.
|
|
416
|
+
// Simple stash — enough for editor selection / lightweight runtime state.
|
|
417
|
+
const RELOAD_STATE_PREFIX = "castle-reload-state:";
|
|
418
|
+
const reloadStateSavers = new Map();
|
|
419
|
+
export function onSaveReloadState(key, save) {
|
|
420
|
+
reloadStateSavers.set(key, save);
|
|
421
|
+
return () => {
|
|
422
|
+
if (reloadStateSavers.get(key) === save)
|
|
423
|
+
reloadStateSavers.delete(key);
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
export function takeReloadState(key) {
|
|
427
|
+
try {
|
|
428
|
+
const raw = sessionStorage.getItem(RELOAD_STATE_PREFIX + key);
|
|
429
|
+
if (raw === null)
|
|
430
|
+
return null;
|
|
431
|
+
sessionStorage.removeItem(RELOAD_STATE_PREFIX + key);
|
|
432
|
+
return JSON.parse(raw);
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function stashReloadState() {
|
|
439
|
+
for (const [key, save] of reloadStateSavers) {
|
|
440
|
+
try {
|
|
441
|
+
const value = save();
|
|
442
|
+
if (value === undefined)
|
|
443
|
+
continue;
|
|
444
|
+
sessionStorage.setItem(RELOAD_STATE_PREFIX + key, JSON.stringify(value));
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
// a failed saver shouldn't block the reload
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// Reload this page after honoring the save hooks. Consumers call this when a
|
|
452
|
+
// files-changed event tells them code they run on has changed.
|
|
453
|
+
export function requestReload() {
|
|
454
|
+
scheduleReload(REQUEST_RELOAD_DEBOUNCE_MS);
|
|
455
|
+
}
|
|
456
|
+
function scheduleReload(delayMs) {
|
|
370
457
|
if (restartTimer !== null)
|
|
371
458
|
clearTimeout(restartTimer);
|
|
372
459
|
restartTimer = setTimeout(() => {
|
|
@@ -377,9 +464,10 @@ function scheduleRestart() {
|
|
|
377
464
|
catch {
|
|
378
465
|
// a failed flush shouldn't block the reload
|
|
379
466
|
}
|
|
467
|
+
stashReloadState();
|
|
380
468
|
location.reload();
|
|
381
469
|
})();
|
|
382
|
-
},
|
|
470
|
+
}, delayMs);
|
|
383
471
|
}
|
|
384
472
|
function localWsUrl(path) {
|
|
385
473
|
const url = new URL(path, location.href);
|
package/dist/transport.js
CHANGED