@playcademy/sdk 0.1.18 → 0.2.1
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 +48 -0
- package/dist/index.d.ts +224 -3558
- package/dist/index.js +737 -1826
- package/dist/internal.d.ts +6598 -0
- package/dist/internal.js +3006 -0
- package/dist/server.d.ts +86 -40
- package/dist/server.js +9 -35
- package/dist/types.d.ts +589 -2219
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -1,28 +1,21 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __export = (target, all) => {
|
|
3
|
-
for (var name in all)
|
|
4
|
-
__defProp(target, name, {
|
|
5
|
-
get: all[name],
|
|
6
|
-
enumerable: true,
|
|
7
|
-
configurable: true,
|
|
8
|
-
set: (newValue) => all[name] = () => newValue
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
12
|
-
|
|
13
1
|
// ../logger/src/index.ts
|
|
14
2
|
var isBrowser = () => {
|
|
15
3
|
const g = globalThis;
|
|
16
4
|
return typeof g.window !== "undefined" && typeof g.document !== "undefined";
|
|
17
|
-
}
|
|
5
|
+
};
|
|
6
|
+
var isProduction = () => {
|
|
18
7
|
return typeof process !== "undefined" && false;
|
|
19
|
-
}
|
|
8
|
+
};
|
|
9
|
+
var isDevelopment = () => {
|
|
20
10
|
return typeof process !== "undefined" && true;
|
|
21
|
-
}
|
|
11
|
+
};
|
|
12
|
+
var isInteractiveTTY = () => {
|
|
22
13
|
return typeof process !== "undefined" && Boolean(process.stdout && process.stdout.isTTY);
|
|
23
|
-
}
|
|
14
|
+
};
|
|
15
|
+
var isSilent = () => {
|
|
24
16
|
return typeof process !== "undefined" && process.env.LOG_SILENT === "true";
|
|
25
|
-
}
|
|
17
|
+
};
|
|
18
|
+
var detectOutputFormat = () => {
|
|
26
19
|
if (isBrowser()) {
|
|
27
20
|
return "browser";
|
|
28
21
|
}
|
|
@@ -46,7 +39,17 @@ var isBrowser = () => {
|
|
|
46
39
|
return "color-tty";
|
|
47
40
|
}
|
|
48
41
|
return "json-single-line";
|
|
49
|
-
}
|
|
42
|
+
};
|
|
43
|
+
var colors = {
|
|
44
|
+
reset: "\x1B[0m",
|
|
45
|
+
dim: "\x1B[2m",
|
|
46
|
+
red: "\x1B[31m",
|
|
47
|
+
yellow: "\x1B[33m",
|
|
48
|
+
blue: "\x1B[34m",
|
|
49
|
+
cyan: "\x1B[36m",
|
|
50
|
+
gray: "\x1B[90m"
|
|
51
|
+
};
|
|
52
|
+
var getLevelColor = (level) => {
|
|
50
53
|
switch (level) {
|
|
51
54
|
case "debug":
|
|
52
55
|
return colors.blue;
|
|
@@ -59,7 +62,8 @@ var isBrowser = () => {
|
|
|
59
62
|
default:
|
|
60
63
|
return colors.reset;
|
|
61
64
|
}
|
|
62
|
-
}
|
|
65
|
+
};
|
|
66
|
+
var formatBrowserOutput = (level, message, context) => {
|
|
63
67
|
const timestamp = new Date().toISOString();
|
|
64
68
|
const levelUpper = level.toUpperCase();
|
|
65
69
|
const consoleMethod = getConsoleMethod(level);
|
|
@@ -68,7 +72,8 @@ var isBrowser = () => {
|
|
|
68
72
|
} else {
|
|
69
73
|
consoleMethod(`[${timestamp}] ${levelUpper}`, message);
|
|
70
74
|
}
|
|
71
|
-
}
|
|
75
|
+
};
|
|
76
|
+
var formatColorTTY = (level, message, context) => {
|
|
72
77
|
const timestamp = new Date().toISOString();
|
|
73
78
|
const levelColor = getLevelColor(level);
|
|
74
79
|
const levelUpper = level.toUpperCase().padEnd(5);
|
|
@@ -79,7 +84,8 @@ var isBrowser = () => {
|
|
|
79
84
|
} else {
|
|
80
85
|
consoleMethod(`${coloredPrefix} ${message}`);
|
|
81
86
|
}
|
|
82
|
-
}
|
|
87
|
+
};
|
|
88
|
+
var formatJSONSingleLine = (level, message, context) => {
|
|
83
89
|
const timestamp = new Date().toISOString();
|
|
84
90
|
const logEntry = {
|
|
85
91
|
timestamp,
|
|
@@ -89,7 +95,8 @@ var isBrowser = () => {
|
|
|
89
95
|
};
|
|
90
96
|
const consoleMethod = getConsoleMethod(level);
|
|
91
97
|
consoleMethod(JSON.stringify(logEntry));
|
|
92
|
-
}
|
|
98
|
+
};
|
|
99
|
+
var formatJSONPretty = (level, message, context) => {
|
|
93
100
|
const timestamp = new Date().toISOString();
|
|
94
101
|
const logEntry = {
|
|
95
102
|
timestamp,
|
|
@@ -99,7 +106,8 @@ var isBrowser = () => {
|
|
|
99
106
|
};
|
|
100
107
|
const consoleMethod = getConsoleMethod(level);
|
|
101
108
|
consoleMethod(JSON.stringify(logEntry, null, 2));
|
|
102
|
-
}
|
|
109
|
+
};
|
|
110
|
+
var getConsoleMethod = (level) => {
|
|
103
111
|
switch (level) {
|
|
104
112
|
case "debug":
|
|
105
113
|
return console.debug;
|
|
@@ -112,18 +120,28 @@ var isBrowser = () => {
|
|
|
112
120
|
default:
|
|
113
121
|
return console.log;
|
|
114
122
|
}
|
|
115
|
-
}
|
|
123
|
+
};
|
|
124
|
+
var levelPriority = {
|
|
125
|
+
debug: 0,
|
|
126
|
+
info: 1,
|
|
127
|
+
warn: 2,
|
|
128
|
+
error: 3
|
|
129
|
+
};
|
|
130
|
+
var getMinimumLogLevel = () => {
|
|
116
131
|
const envLevel = typeof process !== "undefined" ? (process.env.LOG_LEVEL ?? "").toLowerCase() : "";
|
|
117
132
|
if (envLevel && ["debug", "info", "warn", "error"].includes(envLevel)) {
|
|
118
133
|
return envLevel;
|
|
119
134
|
}
|
|
120
135
|
return isProduction() ? "info" : "debug";
|
|
121
|
-
}
|
|
136
|
+
};
|
|
137
|
+
var shouldLog = (level) => {
|
|
122
138
|
if (isSilent())
|
|
123
139
|
return false;
|
|
124
140
|
const minLevel = getMinimumLogLevel();
|
|
125
141
|
return levelPriority[level] >= levelPriority[minLevel];
|
|
126
|
-
}
|
|
142
|
+
};
|
|
143
|
+
var customHandler;
|
|
144
|
+
var performLog = (level, message, context) => {
|
|
127
145
|
if (!shouldLog(level))
|
|
128
146
|
return;
|
|
129
147
|
if (customHandler) {
|
|
@@ -145,7 +163,8 @@ var isBrowser = () => {
|
|
|
145
163
|
formatJSONPretty(level, message, context);
|
|
146
164
|
break;
|
|
147
165
|
}
|
|
148
|
-
}
|
|
166
|
+
};
|
|
167
|
+
var createLogger = () => {
|
|
149
168
|
return {
|
|
150
169
|
debug: (message, context) => performLog("debug", message, context),
|
|
151
170
|
info: (message, context) => performLog("info", message, context),
|
|
@@ -153,25 +172,8 @@ var isBrowser = () => {
|
|
|
153
172
|
error: (message, context) => performLog("error", message, context),
|
|
154
173
|
log: performLog
|
|
155
174
|
};
|
|
156
|
-
}
|
|
157
|
-
var
|
|
158
|
-
colors = {
|
|
159
|
-
reset: "\x1B[0m",
|
|
160
|
-
dim: "\x1B[2m",
|
|
161
|
-
red: "\x1B[31m",
|
|
162
|
-
yellow: "\x1B[33m",
|
|
163
|
-
blue: "\x1B[34m",
|
|
164
|
-
cyan: "\x1B[36m",
|
|
165
|
-
gray: "\x1B[90m"
|
|
166
|
-
};
|
|
167
|
-
levelPriority = {
|
|
168
|
-
debug: 0,
|
|
169
|
-
info: 1,
|
|
170
|
-
warn: 2,
|
|
171
|
-
error: 3
|
|
172
|
-
};
|
|
173
|
-
log = createLogger();
|
|
174
|
-
});
|
|
175
|
+
};
|
|
176
|
+
var log = createLogger();
|
|
175
177
|
|
|
176
178
|
// src/core/auth/strategies.ts
|
|
177
179
|
class ApiKeyAuth {
|
|
@@ -421,8 +423,25 @@ class ConnectionMonitor {
|
|
|
421
423
|
});
|
|
422
424
|
}
|
|
423
425
|
}
|
|
424
|
-
|
|
425
426
|
// src/messaging.ts
|
|
427
|
+
var MessageEvents;
|
|
428
|
+
((MessageEvents2) => {
|
|
429
|
+
MessageEvents2["INIT"] = "PLAYCADEMY_INIT";
|
|
430
|
+
MessageEvents2["TOKEN_REFRESH"] = "PLAYCADEMY_TOKEN_REFRESH";
|
|
431
|
+
MessageEvents2["PAUSE"] = "PLAYCADEMY_PAUSE";
|
|
432
|
+
MessageEvents2["RESUME"] = "PLAYCADEMY_RESUME";
|
|
433
|
+
MessageEvents2["FORCE_EXIT"] = "PLAYCADEMY_FORCE_EXIT";
|
|
434
|
+
MessageEvents2["OVERLAY"] = "PLAYCADEMY_OVERLAY";
|
|
435
|
+
MessageEvents2["CONNECTION_STATE"] = "PLAYCADEMY_CONNECTION_STATE";
|
|
436
|
+
MessageEvents2["READY"] = "PLAYCADEMY_READY";
|
|
437
|
+
MessageEvents2["EXIT"] = "PLAYCADEMY_EXIT";
|
|
438
|
+
MessageEvents2["TELEMETRY"] = "PLAYCADEMY_TELEMETRY";
|
|
439
|
+
MessageEvents2["KEY_EVENT"] = "PLAYCADEMY_KEY_EVENT";
|
|
440
|
+
MessageEvents2["DISPLAY_ALERT"] = "PLAYCADEMY_DISPLAY_ALERT";
|
|
441
|
+
MessageEvents2["AUTH_STATE_CHANGE"] = "PLAYCADEMY_AUTH_STATE_CHANGE";
|
|
442
|
+
MessageEvents2["AUTH_CALLBACK"] = "PLAYCADEMY_AUTH_CALLBACK";
|
|
443
|
+
})(MessageEvents ||= {});
|
|
444
|
+
|
|
426
445
|
class PlaycademyMessaging {
|
|
427
446
|
listeners = new Map;
|
|
428
447
|
send(type, payload, options) {
|
|
@@ -498,26 +517,7 @@ class PlaycademyMessaging {
|
|
|
498
517
|
window.dispatchEvent(new CustomEvent(type, { detail: payload }));
|
|
499
518
|
}
|
|
500
519
|
}
|
|
501
|
-
var
|
|
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
|
-
});
|
|
520
|
+
var messaging = new PlaycademyMessaging;
|
|
521
521
|
|
|
522
522
|
// src/core/connection/utils.ts
|
|
523
523
|
function createDisplayAlert(authContext) {
|
|
@@ -599,16 +599,24 @@ class ConnectionManager {
|
|
|
599
599
|
}
|
|
600
600
|
}
|
|
601
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
|
-
|
|
611
602
|
// src/core/errors.ts
|
|
603
|
+
class PlaycademyError extends Error {
|
|
604
|
+
constructor(message) {
|
|
605
|
+
super(message);
|
|
606
|
+
this.name = "PlaycademyError";
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
class ApiError extends Error {
|
|
611
|
+
status;
|
|
612
|
+
details;
|
|
613
|
+
constructor(status, message, details) {
|
|
614
|
+
super(`${status} ${message}`);
|
|
615
|
+
this.status = status;
|
|
616
|
+
this.details = details;
|
|
617
|
+
Object.setPrototypeOf(this, ApiError.prototype);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
612
620
|
function extractApiErrorInfo(error) {
|
|
613
621
|
if (!(error instanceof ApiError)) {
|
|
614
622
|
return null;
|
|
@@ -617,216 +625,370 @@ function extractApiErrorInfo(error) {
|
|
|
617
625
|
status: error.status,
|
|
618
626
|
statusText: error.message
|
|
619
627
|
};
|
|
620
|
-
if (error.details
|
|
621
|
-
|
|
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
|
-
}
|
|
628
|
+
if (error.details) {
|
|
629
|
+
info.details = error.details;
|
|
631
630
|
}
|
|
632
631
|
return info;
|
|
633
632
|
}
|
|
634
|
-
var PlaycademyError, ApiError;
|
|
635
|
-
var init_errors = __esm(() => {
|
|
636
|
-
PlaycademyError = class PlaycademyError extends Error {
|
|
637
|
-
constructor(message) {
|
|
638
|
-
super(message);
|
|
639
|
-
this.name = "PlaycademyError";
|
|
640
|
-
}
|
|
641
|
-
};
|
|
642
|
-
ApiError = class ApiError extends Error {
|
|
643
|
-
status;
|
|
644
|
-
details;
|
|
645
|
-
constructor(status, message, details) {
|
|
646
|
-
super(`${status} ${message}`);
|
|
647
|
-
this.status = status;
|
|
648
|
-
this.details = details;
|
|
649
|
-
Object.setPrototypeOf(this, ApiError.prototype);
|
|
650
|
-
}
|
|
651
|
-
};
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
// src/core/namespaces/auth.ts
|
|
655
|
-
function createAuthNamespace(client) {
|
|
656
|
-
return {
|
|
657
|
-
login: async (credentials) => {
|
|
658
|
-
try {
|
|
659
|
-
const response = await client["request"]("/auth/sign-in/email", "POST", { body: credentials });
|
|
660
|
-
client.setToken(response.token, "session");
|
|
661
|
-
return {
|
|
662
|
-
success: true,
|
|
663
|
-
token: response.token,
|
|
664
|
-
user: response.user,
|
|
665
|
-
expiresAt: response.expiresAt
|
|
666
|
-
};
|
|
667
|
-
} catch (error) {
|
|
668
|
-
return {
|
|
669
|
-
success: false,
|
|
670
|
-
error: error instanceof Error ? error.message : "Authentication failed"
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
},
|
|
674
|
-
logout: async () => {
|
|
675
|
-
try {
|
|
676
|
-
await client["request"]("/auth/sign-out", "POST");
|
|
677
|
-
} catch {}
|
|
678
|
-
client.setToken(null);
|
|
679
|
-
},
|
|
680
|
-
apiKeys: {
|
|
681
|
-
create: async (options) => {
|
|
682
|
-
return client["request"]("/dev/api-keys", "POST", {
|
|
683
|
-
body: {
|
|
684
|
-
name: options?.name || `SDK Key - ${new Date().toISOString()}`,
|
|
685
|
-
expiresIn: options?.expiresIn !== undefined ? options.expiresIn : null,
|
|
686
|
-
permissions: options?.permissions || {
|
|
687
|
-
games: ["read", "write", "delete"],
|
|
688
|
-
users: ["read:self", "write:self"],
|
|
689
|
-
dev: ["read", "write"]
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
});
|
|
693
|
-
},
|
|
694
|
-
list: async () => {
|
|
695
|
-
return client["request"]("/auth/api-key/list", "GET");
|
|
696
|
-
},
|
|
697
|
-
revoke: async (keyId) => {
|
|
698
|
-
await client["request"]("/auth/api-key/revoke", "POST", {
|
|
699
|
-
body: { id: keyId }
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// ../utils/src/random.ts
|
|
707
|
-
async function generateSecureRandomString(length) {
|
|
708
|
-
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
709
|
-
const randomValues = new Uint8Array(length);
|
|
710
|
-
globalThis.crypto.getRandomValues(randomValues);
|
|
711
|
-
return Array.from(randomValues).map((byte) => charset[byte % charset.length]).join("");
|
|
712
|
-
}
|
|
713
633
|
|
|
714
|
-
// src/core/
|
|
715
|
-
function
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
634
|
+
// src/core/request.ts
|
|
635
|
+
function checkDevWarnings(data) {
|
|
636
|
+
if (!data || typeof data !== "object")
|
|
637
|
+
return;
|
|
638
|
+
const response = data;
|
|
639
|
+
const warningType = response.__playcademyDevWarning;
|
|
640
|
+
if (!warningType)
|
|
641
|
+
return;
|
|
642
|
+
switch (warningType) {
|
|
643
|
+
case "timeback-not-configured":
|
|
644
|
+
console.warn("%c⚠️ TimeBack Not Configured", "background: #f59e0b; color: white; padding: 6px 12px; border-radius: 4px; font-weight: bold; font-size: 13px");
|
|
645
|
+
console.log("%cTimeBack is configured in playcademy.config.js but the sandbox does not have TimeBack credentials.", "color: #f59e0b; font-weight: 500");
|
|
646
|
+
console.log("To test TimeBack locally:");
|
|
647
|
+
console.log(" Set the following environment variables:");
|
|
648
|
+
console.log(" • %cTIMEBACK_ONEROSTER_API_URL", "color: #0ea5e9; font-weight: 600; font-family: monospace");
|
|
649
|
+
console.log(" • %cTIMEBACK_CALIPER_API_URL", "color: #0ea5e9; font-weight: 600; font-family: monospace");
|
|
650
|
+
console.log(" • %cTIMEBACK_API_CLIENT_ID/SECRET", "color: #0ea5e9; font-weight: 600; font-family: monospace");
|
|
651
|
+
console.log(" Or deploy your game: %cplaycademy deploy", "color: #10b981; font-weight: 600; font-family: monospace");
|
|
652
|
+
console.log(" Or wait for %c@superbuilders/timeback-local%c (coming soon)", "color: #8b5cf6; font-weight: 600; font-family: monospace", "color: inherit");
|
|
653
|
+
break;
|
|
654
|
+
default:
|
|
655
|
+
console.warn(`[Playcademy Dev Warning] ${warningType}`);
|
|
656
|
+
}
|
|
721
657
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const jsonStr = JSON.stringify(data);
|
|
726
|
-
const base64 = btoa(jsonStr).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
727
|
-
return `${csrfToken}.${base64}`;
|
|
658
|
+
function prepareRequestBody(body, headers) {
|
|
659
|
+
if (body instanceof FormData) {
|
|
660
|
+
return body;
|
|
728
661
|
}
|
|
729
|
-
|
|
662
|
+
if (body instanceof ArrayBuffer || body instanceof Blob || ArrayBuffer.isView(body)) {
|
|
663
|
+
if (!headers["Content-Type"]) {
|
|
664
|
+
headers["Content-Type"] = "application/octet-stream";
|
|
665
|
+
}
|
|
666
|
+
return body;
|
|
667
|
+
}
|
|
668
|
+
if (body !== undefined && body !== null) {
|
|
669
|
+
headers["Content-Type"] = "application/json";
|
|
670
|
+
return JSON.stringify(body);
|
|
671
|
+
}
|
|
672
|
+
return;
|
|
730
673
|
}
|
|
731
|
-
function
|
|
732
|
-
|
|
733
|
-
|
|
674
|
+
async function request({
|
|
675
|
+
path,
|
|
676
|
+
baseUrl,
|
|
677
|
+
method = "GET",
|
|
678
|
+
body,
|
|
679
|
+
extraHeaders = {},
|
|
680
|
+
raw = false
|
|
681
|
+
}) {
|
|
682
|
+
const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
|
|
683
|
+
const headers = { ...extraHeaders };
|
|
684
|
+
const payload = prepareRequestBody(body, headers);
|
|
685
|
+
const res = await fetch(url, {
|
|
686
|
+
method,
|
|
687
|
+
headers,
|
|
688
|
+
body: payload,
|
|
689
|
+
credentials: "omit"
|
|
690
|
+
});
|
|
691
|
+
if (raw) {
|
|
692
|
+
return res;
|
|
693
|
+
}
|
|
694
|
+
if (!res.ok) {
|
|
695
|
+
const clonedRes = res.clone();
|
|
696
|
+
const errorBody = await clonedRes.json().catch(() => clonedRes.text().catch(() => {
|
|
697
|
+
return;
|
|
698
|
+
})) ?? undefined;
|
|
699
|
+
throw new ApiError(res.status, res.statusText, errorBody);
|
|
700
|
+
}
|
|
701
|
+
if (res.status === 204)
|
|
702
|
+
return;
|
|
703
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
704
|
+
if (contentType.includes("application/json")) {
|
|
734
705
|
try {
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
}
|
|
706
|
+
const parsed = await res.json();
|
|
707
|
+
checkDevWarnings(parsed);
|
|
708
|
+
return parsed;
|
|
709
|
+
} catch (err) {
|
|
710
|
+
if (err instanceof SyntaxError)
|
|
711
|
+
return;
|
|
712
|
+
throw err;
|
|
713
|
+
}
|
|
743
714
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
function getOAuthConfig(provider) {
|
|
747
|
-
const configGetter = OAUTH_CONFIGS[provider];
|
|
748
|
-
if (!configGetter)
|
|
749
|
-
throw new Error(`Unsupported auth provider: ${provider}`);
|
|
750
|
-
return configGetter();
|
|
715
|
+
const rawText = await res.text().catch(() => "");
|
|
716
|
+
return rawText && rawText.length > 0 ? rawText : undefined;
|
|
751
717
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
OAUTH_CONFIGS = {
|
|
755
|
-
get TIMEBACK() {
|
|
756
|
-
return getTimebackConfig;
|
|
757
|
-
}
|
|
758
|
-
};
|
|
759
|
-
});
|
|
760
|
-
|
|
761
|
-
// src/core/auth/flows/popup.ts
|
|
762
|
-
async function initiatePopupFlow(options) {
|
|
763
|
-
const { provider, callbackUrl, onStateChange, oauth } = options;
|
|
718
|
+
async function fetchManifest(deploymentUrl) {
|
|
719
|
+
const manifestUrl = `${deploymentUrl.replace(/\/$/, "")}/playcademy.manifest.json`;
|
|
764
720
|
try {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const defaults = getOAuthConfig(provider);
|
|
770
|
-
const config = oauth ? { ...defaults, ...oauth } : defaults;
|
|
771
|
-
if (!config.clientId) {
|
|
772
|
-
throw new Error(`clientId is required for ${provider} authentication. ` + "Please provide it in the oauth parameter.");
|
|
773
|
-
}
|
|
774
|
-
const stateData = options.stateData;
|
|
775
|
-
const state = await generateOAuthState(stateData);
|
|
776
|
-
const params = new URLSearchParams({
|
|
777
|
-
response_type: "code",
|
|
778
|
-
client_id: config.clientId,
|
|
779
|
-
redirect_uri: callbackUrl,
|
|
780
|
-
state
|
|
781
|
-
});
|
|
782
|
-
if (config.scope) {
|
|
783
|
-
params.set("scope", config.scope);
|
|
784
|
-
}
|
|
785
|
-
const authUrl = `${config.authorizationEndpoint}?${params.toString()}`;
|
|
786
|
-
const popup = openPopupWindow(authUrl, "playcademy-auth");
|
|
787
|
-
if (!popup || popup.closed) {
|
|
788
|
-
throw new Error("Popup blocked. Please enable popups and try again.");
|
|
721
|
+
const response = await fetch(manifestUrl);
|
|
722
|
+
if (!response.ok) {
|
|
723
|
+
log.error(`[fetchManifest] Failed to fetch manifest from ${manifestUrl}. Status: ${response.status}`);
|
|
724
|
+
throw new PlaycademyError(`Failed to fetch manifest: ${response.status} ${response.statusText}`);
|
|
789
725
|
}
|
|
790
|
-
|
|
791
|
-
status: "exchanging_token",
|
|
792
|
-
message: "Waiting for authentication..."
|
|
793
|
-
});
|
|
794
|
-
return await waitForServerMessage(popup, onStateChange);
|
|
726
|
+
return await response.json();
|
|
795
727
|
} catch (error) {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
error
|
|
728
|
+
if (error instanceof PlaycademyError) {
|
|
729
|
+
throw error;
|
|
730
|
+
}
|
|
731
|
+
log.error(`[Playcademy SDK] Error fetching or parsing manifest from ${manifestUrl}:`, {
|
|
732
|
+
error
|
|
801
733
|
});
|
|
802
|
-
throw
|
|
734
|
+
throw new PlaycademyError("Failed to load or parse game manifest");
|
|
803
735
|
}
|
|
804
736
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
737
|
+
|
|
738
|
+
// src/core/static/init.ts
|
|
739
|
+
async function getPlaycademyConfig(allowedParentOrigins) {
|
|
740
|
+
const preloaded = window.PLAYCADEMY;
|
|
741
|
+
if (preloaded?.token) {
|
|
742
|
+
return preloaded;
|
|
743
|
+
}
|
|
744
|
+
if (window.self !== window.top) {
|
|
745
|
+
return await waitForPlaycademyInit(allowedParentOrigins);
|
|
746
|
+
} else {
|
|
747
|
+
return createStandaloneConfig();
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
function getReferrerOrigin() {
|
|
751
|
+
try {
|
|
752
|
+
return document.referrer ? new URL(document.referrer).origin : null;
|
|
753
|
+
} catch {
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
function buildAllowedOrigins(explicit) {
|
|
758
|
+
if (Array.isArray(explicit) && explicit.length > 0)
|
|
759
|
+
return explicit;
|
|
760
|
+
const ref = getReferrerOrigin();
|
|
761
|
+
return ref ? [ref] : [];
|
|
762
|
+
}
|
|
763
|
+
function isOriginAllowed(origin, allowlist) {
|
|
764
|
+
if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") {
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
if (!allowlist || allowlist.length === 0) {
|
|
768
|
+
console.error("[Playcademy SDK] No allowed origins configured. Consider passing allowedParentOrigins explicitly to init().");
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
return allowlist.includes(origin);
|
|
772
|
+
}
|
|
773
|
+
async function waitForPlaycademyInit(allowedParentOrigins) {
|
|
774
|
+
return new Promise((resolve, reject) => {
|
|
775
|
+
let contextReceived = false;
|
|
776
|
+
const timeoutDuration = 5000;
|
|
777
|
+
const allowlist = buildAllowedOrigins(allowedParentOrigins);
|
|
778
|
+
let hasWarnedAboutUntrustedOrigin = false;
|
|
779
|
+
function warnAboutUntrustedOrigin(origin) {
|
|
780
|
+
if (hasWarnedAboutUntrustedOrigin)
|
|
781
|
+
return;
|
|
782
|
+
hasWarnedAboutUntrustedOrigin = true;
|
|
783
|
+
console.warn("[Playcademy SDK] Ignoring INIT from untrusted origin:", origin);
|
|
784
|
+
}
|
|
785
|
+
const handleMessage = (event) => {
|
|
786
|
+
if (event.data?.type !== "PLAYCADEMY_INIT" /* INIT */)
|
|
787
|
+
return;
|
|
788
|
+
if (!isOriginAllowed(event.origin, allowlist)) {
|
|
789
|
+
warnAboutUntrustedOrigin(event.origin);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
contextReceived = true;
|
|
793
|
+
window.removeEventListener("message", handleMessage);
|
|
794
|
+
clearTimeout(timeoutId);
|
|
795
|
+
window.PLAYCADEMY = event.data.payload;
|
|
796
|
+
resolve(event.data.payload);
|
|
797
|
+
};
|
|
798
|
+
window.addEventListener("message", handleMessage);
|
|
799
|
+
const timeoutId = setTimeout(() => {
|
|
800
|
+
if (!contextReceived) {
|
|
801
|
+
window.removeEventListener("message", handleMessage);
|
|
802
|
+
reject(new Error(`${"PLAYCADEMY_INIT" /* INIT */} not received within ${timeoutDuration}ms`));
|
|
803
|
+
}
|
|
804
|
+
}, timeoutDuration);
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
function createStandaloneConfig() {
|
|
808
|
+
console.debug("[Playcademy SDK] Standalone mode detected, creating mock context for sandbox development");
|
|
809
|
+
const mockConfig = {
|
|
810
|
+
baseUrl: "http://localhost:4321",
|
|
811
|
+
gameUrl: window.location.origin,
|
|
812
|
+
token: "mock-game-token-for-local-dev",
|
|
813
|
+
gameId: "mock-game-id-from-template",
|
|
814
|
+
realtimeUrl: undefined
|
|
815
|
+
};
|
|
816
|
+
window.PLAYCADEMY = mockConfig;
|
|
817
|
+
return mockConfig;
|
|
818
|
+
}
|
|
819
|
+
async function init(options) {
|
|
820
|
+
if (typeof window === "undefined") {
|
|
821
|
+
throw new Error("Playcademy SDK must run in a browser context");
|
|
822
|
+
}
|
|
823
|
+
const config = await getPlaycademyConfig(options?.allowedParentOrigins);
|
|
824
|
+
if (options?.baseUrl) {
|
|
825
|
+
config.baseUrl = options.baseUrl;
|
|
826
|
+
}
|
|
827
|
+
const client = new this({
|
|
828
|
+
baseUrl: config.baseUrl,
|
|
829
|
+
gameUrl: config.gameUrl,
|
|
830
|
+
token: config.token,
|
|
831
|
+
gameId: config.gameId,
|
|
832
|
+
autoStartSession: window.self !== window.top,
|
|
833
|
+
onDisconnect: options?.onDisconnect,
|
|
834
|
+
enableConnectionMonitoring: options?.enableConnectionMonitoring
|
|
835
|
+
});
|
|
836
|
+
client["initPayload"] = config;
|
|
837
|
+
messaging.listen("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, ({ token }) => client.setToken(token));
|
|
838
|
+
messaging.send("PLAYCADEMY_READY" /* READY */, undefined);
|
|
839
|
+
return client;
|
|
840
|
+
}
|
|
841
|
+
// src/core/static/login.ts
|
|
842
|
+
async function login(baseUrl, email, password) {
|
|
843
|
+
let url = baseUrl;
|
|
844
|
+
if (baseUrl.startsWith("/") && typeof window !== "undefined") {
|
|
845
|
+
url = window.location.origin + baseUrl;
|
|
846
|
+
}
|
|
847
|
+
url = url + "/auth/login";
|
|
848
|
+
const response = await fetch(url, {
|
|
849
|
+
method: "POST",
|
|
850
|
+
headers: {
|
|
851
|
+
"Content-Type": "application/json"
|
|
852
|
+
},
|
|
853
|
+
body: JSON.stringify({ email, password })
|
|
854
|
+
});
|
|
855
|
+
if (!response.ok) {
|
|
856
|
+
try {
|
|
857
|
+
const errorData = await response.json();
|
|
858
|
+
const errorMessage = errorData && errorData.message ? String(errorData.message) : response.statusText;
|
|
859
|
+
throw new PlaycademyError(errorMessage);
|
|
860
|
+
} catch (error) {
|
|
861
|
+
log.error("[Playcademy SDK] Failed to parse error response JSON, using status text instead:", { error });
|
|
862
|
+
throw new PlaycademyError(response.statusText);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return response.json();
|
|
866
|
+
}
|
|
867
|
+
// ../utils/src/random.ts
|
|
868
|
+
async function generateSecureRandomString(length) {
|
|
869
|
+
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
870
|
+
const randomValues = new Uint8Array(length);
|
|
871
|
+
globalThis.crypto.getRandomValues(randomValues);
|
|
872
|
+
return Array.from(randomValues).map((byte) => charset[byte % charset.length]).join("");
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// src/core/auth/oauth.ts
|
|
876
|
+
function getTimebackConfig() {
|
|
877
|
+
return {
|
|
878
|
+
authorizationEndpoint: "https://alpha-auth-production-idp.auth.us-west-2.amazoncognito.com/oauth2/authorize",
|
|
879
|
+
tokenEndpoint: "https://alpha-auth-production-idp.auth.us-west-2.amazoncognito.com/oauth2/token",
|
|
880
|
+
scope: "openid email phone"
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
var OAUTH_CONFIGS = {
|
|
884
|
+
get TIMEBACK() {
|
|
885
|
+
return getTimebackConfig;
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
async function generateOAuthState(data) {
|
|
889
|
+
const csrfToken = await generateSecureRandomString(32);
|
|
890
|
+
if (data && Object.keys(data).length > 0) {
|
|
891
|
+
const jsonStr = JSON.stringify(data);
|
|
892
|
+
const base64 = btoa(jsonStr).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
893
|
+
return `${csrfToken}.${base64}`;
|
|
894
|
+
}
|
|
895
|
+
return csrfToken;
|
|
896
|
+
}
|
|
897
|
+
function parseOAuthState(state) {
|
|
898
|
+
const lastDotIndex = state.lastIndexOf(".");
|
|
899
|
+
if (lastDotIndex > 0 && lastDotIndex < state.length - 1) {
|
|
900
|
+
try {
|
|
901
|
+
const csrfToken = state.substring(0, lastDotIndex);
|
|
902
|
+
const base64 = state.substring(lastDotIndex + 1);
|
|
903
|
+
const base64WithPadding = base64.replace(/-/g, "+").replace(/_/g, "/");
|
|
904
|
+
const paddedBase64 = base64WithPadding + "=".repeat((4 - base64WithPadding.length % 4) % 4);
|
|
905
|
+
const jsonStr = atob(paddedBase64);
|
|
906
|
+
const data = JSON.parse(jsonStr);
|
|
907
|
+
return { csrfToken, data };
|
|
908
|
+
} catch {}
|
|
909
|
+
}
|
|
910
|
+
return { csrfToken: state };
|
|
911
|
+
}
|
|
912
|
+
function getOAuthConfig(provider) {
|
|
913
|
+
const configGetter = OAUTH_CONFIGS[provider];
|
|
914
|
+
if (!configGetter)
|
|
915
|
+
throw new Error(`Unsupported auth provider: ${provider}`);
|
|
916
|
+
return configGetter();
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// src/core/static/identity.ts
|
|
920
|
+
var identity = {
|
|
921
|
+
parseOAuthState
|
|
922
|
+
};
|
|
923
|
+
// src/core/auth/flows/popup.ts
|
|
924
|
+
async function initiatePopupFlow(options) {
|
|
925
|
+
const { provider, callbackUrl, onStateChange, oauth } = options;
|
|
926
|
+
try {
|
|
927
|
+
onStateChange?.({
|
|
928
|
+
status: "opening_popup",
|
|
929
|
+
message: "Opening authentication window..."
|
|
930
|
+
});
|
|
931
|
+
const defaults = getOAuthConfig(provider);
|
|
932
|
+
const config = oauth ? { ...defaults, ...oauth } : defaults;
|
|
933
|
+
if (!config.clientId) {
|
|
934
|
+
throw new Error(`clientId is required for ${provider} authentication. ` + "Please provide it in the oauth parameter.");
|
|
935
|
+
}
|
|
936
|
+
const stateData = options.stateData;
|
|
937
|
+
const state = await generateOAuthState(stateData);
|
|
938
|
+
const params = new URLSearchParams({
|
|
939
|
+
response_type: "code",
|
|
940
|
+
client_id: config.clientId,
|
|
941
|
+
redirect_uri: callbackUrl,
|
|
942
|
+
state
|
|
943
|
+
});
|
|
944
|
+
if (config.scope) {
|
|
945
|
+
params.set("scope", config.scope);
|
|
946
|
+
}
|
|
947
|
+
const authUrl = `${config.authorizationEndpoint}?${params.toString()}`;
|
|
948
|
+
const popup = openPopupWindow(authUrl, "playcademy-auth");
|
|
949
|
+
if (!popup || popup.closed) {
|
|
950
|
+
throw new Error("Popup blocked. Please enable popups and try again.");
|
|
951
|
+
}
|
|
952
|
+
onStateChange?.({
|
|
953
|
+
status: "exchanging_token",
|
|
954
|
+
message: "Waiting for authentication..."
|
|
955
|
+
});
|
|
956
|
+
return await waitForServerMessage(popup, onStateChange);
|
|
957
|
+
} catch (error) {
|
|
958
|
+
const errorMessage = error instanceof Error ? error.message : "Authentication failed";
|
|
959
|
+
onStateChange?.({
|
|
960
|
+
status: "error",
|
|
961
|
+
message: errorMessage,
|
|
962
|
+
error: error instanceof Error ? error : new Error(errorMessage)
|
|
963
|
+
});
|
|
964
|
+
throw error;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
async function waitForServerMessage(popup, onStateChange) {
|
|
968
|
+
return new Promise((resolve) => {
|
|
969
|
+
let resolved = false;
|
|
970
|
+
const handleMessage = (event) => {
|
|
971
|
+
if (event.origin !== window.location.origin)
|
|
972
|
+
return;
|
|
973
|
+
const data = event.data;
|
|
974
|
+
if (data?.type === "PLAYCADEMY_AUTH_STATE_CHANGE") {
|
|
975
|
+
resolved = true;
|
|
976
|
+
window.removeEventListener("message", handleMessage);
|
|
977
|
+
if (data.authenticated && data.user) {
|
|
978
|
+
onStateChange?.({
|
|
979
|
+
status: "complete",
|
|
980
|
+
message: "Authentication successful"
|
|
981
|
+
});
|
|
982
|
+
resolve({
|
|
983
|
+
success: true,
|
|
984
|
+
user: data.user
|
|
985
|
+
});
|
|
986
|
+
} else {
|
|
987
|
+
const error = new Error(data.error || "Authentication failed");
|
|
988
|
+
onStateChange?.({
|
|
989
|
+
status: "error",
|
|
990
|
+
message: error.message,
|
|
991
|
+
error
|
|
830
992
|
});
|
|
831
993
|
resolve({
|
|
832
994
|
success: false,
|
|
@@ -870,9 +1032,6 @@ async function waitForServerMessage(popup, onStateChange) {
|
|
|
870
1032
|
}, 5 * 60 * 1000);
|
|
871
1033
|
});
|
|
872
1034
|
}
|
|
873
|
-
var init_popup = __esm(() => {
|
|
874
|
-
init_oauth();
|
|
875
|
-
});
|
|
876
1035
|
|
|
877
1036
|
// src/core/auth/flows/redirect.ts
|
|
878
1037
|
async function initiateRedirectFlow(options) {
|
|
@@ -911,9 +1070,6 @@ async function initiateRedirectFlow(options) {
|
|
|
911
1070
|
throw error;
|
|
912
1071
|
}
|
|
913
1072
|
}
|
|
914
|
-
var init_redirect = __esm(() => {
|
|
915
|
-
init_oauth();
|
|
916
|
-
});
|
|
917
1073
|
|
|
918
1074
|
// src/core/auth/flows/unified.ts
|
|
919
1075
|
async function initiateUnifiedFlow(options) {
|
|
@@ -928,13 +1084,9 @@ async function initiateUnifiedFlow(options) {
|
|
|
928
1084
|
throw new Error(`Unsupported authentication mode: ${effectiveMode}`);
|
|
929
1085
|
}
|
|
930
1086
|
}
|
|
931
|
-
var init_unified = __esm(() => {
|
|
932
|
-
init_popup();
|
|
933
|
-
init_redirect();
|
|
934
|
-
});
|
|
935
1087
|
|
|
936
1088
|
// src/core/auth/login.ts
|
|
937
|
-
async function
|
|
1089
|
+
async function login2(client, options) {
|
|
938
1090
|
try {
|
|
939
1091
|
let stateData = options.stateData;
|
|
940
1092
|
if (!stateData) {
|
|
@@ -973,25 +1125,17 @@ async function login(client, options) {
|
|
|
973
1125
|
};
|
|
974
1126
|
}
|
|
975
1127
|
}
|
|
976
|
-
var init_login = __esm(() => {
|
|
977
|
-
init_src();
|
|
978
|
-
init_unified();
|
|
979
|
-
});
|
|
980
1128
|
|
|
981
|
-
// src/
|
|
1129
|
+
// src/namespaces/game/identity.ts
|
|
982
1130
|
function createIdentityNamespace(client) {
|
|
983
1131
|
return {
|
|
984
|
-
connect: (options) =>
|
|
1132
|
+
connect: (options) => login2(client, options),
|
|
985
1133
|
_getContext: () => ({
|
|
986
1134
|
isInIframe: client["authContext"]?.isInIframe ?? false
|
|
987
1135
|
})
|
|
988
1136
|
};
|
|
989
1137
|
}
|
|
990
|
-
|
|
991
|
-
init_login();
|
|
992
|
-
});
|
|
993
|
-
|
|
994
|
-
// src/core/namespaces/runtime.ts
|
|
1138
|
+
// src/namespaces/game/runtime.ts
|
|
995
1139
|
function createRuntimeNamespace(client) {
|
|
996
1140
|
const eventListeners = new Map;
|
|
997
1141
|
const trackListener = (eventType, handler) => {
|
|
@@ -1040,7 +1184,7 @@ function createRuntimeNamespace(client) {
|
|
|
1040
1184
|
exit: async () => {
|
|
1041
1185
|
if (client["internalClientSessionId"] && client["gameId"]) {
|
|
1042
1186
|
try {
|
|
1043
|
-
await client.
|
|
1187
|
+
await client["_sessionManager"].endSession(client["internalClientSessionId"], client["gameId"]);
|
|
1044
1188
|
} catch (error) {
|
|
1045
1189
|
log.error("[Playcademy SDK] Failed to auto-end session:", {
|
|
1046
1190
|
sessionId: client["internalClientSessionId"],
|
|
@@ -1145,262 +1289,46 @@ function createRuntimeNamespace(client) {
|
|
|
1145
1289
|
}
|
|
1146
1290
|
};
|
|
1147
1291
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
// src/core/cache/ttl-cache.ts
|
|
1154
|
-
function createTTLCache(options) {
|
|
1155
|
-
const cache = new Map;
|
|
1156
|
-
const { ttl: defaultTTL, keyPrefix = "", onClear } = options;
|
|
1157
|
-
async function get(key, loader, config) {
|
|
1158
|
-
const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
|
|
1159
|
-
const now = Date.now();
|
|
1160
|
-
const effectiveTTL = config?.ttl !== undefined ? config.ttl : defaultTTL;
|
|
1161
|
-
const force = config?.force || false;
|
|
1162
|
-
const skipCache = config?.skipCache || false;
|
|
1163
|
-
if (effectiveTTL === 0 || skipCache) {
|
|
1164
|
-
return loader();
|
|
1165
|
-
}
|
|
1166
|
-
if (!force) {
|
|
1167
|
-
const cached = cache.get(fullKey);
|
|
1168
|
-
if (cached && cached.expiresAt > now) {
|
|
1169
|
-
return cached.value;
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
const promise = loader().catch((error) => {
|
|
1173
|
-
cache.delete(fullKey);
|
|
1174
|
-
throw error;
|
|
1175
|
-
});
|
|
1176
|
-
cache.set(fullKey, {
|
|
1177
|
-
value: promise,
|
|
1178
|
-
expiresAt: now + effectiveTTL
|
|
1179
|
-
});
|
|
1180
|
-
return promise;
|
|
1181
|
-
}
|
|
1182
|
-
function clear(key) {
|
|
1183
|
-
if (key === undefined) {
|
|
1184
|
-
cache.clear();
|
|
1185
|
-
onClear?.();
|
|
1186
|
-
} else {
|
|
1187
|
-
const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
|
|
1188
|
-
cache.delete(fullKey);
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
function size() {
|
|
1192
|
-
return cache.size;
|
|
1193
|
-
}
|
|
1194
|
-
function prune() {
|
|
1195
|
-
const now = Date.now();
|
|
1196
|
-
for (const [key, entry] of cache.entries()) {
|
|
1197
|
-
if (entry.expiresAt <= now) {
|
|
1198
|
-
cache.delete(key);
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
function getKeys() {
|
|
1203
|
-
const keys = [];
|
|
1204
|
-
const prefixLen = keyPrefix ? keyPrefix.length + 1 : 0;
|
|
1205
|
-
for (const fullKey of cache.keys()) {
|
|
1206
|
-
keys.push(fullKey.substring(prefixLen));
|
|
1207
|
-
}
|
|
1208
|
-
return keys;
|
|
1209
|
-
}
|
|
1210
|
-
function has(key) {
|
|
1211
|
-
const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
|
|
1212
|
-
const cached = cache.get(fullKey);
|
|
1213
|
-
if (!cached)
|
|
1214
|
-
return false;
|
|
1215
|
-
const now = Date.now();
|
|
1216
|
-
if (cached.expiresAt <= now) {
|
|
1217
|
-
cache.delete(fullKey);
|
|
1218
|
-
return false;
|
|
1219
|
-
}
|
|
1220
|
-
return true;
|
|
1221
|
-
}
|
|
1222
|
-
return { get, clear, size, prune, getKeys, has };
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
// src/core/request.ts
|
|
1226
|
-
function checkDevWarnings(data) {
|
|
1227
|
-
if (!data || typeof data !== "object")
|
|
1228
|
-
return;
|
|
1229
|
-
const response = data;
|
|
1230
|
-
const warningType = response.__playcademyDevWarning;
|
|
1231
|
-
if (!warningType)
|
|
1232
|
-
return;
|
|
1233
|
-
switch (warningType) {
|
|
1234
|
-
case "timeback-not-configured":
|
|
1235
|
-
console.warn("%c⚠️ TimeBack Not Configured", "background: #f59e0b; color: white; padding: 6px 12px; border-radius: 4px; font-weight: bold; font-size: 13px");
|
|
1236
|
-
console.log("%cTimeBack is configured in playcademy.config.js but the sandbox does not have TimeBack credentials.", "color: #f59e0b; font-weight: 500");
|
|
1237
|
-
console.log("To test TimeBack locally:");
|
|
1238
|
-
console.log(" Set the following environment variables:");
|
|
1239
|
-
console.log(" • %cTIMEBACK_ONEROSTER_API_URL", "color: #0ea5e9; font-weight: 600; font-family: monospace");
|
|
1240
|
-
console.log(" • %cTIMEBACK_CALIPER_API_URL", "color: #0ea5e9; font-weight: 600; font-family: monospace");
|
|
1241
|
-
console.log(" • %cTIMEBACK_API_CLIENT_ID/SECRET", "color: #0ea5e9; font-weight: 600; font-family: monospace");
|
|
1242
|
-
console.log(" Or deploy your game: %cplaycademy deploy", "color: #10b981; font-weight: 600; font-family: monospace");
|
|
1243
|
-
console.log(" Or wait for %c@superbuilders/timeback-local%c (coming soon)", "color: #8b5cf6; font-weight: 600; font-family: monospace", "color: inherit");
|
|
1244
|
-
break;
|
|
1245
|
-
default:
|
|
1246
|
-
console.warn(`[Playcademy Dev Warning] ${warningType}`);
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
function prepareRequestBody(body, headers) {
|
|
1250
|
-
if (body instanceof FormData) {
|
|
1251
|
-
return body;
|
|
1252
|
-
}
|
|
1253
|
-
if (body instanceof ArrayBuffer || body instanceof Blob || ArrayBuffer.isView(body)) {
|
|
1254
|
-
if (!headers["Content-Type"]) {
|
|
1255
|
-
headers["Content-Type"] = "application/octet-stream";
|
|
1256
|
-
}
|
|
1257
|
-
return body;
|
|
1258
|
-
}
|
|
1259
|
-
if (body !== undefined && body !== null) {
|
|
1260
|
-
headers["Content-Type"] = "application/json";
|
|
1261
|
-
return JSON.stringify(body);
|
|
1262
|
-
}
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
async function request({
|
|
1266
|
-
path,
|
|
1267
|
-
baseUrl,
|
|
1268
|
-
method = "GET",
|
|
1269
|
-
body,
|
|
1270
|
-
extraHeaders = {},
|
|
1271
|
-
raw = false
|
|
1272
|
-
}) {
|
|
1273
|
-
const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
|
|
1274
|
-
const headers = { ...extraHeaders };
|
|
1275
|
-
const payload = prepareRequestBody(body, headers);
|
|
1276
|
-
const res = await fetch(url, {
|
|
1277
|
-
method,
|
|
1278
|
-
headers,
|
|
1279
|
-
body: payload,
|
|
1280
|
-
credentials: "omit"
|
|
1281
|
-
});
|
|
1282
|
-
if (raw) {
|
|
1283
|
-
return res;
|
|
1284
|
-
}
|
|
1285
|
-
if (!res.ok) {
|
|
1286
|
-
const clonedRes = res.clone();
|
|
1287
|
-
const errorBody = await clonedRes.json().catch(() => clonedRes.text().catch(() => {
|
|
1288
|
-
return;
|
|
1289
|
-
})) ?? undefined;
|
|
1290
|
-
throw new ApiError(res.status, res.statusText, errorBody);
|
|
1291
|
-
}
|
|
1292
|
-
if (res.status === 204)
|
|
1293
|
-
return;
|
|
1294
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
1295
|
-
if (contentType.includes("application/json")) {
|
|
1296
|
-
try {
|
|
1297
|
-
const parsed = await res.json();
|
|
1298
|
-
checkDevWarnings(parsed);
|
|
1299
|
-
return parsed;
|
|
1300
|
-
} catch (err) {
|
|
1301
|
-
if (err instanceof SyntaxError)
|
|
1302
|
-
return;
|
|
1303
|
-
throw err;
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
const rawText = await res.text().catch(() => "");
|
|
1307
|
-
return rawText && rawText.length > 0 ? rawText : undefined;
|
|
1308
|
-
}
|
|
1309
|
-
async function fetchManifest(deploymentUrl) {
|
|
1310
|
-
const manifestUrl = `${deploymentUrl.replace(/\/$/, "")}/playcademy.manifest.json`;
|
|
1311
|
-
try {
|
|
1312
|
-
const response = await fetch(manifestUrl);
|
|
1313
|
-
if (!response.ok) {
|
|
1314
|
-
log.error(`[fetchManifest] Failed to fetch manifest from ${manifestUrl}. Status: ${response.status}`);
|
|
1315
|
-
throw new PlaycademyError(`Failed to fetch manifest: ${response.status} ${response.statusText}`);
|
|
1316
|
-
}
|
|
1317
|
-
return await response.json();
|
|
1318
|
-
} catch (error) {
|
|
1319
|
-
if (error instanceof PlaycademyError) {
|
|
1320
|
-
throw error;
|
|
1321
|
-
}
|
|
1322
|
-
log.error(`[Playcademy SDK] Error fetching or parsing manifest from ${manifestUrl}:`, {
|
|
1323
|
-
error
|
|
1324
|
-
});
|
|
1325
|
-
throw new PlaycademyError("Failed to load or parse game manifest");
|
|
1292
|
+
// src/namespaces/game/backend.ts
|
|
1293
|
+
function createBackendNamespace(client) {
|
|
1294
|
+
function normalizePath(path) {
|
|
1295
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
1326
1296
|
}
|
|
1327
|
-
}
|
|
1328
|
-
var init_request = __esm(() => {
|
|
1329
|
-
init_src();
|
|
1330
|
-
init_errors();
|
|
1331
|
-
});
|
|
1332
|
-
|
|
1333
|
-
// src/core/namespaces/games.ts
|
|
1334
|
-
function createGamesNamespace(client) {
|
|
1335
|
-
const gamesListCache = createTTLCache({
|
|
1336
|
-
ttl: 60 * 1000,
|
|
1337
|
-
keyPrefix: "games.list"
|
|
1338
|
-
});
|
|
1339
|
-
const gameFetchCache = createTTLCache({
|
|
1340
|
-
ttl: 60 * 1000,
|
|
1341
|
-
keyPrefix: "games.fetch"
|
|
1342
|
-
});
|
|
1343
1297
|
return {
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
return gameFetchCache.get(gameIdOrSlug, async () => {
|
|
1347
|
-
const baseGameData = await promise;
|
|
1348
|
-
if (baseGameData.gameType === "hosted" && baseGameData.deploymentUrl !== null && baseGameData.deploymentUrl !== "") {
|
|
1349
|
-
const manifestData = await fetchManifest(baseGameData.deploymentUrl);
|
|
1350
|
-
return { ...baseGameData, manifest: manifestData };
|
|
1351
|
-
}
|
|
1352
|
-
return baseGameData;
|
|
1353
|
-
}, options);
|
|
1298
|
+
async get(path, headers) {
|
|
1299
|
+
return client["requestGameBackend"](normalizePath(path), "GET", undefined, headers);
|
|
1354
1300
|
},
|
|
1355
|
-
|
|
1356
|
-
return
|
|
1301
|
+
async post(path, body, headers) {
|
|
1302
|
+
return client["requestGameBackend"](normalizePath(path), "POST", body, headers);
|
|
1357
1303
|
},
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
await client["request"](`/games/${gameId}/state`, "POST", { body: state });
|
|
1304
|
+
async put(path, body, headers) {
|
|
1305
|
+
return client["requestGameBackend"](normalizePath(path), "PUT", body, headers);
|
|
1361
1306
|
},
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
return client["request"](`/games/${gameId}/state`, "GET");
|
|
1307
|
+
async patch(path, body, headers) {
|
|
1308
|
+
return client["requestGameBackend"](normalizePath(path), "PATCH", body, headers);
|
|
1365
1309
|
},
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
return client["request"](`/games/${idToUse}/sessions`, "POST", {});
|
|
1310
|
+
async delete(path, headers) {
|
|
1311
|
+
return client["requestGameBackend"](normalizePath(path), "DELETE", undefined, headers);
|
|
1369
1312
|
},
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
if (client["internalClientSessionId"] && sessionId === client["internalClientSessionId"] && effectiveGameIdToEnd === client["gameId"]) {
|
|
1373
|
-
client["internalClientSessionId"] = undefined;
|
|
1374
|
-
}
|
|
1375
|
-
await client["request"](`/games/${effectiveGameIdToEnd}/sessions/${sessionId}/end`, "POST");
|
|
1313
|
+
async request(path, method, body, headers) {
|
|
1314
|
+
return client["requestGameBackend"](normalizePath(path), method, body, headers);
|
|
1376
1315
|
},
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
const res = await client["request"](`/games/${gameId}/token`, "POST");
|
|
1380
|
-
if (options?.apply) {
|
|
1381
|
-
client.setToken(res.token);
|
|
1382
|
-
}
|
|
1383
|
-
return res;
|
|
1384
|
-
}
|
|
1316
|
+
async download(path, method = "GET", body, headers) {
|
|
1317
|
+
return client["requestGameBackend"](normalizePath(path), method, body, headers, true);
|
|
1385
1318
|
},
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
const
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
const queryString = params.toString();
|
|
1394
|
-
const path = queryString ? `/games/${gameId}/leaderboard?${queryString}` : `/games/${gameId}/leaderboard`;
|
|
1395
|
-
return client["request"](path, "GET");
|
|
1319
|
+
url(pathOrStrings, ...values) {
|
|
1320
|
+
if (Array.isArray(pathOrStrings) && "raw" in pathOrStrings) {
|
|
1321
|
+
const strings = pathOrStrings;
|
|
1322
|
+
const path2 = strings.reduce((acc, str, i) => {
|
|
1323
|
+
return acc + str + (values[i] != null ? String(values[i]) : "");
|
|
1324
|
+
}, "");
|
|
1325
|
+
return `${client.gameUrl}/api${path2.startsWith("/") ? path2 : `/${path2}`}`;
|
|
1396
1326
|
}
|
|
1327
|
+
const path = pathOrStrings;
|
|
1328
|
+
return `${client.gameUrl}/api${path.startsWith("/") ? path : `/${path}`}`;
|
|
1397
1329
|
}
|
|
1398
1330
|
};
|
|
1399
1331
|
}
|
|
1400
|
-
var init_games = __esm(() => {
|
|
1401
|
-
init_request();
|
|
1402
|
-
});
|
|
1403
|
-
|
|
1404
1332
|
// src/core/cache/permanent-cache.ts
|
|
1405
1333
|
function createPermanentCache(keyPrefix) {
|
|
1406
1334
|
const cache = new Map;
|
|
@@ -1442,7 +1370,7 @@ function createPermanentCache(keyPrefix) {
|
|
|
1442
1370
|
return { get, clear, has, size, keys };
|
|
1443
1371
|
}
|
|
1444
1372
|
|
|
1445
|
-
// src/
|
|
1373
|
+
// src/namespaces/game/users.ts
|
|
1446
1374
|
function createUsersNamespace(client) {
|
|
1447
1375
|
const itemIdCache = createPermanentCache("items");
|
|
1448
1376
|
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -1498,594 +1426,105 @@ function createUsersNamespace(client) {
|
|
|
1498
1426
|
const qty = item?.quantity ?? 0;
|
|
1499
1427
|
return qty >= minQuantity;
|
|
1500
1428
|
}
|
|
1501
|
-
},
|
|
1502
|
-
scores: {
|
|
1503
|
-
get: async (userIdOrOptions, options) => {
|
|
1504
|
-
let userId;
|
|
1505
|
-
let queryOptions;
|
|
1506
|
-
if (typeof userIdOrOptions === "string") {
|
|
1507
|
-
userId = userIdOrOptions;
|
|
1508
|
-
queryOptions = options || {};
|
|
1509
|
-
} else {
|
|
1510
|
-
queryOptions = userIdOrOptions || {};
|
|
1511
|
-
const user = await client["request"]("/users/me", "GET");
|
|
1512
|
-
userId = user.id;
|
|
1513
|
-
}
|
|
1514
|
-
const params = new URLSearchParams({
|
|
1515
|
-
limit: String(queryOptions.limit || 50)
|
|
1516
|
-
});
|
|
1517
|
-
if (queryOptions.gameId) {
|
|
1518
|
-
params.append("gameId", queryOptions.gameId);
|
|
1519
|
-
}
|
|
1520
|
-
return client["request"](`/users/${userId}/scores?${params}`, "GET");
|
|
1521
|
-
}
|
|
1522
1429
|
}
|
|
1523
1430
|
};
|
|
1524
1431
|
}
|
|
1525
|
-
|
|
1432
|
+
// ../constants/src/overworld.ts
|
|
1433
|
+
var ITEM_SLUGS = {
|
|
1434
|
+
PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
|
|
1435
|
+
PLAYCADEMY_XP: "PLAYCADEMY_XP",
|
|
1436
|
+
FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
|
|
1437
|
+
EARLY_ADOPTER_BADGE: "EARLY_ADOPTER_BADGE",
|
|
1438
|
+
FIRST_GAME_BADGE: "FIRST_GAME_BADGE",
|
|
1439
|
+
COMMON_SWORD: "COMMON_SWORD",
|
|
1440
|
+
SMALL_HEALTH_POTION: "SMALL_HEALTH_POTION",
|
|
1441
|
+
SMALL_BACKPACK: "SMALL_BACKPACK",
|
|
1442
|
+
LAVA_LAMP: "LAVA_LAMP",
|
|
1443
|
+
BOOMBOX: "BOOMBOX",
|
|
1444
|
+
CABIN_BED: "CABIN_BED"
|
|
1445
|
+
};
|
|
1446
|
+
var CURRENCIES = {
|
|
1447
|
+
PRIMARY: ITEM_SLUGS.PLAYCADEMY_CREDITS,
|
|
1448
|
+
XP: ITEM_SLUGS.PLAYCADEMY_XP
|
|
1449
|
+
};
|
|
1450
|
+
var BADGES = {
|
|
1451
|
+
FOUNDING_MEMBER: ITEM_SLUGS.FOUNDING_MEMBER_BADGE,
|
|
1452
|
+
EARLY_ADOPTER: ITEM_SLUGS.EARLY_ADOPTER_BADGE,
|
|
1453
|
+
FIRST_GAME: ITEM_SLUGS.FIRST_GAME_BADGE
|
|
1454
|
+
};
|
|
1455
|
+
// ../constants/src/timeback.ts
|
|
1456
|
+
var TIMEBACK_ROUTES = {
|
|
1457
|
+
END_ACTIVITY: "/integrations/timeback/end-activity"
|
|
1458
|
+
};
|
|
1459
|
+
// src/core/cache/singleton-cache.ts
|
|
1460
|
+
function createSingletonCache() {
|
|
1461
|
+
let cachedValue;
|
|
1462
|
+
let hasValue = false;
|
|
1463
|
+
async function get(loader) {
|
|
1464
|
+
if (hasValue) {
|
|
1465
|
+
return cachedValue;
|
|
1466
|
+
}
|
|
1467
|
+
const value = await loader();
|
|
1468
|
+
cachedValue = value;
|
|
1469
|
+
hasValue = true;
|
|
1470
|
+
return value;
|
|
1471
|
+
}
|
|
1472
|
+
function clear() {
|
|
1473
|
+
cachedValue = undefined;
|
|
1474
|
+
hasValue = false;
|
|
1475
|
+
}
|
|
1476
|
+
function has() {
|
|
1477
|
+
return hasValue;
|
|
1478
|
+
}
|
|
1479
|
+
return { get, clear, has };
|
|
1480
|
+
}
|
|
1526
1481
|
|
|
1527
|
-
// src/
|
|
1528
|
-
function
|
|
1482
|
+
// src/namespaces/game/credits.ts
|
|
1483
|
+
function createCreditsNamespace(client) {
|
|
1484
|
+
const creditsIdCache = createSingletonCache();
|
|
1485
|
+
const getCreditsItemId = async () => {
|
|
1486
|
+
return creditsIdCache.get(async () => {
|
|
1487
|
+
const queryParams = new URLSearchParams({ slug: CURRENCIES.PRIMARY });
|
|
1488
|
+
const creditsItem = await client["request"](`/items/resolve?${queryParams.toString()}`, "GET");
|
|
1489
|
+
if (!creditsItem || !creditsItem.id) {
|
|
1490
|
+
throw new Error("Playcademy Credits item not found in catalog");
|
|
1491
|
+
}
|
|
1492
|
+
return creditsItem.id;
|
|
1493
|
+
});
|
|
1494
|
+
};
|
|
1529
1495
|
return {
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1496
|
+
balance: async () => {
|
|
1497
|
+
const inventory = await client["request"]("/inventory", "GET");
|
|
1498
|
+
const primaryCurrencyInventoryItem = inventory.find((item) => item.item?.slug === CURRENCIES.PRIMARY);
|
|
1499
|
+
return primaryCurrencyInventoryItem?.quantity ?? 0;
|
|
1500
|
+
},
|
|
1501
|
+
add: async (amount) => {
|
|
1502
|
+
if (amount <= 0) {
|
|
1503
|
+
throw new Error("Amount must be positive");
|
|
1535
1504
|
}
|
|
1505
|
+
const creditsItemId = await getCreditsItemId();
|
|
1506
|
+
const result = await client["request"]("/inventory/add", "POST", {
|
|
1507
|
+
body: {
|
|
1508
|
+
itemId: creditsItemId,
|
|
1509
|
+
qty: amount
|
|
1510
|
+
}
|
|
1511
|
+
});
|
|
1512
|
+
client["emit"]("inventoryChange", {
|
|
1513
|
+
itemId: creditsItemId,
|
|
1514
|
+
delta: amount,
|
|
1515
|
+
newTotal: result.newTotal
|
|
1516
|
+
});
|
|
1517
|
+
return result.newTotal;
|
|
1536
1518
|
},
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
if (metadata.gameType === "external" && !file && !backend) {
|
|
1547
|
-
return game;
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
let uploadToken;
|
|
1551
|
-
if (file) {
|
|
1552
|
-
const fileName = file instanceof File ? file.name : "game.zip";
|
|
1553
|
-
const initiateResponse = await client["request"]("/games/uploads/initiate/", "POST", {
|
|
1554
|
-
body: {
|
|
1555
|
-
fileName,
|
|
1556
|
-
gameId: game?.id || slug
|
|
1557
|
-
}
|
|
1558
|
-
});
|
|
1559
|
-
uploadToken = initiateResponse.uploadToken;
|
|
1560
|
-
if (hooks?.onEvent && typeof XMLHttpRequest !== "undefined") {
|
|
1561
|
-
await new Promise((resolve, reject) => {
|
|
1562
|
-
const xhr = new XMLHttpRequest;
|
|
1563
|
-
xhr.open("PUT", initiateResponse.presignedUrl, true);
|
|
1564
|
-
const contentType = file.type || "application/octet-stream";
|
|
1565
|
-
try {
|
|
1566
|
-
xhr.setRequestHeader("Content-Type", contentType);
|
|
1567
|
-
} catch {}
|
|
1568
|
-
xhr.upload.onprogress = (event) => {
|
|
1569
|
-
if (event.lengthComputable) {
|
|
1570
|
-
const percent = event.loaded / event.total;
|
|
1571
|
-
hooks.onEvent?.({
|
|
1572
|
-
type: "s3Progress",
|
|
1573
|
-
loaded: event.loaded,
|
|
1574
|
-
total: event.total,
|
|
1575
|
-
percent
|
|
1576
|
-
});
|
|
1577
|
-
}
|
|
1578
|
-
};
|
|
1579
|
-
xhr.onload = () => {
|
|
1580
|
-
if (xhr.status >= 200 && xhr.status < 300)
|
|
1581
|
-
resolve();
|
|
1582
|
-
else
|
|
1583
|
-
reject(new Error(`File upload failed: ${xhr.status} ${xhr.statusText}`));
|
|
1584
|
-
};
|
|
1585
|
-
xhr.onerror = () => reject(new Error("File upload failed: network error"));
|
|
1586
|
-
xhr.send(file);
|
|
1587
|
-
});
|
|
1588
|
-
} else {
|
|
1589
|
-
const uploadResponse = await fetch(initiateResponse.presignedUrl, {
|
|
1590
|
-
method: "PUT",
|
|
1591
|
-
body: file,
|
|
1592
|
-
headers: {
|
|
1593
|
-
"Content-Type": file.type || "application/octet-stream"
|
|
1594
|
-
}
|
|
1595
|
-
});
|
|
1596
|
-
if (!uploadResponse.ok) {
|
|
1597
|
-
throw new Error(`File upload failed: ${uploadResponse.status} ${uploadResponse.statusText}`);
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
if (uploadToken || backend) {
|
|
1602
|
-
const deployUrl = `${client.baseUrl}/games/${slug}/deploy`;
|
|
1603
|
-
const authToken = client.getToken();
|
|
1604
|
-
const tokenType = client.getTokenType();
|
|
1605
|
-
const headers = {
|
|
1606
|
-
"Content-Type": "application/json"
|
|
1607
|
-
};
|
|
1608
|
-
if (authToken) {
|
|
1609
|
-
if (tokenType === "apiKey") {
|
|
1610
|
-
headers["x-api-key"] = authToken;
|
|
1611
|
-
} else {
|
|
1612
|
-
headers["Authorization"] = `Bearer ${authToken}`;
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
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, {
|
|
1631
|
-
method: "POST",
|
|
1632
|
-
headers,
|
|
1633
|
-
body: JSON.stringify(requestBody),
|
|
1634
|
-
credentials: "omit"
|
|
1635
|
-
});
|
|
1636
|
-
if (!finalizeResponse.ok) {
|
|
1637
|
-
const errText = await finalizeResponse.text().catch(() => "");
|
|
1638
|
-
throw new Error(`Deploy request failed: ${finalizeResponse.status} ${finalizeResponse.statusText}${errText ? ` - ${errText}` : ""}`);
|
|
1639
|
-
}
|
|
1640
|
-
if (!finalizeResponse.body) {
|
|
1641
|
-
throw new Error("Deploy response body missing");
|
|
1642
|
-
}
|
|
1643
|
-
hooks?.onEvent?.({ type: "finalizeStart" });
|
|
1644
|
-
let sawAnyServerEvent = false;
|
|
1645
|
-
const reader = finalizeResponse.body.pipeThrough(new TextDecoderStream).getReader();
|
|
1646
|
-
let buffer = "";
|
|
1647
|
-
while (true) {
|
|
1648
|
-
const { done, value } = await reader.read();
|
|
1649
|
-
if (done) {
|
|
1650
|
-
if (!sawAnyServerEvent) {
|
|
1651
|
-
hooks?.onClose?.();
|
|
1652
|
-
hooks?.onEvent?.({ type: "close" });
|
|
1653
|
-
}
|
|
1654
|
-
break;
|
|
1655
|
-
}
|
|
1656
|
-
buffer += value;
|
|
1657
|
-
let eolIndex;
|
|
1658
|
-
while ((eolIndex = buffer.indexOf(`
|
|
1659
|
-
|
|
1660
|
-
`)) >= 0) {
|
|
1661
|
-
const message = buffer.slice(0, eolIndex);
|
|
1662
|
-
buffer = buffer.slice(eolIndex + 2);
|
|
1663
|
-
const eventLine = message.match(/^event: (.*)$/m);
|
|
1664
|
-
const dataLine = message.match(/^data: (.*)$/m);
|
|
1665
|
-
if (eventLine && dataLine) {
|
|
1666
|
-
const eventType = eventLine[1];
|
|
1667
|
-
const eventData = JSON.parse(dataLine[1]);
|
|
1668
|
-
if (eventType === "uploadProgress") {
|
|
1669
|
-
sawAnyServerEvent = true;
|
|
1670
|
-
const percent = (eventData.value ?? 0) / 100;
|
|
1671
|
-
hooks?.onEvent?.({
|
|
1672
|
-
type: "finalizeProgress",
|
|
1673
|
-
percent,
|
|
1674
|
-
currentFileLabel: eventData.currentFileLabel || ""
|
|
1675
|
-
});
|
|
1676
|
-
} else if (eventType === "status") {
|
|
1677
|
-
sawAnyServerEvent = true;
|
|
1678
|
-
if (eventData.message) {
|
|
1679
|
-
hooks?.onEvent?.({
|
|
1680
|
-
type: "finalizeStatus",
|
|
1681
|
-
message: eventData.message
|
|
1682
|
-
});
|
|
1683
|
-
}
|
|
1684
|
-
} else if (eventType === "complete") {
|
|
1685
|
-
sawAnyServerEvent = true;
|
|
1686
|
-
reader.cancel();
|
|
1687
|
-
return eventData;
|
|
1688
|
-
} else if (eventType === "error") {
|
|
1689
|
-
sawAnyServerEvent = true;
|
|
1690
|
-
reader.cancel();
|
|
1691
|
-
throw new Error(eventData.message);
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
throw new Error("Deployment completed but no final game data received");
|
|
1697
|
-
}
|
|
1698
|
-
if (game) {
|
|
1699
|
-
return game;
|
|
1700
|
-
}
|
|
1701
|
-
throw new Error("No deployment actions specified (need metadata, file, or backend)");
|
|
1702
|
-
},
|
|
1703
|
-
seed: async (slug, code, environment) => {
|
|
1704
|
-
return client["request"](`/games/${slug}/seed`, "POST", {
|
|
1705
|
-
body: { code, environment }
|
|
1706
|
-
});
|
|
1707
|
-
},
|
|
1708
|
-
upsert: async (slug, metadata) => client["request"](`/games/${slug}`, "PUT", { body: metadata }),
|
|
1709
|
-
delete: (gameId) => client["request"](`/games/${gameId}`, "DELETE"),
|
|
1710
|
-
secrets: {
|
|
1711
|
-
set: async (slug, secrets) => {
|
|
1712
|
-
const result = await client["request"](`/games/${slug}/secrets`, "POST", { body: secrets });
|
|
1713
|
-
return result.keys;
|
|
1714
|
-
},
|
|
1715
|
-
list: async (slug) => {
|
|
1716
|
-
const result = await client["request"](`/games/${slug}/secrets`, "GET");
|
|
1717
|
-
return result.keys;
|
|
1718
|
-
},
|
|
1719
|
-
get: async (slug) => {
|
|
1720
|
-
const result = await client["request"](`/games/${slug}/secrets/values`, "GET");
|
|
1721
|
-
return result.secrets;
|
|
1722
|
-
},
|
|
1723
|
-
delete: async (slug, key) => {
|
|
1724
|
-
await client["request"](`/games/${slug}/secrets/${key}`, "DELETE");
|
|
1725
|
-
}
|
|
1726
|
-
},
|
|
1727
|
-
database: {
|
|
1728
|
-
reset: async (slug, schema) => {
|
|
1729
|
-
return client["request"](`/games/${slug}/database/reset`, "POST", {
|
|
1730
|
-
body: { schema }
|
|
1731
|
-
});
|
|
1732
|
-
}
|
|
1733
|
-
},
|
|
1734
|
-
bucket: {
|
|
1735
|
-
list: async (slug, prefix) => {
|
|
1736
|
-
const params = prefix ? `?prefix=${encodeURIComponent(prefix)}` : "";
|
|
1737
|
-
const result = await client["request"](`/games/${slug}/bucket${params}`, "GET");
|
|
1738
|
-
return result.files;
|
|
1739
|
-
},
|
|
1740
|
-
get: async (slug, key) => {
|
|
1741
|
-
const res = await client["request"](`/games/${slug}/bucket/${encodeURIComponent(key)}`, "GET", { raw: true });
|
|
1742
|
-
if (!res.ok) {
|
|
1743
|
-
let errorMessage = res.statusText;
|
|
1744
|
-
try {
|
|
1745
|
-
const errorData = await res.json();
|
|
1746
|
-
if (errorData.error) {
|
|
1747
|
-
errorMessage = errorData.error;
|
|
1748
|
-
} else if (errorData.message) {
|
|
1749
|
-
errorMessage = errorData.message;
|
|
1750
|
-
}
|
|
1751
|
-
} catch {}
|
|
1752
|
-
throw new Error(errorMessage);
|
|
1753
|
-
}
|
|
1754
|
-
return res.arrayBuffer();
|
|
1755
|
-
},
|
|
1756
|
-
put: async (slug, key, content, contentType) => {
|
|
1757
|
-
await client["request"](`/games/${slug}/bucket/${encodeURIComponent(key)}`, "PUT", {
|
|
1758
|
-
body: content,
|
|
1759
|
-
headers: contentType ? { "Content-Type": contentType } : undefined
|
|
1760
|
-
});
|
|
1761
|
-
},
|
|
1762
|
-
delete: async (slug, key) => {
|
|
1763
|
-
await client["request"](`/games/${slug}/bucket/${encodeURIComponent(key)}`, "DELETE");
|
|
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
|
-
}
|
|
1783
|
-
}
|
|
1784
|
-
},
|
|
1785
|
-
items: {
|
|
1786
|
-
create: (gameId, slug, itemData) => client["request"](`/games/${gameId}/items`, "POST", {
|
|
1787
|
-
body: {
|
|
1788
|
-
slug,
|
|
1789
|
-
...itemData
|
|
1790
|
-
}
|
|
1791
|
-
}),
|
|
1792
|
-
update: (gameId, itemId, updates) => client["request"](`/games/${gameId}/items/${itemId}`, "PATCH", {
|
|
1793
|
-
body: updates
|
|
1794
|
-
}),
|
|
1795
|
-
list: (gameId) => client["request"](`/games/${gameId}/items`, "GET"),
|
|
1796
|
-
get: (gameId, slug) => {
|
|
1797
|
-
const queryParams = new URLSearchParams({ slug, gameId });
|
|
1798
|
-
return client["request"](`/items/resolve?${queryParams.toString()}`, "GET");
|
|
1799
|
-
},
|
|
1800
|
-
delete: (gameId, itemId) => client["request"](`/games/${gameId}/items/${itemId}`, "DELETE"),
|
|
1801
|
-
shop: {
|
|
1802
|
-
create: (gameId, itemId, listingData) => {
|
|
1803
|
-
return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "POST", { body: listingData });
|
|
1804
|
-
},
|
|
1805
|
-
get: (gameId, itemId) => {
|
|
1806
|
-
return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "GET");
|
|
1807
|
-
},
|
|
1808
|
-
update: (gameId, itemId, updates) => {
|
|
1809
|
-
return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "PATCH", { body: updates });
|
|
1810
|
-
},
|
|
1811
|
-
delete: (gameId, itemId) => {
|
|
1812
|
-
return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "DELETE");
|
|
1813
|
-
},
|
|
1814
|
-
list: (gameId) => {
|
|
1815
|
-
return client["request"](`/games/${gameId}/shop-listings`, "GET");
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
};
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
// src/core/namespaces/maps.ts
|
|
1823
|
-
function createMapsNamespace(client) {
|
|
1824
|
-
const mapDataCache = createTTLCache({
|
|
1825
|
-
ttl: 5 * 60 * 1000,
|
|
1826
|
-
keyPrefix: "maps.data"
|
|
1827
|
-
});
|
|
1828
|
-
const mapElementsCache = createTTLCache({
|
|
1829
|
-
ttl: 60 * 1000,
|
|
1830
|
-
keyPrefix: "maps.elements"
|
|
1831
|
-
});
|
|
1832
|
-
return {
|
|
1833
|
-
get: (identifier, options) => mapDataCache.get(identifier, () => client["request"](`/maps/${identifier}`, "GET"), options),
|
|
1834
|
-
elements: (mapId, options) => mapElementsCache.get(mapId, () => client["request"](`/map/elements?mapId=${mapId}`, "GET"), options),
|
|
1835
|
-
objects: {
|
|
1836
|
-
list: (mapId) => client["request"](`/maps/${mapId}/objects`, "GET"),
|
|
1837
|
-
create: (mapId, objectData) => client["request"](`/maps/${mapId}/objects`, "POST", {
|
|
1838
|
-
body: objectData
|
|
1839
|
-
}),
|
|
1840
|
-
delete: (mapId, objectId) => client["request"](`/maps/${mapId}/objects/${objectId}`, "DELETE")
|
|
1841
|
-
}
|
|
1842
|
-
};
|
|
1843
|
-
}
|
|
1844
|
-
var init_maps = () => {};
|
|
1845
|
-
|
|
1846
|
-
// src/core/namespaces/admin.ts
|
|
1847
|
-
function createAdminNamespace(client) {
|
|
1848
|
-
return {
|
|
1849
|
-
games: {
|
|
1850
|
-
pauseGame: (gameId) => client["request"](`/admin/games/${gameId}/pause`, "POST"),
|
|
1851
|
-
resumeGame: (gameId) => client["request"](`/admin/games/${gameId}/resume`, "POST")
|
|
1852
|
-
},
|
|
1853
|
-
items: {
|
|
1854
|
-
create: (props) => client["request"]("/items", "POST", { body: props }),
|
|
1855
|
-
get: (itemId) => client["request"](`/items/${itemId}`, "GET"),
|
|
1856
|
-
list: () => client["request"]("/items", "GET"),
|
|
1857
|
-
update: (itemId, props) => client["request"](`/items/${itemId}`, "PATCH", { body: props }),
|
|
1858
|
-
delete: (itemId) => client["request"](`/items/${itemId}`, "DELETE")
|
|
1859
|
-
},
|
|
1860
|
-
currencies: {
|
|
1861
|
-
create: (props) => client["request"]("/currencies", "POST", { body: props }),
|
|
1862
|
-
get: (currencyId) => client["request"](`/currencies/${currencyId}`, "GET"),
|
|
1863
|
-
list: () => client["request"]("/currencies", "GET"),
|
|
1864
|
-
update: (currencyId, props) => client["request"](`/currencies/${currencyId}`, "PATCH", { body: props }),
|
|
1865
|
-
delete: (currencyId) => client["request"](`/currencies/${currencyId}`, "DELETE")
|
|
1866
|
-
},
|
|
1867
|
-
shopListings: {
|
|
1868
|
-
create: (props) => client["request"]("/shop-listings", "POST", { body: props }),
|
|
1869
|
-
get: (listingId) => client["request"](`/shop-listings/${listingId}`, "GET"),
|
|
1870
|
-
list: () => client["request"]("/shop-listings", "GET"),
|
|
1871
|
-
update: (listingId, props) => client["request"](`/shop-listings/${listingId}`, "PATCH", {
|
|
1872
|
-
body: props
|
|
1873
|
-
}),
|
|
1874
|
-
delete: (listingId) => client["request"](`/shop-listings/${listingId}`, "DELETE")
|
|
1875
|
-
}
|
|
1876
|
-
};
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
// src/core/namespaces/shop.ts
|
|
1880
|
-
function createShopNamespace(client) {
|
|
1881
|
-
return {
|
|
1882
|
-
view: () => {
|
|
1883
|
-
return client["request"]("/shop/view", "GET");
|
|
1884
|
-
}
|
|
1885
|
-
};
|
|
1886
|
-
}
|
|
1887
|
-
|
|
1888
|
-
// src/core/namespaces/telemetry.ts
|
|
1889
|
-
function createTelemetryNamespace(client) {
|
|
1890
|
-
return {
|
|
1891
|
-
pushMetrics: (metrics) => client["request"](`/telemetry/metrics`, "POST", { body: metrics })
|
|
1892
|
-
};
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
// src/core/cache/cooldown-cache.ts
|
|
1896
|
-
function createCooldownCache(defaultCooldownMs) {
|
|
1897
|
-
const lastFetchTime = new Map;
|
|
1898
|
-
const pendingRequests = new Map;
|
|
1899
|
-
const lastResults = new Map;
|
|
1900
|
-
async function get(key, loader, config) {
|
|
1901
|
-
const now = Date.now();
|
|
1902
|
-
const lastFetch = lastFetchTime.get(key) || 0;
|
|
1903
|
-
const timeSinceLastFetch = now - lastFetch;
|
|
1904
|
-
const effectiveCooldown = config?.cooldown !== undefined ? config.cooldown : defaultCooldownMs;
|
|
1905
|
-
const force = config?.force || false;
|
|
1906
|
-
const pending = pendingRequests.get(key);
|
|
1907
|
-
if (pending) {
|
|
1908
|
-
return pending;
|
|
1909
|
-
}
|
|
1910
|
-
if (!force && timeSinceLastFetch < effectiveCooldown) {
|
|
1911
|
-
const cachedResult = lastResults.get(key);
|
|
1912
|
-
if (cachedResult !== undefined) {
|
|
1913
|
-
return Promise.resolve(cachedResult);
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
const promise = loader().then((result) => {
|
|
1917
|
-
pendingRequests.delete(key);
|
|
1918
|
-
lastFetchTime.set(key, Date.now());
|
|
1919
|
-
lastResults.set(key, result);
|
|
1920
|
-
return result;
|
|
1921
|
-
}).catch((error) => {
|
|
1922
|
-
pendingRequests.delete(key);
|
|
1923
|
-
throw error;
|
|
1924
|
-
});
|
|
1925
|
-
pendingRequests.set(key, promise);
|
|
1926
|
-
return promise;
|
|
1927
|
-
}
|
|
1928
|
-
function clear(key) {
|
|
1929
|
-
if (key === undefined) {
|
|
1930
|
-
lastFetchTime.clear();
|
|
1931
|
-
pendingRequests.clear();
|
|
1932
|
-
lastResults.clear();
|
|
1933
|
-
} else {
|
|
1934
|
-
lastFetchTime.delete(key);
|
|
1935
|
-
pendingRequests.delete(key);
|
|
1936
|
-
lastResults.delete(key);
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
return { get, clear };
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
|
-
// src/core/namespaces/levels.ts
|
|
1943
|
-
function createLevelsNamespace(client) {
|
|
1944
|
-
const progressCache = createCooldownCache(5000);
|
|
1945
|
-
return {
|
|
1946
|
-
get: async () => {
|
|
1947
|
-
return client["request"]("/users/level", "GET");
|
|
1948
|
-
},
|
|
1949
|
-
progress: async (options) => {
|
|
1950
|
-
return progressCache.get("user-progress", () => client["request"]("/users/level/progress", "GET"), options);
|
|
1951
|
-
},
|
|
1952
|
-
config: {
|
|
1953
|
-
list: async () => {
|
|
1954
|
-
return client["request"]("/levels/config", "GET");
|
|
1955
|
-
},
|
|
1956
|
-
get: async (level) => {
|
|
1957
|
-
return client["request"](`/levels/config/${level}`, "GET");
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
};
|
|
1961
|
-
}
|
|
1962
|
-
var init_levels = () => {};
|
|
1963
|
-
|
|
1964
|
-
// ../constants/src/auth.ts
|
|
1965
|
-
var init_auth = () => {};
|
|
1966
|
-
|
|
1967
|
-
// ../constants/src/domains.ts
|
|
1968
|
-
var init_domains = () => {};
|
|
1969
|
-
|
|
1970
|
-
// ../constants/src/env-vars.ts
|
|
1971
|
-
var init_env_vars = () => {};
|
|
1972
|
-
|
|
1973
|
-
// ../constants/src/overworld.ts
|
|
1974
|
-
var ITEM_SLUGS, CURRENCIES, BADGES;
|
|
1975
|
-
var init_overworld = __esm(() => {
|
|
1976
|
-
ITEM_SLUGS = {
|
|
1977
|
-
PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
|
|
1978
|
-
PLAYCADEMY_XP: "PLAYCADEMY_XP",
|
|
1979
|
-
FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
|
|
1980
|
-
EARLY_ADOPTER_BADGE: "EARLY_ADOPTER_BADGE",
|
|
1981
|
-
FIRST_GAME_BADGE: "FIRST_GAME_BADGE",
|
|
1982
|
-
COMMON_SWORD: "COMMON_SWORD",
|
|
1983
|
-
SMALL_HEALTH_POTION: "SMALL_HEALTH_POTION",
|
|
1984
|
-
SMALL_BACKPACK: "SMALL_BACKPACK",
|
|
1985
|
-
LAVA_LAMP: "LAVA_LAMP",
|
|
1986
|
-
BOOMBOX: "BOOMBOX",
|
|
1987
|
-
CABIN_BED: "CABIN_BED"
|
|
1988
|
-
};
|
|
1989
|
-
CURRENCIES = {
|
|
1990
|
-
PRIMARY: ITEM_SLUGS.PLAYCADEMY_CREDITS,
|
|
1991
|
-
XP: ITEM_SLUGS.PLAYCADEMY_XP
|
|
1992
|
-
};
|
|
1993
|
-
BADGES = {
|
|
1994
|
-
FOUNDING_MEMBER: ITEM_SLUGS.FOUNDING_MEMBER_BADGE,
|
|
1995
|
-
EARLY_ADOPTER: ITEM_SLUGS.EARLY_ADOPTER_BADGE,
|
|
1996
|
-
FIRST_GAME: ITEM_SLUGS.FIRST_GAME_BADGE
|
|
1997
|
-
};
|
|
1998
|
-
});
|
|
1999
|
-
// ../constants/src/timeback.ts
|
|
2000
|
-
var TIMEBACK_ROUTES;
|
|
2001
|
-
var init_timeback = __esm(() => {
|
|
2002
|
-
TIMEBACK_ROUTES = {
|
|
2003
|
-
END_ACTIVITY: "/integrations/timeback/end-activity"
|
|
2004
|
-
};
|
|
2005
|
-
});
|
|
2006
|
-
|
|
2007
|
-
// ../constants/src/workers.ts
|
|
2008
|
-
var init_workers = () => {};
|
|
2009
|
-
|
|
2010
|
-
// ../constants/src/index.ts
|
|
2011
|
-
var init_src2 = __esm(() => {
|
|
2012
|
-
init_auth();
|
|
2013
|
-
init_domains();
|
|
2014
|
-
init_env_vars();
|
|
2015
|
-
init_overworld();
|
|
2016
|
-
init_timeback();
|
|
2017
|
-
init_workers();
|
|
2018
|
-
});
|
|
2019
|
-
|
|
2020
|
-
// src/core/cache/singleton-cache.ts
|
|
2021
|
-
function createSingletonCache() {
|
|
2022
|
-
let cachedValue;
|
|
2023
|
-
let hasValue = false;
|
|
2024
|
-
async function get(loader) {
|
|
2025
|
-
if (hasValue) {
|
|
2026
|
-
return cachedValue;
|
|
2027
|
-
}
|
|
2028
|
-
const value = await loader();
|
|
2029
|
-
cachedValue = value;
|
|
2030
|
-
hasValue = true;
|
|
2031
|
-
return value;
|
|
2032
|
-
}
|
|
2033
|
-
function clear() {
|
|
2034
|
-
cachedValue = undefined;
|
|
2035
|
-
hasValue = false;
|
|
2036
|
-
}
|
|
2037
|
-
function has() {
|
|
2038
|
-
return hasValue;
|
|
2039
|
-
}
|
|
2040
|
-
return { get, clear, has };
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
// src/core/namespaces/credits.ts
|
|
2044
|
-
function createCreditsNamespace(client) {
|
|
2045
|
-
const creditsIdCache = createSingletonCache();
|
|
2046
|
-
const getCreditsItemId = async () => {
|
|
2047
|
-
return creditsIdCache.get(async () => {
|
|
2048
|
-
const queryParams = new URLSearchParams({ slug: CURRENCIES.PRIMARY });
|
|
2049
|
-
const creditsItem = await client["request"](`/items/resolve?${queryParams.toString()}`, "GET");
|
|
2050
|
-
if (!creditsItem || !creditsItem.id) {
|
|
2051
|
-
throw new Error("Playcademy Credits item not found in catalog");
|
|
2052
|
-
}
|
|
2053
|
-
return creditsItem.id;
|
|
2054
|
-
});
|
|
2055
|
-
};
|
|
2056
|
-
return {
|
|
2057
|
-
balance: async () => {
|
|
2058
|
-
const inventory = await client["request"]("/inventory", "GET");
|
|
2059
|
-
const primaryCurrencyInventoryItem = inventory.find((item) => item.item?.slug === CURRENCIES.PRIMARY);
|
|
2060
|
-
return primaryCurrencyInventoryItem?.quantity ?? 0;
|
|
2061
|
-
},
|
|
2062
|
-
add: async (amount) => {
|
|
2063
|
-
if (amount <= 0) {
|
|
2064
|
-
throw new Error("Amount must be positive");
|
|
2065
|
-
}
|
|
2066
|
-
const creditsItemId = await getCreditsItemId();
|
|
2067
|
-
const result = await client["request"]("/inventory/add", "POST", {
|
|
2068
|
-
body: {
|
|
2069
|
-
itemId: creditsItemId,
|
|
2070
|
-
qty: amount
|
|
2071
|
-
}
|
|
2072
|
-
});
|
|
2073
|
-
client["emit"]("inventoryChange", {
|
|
2074
|
-
itemId: creditsItemId,
|
|
2075
|
-
delta: amount,
|
|
2076
|
-
newTotal: result.newTotal
|
|
2077
|
-
});
|
|
2078
|
-
return result.newTotal;
|
|
2079
|
-
},
|
|
2080
|
-
spend: async (amount) => {
|
|
2081
|
-
if (amount <= 0) {
|
|
2082
|
-
throw new Error("Amount must be positive");
|
|
2083
|
-
}
|
|
2084
|
-
const creditsItemId = await getCreditsItemId();
|
|
2085
|
-
const result = await client["request"]("/inventory/remove", "POST", {
|
|
2086
|
-
body: {
|
|
2087
|
-
itemId: creditsItemId,
|
|
2088
|
-
qty: amount
|
|
1519
|
+
spend: async (amount) => {
|
|
1520
|
+
if (amount <= 0) {
|
|
1521
|
+
throw new Error("Amount must be positive");
|
|
1522
|
+
}
|
|
1523
|
+
const creditsItemId = await getCreditsItemId();
|
|
1524
|
+
const result = await client["request"]("/inventory/remove", "POST", {
|
|
1525
|
+
body: {
|
|
1526
|
+
itemId: creditsItemId,
|
|
1527
|
+
qty: amount
|
|
2089
1528
|
}
|
|
2090
1529
|
});
|
|
2091
1530
|
client["emit"]("inventoryChange", {
|
|
@@ -2097,31 +1536,7 @@ function createCreditsNamespace(client) {
|
|
|
2097
1536
|
}
|
|
2098
1537
|
};
|
|
2099
1538
|
}
|
|
2100
|
-
|
|
2101
|
-
init_src2();
|
|
2102
|
-
});
|
|
2103
|
-
|
|
2104
|
-
// src/core/namespaces/leaderboard.ts
|
|
2105
|
-
function createLeaderboardNamespace(client) {
|
|
2106
|
-
return {
|
|
2107
|
-
fetch: async (options) => {
|
|
2108
|
-
const params = new URLSearchParams({
|
|
2109
|
-
timeframe: options?.timeframe || "all_time",
|
|
2110
|
-
limit: String(options?.limit || 10),
|
|
2111
|
-
offset: String(options?.offset || 0)
|
|
2112
|
-
});
|
|
2113
|
-
if (options?.gameId) {
|
|
2114
|
-
params.append("gameId", options.gameId);
|
|
2115
|
-
}
|
|
2116
|
-
return client["request"](`/leaderboard?${params}`, "GET");
|
|
2117
|
-
},
|
|
2118
|
-
getUserRank: async (gameId, userId) => {
|
|
2119
|
-
return client["request"](`/games/${gameId}/users/${userId}/rank`, "GET");
|
|
2120
|
-
}
|
|
2121
|
-
};
|
|
2122
|
-
}
|
|
2123
|
-
|
|
2124
|
-
// src/core/namespaces/scores.ts
|
|
1539
|
+
// src/namespaces/game/scores.ts
|
|
2125
1540
|
function createScoresNamespace(client) {
|
|
2126
1541
|
return {
|
|
2127
1542
|
submit: async (gameId, score, metadata) => {
|
|
@@ -2131,99 +1546,15 @@ function createScoresNamespace(client) {
|
|
|
2131
1546
|
metadata
|
|
2132
1547
|
}
|
|
2133
1548
|
});
|
|
2134
|
-
},
|
|
2135
|
-
getByUser: async (gameId, userId, options) => {
|
|
2136
|
-
const params = new URLSearchParams;
|
|
2137
|
-
if (options?.limit) {
|
|
2138
|
-
params.append("limit", String(options.limit));
|
|
2139
|
-
}
|
|
2140
|
-
const queryString = params.toString();
|
|
2141
|
-
const path = queryString ? `/games/${gameId}/users/${userId}/scores?${queryString}` : `/games/${gameId}/users/${userId}/scores`;
|
|
2142
|
-
return client["request"](path, "GET");
|
|
2143
|
-
}
|
|
2144
|
-
};
|
|
2145
|
-
}
|
|
2146
|
-
|
|
2147
|
-
// src/core/namespaces/character.ts
|
|
2148
|
-
function createCharacterNamespace(client) {
|
|
2149
|
-
const componentCache = createTTLCache({
|
|
2150
|
-
ttl: 5 * 60 * 1000,
|
|
2151
|
-
keyPrefix: "character.components"
|
|
2152
|
-
});
|
|
2153
|
-
return {
|
|
2154
|
-
get: async (userId) => {
|
|
2155
|
-
try {
|
|
2156
|
-
const path = userId ? `/character/${userId}` : "/character";
|
|
2157
|
-
return await client["request"](path, "GET");
|
|
2158
|
-
} catch (error) {
|
|
2159
|
-
if (error instanceof Error) {
|
|
2160
|
-
if (error.message.includes("404")) {
|
|
2161
|
-
return null;
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
throw error;
|
|
2165
|
-
}
|
|
2166
|
-
},
|
|
2167
|
-
create: async (characterData) => {
|
|
2168
|
-
return client["request"]("/character", "POST", { body: characterData });
|
|
2169
|
-
},
|
|
2170
|
-
update: async (updates) => {
|
|
2171
|
-
return client["request"]("/character", "PATCH", { body: updates });
|
|
2172
|
-
},
|
|
2173
|
-
components: {
|
|
2174
|
-
list: async (options) => {
|
|
2175
|
-
const cacheKey = options?.level === undefined ? "all" : String(options.level);
|
|
2176
|
-
return componentCache.get(cacheKey, async () => {
|
|
2177
|
-
const path = options?.level !== undefined ? `/character/components?level=${options.level}` : "/character/components";
|
|
2178
|
-
const components = await client["request"](path, "GET");
|
|
2179
|
-
return components || [];
|
|
2180
|
-
}, options);
|
|
2181
|
-
},
|
|
2182
|
-
clearCache: (key) => componentCache.clear(key),
|
|
2183
|
-
getCacheKeys: () => componentCache.getKeys()
|
|
2184
|
-
},
|
|
2185
|
-
accessories: {
|
|
2186
|
-
equip: async (slot, componentId) => {
|
|
2187
|
-
return client["request"]("/character/accessories/equip", "POST", { body: { slot, accessoryComponentId: componentId } });
|
|
2188
|
-
},
|
|
2189
|
-
remove: async (slot) => {
|
|
2190
|
-
return client["request"](`/character/accessories/${slot}`, "DELETE");
|
|
2191
|
-
},
|
|
2192
|
-
list: async () => {
|
|
2193
|
-
const character = await client.character.get();
|
|
2194
|
-
return character?.accessories || [];
|
|
2195
|
-
}
|
|
2196
1549
|
}
|
|
2197
1550
|
};
|
|
2198
1551
|
}
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
return {
|
|
2205
|
-
templates: {
|
|
2206
|
-
get: async (slug) => {
|
|
2207
|
-
if (!slug)
|
|
2208
|
-
throw new Error("Sprite template slug is required");
|
|
2209
|
-
const templateMeta = await templateUrlCache.get(slug, async () => {
|
|
2210
|
-
return client["request"](`/sprites/templates/${slug}`, "GET");
|
|
2211
|
-
});
|
|
2212
|
-
if (!templateMeta.url) {
|
|
2213
|
-
throw new Error(`Template ${slug} has no URL in database`);
|
|
2214
|
-
}
|
|
2215
|
-
const response = await fetch(templateMeta.url);
|
|
2216
|
-
if (!response.ok) {
|
|
2217
|
-
throw new Error(`Failed to fetch template JSON from ${templateMeta.url}: ${response.statusText}`);
|
|
2218
|
-
}
|
|
2219
|
-
return await response.json();
|
|
2220
|
-
}
|
|
2221
|
-
}
|
|
2222
|
-
};
|
|
2223
|
-
}
|
|
2224
|
-
var init_sprites = () => {};
|
|
1552
|
+
// src/namespaces/game/realtime.client.ts
|
|
1553
|
+
var CLOSE_CODES = {
|
|
1554
|
+
NORMAL_CLOSURE: 1000,
|
|
1555
|
+
TOKEN_REFRESH: 4000
|
|
1556
|
+
};
|
|
2225
1557
|
|
|
2226
|
-
// src/core/namespaces/realtime.client.ts
|
|
2227
1558
|
class RealtimeChannelClient {
|
|
2228
1559
|
gameId;
|
|
2229
1560
|
_channelName;
|
|
@@ -2420,17 +1751,8 @@ class RealtimeChannelClient {
|
|
|
2420
1751
|
return this.ws?.readyState === WebSocket.OPEN;
|
|
2421
1752
|
}
|
|
2422
1753
|
}
|
|
2423
|
-
var CLOSE_CODES;
|
|
2424
|
-
var init_realtime_client = __esm(() => {
|
|
2425
|
-
init_src();
|
|
2426
|
-
init_messaging();
|
|
2427
|
-
CLOSE_CODES = {
|
|
2428
|
-
NORMAL_CLOSURE: 1000,
|
|
2429
|
-
TOKEN_REFRESH: 4000
|
|
2430
|
-
};
|
|
2431
|
-
});
|
|
2432
1754
|
|
|
2433
|
-
// src/
|
|
1755
|
+
// src/namespaces/game/realtime.ts
|
|
2434
1756
|
function createRealtimeNamespace(client) {
|
|
2435
1757
|
return {
|
|
2436
1758
|
token: {
|
|
@@ -2455,45 +1777,7 @@ function createRealtimeNamespace(client) {
|
|
|
2455
1777
|
}
|
|
2456
1778
|
};
|
|
2457
1779
|
}
|
|
2458
|
-
|
|
2459
|
-
init_realtime_client();
|
|
2460
|
-
});
|
|
2461
|
-
|
|
2462
|
-
// src/core/namespaces/achievements.ts
|
|
2463
|
-
function createAchievementsNamespace(client) {
|
|
2464
|
-
const achievementsListCache = createTTLCache({
|
|
2465
|
-
ttl: 5 * 1000,
|
|
2466
|
-
keyPrefix: "achievements.list"
|
|
2467
|
-
});
|
|
2468
|
-
const achievementsHistoryCache = createTTLCache({
|
|
2469
|
-
ttl: 5 * 1000,
|
|
2470
|
-
keyPrefix: "achievements.history"
|
|
2471
|
-
});
|
|
2472
|
-
return {
|
|
2473
|
-
list: (options) => {
|
|
2474
|
-
return achievementsListCache.get("current", () => client["request"]("/achievements/current", "GET"), options);
|
|
2475
|
-
},
|
|
2476
|
-
history: {
|
|
2477
|
-
list: async (queryOptions, cacheOptions) => {
|
|
2478
|
-
const params = new URLSearchParams;
|
|
2479
|
-
if (queryOptions?.limit)
|
|
2480
|
-
params.append("limit", String(queryOptions.limit));
|
|
2481
|
-
const qs = params.toString();
|
|
2482
|
-
const path = qs ? `/achievements/history?${qs}` : "/achievements/history";
|
|
2483
|
-
const cacheKey = qs ? `history-${qs}` : "history";
|
|
2484
|
-
return achievementsHistoryCache.get(cacheKey, () => client["request"](path, "GET"), cacheOptions);
|
|
2485
|
-
}
|
|
2486
|
-
},
|
|
2487
|
-
progress: {
|
|
2488
|
-
submit: async (achievementId) => client["request"]("/achievements/progress", "POST", {
|
|
2489
|
-
body: { achievementId }
|
|
2490
|
-
})
|
|
2491
|
-
}
|
|
2492
|
-
};
|
|
2493
|
-
}
|
|
2494
|
-
var init_achievements = () => {};
|
|
2495
|
-
|
|
2496
|
-
// src/core/namespaces/timeback.ts
|
|
1780
|
+
// src/namespaces/game/timeback.ts
|
|
2497
1781
|
function createTimebackNamespace(client) {
|
|
2498
1782
|
let currentActivity = null;
|
|
2499
1783
|
return {
|
|
@@ -2548,7 +1832,8 @@ function createTimebackNamespace(client) {
|
|
|
2548
1832
|
timingData: {
|
|
2549
1833
|
durationSeconds
|
|
2550
1834
|
},
|
|
2551
|
-
xpEarned: data.xpAwarded
|
|
1835
|
+
xpEarned: data.xpAwarded,
|
|
1836
|
+
masteredUnits: data.masteredUnits
|
|
2552
1837
|
};
|
|
2553
1838
|
try {
|
|
2554
1839
|
const response = await client["requestGameBackend"](TIMEBACK_ROUTES.END_ACTIVITY, "POST", request2);
|
|
@@ -2558,574 +1843,200 @@ function createTimebackNamespace(client) {
|
|
|
2558
1843
|
currentActivity = null;
|
|
2559
1844
|
throw error;
|
|
2560
1845
|
}
|
|
2561
|
-
},
|
|
2562
|
-
management: {
|
|
2563
|
-
setup: (request2) => {
|
|
2564
|
-
return client["request"]("/timeback/setup", "POST", {
|
|
2565
|
-
body: request2
|
|
2566
|
-
});
|
|
2567
|
-
},
|
|
2568
|
-
verify: (gameId) => {
|
|
2569
|
-
return client["request"](`/timeback/verify/${gameId}`, "GET");
|
|
2570
|
-
},
|
|
2571
|
-
cleanup: (gameId) => {
|
|
2572
|
-
return client["request"](`/timeback/integrations/${gameId}`, "DELETE");
|
|
2573
|
-
},
|
|
2574
|
-
get: (gameId) => {
|
|
2575
|
-
return client["request"](`/timeback/integrations/${gameId}`, "GET");
|
|
2576
|
-
},
|
|
2577
|
-
getConfig: (gameId) => {
|
|
2578
|
-
return client["request"](`/timeback/config/${gameId}`, "GET");
|
|
2579
|
-
}
|
|
2580
|
-
},
|
|
2581
|
-
xp: {
|
|
2582
|
-
today: async (options) => {
|
|
2583
|
-
const params = new URLSearchParams;
|
|
2584
|
-
if (options?.date)
|
|
2585
|
-
params.set("date", options.date);
|
|
2586
|
-
if (options?.timezone)
|
|
2587
|
-
params.set("tz", options.timezone);
|
|
2588
|
-
const query = params.toString();
|
|
2589
|
-
const endpoint = query ? `/timeback/xp/today?${query}` : "/timeback/xp/today";
|
|
2590
|
-
return client["request"](endpoint, "GET");
|
|
2591
|
-
},
|
|
2592
|
-
total: async () => {
|
|
2593
|
-
return client["request"]("/timeback/xp/total", "GET");
|
|
2594
|
-
},
|
|
2595
|
-
history: async (options) => {
|
|
2596
|
-
const params = new URLSearchParams;
|
|
2597
|
-
if (options?.startDate)
|
|
2598
|
-
params.set("startDate", options.startDate);
|
|
2599
|
-
if (options?.endDate)
|
|
2600
|
-
params.set("endDate", options.endDate);
|
|
2601
|
-
const query = params.toString();
|
|
2602
|
-
const endpoint = query ? `/timeback/xp/history?${query}` : "/timeback/xp/history";
|
|
2603
|
-
return client["request"](endpoint, "GET");
|
|
2604
|
-
},
|
|
2605
|
-
summary: async (options) => {
|
|
2606
|
-
const [today, total] = await Promise.all([
|
|
2607
|
-
client["request"]((() => {
|
|
2608
|
-
const params = new URLSearchParams;
|
|
2609
|
-
if (options?.date)
|
|
2610
|
-
params.set("date", options.date);
|
|
2611
|
-
if (options?.timezone)
|
|
2612
|
-
params.set("tz", options.timezone);
|
|
2613
|
-
const query = params.toString();
|
|
2614
|
-
return query ? `/timeback/xp/today?${query}` : "/timeback/xp/today";
|
|
2615
|
-
})(), "GET"),
|
|
2616
|
-
client["request"]("/timeback/xp/total", "GET")
|
|
2617
|
-
]);
|
|
2618
|
-
return { today, total };
|
|
2619
|
-
}
|
|
2620
1846
|
}
|
|
2621
1847
|
};
|
|
2622
1848
|
}
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
list: async (queryOptions, cacheOptions) => {
|
|
2639
|
-
const params = new URLSearchParams;
|
|
2640
|
-
if (queryOptions?.status)
|
|
2641
|
-
params.append("status", queryOptions.status);
|
|
2642
|
-
if (queryOptions?.type)
|
|
2643
|
-
params.append("type", queryOptions.type);
|
|
2644
|
-
if (queryOptions?.limit)
|
|
2645
|
-
params.append("limit", String(queryOptions.limit));
|
|
2646
|
-
if (queryOptions?.offset)
|
|
2647
|
-
params.append("offset", String(queryOptions.offset));
|
|
2648
|
-
const qs = params.toString();
|
|
2649
|
-
const path = qs ? `/notifications?${qs}` : "/notifications";
|
|
2650
|
-
const cacheKey = qs ? `list-${qs}` : "list";
|
|
2651
|
-
return notificationsListCache.get(cacheKey, () => client["request"](path, "GET"), cacheOptions);
|
|
2652
|
-
},
|
|
2653
|
-
markAsSeen: async (notificationId) => {
|
|
2654
|
-
const result = await client["request"](`/notifications/${notificationId}/status`, "PATCH", {
|
|
2655
|
-
body: {
|
|
2656
|
-
id: notificationId,
|
|
2657
|
-
status: "seen"
|
|
2658
|
-
}
|
|
2659
|
-
});
|
|
2660
|
-
notificationsListCache.clear();
|
|
2661
|
-
return result;
|
|
1849
|
+
// src/clients/public.ts
|
|
1850
|
+
class PlaycademyClient {
|
|
1851
|
+
baseUrl;
|
|
1852
|
+
gameUrl;
|
|
1853
|
+
authStrategy;
|
|
1854
|
+
gameId;
|
|
1855
|
+
config;
|
|
1856
|
+
listeners = {};
|
|
1857
|
+
internalClientSessionId;
|
|
1858
|
+
authContext;
|
|
1859
|
+
initPayload;
|
|
1860
|
+
connectionManager;
|
|
1861
|
+
_sessionManager = {
|
|
1862
|
+
startSession: async (gameId) => {
|
|
1863
|
+
return this.request(`/games/${gameId}/sessions`, "POST");
|
|
2662
1864
|
},
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
body: {
|
|
2666
|
-
id: notificationId,
|
|
2667
|
-
status: "clicked"
|
|
2668
|
-
}
|
|
2669
|
-
});
|
|
2670
|
-
notificationsListCache.clear();
|
|
2671
|
-
return result;
|
|
2672
|
-
},
|
|
2673
|
-
dismiss: async (notificationId) => {
|
|
2674
|
-
const result = await client["request"](`/notifications/${notificationId}/status`, "PATCH", {
|
|
2675
|
-
body: {
|
|
2676
|
-
id: notificationId,
|
|
2677
|
-
status: "dismissed"
|
|
2678
|
-
}
|
|
2679
|
-
});
|
|
2680
|
-
notificationsListCache.clear();
|
|
2681
|
-
return result;
|
|
2682
|
-
},
|
|
2683
|
-
stats: {
|
|
2684
|
-
get: async (queryOptions, cacheOptions) => {
|
|
2685
|
-
const user = await client.users.me();
|
|
2686
|
-
const params = new URLSearchParams;
|
|
2687
|
-
if (queryOptions?.from)
|
|
2688
|
-
params.append("from", queryOptions.from);
|
|
2689
|
-
if (queryOptions?.to)
|
|
2690
|
-
params.append("to", queryOptions.to);
|
|
2691
|
-
const qs = params.toString();
|
|
2692
|
-
const path = qs ? `/notifications/stats/${user.id}?${qs}` : `/notifications/stats/${user.id}`;
|
|
2693
|
-
const cacheKey = qs ? `stats-${qs}` : "stats";
|
|
2694
|
-
return notificationStatsCache.get(cacheKey, () => client["request"](path, "GET"), cacheOptions);
|
|
2695
|
-
}
|
|
1865
|
+
endSession: async (sessionId, gameId) => {
|
|
1866
|
+
return this.request(`/games/${gameId}/sessions/${sessionId}`, "DELETE");
|
|
2696
1867
|
}
|
|
2697
1868
|
};
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
1869
|
+
constructor(config) {
|
|
1870
|
+
this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
|
|
1871
|
+
this.gameUrl = config?.gameUrl;
|
|
1872
|
+
this.gameId = config?.gameId;
|
|
1873
|
+
this.config = config || {};
|
|
1874
|
+
this.authStrategy = createAuthStrategy(config?.token ?? null, config?.tokenType);
|
|
1875
|
+
this._detectAuthContext();
|
|
1876
|
+
this._initializeInternalSession().catch(() => {});
|
|
1877
|
+
this._initializeConnectionMonitor();
|
|
1878
|
+
}
|
|
1879
|
+
getBaseUrl() {
|
|
1880
|
+
const isRelative = this.baseUrl.startsWith("/");
|
|
1881
|
+
const isBrowser2 = typeof window !== "undefined";
|
|
1882
|
+
return isRelative && isBrowser2 ? `${window.location.origin}${this.baseUrl}` : this.baseUrl;
|
|
1883
|
+
}
|
|
1884
|
+
getGameBackendUrl() {
|
|
1885
|
+
if (!this.gameUrl) {
|
|
1886
|
+
throw new PlaycademyError("Game backend URL not configured. gameUrl must be set to use game backend features.");
|
|
1887
|
+
}
|
|
1888
|
+
const isRelative = this.gameUrl.startsWith("/");
|
|
1889
|
+
const isBrowser2 = typeof window !== "undefined";
|
|
1890
|
+
const effectiveGameUrl = isRelative && isBrowser2 ? `${window.location.origin}${this.gameUrl}` : this.gameUrl;
|
|
1891
|
+
return `${effectiveGameUrl}/api`;
|
|
1892
|
+
}
|
|
1893
|
+
ping() {
|
|
1894
|
+
return "pong";
|
|
1895
|
+
}
|
|
1896
|
+
setToken(token, tokenType) {
|
|
1897
|
+
this.authStrategy = createAuthStrategy(token, tokenType);
|
|
1898
|
+
this.emit("authChange", { token });
|
|
1899
|
+
}
|
|
1900
|
+
getTokenType() {
|
|
1901
|
+
return this.authStrategy.getType();
|
|
2705
1902
|
}
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
return client["requestGameBackend"](normalizePath(path), "GET", undefined, headers);
|
|
2709
|
-
},
|
|
2710
|
-
async post(path, body, headers) {
|
|
2711
|
-
return client["requestGameBackend"](normalizePath(path), "POST", body, headers);
|
|
2712
|
-
},
|
|
2713
|
-
async put(path, body, headers) {
|
|
2714
|
-
return client["requestGameBackend"](normalizePath(path), "PUT", body, headers);
|
|
2715
|
-
},
|
|
2716
|
-
async patch(path, body, headers) {
|
|
2717
|
-
return client["requestGameBackend"](normalizePath(path), "PATCH", body, headers);
|
|
2718
|
-
},
|
|
2719
|
-
async delete(path, headers) {
|
|
2720
|
-
return client["requestGameBackend"](normalizePath(path), "DELETE", undefined, headers);
|
|
2721
|
-
},
|
|
2722
|
-
async request(path, method, body, headers) {
|
|
2723
|
-
return client["requestGameBackend"](normalizePath(path), method, body, headers);
|
|
2724
|
-
},
|
|
2725
|
-
async download(path, method = "GET", body, headers) {
|
|
2726
|
-
return client["requestGameBackend"](normalizePath(path), method, body, headers, true);
|
|
2727
|
-
},
|
|
2728
|
-
url(pathOrStrings, ...values) {
|
|
2729
|
-
if (Array.isArray(pathOrStrings) && "raw" in pathOrStrings) {
|
|
2730
|
-
const strings = pathOrStrings;
|
|
2731
|
-
const path2 = strings.reduce((acc, str, i) => {
|
|
2732
|
-
return acc + str + (values[i] != null ? String(values[i]) : "");
|
|
2733
|
-
}, "");
|
|
2734
|
-
return `${client.gameUrl}/api${path2.startsWith("/") ? path2 : `/${path2}`}`;
|
|
2735
|
-
}
|
|
2736
|
-
const path = pathOrStrings;
|
|
2737
|
-
return `${client.gameUrl}/api${path.startsWith("/") ? path : `/${path}`}`;
|
|
2738
|
-
}
|
|
2739
|
-
};
|
|
2740
|
-
}
|
|
2741
|
-
|
|
2742
|
-
// src/core/namespaces/index.ts
|
|
2743
|
-
var init_namespaces = __esm(() => {
|
|
2744
|
-
init_identity();
|
|
2745
|
-
init_runtime();
|
|
2746
|
-
init_games();
|
|
2747
|
-
init_users();
|
|
2748
|
-
init_maps();
|
|
2749
|
-
init_levels();
|
|
2750
|
-
init_credits();
|
|
2751
|
-
init_character();
|
|
2752
|
-
init_sprites();
|
|
2753
|
-
init_realtime();
|
|
2754
|
-
init_achievements();
|
|
2755
|
-
init_timeback2();
|
|
2756
|
-
init_notifications();
|
|
2757
|
-
});
|
|
2758
|
-
|
|
2759
|
-
// src/core/static/init.ts
|
|
2760
|
-
async function getPlaycademyConfig(allowedParentOrigins) {
|
|
2761
|
-
const preloaded = window.PLAYCADEMY;
|
|
2762
|
-
if (preloaded?.token) {
|
|
2763
|
-
return preloaded;
|
|
1903
|
+
getToken() {
|
|
1904
|
+
return this.authStrategy.getToken();
|
|
2764
1905
|
}
|
|
2765
|
-
|
|
2766
|
-
return
|
|
2767
|
-
} else {
|
|
2768
|
-
return createStandaloneConfig();
|
|
1906
|
+
isAuthenticated() {
|
|
1907
|
+
return this.authStrategy.getToken() !== null;
|
|
2769
1908
|
}
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
try {
|
|
2773
|
-
return document.referrer ? new URL(document.referrer).origin : null;
|
|
2774
|
-
} catch {
|
|
2775
|
-
return null;
|
|
1909
|
+
onAuthChange(callback) {
|
|
1910
|
+
this.on("authChange", (payload) => callback(payload.token));
|
|
2776
1911
|
}
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
return ref ? [ref] : [];
|
|
2783
|
-
}
|
|
2784
|
-
function isOriginAllowed(origin, allowlist) {
|
|
2785
|
-
if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") {
|
|
2786
|
-
return true;
|
|
1912
|
+
onDisconnect(callback) {
|
|
1913
|
+
if (!this.connectionManager) {
|
|
1914
|
+
return () => {};
|
|
1915
|
+
}
|
|
1916
|
+
return this.connectionManager.onDisconnect(callback);
|
|
2787
1917
|
}
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
return false;
|
|
1918
|
+
getConnectionState() {
|
|
1919
|
+
return this.connectionManager?.getState() ?? "unknown";
|
|
2791
1920
|
}
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
let contextReceived = false;
|
|
2797
|
-
const timeoutDuration = 5000;
|
|
2798
|
-
const allowlist = buildAllowedOrigins(allowedParentOrigins);
|
|
2799
|
-
let hasWarnedAboutUntrustedOrigin = false;
|
|
2800
|
-
function warnAboutUntrustedOrigin(origin) {
|
|
2801
|
-
if (hasWarnedAboutUntrustedOrigin)
|
|
2802
|
-
return;
|
|
2803
|
-
hasWarnedAboutUntrustedOrigin = true;
|
|
2804
|
-
console.warn("[Playcademy SDK] Ignoring INIT from untrusted origin:", origin);
|
|
2805
|
-
}
|
|
2806
|
-
const handleMessage = (event) => {
|
|
2807
|
-
if (event.data?.type !== "PLAYCADEMY_INIT" /* INIT */)
|
|
2808
|
-
return;
|
|
2809
|
-
if (!isOriginAllowed(event.origin, allowlist)) {
|
|
2810
|
-
warnAboutUntrustedOrigin(event.origin);
|
|
2811
|
-
return;
|
|
2812
|
-
}
|
|
2813
|
-
contextReceived = true;
|
|
2814
|
-
window.removeEventListener("message", handleMessage);
|
|
2815
|
-
clearTimeout(timeoutId);
|
|
2816
|
-
window.PLAYCADEMY = event.data.payload;
|
|
2817
|
-
resolve(event.data.payload);
|
|
2818
|
-
};
|
|
2819
|
-
window.addEventListener("message", handleMessage);
|
|
2820
|
-
const timeoutId = setTimeout(() => {
|
|
2821
|
-
if (!contextReceived) {
|
|
2822
|
-
window.removeEventListener("message", handleMessage);
|
|
2823
|
-
reject(new Error(`${"PLAYCADEMY_INIT" /* INIT */} not received within ${timeoutDuration}ms`));
|
|
2824
|
-
}
|
|
2825
|
-
}, timeoutDuration);
|
|
2826
|
-
});
|
|
2827
|
-
}
|
|
2828
|
-
function createStandaloneConfig() {
|
|
2829
|
-
console.debug("[Playcademy SDK] Standalone mode detected, creating mock context for sandbox development");
|
|
2830
|
-
const mockConfig = {
|
|
2831
|
-
baseUrl: "http://localhost:4321",
|
|
2832
|
-
gameUrl: window.location.origin,
|
|
2833
|
-
token: "mock-game-token-for-local-dev",
|
|
2834
|
-
gameId: "mock-game-id-from-template",
|
|
2835
|
-
realtimeUrl: undefined
|
|
2836
|
-
};
|
|
2837
|
-
window.PLAYCADEMY = mockConfig;
|
|
2838
|
-
return mockConfig;
|
|
2839
|
-
}
|
|
2840
|
-
async function init(options) {
|
|
2841
|
-
const { PlaycademyClient } = await Promise.resolve().then(() => (init_client(), exports_client));
|
|
2842
|
-
if (typeof window === "undefined") {
|
|
2843
|
-
throw new Error("Playcademy SDK must run in a browser context");
|
|
1921
|
+
async checkConnection() {
|
|
1922
|
+
if (!this.connectionManager)
|
|
1923
|
+
return "unknown";
|
|
1924
|
+
return await this.connectionManager.checkNow();
|
|
2844
1925
|
}
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
config.baseUrl = options.baseUrl;
|
|
1926
|
+
_setAuthContext(context) {
|
|
1927
|
+
this.authContext = context;
|
|
2848
1928
|
}
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
token: config.token,
|
|
2853
|
-
gameId: config.gameId,
|
|
2854
|
-
autoStartSession: window.self !== window.top,
|
|
2855
|
-
onDisconnect: options?.onDisconnect,
|
|
2856
|
-
enableConnectionMonitoring: options?.enableConnectionMonitoring
|
|
2857
|
-
});
|
|
2858
|
-
client["initPayload"] = config;
|
|
2859
|
-
messaging.listen("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, ({ token }) => client.setToken(token));
|
|
2860
|
-
messaging.send("PLAYCADEMY_READY" /* READY */, undefined);
|
|
2861
|
-
return client;
|
|
2862
|
-
}
|
|
2863
|
-
var init_init = __esm(() => {
|
|
2864
|
-
init_messaging();
|
|
2865
|
-
});
|
|
2866
|
-
|
|
2867
|
-
// src/core/static/login.ts
|
|
2868
|
-
async function login2(baseUrl, email, password) {
|
|
2869
|
-
let url = baseUrl;
|
|
2870
|
-
if (baseUrl.startsWith("/") && typeof window !== "undefined") {
|
|
2871
|
-
url = window.location.origin + baseUrl;
|
|
1929
|
+
on(event, callback) {
|
|
1930
|
+
this.listeners[event] = this.listeners[event] ?? [];
|
|
1931
|
+
this.listeners[event].push(callback);
|
|
2872
1932
|
}
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
1933
|
+
emit(event, payload) {
|
|
1934
|
+
(this.listeners[event] ?? []).forEach((listener) => {
|
|
1935
|
+
listener(payload);
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
async request(path, method, options) {
|
|
1939
|
+
const effectiveHeaders = {
|
|
1940
|
+
...options?.headers,
|
|
1941
|
+
...this.authStrategy.getHeaders()
|
|
1942
|
+
};
|
|
2882
1943
|
try {
|
|
2883
|
-
const
|
|
2884
|
-
|
|
2885
|
-
|
|
1944
|
+
const result = await request({
|
|
1945
|
+
path,
|
|
1946
|
+
method,
|
|
1947
|
+
body: options?.body,
|
|
1948
|
+
baseUrl: this.baseUrl,
|
|
1949
|
+
extraHeaders: effectiveHeaders,
|
|
1950
|
+
raw: options?.raw
|
|
1951
|
+
});
|
|
1952
|
+
this.connectionManager?.reportRequestSuccess();
|
|
1953
|
+
return result;
|
|
2886
1954
|
} catch (error) {
|
|
2887
|
-
|
|
2888
|
-
throw
|
|
1955
|
+
this.connectionManager?.reportRequestFailure(error);
|
|
1956
|
+
throw error;
|
|
2889
1957
|
}
|
|
2890
1958
|
}
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
};
|
|
2905
|
-
});
|
|
2906
|
-
|
|
2907
|
-
// src/core/static/index.ts
|
|
2908
|
-
var init_static = __esm(() => {
|
|
2909
|
-
init_init();
|
|
2910
|
-
init_login2();
|
|
2911
|
-
init_identity2();
|
|
2912
|
-
});
|
|
2913
|
-
|
|
2914
|
-
// src/core/client.ts
|
|
2915
|
-
var exports_client = {};
|
|
2916
|
-
__export(exports_client, {
|
|
2917
|
-
PlaycademyClient: () => PlaycademyClient
|
|
2918
|
-
});
|
|
2919
|
-
var PlaycademyClient;
|
|
2920
|
-
var init_client = __esm(() => {
|
|
2921
|
-
init_src();
|
|
2922
|
-
init_connection();
|
|
2923
|
-
init_errors();
|
|
2924
|
-
init_namespaces();
|
|
2925
|
-
init_request();
|
|
2926
|
-
init_static();
|
|
2927
|
-
PlaycademyClient = class PlaycademyClient {
|
|
2928
|
-
baseUrl;
|
|
2929
|
-
gameUrl;
|
|
2930
|
-
authStrategy;
|
|
2931
|
-
gameId;
|
|
2932
|
-
config;
|
|
2933
|
-
listeners = {};
|
|
2934
|
-
internalClientSessionId;
|
|
2935
|
-
authContext;
|
|
2936
|
-
initPayload;
|
|
2937
|
-
connectionManager;
|
|
2938
|
-
constructor(config) {
|
|
2939
|
-
this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
|
|
2940
|
-
this.gameUrl = config?.gameUrl;
|
|
2941
|
-
this.gameId = config?.gameId;
|
|
2942
|
-
this.config = config || {};
|
|
2943
|
-
this.authStrategy = createAuthStrategy(config?.token ?? null, config?.tokenType);
|
|
2944
|
-
this._detectAuthContext();
|
|
2945
|
-
this._initializeInternalSession().catch(() => {});
|
|
2946
|
-
this._initializeConnectionMonitor();
|
|
2947
|
-
}
|
|
2948
|
-
getBaseUrl() {
|
|
2949
|
-
const isRelative = this.baseUrl.startsWith("/");
|
|
2950
|
-
const isBrowser2 = typeof window !== "undefined";
|
|
2951
|
-
return isRelative && isBrowser2 ? `${window.location.origin}${this.baseUrl}` : this.baseUrl;
|
|
2952
|
-
}
|
|
2953
|
-
getGameBackendUrl() {
|
|
2954
|
-
if (!this.gameUrl) {
|
|
2955
|
-
throw new PlaycademyError("Game backend URL not configured. gameUrl must be set to use game backend features.");
|
|
2956
|
-
}
|
|
2957
|
-
const isRelative = this.gameUrl.startsWith("/");
|
|
2958
|
-
const isBrowser2 = typeof window !== "undefined";
|
|
2959
|
-
const effectiveGameUrl = isRelative && isBrowser2 ? `${window.location.origin}${this.gameUrl}` : this.gameUrl;
|
|
2960
|
-
return `${effectiveGameUrl}/api`;
|
|
2961
|
-
}
|
|
2962
|
-
ping() {
|
|
2963
|
-
return "pong";
|
|
2964
|
-
}
|
|
2965
|
-
setToken(token, tokenType) {
|
|
2966
|
-
this.authStrategy = createAuthStrategy(token, tokenType);
|
|
2967
|
-
this.emit("authChange", { token });
|
|
2968
|
-
}
|
|
2969
|
-
getTokenType() {
|
|
2970
|
-
return this.authStrategy.getType();
|
|
2971
|
-
}
|
|
2972
|
-
getToken() {
|
|
2973
|
-
return this.authStrategy.getToken();
|
|
2974
|
-
}
|
|
2975
|
-
isAuthenticated() {
|
|
2976
|
-
return this.authStrategy.getToken() !== null;
|
|
2977
|
-
}
|
|
2978
|
-
onAuthChange(callback) {
|
|
2979
|
-
this.on("authChange", (payload) => callback(payload.token));
|
|
2980
|
-
}
|
|
2981
|
-
onDisconnect(callback) {
|
|
2982
|
-
if (!this.connectionManager) {
|
|
2983
|
-
return () => {};
|
|
2984
|
-
}
|
|
2985
|
-
return this.connectionManager.onDisconnect(callback);
|
|
2986
|
-
}
|
|
2987
|
-
getConnectionState() {
|
|
2988
|
-
return this.connectionManager?.getState() ?? "unknown";
|
|
2989
|
-
}
|
|
2990
|
-
async checkConnection() {
|
|
2991
|
-
if (!this.connectionManager)
|
|
2992
|
-
return "unknown";
|
|
2993
|
-
return await this.connectionManager.checkNow();
|
|
2994
|
-
}
|
|
2995
|
-
_setAuthContext(context) {
|
|
2996
|
-
this.authContext = context;
|
|
2997
|
-
}
|
|
2998
|
-
on(event, callback) {
|
|
2999
|
-
this.listeners[event] = this.listeners[event] ?? [];
|
|
3000
|
-
this.listeners[event].push(callback);
|
|
3001
|
-
}
|
|
3002
|
-
emit(event, payload) {
|
|
3003
|
-
(this.listeners[event] ?? []).forEach((listener) => {
|
|
3004
|
-
listener(payload);
|
|
1959
|
+
async requestGameBackend(path, method, body, headers, raw) {
|
|
1960
|
+
const effectiveHeaders = {
|
|
1961
|
+
...headers,
|
|
1962
|
+
...this.authStrategy.getHeaders()
|
|
1963
|
+
};
|
|
1964
|
+
try {
|
|
1965
|
+
const result = await request({
|
|
1966
|
+
path,
|
|
1967
|
+
method,
|
|
1968
|
+
body,
|
|
1969
|
+
baseUrl: this.getGameBackendUrl(),
|
|
1970
|
+
extraHeaders: effectiveHeaders,
|
|
1971
|
+
raw
|
|
3005
1972
|
});
|
|
1973
|
+
this.connectionManager?.reportRequestSuccess();
|
|
1974
|
+
return result;
|
|
1975
|
+
} catch (error) {
|
|
1976
|
+
this.connectionManager?.reportRequestFailure(error);
|
|
1977
|
+
throw error;
|
|
3006
1978
|
}
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
};
|
|
3012
|
-
try {
|
|
3013
|
-
const result = await request({
|
|
3014
|
-
path,
|
|
3015
|
-
method,
|
|
3016
|
-
body: options?.body,
|
|
3017
|
-
baseUrl: this.baseUrl,
|
|
3018
|
-
extraHeaders: effectiveHeaders,
|
|
3019
|
-
raw: options?.raw
|
|
3020
|
-
});
|
|
3021
|
-
this.connectionManager?.reportRequestSuccess();
|
|
3022
|
-
return result;
|
|
3023
|
-
} catch (error) {
|
|
3024
|
-
this.connectionManager?.reportRequestFailure(error);
|
|
3025
|
-
throw error;
|
|
3026
|
-
}
|
|
3027
|
-
}
|
|
3028
|
-
async requestGameBackend(path, method, body, headers, raw) {
|
|
3029
|
-
const effectiveHeaders = {
|
|
3030
|
-
...headers,
|
|
3031
|
-
...this.authStrategy.getHeaders()
|
|
3032
|
-
};
|
|
3033
|
-
try {
|
|
3034
|
-
const result = await request({
|
|
3035
|
-
path,
|
|
3036
|
-
method,
|
|
3037
|
-
body,
|
|
3038
|
-
baseUrl: this.getGameBackendUrl(),
|
|
3039
|
-
extraHeaders: effectiveHeaders,
|
|
3040
|
-
raw
|
|
3041
|
-
});
|
|
3042
|
-
this.connectionManager?.reportRequestSuccess();
|
|
3043
|
-
return result;
|
|
3044
|
-
} catch (error) {
|
|
3045
|
-
this.connectionManager?.reportRequestFailure(error);
|
|
3046
|
-
throw error;
|
|
3047
|
-
}
|
|
3048
|
-
}
|
|
3049
|
-
_ensureGameId() {
|
|
3050
|
-
if (!this.gameId) {
|
|
3051
|
-
throw new PlaycademyError("This operation requires a gameId, but none was provided when initializing the client.");
|
|
3052
|
-
}
|
|
3053
|
-
return this.gameId;
|
|
3054
|
-
}
|
|
3055
|
-
_detectAuthContext() {
|
|
3056
|
-
this.authContext = { isInIframe: isInIframe() };
|
|
1979
|
+
}
|
|
1980
|
+
_ensureGameId() {
|
|
1981
|
+
if (!this.gameId) {
|
|
1982
|
+
throw new PlaycademyError("This operation requires a gameId, but none was provided when initializing the client.");
|
|
3057
1983
|
}
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
1984
|
+
return this.gameId;
|
|
1985
|
+
}
|
|
1986
|
+
_detectAuthContext() {
|
|
1987
|
+
this.authContext = { isInIframe: isInIframe() };
|
|
1988
|
+
}
|
|
1989
|
+
_initializeConnectionMonitor() {
|
|
1990
|
+
if (typeof window === "undefined")
|
|
1991
|
+
return;
|
|
1992
|
+
const isEnabled = this.config.enableConnectionMonitoring ?? true;
|
|
1993
|
+
if (!isEnabled)
|
|
1994
|
+
return;
|
|
1995
|
+
try {
|
|
1996
|
+
this.connectionManager = new ConnectionManager({
|
|
1997
|
+
baseUrl: this.baseUrl,
|
|
1998
|
+
authContext: this.authContext,
|
|
1999
|
+
onDisconnect: this.config.onDisconnect,
|
|
2000
|
+
onConnectionChange: (state, reason) => {
|
|
2001
|
+
this.emit("connectionChange", { state, reason });
|
|
2002
|
+
}
|
|
2003
|
+
});
|
|
2004
|
+
} catch (error) {
|
|
2005
|
+
log.error("[Playcademy SDK] Failed to initialize connection manager:", { error });
|
|
3076
2006
|
}
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
}
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
}
|
|
2007
|
+
}
|
|
2008
|
+
async _initializeInternalSession() {
|
|
2009
|
+
if (!this.gameId || this.internalClientSessionId)
|
|
2010
|
+
return;
|
|
2011
|
+
const shouldAutoStart = this.config.autoStartSession ?? true;
|
|
2012
|
+
if (!shouldAutoStart)
|
|
2013
|
+
return;
|
|
2014
|
+
try {
|
|
2015
|
+
const response = await this._sessionManager.startSession(this.gameId);
|
|
2016
|
+
this.internalClientSessionId = response.sessionId;
|
|
2017
|
+
log.debug("[Playcademy SDK] Auto-started game session", {
|
|
2018
|
+
gameId: this.gameId,
|
|
2019
|
+
sessionId: this.internalClientSessionId
|
|
2020
|
+
});
|
|
2021
|
+
} catch (error) {
|
|
2022
|
+
log.error("[Playcademy SDK] Auto-starting session failed for game", {
|
|
2023
|
+
gameId: this.gameId,
|
|
2024
|
+
error
|
|
2025
|
+
});
|
|
3096
2026
|
}
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
leaderboard = createLeaderboardNamespace(this);
|
|
3111
|
-
scores = createScoresNamespace(this);
|
|
3112
|
-
character = createCharacterNamespace(this);
|
|
3113
|
-
sprites = createSpritesNamespace(this);
|
|
3114
|
-
realtime = createRealtimeNamespace(this);
|
|
3115
|
-
achievements = createAchievementsNamespace(this);
|
|
3116
|
-
notifications = createNotificationsNamespace(this);
|
|
3117
|
-
backend = createBackendNamespace(this);
|
|
3118
|
-
static init = init;
|
|
3119
|
-
static login = login2;
|
|
3120
|
-
static identity = identity;
|
|
3121
|
-
};
|
|
3122
|
-
});
|
|
3123
|
-
|
|
3124
|
-
// src/index.ts
|
|
3125
|
-
init_client();
|
|
3126
|
-
init_errors();
|
|
3127
|
-
init_connection();
|
|
3128
|
-
init_messaging();
|
|
2027
|
+
}
|
|
2028
|
+
identity = createIdentityNamespace(this);
|
|
2029
|
+
runtime = createRuntimeNamespace(this);
|
|
2030
|
+
users = createUsersNamespace(this);
|
|
2031
|
+
timeback = createTimebackNamespace(this);
|
|
2032
|
+
credits = createCreditsNamespace(this);
|
|
2033
|
+
scores = createScoresNamespace(this);
|
|
2034
|
+
realtime = createRealtimeNamespace(this);
|
|
2035
|
+
backend = createBackendNamespace(this);
|
|
2036
|
+
static init = init;
|
|
2037
|
+
static login = login;
|
|
2038
|
+
static identity = identity;
|
|
2039
|
+
}
|
|
3129
2040
|
export {
|
|
3130
2041
|
messaging,
|
|
3131
2042
|
extractApiErrorInfo,
|