@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/index.d.ts +26 -30
- package/dist/index.js +264 -204
- package/dist/internal.d.ts +4347 -4026
- package/dist/internal.js +517 -449
- package/dist/server.d.ts +4 -11
- package/dist/server.js +40 -22
- package/dist/types.d.ts +3077 -3080
- package/package.json +8 -7
package/dist/index.js
CHANGED
|
@@ -32,15 +32,15 @@ class PlaycademyMessaging {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
listen(type, handler) {
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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" &&
|
|
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
|
-
|
|
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 =
|
|
99
|
+
const preloaded = globalThis.PLAYCADEMY;
|
|
100
100
|
if (preloaded?.token) {
|
|
101
101
|
return preloaded;
|
|
102
102
|
}
|
|
103
|
-
if (
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
204
|
+
function isBrowser() {
|
|
202
205
|
const g = globalThis;
|
|
203
206
|
return typeof g.window !== "undefined" && typeof g.document !== "undefined";
|
|
204
|
-
}
|
|
205
|
-
|
|
207
|
+
}
|
|
208
|
+
function isProduction() {
|
|
206
209
|
return typeof process !== "undefined" && false;
|
|
207
|
-
}
|
|
208
|
-
|
|
210
|
+
}
|
|
211
|
+
function isDevelopment() {
|
|
209
212
|
return typeof process !== "undefined" && true;
|
|
210
|
-
}
|
|
211
|
-
|
|
213
|
+
}
|
|
214
|
+
function isInteractiveTTY() {
|
|
212
215
|
return typeof process !== "undefined" && Boolean(process.stdout && process.stdout.isTTY);
|
|
213
|
-
}
|
|
214
|
-
|
|
216
|
+
}
|
|
217
|
+
function isSilent() {
|
|
215
218
|
return typeof process !== "undefined" && process.env.LOG_SILENT === "true";
|
|
216
|
-
}
|
|
217
|
-
|
|
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
|
-
|
|
255
|
+
function getLevelColor(level) {
|
|
253
256
|
switch (level) {
|
|
254
|
-
case "debug":
|
|
257
|
+
case "debug": {
|
|
255
258
|
return colors.blue;
|
|
256
|
-
|
|
259
|
+
}
|
|
260
|
+
case "info": {
|
|
257
261
|
return colors.cyan;
|
|
258
|
-
|
|
262
|
+
}
|
|
263
|
+
case "warn": {
|
|
259
264
|
return colors.yellow;
|
|
260
|
-
|
|
265
|
+
}
|
|
266
|
+
case "error": {
|
|
261
267
|
return colors.red;
|
|
262
|
-
|
|
268
|
+
}
|
|
269
|
+
default: {
|
|
263
270
|
return colors.reset;
|
|
271
|
+
}
|
|
264
272
|
}
|
|
265
|
-
}
|
|
266
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
321
|
+
}
|
|
322
|
+
function getConsoleMethod(level) {
|
|
315
323
|
switch (level) {
|
|
316
|
-
case "debug":
|
|
324
|
+
case "debug": {
|
|
317
325
|
return console.debug;
|
|
318
|
-
|
|
326
|
+
}
|
|
327
|
+
case "info": {
|
|
319
328
|
return console.info;
|
|
320
|
-
|
|
329
|
+
}
|
|
330
|
+
case "warn": {
|
|
321
331
|
return console.warn;
|
|
322
|
-
|
|
332
|
+
}
|
|
333
|
+
case "error": {
|
|
323
334
|
return console.error;
|
|
324
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
375
|
+
}
|
|
376
|
+
case "color-tty": {
|
|
361
377
|
formatColorTTY(level, message, context, scope);
|
|
362
378
|
break;
|
|
363
|
-
|
|
379
|
+
}
|
|
380
|
+
case "json-single-line": {
|
|
364
381
|
formatJSONSingleLine(level, message, context, scope);
|
|
365
382
|
break;
|
|
366
|
-
|
|
383
|
+
}
|
|
384
|
+
case "json-pretty": {
|
|
367
385
|
formatJSONPretty(level, message, context, scope);
|
|
368
386
|
break;
|
|
387
|
+
}
|
|
369
388
|
}
|
|
370
|
-
}
|
|
371
|
-
|
|
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
|
-
|
|
451
|
+
}
|
|
452
|
+
case 401: {
|
|
433
453
|
return "UNAUTHORIZED";
|
|
434
|
-
|
|
454
|
+
}
|
|
455
|
+
case 403: {
|
|
435
456
|
return "FORBIDDEN";
|
|
436
|
-
|
|
457
|
+
}
|
|
458
|
+
case 404: {
|
|
437
459
|
return "NOT_FOUND";
|
|
438
|
-
|
|
460
|
+
}
|
|
461
|
+
case 405: {
|
|
439
462
|
return "METHOD_NOT_ALLOWED";
|
|
440
|
-
|
|
463
|
+
}
|
|
464
|
+
case 409: {
|
|
441
465
|
return "CONFLICT";
|
|
442
|
-
|
|
466
|
+
}
|
|
467
|
+
case 410: {
|
|
443
468
|
return "GONE";
|
|
444
|
-
|
|
469
|
+
}
|
|
470
|
+
case 412: {
|
|
445
471
|
return "PRECONDITION_FAILED";
|
|
446
|
-
|
|
472
|
+
}
|
|
473
|
+
case 413: {
|
|
447
474
|
return "PAYLOAD_TOO_LARGE";
|
|
448
|
-
|
|
475
|
+
}
|
|
476
|
+
case 422: {
|
|
449
477
|
return "VALIDATION_FAILED";
|
|
450
|
-
|
|
478
|
+
}
|
|
479
|
+
case 429: {
|
|
451
480
|
return "TOO_MANY_REQUESTS";
|
|
452
|
-
|
|
481
|
+
}
|
|
482
|
+
case 500: {
|
|
453
483
|
return "INTERNAL_ERROR";
|
|
454
|
-
|
|
484
|
+
}
|
|
485
|
+
case 501: {
|
|
455
486
|
return "NOT_IMPLEMENTED";
|
|
456
|
-
|
|
487
|
+
}
|
|
488
|
+
case 503: {
|
|
457
489
|
return "SERVICE_UNAVAILABLE";
|
|
458
|
-
|
|
490
|
+
}
|
|
491
|
+
case 504: {
|
|
459
492
|
return "TIMEOUT";
|
|
460
|
-
|
|
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 =
|
|
514
|
+
if (baseUrl.startsWith("/") && typeof globalThis.window !== "undefined") {
|
|
515
|
+
url = globalThis.location.origin + baseUrl;
|
|
481
516
|
}
|
|
482
|
-
url
|
|
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
|
|
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
|
|
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
|
-
|
|
635
|
-
if (event.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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
787
|
+
}
|
|
788
|
+
case "redirect": {
|
|
746
789
|
return initiateRedirectFlow(options);
|
|
747
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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" &&
|
|
821
|
-
|
|
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
|
-
|
|
834
|
-
|
|
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
|
-
|
|
837
|
-
|
|
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
|
-
|
|
959
|
+
async function fetchAsset(path, options) {
|
|
915
960
|
const gameUrl = client["initPayload"]?.gameUrl;
|
|
916
961
|
if (!gameUrl) {
|
|
917
|
-
const relativePath = path.startsWith("./") ? 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
|
|
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 :
|
|
979
|
+
return path.startsWith("./") ? path : `./${path}`;
|
|
937
980
|
}
|
|
938
981
|
const cleanPath = path.startsWith("./") ? path.slice(2) : path;
|
|
939
|
-
return gameUrl
|
|
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
|
-
|
|
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
|
|
1189
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
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;
|
|
@@ -1662,24 +1710,26 @@ class ConnectionMonitor {
|
|
|
1662
1710
|
this._detectInitialState();
|
|
1663
1711
|
}
|
|
1664
1712
|
start() {
|
|
1665
|
-
if (this.isMonitoring)
|
|
1713
|
+
if (this.isMonitoring) {
|
|
1666
1714
|
return;
|
|
1715
|
+
}
|
|
1667
1716
|
this.isMonitoring = true;
|
|
1668
|
-
if (this.config.enableOfflineEvents && typeof window !== "undefined") {
|
|
1669
|
-
|
|
1670
|
-
|
|
1717
|
+
if (this.config.enableOfflineEvents && typeof globalThis.window !== "undefined") {
|
|
1718
|
+
globalThis.addEventListener("online", this._handleOnline);
|
|
1719
|
+
globalThis.addEventListener("offline", this._handleOffline);
|
|
1671
1720
|
}
|
|
1672
1721
|
if (this.config.enableHeartbeat) {
|
|
1673
1722
|
this._startHeartbeat();
|
|
1674
1723
|
}
|
|
1675
1724
|
}
|
|
1676
1725
|
stop() {
|
|
1677
|
-
if (!this.isMonitoring)
|
|
1726
|
+
if (!this.isMonitoring) {
|
|
1678
1727
|
return;
|
|
1728
|
+
}
|
|
1679
1729
|
this.isMonitoring = false;
|
|
1680
|
-
if (typeof window !== "undefined") {
|
|
1681
|
-
|
|
1682
|
-
|
|
1730
|
+
if (typeof globalThis.window !== "undefined") {
|
|
1731
|
+
globalThis.removeEventListener("online", this._handleOnline);
|
|
1732
|
+
globalThis.removeEventListener("offline", this._handleOffline);
|
|
1683
1733
|
}
|
|
1684
1734
|
if (this.heartbeatInterval) {
|
|
1685
1735
|
clearInterval(this.heartbeatInterval);
|
|
@@ -1699,8 +1749,9 @@ class ConnectionMonitor {
|
|
|
1699
1749
|
}
|
|
1700
1750
|
reportRequestFailure(error) {
|
|
1701
1751
|
const isNetworkError = error instanceof TypeError || error instanceof Error && error.message.includes("fetch");
|
|
1702
|
-
if (!isNetworkError)
|
|
1752
|
+
if (!isNetworkError) {
|
|
1703
1753
|
return;
|
|
1754
|
+
}
|
|
1704
1755
|
this.consecutiveFailures++;
|
|
1705
1756
|
if (this.consecutiveFailures >= this.config.failureThreshold) {
|
|
1706
1757
|
this._setState("degraded", "Multiple consecutive request failures");
|
|
@@ -1768,8 +1819,9 @@ class ConnectionMonitor {
|
|
|
1768
1819
|
}
|
|
1769
1820
|
}
|
|
1770
1821
|
_setState(newState, reason) {
|
|
1771
|
-
if (this.state === newState)
|
|
1822
|
+
if (this.state === newState) {
|
|
1772
1823
|
return;
|
|
1824
|
+
}
|
|
1773
1825
|
const oldState = this.state;
|
|
1774
1826
|
this.state = newState;
|
|
1775
1827
|
console.debug(`[ConnectionMonitor] ${oldState} → ${newState}: ${reason}`);
|
|
@@ -1785,14 +1837,15 @@ class ConnectionMonitor {
|
|
|
1785
1837
|
// src/core/connection/utils.ts
|
|
1786
1838
|
function createDisplayAlert(authContext) {
|
|
1787
1839
|
return (message, options) => {
|
|
1788
|
-
if (authContext?.isInIframe && typeof window !== "undefined" && window.parent !== window) {
|
|
1840
|
+
if (authContext?.isInIframe && typeof globalThis.window !== "undefined" && globalThis.window.parent !== globalThis.window) {
|
|
1789
1841
|
window.parent.postMessage({
|
|
1790
1842
|
type: "PLAYCADEMY_DISPLAY_ALERT",
|
|
1791
1843
|
message,
|
|
1792
1844
|
options
|
|
1793
1845
|
}, "*");
|
|
1794
1846
|
} else {
|
|
1795
|
-
const
|
|
1847
|
+
const prefixMap = { error: "❌", warning: "⚠️", info: "ℹ️" };
|
|
1848
|
+
const prefix = (options?.type && prefixMap[options.type]) ?? "ℹ️";
|
|
1796
1849
|
console.log(`${prefix} ${message}`);
|
|
1797
1850
|
}
|
|
1798
1851
|
};
|
|
@@ -1864,14 +1917,16 @@ class ConnectionManager {
|
|
|
1864
1917
|
}
|
|
1865
1918
|
// src/core/request.ts
|
|
1866
1919
|
function checkDevWarnings(data) {
|
|
1867
|
-
if (!data || typeof data !== "object")
|
|
1920
|
+
if (!data || typeof data !== "object") {
|
|
1868
1921
|
return;
|
|
1922
|
+
}
|
|
1869
1923
|
const response = data;
|
|
1870
1924
|
const warningType = response.__playcademyDevWarning;
|
|
1871
|
-
if (!warningType)
|
|
1925
|
+
if (!warningType) {
|
|
1872
1926
|
return;
|
|
1927
|
+
}
|
|
1873
1928
|
switch (warningType) {
|
|
1874
|
-
case "timeback-not-configured":
|
|
1929
|
+
case "timeback-not-configured": {
|
|
1875
1930
|
console.warn("%c⚠️ TimeBack Not Configured", "background: #f59e0b; color: white; padding: 6px 12px; border-radius: 4px; font-weight: bold; font-size: 13px");
|
|
1876
1931
|
console.log("%cTimeBack is configured in playcademy.config.js but the sandbox does not have TimeBack credentials.", "color: #f59e0b; font-weight: 500");
|
|
1877
1932
|
console.log("To test TimeBack locally:");
|
|
@@ -1882,8 +1937,10 @@ function checkDevWarnings(data) {
|
|
|
1882
1937
|
console.log(" Or deploy your game: %cplaycademy deploy", "color: #10b981; font-weight: 600; font-family: monospace");
|
|
1883
1938
|
console.log(" Or wait for %c@superbuilders/timeback-local%c (coming soon)", "color: #8b5cf6; font-weight: 600; font-family: monospace", "color: inherit");
|
|
1884
1939
|
break;
|
|
1885
|
-
|
|
1940
|
+
}
|
|
1941
|
+
default: {
|
|
1886
1942
|
console.warn(`[Playcademy Dev Warning] ${warningType}`);
|
|
1943
|
+
}
|
|
1887
1944
|
}
|
|
1888
1945
|
}
|
|
1889
1946
|
function prepareRequestBody(body, headers) {
|
|
@@ -1932,18 +1989,20 @@ async function request({
|
|
|
1932
1989
|
})) ?? undefined;
|
|
1933
1990
|
throw ApiError.fromResponse(res.status, res.statusText, errorBody);
|
|
1934
1991
|
}
|
|
1935
|
-
if (res.status === 204)
|
|
1992
|
+
if (res.status === 204) {
|
|
1936
1993
|
return;
|
|
1994
|
+
}
|
|
1937
1995
|
const contentType = res.headers.get("content-type") ?? "";
|
|
1938
1996
|
if (contentType.includes("application/json")) {
|
|
1939
1997
|
try {
|
|
1940
1998
|
const parsed = await res.json();
|
|
1941
1999
|
checkDevWarnings(parsed);
|
|
1942
2000
|
return parsed;
|
|
1943
|
-
} catch (
|
|
1944
|
-
if (
|
|
2001
|
+
} catch (error) {
|
|
2002
|
+
if (error instanceof SyntaxError) {
|
|
1945
2003
|
return;
|
|
1946
|
-
|
|
2004
|
+
}
|
|
2005
|
+
throw error;
|
|
1947
2006
|
}
|
|
1948
2007
|
}
|
|
1949
2008
|
const rawText = await res.text().catch(() => "");
|
|
@@ -1982,12 +2041,8 @@ class PlaycademyBaseClient {
|
|
|
1982
2041
|
initPayload;
|
|
1983
2042
|
connectionManager;
|
|
1984
2043
|
_sessionManager = {
|
|
1985
|
-
startSession: async (gameId) => {
|
|
1986
|
-
|
|
1987
|
-
},
|
|
1988
|
-
endSession: async (sessionId, gameId) => {
|
|
1989
|
-
return this.request(`/games/${gameId}/sessions/${sessionId}`, "DELETE");
|
|
1990
|
-
}
|
|
2044
|
+
startSession: async (gameId) => this.request(`/games/${gameId}/sessions`, "POST"),
|
|
2045
|
+
endSession: async (sessionId, gameId) => this.request(`/games/${gameId}/sessions/${sessionId}`, "DELETE")
|
|
1991
2046
|
};
|
|
1992
2047
|
constructor(config) {
|
|
1993
2048
|
this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
|
|
@@ -2001,16 +2056,16 @@ class PlaycademyBaseClient {
|
|
|
2001
2056
|
}
|
|
2002
2057
|
getBaseUrl() {
|
|
2003
2058
|
const isRelative = this.baseUrl.startsWith("/");
|
|
2004
|
-
const isBrowser2 = typeof window !== "undefined";
|
|
2005
|
-
return isRelative && isBrowser2 ? `${
|
|
2059
|
+
const isBrowser2 = typeof globalThis.window !== "undefined";
|
|
2060
|
+
return isRelative && isBrowser2 ? `${globalThis.location.origin}${this.baseUrl}` : this.baseUrl;
|
|
2006
2061
|
}
|
|
2007
2062
|
getGameBackendUrl() {
|
|
2008
2063
|
if (!this.gameUrl) {
|
|
2009
2064
|
throw new PlaycademyError("Game backend URL not configured. gameUrl must be set to use game backend features.");
|
|
2010
2065
|
}
|
|
2011
2066
|
const isRelative = this.gameUrl.startsWith("/");
|
|
2012
|
-
const isBrowser2 = typeof window !== "undefined";
|
|
2013
|
-
const effectiveGameUrl = isRelative && isBrowser2 ? `${
|
|
2067
|
+
const isBrowser2 = typeof globalThis.window !== "undefined";
|
|
2068
|
+
const effectiveGameUrl = isRelative && isBrowser2 ? `${globalThis.location.origin}${this.gameUrl}` : this.gameUrl;
|
|
2014
2069
|
return `${effectiveGameUrl}/api`;
|
|
2015
2070
|
}
|
|
2016
2071
|
ping() {
|
|
@@ -2042,8 +2097,9 @@ class PlaycademyBaseClient {
|
|
|
2042
2097
|
return this.connectionManager?.getState() ?? "unknown";
|
|
2043
2098
|
}
|
|
2044
2099
|
async checkConnection() {
|
|
2045
|
-
if (!this.connectionManager)
|
|
2100
|
+
if (!this.connectionManager) {
|
|
2046
2101
|
return "unknown";
|
|
2102
|
+
}
|
|
2047
2103
|
return await this.connectionManager.checkNow();
|
|
2048
2104
|
}
|
|
2049
2105
|
_setAuthContext(context) {
|
|
@@ -2110,11 +2166,13 @@ class PlaycademyBaseClient {
|
|
|
2110
2166
|
this.authContext = { isInIframe: isInIframe() };
|
|
2111
2167
|
}
|
|
2112
2168
|
_initializeConnectionMonitor() {
|
|
2113
|
-
if (typeof window === "undefined")
|
|
2169
|
+
if (typeof globalThis.window === "undefined") {
|
|
2114
2170
|
return;
|
|
2171
|
+
}
|
|
2115
2172
|
const isEnabled = this.config.enableConnectionMonitoring ?? true;
|
|
2116
|
-
if (!isEnabled)
|
|
2173
|
+
if (!isEnabled) {
|
|
2117
2174
|
return;
|
|
2175
|
+
}
|
|
2118
2176
|
try {
|
|
2119
2177
|
this.connectionManager = new ConnectionManager({
|
|
2120
2178
|
baseUrl: this.baseUrl,
|
|
@@ -2129,11 +2187,13 @@ class PlaycademyBaseClient {
|
|
|
2129
2187
|
}
|
|
2130
2188
|
}
|
|
2131
2189
|
async _initializeInternalSession() {
|
|
2132
|
-
if (!this.gameId || this.internalClientSessionId)
|
|
2190
|
+
if (!this.gameId || this.internalClientSessionId) {
|
|
2133
2191
|
return;
|
|
2192
|
+
}
|
|
2134
2193
|
const shouldAutoStart = this.config.autoStartSession ?? true;
|
|
2135
|
-
if (!shouldAutoStart)
|
|
2194
|
+
if (!shouldAutoStart) {
|
|
2136
2195
|
return;
|
|
2196
|
+
}
|
|
2137
2197
|
try {
|
|
2138
2198
|
const response = await this._sessionManager.startSession(this.gameId);
|
|
2139
2199
|
this.internalClientSessionId = response.sessionId;
|