castle-web-sdk 0.4.7 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -5
- package/dist/castle.d.ts +2 -0
- package/dist/castle.js +1 -0
- 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.js +13 -17
- 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,6 +6,8 @@ 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 { Portal } from "./portal";
|
|
10
|
+
export type { CastlePortalApi, PortalOpenResult, PortalOpenStatus, PortalPrefetchResult, PortalPrefetchStatus, } from "./portal";
|
|
9
11
|
export { CARD_RATIO, initCard, onBeforeRestart, setup, writeFile } from "./runtime";
|
|
10
12
|
export { SharedStorage, Storage } from "./storage";
|
|
11
13
|
export { Time } from "./time";
|
package/dist/castle.js
CHANGED
|
@@ -4,6 +4,7 @@ export { CastleError } from "./errors";
|
|
|
4
4
|
export { Leaderboard } from "./leaderboard";
|
|
5
5
|
export { Lifecycle } from "./lifecycle";
|
|
6
6
|
export { Pass } from "./passes";
|
|
7
|
+
export { Portal } from "./portal";
|
|
7
8
|
export { CARD_RATIO, initCard, onBeforeRestart, setup, writeFile } from "./runtime";
|
|
8
9
|
export { SharedStorage, Storage } from "./storage";
|
|
9
10
|
export { Time } from "./time";
|
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.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));
|
package/dist/transport.js
CHANGED