castle-web-sdk 0.4.45 → 0.4.47
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 +526 -53
- package/dist/agent.js +4 -4
- package/dist/avatar.d.ts +16 -0
- package/dist/avatar.js +31 -0
- package/dist/castle.d.ts +10 -1
- package/dist/castle.js +5 -0
- package/dist/commands.d.ts +237 -0
- package/dist/commands.js +16 -0
- package/dist/context.d.ts +6 -0
- package/dist/experiments.d.ts +15 -0
- package/dist/experiments.js +83 -0
- package/dist/hub.d.ts +89 -0
- package/dist/hub.js +120 -0
- package/dist/multiplayer.d.ts +86 -6
- package/dist/multiplayer.js +226 -9
- package/dist/save.d.ts +197 -0
- package/dist/save.js +526 -0
- package/dist/saveCoordinator.d.ts +39 -0
- package/dist/saveCoordinator.js +252 -0
- package/dist/server/hub.d.ts +256 -0
- package/dist/server/hub.js +195 -0
- package/dist/server/platformHandle.d.ts +80 -3
- package/dist/server/storage.d.ts +71 -4
- package/dist/server/storage.js +71 -5
- package/dist/server/wrapper.d.ts +123 -9
- package/dist/server/wrapper.js +478 -38
- package/dist/store.d.ts +3 -2
- package/dist/store.js +5 -0
- package/dist/transport.d.ts +2 -1
- package/dist/transport.js +13 -1
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
// promise resolves with the whole message. A deck runs any tool calls itself
|
|
7
7
|
// and calls Agent.chat again with the results.
|
|
8
8
|
//
|
|
9
|
-
// Agent.chat
|
|
10
|
-
// `refused`. A player whose allowance for the day is
|
|
11
|
-
// `allowance_exhausted`. A host with no route to the service
|
|
12
|
-
// `unavailable`.
|
|
9
|
+
// Agent.chat answers Castle admins and the players Castle has given access;
|
|
10
|
+
// every other player gets `refused`. A player whose allowance for the day is
|
|
11
|
+
// spent gets `allowance_exhausted`. A host with no route to the service
|
|
12
|
+
// answers `unavailable`.
|
|
13
13
|
import { CastleError } from "./errors";
|
|
14
14
|
import { hostRequest } from "./transport";
|
|
15
15
|
async function chat(params) {
|
package/dist/avatar.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AvatarResult } from "./commands";
|
|
2
|
+
export type { AvatarResult } from "./commands";
|
|
3
|
+
export interface CastleAvatarApi {
|
|
4
|
+
/** Present only in an SDK whose Avatar.set carries { color }; the hub-game kit offers colours only when it is here. */
|
|
5
|
+
readonly colors?: true;
|
|
6
|
+
/**
|
|
7
|
+
* The player's avatar: their choice, or the default every account has.
|
|
8
|
+
* `unavailable` for a signed-out visitor, or on a host without avatars.
|
|
9
|
+
*/
|
|
10
|
+
get(): Promise<AvatarResult>;
|
|
11
|
+
/** Choose the avatar, and optionally its colour, worn everywhere. Requires a host with colours for options.color. */
|
|
12
|
+
set(avatar: number, options?: {
|
|
13
|
+
color?: number;
|
|
14
|
+
}): Promise<AvatarResult>;
|
|
15
|
+
}
|
|
16
|
+
export declare const Avatar: CastleAvatarApi;
|
package/dist/avatar.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Avatar — the player's platform avatar (cauldron-game-server docs/HUB.md,
|
|
2
|
+
// decision 4): one per account, worn in every hub place. v1 is an index into the
|
|
3
|
+
// hub-game kit's character templates, with optional colour choices.
|
|
4
|
+
//
|
|
5
|
+
// The hub-game kit is the one caller: it reads the avatar to dress the player
|
|
6
|
+
// and offers the choice in its picker. Other players' avatars are not read
|
|
7
|
+
// here; a place's server learns them from ghost.
|
|
8
|
+
import { CastleError } from "./errors";
|
|
9
|
+
import { hostRequest } from "./transport";
|
|
10
|
+
export const Avatar = { get, set, colors: true };
|
|
11
|
+
async function get() {
|
|
12
|
+
return hostRequest("avatar.get", {});
|
|
13
|
+
}
|
|
14
|
+
async function set(avatar, options) {
|
|
15
|
+
if (typeof avatar !== "number" || !Number.isInteger(avatar) || avatar < 0) {
|
|
16
|
+
throw new CastleError({
|
|
17
|
+
code: "INVALID_ARGUMENT",
|
|
18
|
+
message: "Avatar.set requires a template index.",
|
|
19
|
+
operation: "Avatar.set",
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const color = options?.color;
|
|
23
|
+
if (color !== undefined && (!Number.isInteger(color) || color < 0)) {
|
|
24
|
+
throw new CastleError({
|
|
25
|
+
code: "INVALID_ARGUMENT",
|
|
26
|
+
message: "Avatar.set requires a non-negative integer colour.",
|
|
27
|
+
operation: "Avatar.set",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return hostRequest("avatar.set", color === undefined ? { avatar } : { avatar, color });
|
|
31
|
+
}
|
package/dist/castle.d.ts
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
export { isEdit } from "./context";
|
|
2
2
|
export { Agent } from "./agent";
|
|
3
3
|
export type { AgentChatDelta, AgentChatParams, AgentChatResult, AgentMessage, AgentReasoning, AgentTool, AgentToolCall, AgentToolChoice, AgentUsage, CastleAgentApi, } from "./agent";
|
|
4
|
+
export { Avatar } from "./avatar";
|
|
5
|
+
export type { AvatarResult, CastleAvatarApi } from "./avatar";
|
|
4
6
|
export { Characters } from "./characters";
|
|
5
7
|
export type { CastleCharactersApi, CharacterHandle, CharacterHistoryOptions, CharacterSendInput, } from "./characters";
|
|
6
8
|
export { Decks } from "./decks";
|
|
7
9
|
export type { CastleDecksApi, DeckSummary, DecksListParams, DecksListResult } from "./decks";
|
|
8
10
|
export { CastleError } from "./errors";
|
|
11
|
+
export { experiment, experiments, experimentsReady } from "./experiments";
|
|
9
12
|
export { deckPrefixOf, IMPORTS_DIR, IMPORTS_PREFIX, isImportedFile, resolveDeckFile, } from "./files";
|
|
10
13
|
export { Haptics } from "./haptics";
|
|
11
14
|
export type { CastleHapticsApi, HapticsResult, HapticsStatus, HapticStyle } from "./haptics";
|
|
15
|
+
export { Hub } from "./hub";
|
|
16
|
+
export type { CastleHubApi, HubBackResult, HubCancelResult, HubCancelStatus, HubChatMessage, HubChatResult, HubDoor, HubDoorsResult, HubFriend, HubFriendsResult, HubHeartsResult, HubGoParams, HubGoResult, HubHandoffResult, HubHandoffStatus, HubJumpResult, HubPlacementKind, HubReportResult, HubSayResult, } from "./hub";
|
|
12
17
|
export { Leaderboard } from "./leaderboard";
|
|
13
18
|
export type { LeaderboardData, LeaderboardEntry, LeaderboardOptions, LeaderboardScope, LeaderboardShowOptions, LeaderboardShowResult, LeaderboardShowStatus, LeaderboardSort, } from "./leaderboard";
|
|
14
19
|
export { Lifecycle } from "./lifecycle";
|
|
15
20
|
export type { CastleLifecycleApi } from "./lifecycle";
|
|
16
21
|
export { Multiplayer } from "./multiplayer";
|
|
17
|
-
export type { CastleMultiplayerApi, MultiplayerConnection, MultiplayerConnectionState, MultiplayerDestinationJoinResult, MultiplayerDestinationOptions, MultiplayerJoinHint, MultiplayerJoinOptions, MultiplayerRoster, MultiplayerStateMetadata, } from "./multiplayer";
|
|
22
|
+
export type { CastleMultiplayerApi, MultiplayerConnection, MultiplayerConnectionState, MultiplayerCompleteScheduledBeatCommand, MultiplayerCompleteScheduledBeatResult, MultiplayerDestinationJoinResult, MultiplayerDestinationOptions, MultiplayerEvaluateBeatsCommand, MultiplayerEvaluateBeatsResult, MultiplayerJoinHint, MultiplayerJoinOptions, MultiplayerRoster, MultiplayerScheduledBeatReason, MultiplayerScheduledBeatSelection, MultiplayerStateMetadata, MultiplayerStoryOpportunity, MultiplayerWorldCharacterCommand, MultiplayerWorldCharacterCommandResult, MultiplayerWorldCharacterCommandResultValue, } from "./multiplayer";
|
|
18
23
|
export type { MultiplayerVoice, VoiceJoinStatus, VoiceListener, VoiceMixEntry, VoiceSpatialOptions, VoiceState, VoiceVector, } from "./multiplayerVoice";
|
|
19
24
|
export type { MultiplayerDestinationAudience, MultiplayerSoloReason } from "./commands";
|
|
20
25
|
export { MAX_CLIENT_FRAME_BYTES, MAX_PLAYERS_CAP } from "./multiplayerProtocol";
|
|
@@ -41,3 +46,7 @@ export type { CastleClockZone, CastleDateParts, CastleTimeApi } from "./time";
|
|
|
41
46
|
export type { Json } from "./types";
|
|
42
47
|
export { User } from "./user";
|
|
43
48
|
export type { CastleUser, CastleUserApi } from "./user";
|
|
49
|
+
export { createSaveCoordinator } from "./saveCoordinator";
|
|
50
|
+
export type { CoordinatedChange, CoordinatedSave, SaveCoordinator, SaveStatus, } from "./saveCoordinator";
|
|
51
|
+
export { SaveError } from "./save";
|
|
52
|
+
export type { SaveApi, SaveBucketApi, SaveBucketRead, SaveChange, SaveCheck, SaveClaimed, SaveClaimInput, SaveErrorEnvelope, SaveInitial, SaveInitializeInput, SaveOpened, SaveOperation, SaveReadTarget, SaveReceipt, SaveRevision, SaveRows, SaveScope, SaveSelection, SaveSnapshot, SaveTarget, SaveTransactionInput, SaveWriter, } from "./save";
|
package/dist/castle.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
// Castle Web SDK
|
|
2
2
|
export { isEdit } from "./context";
|
|
3
3
|
export { Agent } from "./agent";
|
|
4
|
+
export { Avatar } from "./avatar";
|
|
4
5
|
export { Characters } from "./characters";
|
|
5
6
|
export { Decks } from "./decks";
|
|
6
7
|
export { CastleError } from "./errors";
|
|
8
|
+
export { experiment, experiments, experimentsReady } from "./experiments";
|
|
7
9
|
export { deckPrefixOf, IMPORTS_DIR, IMPORTS_PREFIX, isImportedFile, resolveDeckFile, } from "./files";
|
|
8
10
|
export { Haptics } from "./haptics";
|
|
11
|
+
export { Hub } from "./hub";
|
|
9
12
|
export { Leaderboard } from "./leaderboard";
|
|
10
13
|
export { Lifecycle } from "./lifecycle";
|
|
11
14
|
export { Multiplayer } from "./multiplayer";
|
|
@@ -21,3 +24,5 @@ export { SharedStorage, Storage } from "./storage";
|
|
|
21
24
|
export { Store } from "./store";
|
|
22
25
|
export { Time } from "./time";
|
|
23
26
|
export { User } from "./user";
|
|
27
|
+
export { createSaveCoordinator } from "./saveCoordinator";
|
|
28
|
+
export { SaveError } from "./save";
|
package/dist/commands.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SaveClaimed, SaveClaimInput, SaveInitializeInput, SaveOpened, SaveOperation, SaveReadTarget, SaveReceipt, SaveSnapshot, SaveTransactionInput } from "./save";
|
|
1
2
|
import type { Json } from "./types";
|
|
2
3
|
export declare const CASTLE_SDK_PROTOCOL = 1;
|
|
3
4
|
export type StorageBlob = Record<string, string>;
|
|
@@ -83,6 +84,179 @@ export type PortalInfoResult = {
|
|
|
83
84
|
export interface PortalPrefetchResult {
|
|
84
85
|
status: PortalPrefetchStatus;
|
|
85
86
|
}
|
|
87
|
+
export interface HubGoParams {
|
|
88
|
+
doorId: string;
|
|
89
|
+
}
|
|
90
|
+
export type HubPlacementKind = "layer" | "pad" | "exact";
|
|
91
|
+
export type HubHandoffStatus = "ok" | "cancelled" | "full" | "unavailable" | "not_found" | "forbidden" | "party_required";
|
|
92
|
+
/** Session acquisition, not a completed scene swap or socket join. */
|
|
93
|
+
export type HubHandoffResult = {
|
|
94
|
+
status: "ok";
|
|
95
|
+
/** Destination session WebSocket URL, as in multiplayer.getSession. */
|
|
96
|
+
url: string;
|
|
97
|
+
/** Single-use join nonce, valid for 60 s. */
|
|
98
|
+
nonce: string;
|
|
99
|
+
sessionId: string;
|
|
100
|
+
/** The destination session's pinned server bundle (sha256). */
|
|
101
|
+
bundleHash: string;
|
|
102
|
+
/** Immutable URL of the content pack `bundleHash` names; null when the
|
|
103
|
+
* destination has none. */
|
|
104
|
+
contentUrl: string | null;
|
|
105
|
+
kind?: undefined;
|
|
106
|
+
} | {
|
|
107
|
+
/**
|
|
108
|
+
* A cabinet: the door leads to a classic Castle deck, which the host plays
|
|
109
|
+
* itself. There is no session to join; the trip is already committed, and
|
|
110
|
+
* `Hub.back()` is the way out.
|
|
111
|
+
*/
|
|
112
|
+
status: "ok";
|
|
113
|
+
kind: "classic";
|
|
114
|
+
url: null;
|
|
115
|
+
nonce: null;
|
|
116
|
+
sessionId: null;
|
|
117
|
+
bundleHash: string;
|
|
118
|
+
contentUrl: null;
|
|
119
|
+
} | {
|
|
120
|
+
status: Exclude<HubHandoffStatus, "ok">;
|
|
121
|
+
url: null;
|
|
122
|
+
nonce: null;
|
|
123
|
+
sessionId: null;
|
|
124
|
+
bundleHash: null;
|
|
125
|
+
contentUrl: null;
|
|
126
|
+
};
|
|
127
|
+
export type HubGoResult = HubHandoffResult;
|
|
128
|
+
export type HubBackResult = HubHandoffResult;
|
|
129
|
+
export type HubJumpResult = HubHandoffResult;
|
|
130
|
+
export interface HubJumpParams {
|
|
131
|
+
userId: string;
|
|
132
|
+
}
|
|
133
|
+
/** A mutual friend ghost shows in a layer of the current place right now. */
|
|
134
|
+
export interface HubFriend {
|
|
135
|
+
userId: string;
|
|
136
|
+
username: string | null;
|
|
137
|
+
/** In the layer the player is in already: nothing to jump to. */
|
|
138
|
+
here: boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Set when the friend is not in this place but inside the place behind one
|
|
141
|
+
* of its doors: that door's id. Null for a friend in a layer of this place.
|
|
142
|
+
*/
|
|
143
|
+
door: string | null;
|
|
144
|
+
}
|
|
145
|
+
/** Hub.report: another player, by user id, and an optional short reason. */
|
|
146
|
+
export interface HubReportParams {
|
|
147
|
+
userId: string;
|
|
148
|
+
reason?: string;
|
|
149
|
+
}
|
|
150
|
+
/** `unavailable` on hosts without hub support or outside a hub session. */
|
|
151
|
+
export interface HubReportResult {
|
|
152
|
+
status: "ok" | "unavailable";
|
|
153
|
+
}
|
|
154
|
+
export type HubFriendsResult = {
|
|
155
|
+
status: "ok";
|
|
156
|
+
friends: HubFriend[];
|
|
157
|
+
} | {
|
|
158
|
+
status: "unavailable";
|
|
159
|
+
friends: null;
|
|
160
|
+
};
|
|
161
|
+
/** Account-wide hub door hearts, in oldest-first order (at most eight). */
|
|
162
|
+
export interface HubHeartsResult {
|
|
163
|
+
status: "ok" | "unavailable";
|
|
164
|
+
hearts: string[];
|
|
165
|
+
}
|
|
166
|
+
/** Hub.chat: only the messages after a cursor an earlier answer returned. */
|
|
167
|
+
export interface HubChatParams {
|
|
168
|
+
after?: string;
|
|
169
|
+
}
|
|
170
|
+
/** One chat message ghost lets this player read. */
|
|
171
|
+
export interface HubChatMessage {
|
|
172
|
+
id: string;
|
|
173
|
+
fromUserId: string;
|
|
174
|
+
fromUsername: string | null;
|
|
175
|
+
/** At most 200 characters. */
|
|
176
|
+
text: string;
|
|
177
|
+
/** When ghost took it, as an ISO 8601 time. */
|
|
178
|
+
at: string;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* `canChat` is whether ghost lets this player chat at all; `peers` are the
|
|
182
|
+
* user ids in this hub session who would be sent what they say. `unavailable`
|
|
183
|
+
* on hosts without hub support or outside a hub session.
|
|
184
|
+
*/
|
|
185
|
+
export type HubChatResult = {
|
|
186
|
+
status: "ok";
|
|
187
|
+
canChat: boolean;
|
|
188
|
+
peers: string[];
|
|
189
|
+
messages: HubChatMessage[];
|
|
190
|
+
/** Pass to the next `Hub.chat` to read only what is newer. */
|
|
191
|
+
cursor: string | null;
|
|
192
|
+
} | {
|
|
193
|
+
status: "unavailable";
|
|
194
|
+
canChat: false;
|
|
195
|
+
peers: null;
|
|
196
|
+
messages: null;
|
|
197
|
+
cursor: null;
|
|
198
|
+
};
|
|
199
|
+
/** Hub.say: free text, at most 200 characters once trimmed. */
|
|
200
|
+
export interface HubSayParams {
|
|
201
|
+
text: string;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* `sent` carries the message's id; `none` when nobody in the session may read
|
|
205
|
+
* it, `refused` when ghost will not take it, `limited` when the player is
|
|
206
|
+
* saying too much too fast. `unavailable` on hosts without hub support or
|
|
207
|
+
* outside a hub session.
|
|
208
|
+
*/
|
|
209
|
+
export interface HubSayResult {
|
|
210
|
+
status: "sent" | "none" | "refused" | "limited" | "unavailable";
|
|
211
|
+
id: string | null;
|
|
212
|
+
}
|
|
213
|
+
/** Hub.reportChat: a message this player was returned, by its id. */
|
|
214
|
+
export interface HubReportChatParams {
|
|
215
|
+
messageId: string;
|
|
216
|
+
}
|
|
217
|
+
/** Public door row; destination identity and return tickets stay host-owned. */
|
|
218
|
+
export interface HubDoor {
|
|
219
|
+
doorId: string;
|
|
220
|
+
launcherKind: string;
|
|
221
|
+
placementKind: HubPlacementKind;
|
|
222
|
+
/** The row's placement data: `{}`, or `{ key }` for an exact door. */
|
|
223
|
+
placement: Json;
|
|
224
|
+
padSize: number | null;
|
|
225
|
+
enabled: boolean;
|
|
226
|
+
featured: boolean;
|
|
227
|
+
/** Ghost-owned presence, not a count maintained by the place's engine. */
|
|
228
|
+
status: {
|
|
229
|
+
playersInside: number;
|
|
230
|
+
friendsInside: number;
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
export type HubDoorsResult = {
|
|
234
|
+
status: "ok";
|
|
235
|
+
doors: HubDoor[];
|
|
236
|
+
} | {
|
|
237
|
+
status: "unavailable";
|
|
238
|
+
doors: null;
|
|
239
|
+
};
|
|
240
|
+
export interface HubCommitParams {
|
|
241
|
+
/** The acquisition's nonce: unique to it, unlike its session. */
|
|
242
|
+
nonce: string;
|
|
243
|
+
}
|
|
244
|
+
export type HubCommitStatus = "committed" | "expired" | "superseded" | "cancelled" | "none" | "unavailable";
|
|
245
|
+
export interface HubCommitResult {
|
|
246
|
+
status: HubCommitStatus;
|
|
247
|
+
}
|
|
248
|
+
export type HubCancelStatus = "cancelled" | "expired" | "none" | "unavailable";
|
|
249
|
+
export interface HubCancelResult {
|
|
250
|
+
status: HubCancelStatus;
|
|
251
|
+
}
|
|
252
|
+
export type HubEvent = {
|
|
253
|
+
type: "back";
|
|
254
|
+
};
|
|
255
|
+
export interface HubEventEnvelope {
|
|
256
|
+
castleSdk: typeof CASTLE_SDK_PROTOCOL;
|
|
257
|
+
hub: HubEvent;
|
|
258
|
+
}
|
|
259
|
+
export declare function hubEventFrom(value: unknown): HubEvent | null;
|
|
86
260
|
export type HapticStyle = "light" | "medium" | "heavy" | "selection" | "success" | "warning" | "error";
|
|
87
261
|
export type HapticsStatus = "triggered" | "unavailable";
|
|
88
262
|
export interface HapticsResult {
|
|
@@ -331,10 +505,16 @@ export interface CommandParams {
|
|
|
331
505
|
"user.get": {
|
|
332
506
|
userId: string;
|
|
333
507
|
};
|
|
508
|
+
"experiments.get": Record<string, never>;
|
|
334
509
|
"time.getServerTime": Record<string, never>;
|
|
335
510
|
"pass.has": {
|
|
336
511
|
passId: string;
|
|
337
512
|
};
|
|
513
|
+
"avatar.get": Record<string, never>;
|
|
514
|
+
"avatar.set": {
|
|
515
|
+
avatar: number;
|
|
516
|
+
color?: number;
|
|
517
|
+
};
|
|
338
518
|
"pass.offer": {
|
|
339
519
|
passId: string;
|
|
340
520
|
};
|
|
@@ -352,6 +532,23 @@ export interface CommandParams {
|
|
|
352
532
|
"portal.info": {
|
|
353
533
|
targetDeckId: string;
|
|
354
534
|
};
|
|
535
|
+
"hub.go": HubGoParams;
|
|
536
|
+
"hub.back": Record<string, never>;
|
|
537
|
+
"hub.doors": Record<string, never>;
|
|
538
|
+
"hub.friends": Record<string, never>;
|
|
539
|
+
"hub.hearts": Record<string, never>;
|
|
540
|
+
"hub.setHearts": {
|
|
541
|
+
doorIds: string[];
|
|
542
|
+
};
|
|
543
|
+
"hub.jump": HubJumpParams;
|
|
544
|
+
"hub.report": HubReportParams;
|
|
545
|
+
"hub.chat": HubChatParams;
|
|
546
|
+
"hub.say": HubSayParams;
|
|
547
|
+
"hub.reportChat": HubReportChatParams;
|
|
548
|
+
"hub.commit": HubCommitParams;
|
|
549
|
+
"hub.cancel": {
|
|
550
|
+
nonce?: string;
|
|
551
|
+
};
|
|
355
552
|
"haptics.play": {
|
|
356
553
|
style: HapticStyle;
|
|
357
554
|
};
|
|
@@ -398,6 +595,15 @@ export interface CommandParams {
|
|
|
398
595
|
characterId: string;
|
|
399
596
|
operationId: string;
|
|
400
597
|
};
|
|
598
|
+
"cauldronStorage.snapshot": {
|
|
599
|
+
targets: SaveReadTarget[];
|
|
600
|
+
};
|
|
601
|
+
"cauldronStorage.transaction": SaveTransactionInput;
|
|
602
|
+
"cauldronStorage.claim": SaveClaimInput;
|
|
603
|
+
"cauldronStorage.initialize": SaveInitializeInput;
|
|
604
|
+
"cauldronStorage.operation": {
|
|
605
|
+
operationId: string;
|
|
606
|
+
};
|
|
401
607
|
"cauldronStorage.get": {
|
|
402
608
|
scope: StoreScope;
|
|
403
609
|
subject?: string | null;
|
|
@@ -482,6 +688,9 @@ export interface CommandResult {
|
|
|
482
688
|
"user.get": {
|
|
483
689
|
user: UserPayload | null;
|
|
484
690
|
};
|
|
691
|
+
"experiments.get": {
|
|
692
|
+
variants: Record<string, string>;
|
|
693
|
+
};
|
|
485
694
|
"time.getServerTime": {
|
|
486
695
|
timestamp: number;
|
|
487
696
|
timezoneOffset: number;
|
|
@@ -490,11 +699,26 @@ export interface CommandResult {
|
|
|
490
699
|
"pass.has": {
|
|
491
700
|
hasPass: boolean;
|
|
492
701
|
};
|
|
702
|
+
"avatar.get": AvatarResult;
|
|
703
|
+
"avatar.set": AvatarResult;
|
|
493
704
|
"pass.offer": PassOfferResult;
|
|
494
705
|
"snapshot.offer": SnapshotOfferResult;
|
|
495
706
|
"portal.open": PortalOpenResult;
|
|
496
707
|
"portal.prefetch": PortalPrefetchResult;
|
|
497
708
|
"portal.info": PortalInfoResult;
|
|
709
|
+
"hub.go": HubGoResult;
|
|
710
|
+
"hub.back": HubBackResult;
|
|
711
|
+
"hub.doors": HubDoorsResult;
|
|
712
|
+
"hub.friends": HubFriendsResult;
|
|
713
|
+
"hub.hearts": HubHeartsResult;
|
|
714
|
+
"hub.setHearts": HubHeartsResult;
|
|
715
|
+
"hub.jump": HubJumpResult;
|
|
716
|
+
"hub.report": HubReportResult;
|
|
717
|
+
"hub.chat": HubChatResult;
|
|
718
|
+
"hub.say": HubSayResult;
|
|
719
|
+
"hub.reportChat": HubReportResult;
|
|
720
|
+
"hub.commit": HubCommitResult;
|
|
721
|
+
"hub.cancel": HubCancelResult;
|
|
498
722
|
"haptics.play": HapticsResult;
|
|
499
723
|
"multiplayer.getSession": MultiplayerGetSessionResult;
|
|
500
724
|
"multiplayer.getJoinHint": MultiplayerJoinHint | null;
|
|
@@ -519,6 +743,11 @@ export interface CommandResult {
|
|
|
519
743
|
"characters.status": CharacterOperation;
|
|
520
744
|
"characters.history": CharacterHistory;
|
|
521
745
|
"characters.reset": CharacterAdmission;
|
|
746
|
+
"cauldronStorage.snapshot": SaveSnapshot;
|
|
747
|
+
"cauldronStorage.transaction": SaveReceipt;
|
|
748
|
+
"cauldronStorage.claim": SaveClaimed;
|
|
749
|
+
"cauldronStorage.initialize": SaveOpened;
|
|
750
|
+
"cauldronStorage.operation": SaveOperation;
|
|
522
751
|
"cauldronStorage.get": {
|
|
523
752
|
entries: StoreEntry[];
|
|
524
753
|
};
|
|
@@ -557,6 +786,14 @@ export interface UserPayload {
|
|
|
557
786
|
frameUrl: string | null;
|
|
558
787
|
color: string | null;
|
|
559
788
|
}
|
|
789
|
+
export interface AvatarResult {
|
|
790
|
+
/** Absent from a host before avatar colours. */
|
|
791
|
+
color?: number;
|
|
792
|
+
status: "ok" | "unavailable";
|
|
793
|
+
avatar: number | null;
|
|
794
|
+
/** False while the player wears the default ghost gives every account. */
|
|
795
|
+
chosen: boolean;
|
|
796
|
+
}
|
|
560
797
|
export type CommandName = keyof CommandParams;
|
|
561
798
|
export declare const COMMAND_TIMEOUTS_MS: Partial<Record<CommandName, number>>;
|
|
562
799
|
export declare const STREAM_IDLE_TIMEOUT_MS = 60000;
|
package/dist/commands.js
CHANGED
|
@@ -6,6 +6,17 @@
|
|
|
6
6
|
// Marker + protocol version. Doubles as a discriminator so host messages can't
|
|
7
7
|
// be confused with embed.js `castlexyz:` strings or RN console messages.
|
|
8
8
|
export const CASTLE_SDK_PROTOCOL = 1;
|
|
9
|
+
export function hubEventFrom(value) {
|
|
10
|
+
if (typeof value !== "object" || value === null)
|
|
11
|
+
return null;
|
|
12
|
+
const record = value;
|
|
13
|
+
if (record.castleSdk !== CASTLE_SDK_PROTOCOL)
|
|
14
|
+
return null;
|
|
15
|
+
const event = record.hub;
|
|
16
|
+
if (typeof event !== "object" || event === null)
|
|
17
|
+
return null;
|
|
18
|
+
return event.type === "back" ? { type: "back" } : null;
|
|
19
|
+
}
|
|
9
20
|
// How long the deck waits for a reply to these commands, in place of the
|
|
10
21
|
// data-command timeout. A deck list fetches every cover on the list. A host
|
|
11
22
|
// from before streaming reads a whole model reply before answering agent.chat.
|
|
@@ -13,6 +24,11 @@ export const COMMAND_TIMEOUTS_MS = {
|
|
|
13
24
|
"agent.chat": 180_000,
|
|
14
25
|
"decks.mine": 30_000,
|
|
15
26
|
"decks.saved": 30_000,
|
|
27
|
+
// A door used right after arriving can wait out ghost's roster lag (up to
|
|
28
|
+
// 15 s) before the host gives up on it.
|
|
29
|
+
"hub.go": 30_000,
|
|
30
|
+
"hub.back": 30_000,
|
|
31
|
+
"hub.jump": 30_000,
|
|
16
32
|
};
|
|
17
33
|
// Once a host streams a command, each stream envelope restarts the wait at this
|
|
18
34
|
// length instead. The deck-agent service sends a heartbeat every 15 seconds, and the host
|
package/dist/context.d.ts
CHANGED
|
@@ -7,6 +7,12 @@ export interface CastleEmbed {
|
|
|
7
7
|
*/
|
|
8
8
|
feed?: boolean;
|
|
9
9
|
host?: CastleHost;
|
|
10
|
+
/**
|
|
11
|
+
* The player's A/B arms on this deck, `{ experimentName: variant }`, when the
|
|
12
|
+
* host hands them over with the page. A host that does not answers the
|
|
13
|
+
* `experiments.get` command instead (experiments.ts).
|
|
14
|
+
*/
|
|
15
|
+
experiments?: Record<string, string>;
|
|
10
16
|
}
|
|
11
17
|
declare global {
|
|
12
18
|
interface Window {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The variant this player has in `name`, or `null` when they are in no such
|
|
3
|
+
* experiment. Synchronous; see `experimentsReady` for the one case where a read
|
|
4
|
+
* can come before the answer.
|
|
5
|
+
*/
|
|
6
|
+
export declare function experiment(name: string): string | null;
|
|
7
|
+
/** Every experiment this player is in on this deck, `{ name: variant }`. */
|
|
8
|
+
export declare function experiments(): Record<string, string>;
|
|
9
|
+
/**
|
|
10
|
+
* Resolves, with the same map `experiments()` returns, once the host has
|
|
11
|
+
* answered. Await it once before the first read. On a host that hands the
|
|
12
|
+
* answer over with the page it is already resolved. Never rejects: a host that
|
|
13
|
+
* cannot answer means no experiments.
|
|
14
|
+
*/
|
|
15
|
+
export declare function experimentsReady(): Promise<Record<string, string>>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// A/B experiments. Castle's server gives each player one variant of every
|
|
2
|
+
// experiment this deck is in (castle.json `experiments`, plus any Castle runs
|
|
3
|
+
// across decks and lists this deck in), the same one on every load, and resolves
|
|
4
|
+
// them once per deck load. The deck only ever reads the answer.
|
|
5
|
+
//
|
|
6
|
+
// The reads are synchronous. A host that builds the deck's document puts the
|
|
7
|
+
// answer in `CastleEmbed.experiments`, and it is there from the first line the
|
|
8
|
+
// deck runs. Any other host answers one `experiments.get` command instead, so a
|
|
9
|
+
// deck awaits `experimentsReady()` once at boot and reads synchronously after.
|
|
10
|
+
//
|
|
11
|
+
// `null` is a normal answer: no such experiment, one that has ended, a player
|
|
12
|
+
// who is not in it, or a host that predates experiments. Always handle it.
|
|
13
|
+
import { getCastleEmbed } from "./context";
|
|
14
|
+
import { hostRequest } from "./transport";
|
|
15
|
+
const NONE = Object.freeze({});
|
|
16
|
+
let variants = null;
|
|
17
|
+
let loading = null;
|
|
18
|
+
let warnedEarlyRead = false;
|
|
19
|
+
/**
|
|
20
|
+
* The variant this player has in `name`, or `null` when they are in no such
|
|
21
|
+
* experiment. Synchronous; see `experimentsReady` for the one case where a read
|
|
22
|
+
* can come before the answer.
|
|
23
|
+
*/
|
|
24
|
+
export function experiment(name) {
|
|
25
|
+
const known = current();
|
|
26
|
+
return Object.hasOwn(known, name) ? known[name] : null;
|
|
27
|
+
}
|
|
28
|
+
/** Every experiment this player is in on this deck, `{ name: variant }`. */
|
|
29
|
+
export function experiments() {
|
|
30
|
+
return { ...current() };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolves, with the same map `experiments()` returns, once the host has
|
|
34
|
+
* answered. Await it once before the first read. On a host that hands the
|
|
35
|
+
* answer over with the page it is already resolved. Never rejects: a host that
|
|
36
|
+
* cannot answer means no experiments.
|
|
37
|
+
*/
|
|
38
|
+
export function experimentsReady() {
|
|
39
|
+
return load().then((known) => ({ ...known }));
|
|
40
|
+
}
|
|
41
|
+
function current() {
|
|
42
|
+
if (variants)
|
|
43
|
+
return variants;
|
|
44
|
+
const embedded = fromEmbed();
|
|
45
|
+
if (embedded) {
|
|
46
|
+
variants = embedded;
|
|
47
|
+
return variants;
|
|
48
|
+
}
|
|
49
|
+
if (!loading && !warnedEarlyRead) {
|
|
50
|
+
warnedEarlyRead = true;
|
|
51
|
+
console.warn("Castle experiment() was read before the host answered, so it returned null. " +
|
|
52
|
+
"Call `await experimentsReady()` once at boot, before the first read.");
|
|
53
|
+
}
|
|
54
|
+
// Asked for now, so a later read has the answer even without the await.
|
|
55
|
+
void load();
|
|
56
|
+
return NONE;
|
|
57
|
+
}
|
|
58
|
+
function load() {
|
|
59
|
+
if (variants)
|
|
60
|
+
return Promise.resolve(variants);
|
|
61
|
+
const embedded = fromEmbed();
|
|
62
|
+
if (embedded) {
|
|
63
|
+
variants = embedded;
|
|
64
|
+
return Promise.resolve(variants);
|
|
65
|
+
}
|
|
66
|
+
loading ??= hostRequest("experiments.get", {})
|
|
67
|
+
.then((result) => clean(result?.variants) ?? NONE)
|
|
68
|
+
// A host from before experiments does not know the command.
|
|
69
|
+
.catch(() => NONE)
|
|
70
|
+
.then((known) => {
|
|
71
|
+
variants = known;
|
|
72
|
+
return known;
|
|
73
|
+
});
|
|
74
|
+
return loading;
|
|
75
|
+
}
|
|
76
|
+
function fromEmbed() {
|
|
77
|
+
return clean(getCastleEmbed()?.experiments);
|
|
78
|
+
}
|
|
79
|
+
function clean(value) {
|
|
80
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
81
|
+
return null;
|
|
82
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string")));
|
|
83
|
+
}
|
package/dist/hub.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { HubBackResult, HubCancelResult, HubChatResult, HubDoorsResult, HubFriendsResult, HubHeartsResult, HubGoResult, HubJumpResult, HubReportResult, HubSayResult } from "./commands";
|
|
2
|
+
export type { HubBackResult, HubCancelResult, HubCancelStatus, HubChatMessage, HubChatResult, HubDoor, HubDoorsResult, HubFriend, HubFriendsResult, HubHeartsResult, HubGoParams, HubGoResult, HubHandoffResult, HubHandoffStatus, HubJumpResult, HubPlacementKind, HubReportResult, HubSayResult, } from "./commands";
|
|
3
|
+
/**
|
|
4
|
+
* Doors between hub places. Every hub-kit deck is a place; ghost's door table
|
|
5
|
+
* is the allowlist, and the host supplies real authentication and the current
|
|
6
|
+
* place, session and party. A deck names only a door, never a destination
|
|
7
|
+
* deck, session or party.
|
|
8
|
+
*
|
|
9
|
+
* Going through a door is acquire, then join: `go` (or `back`) acquires the
|
|
10
|
+
* destination and changes nothing else; the deck loads the destination's
|
|
11
|
+
* content behind its door animation and joins with
|
|
12
|
+
* `Multiplayer.join({ mode: "hub", handoff })`. When that socket says hello the
|
|
13
|
+
* SDK tells the host, which commits the trip: presence moves and the return
|
|
14
|
+
* ticket is written. A failure before then leaves the player where they were.
|
|
15
|
+
*/
|
|
16
|
+
export interface CastleHubApi {
|
|
17
|
+
/**
|
|
18
|
+
* Acquire the destination behind a door of the current place. A door used
|
|
19
|
+
* within seconds of arriving may take up to 15 s while ghost catches up with
|
|
20
|
+
* the arrival. `unavailable` on hosts without hub support.
|
|
21
|
+
*/
|
|
22
|
+
go(doorId: string): Promise<HubGoResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Acquire the way back through the door this player came in by: the layer
|
|
25
|
+
* left behind if it has room, else the party's, else the usual pick. The deck
|
|
26
|
+
* cannot choose the return place. `not_found` when there is no way back.
|
|
27
|
+
*/
|
|
28
|
+
back(): Promise<HubBackResult>;
|
|
29
|
+
/** The current place's door rows and who is inside, read at boot. */
|
|
30
|
+
doors(): Promise<HubDoorsResult>;
|
|
31
|
+
/**
|
|
32
|
+
* Mutual friends in a layer of the current place right now (at most 20);
|
|
33
|
+
* `here` is true for those in the player's own layer. Ghost shows only the
|
|
34
|
+
* friends whose now-playing privacy lets this player see them. `unavailable`
|
|
35
|
+
* on hosts without hub support.
|
|
36
|
+
*/
|
|
37
|
+
friends(): Promise<HubFriendsResult>;
|
|
38
|
+
/** Read this signed-in account's hub door hearts. */
|
|
39
|
+
hearts(): Promise<HubHeartsResult>;
|
|
40
|
+
/** Replace the hearts, oldest first, at most eight distinct door ids. */
|
|
41
|
+
setHearts(doorIds: string[]): Promise<HubHeartsResult>;
|
|
42
|
+
/**
|
|
43
|
+
* Acquire the layer of this place a friend is in. The destination is this
|
|
44
|
+
* same place in another session; join it exactly like a door's
|
|
45
|
+
* (`Multiplayer.join({ mode: "hub", handoff })`), which commits it. The way
|
|
46
|
+
* back is unchanged. `not_found` when the friend is not in another layer.
|
|
47
|
+
*/
|
|
48
|
+
jump(userId: string): Promise<HubJumpResult>;
|
|
49
|
+
/**
|
|
50
|
+
* Report another player in this hub session to Castle's moderators, with an
|
|
51
|
+
* optional short reason. Hub chrome only: the host fills in the place and
|
|
52
|
+
* session. `unavailable` on hosts without hub support.
|
|
53
|
+
*/
|
|
54
|
+
report(userId: string, reason?: string): Promise<HubReportResult>;
|
|
55
|
+
/**
|
|
56
|
+
* Read this hub session's chat: the messages this player may read, whether
|
|
57
|
+
* they may chat at all, and the user ids who would be sent what they say.
|
|
58
|
+
* Who may chat is ghost's rule (players who follow each other, both over
|
|
59
|
+
* 13), never the deck's. Poll it, passing the last answer's `cursor` as
|
|
60
|
+
* `after` for only what is newer. `unavailable` on hosts without hub support
|
|
61
|
+
* or outside a hub session.
|
|
62
|
+
*/
|
|
63
|
+
chat(after?: string): Promise<HubChatResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Say something in this hub session's chat (at most 200 characters). The
|
|
66
|
+
* deck cannot choose who is sent it: ghost sends it to the players here who
|
|
67
|
+
* may chat with this one. `none` when there is nobody, `refused` when ghost
|
|
68
|
+
* will not take it, `limited` when said too fast.
|
|
69
|
+
*/
|
|
70
|
+
say(text: string): Promise<HubSayResult>;
|
|
71
|
+
/**
|
|
72
|
+
* Report a chat message this player was returned to Castle's moderators, by
|
|
73
|
+
* its id. Hub chrome only: the host fills in the place and session.
|
|
74
|
+
* `unavailable` on hosts without hub support or outside a hub session.
|
|
75
|
+
*/
|
|
76
|
+
reportChat(messageId: string): Promise<HubReportResult>;
|
|
77
|
+
/**
|
|
78
|
+
* Abandon an acquisition the deck will not join (its content failed to load,
|
|
79
|
+
* the player walked away). Optional: an unjoined one expires on its own.
|
|
80
|
+
*/
|
|
81
|
+
cancel(): Promise<HubCancelResult>;
|
|
82
|
+
/**
|
|
83
|
+
* The player pressed the host's Back control, which a game cannot hide.
|
|
84
|
+
* Answer it with `back()` and the door animation; a host that hears nothing
|
|
85
|
+
* takes the player home itself. Returns an unsubscribe function.
|
|
86
|
+
*/
|
|
87
|
+
onBack(listener: () => void): () => void;
|
|
88
|
+
}
|
|
89
|
+
export declare const Hub: CastleHubApi;
|