@playcademy/sdk 0.0.1-beta.3 → 0.0.1-beta.30

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/index.js ADDED
@@ -0,0 +1,1924 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, {
5
+ get: all[name],
6
+ enumerable: true,
7
+ configurable: true,
8
+ set: (newValue) => all[name] = () => newValue
9
+ });
10
+ };
11
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
12
+
13
+ // ../logger/src/index.ts
14
+ var isBrowser = () => {
15
+ return typeof window !== "undefined" && typeof document !== "undefined";
16
+ }, colors, getLevelColor = (level) => {
17
+ switch (level) {
18
+ case "debug":
19
+ return colors.blue;
20
+ case "info":
21
+ return colors.cyan;
22
+ case "warn":
23
+ return colors.yellow;
24
+ case "error":
25
+ return colors.red;
26
+ default:
27
+ return colors.reset;
28
+ }
29
+ }, logInBrowser = (level, message, context) => {
30
+ const timestamp = new Date().toISOString();
31
+ const levelUpper = level.toUpperCase();
32
+ const consoleMethod = getConsoleMethod(level);
33
+ if (context && Object.keys(context).length > 0) {
34
+ consoleMethod(`[${timestamp}] ${levelUpper}`, message, context);
35
+ } else {
36
+ consoleMethod(`[${timestamp}] ${levelUpper}`, message);
37
+ }
38
+ }, logOnServer = (level, message, context) => {
39
+ const consoleMethod = getConsoleMethod(level);
40
+ if (true) {
41
+ const timestamp = new Date().toISOString();
42
+ const levelColor = getLevelColor(level);
43
+ const levelUpper = level.toUpperCase().padEnd(5);
44
+ const coloredPrefix = `${colors.dim}[${timestamp}]${colors.reset} ${levelColor}${levelUpper}${colors.reset}`;
45
+ if (context && Object.keys(context).length > 0) {
46
+ consoleMethod(`${coloredPrefix} ${message}`, context);
47
+ } else {
48
+ consoleMethod(`${coloredPrefix} ${message}`);
49
+ }
50
+ } else {}
51
+ }, getConsoleMethod = (level) => {
52
+ switch (level) {
53
+ case "debug":
54
+ return console.debug;
55
+ case "info":
56
+ return console.info;
57
+ case "warn":
58
+ return console.warn;
59
+ case "error":
60
+ return console.error;
61
+ default:
62
+ return console.log;
63
+ }
64
+ }, performLog = (level, message, context) => {
65
+ if (level === "debug" && false) {}
66
+ if (isBrowser()) {
67
+ logInBrowser(level, message, context);
68
+ } else {
69
+ logOnServer(level, message, context);
70
+ }
71
+ }, createLogger = () => {
72
+ return {
73
+ debug: (message, context) => performLog("debug", message, context),
74
+ info: (message, context) => performLog("info", message, context),
75
+ warn: (message, context) => performLog("warn", message, context),
76
+ error: (message, context) => performLog("error", message, context),
77
+ log: performLog
78
+ };
79
+ }, log;
80
+ var init_src = __esm(() => {
81
+ colors = {
82
+ reset: "\x1B[0m",
83
+ dim: "\x1B[2m",
84
+ red: "\x1B[31m",
85
+ yellow: "\x1B[33m",
86
+ blue: "\x1B[34m",
87
+ cyan: "\x1B[36m",
88
+ gray: "\x1B[90m"
89
+ };
90
+ log = createLogger();
91
+ });
92
+
93
+ // src/core/errors.ts
94
+ var PlaycademyError, ApiError;
95
+ var init_errors = __esm(() => {
96
+ PlaycademyError = class PlaycademyError extends Error {
97
+ constructor(message) {
98
+ super(message);
99
+ this.name = "PlaycademyError";
100
+ }
101
+ };
102
+ ApiError = class ApiError extends Error {
103
+ status;
104
+ details;
105
+ constructor(status, message, details) {
106
+ super(`${status} ${message}`);
107
+ this.status = status;
108
+ this.details = details;
109
+ Object.setPrototypeOf(this, ApiError.prototype);
110
+ }
111
+ };
112
+ });
113
+
114
+ // src/core/namespaces/auth.ts
115
+ function createAuthNamespace(client) {
116
+ return {
117
+ logout: async () => {
118
+ await client["request"](`/auth/logout`, "DELETE");
119
+ client.setToken(null);
120
+ }
121
+ };
122
+ }
123
+
124
+ // src/messaging.ts
125
+ class PlaycademyMessaging {
126
+ listeners = new Map;
127
+ send(type, payload, options) {
128
+ if (options?.target) {
129
+ this.sendViaPostMessage(type, payload, options.target, options.origin || "*");
130
+ return;
131
+ }
132
+ const context = this.getMessagingContext(type);
133
+ if (context.shouldUsePostMessage) {
134
+ this.sendViaPostMessage(type, payload, context.target, context.origin);
135
+ } else {
136
+ this.sendViaCustomEvent(type, payload);
137
+ }
138
+ }
139
+ listen(type, handler) {
140
+ const postMessageListener = (event) => {
141
+ const messageEvent = event;
142
+ if (messageEvent.data?.type === type) {
143
+ handler(messageEvent.data.payload || messageEvent.data);
144
+ }
145
+ };
146
+ const customEventListener = (event) => {
147
+ handler(event.detail);
148
+ };
149
+ if (!this.listeners.has(type)) {
150
+ this.listeners.set(type, new Map);
151
+ }
152
+ const listenerMap = this.listeners.get(type);
153
+ listenerMap.set(handler, {
154
+ postMessage: postMessageListener,
155
+ customEvent: customEventListener
156
+ });
157
+ window.addEventListener("message", postMessageListener);
158
+ window.addEventListener(type, customEventListener);
159
+ }
160
+ unlisten(type, handler) {
161
+ const typeListeners = this.listeners.get(type);
162
+ if (!typeListeners || !typeListeners.has(handler)) {
163
+ return;
164
+ }
165
+ const listeners = typeListeners.get(handler);
166
+ window.removeEventListener("message", listeners.postMessage);
167
+ window.removeEventListener(type, listeners.customEvent);
168
+ typeListeners.delete(handler);
169
+ if (typeListeners.size === 0) {
170
+ this.listeners.delete(type);
171
+ }
172
+ }
173
+ getMessagingContext(eventType) {
174
+ const isIframe = typeof window !== "undefined" && window.self !== window.top;
175
+ const iframeToParentEvents = [
176
+ "PLAYCADEMY_READY" /* READY */,
177
+ "PLAYCADEMY_EXIT" /* EXIT */,
178
+ "PLAYCADEMY_TELEMETRY" /* TELEMETRY */,
179
+ "PLAYCADEMY_KEY_EVENT" /* KEY_EVENT */
180
+ ];
181
+ const shouldUsePostMessage = isIframe && iframeToParentEvents.includes(eventType);
182
+ return {
183
+ shouldUsePostMessage,
184
+ target: shouldUsePostMessage ? window.parent : undefined,
185
+ origin: "*"
186
+ };
187
+ }
188
+ sendViaPostMessage(type, payload, target = window.parent, origin = "*") {
189
+ const messageData = { type };
190
+ if (payload !== undefined) {
191
+ messageData.payload = payload;
192
+ }
193
+ target.postMessage(messageData, origin);
194
+ }
195
+ sendViaCustomEvent(type, payload) {
196
+ window.dispatchEvent(new CustomEvent(type, { detail: payload }));
197
+ }
198
+ }
199
+ var MessageEvents, messaging;
200
+ var init_messaging = __esm(() => {
201
+ ((MessageEvents2) => {
202
+ MessageEvents2["INIT"] = "PLAYCADEMY_INIT";
203
+ MessageEvents2["TOKEN_REFRESH"] = "PLAYCADEMY_TOKEN_REFRESH";
204
+ MessageEvents2["PAUSE"] = "PLAYCADEMY_PAUSE";
205
+ MessageEvents2["RESUME"] = "PLAYCADEMY_RESUME";
206
+ MessageEvents2["FORCE_EXIT"] = "PLAYCADEMY_FORCE_EXIT";
207
+ MessageEvents2["OVERLAY"] = "PLAYCADEMY_OVERLAY";
208
+ MessageEvents2["READY"] = "PLAYCADEMY_READY";
209
+ MessageEvents2["EXIT"] = "PLAYCADEMY_EXIT";
210
+ MessageEvents2["TELEMETRY"] = "PLAYCADEMY_TELEMETRY";
211
+ MessageEvents2["KEY_EVENT"] = "PLAYCADEMY_KEY_EVENT";
212
+ })(MessageEvents ||= {});
213
+ messaging = new PlaycademyMessaging;
214
+ });
215
+
216
+ // src/core/namespaces/runtime.ts
217
+ function createRuntimeNamespace(client) {
218
+ const eventListeners = new Map;
219
+ const trackListener = (eventType, handler) => {
220
+ if (!eventListeners.has(eventType)) {
221
+ eventListeners.set(eventType, new Set);
222
+ }
223
+ eventListeners.get(eventType).add(handler);
224
+ };
225
+ const untrackListener = (eventType, handler) => {
226
+ const listeners = eventListeners.get(eventType);
227
+ if (listeners) {
228
+ listeners.delete(handler);
229
+ if (listeners.size === 0) {
230
+ eventListeners.delete(eventType);
231
+ }
232
+ }
233
+ };
234
+ if (typeof window !== "undefined" && window.self !== window.top) {
235
+ const playcademyConfig = window.PLAYCADEMY;
236
+ const forwardKeys = Array.isArray(playcademyConfig?.forwardKeys) ? playcademyConfig.forwardKeys : ["Escape"];
237
+ const keySet = new Set(forwardKeys.map((k) => k.toLowerCase()));
238
+ const keyListener = (event) => {
239
+ if (keySet.has(event.key.toLowerCase()) || keySet.has(event.code.toLowerCase())) {
240
+ messaging.send("PLAYCADEMY_KEY_EVENT" /* KEY_EVENT */, {
241
+ key: event.key,
242
+ code: event.code,
243
+ type: event.type
244
+ });
245
+ }
246
+ };
247
+ window.addEventListener("keydown", keyListener);
248
+ window.addEventListener("keyup", keyListener);
249
+ trackListener("PLAYCADEMY_FORCE_EXIT" /* FORCE_EXIT */, () => {
250
+ window.removeEventListener("keydown", keyListener);
251
+ window.removeEventListener("keyup", keyListener);
252
+ });
253
+ }
254
+ return {
255
+ getGameToken: async (gameId, options) => {
256
+ const res = await client["request"](`/games/${gameId}/token`, "POST");
257
+ if (options?.apply) {
258
+ client.setToken(res.token);
259
+ }
260
+ return res;
261
+ },
262
+ exit: async () => {
263
+ if (client["internalClientSessionId"] && client["gameId"]) {
264
+ try {
265
+ await client.games.endSession(client["internalClientSessionId"], client["gameId"]);
266
+ } catch (error) {
267
+ log.error("[Playcademy SDK] Failed to auto-end session:", {
268
+ sessionId: client["internalClientSessionId"],
269
+ error
270
+ });
271
+ }
272
+ }
273
+ messaging.send("PLAYCADEMY_EXIT" /* EXIT */, undefined);
274
+ },
275
+ onInit: (handler) => {
276
+ messaging.listen("PLAYCADEMY_INIT" /* INIT */, handler);
277
+ trackListener("PLAYCADEMY_INIT" /* INIT */, handler);
278
+ },
279
+ onTokenRefresh: (handler) => {
280
+ messaging.listen("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, handler);
281
+ trackListener("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, handler);
282
+ },
283
+ onPause: (handler) => {
284
+ messaging.listen("PLAYCADEMY_PAUSE" /* PAUSE */, handler);
285
+ trackListener("PLAYCADEMY_PAUSE" /* PAUSE */, handler);
286
+ },
287
+ onResume: (handler) => {
288
+ messaging.listen("PLAYCADEMY_RESUME" /* RESUME */, handler);
289
+ trackListener("PLAYCADEMY_RESUME" /* RESUME */, handler);
290
+ },
291
+ onForceExit: (handler) => {
292
+ messaging.listen("PLAYCADEMY_FORCE_EXIT" /* FORCE_EXIT */, handler);
293
+ trackListener("PLAYCADEMY_FORCE_EXIT" /* FORCE_EXIT */, handler);
294
+ },
295
+ onOverlay: (handler) => {
296
+ messaging.listen("PLAYCADEMY_OVERLAY" /* OVERLAY */, handler);
297
+ trackListener("PLAYCADEMY_OVERLAY" /* OVERLAY */, handler);
298
+ },
299
+ ready: () => {
300
+ messaging.send("PLAYCADEMY_READY" /* READY */, undefined);
301
+ },
302
+ sendTelemetry: (data) => {
303
+ messaging.send("PLAYCADEMY_TELEMETRY" /* TELEMETRY */, data);
304
+ },
305
+ removeListener: (eventType, handler) => {
306
+ messaging.unlisten(eventType, handler);
307
+ untrackListener(eventType, handler);
308
+ },
309
+ removeAllListeners: () => {
310
+ for (const [eventType, handlers] of eventListeners.entries()) {
311
+ for (const handler of handlers) {
312
+ messaging.unlisten(eventType, handler);
313
+ }
314
+ }
315
+ eventListeners.clear();
316
+ },
317
+ getListenerCounts: () => {
318
+ const counts = {};
319
+ for (const [eventType, handlers] of eventListeners.entries()) {
320
+ counts[eventType] = handlers.size;
321
+ }
322
+ return counts;
323
+ }
324
+ };
325
+ }
326
+ var init_runtime = __esm(() => {
327
+ init_src();
328
+ init_messaging();
329
+ });
330
+
331
+ // src/core/request.ts
332
+ async function request({
333
+ path,
334
+ baseUrl,
335
+ token,
336
+ method = "GET",
337
+ body,
338
+ extraHeaders = {}
339
+ }) {
340
+ const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
341
+ const headers = { ...extraHeaders };
342
+ let payload;
343
+ if (body instanceof FormData) {
344
+ payload = body;
345
+ } else if (body !== undefined && body !== null) {
346
+ payload = JSON.stringify(body);
347
+ headers["Content-Type"] = "application/json";
348
+ }
349
+ if (token)
350
+ headers["Authorization"] = `Bearer ${token}`;
351
+ const res = await fetch(url, {
352
+ method,
353
+ headers,
354
+ body: payload,
355
+ credentials: "omit"
356
+ });
357
+ if (!res.ok) {
358
+ const clonedRes = res.clone();
359
+ const errorBody = await clonedRes.json().catch(() => clonedRes.text().catch(() => {
360
+ return;
361
+ })) ?? undefined;
362
+ throw new ApiError(res.status, res.statusText, errorBody);
363
+ }
364
+ if (res.status === 204)
365
+ return;
366
+ const contentType = res.headers.get("content-type") ?? "";
367
+ if (contentType.includes("application/json")) {
368
+ return await res.json();
369
+ }
370
+ return await res.text();
371
+ }
372
+ async function fetchManifest(assetBundleBase) {
373
+ const manifestUrl = `${assetBundleBase.replace(/\/$/, "")}/playcademy.manifest.json`;
374
+ try {
375
+ const response = await fetch(manifestUrl);
376
+ if (!response.ok) {
377
+ log.error(`[fetchManifest] Failed to fetch manifest from ${manifestUrl}. Status: ${response.status}`);
378
+ throw new PlaycademyError(`Failed to fetch manifest: ${response.status} ${response.statusText}`);
379
+ }
380
+ return await response.json();
381
+ } catch (error) {
382
+ if (error instanceof PlaycademyError) {
383
+ throw error;
384
+ }
385
+ log.error(`[Playcademy SDK] Error fetching or parsing manifest from ${manifestUrl}:`, {
386
+ error
387
+ });
388
+ throw new PlaycademyError("Failed to load or parse game manifest");
389
+ }
390
+ }
391
+ var init_request = __esm(() => {
392
+ init_src();
393
+ init_errors();
394
+ });
395
+
396
+ // src/core/namespaces/games.ts
397
+ function createGamesNamespace(client) {
398
+ return {
399
+ fetch: async (gameIdOrSlug) => {
400
+ const baseGameData = await client["request"](`/games/${gameIdOrSlug}`, "GET");
401
+ const manifestData = await fetchManifest(baseGameData.assetBundleBase);
402
+ return {
403
+ ...baseGameData,
404
+ manifest: manifestData
405
+ };
406
+ },
407
+ list: () => client["request"]("/games", "GET"),
408
+ saveState: async (state) => {
409
+ const gameId = client["_ensureGameId"]();
410
+ await client["request"](`/games/${gameId}/state`, "POST", state);
411
+ },
412
+ loadState: async () => {
413
+ const gameId = client["_ensureGameId"]();
414
+ return client["request"](`/games/${gameId}/state`, "GET");
415
+ },
416
+ startSession: async (gameId) => {
417
+ const idToUse = gameId ?? client["_ensureGameId"]();
418
+ return client["request"](`/games/${idToUse}/sessions`, "POST", {});
419
+ },
420
+ endSession: async (sessionId, gameId) => {
421
+ const effectiveGameIdToEnd = gameId ?? client["_ensureGameId"]();
422
+ if (client["internalClientSessionId"] && sessionId === client["internalClientSessionId"] && effectiveGameIdToEnd === client["gameId"]) {
423
+ client["internalClientSessionId"] = undefined;
424
+ }
425
+ await client["request"](`/games/${effectiveGameIdToEnd}/sessions/${sessionId}/end`, "POST");
426
+ },
427
+ token: {
428
+ create: async (gameId, options) => {
429
+ const res = await client["request"](`/games/${gameId}/token`, "POST");
430
+ if (options?.apply) {
431
+ client.setToken(res.token);
432
+ }
433
+ return res;
434
+ }
435
+ },
436
+ leaderboard: {
437
+ get: async (gameId, options) => {
438
+ const params = new URLSearchParams;
439
+ if (options?.limit)
440
+ params.append("limit", String(options.limit));
441
+ if (options?.offset)
442
+ params.append("offset", String(options.offset));
443
+ const queryString = params.toString();
444
+ const path = queryString ? `/games/${gameId}/leaderboard?${queryString}` : `/games/${gameId}/leaderboard`;
445
+ return client["request"](path, "GET");
446
+ }
447
+ }
448
+ };
449
+ }
450
+ var init_games = __esm(() => {
451
+ init_request();
452
+ });
453
+
454
+ // src/core/namespaces/users.ts
455
+ function createUsersNamespace(client) {
456
+ const itemIdCache = new Map;
457
+ const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
458
+ const resolveItemId = async (identifier) => {
459
+ if (UUID_REGEX.test(identifier))
460
+ return identifier;
461
+ const gameId = client["gameId"];
462
+ const cacheKey = gameId ? `${identifier}:${gameId}` : identifier;
463
+ if (itemIdCache.has(cacheKey)) {
464
+ return itemIdCache.get(cacheKey);
465
+ }
466
+ const queryParams = new URLSearchParams({ slug: identifier });
467
+ if (gameId)
468
+ queryParams.append("gameId", gameId);
469
+ const item = await client["request"](`/items/resolve?${queryParams.toString()}`, "GET");
470
+ itemIdCache.set(cacheKey, item.id);
471
+ return item.id;
472
+ };
473
+ return {
474
+ me: async () => {
475
+ return client["request"]("/users/me", "GET");
476
+ },
477
+ inventory: {
478
+ get: async () => client["request"](`/inventory`, "GET"),
479
+ add: async (identifier, qty) => {
480
+ const itemId = await resolveItemId(identifier);
481
+ const res = await client["request"](`/inventory/add`, "POST", { itemId, qty });
482
+ client["emit"]("inventoryChange", {
483
+ itemId,
484
+ delta: qty,
485
+ newTotal: res.newTotal
486
+ });
487
+ return res;
488
+ },
489
+ remove: async (identifier, qty) => {
490
+ const itemId = await resolveItemId(identifier);
491
+ const res = await client["request"](`/inventory/remove`, "POST", { itemId, qty });
492
+ client["emit"]("inventoryChange", {
493
+ itemId,
494
+ delta: -qty,
495
+ newTotal: res.newTotal
496
+ });
497
+ return res;
498
+ },
499
+ quantity: async (identifier) => {
500
+ const itemId = await resolveItemId(identifier);
501
+ const inventory = await client["request"](`/inventory`, "GET");
502
+ const item = inventory.find((inv) => inv.item?.id === itemId);
503
+ return item?.quantity ?? 0;
504
+ },
505
+ has: async (identifier, minQuantity = 1) => {
506
+ const itemId = await resolveItemId(identifier);
507
+ const inventory = await client["request"](`/inventory`, "GET");
508
+ const item = inventory.find((inv) => inv.item?.id === itemId);
509
+ const qty = item?.quantity ?? 0;
510
+ return qty >= minQuantity;
511
+ }
512
+ },
513
+ scores: {
514
+ get: async (userIdOrOptions, options) => {
515
+ let userId;
516
+ let queryOptions;
517
+ if (typeof userIdOrOptions === "string") {
518
+ userId = userIdOrOptions;
519
+ queryOptions = options || {};
520
+ } else {
521
+ queryOptions = userIdOrOptions || {};
522
+ const user = await client["request"]("/users/me", "GET");
523
+ userId = user.id;
524
+ }
525
+ const params = new URLSearchParams({
526
+ limit: String(queryOptions.limit || 50)
527
+ });
528
+ if (queryOptions.gameId) {
529
+ params.append("gameId", queryOptions.gameId);
530
+ }
531
+ return client["request"](`/users/${userId}/scores?${params}`, "GET");
532
+ }
533
+ }
534
+ };
535
+ }
536
+
537
+ // src/core/namespaces/dev.ts
538
+ function createDevNamespace(client) {
539
+ return {
540
+ auth: {
541
+ applyForDeveloper: () => client["request"]("/dev/apply", "POST"),
542
+ getStatus: async () => {
543
+ const response = await client["request"]("/dev/status", "GET");
544
+ return response.status;
545
+ }
546
+ },
547
+ games: {
548
+ upsert: async (slug, metadata, file) => {
549
+ const game = await client["request"](`/games/${slug}`, "PUT", metadata);
550
+ const fileName = file instanceof File ? file.name : "game.zip";
551
+ const initiateResponse = await client["request"]("/games/uploads/initiate/", "POST", {
552
+ fileName,
553
+ gameId: game.id
554
+ });
555
+ const uploadResponse = await fetch(initiateResponse.presignedUrl, {
556
+ method: "PUT",
557
+ body: file,
558
+ headers: {
559
+ "Content-Type": file.type || "application/octet-stream"
560
+ }
561
+ });
562
+ if (!uploadResponse.ok) {
563
+ throw new Error(`File upload failed: ${uploadResponse.status} ${uploadResponse.statusText}`);
564
+ }
565
+ const finalizeResponse = await client["request"]("/games/uploads/finalize/", "POST", {
566
+ tempS3Key: initiateResponse.tempS3Key,
567
+ gameId: initiateResponse.gameId,
568
+ version: initiateResponse.version,
569
+ slug,
570
+ metadata,
571
+ originalFileName: fileName
572
+ });
573
+ if (!finalizeResponse.body) {
574
+ throw new Error("Finalize response body missing");
575
+ }
576
+ const reader = finalizeResponse.body.pipeThrough(new TextDecoderStream).getReader();
577
+ let buffer = "";
578
+ while (true) {
579
+ const { done, value } = await reader.read();
580
+ if (done)
581
+ break;
582
+ buffer += value;
583
+ let eolIndex;
584
+ while ((eolIndex = buffer.indexOf(`
585
+
586
+ `)) >= 0) {
587
+ const message = buffer.slice(0, eolIndex);
588
+ buffer = buffer.slice(eolIndex + 2);
589
+ const eventLine = message.match(/^event: (.*)$/m);
590
+ const dataLine = message.match(/^data: (.*)$/m);
591
+ if (eventLine && dataLine) {
592
+ const eventType = eventLine[1];
593
+ const eventData = JSON.parse(dataLine[1]);
594
+ if (eventType === "complete") {
595
+ reader.cancel();
596
+ return eventData;
597
+ } else if (eventType === "error") {
598
+ reader.cancel();
599
+ throw new Error(eventData.message);
600
+ }
601
+ }
602
+ }
603
+ }
604
+ throw new Error("Upload completed but no final game data received");
605
+ },
606
+ update: (gameId, props) => client["request"](`/games/${gameId}`, "PATCH", props),
607
+ delete: (gameId) => client["request"](`/games/${gameId}`, "DELETE")
608
+ },
609
+ keys: {
610
+ create: (label) => client["request"](`/dev/keys`, "POST", { label }),
611
+ list: () => client["request"](`/dev/keys`, "GET"),
612
+ revoke: (keyId) => client["request"](`/dev/keys/${keyId}`, "DELETE")
613
+ },
614
+ items: {
615
+ create: (gameId, slug, itemData) => client["request"](`/games/${gameId}/items`, "POST", {
616
+ slug,
617
+ ...itemData
618
+ }),
619
+ update: (gameId, itemId, updates) => client["request"](`/games/${gameId}/items/${itemId}`, "PATCH", updates),
620
+ list: (gameId) => client["request"](`/games/${gameId}/items`, "GET"),
621
+ get: (gameId, slug) => {
622
+ const queryParams = new URLSearchParams({ slug, gameId });
623
+ return client["request"](`/items/resolve?${queryParams.toString()}`, "GET");
624
+ },
625
+ delete: (gameId, itemId) => client["request"](`/games/${gameId}/items/${itemId}`, "DELETE"),
626
+ shop: {
627
+ create: (gameId, itemId, listingData) => {
628
+ return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "POST", listingData);
629
+ },
630
+ get: (gameId, itemId) => {
631
+ return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "GET");
632
+ },
633
+ update: (gameId, itemId, updates) => {
634
+ return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "PATCH", updates);
635
+ },
636
+ delete: (gameId, itemId) => {
637
+ return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "DELETE");
638
+ },
639
+ list: (gameId) => {
640
+ return client["request"](`/games/${gameId}/shop-listings`, "GET");
641
+ }
642
+ }
643
+ }
644
+ };
645
+ }
646
+
647
+ // src/core/namespaces/maps.ts
648
+ function createMapsNamespace(client) {
649
+ return {
650
+ get: (identifier) => client["request"](`/maps/${identifier}`, "GET"),
651
+ elements: (mapId) => client["request"](`/map/elements?mapId=${mapId}`, "GET"),
652
+ objects: {
653
+ list: (mapId) => client["request"](`/maps/${mapId}/objects`, "GET"),
654
+ create: (mapId, objectData) => client["request"](`/maps/${mapId}/objects`, "POST", objectData),
655
+ delete: (mapId, objectId) => client["request"](`/maps/${mapId}/objects/${objectId}`, "DELETE")
656
+ }
657
+ };
658
+ }
659
+
660
+ // src/core/namespaces/admin.ts
661
+ function createAdminNamespace(client) {
662
+ return {
663
+ games: {
664
+ pauseGame: (gameId) => client["request"](`/admin/games/${gameId}/pause`, "POST"),
665
+ resumeGame: (gameId) => client["request"](`/admin/games/${gameId}/resume`, "POST")
666
+ },
667
+ items: {
668
+ create: (props) => client["request"]("/items", "POST", props),
669
+ get: (itemId) => client["request"](`/items/${itemId}`, "GET"),
670
+ list: () => client["request"]("/items", "GET"),
671
+ update: (itemId, props) => client["request"](`/items/${itemId}`, "PATCH", props),
672
+ delete: (itemId) => client["request"](`/items/${itemId}`, "DELETE")
673
+ },
674
+ currencies: {
675
+ create: (props) => client["request"]("/currencies", "POST", props),
676
+ get: (currencyId) => client["request"](`/currencies/${currencyId}`, "GET"),
677
+ list: () => client["request"]("/currencies", "GET"),
678
+ update: (currencyId, props) => client["request"](`/currencies/${currencyId}`, "PATCH", props),
679
+ delete: (currencyId) => client["request"](`/currencies/${currencyId}`, "DELETE")
680
+ },
681
+ shopListings: {
682
+ create: (props) => client["request"]("/shop-listings", "POST", props),
683
+ get: (listingId) => client["request"](`/shop-listings/${listingId}`, "GET"),
684
+ list: () => client["request"]("/shop-listings", "GET"),
685
+ update: (listingId, props) => client["request"](`/shop-listings/${listingId}`, "PATCH", props),
686
+ delete: (listingId) => client["request"](`/shop-listings/${listingId}`, "DELETE")
687
+ }
688
+ };
689
+ }
690
+
691
+ // src/core/namespaces/shop.ts
692
+ function createShopNamespace(client) {
693
+ return {
694
+ view: () => {
695
+ return client["request"]("/shop/view", "GET");
696
+ }
697
+ };
698
+ }
699
+
700
+ // src/core/namespaces/telemetry.ts
701
+ function createTelemetryNamespace(client) {
702
+ return {
703
+ pushMetrics: (metrics) => client["request"](`/telemetry/metrics`, "POST", metrics)
704
+ };
705
+ }
706
+
707
+ // src/core/namespaces/levels.ts
708
+ function createLevelsNamespace(client) {
709
+ return {
710
+ get: async () => {
711
+ return client["request"]("/users/level", "GET");
712
+ },
713
+ progress: async () => {
714
+ return client["request"]("/users/level/progress", "GET");
715
+ },
716
+ addXP: async (amount) => {
717
+ const currentUserLevel = await client["request"]("/users/level", "GET");
718
+ const oldLevel = currentUserLevel.currentLevel;
719
+ const payload = { amount };
720
+ const result = await client["request"]("/users/xp/add", "POST", payload);
721
+ client["emit"]("xpGained", {
722
+ amount,
723
+ totalXP: result.totalXP,
724
+ leveledUp: result.leveledUp
725
+ });
726
+ if (result.leveledUp) {
727
+ client["emit"]("levelUp", {
728
+ oldLevel,
729
+ newLevel: result.newLevel,
730
+ creditsAwarded: result.creditsAwarded
731
+ });
732
+ }
733
+ return result;
734
+ },
735
+ config: {
736
+ list: async () => {
737
+ return client["request"]("/levels/config", "GET");
738
+ },
739
+ get: async (level) => {
740
+ return client["request"](`/levels/config/${level}`, "GET");
741
+ }
742
+ }
743
+ };
744
+ }
745
+
746
+ // ../data/src/domains/game/table.ts
747
+ import {
748
+ jsonb,
749
+ pgEnum,
750
+ pgTable,
751
+ text,
752
+ timestamp,
753
+ uniqueIndex,
754
+ uuid,
755
+ varchar
756
+ } from "drizzle-orm/pg-core";
757
+ var gamePlatformEnum, gameBootModeEnum, games, gameSessions, gameStates;
758
+ var init_table = __esm(() => {
759
+ init_table3();
760
+ init_table4();
761
+ gamePlatformEnum = pgEnum("game_platform", ["web", "godot", "unity"]);
762
+ gameBootModeEnum = pgEnum("game_boot_mode", ["iframe", "module"]);
763
+ games = pgTable("games", {
764
+ id: uuid("id").primaryKey().defaultRandom(),
765
+ developerId: text("developer_id").references(() => users.id, {
766
+ onDelete: "set null"
767
+ }),
768
+ slug: varchar("slug", { length: 255 }).notNull().unique(),
769
+ displayName: varchar("display_name", { length: 255 }).notNull(),
770
+ version: varchar("version", { length: 50 }).notNull(),
771
+ assetBundleBase: text("asset_bundle_base").notNull(),
772
+ platform: gamePlatformEnum("platform").notNull().default("web"),
773
+ mapElementId: uuid("map_element_id").references(() => mapElements.id, {
774
+ onDelete: "set null"
775
+ }),
776
+ metadata: jsonb("metadata").default("{}"),
777
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
778
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow()
779
+ });
780
+ gameSessions = pgTable("game_sessions", {
781
+ id: uuid("id").primaryKey().defaultRandom(),
782
+ userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
783
+ gameId: uuid("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
784
+ startedAt: timestamp("started_at", { withTimezone: true }).notNull().defaultNow(),
785
+ endedAt: timestamp("ended_at", { withTimezone: true })
786
+ });
787
+ gameStates = pgTable("game_states", {
788
+ userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
789
+ gameId: uuid("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
790
+ data: jsonb("data").default("{}"),
791
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow()
792
+ }, (table) => [uniqueIndex("unique_user_game_idx").on(table.userId, table.gameId)]);
793
+ });
794
+
795
+ // ../data/src/domains/inventory/table.ts
796
+ import { relations, sql } from "drizzle-orm";
797
+ import {
798
+ boolean,
799
+ integer,
800
+ jsonb as jsonb2,
801
+ pgEnum as pgEnum2,
802
+ pgTable as pgTable2,
803
+ text as text2,
804
+ timestamp as timestamp2,
805
+ uniqueIndex as uniqueIndex2,
806
+ uuid as uuid2
807
+ } from "drizzle-orm/pg-core";
808
+ var itemTypeEnum, items, inventoryItems, currencies, shopListings, itemsRelations, currenciesRelations, shopListingsRelations, inventoryItemsRelations;
809
+ var init_table2 = __esm(() => {
810
+ init_table();
811
+ init_table3();
812
+ init_table4();
813
+ itemTypeEnum = pgEnum2("item_type", [
814
+ "currency",
815
+ "badge",
816
+ "trophy",
817
+ "collectible",
818
+ "consumable",
819
+ "unlock",
820
+ "upgrade",
821
+ "accessory",
822
+ "other"
823
+ ]);
824
+ items = pgTable2("items", {
825
+ id: uuid2("id").primaryKey().defaultRandom(),
826
+ slug: text2("slug").notNull(),
827
+ gameId: uuid2("game_id").references(() => games.id, {
828
+ onDelete: "cascade"
829
+ }),
830
+ displayName: text2("display_name").notNull(),
831
+ description: text2("description"),
832
+ type: itemTypeEnum("type").notNull().default("other"),
833
+ isPlaceable: boolean("is_placeable").default(false).notNull(),
834
+ imageUrl: text2("image_url"),
835
+ metadata: jsonb2("metadata").default({}),
836
+ createdAt: timestamp2("created_at").defaultNow().notNull()
837
+ }, (table) => [
838
+ uniqueIndex2("items_game_slug_idx").on(table.gameId, table.slug),
839
+ uniqueIndex2("items_global_slug_idx").on(table.slug).where(sql`game_id IS NULL`)
840
+ ]);
841
+ inventoryItems = pgTable2("inventory_items", {
842
+ id: uuid2("id").primaryKey().defaultRandom(),
843
+ userId: text2("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
844
+ itemId: uuid2("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
845
+ quantity: integer("quantity").notNull().default(1),
846
+ updatedAt: timestamp2("updated_at", { withTimezone: true }).defaultNow()
847
+ }, (table) => [uniqueIndex2("unique_user_item_idx").on(table.userId, table.itemId)]);
848
+ currencies = pgTable2("currencies", {
849
+ id: uuid2("id").primaryKey().defaultRandom(),
850
+ itemId: uuid2("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
851
+ symbol: text2("symbol"),
852
+ isPrimary: boolean("is_primary").default(false).notNull(),
853
+ createdAt: timestamp2("created_at").defaultNow().notNull(),
854
+ updatedAt: timestamp2("updated_at", { withTimezone: true }).defaultNow().$onUpdate(() => new Date)
855
+ }, (table) => [uniqueIndex2("currency_item_id_idx").on(table.itemId)]);
856
+ shopListings = pgTable2("shop_listings", {
857
+ id: uuid2("id").primaryKey().defaultRandom(),
858
+ itemId: uuid2("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
859
+ currencyId: uuid2("currency_id").notNull().references(() => currencies.id, { onDelete: "restrict" }),
860
+ price: integer("price").notNull(),
861
+ sellBackPercentage: integer("sell_back_percentage"),
862
+ stock: integer("stock"),
863
+ isActive: boolean("is_active").default(true).notNull(),
864
+ availableFrom: timestamp2("available_from", { withTimezone: true }),
865
+ availableUntil: timestamp2("available_until", { withTimezone: true }),
866
+ createdAt: timestamp2("created_at").defaultNow().notNull(),
867
+ updatedAt: timestamp2("updated_at", { withTimezone: true }).defaultNow().$onUpdate(() => new Date)
868
+ }, (table) => [uniqueIndex2("unique_item_currency_listing_idx").on(table.itemId, table.currencyId)]);
869
+ itemsRelations = relations(items, ({ many }) => ({
870
+ shopListings: many(shopListings),
871
+ inventoryItems: many(inventoryItems),
872
+ mapObjects: many(mapObjects)
873
+ }));
874
+ currenciesRelations = relations(currencies, ({ many }) => ({
875
+ shopListings: many(shopListings)
876
+ }));
877
+ shopListingsRelations = relations(shopListings, ({ one }) => ({
878
+ item: one(items, {
879
+ fields: [shopListings.itemId],
880
+ references: [items.id]
881
+ }),
882
+ currency: one(currencies, {
883
+ fields: [shopListings.currencyId],
884
+ references: [currencies.id]
885
+ })
886
+ }));
887
+ inventoryItemsRelations = relations(inventoryItems, ({ one }) => ({
888
+ item: one(items, {
889
+ fields: [inventoryItems.itemId],
890
+ references: [items.id]
891
+ }),
892
+ user: one(users, {
893
+ fields: [inventoryItems.userId],
894
+ references: [users.id]
895
+ })
896
+ }));
897
+ });
898
+
899
+ // ../data/src/domains/map/table.ts
900
+ import { relations as relations2 } from "drizzle-orm";
901
+ import {
902
+ doublePrecision,
903
+ index,
904
+ integer as integer2,
905
+ jsonb as jsonb3,
906
+ pgEnum as pgEnum3,
907
+ pgTable as pgTable3,
908
+ text as text3,
909
+ timestamp as timestamp3,
910
+ uniqueIndex as uniqueIndex3,
911
+ uuid as uuid3,
912
+ varchar as varchar2
913
+ } from "drizzle-orm/pg-core";
914
+ var interactionTypeEnum, maps, mapElements, mapObjects, mapElementsRelations, mapsRelations, mapObjectsRelations;
915
+ var init_table3 = __esm(() => {
916
+ init_table();
917
+ init_table2();
918
+ init_table4();
919
+ interactionTypeEnum = pgEnum3("interaction_type", [
920
+ "game_entry",
921
+ "game_registry",
922
+ "info",
923
+ "teleport",
924
+ "door_in",
925
+ "door_out",
926
+ "npc_interaction",
927
+ "quest_trigger"
928
+ ]);
929
+ maps = pgTable3("maps", {
930
+ id: uuid3("id").primaryKey().defaultRandom(),
931
+ identifier: varchar2("identifier", { length: 255 }).notNull().unique(),
932
+ displayName: varchar2("display_name", { length: 255 }).notNull(),
933
+ filePath: varchar2("file_path", { length: 255 }).notNull(),
934
+ tilesetBasePath: varchar2("tileset_base_path", { length: 255 }).notNull().default("/tilesets"),
935
+ defaultSpawnTileX: doublePrecision("default_spawn_tile_x").notNull().default(0),
936
+ defaultSpawnTileY: doublePrecision("default_spawn_tile_y").notNull().default(0),
937
+ description: text3("description")
938
+ });
939
+ mapElements = pgTable3("map_elements", {
940
+ id: uuid3("id").primaryKey().defaultRandom(),
941
+ mapId: uuid3("map_id").references(() => maps.id, {
942
+ onDelete: "cascade"
943
+ }),
944
+ elementSlug: varchar2("element_slug", { length: 255 }).notNull(),
945
+ interactionType: interactionTypeEnum("interaction_type").notNull(),
946
+ gameId: uuid3("game_id").references(() => games.id, {
947
+ onDelete: "set null"
948
+ }),
949
+ metadata: jsonb3("metadata").$type().default({})
950
+ }, (table) => [uniqueIndex3("map_id_element_slug_unique_idx").on(table.mapId, table.elementSlug)]);
951
+ mapObjects = pgTable3("map_objects", {
952
+ id: uuid3("id").primaryKey().defaultRandom(),
953
+ userId: text3("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
954
+ mapId: uuid3("map_id").notNull().references(() => maps.id, { onDelete: "cascade" }),
955
+ itemId: uuid3("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
956
+ worldX: doublePrecision("world_x").notNull(),
957
+ worldY: doublePrecision("world_y").notNull(),
958
+ rotation: integer2("rotation").default(0).notNull(),
959
+ scale: doublePrecision("scale").default(1).notNull(),
960
+ createdAt: timestamp3("created_at").defaultNow().notNull()
961
+ }, (table) => [
962
+ index("map_objects_map_idx").on(table.mapId),
963
+ index("map_objects_spatial_idx").on(table.mapId, table.worldX, table.worldY)
964
+ ]);
965
+ mapElementsRelations = relations2(mapElements, ({ one }) => ({
966
+ game: one(games, {
967
+ fields: [mapElements.gameId],
968
+ references: [games.id]
969
+ }),
970
+ map: one(maps, {
971
+ fields: [mapElements.mapId],
972
+ references: [maps.id]
973
+ })
974
+ }));
975
+ mapsRelations = relations2(maps, ({ many }) => ({
976
+ elements: many(mapElements),
977
+ objects: many(mapObjects)
978
+ }));
979
+ mapObjectsRelations = relations2(mapObjects, ({ one }) => ({
980
+ user: one(users, {
981
+ fields: [mapObjects.userId],
982
+ references: [users.id]
983
+ }),
984
+ map: one(maps, {
985
+ fields: [mapObjects.mapId],
986
+ references: [maps.id]
987
+ }),
988
+ item: one(items, {
989
+ fields: [mapObjects.itemId],
990
+ references: [items.id]
991
+ })
992
+ }));
993
+ });
994
+
995
+ // ../data/src/domains/user/table.ts
996
+ import { relations as relations3 } from "drizzle-orm";
997
+ import { boolean as boolean2, pgEnum as pgEnum4, pgTable as pgTable4, text as text4, timestamp as timestamp4, uniqueIndex as uniqueIndex4 } from "drizzle-orm/pg-core";
998
+ var userRoleEnum, developerStatusEnum, users, accounts, sessions, verification, usersRelations;
999
+ var init_table4 = __esm(() => {
1000
+ init_table3();
1001
+ userRoleEnum = pgEnum4("user_role", ["admin", "player", "developer"]);
1002
+ developerStatusEnum = pgEnum4("developer_status", ["none", "pending", "approved"]);
1003
+ users = pgTable4("user", {
1004
+ id: text4("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
1005
+ name: text4("name").notNull(),
1006
+ username: text4("username").unique(),
1007
+ email: text4("email").notNull().unique(),
1008
+ emailVerified: boolean2("email_verified").notNull().default(false),
1009
+ image: text4("image"),
1010
+ role: userRoleEnum("role").notNull().default("player"),
1011
+ developerStatus: developerStatusEnum("developer_status").notNull().default("none"),
1012
+ characterCreated: boolean2("character_created").notNull().default(false),
1013
+ createdAt: timestamp4("created_at", {
1014
+ mode: "date",
1015
+ withTimezone: true
1016
+ }).notNull(),
1017
+ updatedAt: timestamp4("updated_at", {
1018
+ mode: "date",
1019
+ withTimezone: true
1020
+ }).notNull()
1021
+ });
1022
+ accounts = pgTable4("account", {
1023
+ id: text4("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
1024
+ userId: text4("userId").notNull().references(() => users.id, { onDelete: "cascade" }),
1025
+ accountId: text4("account_id").notNull(),
1026
+ providerId: text4("provider_id").notNull(),
1027
+ accessToken: text4("access_token"),
1028
+ refreshToken: text4("refresh_token"),
1029
+ idToken: text4("id_token"),
1030
+ accessTokenExpiresAt: timestamp4("access_token_expires_at", {
1031
+ mode: "date",
1032
+ withTimezone: true
1033
+ }),
1034
+ refreshTokenExpiresAt: timestamp4("refresh_token_expires_at", {
1035
+ mode: "date",
1036
+ withTimezone: true
1037
+ }),
1038
+ scope: text4("scope"),
1039
+ password: text4("password"),
1040
+ createdAt: timestamp4("created_at", {
1041
+ mode: "date",
1042
+ withTimezone: true
1043
+ }).notNull(),
1044
+ updatedAt: timestamp4("updated_at", {
1045
+ mode: "date",
1046
+ withTimezone: true
1047
+ }).notNull()
1048
+ }, (table) => [uniqueIndex4("account_provider_providerId_idx").on(table.accountId, table.providerId)]);
1049
+ sessions = pgTable4("session", {
1050
+ id: text4("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
1051
+ userId: text4("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
1052
+ expiresAt: timestamp4("expires_at", {
1053
+ mode: "date",
1054
+ withTimezone: true
1055
+ }).notNull(),
1056
+ token: text4("token").notNull().unique(),
1057
+ ipAddress: text4("ip_address"),
1058
+ userAgent: text4("user_agent"),
1059
+ createdAt: timestamp4("created_at", {
1060
+ mode: "date",
1061
+ withTimezone: true
1062
+ }).notNull(),
1063
+ updatedAt: timestamp4("updated_at", {
1064
+ mode: "date",
1065
+ withTimezone: true
1066
+ }).notNull()
1067
+ });
1068
+ verification = pgTable4("verification", {
1069
+ id: text4("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
1070
+ identifier: text4("identifier").notNull(),
1071
+ value: text4("value").notNull(),
1072
+ expiresAt: timestamp4("expires_at", {
1073
+ mode: "date",
1074
+ withTimezone: true
1075
+ }).notNull(),
1076
+ createdAt: timestamp4("created_at", {
1077
+ mode: "date",
1078
+ withTimezone: true
1079
+ }).notNull(),
1080
+ updatedAt: timestamp4("updated_at", {
1081
+ mode: "date",
1082
+ withTimezone: true
1083
+ }).notNull()
1084
+ });
1085
+ usersRelations = relations3(users, ({ many }) => ({
1086
+ mapObjects: many(mapObjects)
1087
+ }));
1088
+ });
1089
+
1090
+ // ../data/src/domains/developer/table.ts
1091
+ import { pgTable as pgTable5, text as text5, timestamp as timestamp5, uuid as uuid4, varchar as varchar3 } from "drizzle-orm/pg-core";
1092
+ var developerKeys;
1093
+ var init_table5 = __esm(() => {
1094
+ init_table4();
1095
+ developerKeys = pgTable5("developer_keys", {
1096
+ id: uuid4("id").primaryKey().defaultRandom(),
1097
+ userId: text5("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
1098
+ label: varchar3("label", { length: 255 }),
1099
+ keyHash: text5("key_hash").notNull().unique(),
1100
+ createdAt: timestamp5("created_at", { withTimezone: true }).notNull().defaultNow()
1101
+ });
1102
+ });
1103
+
1104
+ // ../data/src/domains/level/table.ts
1105
+ import { relations as relations4 } from "drizzle-orm";
1106
+ import { integer as integer3, pgTable as pgTable6, text as text6, timestamp as timestamp6, uniqueIndex as uniqueIndex5, uuid as uuid5 } from "drizzle-orm/pg-core";
1107
+ var userLevels, levelConfigs, userLevelsRelations;
1108
+ var init_table6 = __esm(() => {
1109
+ init_table4();
1110
+ userLevels = pgTable6("user_levels", {
1111
+ userId: text6("user_id").primaryKey().references(() => users.id, { onDelete: "cascade" }),
1112
+ currentLevel: integer3("current_level").notNull().default(1),
1113
+ currentXp: integer3("current_xp").notNull().default(0),
1114
+ totalXP: integer3("total_xp").notNull().default(0),
1115
+ lastLevelUpAt: timestamp6("last_level_up_at", { withTimezone: true }),
1116
+ createdAt: timestamp6("created_at").defaultNow().notNull(),
1117
+ updatedAt: timestamp6("updated_at", { withTimezone: true }).defaultNow().$onUpdate(() => new Date)
1118
+ });
1119
+ levelConfigs = pgTable6("level_configs", {
1120
+ id: uuid5("id").primaryKey().defaultRandom(),
1121
+ level: integer3("level").notNull().unique(),
1122
+ xpRequired: integer3("xp_required").notNull(),
1123
+ creditsReward: integer3("credits_reward").notNull().default(0),
1124
+ createdAt: timestamp6("created_at").defaultNow().notNull()
1125
+ }, (table) => [uniqueIndex5("unique_level_config_idx").on(table.level)]);
1126
+ userLevelsRelations = relations4(userLevels, ({ one }) => ({
1127
+ user: one(users, {
1128
+ fields: [userLevels.userId],
1129
+ references: [users.id]
1130
+ })
1131
+ }));
1132
+ });
1133
+
1134
+ // ../data/src/domains/leaderboard/table.ts
1135
+ import { relations as relations5 } from "drizzle-orm";
1136
+ import { index as index2, integer as integer4, jsonb as jsonb4, pgTable as pgTable7, text as text7, timestamp as timestamp7, uuid as uuid6 } from "drizzle-orm/pg-core";
1137
+ var gameScores, gameScoresRelations;
1138
+ var init_table7 = __esm(() => {
1139
+ init_table();
1140
+ init_table4();
1141
+ gameScores = pgTable7("game_scores", {
1142
+ id: uuid6("id").primaryKey().defaultRandom(),
1143
+ userId: text7("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
1144
+ gameId: uuid6("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
1145
+ score: integer4("score").notNull(),
1146
+ metadata: jsonb4("metadata").default("{}"),
1147
+ achievedAt: timestamp7("achieved_at", { withTimezone: true }).defaultNow().notNull(),
1148
+ sessionId: uuid6("session_id").references(() => gameSessions.id, { onDelete: "set null" })
1149
+ }, (table) => [
1150
+ index2("game_scores_user_game_idx").on(table.userId, table.gameId),
1151
+ index2("game_scores_game_score_idx").on(table.gameId, table.score),
1152
+ index2("game_scores_achieved_at_idx").on(table.achievedAt)
1153
+ ]);
1154
+ gameScoresRelations = relations5(gameScores, ({ one }) => ({
1155
+ user: one(users, {
1156
+ fields: [gameScores.userId],
1157
+ references: [users.id]
1158
+ }),
1159
+ game: one(games, {
1160
+ fields: [gameScores.gameId],
1161
+ references: [games.id]
1162
+ }),
1163
+ session: one(gameSessions, {
1164
+ fields: [gameScores.sessionId],
1165
+ references: [gameSessions.id]
1166
+ })
1167
+ }));
1168
+ });
1169
+
1170
+ // ../data/src/domains/sprite/table.ts
1171
+ import { relations as relations6 } from "drizzle-orm";
1172
+ import { integer as integer5, pgTable as pgTable8, timestamp as timestamp8, uuid as uuid7, varchar as varchar4 } from "drizzle-orm/pg-core";
1173
+ var spriteTemplates, spriteSheets, spriteTemplatesRelations, spriteSheetsRelations;
1174
+ var init_table8 = __esm(() => {
1175
+ spriteTemplates = pgTable8("sprite_templates", {
1176
+ id: uuid7("id").primaryKey().defaultRandom(),
1177
+ slug: varchar4("slug", { length: 64 }).notNull().unique(),
1178
+ url: varchar4("url", { length: 255 }).notNull(),
1179
+ createdAt: timestamp8("created_at", { withTimezone: true }).notNull().defaultNow(),
1180
+ updatedAt: timestamp8("updated_at", { withTimezone: true }).notNull().defaultNow()
1181
+ });
1182
+ spriteSheets = pgTable8("sprite_sheets", {
1183
+ id: uuid7("id").primaryKey().defaultRandom(),
1184
+ templateId: uuid7("template_id").notNull().references(() => spriteTemplates.id, { onDelete: "cascade" }),
1185
+ width: integer5("width").notNull(),
1186
+ height: integer5("height").notNull(),
1187
+ url: varchar4("url", { length: 255 }).notNull(),
1188
+ createdAt: timestamp8("created_at", { withTimezone: true }).notNull().defaultNow(),
1189
+ updatedAt: timestamp8("updated_at", { withTimezone: true }).notNull().defaultNow()
1190
+ });
1191
+ spriteTemplatesRelations = relations6(spriteTemplates, ({ many }) => ({
1192
+ sheets: many(spriteSheets)
1193
+ }));
1194
+ spriteSheetsRelations = relations6(spriteSheets, ({ one }) => ({
1195
+ template: one(spriteTemplates, {
1196
+ fields: [spriteSheets.templateId],
1197
+ references: [spriteTemplates.id]
1198
+ })
1199
+ }));
1200
+ });
1201
+
1202
+ // ../data/src/domains/character/table.ts
1203
+ import { relations as relations7 } from "drizzle-orm";
1204
+ import { integer as integer6, pgEnum as pgEnum5, pgTable as pgTable9, text as text8, timestamp as timestamp9, uuid as uuid8, varchar as varchar5 } from "drizzle-orm/pg-core";
1205
+ var characterComponentTypeEnum, characterComponents, playerCharacters, characterComponentsRelations, playerCharactersRelations;
1206
+ var init_table9 = __esm(() => {
1207
+ init_table8();
1208
+ init_table4();
1209
+ characterComponentTypeEnum = pgEnum5("character_component_type", [
1210
+ "body",
1211
+ "outfit",
1212
+ "hairstyle",
1213
+ "eyes",
1214
+ "accessory"
1215
+ ]);
1216
+ characterComponents = pgTable9("character_components", {
1217
+ id: uuid8("id").primaryKey().defaultRandom(),
1218
+ componentType: characterComponentTypeEnum("component_type").notNull(),
1219
+ slug: varchar5("slug", { length: 128 }).notNull().unique(),
1220
+ displayName: varchar5("display_name", { length: 128 }).notNull(),
1221
+ slot: varchar5("slot", { length: 64 }).notNull(),
1222
+ spriteSheetId: uuid8("sprite_sheet_id").notNull().references(() => spriteSheets.id, { onDelete: "cascade" }),
1223
+ unlockLevel: integer6("unlock_level").notNull().default(0),
1224
+ variant: integer6("variant").notNull().default(0),
1225
+ createdAt: timestamp9("created_at", { withTimezone: true }).notNull().defaultNow(),
1226
+ updatedAt: timestamp9("updated_at", { withTimezone: true }).notNull().defaultNow()
1227
+ });
1228
+ playerCharacters = pgTable9("player_characters", {
1229
+ id: uuid8("id").primaryKey().defaultRandom(),
1230
+ userId: text8("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
1231
+ bodyComponentId: uuid8("body_component_id").notNull().references(() => characterComponents.id, { onDelete: "restrict" }),
1232
+ eyesComponentId: uuid8("eyes_component_id").notNull().references(() => characterComponents.id, { onDelete: "restrict" }),
1233
+ hairstyleComponentId: uuid8("hairstyle_component_id").notNull().references(() => characterComponents.id, { onDelete: "restrict" }),
1234
+ outfitComponentId: uuid8("outfit_component_id").notNull().references(() => characterComponents.id, { onDelete: "restrict" }),
1235
+ accessoryComponentId: uuid8("accessory_component_id").references(() => characterComponents.id, {
1236
+ onDelete: "set null"
1237
+ }),
1238
+ createdAt: timestamp9("created_at", { withTimezone: true }).notNull().defaultNow(),
1239
+ updatedAt: timestamp9("updated_at", { withTimezone: true }).notNull().defaultNow()
1240
+ });
1241
+ characterComponentsRelations = relations7(characterComponents, ({ one }) => ({
1242
+ sheet: one(spriteSheets, {
1243
+ fields: [characterComponents.spriteSheetId],
1244
+ references: [spriteSheets.id]
1245
+ })
1246
+ }));
1247
+ playerCharactersRelations = relations7(playerCharacters, ({ one }) => ({
1248
+ user: one(users, {
1249
+ fields: [playerCharacters.userId],
1250
+ references: [users.id]
1251
+ }),
1252
+ body: one(characterComponents, {
1253
+ fields: [playerCharacters.bodyComponentId],
1254
+ references: [characterComponents.id]
1255
+ }),
1256
+ eyes: one(characterComponents, {
1257
+ fields: [playerCharacters.eyesComponentId],
1258
+ references: [characterComponents.id]
1259
+ }),
1260
+ hair: one(characterComponents, {
1261
+ fields: [playerCharacters.hairstyleComponentId],
1262
+ references: [characterComponents.id]
1263
+ }),
1264
+ outfit: one(characterComponents, {
1265
+ fields: [playerCharacters.outfitComponentId],
1266
+ references: [characterComponents.id]
1267
+ }),
1268
+ accessory: one(characterComponents, {
1269
+ fields: [playerCharacters.accessoryComponentId],
1270
+ references: [characterComponents.id]
1271
+ })
1272
+ }));
1273
+ });
1274
+
1275
+ // ../data/src/tables.index.ts
1276
+ var init_tables_index = __esm(() => {
1277
+ init_table4();
1278
+ init_table5();
1279
+ init_table();
1280
+ init_table2();
1281
+ init_table3();
1282
+ init_table6();
1283
+ init_table7();
1284
+ init_table8();
1285
+ init_table9();
1286
+ });
1287
+
1288
+ // ../data/src/constants.ts
1289
+ var ITEM_SLUGS, CURRENCIES, BADGES, INTERACTION_TYPE;
1290
+ var init_constants = __esm(() => {
1291
+ init_tables_index();
1292
+ ITEM_SLUGS = {
1293
+ PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
1294
+ PLAYCADEMY_XP: "PLAYCADEMY_XP",
1295
+ FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
1296
+ EARLY_ADOPTER_BADGE: "EARLY_ADOPTER_BADGE",
1297
+ FIRST_GAME_BADGE: "FIRST_GAME_BADGE",
1298
+ COMMON_SWORD: "COMMON_SWORD",
1299
+ SMALL_HEALTH_POTION: "SMALL_HEALTH_POTION",
1300
+ SMALL_BACKPACK: "SMALL_BACKPACK",
1301
+ LAVA_LAMP: "LAVA_LAMP",
1302
+ BOOMBOX: "BOOMBOX",
1303
+ CABIN_BED: "CABIN_BED"
1304
+ };
1305
+ CURRENCIES = {
1306
+ PRIMARY: ITEM_SLUGS.PLAYCADEMY_CREDITS,
1307
+ XP: ITEM_SLUGS.PLAYCADEMY_XP
1308
+ };
1309
+ BADGES = {
1310
+ FOUNDING_MEMBER: ITEM_SLUGS.FOUNDING_MEMBER_BADGE,
1311
+ EARLY_ADOPTER: ITEM_SLUGS.EARLY_ADOPTER_BADGE,
1312
+ FIRST_GAME: ITEM_SLUGS.FIRST_GAME_BADGE
1313
+ };
1314
+ INTERACTION_TYPE = Object.fromEntries(interactionTypeEnum.enumValues.map((value) => [value, value]));
1315
+ });
1316
+
1317
+ // src/core/namespaces/credits.ts
1318
+ function createCreditsNamespace(client) {
1319
+ let cachedCreditsItemId = null;
1320
+ const getCreditsItemId = async () => {
1321
+ if (cachedCreditsItemId) {
1322
+ return cachedCreditsItemId;
1323
+ }
1324
+ const queryParams = new URLSearchParams({ slug: CURRENCIES.PRIMARY });
1325
+ const creditsItem = await client["request"](`/items/resolve?${queryParams.toString()}`, "GET");
1326
+ if (!creditsItem || !creditsItem.id) {
1327
+ throw new Error("Playcademy Credits item not found in catalog");
1328
+ }
1329
+ cachedCreditsItemId = creditsItem.id;
1330
+ return creditsItem.id;
1331
+ };
1332
+ return {
1333
+ balance: async () => {
1334
+ const inventory = await client["request"]("/inventory", "GET");
1335
+ const primaryCurrencyInventoryItem = inventory.find((item) => item.item?.slug === CURRENCIES.PRIMARY);
1336
+ return primaryCurrencyInventoryItem?.quantity ?? 0;
1337
+ },
1338
+ add: async (amount) => {
1339
+ if (amount <= 0) {
1340
+ throw new Error("Amount must be positive");
1341
+ }
1342
+ const creditsItemId = await getCreditsItemId();
1343
+ const result = await client["request"]("/inventory/add", "POST", {
1344
+ itemId: creditsItemId,
1345
+ qty: amount
1346
+ });
1347
+ client["emit"]("inventoryChange", {
1348
+ itemId: creditsItemId,
1349
+ delta: amount,
1350
+ newTotal: result.newTotal
1351
+ });
1352
+ return result.newTotal;
1353
+ },
1354
+ spend: async (amount) => {
1355
+ if (amount <= 0) {
1356
+ throw new Error("Amount must be positive");
1357
+ }
1358
+ const creditsItemId = await getCreditsItemId();
1359
+ const result = await client["request"]("/inventory/remove", "POST", {
1360
+ itemId: creditsItemId,
1361
+ qty: amount
1362
+ });
1363
+ client["emit"]("inventoryChange", {
1364
+ itemId: creditsItemId,
1365
+ delta: -amount,
1366
+ newTotal: result.newTotal
1367
+ });
1368
+ return result.newTotal;
1369
+ }
1370
+ };
1371
+ }
1372
+ var init_credits = __esm(() => {
1373
+ init_constants();
1374
+ });
1375
+
1376
+ // src/core/namespaces/leaderboard.ts
1377
+ function createLeaderboardNamespace(client) {
1378
+ return {
1379
+ fetch: async (options) => {
1380
+ const params = new URLSearchParams({
1381
+ timeframe: options?.timeframe || "all_time",
1382
+ limit: String(options?.limit || 10),
1383
+ offset: String(options?.offset || 0)
1384
+ });
1385
+ if (options?.gameId) {
1386
+ params.append("gameId", options.gameId);
1387
+ }
1388
+ return client["request"](`/leaderboard?${params}`, "GET");
1389
+ },
1390
+ getUserRank: async (gameId, userId) => {
1391
+ return client["request"](`/games/${gameId}/users/${userId}/rank`, "GET");
1392
+ }
1393
+ };
1394
+ }
1395
+
1396
+ // src/core/namespaces/scores.ts
1397
+ function createScoresNamespace(client) {
1398
+ return {
1399
+ submit: async (gameId, score, metadata) => {
1400
+ return client["request"](`/games/${gameId}/scores`, "POST", {
1401
+ score,
1402
+ metadata
1403
+ });
1404
+ },
1405
+ getByUser: async (gameId, userId, options) => {
1406
+ const params = new URLSearchParams;
1407
+ if (options?.limit) {
1408
+ params.append("limit", String(options.limit));
1409
+ }
1410
+ const queryString = params.toString();
1411
+ const path = queryString ? `/games/${gameId}/users/${userId}/scores?${queryString}` : `/games/${gameId}/users/${userId}/scores`;
1412
+ return client["request"](path, "GET");
1413
+ }
1414
+ };
1415
+ }
1416
+
1417
+ // src/core/namespaces/character.ts
1418
+ function createCharacterNamespace(client) {
1419
+ const componentCache = {};
1420
+ const clearComponentCache = () => {
1421
+ Object.keys(componentCache).forEach((key) => delete componentCache[key]);
1422
+ };
1423
+ const getCachedComponentLevels = () => {
1424
+ return Object.keys(componentCache);
1425
+ };
1426
+ return {
1427
+ get: async (userId) => {
1428
+ try {
1429
+ const path = userId ? `/character/${userId}` : "/character";
1430
+ return await client["request"](path, "GET");
1431
+ } catch (error) {
1432
+ if (error instanceof Error) {
1433
+ if (error.message.includes("404")) {
1434
+ return null;
1435
+ }
1436
+ }
1437
+ throw error;
1438
+ }
1439
+ },
1440
+ create: async (characterData) => {
1441
+ return client["request"]("/character", "POST", characterData);
1442
+ },
1443
+ update: async (updates) => {
1444
+ return client["request"]("/character", "PATCH", updates);
1445
+ },
1446
+ components: {
1447
+ list: async (options) => {
1448
+ const cacheKey = options?.level === undefined ? "all" : String(options.level);
1449
+ if (!options?.skipCache && componentCache[cacheKey]) {
1450
+ return componentCache[cacheKey];
1451
+ }
1452
+ const path = options?.level !== undefined ? `/character/components?level=${options.level}` : "/character/components";
1453
+ const components = await client["request"](path, "GET");
1454
+ componentCache[cacheKey] = components || [];
1455
+ return components || [];
1456
+ },
1457
+ clearCache: clearComponentCache,
1458
+ getCacheKeys: getCachedComponentLevels
1459
+ }
1460
+ };
1461
+ }
1462
+
1463
+ // src/core/namespaces/sprites.ts
1464
+ function createSpritesNamespace(client) {
1465
+ return {
1466
+ templates: {
1467
+ get: async (slug) => {
1468
+ const templateMeta = await client["request"](`/sprites/templates/${slug}`, "GET");
1469
+ if (!templateMeta.url) {
1470
+ throw new Error(`Template ${slug} has no URL in database`);
1471
+ }
1472
+ const response = await fetch(templateMeta.url);
1473
+ if (!response.ok) {
1474
+ throw new Error(`Failed to fetch template JSON from ${templateMeta.url}: ${response.statusText}`);
1475
+ }
1476
+ return response.json();
1477
+ }
1478
+ }
1479
+ };
1480
+ }
1481
+
1482
+ // src/core/namespaces/realtime.client.ts
1483
+ class RealtimeChannelClient {
1484
+ gameId;
1485
+ _channelName;
1486
+ getToken;
1487
+ baseUrl;
1488
+ ws;
1489
+ listeners = new Set;
1490
+ isClosing = false;
1491
+ tokenRefreshUnsubscribe;
1492
+ constructor(gameId, _channelName, getToken, baseUrl) {
1493
+ this.gameId = gameId;
1494
+ this._channelName = _channelName;
1495
+ this.getToken = getToken;
1496
+ this.baseUrl = baseUrl;
1497
+ }
1498
+ async connect() {
1499
+ try {
1500
+ const token = await this.getToken();
1501
+ const wsUrl = this.baseUrl.replace(/^http/, "ws");
1502
+ const url = `${wsUrl}?token=${encodeURIComponent(token)}&c=${encodeURIComponent(this._channelName)}`;
1503
+ this.ws = new WebSocket(url);
1504
+ this.setupEventHandlers();
1505
+ this.setupTokenRefreshListener();
1506
+ await new Promise((resolve, reject) => {
1507
+ if (!this.ws) {
1508
+ reject(new Error("WebSocket creation failed"));
1509
+ return;
1510
+ }
1511
+ const onOpen = () => {
1512
+ this.ws?.removeEventListener("open", onOpen);
1513
+ this.ws?.removeEventListener("error", onError);
1514
+ resolve();
1515
+ };
1516
+ const onError = (event) => {
1517
+ this.ws?.removeEventListener("open", onOpen);
1518
+ this.ws?.removeEventListener("error", onError);
1519
+ reject(new Error(`WebSocket connection failed: ${event}`));
1520
+ };
1521
+ this.ws.addEventListener("open", onOpen);
1522
+ this.ws.addEventListener("error", onError);
1523
+ });
1524
+ log.debug("[RealtimeChannelClient] Connected to channel", {
1525
+ gameId: this.gameId,
1526
+ channel: this._channelName
1527
+ });
1528
+ return this;
1529
+ } catch (error) {
1530
+ log.error("[RealtimeChannelClient] Connection failed", {
1531
+ gameId: this.gameId,
1532
+ channel: this._channelName,
1533
+ error
1534
+ });
1535
+ throw error;
1536
+ }
1537
+ }
1538
+ setupEventHandlers() {
1539
+ if (!this.ws)
1540
+ return;
1541
+ this.ws.onmessage = (event) => {
1542
+ try {
1543
+ const data = JSON.parse(event.data);
1544
+ this.listeners.forEach((callback) => {
1545
+ try {
1546
+ callback(data);
1547
+ } catch (error) {
1548
+ log.warn("[RealtimeChannelClient] Message listener error", {
1549
+ channel: this._channelName,
1550
+ error
1551
+ });
1552
+ }
1553
+ });
1554
+ } catch (error) {
1555
+ log.warn("[RealtimeChannelClient] Failed to parse message", {
1556
+ channel: this._channelName,
1557
+ message: event.data,
1558
+ error
1559
+ });
1560
+ }
1561
+ };
1562
+ this.ws.onclose = (event) => {
1563
+ log.debug("[RealtimeChannelClient] Connection closed", {
1564
+ channel: this._channelName,
1565
+ code: event.code,
1566
+ reason: event.reason,
1567
+ wasClean: event.wasClean
1568
+ });
1569
+ if (!this.isClosing && event.code !== CLOSE_CODES.TOKEN_REFRESH) {
1570
+ log.warn("[RealtimeChannelClient] Unexpected disconnection", {
1571
+ channel: this._channelName,
1572
+ code: event.code,
1573
+ reason: event.reason
1574
+ });
1575
+ }
1576
+ };
1577
+ this.ws.onerror = (event) => {
1578
+ log.error("[RealtimeChannelClient] WebSocket error", {
1579
+ channel: this._channelName,
1580
+ event
1581
+ });
1582
+ };
1583
+ }
1584
+ setupTokenRefreshListener() {
1585
+ const tokenRefreshHandler = async ({ token }) => {
1586
+ log.debug("[RealtimeChannelClient] Token refresh received, reconnecting", {
1587
+ channel: this._channelName
1588
+ });
1589
+ try {
1590
+ await this.reconnectWithNewToken(token);
1591
+ } catch (error) {
1592
+ log.error("[RealtimeChannelClient] Token refresh reconnection failed", {
1593
+ channel: this._channelName,
1594
+ error
1595
+ });
1596
+ }
1597
+ };
1598
+ messaging.listen("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, tokenRefreshHandler);
1599
+ this.tokenRefreshUnsubscribe = () => {
1600
+ messaging.unlisten("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, tokenRefreshHandler);
1601
+ };
1602
+ }
1603
+ async reconnectWithNewToken(_token) {
1604
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1605
+ this.ws.close(CLOSE_CODES.TOKEN_REFRESH, "token_refresh");
1606
+ await new Promise((resolve) => {
1607
+ const checkClosed = () => {
1608
+ if (!this.ws || this.ws.readyState === WebSocket.CLOSED) {
1609
+ resolve();
1610
+ } else {
1611
+ setTimeout(checkClosed, 10);
1612
+ }
1613
+ };
1614
+ checkClosed();
1615
+ });
1616
+ }
1617
+ await this.connect();
1618
+ }
1619
+ send(data) {
1620
+ if (this.ws?.readyState === WebSocket.OPEN) {
1621
+ try {
1622
+ const message = JSON.stringify(data);
1623
+ this.ws.send(message);
1624
+ } catch (error) {
1625
+ log.error("[RealtimeChannelClient] Failed to send message", {
1626
+ channel: this._channelName,
1627
+ error,
1628
+ data
1629
+ });
1630
+ }
1631
+ } else {
1632
+ log.warn("[RealtimeChannelClient] Cannot send message - connection not open", {
1633
+ channel: this._channelName,
1634
+ readyState: this.ws?.readyState
1635
+ });
1636
+ }
1637
+ }
1638
+ onMessage(callback) {
1639
+ this.listeners.add(callback);
1640
+ return () => this.listeners.delete(callback);
1641
+ }
1642
+ close() {
1643
+ this.isClosing = true;
1644
+ if (this.tokenRefreshUnsubscribe) {
1645
+ this.tokenRefreshUnsubscribe();
1646
+ this.tokenRefreshUnsubscribe = undefined;
1647
+ }
1648
+ if (this.ws) {
1649
+ this.ws.close(CLOSE_CODES.NORMAL_CLOSURE, "client_close");
1650
+ this.ws = undefined;
1651
+ }
1652
+ this.listeners.clear();
1653
+ log.debug("[RealtimeChannelClient] Channel closed", {
1654
+ channel: this._channelName
1655
+ });
1656
+ }
1657
+ get channelName() {
1658
+ return this._channelName;
1659
+ }
1660
+ get isConnected() {
1661
+ return this.ws?.readyState === WebSocket.OPEN;
1662
+ }
1663
+ }
1664
+ var CLOSE_CODES;
1665
+ var init_realtime_client = __esm(() => {
1666
+ init_src();
1667
+ init_messaging();
1668
+ CLOSE_CODES = {
1669
+ NORMAL_CLOSURE: 1000,
1670
+ TOKEN_REFRESH: 4000
1671
+ };
1672
+ });
1673
+
1674
+ // src/core/namespaces/realtime.ts
1675
+ function createRealtimeNamespace(client) {
1676
+ return {
1677
+ token: {
1678
+ get: async () => {
1679
+ return client["request"]("/realtime/token", "POST");
1680
+ }
1681
+ },
1682
+ async open(channel = "default") {
1683
+ if (!client["gameId"]) {
1684
+ throw new Error("gameId is required for realtime channels");
1685
+ }
1686
+ const realtimeClient = new RealtimeChannelClient(client["gameId"], channel, () => client.realtime.token.get().then((r) => r.token), client.getBaseUrl());
1687
+ return realtimeClient.connect();
1688
+ }
1689
+ };
1690
+ }
1691
+ var init_realtime = __esm(() => {
1692
+ init_realtime_client();
1693
+ });
1694
+
1695
+ // src/core/namespaces/index.ts
1696
+ var init_namespaces = __esm(() => {
1697
+ init_runtime();
1698
+ init_games();
1699
+ init_credits();
1700
+ init_realtime();
1701
+ });
1702
+
1703
+ // src/core/static/init.ts
1704
+ async function getPlaycademyConfig() {
1705
+ const preloaded = window.PLAYCADEMY;
1706
+ if (preloaded?.token) {
1707
+ return preloaded;
1708
+ }
1709
+ if (window.self !== window.top) {
1710
+ return await waitForPlaycademyInit();
1711
+ } else {
1712
+ return createStandaloneConfig();
1713
+ }
1714
+ }
1715
+ async function waitForPlaycademyInit() {
1716
+ return new Promise((resolve, reject) => {
1717
+ let contextReceived = false;
1718
+ const timeoutDuration = 5000;
1719
+ const handleMessage = (event) => {
1720
+ if (event.data?.type === "PLAYCADEMY_INIT" /* INIT */) {
1721
+ contextReceived = true;
1722
+ window.removeEventListener("message", handleMessage);
1723
+ clearTimeout(timeoutId);
1724
+ window.PLAYCADEMY = event.data.payload;
1725
+ resolve(event.data.payload);
1726
+ }
1727
+ };
1728
+ window.addEventListener("message", handleMessage);
1729
+ const timeoutId = setTimeout(() => {
1730
+ if (!contextReceived) {
1731
+ window.removeEventListener("message", handleMessage);
1732
+ reject(new Error(`${"PLAYCADEMY_INIT" /* INIT */} not received within ${timeoutDuration}ms`));
1733
+ }
1734
+ }, timeoutDuration);
1735
+ });
1736
+ }
1737
+ function createStandaloneConfig() {
1738
+ const mockConfig = {
1739
+ baseUrl: "/api",
1740
+ token: "mock-game-token-for-local-dev",
1741
+ gameId: "mock-game-id-from-template"
1742
+ };
1743
+ window.PLAYCADEMY = mockConfig;
1744
+ return mockConfig;
1745
+ }
1746
+ async function init() {
1747
+ const { PlaycademyClient } = await Promise.resolve().then(() => (init_client(), exports_client));
1748
+ if (typeof window === "undefined") {
1749
+ throw new Error("Playcademy SDK must run in a browser context");
1750
+ }
1751
+ const config = await getPlaycademyConfig();
1752
+ const client = new PlaycademyClient({
1753
+ baseUrl: config.baseUrl,
1754
+ token: config.token,
1755
+ gameId: config.gameId
1756
+ });
1757
+ messaging.listen("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, ({ token }) => client.setToken(token));
1758
+ messaging.send("PLAYCADEMY_READY" /* READY */, undefined);
1759
+ if (import.meta.env?.MODE === "development") {
1760
+ window.PLAYCADEMY_CLIENT = client;
1761
+ }
1762
+ return client;
1763
+ }
1764
+ var init_init = __esm(() => {
1765
+ init_messaging();
1766
+ });
1767
+
1768
+ // src/core/static/login.ts
1769
+ async function login(baseUrl, email, password) {
1770
+ let url = baseUrl;
1771
+ if (baseUrl.startsWith("/") && typeof window !== "undefined") {
1772
+ url = window.location.origin + baseUrl;
1773
+ }
1774
+ url = url + "/auth/login";
1775
+ const response = await fetch(url, {
1776
+ method: "POST",
1777
+ headers: {
1778
+ "Content-Type": "application/json"
1779
+ },
1780
+ body: JSON.stringify({ email, password })
1781
+ });
1782
+ if (!response.ok) {
1783
+ try {
1784
+ const errorData = await response.json();
1785
+ const errorMessage = errorData && errorData.message ? String(errorData.message) : response.statusText;
1786
+ throw new PlaycademyError(errorMessage);
1787
+ } catch (error) {
1788
+ log.error("[Playcademy SDK] Failed to parse error response JSON, using status text instead:", { error });
1789
+ throw new PlaycademyError(response.statusText);
1790
+ }
1791
+ }
1792
+ return response.json();
1793
+ }
1794
+ var init_login = __esm(() => {
1795
+ init_src();
1796
+ init_errors();
1797
+ });
1798
+
1799
+ // src/core/static/index.ts
1800
+ var init_static = __esm(() => {
1801
+ init_init();
1802
+ init_login();
1803
+ });
1804
+
1805
+ // src/core/client.ts
1806
+ var exports_client = {};
1807
+ __export(exports_client, {
1808
+ PlaycademyClient: () => PlaycademyClient
1809
+ });
1810
+ var PlaycademyClient;
1811
+ var init_client = __esm(() => {
1812
+ init_src();
1813
+ init_errors();
1814
+ init_namespaces();
1815
+ init_request();
1816
+ init_static();
1817
+ PlaycademyClient = class PlaycademyClient {
1818
+ baseUrl;
1819
+ token;
1820
+ gameId;
1821
+ listeners = {};
1822
+ internalClientSessionId;
1823
+ constructor(config) {
1824
+ if (!config) {
1825
+ this.baseUrl = "/api";
1826
+ this.token = undefined;
1827
+ this.gameId = undefined;
1828
+ } else {
1829
+ this.baseUrl = config.baseUrl || "/api";
1830
+ this.token = config.token;
1831
+ this.gameId = config.gameId;
1832
+ }
1833
+ if (this.gameId) {
1834
+ this._initializeInternalSession().catch((error) => {
1835
+ log.error("[Playcademy SDK] Background initialization of auto-session failed:", {
1836
+ error
1837
+ });
1838
+ });
1839
+ }
1840
+ }
1841
+ getBaseUrl() {
1842
+ const isRelative = this.baseUrl.startsWith("/");
1843
+ const isBrowser2 = typeof window !== "undefined";
1844
+ return isRelative && isBrowser2 ? `${window.location.origin}${this.baseUrl}` : this.baseUrl;
1845
+ }
1846
+ ping() {
1847
+ return "pong";
1848
+ }
1849
+ setToken(token) {
1850
+ this.token = token ?? undefined;
1851
+ this.emit("authChange", { token: this.token ?? null });
1852
+ }
1853
+ onAuthChange(callback) {
1854
+ this.on("authChange", (payload) => callback(payload.token));
1855
+ }
1856
+ on(event, callback) {
1857
+ this.listeners[event] = this.listeners[event] ?? [];
1858
+ this.listeners[event].push(callback);
1859
+ }
1860
+ emit(event, payload) {
1861
+ (this.listeners[event] ?? []).forEach((listener) => {
1862
+ listener(payload);
1863
+ });
1864
+ }
1865
+ async request(path, method, body, headers) {
1866
+ const effectiveHeaders = { ...headers };
1867
+ return request({
1868
+ path,
1869
+ method,
1870
+ body,
1871
+ baseUrl: this.baseUrl,
1872
+ token: this.token,
1873
+ extraHeaders: effectiveHeaders
1874
+ });
1875
+ }
1876
+ _ensureGameId() {
1877
+ if (!this.gameId) {
1878
+ throw new PlaycademyError("This operation requires a gameId, but none was provided when initializing the client.");
1879
+ }
1880
+ return this.gameId;
1881
+ }
1882
+ async _initializeInternalSession() {
1883
+ if (!this.gameId || this.internalClientSessionId) {
1884
+ return;
1885
+ }
1886
+ try {
1887
+ const response = await this.games.startSession(this.gameId);
1888
+ this.internalClientSessionId = response.sessionId;
1889
+ } catch (error) {
1890
+ log.error("[Playcademy SDK] Auto-starting session failed for game", {
1891
+ gameId: this.gameId,
1892
+ error
1893
+ });
1894
+ }
1895
+ }
1896
+ auth = createAuthNamespace(this);
1897
+ runtime = createRuntimeNamespace(this);
1898
+ games = createGamesNamespace(this);
1899
+ users = createUsersNamespace(this);
1900
+ dev = createDevNamespace(this);
1901
+ maps = createMapsNamespace(this);
1902
+ admin = createAdminNamespace(this);
1903
+ shop = createShopNamespace(this);
1904
+ levels = createLevelsNamespace(this);
1905
+ telemetry = createTelemetryNamespace(this);
1906
+ credits = createCreditsNamespace(this);
1907
+ leaderboard = createLeaderboardNamespace(this);
1908
+ scores = createScoresNamespace(this);
1909
+ character = createCharacterNamespace(this);
1910
+ sprites = createSpritesNamespace(this);
1911
+ realtime = createRealtimeNamespace(this);
1912
+ static init = init;
1913
+ static login = login;
1914
+ };
1915
+ });
1916
+
1917
+ // src/index.ts
1918
+ init_client();
1919
+ init_messaging();
1920
+ export {
1921
+ messaging,
1922
+ PlaycademyClient,
1923
+ MessageEvents
1924
+ };