castle-web-cli 0.4.59 → 0.4.60
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 +47 -0
- package/dist/castle-host/host.js +373 -0
- package/dist/chat-client.js +199 -96
- package/dist/ide.d.ts +3 -3
- package/dist/ide.js +92 -64
- package/dist/serve.js +42 -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,47 @@
|
|
|
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 interface SerializedCommandError {
|
|
30
|
+
code: string;
|
|
31
|
+
message: string;
|
|
32
|
+
command?: string;
|
|
33
|
+
extensions?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface HostResult {
|
|
37
|
+
ok: boolean;
|
|
38
|
+
data?: unknown;
|
|
39
|
+
error?: SerializedCommandError;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function executeCommand(
|
|
43
|
+
ctx: HostContext,
|
|
44
|
+
command: unknown,
|
|
45
|
+
params: unknown,
|
|
46
|
+
graphqlFetch: GraphqlFetch,
|
|
47
|
+
): Promise<HostResult>;
|
|
@@ -0,0 +1,373 @@
|
|
|
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
|
+
];
|
|
28
|
+
function isCommandName(value) {
|
|
29
|
+
return (typeof value === "string" &&
|
|
30
|
+
COMMAND_NAMES.includes(value));
|
|
31
|
+
}
|
|
32
|
+
class HostCommandError extends Error {
|
|
33
|
+
code;
|
|
34
|
+
command;
|
|
35
|
+
extensions;
|
|
36
|
+
constructor(code, message, command, extensions) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = "HostCommandError";
|
|
39
|
+
this.code = code;
|
|
40
|
+
this.command = command;
|
|
41
|
+
this.extensions = extensions;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export async function executeCommand(ctx, command, params, graphqlFetch) {
|
|
45
|
+
if (!isCommandName(command)) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
error: {
|
|
49
|
+
code: "UNKNOWN_COMMAND",
|
|
50
|
+
message: "Unknown Castle command.",
|
|
51
|
+
command: typeof command === "string" ? command : undefined,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const data = await runCommand(ctx, command, (params ?? {}), graphqlFetch);
|
|
57
|
+
return { ok: true, data };
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return { ok: false, error: toSerializedError(error, command) };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function runCommand(ctx, command, params, gql) {
|
|
64
|
+
switch (command) {
|
|
65
|
+
case "deckStorage.load":
|
|
66
|
+
return deckStorageLoad(ctx, gql);
|
|
67
|
+
case "deckStorage.update":
|
|
68
|
+
return deckStorageUpdate(ctx, params, gql);
|
|
69
|
+
case "sharedDeckStorage.load":
|
|
70
|
+
return sharedDeckStorageLoad(ctx, params, gql);
|
|
71
|
+
case "sharedDeckStorage.update":
|
|
72
|
+
return sharedDeckStorageUpdate(ctx, params, gql);
|
|
73
|
+
case "leaderboard.fetch":
|
|
74
|
+
return leaderboardFetch(ctx, params, gql);
|
|
75
|
+
case "leaderboard.save":
|
|
76
|
+
return leaderboardSave(ctx, params, gql);
|
|
77
|
+
case "user.getCurrent":
|
|
78
|
+
return Promise.resolve(userGetCurrent(ctx));
|
|
79
|
+
case "time.getServerTime":
|
|
80
|
+
return timeGetServerTime(gql);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function deckStorageLoad(ctx, gql) {
|
|
84
|
+
const deckId = requireDeckId(ctx, "deckStorage.load");
|
|
85
|
+
const data = await graphql(gql, DECK_STORAGE_QUERY, { deckId, sessionId: ctx.sessionId }, "deckStorage.load");
|
|
86
|
+
return { blob: data.deckStorage ?? {} };
|
|
87
|
+
}
|
|
88
|
+
async function deckStorageUpdate(ctx, params, gql) {
|
|
89
|
+
const deckId = requireDeckId(ctx, "deckStorage.update");
|
|
90
|
+
const data = await graphql(gql, UPDATE_DECK_STORAGE_MUTATION, {
|
|
91
|
+
deckId,
|
|
92
|
+
sessionId: ctx.sessionId,
|
|
93
|
+
updates: asUpdates(params.updates),
|
|
94
|
+
}, "deckStorage.update");
|
|
95
|
+
return { blob: data.updateDeckStorage ?? {} };
|
|
96
|
+
}
|
|
97
|
+
async function sharedDeckStorageLoad(ctx, params, gql) {
|
|
98
|
+
const deckId = requireDeckId(ctx, "sharedDeckStorage.load");
|
|
99
|
+
const scope = asScope(params.scope, "sharedDeckStorage.load");
|
|
100
|
+
// 'user' read: an explicit userId targets another player's public bucket;
|
|
101
|
+
// otherwise the current player's. 'deck' scope ignores userId.
|
|
102
|
+
const userId = scope === "user" ? asOptionalString(params.userId) ?? ctx.userId : null;
|
|
103
|
+
const data = await graphql(gql, SHARED_DECK_STORAGE_QUERY, { deckId, sessionId: ctx.sessionId, userId, keys: asKeys(params.keys) }, "sharedDeckStorage.load");
|
|
104
|
+
return { blob: data.sharedDeckStorage ?? {} };
|
|
105
|
+
}
|
|
106
|
+
async function sharedDeckStorageUpdate(ctx, params, gql) {
|
|
107
|
+
const deckId = requireDeckId(ctx, "sharedDeckStorage.update");
|
|
108
|
+
const scope = asScope(params.scope, "sharedDeckStorage.update");
|
|
109
|
+
// 'user'-scope writes are forced to the current player — the deck cannot
|
|
110
|
+
// write another player's bucket.
|
|
111
|
+
const userId = scope === "user" ? requireUserId(ctx, "sharedDeckStorage.update") : null;
|
|
112
|
+
await graphql(gql, UPDATE_SHARED_DECK_STORAGE_MUTATION, { deckId, sessionId: ctx.sessionId, userId, updates: asUpdates(params.updates) }, "sharedDeckStorage.update");
|
|
113
|
+
return { ok: true };
|
|
114
|
+
}
|
|
115
|
+
async function leaderboardFetch(ctx, params, gql) {
|
|
116
|
+
const deckId = requireDeckId(ctx, "leaderboard.fetch");
|
|
117
|
+
const variables = {
|
|
118
|
+
deckId,
|
|
119
|
+
variable: asString(params.variable, "variable", "leaderboard.fetch"),
|
|
120
|
+
type: asLeaderboardType(params.type),
|
|
121
|
+
filter: "dedupUsers",
|
|
122
|
+
includeFollowList: false,
|
|
123
|
+
includeParties: false,
|
|
124
|
+
scope: asOptionalString(params.scope) ?? null,
|
|
125
|
+
};
|
|
126
|
+
// A non-null score means the deck just wrote this value and wants its own
|
|
127
|
+
// score reflected immediately: write-and-read atomically via leaderboardV2
|
|
128
|
+
// (mirrors the engine's getLeaderboard path in core/src/leaderboards.cpp).
|
|
129
|
+
// No score → plain read of the settled leaderboard.
|
|
130
|
+
const score = asOptionalNumber(params.score);
|
|
131
|
+
if (score !== null) {
|
|
132
|
+
const data = await graphql(gql, LEADERBOARD_V2_MUTATION, { ...variables, score }, "leaderboard.fetch");
|
|
133
|
+
return { leaderboard: data.leaderboardV2, currentUserId: ctx.userId };
|
|
134
|
+
}
|
|
135
|
+
const data = await graphql(gql, LEADERBOARD_QUERY, variables, "leaderboard.fetch");
|
|
136
|
+
return { leaderboard: data.leaderboard, currentUserId: ctx.userId };
|
|
137
|
+
}
|
|
138
|
+
async function leaderboardSave(ctx, params, gql) {
|
|
139
|
+
const deckId = requireDeckId(ctx, "leaderboard.save");
|
|
140
|
+
await graphql(gql, SAVE_LEADERBOARD_MUTATION, {
|
|
141
|
+
deckId,
|
|
142
|
+
variable: asString(params.variable, "variable", "leaderboard.save"),
|
|
143
|
+
score: asNumber(params.score, "score", "leaderboard.save"),
|
|
144
|
+
scope: asOptionalString(params.scope) ?? null,
|
|
145
|
+
}, "leaderboard.save");
|
|
146
|
+
return { ok: true };
|
|
147
|
+
}
|
|
148
|
+
function userGetCurrent(ctx) {
|
|
149
|
+
if (!ctx.userId || !ctx.username)
|
|
150
|
+
return { user: null };
|
|
151
|
+
return { user: { userId: ctx.userId, username: ctx.username } };
|
|
152
|
+
}
|
|
153
|
+
async function timeGetServerTime(gql) {
|
|
154
|
+
const data = await graphql(gql, SERVER_TIME_QUERY, {}, "time.getServerTime");
|
|
155
|
+
return {
|
|
156
|
+
timestamp: data.serverTime.timestamp,
|
|
157
|
+
timezoneOffset: data.serverTime.timezoneOffset,
|
|
158
|
+
castleEpochData: data.serverTime.castleEpochData,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
async function graphql(gql, query, variables, command) {
|
|
162
|
+
const result = await gql(query, variables);
|
|
163
|
+
if (result.errors && result.errors.length > 0) {
|
|
164
|
+
const first = result.errors[0];
|
|
165
|
+
const code = first?.extensions?.code;
|
|
166
|
+
throw new HostCommandError(typeof code === "string" ? code : "GRAPHQL_ERROR", first?.message ?? "Castle GraphQL request failed.", command, first?.extensions);
|
|
167
|
+
}
|
|
168
|
+
if (result.data === null || result.data === undefined) {
|
|
169
|
+
throw new HostCommandError("GRAPHQL_NO_DATA", "Castle GraphQL response did not include data.", command);
|
|
170
|
+
}
|
|
171
|
+
return result.data;
|
|
172
|
+
}
|
|
173
|
+
function requireDeckId(ctx, command) {
|
|
174
|
+
if (ctx.deckId)
|
|
175
|
+
return ctx.deckId;
|
|
176
|
+
throw new HostCommandError("MISSING_DECK_ID", "Save this deck to Castle before using this API.", command);
|
|
177
|
+
}
|
|
178
|
+
function requireUserId(ctx, command) {
|
|
179
|
+
if (ctx.userId)
|
|
180
|
+
return ctx.userId;
|
|
181
|
+
throw new HostCommandError("LOGIN_REQUIRED", "Log in to Castle before using this API.", command);
|
|
182
|
+
}
|
|
183
|
+
function toSerializedError(error, command) {
|
|
184
|
+
if (error instanceof HostCommandError) {
|
|
185
|
+
return {
|
|
186
|
+
code: error.code,
|
|
187
|
+
message: error.message,
|
|
188
|
+
command: error.command ?? command,
|
|
189
|
+
extensions: error.extensions,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
code: "CASTLE_HOST_ERROR",
|
|
194
|
+
message: error instanceof Error ? error.message : "Castle command failed.",
|
|
195
|
+
command,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function asUpdates(value) {
|
|
199
|
+
if (!Array.isArray(value))
|
|
200
|
+
return [];
|
|
201
|
+
return value.flatMap((entry) => {
|
|
202
|
+
if (typeof entry !== "object" || entry === null)
|
|
203
|
+
return [];
|
|
204
|
+
const record = entry;
|
|
205
|
+
if (typeof record.key !== "string")
|
|
206
|
+
return [];
|
|
207
|
+
const raw = record.value;
|
|
208
|
+
return [{ key: record.key, value: typeof raw === "string" ? raw : null }];
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function asKeys(value) {
|
|
212
|
+
if (!Array.isArray(value))
|
|
213
|
+
return [];
|
|
214
|
+
return value.filter((key) => typeof key === "string");
|
|
215
|
+
}
|
|
216
|
+
function asScope(value, command) {
|
|
217
|
+
if (value === "deck" || value === "user")
|
|
218
|
+
return value;
|
|
219
|
+
throw new HostCommandError("CASTLE_STORAGE_INVALID_SCOPE", 'SharedStorage scope must be "deck" or "user".', command);
|
|
220
|
+
}
|
|
221
|
+
function asLeaderboardType(value) {
|
|
222
|
+
if (value === "high" || value === "low")
|
|
223
|
+
return value;
|
|
224
|
+
throw new HostCommandError("INVALID_LEADERBOARD_TYPE", "Leaderboard type must be high or low.", "leaderboard.fetch");
|
|
225
|
+
}
|
|
226
|
+
function asString(value, field, command) {
|
|
227
|
+
if (typeof value === "string" && value.length > 0)
|
|
228
|
+
return value;
|
|
229
|
+
throw new HostCommandError("INVALID_ARGUMENT", `Castle command ${command} requires ${field}.`, command);
|
|
230
|
+
}
|
|
231
|
+
function asNumber(value, field, command) {
|
|
232
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
233
|
+
return value;
|
|
234
|
+
throw new HostCommandError("INVALID_ARGUMENT", `Castle command ${command} requires a numeric ${field}.`, command);
|
|
235
|
+
}
|
|
236
|
+
function asOptionalString(value) {
|
|
237
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
238
|
+
}
|
|
239
|
+
function asOptionalNumber(value) {
|
|
240
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
241
|
+
}
|
|
242
|
+
const DECK_STORAGE_QUERY = `
|
|
243
|
+
query CastleDeckStorage($deckId: ID!, $sessionId: ID) {
|
|
244
|
+
deckStorage(deckId: $deckId, sessionId: $sessionId)
|
|
245
|
+
}
|
|
246
|
+
`;
|
|
247
|
+
const UPDATE_DECK_STORAGE_MUTATION = `
|
|
248
|
+
mutation CastleUpdateDeckStorage(
|
|
249
|
+
$deckId: ID!,
|
|
250
|
+
$updates: [DeckStorageUpdateInput!]!,
|
|
251
|
+
$sessionId: ID
|
|
252
|
+
) {
|
|
253
|
+
updateDeckStorage(deckId: $deckId, updates: $updates, sessionId: $sessionId)
|
|
254
|
+
}
|
|
255
|
+
`;
|
|
256
|
+
const SHARED_DECK_STORAGE_QUERY = `
|
|
257
|
+
query CastleSharedDeckStorage(
|
|
258
|
+
$deckId: ID!,
|
|
259
|
+
$keys: [String!]!,
|
|
260
|
+
$sessionId: ID,
|
|
261
|
+
$userId: ID
|
|
262
|
+
) {
|
|
263
|
+
sharedDeckStorage(deckId: $deckId, keys: $keys, sessionId: $sessionId, userId: $userId)
|
|
264
|
+
}
|
|
265
|
+
`;
|
|
266
|
+
const UPDATE_SHARED_DECK_STORAGE_MUTATION = `
|
|
267
|
+
mutation CastleUpdateSharedDeckStorage(
|
|
268
|
+
$deckId: ID!,
|
|
269
|
+
$updates: [SharedDeckStorageUpdateInput!]!,
|
|
270
|
+
$sessionId: ID,
|
|
271
|
+
$userId: ID
|
|
272
|
+
) {
|
|
273
|
+
updateSharedDeckStorage(
|
|
274
|
+
deckId: $deckId,
|
|
275
|
+
updates: $updates,
|
|
276
|
+
sessionId: $sessionId,
|
|
277
|
+
userId: $userId
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
`;
|
|
281
|
+
const LEADERBOARD_QUERY = `
|
|
282
|
+
query CastleLeaderboard(
|
|
283
|
+
$deckId: ID!
|
|
284
|
+
$variable: String!
|
|
285
|
+
$type: LeaderboardType!
|
|
286
|
+
$filter: LeaderboardFilter!
|
|
287
|
+
$includeFollowList: Boolean
|
|
288
|
+
$includeParties: Boolean
|
|
289
|
+
$scope: String
|
|
290
|
+
) {
|
|
291
|
+
leaderboard(
|
|
292
|
+
deckId: $deckId
|
|
293
|
+
variable: $variable
|
|
294
|
+
type: $type
|
|
295
|
+
filter: $filter
|
|
296
|
+
includeFollowList: $includeFollowList
|
|
297
|
+
includeParties: $includeParties
|
|
298
|
+
scope: $scope
|
|
299
|
+
) {
|
|
300
|
+
yourScore { score }
|
|
301
|
+
list {
|
|
302
|
+
place
|
|
303
|
+
score
|
|
304
|
+
user {
|
|
305
|
+
userId
|
|
306
|
+
username
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
`;
|
|
312
|
+
// Same args + selection as LEADERBOARD_QUERY, plus the required `score`. Unlike
|
|
313
|
+
// saveVariableToLeaderboard, this writes the score AND returns the post-write
|
|
314
|
+
// leaderboard in one round trip, so the deck can show its own fresh score
|
|
315
|
+
// without waiting for server-side settling. Mirrors the engine's leaderboardV2
|
|
316
|
+
// use in core/src/leaderboards.cpp.
|
|
317
|
+
const LEADERBOARD_V2_MUTATION = `
|
|
318
|
+
mutation CastleLeaderboardV2(
|
|
319
|
+
$deckId: ID!
|
|
320
|
+
$variable: String!
|
|
321
|
+
$type: LeaderboardType!
|
|
322
|
+
$filter: LeaderboardFilter!
|
|
323
|
+
$score: Float!
|
|
324
|
+
$includeFollowList: Boolean
|
|
325
|
+
$includeParties: Boolean
|
|
326
|
+
$scope: String
|
|
327
|
+
) {
|
|
328
|
+
leaderboardV2(
|
|
329
|
+
deckId: $deckId
|
|
330
|
+
variable: $variable
|
|
331
|
+
type: $type
|
|
332
|
+
filter: $filter
|
|
333
|
+
score: $score
|
|
334
|
+
includeFollowList: $includeFollowList
|
|
335
|
+
includeParties: $includeParties
|
|
336
|
+
scope: $scope
|
|
337
|
+
) {
|
|
338
|
+
yourScore { score }
|
|
339
|
+
list {
|
|
340
|
+
place
|
|
341
|
+
score
|
|
342
|
+
user {
|
|
343
|
+
userId
|
|
344
|
+
username
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
`;
|
|
350
|
+
const SAVE_LEADERBOARD_MUTATION = `
|
|
351
|
+
mutation CastleSaveVariableToLeaderboard(
|
|
352
|
+
$deckId: ID!
|
|
353
|
+
$variable: String!
|
|
354
|
+
$score: Float!
|
|
355
|
+
$scope: String
|
|
356
|
+
) {
|
|
357
|
+
saveVariableToLeaderboard(
|
|
358
|
+
deckId: $deckId
|
|
359
|
+
variable: $variable
|
|
360
|
+
score: $score
|
|
361
|
+
scope: $scope
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
`;
|
|
365
|
+
const SERVER_TIME_QUERY = `
|
|
366
|
+
query CastleServerTime {
|
|
367
|
+
serverTime {
|
|
368
|
+
timestamp
|
|
369
|
+
timezoneOffset
|
|
370
|
+
castleEpochData
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
`;
|