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

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