castle-web-cli 0.4.59 → 0.4.61
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/dist/agent-prompts.js +3 -2
- package/dist/agent.d.ts +4 -4
- package/dist/agent.js +342 -239
- package/dist/api.d.ts +9 -0
- package/dist/api.js +1 -1
- package/dist/castle-host/host.d.ts +58 -0
- package/dist/castle-host/host.js +457 -0
- package/dist/chat-client.js +199 -96
- package/dist/ide.d.ts +3 -3
- package/dist/ide.js +96 -64
- package/dist/serve.js +45 -1
- package/kits/basic-2d/CLAUDE.md +4 -3
- package/kits/basic-2d/behaviors/Drawing.jsx +5 -2
- package/kits/basic-2d/drawings/default.drawing +70 -0
- package/package.json +5 -2
package/dist/api.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
interface GraphqlResponse {
|
|
2
|
+
data?: Record<string, unknown>;
|
|
3
|
+
errors?: Array<{
|
|
4
|
+
message?: string;
|
|
5
|
+
extensions?: Record<string, unknown>;
|
|
6
|
+
}>;
|
|
7
|
+
}
|
|
8
|
+
export declare function graphql(query: string, variables?: Record<string, unknown>): Promise<GraphqlResponse>;
|
|
1
9
|
export declare function startCLILogin(): Promise<{
|
|
2
10
|
pollToken: string;
|
|
3
11
|
url: string;
|
|
@@ -71,3 +79,4 @@ export interface WebDeckSource {
|
|
|
71
79
|
}
|
|
72
80
|
export declare function webDeckSource(deckId: string): Promise<WebDeckSource | null>;
|
|
73
81
|
export declare function saveWebDeckSource(deckId: string, uploadId: string): Promise<WebDeckSource>;
|
|
82
|
+
export {};
|
package/dist/api.js
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Hand-written declaration for the vendored, self-contained host.js (runtime is
|
|
2
|
+
// copied here by scripts/copy-host-module.mjs). Kept tiny and import-free on
|
|
3
|
+
// purpose — the SDK's emitted .d.ts use extensionless imports that the CLI's
|
|
4
|
+
// Node16 resolution rejects. Mirror the public surface of
|
|
5
|
+
// castle-experimental-web/sdk/src/host.ts; update if that signature changes.
|
|
6
|
+
|
|
7
|
+
export interface HostContext {
|
|
8
|
+
deckId: string | null;
|
|
9
|
+
sessionId: string | null;
|
|
10
|
+
userId: string | null;
|
|
11
|
+
username: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface GraphqlError {
|
|
15
|
+
message?: string;
|
|
16
|
+
extensions?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface GraphqlResponse {
|
|
20
|
+
data?: unknown;
|
|
21
|
+
errors?: GraphqlError[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type GraphqlFetch = (
|
|
25
|
+
query: string,
|
|
26
|
+
variables: Record<string, unknown>,
|
|
27
|
+
) => Promise<GraphqlResponse>;
|
|
28
|
+
|
|
29
|
+
export type PlatformHandler = (
|
|
30
|
+
command: unknown,
|
|
31
|
+
params: Record<string, unknown>,
|
|
32
|
+
ctx: HostContext,
|
|
33
|
+
) => Promise<unknown>;
|
|
34
|
+
|
|
35
|
+
export interface HostCapabilities {
|
|
36
|
+
graphqlFetch: GraphqlFetch;
|
|
37
|
+
platformHandler?: PlatformHandler;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SerializedCommandError {
|
|
41
|
+
code: string;
|
|
42
|
+
message: string;
|
|
43
|
+
command?: string;
|
|
44
|
+
extensions?: Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface HostResult {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
data?: unknown;
|
|
50
|
+
error?: SerializedCommandError;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function executeCommand(
|
|
54
|
+
ctx: HostContext,
|
|
55
|
+
command: unknown,
|
|
56
|
+
params: unknown,
|
|
57
|
+
capabilities: HostCapabilities,
|
|
58
|
+
): Promise<HostResult>;
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
// GENERATED — do not edit. Vendored from castle-experimental-web/sdk/dist/host.js
|
|
2
|
+
// Re-run `npm run copy-host` in castle-experimental-web after an SDK change.
|
|
3
|
+
|
|
4
|
+
// Host-side command executor — the shared module run by every trusted host
|
|
5
|
+
// (web DeckPlayer, mobile React Native, the castle-web dev server). It takes a
|
|
6
|
+
// command + params from an untrusted deck, validates it against the allowlist,
|
|
7
|
+
// stamps trusted context (deckId/sessionId/userId — never taken from the deck),
|
|
8
|
+
// builds the GraphQL query, runs it via the host's own authed fetch, and
|
|
9
|
+
// returns a serializable result/error envelope. The deck never sees a token or
|
|
10
|
+
// builds a query. This file is NOT imported by deck-side code.
|
|
11
|
+
//
|
|
12
|
+
// Usage from a host:
|
|
13
|
+
// import { executeCommand } from "castle-web-sdk/host";
|
|
14
|
+
// const result = await executeCommand(ctx, command, params, graphqlFetch);
|
|
15
|
+
// // reply { castleSdk: 1, requestId, ...result } back to the deck.
|
|
16
|
+
// Runtime command allowlist — kept here (not in commands.ts) so the import
|
|
17
|
+
// above stays type-only. Must stay in sync with CommandParams in commands.ts.
|
|
18
|
+
const COMMAND_NAMES = [
|
|
19
|
+
"deckStorage.load",
|
|
20
|
+
"deckStorage.update",
|
|
21
|
+
"sharedDeckStorage.load",
|
|
22
|
+
"sharedDeckStorage.update",
|
|
23
|
+
"leaderboard.fetch",
|
|
24
|
+
"leaderboard.save",
|
|
25
|
+
"user.getCurrent",
|
|
26
|
+
"time.getServerTime",
|
|
27
|
+
"pass.has",
|
|
28
|
+
"pass.offer",
|
|
29
|
+
];
|
|
30
|
+
// Platform/capability commands: NOT serviced by graphqlFetch. They're dispatched
|
|
31
|
+
// to the host's optional platformHandler (mobile renders native UI; web shows an
|
|
32
|
+
// upsell). A host with no platformHandler returns the command's normalized
|
|
33
|
+
// "unavailable" outcome rather than an error — capability divergence is the
|
|
34
|
+
// host's concern, never a deck-facing gate.
|
|
35
|
+
const PLATFORM_COMMAND_NAMES = ["pass.offer"];
|
|
36
|
+
function isCommandName(value) {
|
|
37
|
+
return (typeof value === "string" &&
|
|
38
|
+
COMMAND_NAMES.includes(value));
|
|
39
|
+
}
|
|
40
|
+
function isPlatformCommand(command) {
|
|
41
|
+
return PLATFORM_COMMAND_NAMES.includes(command);
|
|
42
|
+
}
|
|
43
|
+
class HostCommandError extends Error {
|
|
44
|
+
code;
|
|
45
|
+
command;
|
|
46
|
+
extensions;
|
|
47
|
+
constructor(code, message, command, extensions) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = "HostCommandError";
|
|
50
|
+
this.code = code;
|
|
51
|
+
this.command = command;
|
|
52
|
+
this.extensions = extensions;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export async function executeCommand(ctx, command, params, capabilities) {
|
|
56
|
+
if (!isCommandName(command)) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
error: {
|
|
60
|
+
code: "UNKNOWN_COMMAND",
|
|
61
|
+
message: "Unknown Castle command.",
|
|
62
|
+
command: typeof command === "string" ? command : undefined,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const data = await runCommand(ctx, command, (params ?? {}), capabilities);
|
|
68
|
+
return { ok: true, data };
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return { ok: false, error: toSerializedError(error, command) };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function runCommand(ctx, command, params, caps) {
|
|
75
|
+
if (isPlatformCommand(command)) {
|
|
76
|
+
return runPlatformCommand(ctx, command, params, caps);
|
|
77
|
+
}
|
|
78
|
+
const gql = caps.graphqlFetch;
|
|
79
|
+
switch (command) {
|
|
80
|
+
case "deckStorage.load":
|
|
81
|
+
return deckStorageLoad(ctx, gql);
|
|
82
|
+
case "deckStorage.update":
|
|
83
|
+
return deckStorageUpdate(ctx, params, gql);
|
|
84
|
+
case "sharedDeckStorage.load":
|
|
85
|
+
return sharedDeckStorageLoad(ctx, params, gql);
|
|
86
|
+
case "sharedDeckStorage.update":
|
|
87
|
+
return sharedDeckStorageUpdate(ctx, params, gql);
|
|
88
|
+
case "leaderboard.fetch":
|
|
89
|
+
return leaderboardFetch(ctx, params, gql);
|
|
90
|
+
case "leaderboard.save":
|
|
91
|
+
return leaderboardSave(ctx, params, gql);
|
|
92
|
+
case "user.getCurrent":
|
|
93
|
+
return Promise.resolve(userGetCurrent(ctx));
|
|
94
|
+
case "time.getServerTime":
|
|
95
|
+
return timeGetServerTime(gql);
|
|
96
|
+
case "pass.has":
|
|
97
|
+
return passHas(ctx, params, gql);
|
|
98
|
+
// Platform commands are handled above; listed here to keep the switch
|
|
99
|
+
// exhaustive over CommandName.
|
|
100
|
+
case "pass.offer":
|
|
101
|
+
return runPlatformCommand(ctx, command, params, caps);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Dispatch a platform/capability command to the host's platformHandler. No
|
|
105
|
+
// handler → the command's normalized "unavailable" outcome (a SUCCESS, not an
|
|
106
|
+
// error: a deck on a host without this capability still gets one uniform
|
|
107
|
+
// result). The handler's return is normalized so a malformed outcome can't
|
|
108
|
+
// leak through to the deck.
|
|
109
|
+
async function runPlatformCommand(ctx, command, params, caps) {
|
|
110
|
+
switch (command) {
|
|
111
|
+
case "pass.offer":
|
|
112
|
+
return passesOffer(ctx, params, caps);
|
|
113
|
+
default:
|
|
114
|
+
return unavailableOutcome(command);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function passHas(ctx, params, gql) {
|
|
118
|
+
const deckId = requireDeckId(ctx, "pass.has");
|
|
119
|
+
const passId = asString(params.passId, "passId", "pass.has");
|
|
120
|
+
const data = await graphql(gql, PASSES_FOR_DECK_QUERY, { deckId }, "pass.has");
|
|
121
|
+
const match = (data.passesForDeck ?? []).find((p) => p?.passId === passId);
|
|
122
|
+
return { hasPass: match?.isActive === true };
|
|
123
|
+
}
|
|
124
|
+
async function passesOffer(ctx, params, caps) {
|
|
125
|
+
const passId = asString(params.passId, "passId", "pass.offer");
|
|
126
|
+
// Hosts without bricks support (dev CLI — no handler at all) get a normalized
|
|
127
|
+
// unavailable, never an error or a thrown MISSING_DECK_ID.
|
|
128
|
+
if (!caps.platformHandler)
|
|
129
|
+
return { status: "unavailable" };
|
|
130
|
+
// Real transaction path (mobile native sheet; web upsell): the pass belongs
|
|
131
|
+
// to a deck, so a trusted deckId is required before handing off.
|
|
132
|
+
const deckId = requireDeckId(ctx, "pass.offer");
|
|
133
|
+
const outcome = await caps.platformHandler("pass.offer", { passId, deckId }, ctx);
|
|
134
|
+
return normalizePassOutcome(outcome);
|
|
135
|
+
}
|
|
136
|
+
function normalizePassOutcome(value) {
|
|
137
|
+
const record = typeof value === "object" && value !== null
|
|
138
|
+
? value
|
|
139
|
+
: {};
|
|
140
|
+
const status = record.status;
|
|
141
|
+
const valid = [
|
|
142
|
+
"purchased",
|
|
143
|
+
"alreadyOwned",
|
|
144
|
+
"cancelled",
|
|
145
|
+
"unavailable",
|
|
146
|
+
];
|
|
147
|
+
if (typeof status === "string" && valid.includes(status)) {
|
|
148
|
+
return { status: status };
|
|
149
|
+
}
|
|
150
|
+
return { status: "cancelled" };
|
|
151
|
+
}
|
|
152
|
+
function unavailableOutcome(command) {
|
|
153
|
+
// Only passes exists today; keep this total over future platform commands.
|
|
154
|
+
if (command === "pass.offer") {
|
|
155
|
+
return { status: "unavailable" };
|
|
156
|
+
}
|
|
157
|
+
return { status: "unavailable" };
|
|
158
|
+
}
|
|
159
|
+
async function deckStorageLoad(ctx, gql) {
|
|
160
|
+
const deckId = requireDeckId(ctx, "deckStorage.load");
|
|
161
|
+
const data = await graphql(gql, DECK_STORAGE_QUERY, { deckId, sessionId: ctx.sessionId }, "deckStorage.load");
|
|
162
|
+
return { blob: data.deckStorage ?? {} };
|
|
163
|
+
}
|
|
164
|
+
async function deckStorageUpdate(ctx, params, gql) {
|
|
165
|
+
const deckId = requireDeckId(ctx, "deckStorage.update");
|
|
166
|
+
const data = await graphql(gql, UPDATE_DECK_STORAGE_MUTATION, {
|
|
167
|
+
deckId,
|
|
168
|
+
sessionId: ctx.sessionId,
|
|
169
|
+
updates: asUpdates(params.updates),
|
|
170
|
+
}, "deckStorage.update");
|
|
171
|
+
return { blob: data.updateDeckStorage ?? {} };
|
|
172
|
+
}
|
|
173
|
+
async function sharedDeckStorageLoad(ctx, params, gql) {
|
|
174
|
+
const deckId = requireDeckId(ctx, "sharedDeckStorage.load");
|
|
175
|
+
const scope = asScope(params.scope, "sharedDeckStorage.load");
|
|
176
|
+
// 'user' read: an explicit userId targets another player's public bucket;
|
|
177
|
+
// otherwise the current player's. 'deck' scope ignores userId.
|
|
178
|
+
const userId = scope === "user" ? asOptionalString(params.userId) ?? ctx.userId : null;
|
|
179
|
+
const data = await graphql(gql, SHARED_DECK_STORAGE_QUERY, { deckId, sessionId: ctx.sessionId, userId, keys: asKeys(params.keys) }, "sharedDeckStorage.load");
|
|
180
|
+
return { blob: data.sharedDeckStorage ?? {} };
|
|
181
|
+
}
|
|
182
|
+
async function sharedDeckStorageUpdate(ctx, params, gql) {
|
|
183
|
+
const deckId = requireDeckId(ctx, "sharedDeckStorage.update");
|
|
184
|
+
const scope = asScope(params.scope, "sharedDeckStorage.update");
|
|
185
|
+
// 'user'-scope writes are forced to the current player — the deck cannot
|
|
186
|
+
// write another player's bucket.
|
|
187
|
+
const userId = scope === "user" ? requireUserId(ctx, "sharedDeckStorage.update") : null;
|
|
188
|
+
await graphql(gql, UPDATE_SHARED_DECK_STORAGE_MUTATION, { deckId, sessionId: ctx.sessionId, userId, updates: asUpdates(params.updates) }, "sharedDeckStorage.update");
|
|
189
|
+
return { ok: true };
|
|
190
|
+
}
|
|
191
|
+
async function leaderboardFetch(ctx, params, gql) {
|
|
192
|
+
const deckId = requireDeckId(ctx, "leaderboard.fetch");
|
|
193
|
+
const variables = {
|
|
194
|
+
deckId,
|
|
195
|
+
variable: asString(params.variable, "variable", "leaderboard.fetch"),
|
|
196
|
+
type: asLeaderboardType(params.type),
|
|
197
|
+
filter: "dedupUsers",
|
|
198
|
+
includeFollowList: false,
|
|
199
|
+
includeParties: false,
|
|
200
|
+
scope: asOptionalString(params.scope) ?? null,
|
|
201
|
+
};
|
|
202
|
+
// A non-null score means the deck just wrote this value and wants its own
|
|
203
|
+
// score reflected immediately: write-and-read atomically via leaderboardV2
|
|
204
|
+
// (mirrors the engine's getLeaderboard path in core/src/leaderboards.cpp).
|
|
205
|
+
// No score → plain read of the settled leaderboard.
|
|
206
|
+
const score = asOptionalNumber(params.score);
|
|
207
|
+
if (score !== null) {
|
|
208
|
+
const data = await graphql(gql, LEADERBOARD_V2_MUTATION, { ...variables, score }, "leaderboard.fetch");
|
|
209
|
+
return { leaderboard: data.leaderboardV2, currentUserId: ctx.userId };
|
|
210
|
+
}
|
|
211
|
+
const data = await graphql(gql, LEADERBOARD_QUERY, variables, "leaderboard.fetch");
|
|
212
|
+
return { leaderboard: data.leaderboard, currentUserId: ctx.userId };
|
|
213
|
+
}
|
|
214
|
+
async function leaderboardSave(ctx, params, gql) {
|
|
215
|
+
const deckId = requireDeckId(ctx, "leaderboard.save");
|
|
216
|
+
await graphql(gql, SAVE_LEADERBOARD_MUTATION, {
|
|
217
|
+
deckId,
|
|
218
|
+
variable: asString(params.variable, "variable", "leaderboard.save"),
|
|
219
|
+
score: asNumber(params.score, "score", "leaderboard.save"),
|
|
220
|
+
scope: asOptionalString(params.scope) ?? null,
|
|
221
|
+
}, "leaderboard.save");
|
|
222
|
+
return { ok: true };
|
|
223
|
+
}
|
|
224
|
+
function userGetCurrent(ctx) {
|
|
225
|
+
if (!ctx.userId || !ctx.username)
|
|
226
|
+
return { user: null };
|
|
227
|
+
return { user: { userId: ctx.userId, username: ctx.username } };
|
|
228
|
+
}
|
|
229
|
+
async function timeGetServerTime(gql) {
|
|
230
|
+
const data = await graphql(gql, SERVER_TIME_QUERY, {}, "time.getServerTime");
|
|
231
|
+
return {
|
|
232
|
+
timestamp: data.serverTime.timestamp,
|
|
233
|
+
timezoneOffset: data.serverTime.timezoneOffset,
|
|
234
|
+
castleEpochData: data.serverTime.castleEpochData,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
async function graphql(gql, query, variables, command) {
|
|
238
|
+
const result = await gql(query, variables);
|
|
239
|
+
if (result.errors && result.errors.length > 0) {
|
|
240
|
+
const first = result.errors[0];
|
|
241
|
+
const code = first?.extensions?.code;
|
|
242
|
+
throw new HostCommandError(typeof code === "string" ? code : "GRAPHQL_ERROR", first?.message ?? "Castle GraphQL request failed.", command, first?.extensions);
|
|
243
|
+
}
|
|
244
|
+
if (result.data === null || result.data === undefined) {
|
|
245
|
+
throw new HostCommandError("GRAPHQL_NO_DATA", "Castle GraphQL response did not include data.", command);
|
|
246
|
+
}
|
|
247
|
+
return result.data;
|
|
248
|
+
}
|
|
249
|
+
function requireDeckId(ctx, command) {
|
|
250
|
+
if (ctx.deckId)
|
|
251
|
+
return ctx.deckId;
|
|
252
|
+
throw new HostCommandError("MISSING_DECK_ID", "Save this deck to Castle before using this API.", command);
|
|
253
|
+
}
|
|
254
|
+
function requireUserId(ctx, command) {
|
|
255
|
+
if (ctx.userId)
|
|
256
|
+
return ctx.userId;
|
|
257
|
+
throw new HostCommandError("LOGIN_REQUIRED", "Log in to Castle before using this API.", command);
|
|
258
|
+
}
|
|
259
|
+
function toSerializedError(error, command) {
|
|
260
|
+
if (error instanceof HostCommandError) {
|
|
261
|
+
return {
|
|
262
|
+
code: error.code,
|
|
263
|
+
message: error.message,
|
|
264
|
+
command: error.command ?? command,
|
|
265
|
+
extensions: error.extensions,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
code: "CASTLE_HOST_ERROR",
|
|
270
|
+
message: error instanceof Error ? error.message : "Castle command failed.",
|
|
271
|
+
command,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function asUpdates(value) {
|
|
275
|
+
if (!Array.isArray(value))
|
|
276
|
+
return [];
|
|
277
|
+
return value.flatMap((entry) => {
|
|
278
|
+
if (typeof entry !== "object" || entry === null)
|
|
279
|
+
return [];
|
|
280
|
+
const record = entry;
|
|
281
|
+
if (typeof record.key !== "string")
|
|
282
|
+
return [];
|
|
283
|
+
const raw = record.value;
|
|
284
|
+
return [{ key: record.key, value: typeof raw === "string" ? raw : null }];
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
function asKeys(value) {
|
|
288
|
+
if (!Array.isArray(value))
|
|
289
|
+
return [];
|
|
290
|
+
return value.filter((key) => typeof key === "string");
|
|
291
|
+
}
|
|
292
|
+
function asScope(value, command) {
|
|
293
|
+
if (value === "deck" || value === "user")
|
|
294
|
+
return value;
|
|
295
|
+
throw new HostCommandError("CASTLE_STORAGE_INVALID_SCOPE", 'SharedStorage scope must be "deck" or "user".', command);
|
|
296
|
+
}
|
|
297
|
+
function asLeaderboardType(value) {
|
|
298
|
+
if (value === "high" || value === "low")
|
|
299
|
+
return value;
|
|
300
|
+
throw new HostCommandError("INVALID_LEADERBOARD_TYPE", "Leaderboard type must be high or low.", "leaderboard.fetch");
|
|
301
|
+
}
|
|
302
|
+
function asString(value, field, command) {
|
|
303
|
+
if (typeof value === "string" && value.length > 0)
|
|
304
|
+
return value;
|
|
305
|
+
throw new HostCommandError("INVALID_ARGUMENT", `Castle command ${command} requires ${field}.`, command);
|
|
306
|
+
}
|
|
307
|
+
function asNumber(value, field, command) {
|
|
308
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
309
|
+
return value;
|
|
310
|
+
throw new HostCommandError("INVALID_ARGUMENT", `Castle command ${command} requires a numeric ${field}.`, command);
|
|
311
|
+
}
|
|
312
|
+
function asOptionalString(value) {
|
|
313
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
314
|
+
}
|
|
315
|
+
function asOptionalNumber(value) {
|
|
316
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
317
|
+
}
|
|
318
|
+
const DECK_STORAGE_QUERY = `
|
|
319
|
+
query CastleDeckStorage($deckId: ID!, $sessionId: ID) {
|
|
320
|
+
deckStorage(deckId: $deckId, sessionId: $sessionId)
|
|
321
|
+
}
|
|
322
|
+
`;
|
|
323
|
+
const UPDATE_DECK_STORAGE_MUTATION = `
|
|
324
|
+
mutation CastleUpdateDeckStorage(
|
|
325
|
+
$deckId: ID!,
|
|
326
|
+
$updates: [DeckStorageUpdateInput!]!,
|
|
327
|
+
$sessionId: ID
|
|
328
|
+
) {
|
|
329
|
+
updateDeckStorage(deckId: $deckId, updates: $updates, sessionId: $sessionId)
|
|
330
|
+
}
|
|
331
|
+
`;
|
|
332
|
+
const SHARED_DECK_STORAGE_QUERY = `
|
|
333
|
+
query CastleSharedDeckStorage(
|
|
334
|
+
$deckId: ID!,
|
|
335
|
+
$keys: [String!]!,
|
|
336
|
+
$sessionId: ID,
|
|
337
|
+
$userId: ID
|
|
338
|
+
) {
|
|
339
|
+
sharedDeckStorage(deckId: $deckId, keys: $keys, sessionId: $sessionId, userId: $userId)
|
|
340
|
+
}
|
|
341
|
+
`;
|
|
342
|
+
const UPDATE_SHARED_DECK_STORAGE_MUTATION = `
|
|
343
|
+
mutation CastleUpdateSharedDeckStorage(
|
|
344
|
+
$deckId: ID!,
|
|
345
|
+
$updates: [SharedDeckStorageUpdateInput!]!,
|
|
346
|
+
$sessionId: ID,
|
|
347
|
+
$userId: ID
|
|
348
|
+
) {
|
|
349
|
+
updateSharedDeckStorage(
|
|
350
|
+
deckId: $deckId,
|
|
351
|
+
updates: $updates,
|
|
352
|
+
sessionId: $sessionId,
|
|
353
|
+
userId: $userId
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
`;
|
|
357
|
+
const LEADERBOARD_QUERY = `
|
|
358
|
+
query CastleLeaderboard(
|
|
359
|
+
$deckId: ID!
|
|
360
|
+
$variable: String!
|
|
361
|
+
$type: LeaderboardType!
|
|
362
|
+
$filter: LeaderboardFilter!
|
|
363
|
+
$includeFollowList: Boolean
|
|
364
|
+
$includeParties: Boolean
|
|
365
|
+
$scope: String
|
|
366
|
+
) {
|
|
367
|
+
leaderboard(
|
|
368
|
+
deckId: $deckId
|
|
369
|
+
variable: $variable
|
|
370
|
+
type: $type
|
|
371
|
+
filter: $filter
|
|
372
|
+
includeFollowList: $includeFollowList
|
|
373
|
+
includeParties: $includeParties
|
|
374
|
+
scope: $scope
|
|
375
|
+
) {
|
|
376
|
+
yourScore { score }
|
|
377
|
+
list {
|
|
378
|
+
place
|
|
379
|
+
score
|
|
380
|
+
user {
|
|
381
|
+
userId
|
|
382
|
+
username
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
`;
|
|
388
|
+
// Same args + selection as LEADERBOARD_QUERY, plus the required `score`. Unlike
|
|
389
|
+
// saveVariableToLeaderboard, this writes the score AND returns the post-write
|
|
390
|
+
// leaderboard in one round trip, so the deck can show its own fresh score
|
|
391
|
+
// without waiting for server-side settling. Mirrors the engine's leaderboardV2
|
|
392
|
+
// use in core/src/leaderboards.cpp.
|
|
393
|
+
const LEADERBOARD_V2_MUTATION = `
|
|
394
|
+
mutation CastleLeaderboardV2(
|
|
395
|
+
$deckId: ID!
|
|
396
|
+
$variable: String!
|
|
397
|
+
$type: LeaderboardType!
|
|
398
|
+
$filter: LeaderboardFilter!
|
|
399
|
+
$score: Float!
|
|
400
|
+
$includeFollowList: Boolean
|
|
401
|
+
$includeParties: Boolean
|
|
402
|
+
$scope: String
|
|
403
|
+
) {
|
|
404
|
+
leaderboardV2(
|
|
405
|
+
deckId: $deckId
|
|
406
|
+
variable: $variable
|
|
407
|
+
type: $type
|
|
408
|
+
filter: $filter
|
|
409
|
+
score: $score
|
|
410
|
+
includeFollowList: $includeFollowList
|
|
411
|
+
includeParties: $includeParties
|
|
412
|
+
scope: $scope
|
|
413
|
+
) {
|
|
414
|
+
yourScore { score }
|
|
415
|
+
list {
|
|
416
|
+
place
|
|
417
|
+
score
|
|
418
|
+
user {
|
|
419
|
+
userId
|
|
420
|
+
username
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
`;
|
|
426
|
+
const SAVE_LEADERBOARD_MUTATION = `
|
|
427
|
+
mutation CastleSaveVariableToLeaderboard(
|
|
428
|
+
$deckId: ID!
|
|
429
|
+
$variable: String!
|
|
430
|
+
$score: Float!
|
|
431
|
+
$scope: String
|
|
432
|
+
) {
|
|
433
|
+
saveVariableToLeaderboard(
|
|
434
|
+
deckId: $deckId
|
|
435
|
+
variable: $variable
|
|
436
|
+
score: $score
|
|
437
|
+
scope: $scope
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
`;
|
|
441
|
+
const PASSES_FOR_DECK_QUERY = `
|
|
442
|
+
query CastlePassesForDeck($deckId: ID!) {
|
|
443
|
+
passesForDeck(deckId: $deckId) {
|
|
444
|
+
passId
|
|
445
|
+
isActive
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
`;
|
|
449
|
+
const SERVER_TIME_QUERY = `
|
|
450
|
+
query CastleServerTime {
|
|
451
|
+
serverTime {
|
|
452
|
+
timestamp
|
|
453
|
+
timezoneOffset
|
|
454
|
+
castleEpochData
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
`;
|