@playcademy/sdk 0.3.0 → 0.3.2

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/internal.js CHANGED
@@ -32,15 +32,15 @@ class PlaycademyMessaging {
32
32
  }
33
33
  }
34
34
  listen(type, handler) {
35
- const postMessageListener = (event) => {
35
+ function postMessageListener(event) {
36
36
  const messageEvent = event;
37
37
  if (messageEvent.data?.type === type) {
38
38
  handler(messageEvent.data.payload || messageEvent.data);
39
39
  }
40
- };
41
- const customEventListener = (event) => {
40
+ }
41
+ function customEventListener(event) {
42
42
  handler(event.detail);
43
- };
43
+ }
44
44
  if (!this.listeners.has(type)) {
45
45
  this.listeners.set(type, new Map);
46
46
  }
@@ -50,7 +50,7 @@ class PlaycademyMessaging {
50
50
  customEvent: customEventListener
51
51
  });
52
52
  window.addEventListener("message", postMessageListener);
53
- window.addEventListener(type, customEventListener);
53
+ globalThis.addEventListener(type, customEventListener);
54
54
  }
55
55
  unlisten(type, handler) {
56
56
  const typeListeners = this.listeners.get(type);
@@ -59,14 +59,14 @@ class PlaycademyMessaging {
59
59
  }
60
60
  const listeners = typeListeners.get(handler);
61
61
  window.removeEventListener("message", listeners.postMessage);
62
- window.removeEventListener(type, listeners.customEvent);
62
+ globalThis.removeEventListener(type, listeners.customEvent);
63
63
  typeListeners.delete(handler);
64
64
  if (typeListeners.size === 0) {
65
65
  this.listeners.delete(type);
66
66
  }
67
67
  }
