castle-web-sdk 0.4.32 → 0.4.33

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.
@@ -0,0 +1 @@
1
+ export declare function createDevUnavailableNotice(message: string): () => void;
@@ -0,0 +1,36 @@
1
+ export function createDevUnavailableNotice(message) {
2
+ let noticeEl = null;
3
+ let noticeTimer = null;
4
+ return () => {
5
+ if (typeof document === "undefined")
6
+ return;
7
+ if (!noticeEl) {
8
+ noticeEl = document.createElement("div");
9
+ noticeEl.textContent = message;
10
+ noticeEl.style.cssText = [
11
+ "position:fixed",
12
+ "left:50%",
13
+ "bottom:24px",
14
+ "transform:translateX(-50%)",
15
+ "max-width:80vw",
16
+ "padding:10px 16px",
17
+ "border-radius:8px",
18
+ "background:rgba(0,0,0,0.82)",
19
+ "color:#fff",
20
+ "font:500 13px/1.4 system-ui,sans-serif",
21
+ "text-align:center",
22
+ "z-index:2147483647",
23
+ "pointer-events:none",
24
+ "transition:opacity 0.3s ease",
25
+ ].join(";");
26
+ document.body.appendChild(noticeEl);
27
+ }
28
+ noticeEl.style.opacity = "1";
29
+ if (noticeTimer)
30
+ clearTimeout(noticeTimer);
31
+ noticeTimer = setTimeout(() => {
32
+ if (noticeEl)
33
+ noticeEl.style.opacity = "0";
34
+ }, 3200);
35
+ };
36
+ }
package/dist/passes.js CHANGED
@@ -9,6 +9,7 @@
9
9
  // notice itself (kept deliberately tiny), returns `unavailable`
10
10
  // There is no capability check for the deck to make — every platform returns a
11
11
  // PassOfferResult, so a single code path handles them all.
12
+ import { createDevUnavailableNotice } from "./devUnavailableNotice";
12
13
  import { CastleError } from "./errors";
13
14
  import { getCommandChannel, hostRequest } from "./transport";
14
15
  export const Pass = {
@@ -46,39 +47,6 @@ async function offer(passId) {
46
47
  }
47
48
  return result;
48
49
  }
49
- let devNoticeEl = null;
50
- let devNoticeTimer = null;
51
50
  // Minimal, dependency-free toast. Dev-only affordance — not the place for a
52
51
  // designed purchase UI.
53
- function showDevUnavailableNotice() {
54
- if (typeof document === "undefined")
55
- return;
56
- if (!devNoticeEl) {
57
- devNoticeEl = document.createElement("div");
58
- devNoticeEl.textContent = "Passes can only be purchased in the Castle app.";
59
- devNoticeEl.style.cssText = [
60
- "position:fixed",
61
- "left:50%",
62
- "bottom:24px",
63
- "transform:translateX(-50%)",
64
- "max-width:80vw",
65
- "padding:10px 16px",
66
- "border-radius:8px",
67
- "background:rgba(0,0,0,0.82)",
68
- "color:#fff",
69
- "font:500 13px/1.4 system-ui,sans-serif",
70
- "text-align:center",
71
- "z-index:2147483647",
72
- "pointer-events:none",
73
- "transition:opacity 0.3s ease",
74
- ].join(";");
75
- document.body.appendChild(devNoticeEl);
76
- }
77
- devNoticeEl.style.opacity = "1";
78
- if (devNoticeTimer)
79
- clearTimeout(devNoticeTimer);
80
- devNoticeTimer = setTimeout(() => {
81
- if (devNoticeEl)
82
- devNoticeEl.style.opacity = "0";
83
- }, 3200);
84
- }
52
+ const showDevUnavailableNotice = createDevUnavailableNotice("Passes can only be purchased in the Castle app.");
@@ -63,6 +63,15 @@ export interface SetCharacterGameStateInput {
63
63
  key: string;
64
64
  value: string | number | boolean;
65
65
  }>;
