castle-web-sdk 0.4.46 → 0.4.48
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 +276 -2
- package/dist/avatar.d.ts +6 -2
- package/dist/avatar.js +12 -4
- package/dist/castle.d.ts +7 -3
- package/dist/castle.js +2 -0
- package/dist/commands.d.ts +142 -1
- package/dist/commands.js +3 -0
- package/dist/display.d.ts +3 -0
- package/dist/display.js +9 -3
- package/dist/hub.d.ts +18 -2
- package/dist/hub.js +47 -0
- package/dist/multiplayer.d.ts +7 -1
- package/dist/multiplayer.js +80 -3
- package/dist/multiplayerProtocol.d.ts +2 -0
- package/dist/party.d.ts +23 -0
- package/dist/party.js +91 -0
- package/dist/save.d.ts +26 -1
- package/dist/save.js +54 -7
- package/dist/server/hub.d.ts +110 -0
- package/dist/server/hub.js +104 -2
- package/dist/server/platformHandle.d.ts +39 -1
- package/dist/server/storage.d.ts +15 -0
- package/dist/server/storage.js +57 -3
- package/dist/server/wrapper.d.ts +43 -6
- package/dist/server/wrapper.js +93 -9
- package/dist/social.d.ts +9 -0
- package/dist/social.js +5 -0
- package/package.json +1 -1
package/dist/server/storage.js
CHANGED
|
@@ -91,17 +91,20 @@ export function createServerStorage(platformHandle) {
|
|
|
91
91
|
}
|
|
92
92
|
return createScope(call, "USER", userId, saveApi);
|
|
93
93
|
},
|
|
94
|
+
town(userId) {
|
|
95
|
+
return createTownStorage(call, userId);
|
|
96
|
+
},
|
|
94
97
|
board(name) {
|
|
95
98
|
return createBoard(call, name);
|
|
96
99
|
},
|
|
97
100
|
async playthrough(userId) {
|
|
98
101
|
const subject = assertUserId(userId, "session.storage.playthrough");
|
|
99
102
|
const opened = await call("playthroughOpen", { subject });
|
|
100
|
-
return createPlaythrough(call, subject, assertGeneration(opened?.generation));
|
|
103
|
+
return createPlaythrough(call, subject, assertGeneration(opened?.generation), optionalResetGeneration(opened?.resetGeneration));
|
|
101
104
|
},
|
|
102
105
|
};
|
|
103
106
|
}
|
|
104
|
-
function createPlaythrough(call, subject, generation) {
|
|
107
|
+
function createPlaythrough(call, subject, generation, resetGeneration) {
|
|
105
108
|
const characters = Object.freeze({
|
|
106
109
|
async send(input) {
|
|
107
110
|
const characterId = assertNarrativeId(input?.characterId, "characterId");
|
|
@@ -217,6 +220,7 @@ function createPlaythrough(call, subject, generation) {
|
|
|
217
220
|
});
|
|
218
221
|
return Object.freeze({
|
|
219
222
|
generation,
|
|
223
|
+
...(resetGeneration === undefined ? {} : { resetGeneration }),
|
|
220
224
|
characters,
|
|
221
225
|
async get(keys) {
|
|
222
226
|
const entries = await call("playthroughGet", { subject, generation, keys: assertKeys(keys, "session.storage.playthrough.get") });
|
|
@@ -239,10 +243,53 @@ function createPlaythrough(call, subject, generation) {
|
|
|
239
243
|
generation,
|
|
240
244
|
operationId: assertOperationId(operationId),
|
|
241
245
|
});
|
|
242
|
-
return createPlaythrough(call, subject, assertGeneration(reset?.generation));
|
|
246
|
+
return createPlaythrough(call, subject, assertGeneration(reset?.generation), resetGeneration);
|
|
243
247
|
},
|
|
244
248
|
});
|
|
245
249
|
}
|
|
250
|
+
/** The town's per-player namespaces a door deck may reach; ghost holds the same list. */
|
|
251
|
+
const TOWN_PLAYER_NAMESPACES = ["tokens", "spent", "own", "hat", "chirp"];
|
|
252
|
+
function createTownStorage(call, subject) {
|
|
253
|
+
const args = { scope: "DECK", subject, place: "town" };
|
|
254
|
+
// Validate in the async verbs, so a bad subject poisons the handle without
|
|
255
|
+
// throwing when the author creates it. Validate every key before any chunk lands.
|
|
256
|
+
const keysFor = (keys, operation) => {
|
|
257
|
+
assertUserId(subject, operation);
|
|
258
|
+
const checked = assertKeys(keys, operation);
|
|
259
|
+
for (const key of checked) {
|
|
260
|
+
const segments = key.split(":");
|
|
261
|
+
if (!TOWN_PLAYER_NAMESPACES.includes(segments[0]) || segments[1] !== subject) {
|
|
262
|
+
throw storageError("CAULDRON_STORAGE_INVALID_ARGUMENT", `Town storage key ${JSON.stringify(key)} must be ${TOWN_PLAYER_NAMESPACES.join("|")}:${subject}[:...].`, operation);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return checked;
|
|
266
|
+
};
|
|
267
|
+
return {
|
|
268
|
+
async get(keys) {
|
|
269
|
+
const entries = [];
|
|
270
|
+
for (const chunk of chunked(keysFor(keys, "session.storage.town.get"), MAX_KEYS_PER_CALL)) {
|
|
271
|
+
entries.push(...await call("get", { ...args, keys: chunk }));
|
|
272
|
+
}
|
|
273
|
+
return decodeEntries(entries, "session.storage.town.get");
|
|
274
|
+
},
|
|
275
|
+
async set(values) {
|
|
276
|
+
keysFor(Object.keys(values), "session.storage.town.set");
|
|
277
|
+
const entries = encodeEntries(values, "session.storage.town.set");
|
|
278
|
+
for (const chunk of batched(entries, "session.storage.town.set")) {
|
|
279
|
+
await call("set", { ...args, entries: chunk });
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
async remove(keys) {
|
|
283
|
+
for (const chunk of chunked(keysFor(keys, "session.storage.town.remove"), MAX_KEYS_PER_CALL)) {
|
|
284
|
+
await call("delete", { ...args, keys: chunk });
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
async increment(key, delta = 1) {
|
|
288
|
+
keysFor([key], "session.storage.town.increment");
|
|
289
|
+
return Number(await call("increment", { ...args, key, delta }));
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
246
293
|
function createScope(call, scope, subject, saveApi) {
|
|
247
294
|
return {
|
|
248
295
|
...createSaveBucket(saveApi, scope === "DECK" ? "deck" : "user", subject ?? undefined),
|
|
@@ -371,6 +418,13 @@ function assertGeneration(value) {
|
|
|
371
418
|
}
|
|
372
419
|
return value;
|
|
373
420
|
}
|
|
421
|
+
function optionalResetGeneration(value) {
|
|
422
|
+
if (value === undefined || value === null)
|
|
423
|
+
return undefined;
|
|
424
|
+
if (Number.isSafeInteger(value) && value >= 1 && value <= MAX_GENERATION)
|
|
425
|
+
return value;
|
|
426
|
+
throw storageError("CAULDRON_STORAGE_INVALID_RESPONSE", "The platform returned an invalid progress reset generation.", "session.storage.playthrough");
|
|
427
|
+
}
|
|
374
428
|
function assertOperationId(value) {
|
|
375
429
|
if (typeof value !== "string" ||
|
|
376
430
|
value.length === 0 ||
|
package/dist/server/wrapper.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MultiplayerSessionMode, PlatformHandle, PlayerIdentity } from "./platformHandle";
|
|
1
|
+
import type { MultiplayerSessionMode, PlatformHandle, PlatformPlaceTool, PlayerIdentity } from "./platformHandle";
|
|
2
2
|
import type { Json } from "../types";
|
|
3
3
|
import type { MultiplayerWorldCharacterCommand, MultiplayerWorldCharacterCommandResult, MultiplayerWorldCharacterCommandResultValue } from "../multiplayer";
|
|
4
4
|
import { type ServerStorage } from "./storage";
|
|
@@ -7,9 +7,9 @@ export type { MultiplayerSessionMode, PlatformHandle, PlatformSessionConfig, Pla
|
|
|
7
7
|
export { createSaveCoordinator } from "../saveCoordinator";
|
|
8
8
|
export type { CoordinatedChange, CoordinatedSave, SaveCoordinator, SaveStatus, } from "../saveCoordinator";
|
|
9
9
|
export { SaveError } from "../save";
|
|
10
|
-
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";
|
|
11
|
-
export type { MultiplayerHubArrival, MultiplayerHubBoard, MultiplayerHubOutcome, MultiplayerHubPadUpdateInput, MultiplayerHubPadUpdateResult, MultiplayerHubPlayerResults, MultiplayerHubRecord, MultiplayerHubReportReceipt, MultiplayerHubResultInput, MultiplayerHubResultRow, MultiplayerHubResultShape, MultiplayerHubResultsRead, MultiplayerHubResultsReadInput, MultiplayerHubWearable, MultiplayerHubWearReceipt, MultiplayerServerHub, } from "./hub";
|
|
12
|
-
export type { CharacterActionNotice, CharacterGameStateOperation, CharacterGameStateResult, CharacterKnowledgeDeliveryOperation, CharacterKnowledgeDeliveryResult, CharacterKnowledgeSource, CompleteCharacterBeatInput, DeliverCharacterKnowledgeInput, RecordCharacterEventInput, SetCharacterGameStateInput, ShareCharacterClaimInput, ServerStorage, ServerStorageBoard, ServerStorageBoardScore, ServerStorageListEntry, ServerStorageListOptions, ServerStorageListPage, ServerPlaythrough, ServerPlaythroughCharacters, ServerCharacterHistoryOptions, ServerStorageScope, } from "./storage";
|
|
10
|
+
export type { SaveApi, SaveBucketApi, SaveBucketRead, SaveChange, SaveCheck, SaveConditionalCheck, SaveConditionalWriteInput, SaveClaimed, SaveClaimInput, SaveErrorEnvelope, SaveInitial, SaveInitializeInput, SaveOpened, SaveOperation, SaveReadTarget, SaveReceipt, SaveRecentOperationsInput, SaveRecentOperationsPage, SaveRevision, SaveRows, SaveScope, SaveSelection, SaveSnapshot, SaveTarget, SaveTransactionInput, SaveWriter, } from "../save";
|
|
11
|
+
export type { MultiplayerHubArrival, MultiplayerHubBoard, MultiplayerHubOutcome, MultiplayerHubPadUpdateInput, MultiplayerHubPadUpdateResult, MultiplayerHubPlayerResults, MultiplayerHubRecord, MultiplayerHubReportReceipt, MultiplayerHubResultInput, MultiplayerHubResultRow, MultiplayerHubResultShape, MultiplayerHubResultsRead, MultiplayerHubResultsReadInput, MultiplayerHubWearable, MultiplayerHubWearReceipt, MultiplayerHubEarnedClass, MultiplayerHubEarnedItem, MultiplayerHubHomeClass, MultiplayerHubHomeItem, MultiplayerHubUnlockReceipt, MultiplayerHubUnlocksRead, MultiplayerHubWearEarnedReceipt, MultiplayerServerHub, } from "./hub";
|
|
12
|
+
export type { CharacterActionNotice, CharacterGameStateOperation, CharacterGameStateResult, CharacterKnowledgeDeliveryOperation, CharacterKnowledgeDeliveryResult, CharacterKnowledgeSource, CompleteCharacterBeatInput, DeliverCharacterKnowledgeInput, RecordCharacterEventInput, SetCharacterGameStateInput, ShareCharacterClaimInput, ServerStorage, ServerStorageBoard, ServerStorageBoardScore, ServerStorageListEntry, ServerStorageListOptions, ServerStorageListPage, ServerPlaythrough, ServerPlaythroughCharacters, ServerCharacterHistoryOptions, ServerStorageScope, ServerTownStorage, } from "./storage";
|
|
13
13
|
export interface MultiplayerServerPlayer extends PlayerIdentity {
|
|
14
14
|
playerId: string;
|
|
15
15
|
readonly participantId: string;
|
|
@@ -268,11 +268,41 @@ export interface MultiplayerWorldCharacterTurn {
|
|
|
268
268
|
readonly players: readonly MultiplayerServerPlayer[];
|
|
269
269
|
readonly characterIds: readonly string[];
|
|
270
270
|
};
|
|
271
|
-
/**
|
|
272
|
-
|
|
271
|
+
/**
|
|
272
|
+
* Durably admits the exact host-authorized request. No arguments can broaden it:
|
|
273
|
+
* `tools` only declares lookups the character may make of this place during the
|
|
274
|
+
* turn (answered by `onWorldCharacterTool`), and is dropped on a host that does
|
|
275
|
+
* not serve place tools.
|
|
276
|
+
*/
|
|
277
|
+
send(options?: MultiplayerWorldCharacterSendOptions): Promise<MultiplayerWorldCharacterAdmission>;
|
|
273
278
|
/** Explicitly rejects the request before admission. */
|
|
274
279
|
reject(code?: string): void;
|
|
275
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* A lookup a place offers its character for one turn. The host drops an invalid
|
|
283
|
+
* list whole (the turn proceeds without tools): at most 8, a unique `name` matching
|
|
284
|
+
* `^[A-Za-z][A-Za-z0-9_]{0,31}$` other than `perform_action` or `capture_claim`,
|
|
285
|
+
* a 1..300 character `description`, and a JSON-object `input_schema` of at most
|
|
286
|
+
* 1 KiB, with the whole list at most 6 KiB.
|
|
287
|
+
*/
|
|
288
|
+
export type MultiplayerPlaceTool = PlatformPlaceTool;
|
|
289
|
+
export interface MultiplayerWorldCharacterSendOptions {
|
|
290
|
+
tools?: readonly MultiplayerPlaceTool[];
|
|
291
|
+
/** Private host-brokered transcript metadata, not player-selected. */
|
|
292
|
+
hubTextArchive?: {
|
|
293
|
+
npcId: string;
|
|
294
|
+
conversationId?: string;
|
|
295
|
+
input: string;
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/** One place-tool use by a character during a turn this place admitted. */
|
|
299
|
+
export interface MultiplayerWorldCharacterToolRequest {
|
|
300
|
+
readonly operationId: string;
|
|
301
|
+
readonly characterId: string;
|
|
302
|
+
readonly playerId: string;
|
|
303
|
+
readonly name: string;
|
|
304
|
+
readonly input: Readonly<Record<string, unknown>>;
|
|
305
|
+
}
|
|
276
306
|
type CallbackResult = unknown;
|
|
277
307
|
export interface MultiplayerServerCallbacks {
|
|
278
308
|
onStart?: (session: MultiplayerServerSession) => CallbackResult;
|
|
@@ -284,6 +314,13 @@ export interface MultiplayerServerCallbacks {
|
|
|
284
314
|
onWorldCharacterTurn?: (session: MultiplayerServerSession, turn: MultiplayerWorldCharacterTurn) => CallbackResult;
|
|
285
315
|
onWorldCharacterCommand?: (session: MultiplayerServerSession, request: MultiplayerWorldCharacterCommandRequest) => CallbackResult;
|
|
286
316
|
onWorldCharacterRead?: (session: MultiplayerServerSession, request: MultiplayerWorldCharacterReadRequest) => CallbackResult;
|
|
317
|
+
/**
|
|
318
|
+
* Answers a place-tool use declared through `turn.send({ tools })`. Return a
|
|
319
|
+
* string or a JSON value (stringified); a result over 1 KiB becomes
|
|
320
|
+
* `{"error":"result too large"}` and a throw `{"error":"tool failed"}`. The host
|
|
321
|
+
* waits 1.5 s, then the character answers without it.
|
|
322
|
+
*/
|
|
323
|
+
onWorldCharacterTool?: (request: MultiplayerWorldCharacterToolRequest, session: MultiplayerServerSession) => unknown;
|
|
287
324
|
onTick?: (session: MultiplayerServerSession) => CallbackResult;
|
|
288
325
|
onReplaced?: (session: MultiplayerServerSession) => CallbackResult;
|
|
289
326
|
/**
|
package/dist/server/wrapper.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CastleError } from "../errors";
|
|
1
2
|
import { isBinaryPayload, toUint8Array, } from "../multiplayerProtocol";
|
|
2
3
|
import { encodeStorageValue, storageError } from "../storageJson";
|
|
3
4
|
import { createServerStorage } from "./storage";
|
|
@@ -69,18 +70,15 @@ async function bootSession(platformHandle, callbacks) {
|
|
|
69
70
|
players: Object.freeze(request.audience.players.map((player) => Object.freeze(joinedPlayer(player.playerId, player)))),
|
|
70
71
|
characterIds: Object.freeze([...request.audience.characterIds]),
|
|
71
72
|
}),
|
|
72
|
-
async send() {
|
|
73
|
+
async send(options) {
|
|
73
74
|
if (settled)
|
|
74
75
|
throw new TypeError("This shared character request is already settled.");
|
|
75
76
|
settled = true;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
rawReceipt = await platformHandle.worldCharacterSend(request.grantId);
|
|
79
|
-
}
|
|
80
|
-
catch (error) {
|
|
77
|
+
const rawReceipt = await sendWorldCharacterTurn(platformHandle, request.grantId, options)
|
|
78
|
+
.catch((error) => {
|
|
81
79
|
brokerFailure.rejection = { error };
|
|
82
80
|
throw error;
|
|
83
|
-
}
|
|
81
|
+
});
|
|
84
82
|
return parseWorldCharacterAdmission(rawReceipt, request.operationId);
|
|
85
83
|
},
|
|
86
84
|
reject(code) {
|
|
@@ -103,6 +101,7 @@ async function bootSession(platformHandle, callbacks) {
|
|
|
103
101
|
}
|
|
104
102
|
});
|
|
105
103
|
}
|
|
104
|
+
listenForWorldCharacterTools(platformHandle, callbacks, session);
|
|
106
105
|
if (callbacks.onWorldCharacterCommand &&
|
|
107
106
|
hasWorldCharacterPlatform(platformHandle, "worldCharacterCommand")) {
|
|
108
107
|
platformHandle.on("worldCharacterCommand", async (raw) => {
|
|
@@ -1081,12 +1080,13 @@ function assertRuntimeCharacterCapabilities(value) {
|
|
|
1081
1080
|
}
|
|
1082
1081
|
return Object.freeze({ actionIds: Object.freeze([...normalized].sort()) });
|
|
1083
1082
|
}
|
|
1083
|
+
const LONE_SURROGATE = /[\uD800-\uDFFF]/u;
|
|
1084
1084
|
function assertRuntimeCharacterText(value, field, maxCharacters, maxBytes) {
|
|
1085
1085
|
if (typeof value !== "string" ||
|
|
1086
1086
|
value.length === 0 ||
|
|
1087
1087
|
value.length > maxCharacters ||
|
|
1088
1088
|
value.includes("\0") ||
|
|
1089
|
-
|
|
1089
|
+
LONE_SURROGATE.test(value) ||
|
|
1090
1090
|
new TextEncoder().encode(value).byteLength > maxBytes) {
|
|
1091
1091
|
throw new TypeError(`session.world.characters.create: ${field} must be valid non-empty text of at most ${maxCharacters} characters.`);
|
|
1092
1092
|
}
|
|
@@ -1190,6 +1190,87 @@ function assertPlatformWorldCharacterRead(value) {
|
|
|
1190
1190
|
}
|
|
1191
1191
|
return value;
|
|
1192
1192
|
}
|
|
1193
|
+
// Tools ride only to a host that can answer them (an older host refuses a send
|
|
1194
|
+
// carrying anything beyond the grant); otherwise this is exactly the tool-less send.
|
|
1195
|
+
function sendWorldCharacterTurn(platformHandle, grantId, options) {
|
|
1196
|
+
const tools = options?.tools;
|
|
1197
|
+
if (options?.hubTextArchive !== undefined) {
|
|
1198
|
+
if (platformHandle.hubTextArchiveEnabled !== true)
|
|
1199
|
+
return Promise.reject(new CastleError({ code: 'CAULDRON_HUB_UNAVAILABLE', message: 'This host cannot archive NPC transcripts.', operation: 'worldCharacter.send' }));
|
|
1200
|
+
return platformHandle.worldCharacterSend(grantId, Array.isArray(tools) && typeof platformHandle.worldCharacterToolResult === "function" ? tools : undefined, options.hubTextArchive);
|
|
1201
|
+
}
|
|
1202
|
+
return Array.isArray(tools) && tools.length > 0 &&
|
|
1203
|
+
typeof platformHandle.worldCharacterToolResult === "function"
|
|
1204
|
+
? platformHandle.worldCharacterSend(grantId, tools)
|
|
1205
|
+
: platformHandle.worldCharacterSend(grantId);
|
|
1206
|
+
}
|
|
1207
|
+
// Only a handler on a host that answers place tools listens; without one the
|
|
1208
|
+
// host times the lookup out and the character answers without it.
|
|
1209
|
+
function listenForWorldCharacterTools(platformHandle, callbacks, session) {
|
|
1210
|
+
const handler = callbacks.onWorldCharacterTool;
|
|
1211
|
+
const answer = platformHandle.worldCharacterToolResult?.bind(platformHandle);
|
|
1212
|
+
if (!handler || !answer)
|
|
1213
|
+
return;
|
|
1214
|
+
try {
|
|
1215
|
+
platformHandle.on("worldCharacterTool", async (raw) => {
|
|
1216
|
+
const call = platformWorldCharacterTool(raw);
|
|
1217
|
+
if (!call)
|
|
1218
|
+
return;
|
|
1219
|
+
const result = await worldCharacterToolResult(handler, call.request, session);
|
|
1220
|
+
try {
|
|
1221
|
+
await answer(call.requestId, result);
|
|
1222
|
+
}
|
|
1223
|
+
catch {
|
|
1224
|
+
// The host has stopped waiting (or refused); the character answers without it.
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
catch {
|
|
1229
|
+
// Older shims may reject event names they do not recognize.
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
const MAX_WORLD_CHARACTER_TOOL_RESULT_BYTES = 1024;
|
|
1233
|
+
const WORLD_CHARACTER_TOOL_TOO_LARGE = '{"error":"result too large"}';
|
|
1234
|
+
const WORLD_CHARACTER_TOOL_FAILED = '{"error":"tool failed"}';
|
|
1235
|
+
function platformWorldCharacterTool(value) {
|
|
1236
|
+
if (!value ||
|
|
1237
|
+
typeof value !== "object" ||
|
|
1238
|
+
typeof value.requestId !== "string" ||
|
|
1239
|
+
typeof value.operationId !== "string" ||
|
|
1240
|
+
typeof value.characterId !== "string" ||
|
|
1241
|
+
typeof value.playerId !== "string" ||
|
|
1242
|
+
typeof value.name !== "string" ||
|
|
1243
|
+
!value.input ||
|
|
1244
|
+
typeof value.input !== "object" ||
|
|
1245
|
+
Array.isArray(value.input)) {
|
|
1246
|
+
return null;
|
|
1247
|
+
}
|
|
1248
|
+
return {
|
|
1249
|
+
requestId: value.requestId,
|
|
1250
|
+
request: Object.freeze({
|
|
1251
|
+
operationId: value.operationId,
|
|
1252
|
+
characterId: value.characterId,
|
|
1253
|
+
playerId: value.playerId,
|
|
1254
|
+
name: value.name,
|
|
1255
|
+
input: deepFreezeJsonObject(value.input),
|
|
1256
|
+
}),
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
async function worldCharacterToolResult(handler, request, session) {
|
|
1260
|
+
let result;
|
|
1261
|
+
try {
|
|
1262
|
+
const value = await handler(request, session);
|
|
1263
|
+
result = typeof value === "string" ? value : JSON.stringify(value);
|
|
1264
|
+
}
|
|
1265
|
+
catch {
|
|
1266
|
+
return WORLD_CHARACTER_TOOL_FAILED;
|
|
1267
|
+
}
|
|
1268
|
+
if (typeof result !== "string")
|
|
1269
|
+
return WORLD_CHARACTER_TOOL_FAILED;
|
|
1270
|
+
return new TextEncoder().encode(result).byteLength > MAX_WORLD_CHARACTER_TOOL_RESULT_BYTES
|
|
1271
|
+
? WORLD_CHARACTER_TOOL_TOO_LARGE
|
|
1272
|
+
: result;
|
|
1273
|
+
}
|
|
1193
1274
|
function hasWorldCharacterPlatform(platformHandle, operation) {
|
|
1194
1275
|
return (typeof platformHandle[operation] === "function" &&
|
|
1195
1276
|
typeof platformHandle.worldCharacterReject === "function");
|
|
@@ -1344,10 +1425,11 @@ function joinedPlayer(connId, identity) {
|
|
|
1344
1425
|
delete player.hiddenUserIds;
|
|
1345
1426
|
// A/B arms arrive beside the player; one a host put on it stays off it too.
|
|
1346
1427
|
delete player.experimentVariants;
|
|
1428
|
+
Object.defineProperty(player, "under13", { value: identity.under13 === true, enumerable: false });
|
|
1347
1429
|
return player;
|
|
1348
1430
|
}
|
|
1349
1431
|
const legacy = connId;
|
|
1350
|
-
|
|
1432
|
+
const player = {
|
|
1351
1433
|
playerId: legacy.playerId,
|
|
1352
1434
|
userId: legacy.userId,
|
|
1353
1435
|
username: legacy.username,
|
|
@@ -1355,6 +1437,8 @@ function joinedPlayer(connId, identity) {
|
|
|
1355
1437
|
participantId: legacy.scopedUserId ?? legacy.userId,
|
|
1356
1438
|
...(legacy.scopedUserId ? { scopedUserId: legacy.scopedUserId } : {}),
|
|
1357
1439
|
};
|
|
1440
|
+
Object.defineProperty(player, "under13", { value: legacy.under13 === true, enumerable: false });
|
|
1441
|
+
return player;
|
|
1358
1442
|
}
|
|
1359
1443
|
function parseHandleMessage(text) {
|
|
1360
1444
|
if (typeof text !== "string")
|
package/dist/social.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SocialFriend, SocialPresence } from './commands';
|
|
2
|
+
export type { SocialFriend, SocialPresence } from './commands';
|
|
3
|
+
export interface CastleSocialApi {
|
|
4
|
+
/** The signed-in player's mutual Castle follows, at most 20. */
|
|
5
|
+
friends(): Promise<SocialFriend[]>;
|
|
6
|
+
/** Mutual friends playing this deck now. Poll to refresh; v1 has no event. */
|
|
7
|
+
presence(): Promise<SocialPresence[]>;
|
|
8
|
+
}
|
|
9
|
+
export declare const Social: CastleSocialApi;
|
package/dist/social.js
ADDED