castle-web-sdk 0.4.31 → 0.4.32

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 CHANGED
@@ -591,6 +591,31 @@ link.addEventListener("pointerenter", () => {
591
591
  });
592
592
  ```
593
593
 
594
+ ### `Portal.info(deckId): Promise<PortalInfoResult>`
595
+
596
+ Where a portal leads: the destination deck's title, creator and cover, for a
597
+ sign or label on the portal. A deck can't reach Castle's API or load remote
598
+ images itself, so this is how it shows another deck's details. It needs a
599
+ Castle login, which the app and a signed-in website visitor have; the cloud
600
+ editor's preview and a signed-out visitor get `unavailable`, so keep your own
601
+ label as the fallback. `PortalInfoResult` has a `status`:
602
+
603
+ - `'ok'` — also has `deckId`, `title`, `creator` (`{ userId, username }` or
604
+ `null`), and `coverUrl`, a `data:` url of the deck's cover or `null`.
605
+ - `'broken'` — the portal wouldn't work for this player: the deck doesn't
606
+ exist, has portals turned off, or isn't visible to them.
607
+ - `'unavailable'` — the host couldn't look it up.
608
+
609
+ ```js
610
+ const info = await Portal.info(doorway.deckId);
611
+ if (info.status === "ok") {
612
+ sign.textContent = `${info.title} by @${info.creator?.username ?? "?"}`;
613
+ if (info.coverUrl) sign.style.backgroundImage = `url(${info.coverUrl})`;
614
+ }
615
+ ```
616
+
617
+ Answers are kept per deck for the session, so asking again is cheap.
618
+
594
619
  ## Haptics
595
620
 
596
621
  `Haptics` plays a short device vibration ("buzz") for tactile feedback —
@@ -57,6 +57,18 @@ export interface PortalOpenResult {
57
57
  status: PortalOpenStatus;
58
58
  }
59
59
  export type PortalPrefetchStatus = "prefetching" | "rejected" | "unavailable";
60
+ export type PortalInfoResult = {
61
+ status: "ok";
62
+ deckId: string;
63
+ title: string;
64
+ creator: {
65
+ userId: string;
66
+ username: string;
67
+ } | null;
68
+ coverUrl: string | null;
69
+ } | {
70
+ status: "broken" | "unavailable";
71
+ };
60
72
  export interface PortalPrefetchResult {
61
73
  status: PortalPrefetchStatus;
62
74
  }
@@ -186,6 +198,9 @@ export interface CommandParams {
186
198
  "portal.prefetch": {
187
199
  targetDeckId: string;
188
200
  };
201
+ "portal.info": {
202
+ targetDeckId: string;
203
+ };
189
204
  "haptics.play": {
190
205
  style: HapticStyle;
191
206
  };
@@ -306,6 +321,7 @@ export interface CommandResult {
306
321
  "snapshot.offer": SnapshotOfferResult;
307
322
  "portal.open": PortalOpenResult;
308
323
  "portal.prefetch": PortalPrefetchResult;
324
+ "portal.info": PortalInfoResult;
309
325
  "haptics.play": HapticsResult;
310
326
  "multiplayer.getSession": MultiplayerGetSessionResult;
311
327
  "characters.open": CharacterAdmission;
package/dist/portal.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- import type { PortalOpenResult, PortalPrefetchResult } from "./commands";
2
- export type { PortalOpenResult, PortalOpenStatus, PortalPrefetchResult, PortalPrefetchStatus, } from "./commands";
1
+ import type { PortalInfoResult, PortalOpenResult, PortalPrefetchResult } from "./commands";
2
+ export type { PortalInfoResult, PortalOpenResult, PortalOpenStatus, PortalPrefetchResult, PortalPrefetchStatus, } from "./commands";
3
3
  export interface CastlePortalApi {
4
4
  open(deckId: string): Promise<PortalOpenResult>;
5
5
  prefetch(deckId: string): Promise<PortalPrefetchResult>;
6
+ info(deckId: string): Promise<PortalInfoResult>;
6
7
  }
7
8
  export declare const Portal: CastlePortalApi;
package/dist/portal.js CHANGED
@@ -7,11 +7,16 @@
7
7
  // Navigation is effectively fire-and-forget: once the host accepts it, the
8
8
  // source deck is torn down as the feed moves to the target, so a deck should
9
9
  // not rely on code running after a successful `open`.
10
+ //
11
+ // `info` is a plain read of where a portal leads: the target's title, creator
12
+ // and cover, for a sign on the portal. It works on every host that has a
13
+ // Castle login.
10
14
  import { CastleError } from "./errors";
11
15
  import { hostRequest } from "./transport";
12
16
  export const Portal = {
13
17
  open,
14
18
  prefetch,
19
+ info,
15
20
  };
16
21
  async function open(deckId) {
17
22
  if (typeof deckId !== "string" || deckId.length === 0) {
@@ -33,3 +38,20 @@ async function prefetch(deckId) {
33
38
  }
34
39
  return hostRequest("portal.prefetch", { targetDeckId: deckId });
35
40
  }
41
+ async function info(deckId) {
42
+ if (typeof deckId !== "string" || deckId.length === 0) {
43
+ throw new CastleError({
44
+ code: "INVALID_ARGUMENT",
45
+ message: "Portal.info requires a deckId.",
46
+ operation: "Portal.info",
47
+ });
48
+ }
49
+ try {
50
+ return await hostRequest("portal.info", { targetDeckId: deckId });
51
+ }
52
+ catch {
53
+ // No login, an old host, or a failed lookup all read the same to a deck:
54
+ // there is nothing to show, so it keeps whatever label it had.
55
+ return { status: "unavailable" };
56
+ }
57
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "castle-web-sdk",
3
- "version": "0.4.31",
3
+ "version": "0.4.32",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/castle-xyz/castle-experimental-web.git"