@playcademy/sdk 0.1.13 → 0.1.15
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/README.md +38 -1
- package/dist/index.d.ts +1363 -607
- package/dist/index.js +506 -155
- package/dist/server.d.ts +3 -1
- package/dist/types.d.ts +466 -108
- package/package.json +4 -5
package/dist/index.js
CHANGED
|
@@ -123,9 +123,13 @@ var isBrowser = () => {
|
|
|
123
123
|
return false;
|
|
124
124
|
const minLevel = getMinimumLogLevel();
|
|
125
125
|
return levelPriority[level] >= levelPriority[minLevel];
|
|
126
|
-
}, performLog = (level, message, context) => {
|
|
126
|
+
}, customHandler, performLog = (level, message, context) => {
|
|
127
127
|
if (!shouldLog(level))
|
|
128
128
|
return;
|
|
129
|
+
if (customHandler) {
|
|
130
|
+
customHandler(level, message, context);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
129
133
|
const outputFormat = detectOutputFormat();
|
|
130
134
|
switch (outputFormat) {
|
|
131
135
|
case "browser":
|
|
@@ -277,7 +281,356 @@ function isInIframe() {
|
|
|
277
281
|
}
|
|
278
282
|
}
|
|
279
283
|
|
|
284
|
+
// src/core/connection/monitor.ts
|
|
285
|
+
class ConnectionMonitor {
|
|
286
|
+
state = "online";
|
|
287
|
+
callbacks = new Set;
|
|
288
|
+
heartbeatInterval;
|
|
289
|
+
consecutiveFailures = 0;
|
|
290
|
+
isMonitoring = false;
|
|
291
|
+
config;
|
|
292
|
+
constructor(config) {
|
|
293
|
+
this.config = {
|
|
294
|
+
baseUrl: config.baseUrl,
|
|
295
|
+
heartbeatInterval: config.heartbeatInterval ?? 1e4,
|
|
296
|
+
heartbeatTimeout: config.heartbeatTimeout ?? 5000,
|
|
297
|
+
failureThreshold: config.failureThreshold ?? 2,
|
|
298
|
+
enableHeartbeat: config.enableHeartbeat ?? true,
|
|
299
|
+
enableOfflineEvents: config.enableOfflineEvents ?? true
|
|
300
|
+
};
|
|
301
|
+
this._detectInitialState();
|
|
302
|
+
}
|
|
303
|
+
start() {
|
|
304
|
+
if (this.isMonitoring)
|
|
305
|
+
return;
|
|
306
|
+
this.isMonitoring = true;
|
|
307
|
+
if (this.config.enableOfflineEvents && typeof window !== "undefined") {
|
|
308
|
+
window.addEventListener("online", this._handleOnline);
|
|
309
|
+
window.addEventListener("offline", this._handleOffline);
|
|
310
|
+
}
|
|
311
|
+
if (this.config.enableHeartbeat) {
|
|
312
|
+
this._startHeartbeat();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
stop() {
|
|
316
|
+
if (!this.isMonitoring)
|
|
317
|
+
return;
|
|
318
|
+
this.isMonitoring = false;
|
|
319
|
+
if (typeof window !== "undefined") {
|
|
320
|
+
window.removeEventListener("online", this._handleOnline);
|
|
321
|
+
window.removeEventListener("offline", this._handleOffline);
|
|
322
|
+
}
|
|
323
|
+
if (this.heartbeatInterval) {
|
|
324
|
+
clearInterval(this.heartbeatInterval);
|
|
325
|
+
this.heartbeatInterval = undefined;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
onChange(callback) {
|
|
329
|
+
this.callbacks.add(callback);
|
|
330
|
+
return () => this.callbacks.delete(callback);
|
|
331
|
+
}
|
|
332
|
+
getState() {
|
|
333
|
+
return this.state;
|
|
334
|
+
}
|
|
335
|
+
async checkNow() {
|
|
336
|
+
await this._performHeartbeat();
|
|
337
|
+
return this.state;
|
|
338
|
+
}
|
|
339
|
+
reportRequestFailure(error) {
|
|
340
|
+
const isNetworkError = error instanceof TypeError || error instanceof Error && error.message.includes("fetch");
|
|
341
|
+
if (!isNetworkError)
|
|
342
|
+
return;
|
|
343
|
+
this.consecutiveFailures++;
|
|
344
|
+
if (this.consecutiveFailures >= this.config.failureThreshold) {
|
|
345
|
+
this._setState("degraded", "Multiple consecutive request failures");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
reportRequestSuccess() {
|
|
349
|
+
if (this.consecutiveFailures > 0) {
|
|
350
|
+
this.consecutiveFailures = 0;
|
|
351
|
+
if (this.state === "degraded") {
|
|
352
|
+
this._setState("online", "Requests succeeding again");
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
_detectInitialState() {
|
|
357
|
+
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
|
358
|
+
this.state = "offline";
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
_handleOnline = () => {
|
|
362
|
+
this.consecutiveFailures = 0;
|
|
363
|
+
this._setState("online", "Browser online event");
|
|
364
|
+
};
|
|
365
|
+
_handleOffline = () => {
|
|
366
|
+
this._setState("offline", "Browser offline event");
|
|
367
|
+
};
|
|
368
|
+
_startHeartbeat() {
|
|
369
|
+
this._performHeartbeat();
|
|
370
|
+
this.heartbeatInterval = setInterval(() => {
|
|
371
|
+
this._performHeartbeat();
|
|
372
|
+
}, this.config.heartbeatInterval);
|
|
373
|
+
}
|
|
374
|
+
async _performHeartbeat() {
|
|
375
|
+
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
const controller = new AbortController;
|
|
380
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.heartbeatTimeout);
|
|
381
|
+
const response = await fetch(`${this.config.baseUrl}/ping`, {
|
|
382
|
+
method: "GET",
|
|
383
|
+
signal: controller.signal,
|
|
384
|
+
cache: "no-store"
|
|
385
|
+
});
|
|
386
|
+
clearTimeout(timeoutId);
|
|
387
|
+
if (response.ok) {
|
|
388
|
+
this.consecutiveFailures = 0;
|
|
389
|
+
if (this.state !== "online") {
|
|
390
|
+
this._setState("online", "Heartbeat successful");
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
this._handleHeartbeatFailure("Heartbeat returned non-OK status");
|
|
394
|
+
}
|
|
395
|
+
} catch (error) {
|
|
396
|
+
this._handleHeartbeatFailure(error instanceof Error ? error.message : "Heartbeat failed");
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
_handleHeartbeatFailure(reason) {
|
|
400
|
+
this.consecutiveFailures++;
|
|
401
|
+
if (this.consecutiveFailures >= this.config.failureThreshold) {
|
|
402
|
+
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
|
403
|
+
this._setState("offline", reason);
|
|
404
|
+
} else {
|
|
405
|
+
this._setState("degraded", reason);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
_setState(newState, reason) {
|
|
410
|
+
if (this.state === newState)
|
|
411
|
+
return;
|
|
412
|
+
const oldState = this.state;
|
|
413
|
+
this.state = newState;
|
|
414
|
+
console.debug(`[ConnectionMonitor] ${oldState} → ${newState}: ${reason}`);
|
|
415
|
+
this.callbacks.forEach((callback) => {
|
|
416
|
+
try {
|
|
417
|
+
callback(newState, reason);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
console.error("[ConnectionMonitor] Error in callback:", error);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// src/messaging.ts
|
|
426
|
+
class PlaycademyMessaging {
|
|
427
|
+
listeners = new Map;
|
|
428
|
+
send(type, payload, options) {
|
|
429
|
+
if (options?.target) {
|
|
430
|
+
this.sendViaPostMessage(type, payload, options.target, options.origin || "*");
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const context = this.getMessagingContext(type);
|
|
434
|
+
if (context.shouldUsePostMessage) {
|
|
435
|
+
this.sendViaPostMessage(type, payload, context.target, context.origin);
|
|
436
|
+
} else {
|
|
437
|
+
this.sendViaCustomEvent(type, payload);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
listen(type, handler) {
|
|
441
|
+
const postMessageListener = (event) => {
|
|
442
|
+
const messageEvent = event;
|
|
443
|
+
if (messageEvent.data?.type === type) {
|
|
444
|
+
handler(messageEvent.data.payload || messageEvent.data);
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
const customEventListener = (event) => {
|
|
448
|
+
handler(event.detail);
|
|
449
|
+
};
|
|
450
|
+
if (!this.listeners.has(type)) {
|
|
451
|
+
this.listeners.set(type, new Map);
|
|
452
|
+
}
|
|
453
|
+
const listenerMap = this.listeners.get(type);
|
|
454
|
+
listenerMap.set(handler, {
|
|
455
|
+
postMessage: postMessageListener,
|
|
456
|
+
customEvent: customEventListener
|
|
457
|
+
});
|
|
458
|
+
window.addEventListener("message", postMessageListener);
|
|
459
|
+
window.addEventListener(type, customEventListener);
|
|
460
|
+
}
|
|
461
|
+
unlisten(type, handler) {
|
|
462
|
+
const typeListeners = this.listeners.get(type);
|
|
463
|
+
if (!typeListeners || !typeListeners.has(handler)) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const listeners = typeListeners.get(handler);
|
|
467
|
+
window.removeEventListener("message", listeners.postMessage);
|
|
468
|
+
window.removeEventListener(type, listeners.customEvent);
|
|
469
|
+
typeListeners.delete(handler);
|
|
470
|
+
if (typeListeners.size === 0) {
|
|
471
|
+
this.listeners.delete(type);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
getMessagingContext(eventType) {
|
|
475
|
+
const isIframe = typeof window !== "undefined" && window.self !== window.top;
|
|
476
|
+
const iframeToParentEvents = [
|
|
477
|
+
"PLAYCADEMY_READY" /* READY */,
|
|
478
|
+
"PLAYCADEMY_EXIT" /* EXIT */,
|
|
479
|
+
"PLAYCADEMY_TELEMETRY" /* TELEMETRY */,
|
|
480
|
+
"PLAYCADEMY_KEY_EVENT" /* KEY_EVENT */,
|
|
481
|
+
"PLAYCADEMY_DISPLAY_ALERT" /* DISPLAY_ALERT */
|
|
482
|
+
];
|
|
483
|
+
const shouldUsePostMessage = isIframe && iframeToParentEvents.includes(eventType);
|
|
484
|
+
return {
|
|
485
|
+
shouldUsePostMessage,
|
|
486
|
+
target: shouldUsePostMessage ? window.parent : undefined,
|
|
487
|
+
origin: "*"
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
sendViaPostMessage(type, payload, target = window.parent, origin = "*") {
|
|
491
|
+
const messageData = { type };
|
|
492
|
+
if (payload !== undefined) {
|
|
493
|
+
messageData.payload = payload;
|
|
494
|
+
}
|
|
495
|
+
target.postMessage(messageData, origin);
|
|
496
|
+
}
|
|
497
|
+
sendViaCustomEvent(type, payload) {
|
|
498
|
+
window.dispatchEvent(new CustomEvent(type, { detail: payload }));
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
var MessageEvents, messaging;
|
|
502
|
+
var init_messaging = __esm(() => {
|
|
503
|
+
((MessageEvents2) => {
|
|
504
|
+
MessageEvents2["INIT"] = "PLAYCADEMY_INIT";
|
|
505
|
+
MessageEvents2["TOKEN_REFRESH"] = "PLAYCADEMY_TOKEN_REFRESH";
|
|
506
|
+
MessageEvents2["PAUSE"] = "PLAYCADEMY_PAUSE";
|
|
507
|
+
MessageEvents2["RESUME"] = "PLAYCADEMY_RESUME";
|
|
508
|
+
MessageEvents2["FORCE_EXIT"] = "PLAYCADEMY_FORCE_EXIT";
|
|
509
|
+
MessageEvents2["OVERLAY"] = "PLAYCADEMY_OVERLAY";
|
|
510
|
+
MessageEvents2["CONNECTION_STATE"] = "PLAYCADEMY_CONNECTION_STATE";
|
|
511
|
+
MessageEvents2["READY"] = "PLAYCADEMY_READY";
|
|
512
|
+
MessageEvents2["EXIT"] = "PLAYCADEMY_EXIT";
|
|
513
|
+
MessageEvents2["TELEMETRY"] = "PLAYCADEMY_TELEMETRY";
|
|
514
|
+
MessageEvents2["KEY_EVENT"] = "PLAYCADEMY_KEY_EVENT";
|
|
515
|
+
MessageEvents2["DISPLAY_ALERT"] = "PLAYCADEMY_DISPLAY_ALERT";
|
|
516
|
+
MessageEvents2["AUTH_STATE_CHANGE"] = "PLAYCADEMY_AUTH_STATE_CHANGE";
|
|
517
|
+
MessageEvents2["AUTH_CALLBACK"] = "PLAYCADEMY_AUTH_CALLBACK";
|
|
518
|
+
})(MessageEvents ||= {});
|
|
519
|
+
messaging = new PlaycademyMessaging;
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// src/core/connection/utils.ts
|
|
523
|
+
function createDisplayAlert(authContext) {
|
|
524
|
+
return (message, options) => {
|
|
525
|
+
if (authContext?.isInIframe && typeof window !== "undefined" && window.parent !== window) {
|
|
526
|
+
window.parent.postMessage({
|
|
527
|
+
type: "PLAYCADEMY_DISPLAY_ALERT",
|
|
528
|
+
message,
|
|
529
|
+
options
|
|
530
|
+
}, "*");
|
|
531
|
+
} else {
|
|
532
|
+
const prefix = options?.type === "error" ? "❌" : options?.type === "warning" ? "⚠️" : "ℹ️";
|
|
533
|
+
console.log(`${prefix} ${message}`);
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// src/core/connection/manager.ts
|
|
539
|
+
class ConnectionManager {
|
|
540
|
+
monitor;
|
|
541
|
+
authContext;
|
|
542
|
+
disconnectHandler;
|
|
543
|
+
connectionChangeCallback;
|
|
544
|
+
currentState = "online";
|
|
545
|
+
additionalDisconnectHandlers = new Set;
|
|
546
|
+
constructor(config) {
|
|
547
|
+
this.authContext = config.authContext;
|
|
548
|
+
this.disconnectHandler = config.onDisconnect;
|
|
549
|
+
this.connectionChangeCallback = config.onConnectionChange;
|
|
550
|
+
if (config.authContext?.isInIframe) {
|
|
551
|
+
this._setupPlatformListener();
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
getState() {
|
|
555
|
+
return this.monitor?.getState() ?? this.currentState;
|
|
556
|
+
}
|
|
557
|
+
async checkNow() {
|
|
558
|
+
if (!this.monitor) {
|
|
559
|
+
return this.currentState;
|
|
560
|
+
}
|
|
561
|
+
return await this.monitor.checkNow();
|
|
562
|
+
}
|
|
563
|
+
reportRequestSuccess() {
|
|
564
|
+
this.monitor?.reportRequestSuccess();
|
|
565
|
+
}
|
|
566
|
+
reportRequestFailure(error) {
|
|
567
|
+
this.monitor?.reportRequestFailure(error);
|
|
568
|
+
}
|
|
569
|
+
onDisconnect(callback) {
|
|
570
|
+
this.additionalDisconnectHandlers.add(callback);
|
|
571
|
+
return () => {
|
|
572
|
+
this.additionalDisconnectHandlers.delete(callback);
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
stop() {
|
|
576
|
+
this.monitor?.stop();
|
|
577
|
+
}
|
|
578
|
+
_setupPlatformListener() {
|
|
579
|
+
messaging.listen("PLAYCADEMY_CONNECTION_STATE" /* CONNECTION_STATE */, ({ state, reason }) => {
|
|
580
|
+
this.currentState = state;
|
|
581
|
+
this._handleConnectionChange(state, reason);
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
_handleConnectionChange(state, reason) {
|
|
585
|
+
this.connectionChangeCallback?.(state, reason);
|
|
586
|
+
if (state === "offline" || state === "degraded") {
|
|
587
|
+
const context = {
|
|
588
|
+
state,
|
|
589
|
+
reason,
|
|
590
|
+
timestamp: Date.now(),
|
|
591
|
+
displayAlert: createDisplayAlert(this.authContext)
|
|
592
|
+
};
|
|
593
|
+
if (this.disconnectHandler) {
|
|
594
|
+
this.disconnectHandler(context);
|
|
595
|
+
}
|
|
596
|
+
this.additionalDisconnectHandlers.forEach((handler) => {
|
|
597
|
+
handler(context);
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
var init_manager = __esm(() => {
|
|
603
|
+
init_messaging();
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
// src/core/connection/index.ts
|
|
607
|
+
var init_connection = __esm(() => {
|
|
608
|
+
init_manager();
|
|
609
|
+
});
|
|
610
|
+
|
|
280
611
|
// src/core/errors.ts
|
|
612
|
+
function extractApiErrorInfo(error) {
|
|
613
|
+
if (!(error instanceof ApiError)) {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
const info = {
|
|
617
|
+
status: error.status,
|
|
618
|
+
statusText: error.message
|
|
619
|
+
};
|
|
620
|
+
if (error.details && typeof error.details === "object") {
|
|
621
|
+
const details = error.details;
|
|
622
|
+
if ("error" in details && typeof details.error === "string") {
|
|
623
|
+
info.error = details.error;
|
|
624
|
+
}
|
|
625
|
+
if ("message" in details && typeof details.message === "string") {
|
|
626
|
+
info.message = details.message;
|
|
627
|
+
}
|
|
628
|
+
if (!info.error && !info.message) {
|
|
629
|
+
info.details = error.details;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return info;
|
|
633
|
+
}
|
|
281
634
|
var PlaycademyError, ApiError;
|
|
282
635
|
var init_errors = __esm(() => {
|
|
283
636
|
PlaycademyError = class PlaycademyError extends Error {
|
|
@@ -638,100 +991,6 @@ var init_identity = __esm(() => {
|
|
|
638
991
|
init_login();
|
|
639
992
|
});
|
|
640
993
|
|
|
641
|
-
// src/messaging.ts
|
|
642
|
-
class PlaycademyMessaging {
|
|
643
|
-
listeners = new Map;
|
|
644
|
-
send(type, payload, options) {
|
|
645
|
-
if (options?.target) {
|
|
646
|
-
this.sendViaPostMessage(type, payload, options.target, options.origin || "*");
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
const context = this.getMessagingContext(type);
|
|
650
|
-
if (context.shouldUsePostMessage) {
|
|
651
|
-
this.sendViaPostMessage(type, payload, context.target, context.origin);
|
|
652
|
-
} else {
|
|
653
|
-
this.sendViaCustomEvent(type, payload);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
listen(type, handler) {
|
|
657
|
-
const postMessageListener = (event) => {
|
|
658
|
-
const messageEvent = event;
|
|
659
|
-
if (messageEvent.data?.type === type) {
|
|
660
|
-
handler(messageEvent.data.payload || messageEvent.data);
|
|
661
|
-
}
|
|
662
|
-
};
|
|
663
|
-
const customEventListener = (event) => {
|
|
664
|
-
handler(event.detail);
|
|
665
|
-
};
|
|
666
|
-
if (!this.listeners.has(type)) {
|
|
667
|
-
this.listeners.set(type, new Map);
|
|
668
|
-
}
|
|
669
|
-
const listenerMap = this.listeners.get(type);
|
|
670
|
-
listenerMap.set(handler, {
|
|
671
|
-
postMessage: postMessageListener,
|
|
672
|
-
customEvent: customEventListener
|
|
673
|
-
});
|
|
674
|
-
window.addEventListener("message", postMessageListener);
|
|
675
|
-
window.addEventListener(type, customEventListener);
|
|
676
|
-
}
|
|
677
|
-
unlisten(type, handler) {
|
|
678
|
-
const typeListeners = this.listeners.get(type);
|
|
679
|
-
if (!typeListeners || !typeListeners.has(handler)) {
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
const listeners = typeListeners.get(handler);
|
|
683
|
-
window.removeEventListener("message", listeners.postMessage);
|
|
684
|
-
window.removeEventListener(type, listeners.customEvent);
|
|
685
|
-
typeListeners.delete(handler);
|
|
686
|
-
if (typeListeners.size === 0) {
|
|
687
|
-
this.listeners.delete(type);
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
getMessagingContext(eventType) {
|
|
691
|
-
const isIframe = typeof window !== "undefined" && window.self !== window.top;
|
|
692
|
-
const iframeToParentEvents = [
|
|
693
|
-
"PLAYCADEMY_READY" /* READY */,
|
|
694
|
-
"PLAYCADEMY_EXIT" /* EXIT */,
|
|
695
|
-
"PLAYCADEMY_TELEMETRY" /* TELEMETRY */,
|
|
696
|
-
"PLAYCADEMY_KEY_EVENT" /* KEY_EVENT */
|
|
697
|
-
];
|
|
698
|
-
const shouldUsePostMessage = isIframe && iframeToParentEvents.includes(eventType);
|
|
699
|
-
return {
|
|
700
|
-
shouldUsePostMessage,
|
|
701
|
-
target: shouldUsePostMessage ? window.parent : undefined,
|
|
702
|
-
origin: "*"
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
sendViaPostMessage(type, payload, target = window.parent, origin = "*") {
|
|
706
|
-
const messageData = { type };
|
|
707
|
-
if (payload !== undefined) {
|
|
708
|
-
messageData.payload = payload;
|
|
709
|
-
}
|
|
710
|
-
target.postMessage(messageData, origin);
|
|
711
|
-
}
|
|
712
|
-
sendViaCustomEvent(type, payload) {
|
|
713
|
-
window.dispatchEvent(new CustomEvent(type, { detail: payload }));
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
var MessageEvents, messaging;
|
|
717
|
-
var init_messaging = __esm(() => {
|
|
718
|
-
((MessageEvents2) => {
|
|
719
|
-
MessageEvents2["INIT"] = "PLAYCADEMY_INIT";
|
|
720
|
-
MessageEvents2["TOKEN_REFRESH"] = "PLAYCADEMY_TOKEN_REFRESH";
|
|
721
|
-
MessageEvents2["PAUSE"] = "PLAYCADEMY_PAUSE";
|
|
722
|
-
MessageEvents2["RESUME"] = "PLAYCADEMY_RESUME";
|
|
723
|
-
MessageEvents2["FORCE_EXIT"] = "PLAYCADEMY_FORCE_EXIT";
|
|
724
|
-
MessageEvents2["OVERLAY"] = "PLAYCADEMY_OVERLAY";
|
|
725
|
-
MessageEvents2["READY"] = "PLAYCADEMY_READY";
|
|
726
|
-
MessageEvents2["EXIT"] = "PLAYCADEMY_EXIT";
|
|
727
|
-
MessageEvents2["TELEMETRY"] = "PLAYCADEMY_TELEMETRY";
|
|
728
|
-
MessageEvents2["KEY_EVENT"] = "PLAYCADEMY_KEY_EVENT";
|
|
729
|
-
MessageEvents2["AUTH_STATE_CHANGE"] = "PLAYCADEMY_AUTH_STATE_CHANGE";
|
|
730
|
-
MessageEvents2["AUTH_CALLBACK"] = "PLAYCADEMY_AUTH_CALLBACK";
|
|
731
|
-
})(MessageEvents ||= {});
|
|
732
|
-
messaging = new PlaycademyMessaging;
|
|
733
|
-
});
|
|
734
|
-
|
|
735
994
|
// src/core/namespaces/runtime.ts
|
|
736
995
|
function createRuntimeNamespace(client) {
|
|
737
996
|
const eventListeners = new Map;
|
|
@@ -755,7 +1014,7 @@ function createRuntimeNamespace(client) {
|
|
|
755
1014
|
const forwardKeys = Array.isArray(playcademyConfig?.forwardKeys) ? playcademyConfig.forwardKeys : ["Escape"];
|
|
756
1015
|
const keySet = new Set(forwardKeys.map((k) => k.toLowerCase()));
|
|
757
1016
|
const keyListener = (event) => {
|
|
758
|
-
if (keySet.has(event.key
|
|
1017
|
+
if (keySet.has(event.key?.toLowerCase() ?? "") || keySet.has(event.code?.toLowerCase() ?? "")) {
|
|
759
1018
|
messaging.send("PLAYCADEMY_KEY_EVENT" /* KEY_EVENT */, {
|
|
760
1019
|
key: event.key,
|
|
761
1020
|
code: event.code,
|
|
@@ -842,7 +1101,7 @@ function createRuntimeNamespace(client) {
|
|
|
842
1101
|
},
|
|
843
1102
|
cdn: {
|
|
844
1103
|
url(pathOrStrings, ...values) {
|
|
845
|
-
const
|
|
1104
|
+
const gameUrl = client["initPayload"]?.gameUrl;
|
|
846
1105
|
let path;
|
|
847
1106
|
if (Array.isArray(pathOrStrings) && "raw" in pathOrStrings) {
|
|
848
1107
|
const strings = pathOrStrings;
|
|
@@ -852,20 +1111,20 @@ function createRuntimeNamespace(client) {
|
|
|
852
1111
|
} else {
|
|
853
1112
|
path = pathOrStrings;
|
|
854
1113
|
}
|
|
855
|
-
if (!
|
|
1114
|
+
if (!gameUrl) {
|
|
856
1115
|
return path.startsWith("./") ? path : "./" + path;
|
|
857
1116
|
}
|
|
858
1117
|
const cleanPath = path.startsWith("./") ? path.slice(2) : path;
|
|
859
|
-
return
|
|
1118
|
+
return gameUrl + cleanPath;
|
|
860
1119
|
},
|
|
861
1120
|
fetch: async (path, options) => {
|
|
862
|
-
const
|
|
863
|
-
if (!
|
|
1121
|
+
const gameUrl = client["initPayload"]?.gameUrl;
|
|
1122
|
+
if (!gameUrl) {
|
|
864
1123
|
const relativePath = path.startsWith("./") ? path : "./" + path;
|
|
865
1124
|
return fetch(relativePath, options);
|
|
866
1125
|
}
|
|
867
1126
|
const cleanPath = path.startsWith("./") ? path.slice(2) : path;
|
|
868
|
-
return fetch(
|
|
1127
|
+
return fetch(gameUrl + cleanPath, options);
|
|
869
1128
|
},
|
|
870
1129
|
json: async (path) => {
|
|
871
1130
|
const response = await client.runtime.cdn.fetch(path);
|
|
@@ -1047,8 +1306,8 @@ async function request({
|
|
|
1047
1306
|
const rawText = await res.text().catch(() => "");
|
|
1048
1307
|
return rawText && rawText.length > 0 ? rawText : undefined;
|
|
1049
1308
|
}
|
|
1050
|
-
async function fetchManifest(
|
|
1051
|
-
const manifestUrl = `${
|
|
1309
|
+
async function fetchManifest(deploymentUrl) {
|
|
1310
|
+
const manifestUrl = `${deploymentUrl.replace(/\/$/, "")}/playcademy.manifest.json`;
|
|
1052
1311
|
try {
|
|
1053
1312
|
const response = await fetch(manifestUrl);
|
|
1054
1313
|
if (!response.ok) {
|
|
@@ -1086,8 +1345,8 @@ function createGamesNamespace(client) {
|
|
|
1086
1345
|
const promise = client["request"](`/games/${gameIdOrSlug}`, "GET");
|
|
1087
1346
|
return gameFetchCache.get(gameIdOrSlug, async () => {
|
|
1088
1347
|
const baseGameData = await promise;
|
|
1089
|
-
if (baseGameData.gameType === "hosted" && baseGameData.
|
|
1090
|
-
const manifestData = await fetchManifest(baseGameData.
|
|
1348
|
+
if (baseGameData.gameType === "hosted" && baseGameData.deploymentUrl !== null && baseGameData.deploymentUrl !== "") {
|
|
1349
|
+
const manifestData = await fetchManifest(baseGameData.deploymentUrl);
|
|
1091
1350
|
return { ...baseGameData, manifest: manifestData };
|
|
1092
1351
|
}
|
|
1093
1352
|
return baseGameData;
|
|
@@ -1276,22 +1535,28 @@ function createDevNamespace(client) {
|
|
|
1276
1535
|
}
|
|
1277
1536
|
},
|
|
1278
1537
|
games: {
|
|
1279
|
-
deploy: {
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1538
|
+
deploy: async (slug, options) => {
|
|
1539
|
+
const { metadata, file, backend, hooks } = options;
|
|
1540
|
+
hooks?.onEvent?.({ type: "init" });
|
|
1541
|
+
let game;
|
|
1542
|
+
if (metadata) {
|
|
1543
|
+
game = await client["request"](`/games/${slug}`, "PUT", {
|
|
1283
1544
|
body: metadata
|
|
1284
1545
|
});
|
|
1285
|
-
if (metadata.gameType === "external"
|
|
1546
|
+
if (metadata.gameType === "external" && !file && !backend) {
|
|
1286
1547
|
return game;
|
|
1287
1548
|
}
|
|
1549
|
+
}
|
|
1550
|
+
let uploadToken;
|
|
1551
|
+
if (file) {
|
|
1288
1552
|
const fileName = file instanceof File ? file.name : "game.zip";
|
|
1289
1553
|
const initiateResponse = await client["request"]("/games/uploads/initiate/", "POST", {
|
|
1290
1554
|
body: {
|
|
1291
1555
|
fileName,
|
|
1292
|
-
gameId: game
|
|
1556
|
+
gameId: game?.id || slug
|
|
1293
1557
|
}
|
|
1294
1558
|
});
|
|
1559
|
+
uploadToken = initiateResponse.uploadToken;
|
|
1295
1560
|
if (hooks?.onEvent && typeof XMLHttpRequest !== "undefined") {
|
|
1296
1561
|
await new Promise((resolve, reject) => {
|
|
1297
1562
|
const xhr = new XMLHttpRequest;
|
|
@@ -1332,7 +1597,9 @@ function createDevNamespace(client) {
|
|
|
1332
1597
|
throw new Error(`File upload failed: ${uploadResponse.status} ${uploadResponse.statusText}`);
|
|
1333
1598
|
}
|
|
1334
1599
|
}
|
|
1335
|
-
|
|
1600
|
+
}
|
|
1601
|
+
if (uploadToken || backend) {
|
|
1602
|
+
const deployUrl = `${client.baseUrl}/games/${slug}/deploy`;
|
|
1336
1603
|
const authToken = client.getToken();
|
|
1337
1604
|
const tokenType = client.getTokenType();
|
|
1338
1605
|
const headers = {
|
|
@@ -1345,25 +1612,33 @@ function createDevNamespace(client) {
|
|
|
1345
1612
|
headers["Authorization"] = `Bearer ${authToken}`;
|
|
1346
1613
|
}
|
|
1347
1614
|
}
|
|
1348
|
-
const
|
|
1615
|
+
const requestBody = {};
|
|
1616
|
+
if (uploadToken)
|
|
1617
|
+
requestBody.uploadToken = uploadToken;
|
|
1618
|
+
if (metadata)
|
|
1619
|
+
requestBody.metadata = metadata;
|
|
1620
|
+
if (backend) {
|
|
1621
|
+
requestBody.code = backend.code;
|
|
1622
|
+
requestBody.config = backend.config;
|
|
1623
|
+
if (backend.bindings)
|
|
1624
|
+
requestBody.bindings = backend.bindings;
|
|
1625
|
+
if (backend.schema)
|
|
1626
|
+
requestBody.schema = backend.schema;
|
|
1627
|
+
if (backend.secrets)
|
|
1628
|
+
requestBody.secrets = backend.secrets;
|
|
1629
|
+
}
|
|
1630
|
+
const finalizeResponse = await fetch(deployUrl, {
|
|
1349
1631
|
method: "POST",
|
|
1350
1632
|
headers,
|
|
1351
|
-
body: JSON.stringify(
|
|
1352
|
-
tempS3Key: initiateResponse.tempS3Key,
|
|
1353
|
-
gameId: initiateResponse.gameId,
|
|
1354
|
-
version: initiateResponse.version,
|
|
1355
|
-
slug,
|
|
1356
|
-
metadata,
|
|
1357
|
-
originalFileName: fileName
|
|
1358
|
-
}),
|
|
1633
|
+
body: JSON.stringify(requestBody),
|
|
1359
1634
|
credentials: "omit"
|
|
1360
1635
|
});
|
|
1361
1636
|
if (!finalizeResponse.ok) {
|
|
1362
1637
|
const errText = await finalizeResponse.text().catch(() => "");
|
|
1363
|
-
throw new Error(`
|
|
1638
|
+
throw new Error(`Deploy request failed: ${finalizeResponse.status} ${finalizeResponse.statusText}${errText ? ` - ${errText}` : ""}`);
|
|
1364
1639
|
}
|
|
1365
1640
|
if (!finalizeResponse.body) {
|
|
1366
|
-
throw new Error("
|
|
1641
|
+
throw new Error("Deploy response body missing");
|
|
1367
1642
|
}
|
|
1368
1643
|
hooks?.onEvent?.({ type: "finalizeStart" });
|
|
1369
1644
|
let sawAnyServerEvent = false;
|
|
@@ -1418,20 +1693,19 @@ function createDevNamespace(client) {
|
|
|
1418
1693
|
}
|
|
1419
1694
|
}
|
|
1420
1695
|
}
|
|
1421
|
-
throw new Error("
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
return
|
|
1425
|
-
},
|
|
1426
|
-
seed: async (slug, code, environment) => {
|
|
1427
|
-
return client["request"](`/games/${slug}/seed`, "POST", {
|
|
1428
|
-
body: { code, environment }
|
|
1429
|
-
});
|
|
1696
|
+
throw new Error("Deployment completed but no final game data received");
|
|
1697
|
+
}
|
|
1698
|
+
if (game) {
|
|
1699
|
+
return game;
|
|
1430
1700
|
}
|
|
1701
|
+
throw new Error("No deployment actions specified (need metadata, file, or backend)");
|
|
1431
1702
|
},
|
|
1432
|
-
|
|
1433
|
-
return client
|
|
1703
|
+
seed: async (slug, code, environment) => {
|
|
1704
|
+
return client["request"](`/games/${slug}/seed`, "POST", {
|
|
1705
|
+
body: { code, environment }
|
|
1706
|
+
});
|
|
1434
1707
|
},
|
|
1708
|
+
upsert: async (slug, metadata) => client["request"](`/games/${slug}`, "PUT", { body: metadata }),
|
|
1435
1709
|
delete: (gameId) => client["request"](`/games/${gameId}`, "DELETE"),
|
|
1436
1710
|
secrets: {
|
|
1437
1711
|
set: async (slug, secrets) => {
|
|
@@ -1488,6 +1762,24 @@ function createDevNamespace(client) {
|
|
|
1488
1762
|
delete: async (slug, key) => {
|
|
1489
1763
|
await client["request"](`/games/${slug}/bucket/${encodeURIComponent(key)}`, "DELETE");
|
|
1490
1764
|
}
|
|
1765
|
+
},
|
|
1766
|
+
domains: {
|
|
1767
|
+
add: async (slug, hostname) => {
|
|
1768
|
+
return client["request"](`/games/${slug}/domains`, "POST", {
|
|
1769
|
+
body: { hostname }
|
|
1770
|
+
});
|
|
1771
|
+
},
|
|
1772
|
+
list: async (slug) => {
|
|
1773
|
+
const result = await client["request"](`/games/${slug}/domains`, "GET");
|
|
1774
|
+
return result.domains;
|
|
1775
|
+
},
|
|
1776
|
+
status: async (slug, hostname, refresh) => {
|
|
1777
|
+
const params = refresh ? "?refresh=true" : "";
|
|
1778
|
+
return client["request"](`/games/${slug}/domains/${hostname}${params}`, "GET");
|
|
1779
|
+
},
|
|
1780
|
+
delete: async (slug, hostname) => {
|
|
1781
|
+
await client["request"](`/games/${slug}/domains/${hostname}`, "DELETE");
|
|
1782
|
+
}
|
|
1491
1783
|
}
|
|
1492
1784
|
},
|
|
1493
1785
|
items: {
|
|
@@ -2534,10 +2826,10 @@ async function waitForPlaycademyInit(allowedParentOrigins) {
|
|
|
2534
2826
|
});
|
|
2535
2827
|
}
|
|
2536
2828
|
function createStandaloneConfig() {
|
|
2537
|
-
console.
|
|
2829
|
+
console.debug("[Playcademy SDK] Standalone mode detected, creating mock context for sandbox development");
|
|
2538
2830
|
const mockConfig = {
|
|
2539
2831
|
baseUrl: "http://localhost:4321",
|
|
2540
|
-
gameUrl:
|
|
2832
|
+
gameUrl: window.location.origin,
|
|
2541
2833
|
token: "mock-game-token-for-local-dev",
|
|
2542
2834
|
gameId: "mock-game-id-from-template",
|
|
2543
2835
|
realtimeUrl: undefined
|
|
@@ -2559,7 +2851,9 @@ async function init(options) {
|
|
|
2559
2851
|
gameUrl: config.gameUrl,
|
|
2560
2852
|
token: config.token,
|
|
2561
2853
|
gameId: config.gameId,
|
|
2562
|
-
autoStartSession: window.self !== window.top
|
|
2854
|
+
autoStartSession: window.self !== window.top,
|
|
2855
|
+
onDisconnect: options?.onDisconnect,
|
|
2856
|
+
enableConnectionMonitoring: options?.enableConnectionMonitoring
|
|
2563
2857
|
});
|
|
2564
2858
|
client["initPayload"] = config;
|
|
2565
2859
|
messaging.listen("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, ({ token }) => client.setToken(token));
|
|
@@ -2628,6 +2922,7 @@ __export(exports_client, {
|
|
|
2628
2922
|
var PlaycademyClient;
|
|
2629
2923
|
var init_client = __esm(() => {
|
|
2630
2924
|
init_src();
|
|
2925
|
+
init_connection();
|
|
2631
2926
|
init_errors();
|
|
2632
2927
|
init_namespaces();
|
|
2633
2928
|
init_request();
|
|
@@ -2642,6 +2937,7 @@ var init_client = __esm(() => {
|
|
|
2642
2937
|
internalClientSessionId;
|
|
2643
2938
|
authContext;
|
|
2644
2939
|
initPayload;
|
|
2940
|
+
connectionManager;
|
|
2645
2941
|
constructor(config) {
|
|
2646
2942
|
this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
|
|
2647
2943
|
this.gameUrl = config?.gameUrl;
|
|
@@ -2650,6 +2946,7 @@ var init_client = __esm(() => {
|
|
|
2650
2946
|
this.authStrategy = createAuthStrategy(config?.token ?? null, config?.tokenType);
|
|
2651
2947
|
this._detectAuthContext();
|
|
2652
2948
|
this._initializeInternalSession().catch(() => {});
|
|
2949
|
+
this._initializeConnectionMonitor();
|
|
2653
2950
|
}
|
|
2654
2951
|
getBaseUrl() {
|
|
2655
2952
|
const isRelative = this.baseUrl.startsWith("/");
|
|
@@ -2684,6 +2981,20 @@ var init_client = __esm(() => {
|
|
|
2684
2981
|
onAuthChange(callback) {
|
|
2685
2982
|
this.on("authChange", (payload) => callback(payload.token));
|
|
2686
2983
|
}
|
|
2984
|
+
onDisconnect(callback) {
|
|
2985
|
+
if (!this.connectionManager) {
|
|
2986
|
+
return () => {};
|
|
2987
|
+
}
|
|
2988
|
+
return this.connectionManager.onDisconnect(callback);
|
|
2989
|
+
}
|
|
2990
|
+
getConnectionState() {
|
|
2991
|
+
return this.connectionManager?.getState() ?? "unknown";
|
|
2992
|
+
}
|
|
2993
|
+
async checkConnection() {
|
|
2994
|
+
if (!this.connectionManager)
|
|
2995
|
+
return "unknown";
|
|
2996
|
+
return await this.connectionManager.checkNow();
|
|
2997
|
+
}
|
|
2687
2998
|
_setAuthContext(context) {
|
|
2688
2999
|
this.authContext = context;
|
|
2689
3000
|
}
|
|
@@ -2701,28 +3012,42 @@ var init_client = __esm(() => {
|
|
|
2701
3012
|
...options?.headers,
|
|
2702
3013
|
...this.authStrategy.getHeaders()
|
|
2703
3014
|
};
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
3015
|
+
try {
|
|
3016
|
+
const result = await request({
|
|
3017
|
+
path,
|
|
3018
|
+
method,
|
|
3019
|
+
body: options?.body,
|
|
3020
|
+
baseUrl: this.baseUrl,
|
|
3021
|
+
extraHeaders: effectiveHeaders,
|
|
3022
|
+
raw: options?.raw
|
|
3023
|
+
});
|
|
3024
|
+
this.connectionManager?.reportRequestSuccess();
|
|
3025
|
+
return result;
|
|
3026
|
+
} catch (error) {
|
|
3027
|
+
this.connectionManager?.reportRequestFailure(error);
|
|
3028
|
+
throw error;
|
|
3029
|
+
}
|
|
2712
3030
|
}
|
|
2713
3031
|
async requestGameBackend(path, method, body, headers, raw) {
|
|
2714
3032
|
const effectiveHeaders = {
|
|
2715
3033
|
...headers,
|
|
2716
3034
|
...this.authStrategy.getHeaders()
|
|
2717
3035
|
};
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
3036
|
+
try {
|
|
3037
|
+
const result = await request({
|
|
3038
|
+
path,
|
|
3039
|
+
method,
|
|
3040
|
+
body,
|
|
3041
|
+
baseUrl: this.getGameBackendUrl(),
|
|
3042
|
+
extraHeaders: effectiveHeaders,
|
|
3043
|
+
raw
|
|
3044
|
+
});
|
|
3045
|
+
this.connectionManager?.reportRequestSuccess();
|
|
3046
|
+
return result;
|
|
3047
|
+
} catch (error) {
|
|
3048
|
+
this.connectionManager?.reportRequestFailure(error);
|
|
3049
|
+
throw error;
|
|
3050
|
+
}
|
|
2726
3051
|
}
|
|
2727
3052
|
_ensureGameId() {
|
|
2728
3053
|
if (!this.gameId) {
|
|
@@ -2733,6 +3058,25 @@ var init_client = __esm(() => {
|
|
|
2733
3058
|
_detectAuthContext() {
|
|
2734
3059
|
this.authContext = { isInIframe: isInIframe() };
|
|
2735
3060
|
}
|
|
3061
|
+
_initializeConnectionMonitor() {
|
|
3062
|
+
if (typeof window === "undefined")
|
|
3063
|
+
return;
|
|
3064
|
+
const isEnabled = this.config.enableConnectionMonitoring ?? true;
|
|
3065
|
+
if (!isEnabled)
|
|
3066
|
+
return;
|
|
3067
|
+
try {
|
|
3068
|
+
this.connectionManager = new ConnectionManager({
|
|
3069
|
+
baseUrl: this.baseUrl,
|
|
3070
|
+
authContext: this.authContext,
|
|
3071
|
+
onDisconnect: this.config.onDisconnect,
|
|
3072
|
+
onConnectionChange: (state, reason) => {
|
|
3073
|
+
this.emit("connectionChange", { state, reason });
|
|
3074
|
+
}
|
|
3075
|
+
});
|
|
3076
|
+
} catch (error) {
|
|
3077
|
+
log.error("[Playcademy SDK] Failed to initialize connection manager:", { error });
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
2736
3080
|
async _initializeInternalSession() {
|
|
2737
3081
|
if (!this.gameId || this.internalClientSessionId)
|
|
2738
3082
|
return;
|
|
@@ -2782,9 +3126,16 @@ var init_client = __esm(() => {
|
|
|
2782
3126
|
|
|
2783
3127
|
// src/index.ts
|
|
2784
3128
|
init_client();
|
|
3129
|
+
init_errors();
|
|
3130
|
+
init_connection();
|
|
2785
3131
|
init_messaging();
|
|
2786
3132
|
export {
|
|
2787
3133
|
messaging,
|
|
3134
|
+
extractApiErrorInfo,
|
|
3135
|
+
PlaycademyError,
|
|
2788
3136
|
PlaycademyClient,
|
|
2789
|
-
MessageEvents
|
|
3137
|
+
MessageEvents,
|
|
3138
|
+
ConnectionMonitor,
|
|
3139
|
+
ConnectionManager,
|
|
3140
|
+
ApiError
|
|
2790
3141
|
};
|