66
+ objectChanges?: CharacterObjectChange[];
67
+ }
68
+ export interface CharacterObjectChange {
69
+ objectId: string;
70
+ holderId?: string;
71
+ stateChanges?: Array<{
72
+ key: string;
73
+ value: string | number | boolean;
74
+ }>;
66
75
  }
67
76
  export interface ShareCharacterClaimInput {
68
77
  operationId: string;
@@ -94,11 +94,15 @@ function createPlaythrough(call, subject, generation) {
94
94
  });
95
95
  },
96
96
  async setGameState(input) {
97
+ const objectChanges = input?.objectChanges === undefined
98
+ ? undefined
99
+ : assertObjectChanges(input.objectChanges);
97
100
  return call("characterSetGameState", {
98
101
  subject, generation,
99
102
  operationId: assertOperationId(input?.operationId),
100
103
  expectedRevision: assertStateRevision(input?.expectedRevision),
101
- changes: assertStateChanges(input?.changes),
104
+ changes: assertStateChanges(input?.changes, objectChanges !== undefined),
105
+ ...(objectChanges === undefined ? {} : { objectChanges }),
102
106
  });
103
107
  },
104
108
  async shareClaim(input) {
@@ -398,24 +402,84 @@ function assertStateRevision(value) {
398
402
  }
399
403
  return value;
400
404
  }