68
68
  getMessagingContext(eventType) {
69
- const isIframe = typeof window !== "undefined" && window.self !== window.top;
69
+ const isIframe = typeof globalThis.window !== "undefined" && globalThis.self !== window.top;
70
70
  const iframeToParentEvents = [
71
71
  "PLAYCADEMY_READY" /* READY */,
72
72
  "PLAYCADEMY_EXIT" /* EXIT */,
@@ -89,18 +89,18 @@ class PlaycademyMessaging {
89
89
  target.postMessage(messageData, origin);
90
90
  }
91
91
  sendViaCustomEvent(type, payload) {
92
- window.dispatchEvent(new CustomEvent(type, { detail: payload }));
92
+ globalThis.dispatchEvent(new CustomEvent(type, { detail: payload }));
93
93
  }
94
94
  }
95
95
  var messaging = new PlaycademyMessaging;
96
96
 
97
97
  // src/core/static/init.ts
98
98
  async function getPlaycademyConfig(allowedParentOrigins) {
99
- const preloaded = window.PLAYCADEMY;
99
+ const preloaded = globalThis.PLAYCADEMY;
100
100
  if (preloaded?.token) {
101
101
  return preloaded;
102
102
  }
103
- if (window.self !== window.top) {
103
+ if (globalThis.self !== window.top) {
104
104
  return await waitForPlaycademyInit(allowedParentOrigins);
105
105
  } else {
106
106
  return createStandaloneConfig();
@@ -114,13 +114,14 @@ function getReferrerOrigin() {
114
114
  }
115
115
  }
116
116
  function buildAllowedOrigins(explicit) {
117
- if (Array.isArray(explicit) && explicit.length > 0)
117
+ if (Array.isArray(explicit) && explicit.length > 0) {
118
118
  return explicit;
119
+ }
119
120
  const ref = getReferrerOrigin();
120
121
  return ref ? [ref] : [];
121
122
  }
122
123
  function isOriginAllowed(origin, allowlist) {
123
- if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") {
124
+ if (globalThis.location.hostname === "localhost" || globalThis.location.hostname === "127.0.0.1") {
124
125
  return true;
125
126
  }
126
127
  if (!allowlist || allowlist.length === 0) {
@@ -136,14 +137,16 @@ async function waitForPlaycademyInit(allowedParentOrigins) {
136
137
  const allowlist = buildAllowedOrigins(allowedParentOrigins);
137
138
  let hasWarnedAboutUntrustedOrigin = false;
138
139
  function warnAboutUntrustedOrigin(origin) {
139
- if (hasWarnedAboutUntrustedOrigin)
140
+ if (hasWarnedAboutUntrustedOrigin) {
140
141
  return;
142
+ }
141
143
  hasWarnedAboutUntrustedOrigin = true;
142
144
  console.warn("[Playcademy SDK] Ignoring INIT from untrusted origin:", origin);
143
145
  }
144
- const handleMessage = (event) => {
145
- if (event.data?.type !== "PLAYCADEMY_INIT" /* INIT */)
146
+ function handleMessage(event) {
147
+ if (event.data?.type !== "PLAYCADEMY_INIT" /* INIT */) {
146
148
  return;
149
+ }
147
150
  if (!isOriginAllowed(event.origin, allowlist)) {
148
151
  warnAboutUntrustedOrigin(event.origin);
149
152
  return;
@@ -151,9 +154,9 @@ async function waitForPlaycademyInit(allowedParentOrigins) {
151
154
  contextReceived = true;
152
155
  window.removeEventListener("message", handleMessage);
153
156
  clearTimeout(timeoutId);
154
- window.PLAYCADEMY = event.data.payload;
157
+ globalThis.PLAYCADEMY = event.data.payload;
155
158
  resolve(event.data.payload);
156
- };
159
+ }
157
160
  window.addEventListener("message", handleMessage);
158
161
  const timeoutId = setTimeout(() => {
159
162
  if (!contextReceived) {
@@ -167,16 +170,16 @@ function createStandaloneConfig() {
167
170
  console.debug("[Playcademy SDK] Standalone mode detected, creating mock context for sandbox development");
168
171
  const mockConfig = {
169
172
  baseUrl: "http://localhost:4321",
170
- gameUrl: window.location.origin,
173
+ gameUrl: globalThis.location.origin,
171
174
  token: "mock-game-token-for-local-dev",
172
175
  gameId: "mock-game-id-from-template",
173
176
  realtimeUrl: undefined
174
177
  };
175
- window.PLAYCADEMY = mockConfig;
178
+ globalThis.PLAYCADEMY = mockConfig;
176
179
  return mockConfig;
177
180
  }
178
181
  async function init(options) {
179
- if (typeof window === "undefined") {
182
+ if (typeof globalThis.window === "undefined") {
180
183
  throw new Error("Playcademy SDK must run in a browser context");
181
184
  }
182
185
  const config = await getPlaycademyConfig(options?.allowedParentOrigins);
@@ -188,7 +191,7 @@ async function init(options) {
188
191
  gameUrl: config.gameUrl,
189
192
  token: config.token,
190
193
  gameId: config.gameId,
191
- autoStartSession: window.self !== window.top,
194
+ autoStartSession: globalThis.self !== window.top,
192
195
  onDisconnect: options?.onDisconnect,
193
196
  enableConnectionMonitoring: options?.enableConnectionMonitoring
194
197
  });
@@ -198,23 +201,23 @@ async function init(options) {
198
201
  return client;
199
202
  }
200
203
  // ../logger/src/index.ts
201
- var isBrowser = () => {
204
+ function isBrowser() {
202
205
  const g = globalThis;
203
206
  return typeof g.window !== "undefined" && typeof g.document !== "undefined";
204
- };
205
- var isProduction = () => {
207
+ }
208
+ function isProduction() {
206
209
  return typeof process !== "undefined" && false;
207
- };
208
- var isDevelopment = () => {
210
+ }
211
+ function isDevelopment() {
209
212
  return typeof process !== "undefined" && true;
210
- };
211
- var isInteractiveTTY = () => {
213
+ }
214
+ function isInteractiveTTY() {
212
215
  return typeof process !== "undefined" && Boolean(process.stdout && process.stdout.isTTY);
213
- };
214
- var isSilent = () => {
216
+ }
217
+ function isSilent() {
215
218
  return typeof process !== "undefined" && process.env.LOG_SILENT === "true";
216
- };
217
- var detectOutputFormat = () => {
219
+ }
220
+ function detectOutputFormat() {
218
221
  if (isBrowser()) {
219
222
  return "browser";
220
223
  }
@@ -238,7 +241,7 @@ var detectOutputFormat = () => {
238
241
  return "color-tty";
239
242
  }
240
243
  return "json-single-line";
241
- };
244
+ }
242
245
  var colors = {
243
246
  reset: "\x1B[0m",
244
247
  bold: "\x1B[1m",
@@ -249,21 +252,26 @@ var colors = {
249
252
  cyan: "\x1B[36m",
250
253
  gray: "\x1B[90m"
251
254
  };
252
- var getLevelColor = (level) => {
255
+ function getLevelColor(level) {
253
256
  switch (level) {
254
- case "debug":
257
+ case "debug": {
255
258
  return colors.blue;
256
- case "info":
259
+ }
260
+ case "info": {
257
261
  return colors.cyan;
258
- case "warn":
262
+ }
263
+ case "warn": {
259
264
  return colors.yellow;
260
- case "error":
265
+ }
266
+ case "error": {
261
267
  return colors.red;
262
- default:
268
+ }
269
+ default: {
263
270
  return colors.reset;
271
+ }
264
272
  }
265
- };
266
- var formatBrowserOutput = (level, message, context, scope) => {
273
+ }
274
+ function formatBrowserOutput(level, message, context, scope) {
267
275
  const timestamp = new Date().toISOString();
268
276
  const levelUpper = level.toUpperCase();
269
277
  const consoleMethod = getConsoleMethod(level);
@@ -273,8 +281,8 @@ var formatBrowserOutput = (level, message, context, scope) => {
273
281
  } else {
274
282
  consoleMethod(`[${timestamp}] ${levelUpper}`, `${scopePrefix}${message}`);
275
283
  }
276
- };
277
- var formatColorTTY = (level, message, context, scope) => {
284
+ }
285
+ function formatColorTTY(level, message, context, scope) {
278
286
  const timestamp = new Date().toISOString();
279
287
  const levelColor = getLevelColor(level);
280
288
  const levelUpper = level.toUpperCase().padEnd(5);
@@ -286,8 +294,8 @@ var formatColorTTY = (level, message, context, scope) => {
286
294
  } else {
287
295
  consoleMethod(`${coloredPrefix} ${scopePrefix}${message}`);
288
296
  }
289
- };
290
- var formatJSONSingleLine = (level, message, context, scope) => {
297
+ }
298
+ function formatJSONSingleLine(level, message, context, scope) {
291
299
  const timestamp = new Date().toISOString();
292
300
  const logEntry = {
293
301
  timestamp,
@@ -298,8 +306,8 @@ var formatJSONSingleLine = (level, message, context, scope) => {
298
306
  };
299
307
  const consoleMethod = getConsoleMethod(level);
300
308
  consoleMethod(JSON.stringify(logEntry));
301
- };
302
- var formatJSONPretty = (level, message, context, scope) => {
309
+ }
310
+ function formatJSONPretty(level, message, context, scope) {
303
311
  const timestamp = new Date().toISOString();
304
312
  const logEntry = {
305
313
  timestamp,
@@ -310,65 +318,76 @@ var formatJSONPretty = (level, message, context, scope) => {
310
318
  };
311
319
  const consoleMethod = getConsoleMethod(level);
312
320
  consoleMethod(JSON.stringify(logEntry, null, 2));
313
- };
314
- var getConsoleMethod = (level) => {
321
+ }
322
+ function getConsoleMethod(level) {
315
323
  switch (level) {
316
- case "debug":
324
+ case "debug": {
317
325
  return console.debug;
318
- case "info":
326
+ }
327
+ case "info": {
319
328
  return console.info;
320
- case "warn":
329
+ }
330
+ case "warn": {
321
331
  return console.warn;
322
- case "error":
332
+ }
333
+ case "error": {
323
334
  return console.error;
324
- default:
335
+ }
336
+ default: {
325
337
  return console.log;
338
+ }
326
339
  }
327
- };
340
+ }
328
341
  var levelPriority = {
329
342
  debug: 0,
330
343
  info: 1,
331
344
  warn: 2,
332
345
  error: 3
333
346
  };
334
- var getMinimumLogLevel = () => {
347
+ function getMinimumLogLevel() {
335
348
  const envLevel = typeof process !== "undefined" ? (process.env.LOG_LEVEL ?? "").toLowerCase() : "";
336
349
  if (envLevel && ["debug", "info", "warn", "error"].includes(envLevel)) {
337
350
  return envLevel;
338
351
  }
339
352
  return isProduction() ? "info" : "debug";
340
- };
341
- var shouldLog = (level) => {
342
- if (isSilent())
353
+ }
354
+ function shouldLog(level) {
355
+ if (isSilent()) {
343
356
  return false;
357
+ }
344
358
  const minLevel = getMinimumLogLevel();
345
359
  return levelPriority[level] >= levelPriority[minLevel];
346
- };
360
+ }
347
361
  var customHandler;
348
- var performLog = (level, message, context, scope) => {
349
- if (!shouldLog(level))
362
+ function performLog(level, message, context, scope) {
363
+ if (!shouldLog(level)) {
350
364
  return;
365
+ }
351
366
  if (customHandler) {
352
367
  customHandler(level, message, context, scope);
353
368
  return;
354
369
  }
355
370
  const outputFormat = detectOutputFormat();
356
371
  switch (outputFormat) {
357
- case "browser":
372
+ case "browser": {
358
373
  formatBrowserOutput(level, message, context, scope);
359
374
  break;
360
- case "color-tty":
375
+ }
376
+ case "color-tty": {
361
377
  formatColorTTY(level, message, context, scope);
362
378
  break;
363
- case "json-single-line":
379
+ }
380
+ case "json-single-line": {
364
381
  formatJSONSingleLine(level, message, context, scope);
365
382
  break;
366
- case "json-pretty":
383
+ }
384
+ case "json-pretty": {
367
385
  formatJSONPretty(level, message, context, scope);
368
386
  break;
387
+ }
369
388
  }
370
- };
371
- var createLogger = (scopeName) => {
389
+ }
390
+ function createLogger(scopeName) {
372
391
  return {
373
392
  debug: (message, context) => performLog("debug", message, context, scopeName),
374
393
  info: (message, context) => performLog("info", message, context, scopeName),
@@ -377,7 +396,7 @@ var createLogger = (scopeName) => {
377
396
  log: (level, message, context) => performLog(level, message, context, scopeName),
378
397
  scope: (name) => createLogger(scopeName ? `${scopeName}.${name}` : name)
379
398
  };
380
- };
399
+ }
381
400
  var log = createLogger();
382
401
 
383
402
  // src/core/errors.ts
@@ -389,14 +408,14 @@ class PlaycademyError extends Error {
389
408
  }
390
409
 
391
410
  class ApiError extends Error {
392
- status;
393
411
  code;
394
412
  details;
395
413
  rawBody;
414
+ status;
396
415
  constructor(status, code, message, details, rawBody) {
397
416
  super(message);
398
- this.status = status;
399
417
  this.name = "ApiError";
418
+ this.status = status;
400
419
  this.code = code;
401
420
  this.details = details;
402
421
  this.rawBody = rawBody;
@@ -427,38 +446,54 @@ class ApiError extends Error {
427
446
  }
428
447
  function statusCodeToErrorCode(status) {
429
448
  switch (status) {
430
- case 400:
449
+ case 400: {
431
450
  return "BAD_REQUEST";
432
- case 401:
451
+ }
452
+ case 401: {
433
453
  return "UNAUTHORIZED";
434
- case 403:
454
+ }
455
+ case 403: {
435
456
  return "FORBIDDEN";
436
- case 404:
457
+ }
458
+ case 404: {
437
459
  return "NOT_FOUND";
438
- case 405:
460
+ }
461
+ case 405: {
439
462
  return "METHOD_NOT_ALLOWED";
440
- case 409:
463
+ }
464
+ case 409: {
441
465
  return "CONFLICT";
442
- case 410:
466
+ }
467
+ case 410: {
443
468
  return "GONE";
444
- case 412:
469
+ }
470
+ case 412: {
445
471
  return "PRECONDITION_FAILED";
446
- case 413:
472
+ }
473
+ case 413: {
447
474
  return "PAYLOAD_TOO_LARGE";
448
- case 422:
475
+ }
476
+ case 422: {
449
477
  return "VALIDATION_FAILED";
450
- case 429:
478
+ }
479
+ case 429: {
451
480
  return "TOO_MANY_REQUESTS";
452
- case 500:
481
+ }
482
+ case 500: {
453
483
  return "INTERNAL_ERROR";
454
- case 501:
484
+ }
485
+ case 501: {
455
486
  return "NOT_IMPLEMENTED";
456
- case 503:
487
+ }
488
+ case 503: {
457
489
  return "SERVICE_UNAVAILABLE";
458
- case 504:
490
+ }
491
+ case 504: {
459
492
  return "TIMEOUT";
460
- default:
493
+ }
494
+ default: {
461
495
  return status >= 500 ? "INTERNAL_ERROR" : "BAD_REQUEST";
496
+ }
462
497
  }
463
498
  }
464
499
  function extractApiErrorInfo(error) {
@@ -476,10 +511,10 @@ function extractApiErrorInfo(error) {
476
511
  // src/core/static/login.ts
477
512
  async function login(baseUrl, email, password) {
478
513
  let url = baseUrl;
479
- if (baseUrl.startsWith("/") && typeof window !== "undefined") {
480
- url = window.location.origin + baseUrl;
514
+ if (baseUrl.startsWith("/") && typeof globalThis.window !== "undefined") {
515
+ url = globalThis.location.origin + baseUrl;
481
516
  }
482
- url = url + "/auth/login";
517
+ url += "/auth/login";
483
518
  const response = await fetch(url, {
484
519
  method: "POST",
485
520
  headers: {
@@ -490,7 +525,7 @@ async function login(baseUrl, email, password) {
490
525
  if (!response.ok) {
491
526
  try {
492
527
  const errorData = await response.json();
493
- const errorMessage = errorData && errorData.message ? String(errorData.message) : response.statusText;
528
+ const errorMessage = errorData && errorData.message !== undefined ? String(errorData.message) : response.statusText;
494
529
  throw new PlaycademyError(errorMessage);
495
530
  } catch (error) {
496
531
  log.error("[Playcademy SDK] Failed to parse error response JSON, using status text instead:", { error });
@@ -504,7 +539,7 @@ async function generateSecureRandomString(length) {
504
539
  const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
505
540
  const randomValues = new Uint8Array(length);
506
541
  globalThis.crypto.getRandomValues(randomValues);
507
- return Array.from(randomValues).map((byte) => charset[byte % charset.length]).join("");
542
+ return [...randomValues].map((byte) => charset[byte % charset.length]).join("");
508
543
  }
509
544
 
510
545
  // src/core/auth/oauth.ts
@@ -546,8 +581,9 @@ function parseOAuthState(state) {
546
581
  }
547
582
  function getOAuthConfig(provider) {
548
583
  const configGetter = OAUTH_CONFIGS[provider];
549
- if (!configGetter)
584
+ if (!configGetter) {
550
585
  throw new Error(`Unsupported auth provider: ${provider}`);
586
+ }
551
587
  return configGetter();
552
588
  }
553
589
 
@@ -574,11 +610,11 @@ function openPopupWindow(url, name = "auth-popup", width = 500, height = 600) {
574
610
  return window.open(url, name, features);
575
611
  }
576
612
  function isInIframe() {
577
- if (typeof window === "undefined") {
613
+ if (typeof globalThis.window === "undefined") {
578
614
  return false;
579
615
  }
580
616
  try {
581
- return window.self !== window.top;
617
+ return globalThis.self !== window.top;
582
618
  } catch {
583
619
  return true;
584
620
  }
@@ -631,9 +667,10 @@ async function initiatePopupFlow(options) {
631
667
  async function waitForServerMessage(popup, onStateChange) {
632
668
  return new Promise((resolve) => {
633
669
  let resolved = false;
634
- const handleMessage = (event) => {
635
- if (event.origin !== window.location.origin)
670
+ function handleMessage(event) {
671
+ if (event.origin !== globalThis.location.origin) {
636
672
  return;
673
+ }
637
674
  const data = event.data;
638
675
  if (data?.type === "PLAYCADEMY_AUTH_STATE_CHANGE") {
639
676
  resolved = true;
@@ -660,7 +697,7 @@ async function waitForServerMessage(popup, onStateChange) {
660
697
  });
661
698
  }
662
699
  }
663
- };
700
+ }
664
701
  window.addEventListener("message", handleMessage);
665
702
  const checkClosed = setInterval(() => {
666
703
  if (popup.closed && !resolved) {
@@ -722,7 +759,7 @@ async function initiateRedirectFlow(options) {
722
759
  params.set("scope", config.scope);
723
760
  }
724
761
  const authUrl = `${config.authorizationEndpoint}?${params.toString()}`;
725
- window.location.href = authUrl;
762
+ globalThis.location.href = authUrl;
726
763
  return new Promise(() => {});
727
764
  } catch (error) {
728
765
  const errorMessage = error instanceof Error ? error.message : "Authentication failed";
@@ -738,14 +775,22 @@ async function initiateRedirectFlow(options) {
738
775
  // src/core/auth/flows/unified.ts
739
776
  async function initiateUnifiedFlow(options) {
740
777
  const { mode = "auto" } = options;
741
- const effectiveMode = mode === "auto" ? isInIframe() ? "popup" : "redirect" : mode;
778
+ let effectiveMode;
779
+ if (mode === "auto") {
780
+ effectiveMode = isInIframe() ? "popup" : "redirect";
781
+ } else {
782
+ effectiveMode = mode;
783
+ }
742
784
  switch (effectiveMode) {
743
- case "popup":
785
+ case "popup": {
744
786
  return initiatePopupFlow(options);
745
- case "redirect":
787
+ }
788
+ case "redirect": {
746
789
  return initiateRedirectFlow(options);
747
- default:
790
+ }
791
+ default: {
748
792
  throw new Error(`Unsupported authentication mode: ${effectiveMode}`);
793
+ }
749
794
  }
750
795
  }
751
796
 
@@ -767,7 +812,7 @@ async function login2(client, options) {
767
812
  provider: options.provider,
768
813
  mode: options.mode || "auto",
769
814
  callbackUrl: options.callbackUrl,
770
- hasStateData: !!stateData
815
+ hasStateData: Boolean(stateData)
771
816
  });
772
817
  const optionsWithState = {
773
818
  ...options,
@@ -802,13 +847,13 @@ function createIdentityNamespace(client) {
802
847
  // src/namespaces/game/runtime.ts
803
848
  function createRuntimeNamespace(client) {
804
849
  const eventListeners = new Map;
805
- const trackListener = (eventType, handler) => {
850
+ function trackListener(eventType, handler) {
806
851
  if (!eventListeners.has(eventType)) {
807
852
  eventListeners.set(eventType, new Set);
808
853
  }
809
854
  eventListeners.get(eventType).add(handler);
810
- };
811
- const untrackListener = (eventType, handler) => {
855
+ }
856
+ function untrackListener(eventType, handler) {
812
857
  const listeners = eventListeners.get(eventType);
813
858
  if (listeners) {
814
859
  listeners.delete(handler);
@@ -816,12 +861,9 @@ function createRuntimeNamespace(client) {
816
861
  eventListeners.delete(eventType);
817
862
  }
818
863
  }
819
- };
820
- if (typeof window !== "undefined" && window.self !== window.top) {
821
- const playcademyConfig = window.PLAYCADEMY;
822
- const forwardKeys = Array.isArray(playcademyConfig?.forwardKeys) ? playcademyConfig.forwardKeys : ["Escape"];
823
- const keySet = new Set(forwardKeys.map((k) => k.toLowerCase()));
824
- const keyListener = (event) => {
864
+ }
865
+ if (typeof globalThis.window !== "undefined" && globalThis.self !== window.top) {
866
+ let keyListener = function(event) {
825
867
  if (keySet.has(event.key?.toLowerCase() ?? "") || keySet.has(event.code?.toLowerCase() ?? "")) {
826
868
  messaging.send("PLAYCADEMY_KEY_EVENT" /* KEY_EVENT */, {
827
869
  key: event.key,
@@ -830,11 +872,14 @@ function createRuntimeNamespace(client) {
830
872
  });
831
873
  }
832
874
  };
833
- window.addEventListener("keydown", keyListener);
834
- window.addEventListener("keyup", keyListener);
875
+ const playcademyConfig = globalThis.PLAYCADEMY;
876
+ const forwardKeys = Array.isArray(playcademyConfig?.forwardKeys) ? playcademyConfig.forwardKeys : ["Escape"];
877
+ const keySet = new Set(forwardKeys.map((k) => k.toLowerCase()));
878
+ globalThis.addEventListener("keydown", keyListener);
879
+ globalThis.addEventListener("keyup", keyListener);
835
880
  trackListener("PLAYCADEMY_FORCE_EXIT" /* FORCE_EXIT */, () => {
836
- window.removeEventListener("keydown", keyListener);
837
- window.removeEventListener("keyup", keyListener);
881
+ globalThis.removeEventListener("keydown", keyListener);
882
+ globalThis.removeEventListener("keyup", keyListener);
838
883
  });
839
884
  }
840
885
  return {
@@ -911,32 +956,30 @@ function createRuntimeNamespace(client) {
911
956
  };
912
957
  }
913
958
  function createAssetsNamespace(client) {
914
- const fetchAsset = async (path, options) => {
959
+ async function fetchAsset(path, options) {
915
960
  const gameUrl = client["initPayload"]?.gameUrl;
916
961
  if (!gameUrl) {
917
- const relativePath = path.startsWith("./") ? path : "./" + path;
962
+ const relativePath = path.startsWith("./") ? path : `./${path}`;
918
963
  return fetch(relativePath, options);
919
964
  }
920
965
  const cleanPath = path.startsWith("./") ? path.slice(2) : path;
921
- return fetch(gameUrl + cleanPath, options);
922
- };
966
+ return fetch(`${gameUrl}${cleanPath}`, options);
967
+ }
923
968
  return {
924
969
  url(pathOrStrings, ...values) {
925
970
  const gameUrl = client["initPayload"]?.gameUrl;
926
971
  let path;
927
972
  if (Array.isArray(pathOrStrings) && "raw" in pathOrStrings) {
928
973
  const strings = pathOrStrings;
929
- path = strings.reduce((acc, str, i) => {
930
- return acc + str + (values[i] != null ? String(values[i]) : "");
931
- }, "");
974
+ path = strings.reduce((acc, str, i) => acc + str + (values[i] != null ? String(values[i]) : ""), "");
932
975
  } else {
933
976
  path = pathOrStrings;
934
977
  }
935
978
  if (!gameUrl) {
936
- return path.startsWith("./") ? path : "./" + path;
979
+ return path.startsWith("./") ? path : `./${path}`;
937
980
  }
938
981
  const cleanPath = path.startsWith("./") ? path.slice(2) : path;
939
- return gameUrl + cleanPath;
982
+ return `${gameUrl}${cleanPath}`;
940
983
  },
941
984
  fetch: fetchAsset,
942
985
  json: async (path) => {
@@ -958,10 +1001,10 @@ function createAssetsNamespace(client) {
958
1001
  };
959
1002
  }
960
1003
  // src/namespaces/game/backend.ts
1004
+ function normalizePath(path) {
1005
+ return path.startsWith("/") ? path : `/${path}`;
1006
+ }
961
1007
  function createBackendNamespace(client) {
962
- function normalizePath(path) {
963
- return path.startsWith("/") ? path : `/${path}`;
964
- }
965
1008
  return {
966
1009
  async get(path, headers) {
967
1010
  return client["requestGameBackend"](normalizePath(path), "GET", undefined, headers);
@@ -987,9 +1030,7 @@ function createBackendNamespace(client) {
987
1030
  url(pathOrStrings, ...values) {
988
1031
  if (Array.isArray(pathOrStrings) && "raw" in pathOrStrings) {
989
1032
  const strings = pathOrStrings;
990
- const path2 = strings.reduce((acc, str, i) => {
991
- return acc + str + (values[i] != null ? String(values[i]) : "");
992
- }, "");
1033
+ const path2 = strings.reduce((acc, str, i) => acc + str + (values[i] != null ? String(values[i]) : ""), "");
993
1034
  return `${client.gameUrl}/api${path2.startsWith("/") ? path2 : `/${path2}`}`;
994
1035
  }
995
1036
  const path = pathOrStrings;
@@ -1003,8 +1044,9 @@ function createPermanentCache(keyPrefix) {
1003
1044
  async function get(key, loader) {
1004
1045
  const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
1005
1046
  const existing = cache.get(fullKey);
1006
- if (existing)
1047
+ if (existing) {
1007
1048
  return existing;
1049
+ }
1008
1050
  const promise = loader().catch((error) => {
1009
1051
  cache.delete(fullKey);
1010
1052
  throw error;
@@ -1042,23 +1084,23 @@ function createPermanentCache(keyPrefix) {
1042
1084
  function createUsersNamespace(client) {
1043
1085
  const itemIdCache = createPermanentCache("items");
1044
1086
  const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1045
- const resolveItemId = async (identifier) => {
1046
- if (UUID_REGEX.test(identifier))
1087
+ async function resolveItemId(identifier) {
1088
+ if (UUID_REGEX.test(identifier)) {
1047
1089
  return identifier;
1090
+ }
1048
1091
  const gameId = client["gameId"];
1049
1092
  const cacheKey = gameId ? `${identifier}:${gameId}` : identifier;
1050
1093
  return itemIdCache.get(cacheKey, async () => {
1051
1094
  const queryParams = new URLSearchParams({ slug: identifier });
1052
- if (gameId)
1095
+ if (gameId) {
1053
1096
  queryParams.append("gameId", gameId);
1097
+ }
1054
1098
  const item = await client["request"](`/items/resolve?${queryParams.toString()}`, "GET");
1055
1099
  return item.id;
1056
1100
  });
1057
- };
1101
+ }
1058
1102
  return {
1059
- me: async () => {
1060
- return client["request"]("/users/me", "GET");
1061
- },
1103
+ me: async () => client["request"]("/users/me", "GET"),
1062
1104
  inventory: {
1063
1105
  get: async () => client["request"](`/inventory`, "GET"),
1064
1106
  add: async (identifier, qty) => {
@@ -1185,8 +1227,15 @@ var ACHIEVEMENT_DEFINITIONS = [
1185
1227
  }
1186
1228
  ];
1187
1229
  // ../constants/src/typescript.ts
1188
- var TSC_PACKAGE = "typescript";
1189
- var USE_NATIVE_TSC = TSC_PACKAGE.includes("native-preview");
1230
+ var TypeScriptPackages = {
1231
+ tsc: "tsc",
1232
+ nativePreview: "@typescript/native-preview",
1233
+ nativePreviewPinned: "@typescript/native-preview@7.0.0-dev.20260221.1"
1234
+ };
1235
+ var TYPESCRIPT_RUNNER = {
1236
+ package: TypeScriptPackages.nativePreviewPinned,
1237
+ bin: "tsgo"
1238
+ };
1190
1239
  // ../constants/src/overworld.ts
1191
1240
  var ITEM_SLUGS = {
1192
1241
  PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
@@ -1241,7 +1290,7 @@ function createSingletonCache() {
1241
1290
  // src/namespaces/game/credits.ts
1242
1291
  function createCreditsNamespace(client) {
1243
1292
  const creditsIdCache = createSingletonCache();
1244
- const getCreditsItemId = async () => {
1293
+ async function getCreditsItemId() {
1245
1294
  return creditsIdCache.get(async () => {
1246
1295
  const queryParams = new URLSearchParams({ slug: CURRENCIES.PRIMARY });
1247
1296
  const creditsItem = await client["request"](`/items/resolve?${queryParams.toString()}`, "GET");
@@ -1250,7 +1299,7 @@ function createCreditsNamespace(client) {
1250
1299
  }
1251
1300
  return creditsItem.id;
1252
1301
  });
1253
- };
1302
+ }
1254
1303
  return {
1255
1304
  balance: async () => {
1256
1305
  const inventory = await client["request"]("/inventory", "GET");
@@ -1298,14 +1347,12 @@ function createCreditsNamespace(client) {
1298
1347
  // src/namespaces/game/scores.ts
1299
1348
  function createScoresNamespace(client) {
1300
1349
  return {
1301
- submit: async (gameId, score, metadata) => {
1302
- return client["request"](`/games/${gameId}/scores`, "POST", {
1303
- body: {
1304
- score,
1305
- metadata
1306
- }
1307
- });
1308
- }
1350
+ submit: async (gameId, score, metadata) => client["request"](`/games/${gameId}/scores`, "POST", {
1351
+ body: {
1352
+ score,
1353
+ metadata
1354
+ }
1355
+ })
1309
1356
  };
1310
1357
  }
1311
1358
  // src/namespaces/game/realtime.ts
@@ -1379,8 +1426,9 @@ function createTTLCache(options) {
1379
1426
  function has(key) {
1380
1427
  const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
1381
1428
  const cached = cache.get(fullKey);
1382
- if (!cached)
1429
+ if (!cached) {
1383
1430
  return false;
1431
+ }
1384
1432
  const now = Date.now();
1385
1433
  if (cached.expiresAt <= now) {
1386
1434
  cache.delete(fullKey);
@@ -1422,7 +1470,9 @@ function createTimebackNamespace(client) {
1422
1470
  ttl: 5000,
1423
1471
  keyPrefix: "game.timeback.xp"
1424
1472
  });
1425
- const getTimeback = () => client["initPayload"]?.timeback;
1473
+ function getTimeback() {
1474
+ return client["initPayload"]?.timeback;
1475
+ }
1426
1476
  return {
1427
1477
  get user() {
1428
1478
  return {
@@ -1438,21 +1488,19 @@ function createTimebackNamespace(client) {
1438
1488
  get organizations() {
1439
1489
  return getTimeback()?.organizations ?? [];
1440
1490
  },
1441
- fetch: async (options) => {
1442
- return userCache.get("current", async () => {
1443
- const response = await client["request"]("/timeback/user", "GET");
1444
- const initPayload = client["initPayload"];
1445
- if (initPayload) {
1446
- initPayload.timeback = response;
1447
- }
1448
- return {
1449
- id: response.id,
1450
- role: response.role,
1451
- enrollments: response.enrollments,
1452
- organizations: response.organizations
1453
- };
1454
- }, options);
1455
- },
1491
+ fetch: async (options) => userCache.get("current", async () => {
1492
+ const response = await client["request"]("/timeback/user", "GET");
1493
+ const initPayload = client["initPayload"];
1494
+ if (initPayload) {
1495
+ initPayload.timeback = response;
1496
+ }
1497
+ return {
1498
+ id: response.id,
1499
+ role: response.role,
1500
+ enrollments: response.enrollments,
1501
+ organizations: response.organizations
1502
+ };
1503
+ }, options),
1456
1504
  xp: {
1457
1505
  fetch: async (options) => {
1458
1506
  const hasGrade = options?.grade !== undefined;
@@ -1590,22 +1638,18 @@ function createAuthNamespace(client) {
1590
1638
  client.setToken(null);
1591
1639
  },
1592
1640
  apiKeys: {
1593
- create: async (options) => {
1594
- return client["request"]("/dev/api-keys", "POST", {
1595
- body: {
1596
- name: options?.name || `SDK Key - ${new Date().toISOString()}`,
1597
- expiresIn: options?.expiresIn !== undefined ? options.expiresIn : null,
1598
- permissions: options?.permissions || {
1599
- games: ["read", "write", "delete"],
1600
- users: ["read:self", "write:self"],
1601
- dev: ["read", "write"]
1602
- }
1641
+ create: async (options) => client["request"]("/dev/api-keys", "POST", {
1642
+ body: {
1643
+ name: options?.name || `SDK Key - ${new Date().toISOString()}`,
1644
+ expiresIn: options?.expiresIn !== undefined ? options.expiresIn : null,
1645
+ permissions: options?.permissions || {
1646
+ games: ["read", "write", "delete"],
1647
+ users: ["read:self", "write:self"],
1648
+ dev: ["read", "write"]
1603
1649
  }
1604
- });
1605
- },
1606
- list: async () => {
1607
- return client["request"]("/auth/api-key/list", "GET");
1608
- },
1650
+ }
1651
+ }),
1652
+ list: async () => client["request"]("/auth/api-key/list", "GET"),
1609
1653
  revoke: async (keyId) => {
1610
1654
  await client["request"]("/auth/api-key/revoke", "POST", {
1611
1655
  body: { id: keyId }
@@ -1648,19 +1692,139 @@ function createAdminNamespace(client) {
1648
1692
  }
1649
1693
  };
1650
1694
  }
1651
- // src/namespaces/platform/dev.ts
1652
- function createDevNamespace(client) {
1653
- const DEPLOY_JOB_POLL_INTERVAL_MS = 1000;
1654
- const DEPLOY_JOB_INACTIVITY_TIMEOUT_MS = 60 * 1000;
1655
- async function pollDeployJob(slug, jobId, hooks) {
1695
+ // src/core/deploy.ts
1696
+ class DeployPipeline {
1697
+ static POLL_INTERVAL_MS = 1000;
1698
+ static INACTIVITY_TIMEOUT_MS = 60 * 1000;
1699
+ static GAME_FETCH_RETRIES = 3;
1700
+ static MAX_INLINE_REQUEST_BYTES = 5.5 * 1024 * 1024;
1701
+ static textEncoder = new TextEncoder;
1702
+ client;
1703
+ constructor(client) {
1704
+ this.client = client;
1705
+ }
1706
+ async uploadFile(file, gameId, hooks) {
1707
+ const fileName = file instanceof File ? file.name : "game.zip";
1708
+ const { presignedUrl, uploadToken } = await this.initiateUpload(fileName, gameId);
1709
+ const contentType = file.type || "application/octet-stream";
1710
+ if (hooks?.onEvent && typeof XMLHttpRequest !== "undefined") {
1711
+ await this.uploadViaXHR(presignedUrl, file, contentType, hooks);
1712
+ } else {
1713
+ await this.uploadViaFetch(presignedUrl, file, contentType);
1714
+ }
1715
+ return uploadToken;
1716
+ }
1717
+ async submit(args) {
1718
+ const { requestBody } = await this.buildRequestBody(args);
1719
+ const { slug, hooks } = args;
1720
+ const job = await this.client["request"](`/games/${slug}/deploy`, "POST", { body: requestBody });
1721
+ const completedJob = await this.poll(slug, job.id, hooks);
1722
+ if (!completedJob.result?.url) {
1723
+ throw new Error("Deployment completed but no deployment URL was recorded");
1724
+ }
1725
+ return this.fetchGameWithRetry(slug);
1726
+ }
1727
+ async buildRequestBody(args) {
1728
+ const game = await this.resolveGame(args.slug, args.game);
1729
+ const requestBody = {};
1730
+ if (args.uploadToken) {
1731
+ requestBody.uploadToken = args.uploadToken;
1732
+ }
1733
+ if (args.metadata) {
1734
+ requestBody.metadata = args.metadata;
1735
+ }
1736
+ if (!args.backend) {
1737
+ return { game, requestBody };
1738
+ }
1739
+ const backendFields = {
1740
+ config: args.backend.config,
1741
+ ...args.backend.bindings ? { bindings: args.backend.bindings } : {},
1742
+ ...args.backend.schema ? { schema: args.backend.schema } : {}
1743
+ };
1744
+ const inlineBody = {
1745
+ ...requestBody,
1746
+ ...backendFields,
1747
+ code: args.backend.code
1748
+ };
1749
+ if (this.serializedSize(inlineBody) <= DeployPipeline.MAX_INLINE_REQUEST_BYTES) {
1750
+ return { game, requestBody: inlineBody };
1751
+ }
1752
+ const skeletonBody = {
1753
+ ...requestBody,
1754
+ ...backendFields,
1755
+ codeUploadToken: "________placeholder________"
1756
+ };
1757
+ if (this.serializedSize(skeletonBody) > DeployPipeline.MAX_INLINE_REQUEST_BYTES) {
1758
+ throw new Error("Deploy request is too large even after uploading backend code");
1759
+ }
1760
+ skeletonBody.codeUploadToken = await this.uploadCode(game.id, args.backend.code);
1761
+ return { game, requestBody: skeletonBody };
1762
+ }
1763
+ serializedSize(body) {
1764
+ return DeployPipeline.textEncoder.encode(JSON.stringify(body)).length;
1765
+ }
1766
+ async initiateUpload(fileName, gameId) {
1767
+ return this.client["request"]("/games/uploads/initiate/", "POST", { body: { fileName, gameId } });
1768
+ }
1769
+ async uploadCode(gameId, code) {
1770
+ const { presignedUrl, uploadToken } = await this.initiateUpload("backend.js", gameId);
1771
+ const res = await fetch(presignedUrl, {
1772
+ method: "PUT",
1773
+ body: code,
1774
+ headers: { "Content-Type": "application/javascript" }
1775
+ });
1776
+ if (!res.ok) {
1777
+ throw new Error(`Backend code upload failed: ${res.status} ${res.statusText}`);
1778
+ }
1779
+ return uploadToken;
1780
+ }
1781
+ async uploadViaFetch(url, body, contentType) {
1782
+ const res = await fetch(url, {
1783
+ method: "PUT",
1784
+ body,
1785
+ headers: { "Content-Type": contentType }
1786
+ });
1787
+ if (!res.ok) {
1788
+ throw new Error(`File upload failed: ${res.status} ${res.statusText}`);
1789
+ }
1790
+ }
1791
+ uploadViaXHR(url, body, contentType, hooks) {
1792
+ return new Promise((resolve, reject) => {
1793
+ const xhr = new XMLHttpRequest;
1794
+ xhr.open("PUT", url, true);
1795
+ try {
1796
+ xhr.setRequestHeader("Content-Type", contentType);
1797
+ } catch {}
1798
+ xhr.upload.addEventListener("progress", (event) => {
1799
+ if (event.lengthComputable) {
1800
+ hooks.onEvent?.({
1801
+ type: "s3Progress",
1802
+ loaded: event.loaded,
1803
+ total: event.total,
1804
+ percent: event.loaded / event.total
1805
+ });
1806
+ }
1807
+ });
1808
+ xhr.addEventListener("load", () => {
1809
+ if (xhr.status >= 200 && xhr.status < 300) {
1810
+ resolve();
1811
+ } else {
1812
+ reject(new Error(`File upload failed: ${xhr.status} ${xhr.statusText}`));
1813
+ }
1814
+ });
1815
+ xhr.addEventListener("error", () => reject(new Error("File upload failed: network error")));
1816
+ xhr.send(body);
1817
+ });
1818
+ }
1819
+ async poll(slug, jobId, hooks) {
1656
1820
  hooks?.onEvent?.({ type: "finalizeStart" });
1657
1821
  let seenEvents = 0;
1658
1822
  let lastProgressAt = Date.now();
1659
1823
  while (true) {
1660
- if (Date.now() - lastProgressAt > DEPLOY_JOB_INACTIVITY_TIMEOUT_MS) {
1661
- throw new Error("Deployment job timed out after 5 minutes without progress");
1824
+ if (Date.now() - lastProgressAt > DeployPipeline.INACTIVITY_TIMEOUT_MS) {
1825
+ throw new Error("Deployment job timed out after 1 minute without progress");
1662
1826
  }
1663
- const job = await client["request"](`/games/${slug}/deploy?jobId=${encodeURIComponent(jobId)}`, "GET");
1827
+ const job = await this.client["request"](`/games/${slug}/deploy?jobId=${encodeURIComponent(jobId)}`, "GET");
1664
1828
  const newEvents = job.events.slice(seenEvents);
1665
1829
  seenEvents = job.events.length;
1666
1830
  if (newEvents.length > 0) {
@@ -1679,9 +1843,30 @@ function createDevNamespace(client) {
1679
1843
  if (job.status === "failed") {
1680
1844
  throw new ApiError(job.errorStatus ?? 500, job.errorCode ?? "INTERNAL_ERROR", job.error || "Deployment failed", job);
1681
1845
  }
1682
- await new Promise((resolve) => setTimeout(resolve, DEPLOY_JOB_POLL_INTERVAL_MS));
1846
+ await new Promise((resolve) => setTimeout(resolve, DeployPipeline.POLL_INTERVAL_MS));
1683
1847
  }
1684
1848
  }
1849
+ async resolveGame(slug, game) {
1850
+ return game ?? await this.client["request"](`/games/${slug}`, "GET");
1851
+ }
1852
+ async fetchGameWithRetry(slug) {
1853
+ const { GAME_FETCH_RETRIES } = DeployPipeline;
1854
+ for (let attempt = 0;attempt < GAME_FETCH_RETRIES; attempt++) {
1855
+ try {
1856
+ return await this.client["request"](`/games/${slug}`, "GET");
1857
+ } catch {
1858
+ if (attempt < GAME_FETCH_RETRIES - 1) {
1859
+ await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
1860
+ }
1861
+ }
1862
+ }
1863
+ throw new Error(`Deploy succeeded but failed to fetch updated game after ${GAME_FETCH_RETRIES} attempts`);
1864
+ }
1865
+ }
1866
+
1867
+ // src/namespaces/platform/dev.ts
1868
+ function createDevNamespace(client) {
1869
+ const deploy = new DeployPipeline(client);
1685
1870
  return {
1686
1871
  status: {
1687
1872
  apply: () => client["request"]("/dev/apply", "POST"),
@@ -1703,99 +1888,18 @@ function createDevNamespace(client) {
1703
1888
  return game;
1704
1889
  }
1705
1890
  }
1706
- let uploadToken;
1707
- if (file) {
1708
- const fileName = file instanceof File ? file.name : "game.zip";
1709
- const initiateResponse = await client["request"]("/games/uploads/initiate/", "POST", {
1710
- body: {
1711
- fileName,
1712
- gameId: game?.id || slug
1713
- }
1714
- });
1715
- uploadToken = initiateResponse.uploadToken;
1716
- if (hooks?.onEvent && typeof XMLHttpRequest !== "undefined") {
1717
- await new Promise((resolve, reject) => {
1718
- const xhr = new XMLHttpRequest;
1719
- xhr.open("PUT", initiateResponse.presignedUrl, true);
1720
- const contentType = file.type || "application/octet-stream";
1721
- try {
1722
- xhr.setRequestHeader("Content-Type", contentType);
1723
- } catch {}
1724
- xhr.upload.onprogress = (event) => {
1725
- if (event.lengthComputable) {
1726
- const percent = event.loaded / event.total;
1727
- hooks.onEvent?.({
1728
- type: "s3Progress",
1729
- loaded: event.loaded,
1730
- total: event.total,
1731
- percent
1732
- });
1733
- }
1734
- };
1735
- xhr.onload = () => {
1736
- if (xhr.status >= 200 && xhr.status < 300)
1737
- resolve();
1738
- else
1739
- reject(new Error(`File upload failed: ${xhr.status} ${xhr.statusText}`));
1740
- };
1741
- xhr.onerror = () => reject(new Error("File upload failed: network error"));
1742
- xhr.send(file);
1743
- });
1744
- } else {
1745
- const uploadResponse = await fetch(initiateResponse.presignedUrl, {
1746
- method: "PUT",
1747
- body: file,
1748
- headers: {
1749
- "Content-Type": file.type || "application/octet-stream"
1750
- }
1751
- });
1752
- if (!uploadResponse.ok) {
1753
- throw new Error(`File upload failed: ${uploadResponse.status} ${uploadResponse.statusText}`);
1754
- }
1755
- }
1756
- }
1891
+ const uploadToken = file ? await deploy.uploadFile(file, game?.id || slug, hooks) : undefined;
1757
1892
  if (uploadToken || backend) {
1758
- const requestBody = {};
1759
- if (uploadToken)
1760
- requestBody.uploadToken = uploadToken;
1761
- if (metadata)
1762
- requestBody.metadata = metadata;
1763
- if (backend) {
1764
- requestBody.code = backend.code;
1765
- requestBody.config = backend.config;
1766
- if (backend.bindings)
1767
- requestBody.bindings = backend.bindings;
1768
- if (backend.schema)
1769
- requestBody.schema = backend.schema;
1770
- }
1771
- const job = await client["request"](`/games/${slug}/deploy`, "POST", {
1772
- body: requestBody
1773
- });
1774
- const completedJob = await pollDeployJob(slug, job.id, hooks);
1775
- if (!completedJob.result?.url) {
1776
- throw new Error("Deployment completed but no deployment URL was recorded");
1777
- }
1778
- for (let attempt = 0;attempt < 3; attempt++) {
1779
- try {
1780
- return await client["request"](`/games/${slug}`, "GET");
1781
- } catch {
1782
- if (attempt < 2) {
1783
- await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
1784
- }
1785
- }
1786
- }
1787
- throw new Error("Deploy succeeded but failed to fetch updated game after 3 attempts");
1893
+ return deploy.submit({ slug, game, uploadToken, metadata, backend, hooks });
1788
1894
  }
1789
1895
  if (game) {
1790
1896
  return game;
1791
1897
  }
1792
1898
  throw new Error("No deployment actions specified (need metadata, file, or backend)");
1793
1899
  },
1794
- seed: async (slug, code, environment, secrets) => {
1795
- return client["request"](`/games/${slug}/seed`, "POST", {
1796
- body: { code, environment, secrets }
1797
- });
1798
- },
1900
+ seed: async (slug, code, environment, secrets) => client["request"](`/games/${slug}/seed`, "POST", {
1901
+ body: { code, environment, secrets }
1902
+ }),
1799
1903
  upsert: async (slug, metadata) => client["request"](`/games/${slug}`, "PUT", { body: metadata }),
1800
1904
  delete: (gameId) => client["request"](`/games/${gameId}`, "DELETE"),
1801
1905
  secrets: {
@@ -1812,11 +1916,9 @@ function createDevNamespace(client) {
1812
1916
  }
1813
1917
  },
1814
1918
  database: {
1815
- reset: async (slug, schema) => {
1816
- return client["request"](`/games/${slug}/database/reset`, "POST", {
1817
- body: { schema }
1818
- });
1819
- }
1919
+ reset: async (slug, schema) => client["request"](`/games/${slug}/database/reset`, "POST", {
1920
+ body: { schema }
1921
+ })
1820
1922
  },
1821
1923
  bucket: {
1822
1924
  list: async (slug, prefix) => {
@@ -1868,9 +1970,7 @@ function createDevNamespace(client) {
1868
1970
  const result = await client["request"](`/games/${slug}/kv/${encodeURIComponent(key)}/metadata`, "GET");
1869
1971
  return result.metadata;
1870
1972
  },
1871
- stats: async (slug) => {
1872
- return client["request"](`/games/${slug}/kv/stats`, "GET");
1873
- },
1973
+ stats: async (slug) => client["request"](`/games/${slug}/kv/stats`, "GET"),
1874
1974
  seed: async (slug, entries) => {
1875
1975
  const result = await client["request"](`/games/${slug}/kv/bulk`, "PUT", { body: { entries } });
1876
1976
  return result.count;
@@ -1881,11 +1981,9 @@ function createDevNamespace(client) {
1881
1981
  }
1882
1982
  },
1883
1983
  domains: {
1884
- add: async (slug, hostname) => {
1885
- return client["request"](`/games/${slug}/domains`, "POST", {
1886
- body: { hostname }
1887
- });
1888
- },
1984
+ add: async (slug, hostname) => client["request"](`/games/${slug}/domains`, "POST", {
1985
+ body: { hostname }
1986
+ }),
1889
1987
  list: async (slug) => {
1890
1988
  const result = await client["request"](`/games/${slug}/domains`, "GET");
1891
1989
  return result.domains;
@@ -1899,9 +1997,7 @@ function createDevNamespace(client) {
1899
1997
  }
1900
1998
  },
1901
1999
  logs: {
1902
- getToken: async (slug, environment) => {
1903
- return client["request"](`/games/${slug}/logs/token`, "POST", { body: { environment } });
1904
- }
2000
+ getToken: async (slug, environment) => client["request"](`/games/${slug}/logs/token`, "POST", { body: { environment } })
1905
2001
  }
1906
2002
  },
1907
2003
  items: {
@@ -1921,35 +2017,27 @@ function createDevNamespace(client) {
1921
2017
  },
1922
2018
  delete: (gameId, itemId) => client["request"](`/games/${gameId}/items/${itemId}`, "DELETE"),
1923
2019
  shop: {
1924
- create: (gameId, itemId, listingData) => {
1925
- return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "POST", { body: listingData });
1926
- },
1927
- get: (gameId, itemId) => {
1928
- return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "GET");
1929
- },
1930
- update: (gameId, itemId, updates) => {
1931
- return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "PATCH", { body: updates });
1932
- },
1933
- delete: (gameId, itemId) => {
1934
- return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "DELETE");
1935
- },
1936
- list: (gameId) => {
1937
- return client["request"](`/games/${gameId}/shop-listings`, "GET");
1938
- }
2020
+ create: (gameId, itemId, listingData) => client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "POST", { body: listingData }),
2021
+ get: (gameId, itemId) => client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "GET"),
2022
+ update: (gameId, itemId, updates) => client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "PATCH", { body: updates }),
2023
+ delete: (gameId, itemId) => client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "DELETE"),
2024
+ list: (gameId) => client["request"](`/games/${gameId}/shop-listings`, "GET")
1939
2025
  }
1940
2026
  }
1941
2027
  };
1942
2028
  }
1943
2029
  // src/core/request.ts
1944
2030
  function checkDevWarnings(data) {
1945
- if (!data || typeof data !== "object")
2031
+ if (!data || typeof data !== "object") {
1946
2032
  return;
2033
+ }
1947
2034
  const response = data;
1948
2035
  const warningType = response.__playcademyDevWarning;
1949
- if (!warningType)
2036
+ if (!warningType) {
1950
2037
  return;
2038
+ }
1951
2039
  switch (warningType) {
1952
- case "timeback-not-configured":
2040
+ case "timeback-not-configured": {
1953
2041
  console.warn("%c⚠️ TimeBack Not Configured", "background: #f59e0b; color: white; padding: 6px 12px; border-radius: 4px; font-weight: bold; font-size: 13px");
1954
2042
  console.log("%cTimeBack is configured in playcademy.config.js but the sandbox does not have TimeBack credentials.", "color: #f59e0b; font-weight: 500");
1955
2043
  console.log("To test TimeBack locally:");
@@ -1960,8 +2048,10 @@ function checkDevWarnings(data) {
1960
2048
  console.log(" Or deploy your game: %cplaycademy deploy", "color: #10b981; font-weight: 600; font-family: monospace");
1961
2049
  console.log(" Or wait for %c@superbuilders/timeback-local%c (coming soon)", "color: #8b5cf6; font-weight: 600; font-family: monospace", "color: inherit");
1962
2050
  break;
1963
- default:
2051
+ }
2052
+ default: {
1964
2053
  console.warn(`[Playcademy Dev Warning] ${warningType}`);
2054
+ }
1965
2055
  }
1966
2056
  }
1967
2057
  function prepareRequestBody(body, headers) {
@@ -2010,18 +2100,20 @@ async function request({
2010
2100
  })) ?? undefined;
2011
2101
  throw ApiError.fromResponse(res.status, res.statusText, errorBody);
2012
2102
  }
2013
- if (res.status === 204)
2103
+ if (res.status === 204) {
2014
2104
  return;
2105
+ }
2015
2106
  const contentType = res.headers.get("content-type") ?? "";
2016
2107
  if (contentType.includes("application/json")) {
2017
2108
  try {
2018
2109
  const parsed = await res.json();
2019
2110
  checkDevWarnings(parsed);
2020
2111
  return parsed;
2021
- } catch (err) {
2022
- if (err instanceof SyntaxError)
2112
+ } catch (error) {
2113
+ if (error instanceof SyntaxError) {
2023
2114
  return;
2024
- throw err;
2115
+ }
2116
+ throw error;
2025
2117
  }
2026
2118
  }
2027
2119
  const rawText = await res.text().catch(() => "");
@@ -2069,12 +2161,8 @@ function createGamesNamespace(client) {
2069
2161
  return baseGameData;
2070
2162
  }, options);
2071
2163
  },
2072
- list: (options) => {
2073
- return gamesListCache.get("all", () => client["request"]("/games", "GET"), options);
2074
- },
2075
- getSubjects: () => {
2076
- return client["request"]("/games/subjects", "GET");
2077
- },
2164
+ list: (options) => gamesListCache.get("all", () => client["request"]("/games", "GET"), options),
2165
+ getSubjects: () => client["request"]("/games/subjects", "GET"),
2078
2166
  startSession: async (gameId) => {
2079
2167
  const idToUse = gameId ?? client["_ensureGameId"]();
2080
2168
  return client["request"](`/games/${idToUse}/sessions`, "POST", {});
@@ -2098,10 +2186,12 @@ function createGamesNamespace(client) {
2098
2186
  leaderboard: {
2099
2187
  get: async (gameId, options) => {
2100
2188
  const params = new URLSearchParams;
2101
- if (options?.limit)
2189
+ if (options?.limit) {
2102
2190
  params.append("limit", String(options.limit));
2103
- if (options?.offset)
2191
+ }
2192
+ if (options?.offset) {
2104
2193
  params.append("offset", String(options.offset));
2194
+ }
2105
2195
  const queryString = params.toString();
2106
2196
  const path = queryString ? `/games/${gameId}/leaderboard?${queryString}` : `/games/${gameId}/leaderboard`;
2107
2197
  return client["request"](path, "GET");
@@ -2129,14 +2219,10 @@ function createCharacterNamespace(client) {
2129
2219
  throw error;
2130
2220
  }
2131
2221
  },
2132
- create: async (characterData) => {
2133
- return client["request"]("/character", "POST", {
2134
- body: characterData
2135
- });
2136
- },
2137
- update: async (updates) => {
2138
- return client["request"]("/character", "PATCH", { body: updates });
2139
- },
2222
+ create: async (characterData) => client["request"]("/character", "POST", {
2223
+ body: characterData
2224
+ }),
2225
+ update: async (updates) => client["request"]("/character", "PATCH", { body: updates }),
2140
2226
  components: {
2141
2227
  list: async (options) => {
2142
2228
  const cacheKey = options?.level === undefined ? "all" : String(options.level);
@@ -2150,12 +2236,8 @@ function createCharacterNamespace(client) {
2150
2236
  getCacheKeys: () => componentCache.getKeys()
2151
2237
  },
2152
2238
  accessories: {
2153
- equip: async (slot, componentId) => {
2154
- return client["request"]("/character/accessories/equip", "POST", { body: { slot, accessoryComponentId: componentId } });
2155
- },
2156
- remove: async (slot) => {
2157
- return client["request"](`/character/accessories/${slot}`, "DELETE");
2158
- },
2239
+ equip: async (slot, componentId) => client["request"]("/character/accessories/equip", "POST", { body: { slot, accessoryComponentId: componentId } }),
2240
+ remove: async (slot) => client["request"](`/character/accessories/${slot}`, "DELETE"),
2159
2241
  list: async () => {
2160
2242
  const character2 = await client.character.get();
2161
2243
  return character2?.accessories || [];
@@ -2174,14 +2256,13 @@ function createAchievementsNamespace(client) {
2174
2256
  keyPrefix: "achievements.history"
2175
2257
  });
2176
2258
  return {
2177
- list: (options) => {
2178
- return achievementsListCache.get("current", () => client["request"]("/achievements/current", "GET"), options);
2179
- },
2259
+ list: (options) => achievementsListCache.get("current", () => client["request"]("/achievements/current", "GET"), options),
2180
2260
  history: {
2181
2261
  list: async (queryOptions, cacheOptions) => {
2182
2262
  const params = new URLSearchParams;
2183
- if (queryOptions?.limit)
2263
+ if (queryOptions?.limit) {
2184
2264
  params.append("limit", String(queryOptions.limit));
2265
+ }
2185
2266
  const qs = params.toString();
2186
2267
  const path = qs ? `/achievements/history?${qs}` : "/achievements/history";
2187
2268
  const cacheKey = qs ? `history-${qs}` : "history";
@@ -2209,9 +2290,7 @@ function createLeaderboardNamespace(client) {
2209
2290
  }
2210
2291
  return client["request"](`/leaderboard?${params}`, "GET");
2211
2292
  },
2212
- getUserRank: async (gameId, userId) => {
2213
- return client["request"](`/games/${gameId}/users/${userId}/rank`, "GET");
2214
- }
2293
+ getUserRank: async (gameId, userId) => client["request"](`/games/${gameId}/users/${userId}/rank`, "GET")
2215
2294
  };
2216
2295
  }
2217
2296
  // src/core/cache/cooldown-cache.ts
@@ -2265,28 +2344,18 @@ function createCooldownCache(defaultCooldownMs) {
2265
2344
  function createLevelsNamespace(client) {
2266
2345
  const progressCache = createCooldownCache(5000);
2267
2346
  return {
2268
- get: async () => {
2269
- return client["request"]("/users/level", "GET");
2270
- },
2271
- progress: async (options) => {
2272
- return progressCache.get("user-progress", () => client["request"]("/users/level/progress", "GET"), options);
2273
- },
2347
+ get: async () => client["request"]("/users/level", "GET"),
2348
+ progress: async (options) => progressCache.get("user-progress", () => client["request"]("/users/level/progress", "GET"), options),
2274
2349
  config: {
2275
- list: async () => {
2276
- return client["request"]("/levels/config", "GET");
2277
- },
2278
- get: async (level) => {
2279
- return client["request"](`/levels/config/${level}`, "GET");
2280
- }
2350
+ list: async () => client["request"]("/levels/config", "GET"),
2351
+ get: async (level) => client["request"](`/levels/config/${level}`, "GET")
2281
2352
  }
2282
2353
  };
2283
2354
  }
2284
2355
  // src/namespaces/platform/shop.ts
2285
2356
  function createShopNamespace(client) {
2286
2357
  return {
2287
- view: () => {
2288
- return client["request"]("/shop/view", "GET");
2289
- }
2358
+ view: () => client["request"]("/shop/view", "GET")
2290
2359
  };
2291
2360
  }
2292
2361
  // src/namespaces/platform/notifications.ts
@@ -2302,14 +2371,18 @@ function createNotificationsNamespace(client) {
2302
2371
  return {
2303
2372
  list: async (queryOptions, cacheOptions) => {
2304
2373
  const params = new URLSearchParams;
2305
- if (queryOptions?.status)
2374
+ if (queryOptions?.status) {
2306
2375
  params.append("status", queryOptions.status);
2307
- if (queryOptions?.type)
2376
+ }
2377
+ if (queryOptions?.type) {
2308
2378
  params.append("type", queryOptions.type);
2309
- if (queryOptions?.limit)
2379
+ }
2380
+ if (queryOptions?.limit) {
2310
2381
  params.append("limit", String(queryOptions.limit));
2311
- if (queryOptions?.offset)
2382
+ }
2383
+ if (queryOptions?.offset) {
2312
2384
  params.append("offset", String(queryOptions.offset));
2385
+ }
2313
2386
  const qs = params.toString();
2314
2387
  const path = qs ? `/notifications?${qs}` : "/notifications";
2315
2388
  const cacheKey = qs ? `list-${qs}` : "list";
@@ -2365,10 +2438,12 @@ function createNotificationsNamespace(client) {
2365
2438
  get: async (queryOptions, cacheOptions) => {
2366
2439
  const user = await client.users.me();
2367
2440
  const params = new URLSearchParams;
2368
- if (queryOptions?.from)
2441
+ if (queryOptions?.from) {
2369
2442
  params.append("from", queryOptions.from);
2370
- if (queryOptions?.to)
2443
+ }
2444
+ if (queryOptions?.to) {
2371
2445
  params.append("to", queryOptions.to);
2446
+ }
2372
2447
  const qs = params.toString();
2373
2448
  const path = qs ? `/notifications/stats/${user.id}?${qs}` : `/notifications/stats/${user.id}`;
2374
2449
  const cacheKey = qs ? `stats-${qs}` : "stats";
@@ -2405,11 +2480,10 @@ function createSpritesNamespace(client) {
2405
2480
  return {
2406
2481
  templates: {
2407
2482
  get: async (slug) => {
2408
- if (!slug)
2483
+ if (!slug) {
2409
2484
  throw new Error("Sprite template slug is required");
2410
- const templateMeta = await templateUrlCache.get(slug, async () => {
2411
- return client["request"](`/sprites/templates/${slug}`, "GET");
2412
- });
2485
+ }
2486
+ const templateMeta = await templateUrlCache.get(slug, async () => client["request"](`/sprites/templates/${slug}`, "GET"));
2413
2487
  if (!templateMeta.url) {
2414
2488
  throw new Error(`Template ${slug} has no URL in database`);
2415
2489
  }
@@ -2459,7 +2533,9 @@ function createTimebackNamespace2(client) {
2459
2533
  ttl: 30 * 1000,
2460
2534
  keyPrefix: "platform.timeback.students"
2461
2535
  });
2462
- const getTimeback = () => client["initPayload"]?.timeback;
2536
+ function getTimeback() {
2537
+ return client["initPayload"]?.timeback;
2538
+ }
2463
2539
  return {
2464
2540
  get user() {
2465
2541
  return {
@@ -2475,26 +2551,22 @@ function createTimebackNamespace2(client) {
2475
2551
  get organizations() {
2476
2552
  return getTimeback()?.organizations ?? [];
2477
2553
  },
2478
- fetch: async (options) => {
2479
- return userCache.get("current", async () => {
2480
- const response = await client["request"]("/timeback/user", "GET");
2481
- const initPayload = client["initPayload"];
2482
- if (initPayload) {
2483
- initPayload.timeback = response;
2484
- }
2485
- return {
2486
- id: response.id,
2487
- role: response.role,
2488
- enrollments: response.enrollments,
2489
- organizations: response.organizations
2490
- };
2491
- }, options);
2492
- }
2554
+ fetch: async (options) => userCache.get("current", async () => {
2555
+ const response = await client["request"]("/timeback/user", "GET");
2556
+ const initPayload = client["initPayload"];
2557
+ if (initPayload) {
2558
+ initPayload.timeback = response;
2559
+ }
2560
+ return {
2561
+ id: response.id,
2562
+ role: response.role,
2563
+ enrollments: response.enrollments,
2564
+ organizations: response.organizations
2565
+ };
2566
+ }, options)
2493
2567
  };
2494
2568
  },
2495
- populateStudent: async (names) => {
2496
- return client["request"]("/timeback/populate-student", "POST", names ? { body: names } : undefined);
2497
- },
2569
+ populateStudent: async (names) => client["request"]("/timeback/populate-student", "POST", names ? { body: names } : undefined),
2498
2570
  startActivity: (_metadata) => {
2499
2571
  throw new Error(NOT_SUPPORTED);
2500
2572
  },
@@ -2508,44 +2580,36 @@ function createTimebackNamespace2(client) {
2508
2580
  throw new Error(NOT_SUPPORTED);
2509
2581
  },
2510
2582
  management: {
2511
- setup: (request2) => {
2512
- return client["request"]("/timeback/setup", "POST", {
2513
- body: request2
2514
- });
2515
- },
2516
- verify: (gameId) => {
2517
- return client["request"](`/timeback/verify/${gameId}`, "GET");
2518
- },
2519
- cleanup: (gameId) => {
2520
- return client["request"](`/timeback/integrations/${gameId}`, "DELETE");
2521
- },
2522
- get: (gameId) => {
2523
- return client["request"](`/timeback/integrations/${gameId}`, "GET");
2524
- },
2525
- getConfig: (gameId) => {
2526
- return client["request"](`/timeback/config/${gameId}`, "GET");
2527
- }
2583
+ setup: (request2) => client["request"]("/timeback/setup", "POST", {
2584
+ body: request2
2585
+ }),
2586
+ verify: (gameId) => client["request"](`/timeback/verify/${gameId}`, "GET"),
2587
+ cleanup: (gameId) => client["request"](`/timeback/integrations/${gameId}`, "DELETE"),
2588
+ get: (gameId) => client["request"](`/timeback/integrations/${gameId}`, "GET"),
2589
+ getConfig: (gameId) => client["request"](`/timeback/config/${gameId}`, "GET")
2528
2590
  },
2529
2591
  xp: {
2530
2592
  today: async (options) => {
2531
2593
  const params = new URLSearchParams;
2532
- if (options?.date)
2594
+ if (options?.date) {
2533
2595
  params.set("date", options.date);
2534
- if (options?.timezone)
2596
+ }
2597
+ if (options?.timezone) {
2535
2598
  params.set("tz", options.timezone);
2599
+ }
2536
2600
  const query = params.toString();
2537
2601
  const endpoint = query ? `/timeback/xp/today?${query}` : "/timeback/xp/today";
2538
2602
  return client["request"](endpoint, "GET");
2539
2603
  },
2540
- total: async () => {
2541
- return client["request"]("/timeback/xp/total", "GET");
2542
- },
2604
+ total: async () => client["request"]("/timeback/xp/total", "GET"),
2543
2605
  history: async (options) => {
2544
2606
  const params = new URLSearchParams;
2545
- if (options?.startDate)
2607
+ if (options?.startDate) {
2546
2608
  params.set("startDate", options.startDate);
2547
- if (options?.endDate)
2609
+ }
2610
+ if (options?.endDate) {
2548
2611
  params.set("endDate", options.endDate);
2612
+ }
2549
2613
  const query = params.toString();
2550
2614
  const endpoint = query ? `/timeback/xp/history?${query}` : "/timeback/xp/history";
2551
2615
  return client["request"](endpoint, "GET");
@@ -2554,10 +2618,12 @@ function createTimebackNamespace2(client) {
2554
2618
  const [today, total] = await Promise.all([
2555
2619
  client["request"]((() => {
2556
2620
  const params = new URLSearchParams;
2557
- if (options?.date)
2621
+ if (options?.date) {
2558
2622
  params.set("date", options.date);
2559
- if (options?.timezone)
2623
+ }
2624
+ if (options?.timezone) {
2560
2625
  params.set("tz", options.timezone);
2626
+ }
2561
2627
  const query = params.toString();
2562
2628
  return query ? `/timeback/xp/today?${query}` : "/timeback/xp/today";
2563
2629
  })(), "GET"),
@@ -2567,11 +2633,7 @@ function createTimebackNamespace2(client) {
2567
2633
  }
2568
2634
  },
2569
2635
  students: {
2570
- get: async (timebackId, options) => {
2571
- return studentCache.get(timebackId, async () => {
2572
- return client["request"](`/timeback/user/${timebackId}`, "GET");
2573
- }, options);
2574
- },
2636
+ get: async (timebackId, options) => studentCache.get(timebackId, async () => client["request"](`/timeback/user/${timebackId}`, "GET"), options),
2575
2637
  clearCache: (timebackId) => {
2576
2638
  studentCache.clear(timebackId);
2577
2639
  }
@@ -2677,24 +2739,26 @@ class ConnectionMonitor {
2677
2739
  this._detectInitialState();
2678
2740
  }
2679
2741
  start() {
2680
- if (this.isMonitoring)
2742
+ if (this.isMonitoring) {
2681
2743
  return;
2744
+ }
2682
2745
  this.isMonitoring = true;
2683
- if (this.config.enableOfflineEvents && typeof window !== "undefined") {
2684
- window.addEventListener("online", this._handleOnline);
2685
- window.addEventListener("offline", this._handleOffline);
2746
+ if (this.config.enableOfflineEvents && typeof globalThis.window !== "undefined") {
2747
+ globalThis.addEventListener("online", this._handleOnline);
2748
+ globalThis.addEventListener("offline", this._handleOffline);
2686
2749
  }
2687
2750
  if (this.config.enableHeartbeat) {
2688
2751
  this._startHeartbeat();
2689
2752
  }
2690
2753
  }
2691
2754
  stop() {
2692
- if (!this.isMonitoring)
2755
+ if (!this.isMonitoring) {
2693
2756
  return;
2757
+ }
2694
2758
  this.isMonitoring = false;
2695
- if (typeof window !== "undefined") {
2696
- window.removeEventListener("online", this._handleOnline);
2697
- window.removeEventListener("offline", this._handleOffline);
2759
+ if (typeof globalThis.window !== "undefined") {
2760
+ globalThis.removeEventListener("online", this._handleOnline);
2761
+ globalThis.removeEventListener("offline", this._handleOffline);
2698
2762
  }
2699
2763
  if (this.heartbeatInterval) {
2700
2764
  clearInterval(this.heartbeatInterval);
@@ -2714,8 +2778,9 @@ class ConnectionMonitor {
2714
2778
  }
2715
2779
  reportRequestFailure(error) {
2716
2780
  const isNetworkError = error instanceof TypeError || error instanceof Error && error.message.includes("fetch");
2717
- if (!isNetworkError)
2781
+ if (!isNetworkError) {
2718
2782
  return;
2783
+ }
2719
2784
  this.consecutiveFailures++;
2720
2785
  if (this.consecutiveFailures >= this.config.failureThreshold) {
2721
2786
  this._setState("degraded", "Multiple consecutive request failures");
@@ -2783,8 +2848,9 @@ class ConnectionMonitor {
2783
2848
  }
2784
2849
  }
2785
2850
  _setState(newState, reason) {
2786
- if (this.state === newState)
2851
+ if (this.state === newState) {
2787
2852
  return;
2853
+ }
2788
2854
  const oldState = this.state;
2789
2855
  this.state = newState;
2790
2856
  console.debug(`[ConnectionMonitor] ${oldState} → ${newState}: ${reason}`);
@@ -2800,14 +2866,15 @@ class ConnectionMonitor {
2800
2866
  // src/core/connection/utils.ts
2801
2867
  function createDisplayAlert(authContext) {
2802
2868
  return (message, options) => {
2803
- if (authContext?.isInIframe && typeof window !== "undefined" && window.parent !== window) {
2869
+ if (authContext?.isInIframe && typeof globalThis.window !== "undefined" && globalThis.window.parent !== globalThis.window) {
2804
2870
  window.parent.postMessage({
2805
2871
  type: "PLAYCADEMY_DISPLAY_ALERT",
2806
2872
  message,
2807
2873
  options
2808
2874
  }, "*");
2809
2875
  } else {
2810
- const prefix = options?.type === "error" ? "❌" : options?.type === "warning" ? "⚠️" : "ℹ️";
2876
+ const prefixMap = { error: "❌", warning: "⚠️", info: "ℹ️" };
2877
+ const prefix = (options?.type && prefixMap[options.type]) ?? "ℹ️";
2811
2878
  console.log(`${prefix} ${message}`);
2812
2879
  }
2813
2880
  };
@@ -2890,12 +2957,8 @@ class PlaycademyBaseClient {
2890
2957
  initPayload;
2891
2958
  connectionManager;
2892
2959
  _sessionManager = {
2893
- startSession: async (gameId) => {
2894
- return this.request(`/games/${gameId}/sessions`, "POST");
2895
- },
2896
- endSession: async (sessionId, gameId) => {
2897
- return this.request(`/games/${gameId}/sessions/${sessionId}`, "DELETE");
2898
- }
2960
+ startSession: async (gameId) => this.request(`/games/${gameId}/sessions`, "POST"),
2961
+ endSession: async (sessionId, gameId) => this.request(`/games/${gameId}/sessions/${sessionId}`, "DELETE")
2899
2962
  };
2900
2963
  constructor(config) {
2901
2964
  this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
@@ -2909,16 +2972,16 @@ class PlaycademyBaseClient {
2909
2972
  }
2910
2973
  getBaseUrl() {
2911
2974
  const isRelative = this.baseUrl.startsWith("/");
2912
- const isBrowser2 = typeof window !== "undefined";
2913
- return isRelative && isBrowser2 ? `${window.location.origin}${this.baseUrl}` : this.baseUrl;
2975
+ const isBrowser2 = typeof globalThis.window !== "undefined";
2976
+ return isRelative && isBrowser2 ? `${globalThis.location.origin}${this.baseUrl}` : this.baseUrl;
2914
2977
  }
2915
2978
  getGameBackendUrl() {
2916
2979
  if (!this.gameUrl) {
2917
2980
  throw new PlaycademyError("Game backend URL not configured. gameUrl must be set to use game backend features.");
2918
2981
  }
2919
2982
  const isRelative = this.gameUrl.startsWith("/");
2920
- const isBrowser2 = typeof window !== "undefined";
2921
- const effectiveGameUrl = isRelative && isBrowser2 ? `${window.location.origin}${this.gameUrl}` : this.gameUrl;
2983
+ const isBrowser2 = typeof globalThis.window !== "undefined";
2984
+ const effectiveGameUrl = isRelative && isBrowser2 ? `${globalThis.location.origin}${this.gameUrl}` : this.gameUrl;
2922
2985
  return `${effectiveGameUrl}/api`;
2923
2986
  }
2924
2987
  ping() {
@@ -2950,8 +3013,9 @@ class PlaycademyBaseClient {
2950
3013
  return this.connectionManager?.getState() ?? "unknown";
2951
3014
  }
2952
3015
  async checkConnection() {
2953
- if (!this.connectionManager)
3016
+ if (!this.connectionManager) {
2954
3017
  return "unknown";
3018
+ }
2955
3019
  return await this.connectionManager.checkNow();
2956
3020
  }
2957
3021
  _setAuthContext(context) {
@@ -3018,11 +3082,13 @@ class PlaycademyBaseClient {
3018
3082
  this.authContext = { isInIframe: isInIframe() };
3019
3083
  }
3020
3084
  _initializeConnectionMonitor() {
3021
- if (typeof window === "undefined")
3085
+ if (typeof globalThis.window === "undefined") {
3022
3086
  return;
3087
+ }
3023
3088
  const isEnabled = this.config.enableConnectionMonitoring ?? true;
3024
- if (!isEnabled)
3089
+ if (!isEnabled) {
3025
3090
  return;
3091
+ }
3026
3092
  try {
3027
3093
  this.connectionManager = new ConnectionManager({
3028
3094
  baseUrl: this.baseUrl,
@@ -3037,11 +3103,13 @@ class PlaycademyBaseClient {
3037
3103
  }
3038
3104
  }
3039
3105
  async _initializeInternalSession() {
3040
- if (!this.gameId || this.internalClientSessionId)
3106
+ if (!this.gameId || this.internalClientSessionId) {
3041
3107
  return;
3108
+ }
3042
3109
  const shouldAutoStart = this.config.autoStartSession ?? true;
3043
- if (!shouldAutoStart)
3110
+ if (!shouldAutoStart) {
3044
3111
  return;
3112
+ }
3045
3113
  try {
3046
3114
  const response = await this._sessionManager.startSession(this.gameId);
3047
3115
  this.internalClientSessionId = response.sessionId;