401
- function assertStateChanges(value) {
402
- if (!Array.isArray(value) || value.length < 1 || value.length > 32) {
403
- throw characterArgumentError("changes must contain one to 32 fields.");
405
+ function assertStateChanges(value, hasObjectChanges = false) {
406
+ if (!Array.isArray(value) || value.length > 32 || (!hasObjectChanges && value.length < 1)) {
407
+ throw characterArgumentError(hasObjectChanges
408
+ ? "changes must contain at most 32 fields."
409
+ : "changes must contain one to 32 fields.");
410
+ }
411
+ const seen = new Set();
412
+ return value.map((change) => assertStateChange(change, seen, "state change"));
413
+ }
414
+ function assertObjectChanges(value) {
415
+ if (!Array.isArray(value) || value.length < 1 || value.length > 16) {
416
+ throw characterArgumentError("objectChanges must contain one to 16 objects.");
417
+ }
418
+ const seenObjects = new Set();
419
+ return value.map((change) => {
420
+ if (!change || typeof change !== "object" || Array.isArray(change)) {
421
+ throw characterArgumentError("object change must name an object and a holder or state change.");
422
+ }
423
+ const candidate = change;
424
+ if (Object.keys(candidate).some((key) => key !== "objectId" && key !== "holderId" && key !== "stateChanges")) {
425
+ throw characterArgumentError("object change has an unknown field.");
426
+ }
427
+ const objectId = assertMechanicsKey(candidate.objectId, "objectChanges.objectId");
428
+ if (seenObjects.has(objectId)) {
429
+ throw characterArgumentError("objectChanges contains a duplicate object.");
430
+ }
431
+ seenObjects.add(objectId);
432
+ const hasHolder = candidate.holderId !== undefined;
433
+ const holderId = hasHolder
434
+ ? assertNarrativeId(candidate.holderId, "objectChanges.holderId")
435
+ : undefined;
436
+ const stateChanges = candidate.stateChanges === undefined
437
+ ? undefined
438
+ : assertObjectStateChanges(candidate.stateChanges);
439
+ if (!hasHolder && !stateChanges?.length) {
440
+ throw characterArgumentError("object change must include holderId or stateChanges.");
441
+ }
442
+ return {
443
+ objectId,
444
+ ...(hasHolder ? { holderId } : {}),
445
+ ...(stateChanges === undefined ? {} : { stateChanges }),
446
+ };
447
+ });
448
+ }
449
+ function assertObjectStateChanges(value) {
450
+ if (!Array.isArray(value) || value.length > 8) {
451
+ throw characterArgumentError("object stateChanges must contain at most eight fields.");
404
452
  }
405
453
  const seen = new Set();
406
454
  return value.map((change) => {
407
455
  if (!change || typeof change !== "object" || Array.isArray(change)) {
408
- throw characterArgumentError("state change must contain a key and scalar value.");
456
+ throw characterArgumentError("object state change must contain a key and scalar value.");
409
457
  }
410
- const { key, value: field } = change;
411
- if (typeof key !== "string" || !/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(key) ||
412
- key === "constructor" || key === "prototype" || seen.has(key) || !assertScalar(field)) {
413
- throw characterArgumentError("state change has an invalid or duplicate field.");
458
+ if (Object.keys(change).some((key) => key !== "key" && key !== "value")) {
459
+ throw characterArgumentError("object state change has an unknown field.");
414
460
  }
415
- seen.add(key);
416
- return { key, value: field };
461
+ return assertStateChange(change, seen, "object state change");
417
462
  });
418
463
  }
464
+ function assertStateChange(change, seen, label) {
465
+ if (!change || typeof change !== "object" || Array.isArray(change)) {
466
+ throw characterArgumentError(`${label} must contain a key and scalar value.`);
467
+ }
468
+ const { key, value } = change;
469
+ if (typeof key !== "string" || !/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(key) ||
470
+ key === "constructor" || key === "prototype" || seen.has(key) || !assertScalar(value)) {
471
+ throw characterArgumentError(`${label} has an invalid or duplicate field.`);
472
+ }
473
+ seen.add(key);
474
+ return { key, value };
475
+ }
476
+ function assertMechanicsKey(value, field) {
477
+ if (typeof value !== "string" || !/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(value) ||
478
+ value === "constructor" || value === "prototype") {
479
+ throw characterArgumentError(`${field} must be a safe key of at most 64 characters.`);
480
+ }
481
+ return value;
482
+ }
419
483
  function assertHistoryLimit(value) {
420
484
  if (value === undefined || value === null)
421
485
  return null;
package/dist/snapshots.js CHANGED
@@ -9,6 +9,7 @@
9
9
  // - mobile app : the collect card over the captured frame, then the confirm sheet
10
10
  // - web player : "collect this snapshot in the app", returns `unavailable`
11
11
  // - dev CLI : a small notice, returns `unavailable`
12
+ import { createDevUnavailableNotice } from "./devUnavailableNotice";
12
13
  import { CastleError } from "./errors";
13
14
  import { getCommandChannel, hostRequest } from "./transport";
14
15
  export const Snapshot = {
@@ -89,40 +90,4 @@ function nextPaintedFrame() {
89
90
  });
90
91
  });
91
92
  }
92
- let devNoticeEl = null;
93
- let devNoticeTimer = null;
94
- function showDevUnavailableNotice() {
95
- if (typeof document === "undefined") {
96
- return;
97
- }
98
- if (!devNoticeEl) {
99
- devNoticeEl = document.createElement("div");
100
- devNoticeEl.textContent = "Snapshots can only be collected in the Castle app.";
101
- devNoticeEl.style.cssText = [
102
- "position:fixed",
103
- "left:50%",
104
- "bottom:24px",
105
- "transform:translateX(-50%)",
106
- "max-width:80vw",
107
- "padding:10px 16px",
108
- "border-radius:8px",
109
- "background:rgba(0,0,0,0.82)",
110
- "color:#fff",
111
- "font:500 13px/1.4 system-ui,sans-serif",
112
- "text-align:center",
113
- "z-index:2147483647",
114
- "pointer-events:none",
115
- "transition:opacity 0.3s ease",
116
- ].join(";");
117
- document.body.appendChild(devNoticeEl);
118
- }
119
- devNoticeEl.style.opacity = "1";
120
- if (devNoticeTimer) {
121
- clearTimeout(devNoticeTimer);
122
- }
123
- devNoticeTimer = setTimeout(() => {
124
- if (devNoticeEl) {
125
- devNoticeEl.style.opacity = "0";
126
- }
127
- }, 3200);
128
- }
93
+ const showDevUnavailableNotice = createDevUnavailableNotice("Snapshots can only be collected in the Castle app.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "castle-web-sdk",
3
- "version": "0.4.32",
3
+ "version": "0.4.33",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/castle-xyz/castle-experimental-web.git"