@playcademy/sdk 0.9.1-beta.1 → 0.9.1-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/internal.js CHANGED
@@ -19,13 +19,11 @@ var MessageEvents;
19
19
  MessageEvents2["RESUME"] = "PLAYCADEMY_RESUME";
20
20
  MessageEvents2["FORCE_EXIT"] = "PLAYCADEMY_FORCE_EXIT";
21
21
  MessageEvents2["OVERLAY"] = "PLAYCADEMY_OVERLAY";
22
- MessageEvents2["CONNECTION_STATE"] = "PLAYCADEMY_CONNECTION_STATE";
23
22
  MessageEvents2["READY"] = "PLAYCADEMY_READY";
24
23
  MessageEvents2["INIT_ERROR"] = "PLAYCADEMY_INIT_ERROR";
25
24
  MessageEvents2["EXIT"] = "PLAYCADEMY_EXIT";
26
25
  MessageEvents2["TELEMETRY"] = "PLAYCADEMY_TELEMETRY";
27
26
  MessageEvents2["KEY_EVENT"] = "PLAYCADEMY_KEY_EVENT";
28
- MessageEvents2["DISPLAY_ALERT"] = "PLAYCADEMY_DISPLAY_ALERT";
29
27
  MessageEvents2["DEMO_END"] = "PLAYCADEMY_DEMO_END";
30
28
  MessageEvents2["AUTH_STATE_CHANGE"] = "PLAYCADEMY_AUTH_STATE_CHANGE";
31
29
  MessageEvents2["AUTH_CALLBACK"] = "PLAYCADEMY_AUTH_CALLBACK";
@@ -87,7 +85,6 @@ class PlaycademyMessaging {
87
85
  "PLAYCADEMY_EXIT" /* EXIT */,
88
86
  "PLAYCADEMY_TELEMETRY" /* TELEMETRY */,
89
87
  "PLAYCADEMY_KEY_EVENT" /* KEY_EVENT */,
90
- "PLAYCADEMY_DISPLAY_ALERT" /* DISPLAY_ALERT */,
91
88
  "PLAYCADEMY_DEMO_END" /* DEMO_END */
92
89
  ];
93
90
  const shouldUsePostMessage = isIframe && iframeToParentEvents.includes(eventType);
@@ -194,7 +191,6 @@ function createStandaloneConfig() {
194
191
  gameUrl: globalThis.location.origin,
195
192
  token: "mock-game-token-for-local-dev",
196
193
  gameId: "mock-game-id-from-template",
197
- realtimeUrl: undefined,
198
194
  mode: "standalone"
199
195
  };
200
196
  globalThis.PLAYCADEMY = mockConfig;
@@ -213,10 +209,7 @@ async function init(options) {
213
209
  gameUrl: config.gameUrl,
214
210
  token: config.token,
215
211
  gameId: config.gameId,
216
- mode: config.mode,
217
- autoStartSession: globalThis.self !== window.top,
218
- onDisconnect: options?.onDisconnect,
219
- enableConnectionMonitoring: options?.enableConnectionMonitoring
212
+ mode: config.mode
220
213
  });
221
214
  client["initPayload"] = config;
222
215
  messaging.listen("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, ({ token }) => client.setToken(token));
@@ -637,6 +630,80 @@ function getOAuthConfig(provider) {
637
630
  var identity = {
638
631
  parseOAuthState
639
632
  };
633
+ // src/namespaces/game/backend.ts
634
+ function normalizePath(path) {
635
+ return path.startsWith("/") ? path : `/${path}`;
636
+ }
637
+ function createBackendNamespace(client) {
638
+ return {
639
+ async get(path, headers) {
640
+ return client["requestGameBackend"](normalizePath(path), "GET", undefined, headers);
641
+ },
642
+ async post(path, body, headers) {
643
+ return client["requestGameBackend"](normalizePath(path), "POST", body, headers);
644
+ },
645
+ async put(path, body, headers) {
646
+ return client["requestGameBackend"](normalizePath(path), "PUT", body, headers);
647
+ },
648
+ async patch(path, body, headers) {
649
+ return client["requestGameBackend"](normalizePath(path), "PATCH", body, headers);
650
+ },
651
+ async delete(path, headers) {
652
+ return client["requestGameBackend"](normalizePath(path), "DELETE", undefined, headers);
653
+ },
654
+ async request(path, method, body, headers) {
655
+ return client["requestGameBackend"](normalizePath(path), method, body, headers);
656
+ },
657
+ async download(path, method = "GET", body, headers) {
658
+ return client["requestGameBackend"](normalizePath(path), method, body, headers, { raw: true });
659
+ },
660
+ url(pathOrStrings, ...values) {
661
+ if (Array.isArray(pathOrStrings) && "raw" in pathOrStrings) {
662
+ const strings = pathOrStrings;
663
+ const path2 = strings.reduce((acc, str, i) => acc + str + (values[i] != null ? String(values[i]) : ""), "");
664
+ return `${client.gameUrl}/api${path2.startsWith("/") ? path2 : `/${path2}`}`;
665
+ }
666
+ const path = pathOrStrings;
667
+ return `${client.gameUrl}/api${path.startsWith("/") ? path : `/${path}`}`;
668
+ }
669
+ };
670
+ }
671
+ // src/core/assert.ts
672
+ function assertPlatformMode(client, operation) {
673
+ if (client.mode !== "platform") {
674
+ throw new PlaycademyError(`${operation} requires platform mode (current: ${client.mode}). Check client.mode before calling.`);
675
+ }
676
+ }
677
+ function assertDemoMode(client, operation) {
678
+ if (client.mode !== "demo") {
679
+ throw new PlaycademyError(`${operation} requires demo mode (current: ${client.mode}). Check client.mode before calling.`);
680
+ }
681
+ }
682
+
683
+ // src/namespaces/game/demo.ts
684
+ function createDemoNamespace(client) {
685
+ return {
686
+ profile: {
687
+ get: async () => {
688
+ assertDemoMode(client, "demo.profile.get()");
689
+ return client["request"]("/users/demo-profile", "GET");
690
+ },
691
+ update: async (updates) => {
692
+ assertDemoMode(client, "demo.profile.update()");
693
+ return client["request"]("/users/demo-profile", "PATCH", {
694
+ body: updates
695
+ });
696
+ }
697
+ },
698
+ end: (score, options) => {
699
+ assertDemoMode(client, "demo.end()");
700
+ messaging.send("PLAYCADEMY_DEMO_END" /* DEMO_END */, {
701
+ score,
702
+ ...options
703
+ });
704
+ }
705
+ };
706
+ }
640
707
  // src/core/auth/utils.ts
641
708
  function openPopupWindow(url, name = "auth-popup", width = 500, height = 600) {
642
709
  const left = window.screenX + (window.outerWidth - width) / 2;
@@ -941,17 +1008,7 @@ function createRuntimeNamespace(client) {
941
1008
  }
942
1009
  return res;
943
1010
  },
944
- exit: async () => {
945
- if (client["internalClientSessionId"] && client["gameId"]) {
946
- try {
947
- await client["_sessionManager"].endSession(client["internalClientSessionId"], client["gameId"]);
948
- } catch (error) {
949
- log.error("[Playcademy SDK] Failed to auto-end session:", {
950
- sessionId: client["internalClientSessionId"],
951
- error
952
- });
953
- }
954
- }
1011
+ exit: () => {
955
1012
  messaging.send("PLAYCADEMY_EXIT" /* EXIT */, undefined);
956
1013
  },
957
1014
  onInit: (handler) => {
@@ -1051,401 +1108,6 @@ function createAssetsNamespace(client) {
1051
1108
  }
1052
1109
  };
1053
1110
  }
1054
- // src/namespaces/game/backend.ts
1055
- function normalizePath(path) {
1056
- return path.startsWith("/") ? path : `/${path}`;
1057
- }
1058
- function createBackendNamespace(client) {
1059
- return {
1060
- async get(path, headers) {
1061
- return client["requestGameBackend"](normalizePath(path), "GET", undefined, headers);
1062
- },
1063
- async post(path, body, headers) {
1064
- return client["requestGameBackend"](normalizePath(path), "POST", body, headers);
1065
- },
1066
- async put(path, body, headers) {
1067
- return client["requestGameBackend"](normalizePath(path), "PUT", body, headers);
1068
- },
1069
- async patch(path, body, headers) {
1070
- return client["requestGameBackend"](normalizePath(path), "PATCH", body, headers);
1071
- },
1072
- async delete(path, headers) {
1073
- return client["requestGameBackend"](normalizePath(path), "DELETE", undefined, headers);
1074
- },
1075
- async request(path, method, body, headers) {
1076
- return client["requestGameBackend"](normalizePath(path), method, body, headers);
1077
- },
1078
- async download(path, method = "GET", body, headers) {
1079
- return client["requestGameBackend"](normalizePath(path), method, body, headers, { raw: true });
1080
- },
1081
- url(pathOrStrings, ...values) {
1082
- if (Array.isArray(pathOrStrings) && "raw" in pathOrStrings) {
1083
- const strings = pathOrStrings;
1084
- const path2 = strings.reduce((acc, str, i) => acc + str + (values[i] != null ? String(values[i]) : ""), "");
1085
- return `${client.gameUrl}/api${path2.startsWith("/") ? path2 : `/${path2}`}`;
1086
- }
1087
- const path = pathOrStrings;
1088
- return `${client.gameUrl}/api${path.startsWith("/") ? path : `/${path}`}`;
1089
- }
1090
- };
1091
- }
1092
- // src/namespaces/game/guard.ts
1093
- function assertPlatformMode(client, operation) {
1094
- if (client.mode !== "platform") {
1095
- throw new PlaycademyError(`${operation} requires platform mode (current: ${client.mode}). Check client.mode before calling.`);
1096
- }
1097
- }
1098
- function assertDemoMode(client, operation) {
1099
- if (client.mode !== "demo") {
1100
- throw new PlaycademyError(`${operation} requires demo mode (current: ${client.mode}). Check client.mode before calling.`);
1101
- }
1102
- }
1103
-
1104
- // src/namespaces/game/demo.ts
1105
- function createDemoNamespace(client) {
1106
- return {
1107
- profile: {
1108
- get: async () => {
1109
- assertDemoMode(client, "demo.profile.get()");
1110
- return client["request"]("/users/demo-profile", "GET");
1111
- },
1112
- update: async (updates) => {
1113
- assertDemoMode(client, "demo.profile.update()");
1114
- return client["request"]("/users/demo-profile", "PATCH", {
1115
- body: updates
1116
- });
1117
- }
1118
- },
1119
- end: (score, options) => {
1120
- assertDemoMode(client, "demo.end()");
1121
- messaging.send("PLAYCADEMY_DEMO_END" /* DEMO_END */, {
1122
- score,
1123
- ...options
1124
- });
1125
- }
1126
- };
1127
- }
1128
- // src/core/cache/permanent-cache.ts
1129
- function createPermanentCache(keyPrefix) {
1130
- const cache = new Map;
1131
- async function get(key, loader) {
1132
- const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
1133
- const existing = cache.get(fullKey);
1134
- if (existing) {
1135
- return existing;
1136
- }
1137
- const promise = loader().catch((error) => {
1138
- cache.delete(fullKey);
1139
- throw error;
1140
- });
1141
- cache.set(fullKey, promise);
1142
- return promise;
1143
- }
1144
- function clear(key) {
1145
- if (key === undefined) {
1146
- cache.clear();
1147
- } else {
1148
- const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
1149
- cache.delete(fullKey);
1150
- }
1151
- }
1152
- function has(key) {
1153
- const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
1154
- return cache.has(fullKey);
1155
- }
1156
- function size() {
1157
- return cache.size;
1158
- }
1159
- function keys() {
1160
- const result = [];
1161
- const prefixLen = keyPrefix ? keyPrefix.length + 1 : 0;
1162
- for (const fullKey of cache.keys()) {
1163
- result.push(fullKey.substring(prefixLen));
1164
- }
1165
- return result;
1166
- }
1167
- return { get, clear, has, size, keys };
1168
- }
1169
-
1170
- // src/namespaces/game/users.ts
1171
- function createUsersNamespace(client) {
1172
- const itemIdCache = createPermanentCache("items");
1173
- const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1174
- async function resolveItemId(identifier) {
1175
- if (UUID_REGEX.test(identifier)) {
1176
- return identifier;
1177
- }
1178
- const gameId = client["gameId"];
1179
- const cacheKey = gameId ? `${identifier}:${gameId}` : identifier;
1180
- return itemIdCache.get(cacheKey, async () => {
1181
- const queryParams = new URLSearchParams({ slug: identifier });
1182
- if (gameId) {
1183
- queryParams.append("gameId", gameId);
1184
- }
1185
- const item = await client["request"](`/items/resolve?${queryParams.toString()}`, "GET");
1186
- return item.id;
1187
- });
1188
- }
1189
- return {
1190
- me: async () => {
1191
- assertPlatformMode(client, "users.me()");
1192
- return client["request"]("/users/me", "GET");
1193
- },
1194
- inventory: {
1195
- get: async () => {
1196
- assertPlatformMode(client, "users.inventory.get()");
1197
- return client["request"](`/inventory`, "GET");
1198
- },
1199
- add: async (identifier, qty) => {
1200
- assertPlatformMode(client, "users.inventory.add()");
1201
- const itemId = await resolveItemId(identifier);
1202
- const res = await client["request"](`/inventory/add`, "POST", { body: { itemId, qty } });
1203
- client["emit"]("inventoryChange", {
1204
- itemId,
1205
- delta: qty,
1206
- newTotal: res.newTotal
1207
- });
1208
- return res;
1209
- },
1210
- remove: async (identifier, qty) => {
1211
- assertPlatformMode(client, "users.inventory.remove()");
1212
- const itemId = await resolveItemId(identifier);
1213
- const res = await client["request"](`/inventory/remove`, "POST", { body: { itemId, qty } });
1214
- client["emit"]("inventoryChange", {
1215
- itemId,
1216
- delta: -qty,
1217
- newTotal: res.newTotal
1218
- });
1219
- return res;
1220
- },
1221
- quantity: async (identifier) => {
1222
- assertPlatformMode(client, "users.inventory.quantity()");
1223
- const itemId = await resolveItemId(identifier);
1224
- const inventory = await client["request"](`/inventory`, "GET");
1225
- const item = inventory.find((inv) => inv.item?.id === itemId);
1226
- return item?.quantity ?? 0;
1227
- },
1228
- has: async (identifier, minQuantity = 1) => {
1229
- assertPlatformMode(client, "users.inventory.has()");
1230
- const itemId = await resolveItemId(identifier);
1231
- const inventory = await client["request"](`/inventory`, "GET");
1232
- const item = inventory.find((inv) => inv.item?.id === itemId);
1233
- const qty = item?.quantity ?? 0;
1234
- return qty >= minQuantity;
1235
- }
1236
- }
1237
- };
1238
- }
1239
- // ../constants/src/achievements.ts
1240
- var ACHIEVEMENT_IDS = {
1241
- PLAY_ANY_GAME_DAILY: "play_any_game_daily",
1242
- DAILY_CHEST_OPEN: "daily_chest_open",
1243
- LEADERBOARD_TOP3_DAILY: "leaderboard_top3_daily",
1244
- LEADERBOARD_TOP3_WEEKLY: "leaderboard_top3_weekly",
1245
- FIRST_SCORE_ANY_GAME: "first_score_any_game",
1246
- PERSONAL_BEST_ANY_GAME: "personal_best_any_game"
1247
- };
1248
- var ACHIEVEMENT_DEFINITIONS = [
1249
- {
1250
- id: ACHIEVEMENT_IDS.PLAY_ANY_GAME_DAILY,
1251
- title: "Play any game",
1252
- description: "Play any arcade game for at least 60 seconds in a single session.",
1253
- scope: "daily",
1254
- rewardCredits: 10,
1255
- limit: 1,
1256
- completionType: "time_played_session",
1257
- completionConfig: { minSeconds: 60 },
1258
- target: { anyArcadeGame: true },
1259
- active: true
1260
- },
1261
- {
1262
- id: ACHIEVEMENT_IDS.DAILY_CHEST_OPEN,
1263
- title: "Opened the daily chest",
1264
- description: "Find the chest on the map and open it to claim your reward.",
1265
- scope: "daily",
1266
- rewardCredits: 10,
1267
- limit: 1,
1268
- completionType: "interaction",
1269
- completionConfig: { triggerId: "bunny_chest" },
1270
- target: { map: "arcade" },
1271
- active: true
1272
- },
1273
- {
1274
- id: ACHIEVEMENT_IDS.LEADERBOARD_TOP3_DAILY,
1275
- title: "Daily Champion",
1276
- description: "Finish in the top 3 of any game leaderboard for the day.",
1277
- scope: "daily",
1278
- rewardCredits: 25,
1279
- limit: 1,
1280
- completionType: "leaderboard_rank",
1281
- completionConfig: {
1282
- rankRewards: [50, 30, 15]
1283
- },
1284
- target: { anyArcadeGame: true },
1285
- active: true
1286
- },
1287
- {
1288
- id: ACHIEVEMENT_IDS.LEADERBOARD_TOP3_WEEKLY,
1289
- title: "Weekly Legend",
1290
- description: "Finish in the top 3 of any game leaderboard for the week.",
1291
- scope: "weekly",
1292
- rewardCredits: 50,
1293
- limit: 1,
1294
- completionType: "leaderboard_rank",
1295
- completionConfig: {
1296
- rankRewards: [100, 60, 30]
1297
- },
1298
- target: { anyArcadeGame: true },
1299
- active: true
1300
- },
1301
- {
1302
- id: ACHIEVEMENT_IDS.FIRST_SCORE_ANY_GAME,
1303
- title: "First Score",
1304
- description: "Submit your first score in any arcade game.",
1305
- scope: "game",
1306
- rewardCredits: 5,
1307
- limit: 1,
1308
- completionType: "first_score",
1309
- completionConfig: {},
1310
- target: { anyArcadeGame: true },
1311
- active: true
1312
- },
1313
- {
1314
- id: ACHIEVEMENT_IDS.PERSONAL_BEST_ANY_GAME,
1315
- title: "Personal Best",
1316
- description: "Beat your personal best score in any arcade game.",
1317
- scope: "daily",
1318
- rewardCredits: 5,
1319
- limit: 3,
1320
- completionType: "personal_best",
1321
- completionConfig: {},
1322
- target: { anyArcadeGame: true },
1323
- active: true
1324
- }
1325
- ];
1326
- // ../constants/src/typescript.ts
1327
- var TypeScriptPackages = {
1328
- tsc: "tsc",
1329
- nativePreview: "@typescript/native-preview",
1330
- nativePreviewBeta: "@typescript/native-preview@beta"
1331
- };
1332
- var TYPESCRIPT_RUNNER = {
1333
- package: TypeScriptPackages.nativePreviewBeta,
1334
- bin: "tsgo"
1335
- };
1336
- // ../constants/src/overworld.ts
1337
- var ITEM_SLUGS = {
1338
- PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
1339
- PLAYCADEMY_XP: "PLAYCADEMY_XP",
1340
- FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
1341
- EARLY_ADOPTER_BADGE: "EARLY_ADOPTER_BADGE",
1342
- FIRST_GAME_BADGE: "FIRST_GAME_BADGE",
1343
- COMMON_SWORD: "COMMON_SWORD",
1344
- SMALL_HEALTH_POTION: "SMALL_HEALTH_POTION",
1345
- SMALL_BACKPACK: "SMALL_BACKPACK",
1346
- LAVA_LAMP: "LAVA_LAMP",
1347
- BOOMBOX: "BOOMBOX",
1348
- CABIN_BED: "CABIN_BED"
1349
- };
1350
- var CURRENCIES = {
1351
- PRIMARY: ITEM_SLUGS.PLAYCADEMY_CREDITS,
1352
- XP: ITEM_SLUGS.PLAYCADEMY_XP
1353
- };
1354
- var BADGES = {
1355
- FOUNDING_MEMBER: ITEM_SLUGS.FOUNDING_MEMBER_BADGE,
1356
- EARLY_ADOPTER: ITEM_SLUGS.EARLY_ADOPTER_BADGE,
1357
- FIRST_GAME: ITEM_SLUGS.FIRST_GAME_BADGE
1358
- };
1359
- // ../constants/src/timeback.ts
1360
- var TIMEBACK_ROUTES = {
1361
- END_ACTIVITY: "/integrations/timeback/end-activity",
1362
- GET_XP: "/integrations/timeback/xp",
1363
- HEARTBEAT: "/integrations/timeback/heartbeat",
1364
- ADVANCE_COURSE: "/integrations/timeback/advance-course"
1365
- };
1366
- // src/core/cache/singleton-cache.ts
1367
- function createSingletonCache() {
1368
- let cachedValue;
1369
- let hasValue = false;
1370
- async function get(loader) {
1371
- if (hasValue) {
1372
- return cachedValue;
1373
- }
1374
- const value = await loader();
1375
- cachedValue = value;
1376
- hasValue = true;
1377
- return value;
1378
- }
1379
- function clear() {
1380
- cachedValue = undefined;
1381
- hasValue = false;
1382
- }
1383
- function has() {
1384
- return hasValue;
1385
- }
1386
- return { get, clear, has };
1387
- }
1388
-
1389
- // src/namespaces/game/credits.ts
1390
- function createCreditsNamespace(client) {
1391
- const creditsIdCache = createSingletonCache();
1392
- async function getCreditsItemId() {
1393
- return creditsIdCache.get(async () => {
1394
- const queryParams = new URLSearchParams({ slug: CURRENCIES.PRIMARY });
1395
- const creditsItem = await client["request"](`/items/resolve?${queryParams.toString()}`, "GET");
1396
- if (!creditsItem || !creditsItem.id) {
1397
- throw new Error("Playcademy Credits item not found in catalog");
1398
- }
1399
- return creditsItem.id;
1400
- });
1401
- }
1402
- return {
1403
- balance: async () => {
1404
- assertPlatformMode(client, "credits.balance()");
1405
- const inventory = await client["request"]("/inventory", "GET");
1406
- const primaryCurrencyInventoryItem = inventory.find((item) => item.item?.slug === CURRENCIES.PRIMARY);
1407
- return primaryCurrencyInventoryItem?.quantity ?? 0;
1408
- },
1409
- add: async (amount) => {
1410
- assertPlatformMode(client, "credits.add()");
1411
- if (amount <= 0) {
1412
- throw new Error("Amount must be positive");
1413
- }
1414
- const creditsItemId = await getCreditsItemId();
1415
- const result = await client["request"]("/inventory/add", "POST", {
1416
- body: {
1417
- itemId: creditsItemId,
1418
- qty: amount
1419
- }
1420
- });
1421
- client["emit"]("inventoryChange", {
1422
- itemId: creditsItemId,
1423
- delta: amount,
1424
- newTotal: result.newTotal
1425
- });
1426
- return result.newTotal;
1427
- },
1428
- spend: async (amount) => {
1429
- assertPlatformMode(client, "credits.spend()");
1430
- if (amount <= 0) {
1431
- throw new Error("Amount must be positive");
1432
- }
1433
- const creditsItemId = await getCreditsItemId();
1434
- const result = await client["request"]("/inventory/remove", "POST", {
1435
- body: {
1436
- itemId: creditsItemId,
1437
- qty: amount
1438
- }
1439
- });
1440
- client["emit"]("inventoryChange", {
1441
- itemId: creditsItemId,
1442
- delta: -amount,
1443
- newTotal: result.newTotal
1444
- });
1445
- return result.newTotal;
1446
- }
1447
- };
1448
- }
1449
1111
  // src/namespaces/game/scores.ts
1450
1112
  function createScoresNamespace(client) {
1451
1113
  return {
@@ -1460,18 +1122,6 @@ function createScoresNamespace(client) {
1460
1122
  }
1461
1123
  };
1462
1124
  }
1463
- // src/namespaces/game/realtime.ts
1464
- function createRealtimeNamespace(client) {
1465
- return {
1466
- token: {
1467
- get: async () => {
1468
- assertPlatformMode(client, "realtime.token.get()");
1469
- const endpoint = client["gameId"] ? `/games/${client["gameId"]}/realtime/token` : "/realtime/token";
1470
- return client["request"](endpoint, "POST");
1471
- }
1472
- }
1473
- };
1474
- }
1475
1125
  // src/core/guards.ts
1476
1126
  var VALID_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
1477
1127
  var VALID_SUBJECTS = [
@@ -1491,7 +1141,13 @@ function isValidGrade(value) {
1491
1141
  function isValidSubject(value) {
1492
1142
  return typeof value === "string" && VALID_SUBJECTS.includes(value);
1493
1143
  }
1494
-
1144
+ // ../constants/src/timeback.ts
1145
+ var TIMEBACK_ROUTES = {
1146
+ END_ACTIVITY: "/integrations/timeback/end-activity",
1147
+ GET_XP: "/integrations/timeback/xp",
1148
+ HEARTBEAT: "/integrations/timeback/heartbeat",
1149
+ ADVANCE_COURSE: "/integrations/timeback/advance-course"
1150
+ };
1495
1151
  // src/core/cache/ttl-cache.ts
1496
1152
  function createTTLCache(options) {
1497
1153
  const cache = new Map;
@@ -1953,17 +1609,21 @@ function createTimebackActivityTracker(client) {
1953
1609
  }
1954
1610
  }
1955
1611
  return {
1612
+ currentRunId() {
1613
+ return currentActivity?.runId;
1614
+ },
1956
1615
  startActivity(metadata, options) {
1957
1616
  if (options?.runId !== undefined && !isValidUUID(options.runId)) {
1958
1617
  throw new Error(`startActivity: \`runId\` must be a UUID (received \`${JSON.stringify(options.runId)}\`). Use crypto.randomUUID() or persist a previously-generated UUID.`);
1959
1618
  }
1960
1619
  cleanupListeners();
1961
1620
  const now = Date.now();
1621
+ const runId = options?.runId ?? crypto.randomUUID();
1962
1622
  const heartbeatIntervalMs = normalizeDelayMs(options?.heartbeatIntervalMs, DEFAULT_HEARTBEAT_INTERVAL_MS, false);
1963
1623
  const pausedHeartbeatTimeoutMs = normalizeDelayMs(options?.pausedHeartbeatTimeoutMs ?? options?.hiddenTimeoutMs, DEFAULT_PAUSED_HEARTBEAT_TIMEOUT_MS, false);
1964
1624
  const inactivityTimeoutMs = normalizeDelayMs(options?.inactivityTimeoutMs, DEFAULT_INACTIVITY_TIMEOUT_MS, false);
1965
1625
  currentActivity = {
1966
- runId: options?.runId ?? crypto.randomUUID(),
1626
+ runId,
1967
1627
  resumeId: crypto.randomUUID(),
1968
1628
  startTime: now,
1969
1629
  metadata,
@@ -2006,6 +1666,7 @@ function createTimebackActivityTracker(client) {
2006
1666
  messaging.listen("PLAYCADEMY_PAUSE" /* PAUSE */, boundShellPauseHandler);
2007
1667
  messaging.listen("PLAYCADEMY_RESUME" /* RESUME */, boundShellResumeHandler);
2008
1668
  syncInactivityTracking();
1669
+ return { runId };
2009
1670
  },
2010
1671
  pauseActivity() {
2011
1672
  if (!currentActivity) {
@@ -2189,6 +1850,7 @@ function createTimebackEngine(client) {
2189
1850
  }
2190
1851
  },
2191
1852
  activity: {
1853
+ currentRunId: activityTracker.currentRunId,
2192
1854
  start: activityTracker.startActivity,
2193
1855
  pause: activityTracker.pauseActivity,
2194
1856
  resume: activityTracker.resumeActivity,
@@ -2249,9 +1911,13 @@ function createTimebackNamespace(client) {
2249
1911
  }
2250
1912
  };
2251
1913
  },
1914
+ get currentRunId() {
1915
+ assertPlatformMode(client, "timeback.currentRunId");
1916
+ return engine.activity.currentRunId();
1917
+ },
2252
1918
  startActivity: (metadata, options) => {
2253
1919
  assertPlatformMode(client, "timeback.startActivity()");
2254
- engine.activity.start(metadata, options);
1920
+ return engine.activity.start(metadata, options);
2255
1921
  },
2256
1922
  pauseActivity: () => {
2257
1923
  assertPlatformMode(client, "timeback.pauseActivity()");
@@ -2274,6 +1940,24 @@ function createTimebackNamespace(client) {
2274
1940
  }
2275
1941
  };
2276
1942
  }
1943
+ // src/namespaces/game/users.ts
1944
+ function createUsersNamespace(client) {
1945
+ return {
1946
+ me: async () => {
1947
+ assertPlatformMode(client, "users.me()");
1948
+ return client["request"]("/users/me", "GET");
1949
+ }
1950
+ };
1951
+ }
1952
+ // src/namespaces/platform/admin.ts
1953
+ function createAdminNamespace(client) {
1954
+ return {
1955
+ games: {
1956
+ pauseGame: (gameId) => client["request"](`/admin/games/${gameId}/pause`, "POST"),
1957
+ resumeGame: (gameId) => client["request"](`/admin/games/${gameId}/resume`, "POST")
1958
+ }
1959
+ };
1960
+ }
2277
1961
  // src/namespaces/platform/auth.ts
2278
1962
  function createAuthNamespace(client) {
2279
1963
  return {
@@ -2321,41 +2005,54 @@ function createAuthNamespace(client) {
2321
2005
  }
2322
2006
  };
2323
2007
  }
2324
- // src/namespaces/platform/admin.ts
2325
- function createAdminNamespace(client) {
2326
- return {
2327
- games: {
2328
- pauseGame: (gameId) => client["request"](`/admin/games/${gameId}/pause`, "POST"),
2329
- resumeGame: (gameId) => client["request"](`/admin/games/${gameId}/resume`, "POST")
2330
- },
2331
- items: {
2332
- create: (props) => client["request"]("/items", "POST", { body: props }),
2333
- get: (itemId) => client["request"](`/items/${itemId}`, "GET"),
2334
- list: () => client["request"]("/items", "GET"),
2335
- update: (itemId, props) => client["request"](`/items/${itemId}`, "PATCH", { body: props }),
2336
- delete: (itemId) => client["request"](`/items/${itemId}`, "DELETE")
2337
- },
2338
- currencies: {
2339
- create: (props) => client["request"]("/currencies", "POST", { body: props }),
2340
- get: (currencyId) => client["request"](`/currencies/${currencyId}`, "GET"),
2341
- list: () => client["request"]("/currencies", "GET"),
2342
- update: (currencyId, props) => client["request"](`/currencies/${currencyId}`, "PATCH", {
2343
- body: props
2344
- }),
2345
- delete: (currencyId) => client["request"](`/currencies/${currencyId}`, "DELETE")
2346
- },
2347
- shopListings: {
2348
- create: (props) => client["request"]("/shop-listings", "POST", { body: props }),
2349
- get: (listingId) => client["request"](`/shop-listings/${listingId}`, "GET"),
2350
- list: () => client["request"]("/shop-listings", "GET"),
2351
- update: (listingId, props) => client["request"](`/shop-listings/${listingId}`, "PATCH", {
2352
- body: props
2353
- }),
2354
- delete: (listingId) => client["request"](`/shop-listings/${listingId}`, "DELETE")
2008
+ // src/core/deploy.ts
2009
+ var S3_ERROR_HINTS = {
2010
+ ExpiredToken: "the AWS credentials used to sign the upload URL have expired",
2011
+ TokenRefreshRequired: "the AWS credentials used to sign the upload URL have expired",
2012
+ AccessDenied: "the presigned URL was rejected, the upload token may have expired",
2013
+ SignatureDoesNotMatch: "upload signature mismatch, likely a Content-Type header discrepancy"
2014
+ };
2015
+ async function parseS3Error(response) {
2016
+ try {
2017
+ const xml = await response.text();
2018
+ const code = xml.match(/<Code>(.+?)<\/Code>/)?.[1];
2019
+ const message = xml.match(/<Message>(.+?)<\/Message>/)?.[1];
2020
+ const hint = code ? S3_ERROR_HINTS[code] : undefined;
2021
+ if (hint) {
2022
+ return `${response.status} (${hint})`;
2023
+ }
2024
+ if (code && message) {
2025
+ return `${response.status} (S3 ${code}: ${message})`;
2026
+ }
2027
+ if (code) {
2028
+ return `${response.status} (S3 error: ${code})`;
2029
+ }
2030
+ return `${response.status} ${response.statusText}`;
2031
+ } catch {
2032
+ return `${response.status} ${response.statusText}`;
2033
+ }
2034
+ }
2035
+ function parseS3ErrorFromXHR(xhr) {
2036
+ try {
2037
+ const xml = xhr.responseText;
2038
+ const code = xml.match(/<Code>(.+?)<\/Code>/)?.[1];
2039
+ const message = xml.match(/<Message>(.+?)<\/Message>/)?.[1];
2040
+ const hint = code ? S3_ERROR_HINTS[code] : undefined;
2041
+ if (hint) {
2042
+ return `${xhr.status} (${hint})`;
2355
2043
  }
2356
- };
2044
+ if (code && message) {
2045
+ return `${xhr.status} (S3 ${code}: ${message})`;
2046
+ }
2047
+ if (code) {
2048
+ return `${xhr.status} (S3 error: ${code})`;
2049
+ }
2050
+ return `${xhr.status} ${xhr.statusText}`;
2051
+ } catch {
2052
+ return `${xhr.status} ${xhr.statusText}`;
2053
+ }
2357
2054
  }
2358
- // src/core/deploy.ts
2055
+
2359
2056
  class DeployPipeline {
2360
2057
  static POLL_INTERVAL_MS = 1000;
2361
2058
  static INACTIVITY_TIMEOUT_MS = 60 * 1000;
@@ -2439,7 +2136,8 @@ class DeployPipeline {
2439
2136
  headers: { "Content-Type": "application/javascript" }
2440
2137
  });
2441
2138
  if (!res.ok) {
2442
- throw new Error(`Backend code upload failed: ${res.status} ${res.statusText}`);
2139
+ const detail = await parseS3Error(res);
2140
+ throw new Error(`Backend code upload failed: ${detail}`);
2443
2141
  }
2444
2142
  return uploadToken;
2445
2143
  }
@@ -2450,7 +2148,8 @@ class DeployPipeline {
2450
2148
  headers: { "Content-Type": contentType }
2451
2149
  });
2452
2150
  if (!res.ok) {
2453
- throw new Error(`File upload failed: ${res.status} ${res.statusText}`);
2151
+ const detail = await parseS3Error(res);
2152
+ throw new Error(`File upload failed: ${detail}`);
2454
2153
  }
2455
2154
  }
2456
2155
  uploadViaXHR(url, body, contentType, hooks) {
@@ -2474,7 +2173,8 @@ class DeployPipeline {
2474
2173
  if (xhr.status >= 200 && xhr.status < 300) {
2475
2174
  resolve();
2476
2175
  } else {
2477
- reject(new Error(`File upload failed: ${xhr.status} ${xhr.statusText}`));
2176
+ const detail = parseS3ErrorFromXHR(xhr);
2177
+ reject(new Error(`File upload failed: ${detail}`));
2478
2178
  }
2479
2179
  });
2480
2180
  xhr.addEventListener("error", () => reject(new Error("File upload failed: network error")));
@@ -2665,30 +2365,6 @@ function createDevNamespace(client) {
2665
2365
  logs: {
2666
2366
  getToken: async (slug, environment) => client["request"](`/games/${slug}/logs/token`, "POST", { body: { environment } })
2667
2367
  }
2668
- },
2669
- items: {
2670
- create: (gameId, slug, itemData) => client["request"](`/games/${gameId}/items`, "POST", {
2671
- body: {
2672
- slug,
2673
- ...itemData
2674
- }
2675
- }),
2676
- update: (gameId, itemId, updates) => client["request"](`/games/${gameId}/items/${itemId}`, "PATCH", {
2677
- body: updates
2678
- }),
2679
- list: (gameId) => client["request"](`/games/${gameId}/items`, "GET"),
2680
- get: (gameId, slug) => {
2681
- const queryParams = new URLSearchParams({ slug, gameId });
2682
- return client["request"](`/items/resolve?${queryParams.toString()}`, "GET");
2683
- },
2684
- delete: (gameId, itemId) => client["request"](`/games/${gameId}/items/${itemId}`, "DELETE"),
2685
- shop: {
2686
- create: (gameId, itemId, listingData) => client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "POST", { body: listingData }),
2687
- get: (gameId, itemId) => client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "GET"),
2688
- update: (gameId, itemId, updates) => client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "PATCH", { body: updates }),
2689
- delete: (gameId, itemId) => client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "DELETE"),
2690
- list: (gameId) => client["request"](`/games/${gameId}/shop-listings`, "GET")
2691
- }
2692
2368
  }
2693
2369
  };
2694
2370
  }
@@ -2734,17 +2410,6 @@ function createGamesNamespace(client) {
2734
2410
  search: (gameId, query) => client["request"](`/games/${gameId}/members/search?q=${encodeURIComponent(query)}`, "GET")
2735
2411
  },
2736
2412
  getSubjects: () => client["request"]("/games/subjects", "GET"),
2737
- startSession: async (gameId) => {
2738
- const idToUse = gameId ?? client["_ensureGameId"]();
2739
- return client["request"](`/games/${idToUse}/sessions`, "POST", {});
2740
- },
2741
- endSession: async (sessionId, gameId) => {
2742
- const effectiveGameIdToEnd = gameId ?? client["_ensureGameId"]();
2743
- if (client["internalClientSessionId"] && sessionId === client["internalClientSessionId"] && effectiveGameIdToEnd === client["gameId"]) {
2744
- client["internalClientSessionId"] = undefined;
2745
- }
2746
- await client["request"](`/games/${effectiveGameIdToEnd}/sessions/${sessionId}/end`, "POST");
2747
- },
2748
2413
  token: {
2749
2414
  create: async (gameId, options) => {
2750
2415
  const res = await client["request"](`/games/${gameId}/token`, "POST");
@@ -2770,83 +2435,6 @@ function createGamesNamespace(client) {
2770
2435
  }
2771
2436
  };
2772
2437
  }
2773
- // src/namespaces/platform/character.ts
2774
- function createCharacterNamespace(client) {
2775
- const componentCache = createTTLCache({
2776
- ttl: 5 * 60 * 1000,
2777
- keyPrefix: "character.components"
2778
- });
2779
- return {
2780
- get: async (userId) => {
2781
- try {
2782
- const path = userId ? `/character/${userId}` : "/character";
2783
- return await client["request"](path, "GET");
2784
- } catch (error) {
2785
- if (error instanceof Error) {
2786
- if (error.message.includes("404")) {
2787
- return null;
2788
- }
2789
- }
2790
- throw error;
2791
- }
2792
- },
2793
- create: async (characterData) => client["request"]("/character", "POST", {
2794
- body: characterData
2795
- }),
2796
- update: async (updates) => client["request"]("/character", "PATCH", { body: updates }),
2797
- components: {
2798
- list: async (options) => {
2799
- const cacheKey = options?.level === undefined ? "all" : String(options.level);
2800
- return componentCache.get(cacheKey, async () => {
2801
- const path = options?.level !== undefined ? `/character/components?level=${options.level}` : "/character/components";
2802
- const components = await client["request"](path, "GET");
2803
- return components || [];
2804
- }, options);
2805
- },
2806
- clearCache: (key) => componentCache.clear(key),
2807
- getCacheKeys: () => componentCache.getKeys()
2808
- },
2809
- accessories: {
2810
- equip: async (slot, componentId) => client["request"]("/character/accessories/equip", "POST", { body: { slot, accessoryComponentId: componentId } }),
2811
- remove: async (slot) => client["request"](`/character/accessories/${slot}`, "DELETE"),
2812
- list: async () => {
2813
- const character2 = await client.character.get();
2814
- return character2?.accessories || [];
2815
- }
2816
- }
2817
- };
2818
- }
2819
- // src/namespaces/platform/achievements.ts
2820
- function createAchievementsNamespace(client) {
2821
- const achievementsListCache = createTTLCache({
2822
- ttl: 5 * 1000,
2823
- keyPrefix: "achievements.list"
2824
- });
2825
- const achievementsHistoryCache = createTTLCache({
2826
- ttl: 5 * 1000,
2827
- keyPrefix: "achievements.history"
2828
- });
2829
- return {
2830
- list: (options) => achievementsListCache.get("current", () => client["request"]("/achievements/current", "GET"), options),
2831
- history: {
2832
- list: async (queryOptions, cacheOptions) => {
2833
- const params = new URLSearchParams;
2834
- if (queryOptions?.limit) {
2835
- params.append("limit", String(queryOptions.limit));
2836
- }
2837
- const qs = params.toString();
2838
- const path = qs ? `/achievements/history?${qs}` : "/achievements/history";
2839
- const cacheKey = qs ? `history-${qs}` : "history";
2840
- return achievementsHistoryCache.get(cacheKey, () => client["request"](path, "GET"), cacheOptions);
2841
- }
2842
- },
2843
- progress: {
2844
- submit: async (achievementId) => client["request"]("/achievements/progress", "POST", {
2845
- body: { achievementId }
2846
- })
2847
- }
2848
- };
2849
- }
2850
2438
  // src/namespaces/platform/leaderboard.ts
2851
2439
  async function fetchInternalLeaderboardEntries(client, options) {
2852
2440
  const params = new URLSearchParams({
@@ -2879,215 +2467,6 @@ function createLeaderboardFetchNamespace(client) {
2879
2467
  fetch: async (options) => fetchPublicLeaderboardEntries(client, options)
2880
2468
  };
2881
2469
  }
2882
- // src/core/cache/cooldown-cache.ts
2883
- function createCooldownCache(defaultCooldownMs) {
2884
- const lastFetchTime = new Map;
2885
- const pendingRequests = new Map;
2886
- const lastResults = new Map;
2887
- async function get(key, loader, config) {
2888
- const now = Date.now();
2889
- const lastFetch = lastFetchTime.get(key) || 0;
2890
- const timeSinceLastFetch = now - lastFetch;
2891
- const effectiveCooldown = config?.cooldown !== undefined ? config.cooldown : defaultCooldownMs;
2892
- const force = config?.force || false;
2893
- const pending = pendingRequests.get(key);
2894
- if (pending) {
2895
- return pending;
2896
- }
2897
- if (!force && timeSinceLastFetch < effectiveCooldown) {
2898
- const cachedResult = lastResults.get(key);
2899
- if (cachedResult !== undefined) {
2900
- return Promise.resolve(cachedResult);
2901
- }
2902
- }
2903
- const promise = loader().then((result) => {
2904
- pendingRequests.delete(key);
2905
- lastFetchTime.set(key, Date.now());
2906
- lastResults.set(key, result);
2907
- return result;
2908
- }).catch((error) => {
2909
- pendingRequests.delete(key);
2910
- throw error;
2911
- });
2912
- pendingRequests.set(key, promise);
2913
- return promise;
2914
- }
2915
- function clear(key) {
2916
- if (key === undefined) {
2917
- lastFetchTime.clear();
2918
- pendingRequests.clear();
2919
- lastResults.clear();
2920
- } else {
2921
- lastFetchTime.delete(key);
2922
- pendingRequests.delete(key);
2923
- lastResults.delete(key);
2924
- }
2925
- }
2926
- return { get, clear };
2927
- }
2928
-
2929
- // src/namespaces/platform/levels.ts
2930
- function createLevelsNamespace(client) {
2931
- const progressCache = createCooldownCache(5000);
2932
- return {
2933
- get: async () => client["request"]("/users/level", "GET"),
2934
- progress: async (options) => progressCache.get("user-progress", () => client["request"]("/users/level/progress", "GET"), options),
2935
- config: {
2936
- list: async () => client["request"]("/levels/config", "GET"),
2937
- get: async (level) => client["request"](`/levels/config/${level}`, "GET")
2938
- }
2939
- };
2940
- }
2941
- // src/namespaces/platform/shop.ts
2942
- function createShopNamespace(client) {
2943
- return {
2944
- view: () => client["request"]("/shop/view", "GET")
2945
- };
2946
- }
2947
- // src/namespaces/platform/notifications.ts
2948
- function createNotificationsNamespace(client) {
2949
- const notificationsListCache = createTTLCache({
2950
- ttl: 5 * 1000,
2951
- keyPrefix: "notifications.list"
2952
- });
2953
- const notificationStatsCache = createTTLCache({
2954
- ttl: 30 * 1000,
2955
- keyPrefix: "notifications.stats"
2956
- });
2957
- return {
2958
- list: async (queryOptions, cacheOptions) => {
2959
- const params = new URLSearchParams;
2960
- if (queryOptions?.status) {
2961
- params.append("status", queryOptions.status);
2962
- }
2963
- if (queryOptions?.type) {
2964
- params.append("type", queryOptions.type);
2965
- }
2966
- if (queryOptions?.limit) {
2967
- params.append("limit", String(queryOptions.limit));
2968
- }
2969
- if (queryOptions?.offset) {
2970
- params.append("offset", String(queryOptions.offset));
2971
- }
2972
- const qs = params.toString();
2973
- const path = qs ? `/notifications?${qs}` : "/notifications";
2974
- const cacheKey = qs ? `list-${qs}` : "list";
2975
- return notificationsListCache.get(cacheKey, () => client["request"](path, "GET"), cacheOptions);
2976
- },
2977
- markAsSeen: async (notificationId) => {
2978
- const result = await client["request"](`/notifications/${notificationId}`, "PATCH", {
2979
- body: {
2980
- id: notificationId,
2981
- status: "seen"
2982
- }
2983
- });
2984
- notificationsListCache.clear();
2985
- return result;
2986
- },
2987
- markAsClicked: async (notificationId) => {
2988
- const result = await client["request"](`/notifications/${notificationId}`, "PATCH", {
2989
- body: {
2990
- id: notificationId,
2991
- status: "clicked"
2992
- }
2993
- });
2994
- notificationsListCache.clear();
2995
- return result;
2996
- },
2997
- dismiss: async (notificationId) => {
2998
- const result = await client["request"](`/notifications/${notificationId}`, "PATCH", {
2999
- body: {
3000
- id: notificationId,
3001
- status: "dismissed"
3002
- }
3003
- });
3004
- notificationsListCache.clear();
3005
- return result;
3006
- },
3007
- markAsDelivered: async (notificationId, method = "realtime") => {
3008
- const result = await client["request"](`/notifications/${notificationId}`, "PATCH", {
3009
- body: {
3010
- id: notificationId,
3011
- status: "delivered",
3012
- method
3013
- }
3014
- });
3015
- notificationsListCache.clear();
3016
- return result;
3017
- },
3018
- deliverPending: async () => {
3019
- const result = await client["request"]("/notifications/deliver", "POST");
3020
- notificationsListCache.clear();
3021
- return result;
3022
- },
3023
- stats: {
3024
- get: async (queryOptions, cacheOptions) => {
3025
- const user = await client.users.me();
3026
- const params = new URLSearchParams;
3027
- if (queryOptions?.from) {
3028
- params.append("from", queryOptions.from);
3029
- }
3030
- if (queryOptions?.to) {
3031
- params.append("to", queryOptions.to);
3032
- }
3033
- const qs = params.toString();
3034
- const path = qs ? `/notifications/stats/${user.id}?${qs}` : `/notifications/stats/${user.id}`;
3035
- const cacheKey = qs ? `stats-${qs}` : "stats";
3036
- return notificationStatsCache.get(cacheKey, () => client["request"](path, "GET"), cacheOptions);
3037
- }
3038
- }
3039
- };
3040
- }
3041
- // src/namespaces/platform/maps.ts
3042
- function createMapsNamespace(client) {
3043
- const mapDataCache = createTTLCache({
3044
- ttl: 5 * 60 * 1000,
3045
- keyPrefix: "maps.data"
3046
- });
3047
- const mapElementsCache = createTTLCache({
3048
- ttl: 60 * 1000,
3049
- keyPrefix: "maps.elements"
3050
- });
3051
- return {
3052
- get: (identifier, options) => mapDataCache.get(identifier, () => client["request"](`/maps/${identifier}`, "GET"), options),
3053
- elements: (mapId, options) => mapElementsCache.get(mapId, () => client["request"](`/map/elements?mapId=${mapId}`, "GET"), options),
3054
- objects: {
3055
- list: (mapId) => client["request"](`/maps/${mapId}/objects`, "GET"),
3056
- create: (mapId, objectData) => client["request"](`/maps/${mapId}/objects`, "POST", {
3057
- body: objectData
3058
- }),
3059
- delete: (mapId, objectId) => client["request"](`/maps/${mapId}/objects/${objectId}`, "DELETE")
3060
- }
3061
- };
3062
- }
3063
- // src/namespaces/platform/sprites.ts
3064
- function createSpritesNamespace(client) {
3065
- const templateUrlCache = createPermanentCache("sprite-template-urls");
3066
- return {
3067
- templates: {
3068
- get: async (slug) => {
3069
- if (!slug) {
3070
- throw new Error("Sprite template slug is required");
3071
- }
3072
- const templateMeta = await templateUrlCache.get(slug, async () => client["request"](`/sprites/templates/${slug}`, "GET"));
3073
- if (!templateMeta.url) {
3074
- throw new Error(`Template ${slug} has no URL in database`);
3075
- }
3076
- const response = await fetch(templateMeta.url);
3077
- if (!response.ok) {
3078
- throw new Error(`Failed to fetch template JSON from ${templateMeta.url}: ${response.statusText}`);
3079
- }
3080
- return await response.json();
3081
- }
3082
- }
3083
- };
3084
- }
3085
- // src/namespaces/platform/telemetry.ts
3086
- function createTelemetryNamespace(client) {
3087
- return {
3088
- pushMetrics: (metrics) => client["request"](`/telemetry/metrics`, "POST", { body: metrics })
3089
- };
3090
- }
3091
2470
  // src/namespaces/platform/scores.ts
3092
2471
  function createScoresNamespace2(client) {
3093
2472
  return {
@@ -3157,7 +2536,10 @@ function createTimebackNamespace2(client) {
3157
2536
  };
3158
2537
  },
3159
2538
  populateStudent: async (names) => client["request"]("/timeback/populate-student", "POST", names ? { body: names } : undefined),
3160
- startActivity: (_metadata) => {
2539
+ get currentRunId() {
2540
+ throw new Error(NOT_SUPPORTED);
2541
+ },
2542
+ startActivity: (_metadata, _options) => {
3161
2543
  throw new Error(NOT_SUPPORTED);
3162
2544
  },
3163
2545
  pauseActivity: () => {
@@ -3180,50 +2562,6 @@ function createTimebackNamespace2(client) {
3180
2562
  getIntegrationConfig: (gameId, courseId) => client["request"](`/timeback/integrations/${gameId}/${courseId}`, "GET"),
3181
2563
  getConfig: (gameId) => client["request"](`/timeback/config/${gameId}`, "GET")
3182
2564
  },
3183
- xp: {
3184
- today: async (options) => {
3185
- const params = new URLSearchParams;
3186
- if (options?.date) {
3187
- params.set("date", options.date);
3188
- }
3189
- if (options?.timezone) {
3190
- params.set("tz", options.timezone);
3191
- }
3192
- const query = params.toString();
3193
- const endpoint = query ? `/timeback/xp/today?${query}` : "/timeback/xp/today";
3194
- return client["request"](endpoint, "GET");
3195
- },
3196
- total: async () => client["request"]("/timeback/xp/total", "GET"),
3197
- history: async (options) => {
3198
- const params = new URLSearchParams;
3199
- if (options?.startDate) {
3200
- params.set("startDate", options.startDate);
3201
- }
3202
- if (options?.endDate) {
3203
- params.set("endDate", options.endDate);
3204
- }
3205
- const query = params.toString();
3206
- const endpoint = query ? `/timeback/xp/history?${query}` : "/timeback/xp/history";
3207
- return client["request"](endpoint, "GET");
3208
- },
3209
- summary: async (options) => {
3210
- const [today, total] = await Promise.all([
3211
- client["request"]((() => {
3212
- const params = new URLSearchParams;
3213
- if (options?.date) {
3214
- params.set("date", options.date);
3215
- }
3216
- if (options?.timezone) {
3217
- params.set("tz", options.timezone);
3218
- }
3219
- const query = params.toString();
3220
- return query ? `/timeback/xp/today?${query}` : "/timeback/xp/today";
3221
- })(), "GET"),
3222
- client["request"]("/timeback/xp/total", "GET")
3223
- ]);
3224
- return { today, total };
3225
- }
3226
- },
3227
2565
  students: {
3228
2566
  get: async (timebackId, options) => studentCache.get(timebackId, async () => client["request"](`/timeback/user/${timebackId}`, "GET"), options),
3229
2567
  clearCache: (timebackId) => {
@@ -3376,231 +2714,6 @@ function createAuthStrategy(token, tokenType) {
3376
2714
  return new GameJwtAuth(token);
3377
2715
  }
3378
2716
 
3379
- // src/core/connection/monitor.ts
3380
- class ConnectionMonitor {
3381
- state = "online";
3382
- callbacks = new Set;
3383
- heartbeatInterval;
3384
- consecutiveFailures = 0;
3385
- isMonitoring = false;
3386
- config;
3387
- constructor(config) {
3388
- this.config = {
3389
- baseUrl: config.baseUrl,
3390
- heartbeatInterval: config.heartbeatInterval ?? 1e4,
3391
- heartbeatTimeout: config.heartbeatTimeout ?? 5000,
3392
- failureThreshold: config.failureThreshold ?? 2,
3393
- enableHeartbeat: config.enableHeartbeat ?? true,
3394
- enableOfflineEvents: config.enableOfflineEvents ?? true
3395
- };
3396
- this._detectInitialState();
3397
- }
3398
- start() {
3399
- if (this.isMonitoring) {
3400
- return;
3401
- }
3402
- this.isMonitoring = true;
3403
- if (this.config.enableOfflineEvents && typeof globalThis.window !== "undefined") {
3404
- globalThis.addEventListener("online", this._handleOnline);
3405
- globalThis.addEventListener("offline", this._handleOffline);
3406
- }
3407
- if (this.config.enableHeartbeat) {
3408
- this._startHeartbeat();
3409
- }
3410
- }
3411
- stop() {
3412
- if (!this.isMonitoring) {
3413
- return;
3414
- }
3415
- this.isMonitoring = false;
3416
- if (typeof globalThis.window !== "undefined") {
3417
- globalThis.removeEventListener("online", this._handleOnline);
3418
- globalThis.removeEventListener("offline", this._handleOffline);
3419
- }
3420
- if (this.heartbeatInterval) {
3421
- clearInterval(this.heartbeatInterval);
3422
- this.heartbeatInterval = undefined;
3423
- }
3424
- }
3425
- onChange(callback) {
3426
- this.callbacks.add(callback);
3427
- return () => this.callbacks.delete(callback);
3428
- }
3429
- getState() {
3430
- return this.state;
3431
- }
3432
- async checkNow() {
3433
- await this._performHeartbeat();
3434
- return this.state;
3435
- }
3436
- reportRequestFailure(error) {
3437
- const isNetworkError = error instanceof TypeError || error instanceof Error && error.message.includes("fetch");
3438
- if (!isNetworkError) {
3439
- return;
3440
- }
3441
- this.consecutiveFailures++;
3442
- if (this.consecutiveFailures >= this.config.failureThreshold) {
3443
- this._setState("degraded", "Multiple consecutive request failures");
3444
- }
3445
- }
3446
- reportRequestSuccess() {
3447
- if (this.consecutiveFailures > 0) {
3448
- this.consecutiveFailures = 0;
3449
- if (this.state === "degraded") {
3450
- this._setState("online", "Requests succeeding again");
3451
- }
3452
- }
3453
- }
3454
- _detectInitialState() {
3455
- if (typeof navigator !== "undefined" && !navigator.onLine) {
3456
- this.state = "offline";
3457
- }
3458
- }
3459
- _handleOnline = () => {
3460
- this.consecutiveFailures = 0;
3461
- this._setState("online", "Browser online event");
3462
- };
3463
- _handleOffline = () => {
3464
- this._setState("offline", "Browser offline event");
3465
- };
3466
- _startHeartbeat() {
3467
- this._performHeartbeat();
3468
- this.heartbeatInterval = setInterval(() => {
3469
- this._performHeartbeat();
3470
- }, this.config.heartbeatInterval);
3471
- }
3472
- async _performHeartbeat() {
3473
- if (typeof navigator !== "undefined" && !navigator.onLine) {
3474
- return;
3475
- }
3476
- try {
3477
- const controller = new AbortController;
3478
- const timeoutId = setTimeout(() => controller.abort(), this.config.heartbeatTimeout);
3479
- const response = await fetch(`${this.config.baseUrl}/ping`, {
3480
- method: "GET",
3481
- signal: controller.signal,
3482
- cache: "no-store"
3483
- });
3484
- clearTimeout(timeoutId);
3485
- if (response.ok) {
3486
- this.consecutiveFailures = 0;
3487
- if (this.state !== "online") {
3488
- this._setState("online", "Heartbeat successful");
3489
- }
3490
- } else {
3491
- this._handleHeartbeatFailure("Heartbeat returned non-OK status");
3492
- }
3493
- } catch (error) {
3494
- this._handleHeartbeatFailure(error instanceof Error ? error.message : "Heartbeat failed");
3495
- }
3496
- }
3497
- _handleHeartbeatFailure(reason) {
3498
- this.consecutiveFailures++;
3499
- if (this.consecutiveFailures >= this.config.failureThreshold) {
3500
- if (typeof navigator !== "undefined" && !navigator.onLine) {
3501
- this._setState("offline", reason);
3502
- } else {
3503
- this._setState("degraded", reason);
3504
- }
3505
- }
3506
- }
3507
- _setState(newState, reason) {
3508
- if (this.state === newState) {
3509
- return;
3510
- }
3511
- const oldState = this.state;
3512
- this.state = newState;
3513
- console.debug(`[ConnectionMonitor] ${oldState} → ${newState}: ${reason}`);
3514
- this.callbacks.forEach((callback) => {
3515
- try {
3516
- callback(newState, reason);
3517
- } catch (error) {
3518
- console.error("[ConnectionMonitor] Error in callback:", error);
3519
- }
3520
- });
3521
- }
3522
- }
3523
- // src/core/connection/utils.ts
3524
- function createDisplayAlert(authContext) {
3525
- return (message, options) => {
3526
- if (authContext?.isInIframe && typeof globalThis.window !== "undefined" && globalThis.window.parent !== globalThis.window) {
3527
- window.parent.postMessage({
3528
- type: "PLAYCADEMY_DISPLAY_ALERT",
3529
- message,
3530
- options
3531
- }, "*");
3532
- } else {
3533
- const prefixMap = { error: "❌", warning: "⚠️", info: "ℹ️" };
3534
- const prefix = (options?.type && prefixMap[options.type]) ?? "ℹ️";
3535
- console.log(`${prefix} ${message}`);
3536
- }
3537
- };
3538
- }
3539
-
3540
- // src/core/connection/manager.ts
3541
- class ConnectionManager {
3542
- monitor;
3543
- authContext;
3544
- disconnectHandler;
3545
- connectionChangeCallback;
3546
- currentState = "online";
3547
- additionalDisconnectHandlers = new Set;
3548
- constructor(config) {
3549
- this.authContext = config.authContext;
3550
- this.disconnectHandler = config.onDisconnect;
3551
- this.connectionChangeCallback = config.onConnectionChange;
3552
- if (config.authContext?.isInIframe) {
3553
- this._setupPlatformListener();
3554
- }
3555
- }
3556
- getState() {
3557
- return this.monitor?.getState() ?? this.currentState;
3558
- }
3559
- async checkNow() {
3560
- if (!this.monitor) {
3561
- return this.currentState;
3562
- }
3563
- return await this.monitor.checkNow();
3564
- }
3565
- reportRequestSuccess() {
3566
- this.monitor?.reportRequestSuccess();
3567
- }
3568
- reportRequestFailure(error) {
3569
- this.monitor?.reportRequestFailure(error);
3570
- }
3571
- onDisconnect(callback) {
3572
- this.additionalDisconnectHandlers.add(callback);
3573
- return () => {
3574
- this.additionalDisconnectHandlers.delete(callback);
3575
- };
3576
- }
3577
- stop() {
3578
- this.monitor?.stop();
3579
- }
3580
- _setupPlatformListener() {
3581
- messaging.listen("PLAYCADEMY_CONNECTION_STATE" /* CONNECTION_STATE */, ({ state, reason }) => {
3582
- this.currentState = state;
3583
- this._handleConnectionChange(state, reason);
3584
- });
3585
- }
3586
- _handleConnectionChange(state, reason) {
3587
- this.connectionChangeCallback?.(state, reason);
3588
- if (state === "offline" || state === "degraded") {
3589
- const context = {
3590
- state,
3591
- reason,
3592
- timestamp: Date.now(),
3593
- displayAlert: createDisplayAlert(this.authContext)
3594
- };
3595
- if (this.disconnectHandler) {
3596
- this.disconnectHandler(context);
3597
- }
3598
- this.additionalDisconnectHandlers.forEach((handler) => {
3599
- handler(context);
3600
- });
3601
- }
3602
- }
3603
- }
3604
2717
  // src/core/transport/retry.ts
3605
2718
  var RETRY_DELAYS_MS = [500, 1500];
3606
2719
  function wait(ms) {
@@ -3750,15 +2863,9 @@ class PlaycademyBaseClient {
3750
2863
  gameId;
3751
2864
  config;
3752
2865
  listeners = {};
3753
- internalClientSessionId;
3754
2866
  authContext;
3755
2867
  initPayload;
3756
- connectionManager;
3757
2868
  launchId;
3758
- _sessionManager = {
3759
- startSession: async (gameId) => this.request(`/games/${gameId}/sessions`, "POST"),
3760
- endSession: async (sessionId, gameId) => this.request(`/games/${gameId}/sessions/${sessionId}`, "DELETE")
3761
- };
3762
2869
  constructor(config) {
3763
2870
  this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
3764
2871
  this.gameUrl = config?.gameUrl;
@@ -3768,8 +2875,6 @@ class PlaycademyBaseClient {
3768
2875
  this.config = config || {};
3769
2876
  this.authStrategy = createAuthStrategy(config?.token ?? null, config?.tokenType);
3770
2877
  this._detectAuthContext();
3771
- this._initializeInternalSession().catch(() => {});
3772
- this._initializeConnectionMonitor();
3773
2878
  }
3774
2879
  getBaseUrl() {
3775
2880
  const isRelative = this.baseUrl.startsWith("/");
@@ -3807,21 +2912,6 @@ class PlaycademyBaseClient {
3807
2912
  onAuthChange(callback) {
3808
2913
  this.on("authChange", (payload) => callback(payload.token));
3809
2914
  }
3810
- onDisconnect(callback) {
3811
- if (!this.connectionManager) {
3812
- return () => {};
3813
- }
3814
- return this.connectionManager.onDisconnect(callback);
3815
- }
3816
- getConnectionState() {
3817
- return this.connectionManager?.getState() ?? "unknown";
3818
- }
3819
- async checkConnection() {
3820
- if (!this.connectionManager) {
3821
- return "unknown";
3822
- }
3823
- return await this.connectionManager.checkNow();
3824
- }
3825
2915
  _setAuthContext(context) {
3826
2916
  this.authContext = context;
3827
2917
  }
@@ -3851,44 +2941,30 @@ class PlaycademyBaseClient {
3851
2941
  ...this.authStrategy.getHeaders(),
3852
2942
  ...this.launchId ? { "x-playcademy-launch-id": this.launchId } : {}
3853
2943
  };
3854
- try {
3855
- const result = await request({
3856
- path,
3857
- method,
3858
- body: options?.body,
3859
- baseUrl: this.baseUrl,
3860
- extraHeaders: effectiveHeaders,
3861
- raw: options?.raw,
3862
- retryPolicy: options?.retryPolicy
3863
- });
3864
- this.connectionManager?.reportRequestSuccess();
3865
- return result;
3866
- } catch (error) {
3867
- this.connectionManager?.reportRequestFailure(error);
3868
- throw error;
3869
- }
2944
+ return request({
2945
+ path,
2946
+ method,
2947
+ body: options?.body,
2948
+ baseUrl: this.baseUrl,
2949
+ extraHeaders: effectiveHeaders,
2950
+ raw: options?.raw,
2951
+ retryPolicy: options?.retryPolicy
2952
+ });
3870
2953
  }
3871
2954
  async requestGameBackend(path, method, body, headers, options) {
3872
2955
  const effectiveHeaders = {
3873
2956
  ...headers,
3874
2957
  ...this.authStrategy.getHeaders()
3875
2958
  };
3876
- try {
3877
- const result = await request({
3878
- path,
3879
- method,
3880
- body,
3881
- baseUrl: this.getGameBackendUrl(),
3882
- extraHeaders: effectiveHeaders,
3883
- raw: options?.raw,
3884
- retryPolicy: options?.retryPolicy
3885
- });
3886
- this.connectionManager?.reportRequestSuccess();
3887
- return result;
3888
- } catch (error) {
3889
- this.connectionManager?.reportRequestFailure(error);
3890
- throw error;
3891
- }
2959
+ return request({
2960
+ path,
2961
+ method,
2962
+ body,
2963
+ baseUrl: this.getGameBackendUrl(),
2964
+ extraHeaders: effectiveHeaders,
2965
+ raw: options?.raw,
2966
+ retryPolicy: options?.retryPolicy
2967
+ });
3892
2968
  }
3893
2969
  _ensureGameId() {
3894
2970
  if (!this.gameId) {
@@ -3899,49 +2975,6 @@ class PlaycademyBaseClient {
3899
2975
  _detectAuthContext() {
3900
2976
  this.authContext = { isInIframe: isInIframe() };
3901
2977
  }
3902
- _initializeConnectionMonitor() {
3903
- if (typeof globalThis.window === "undefined") {
3904
- return;
3905
- }
3906
- const isEnabled = this.config.enableConnectionMonitoring ?? true;
3907
- if (!isEnabled) {
3908
- return;
3909
- }
3910
- try {
3911
- this.connectionManager = new ConnectionManager({
3912
- baseUrl: this.baseUrl,
3913
- authContext: this.authContext,
3914
- onDisconnect: this.config.onDisconnect,
3915
- onConnectionChange: (state, reason) => {
3916
- this.emit("connectionChange", { state, reason });
3917
- }
3918
- });
3919
- } catch (error) {
3920
- log.error("[Playcademy SDK] Failed to initialize connection manager:", { error });
3921
- }
3922
- }
3923
- async _initializeInternalSession() {
3924
- if (!this.gameId || this.internalClientSessionId) {
3925
- return;
3926
- }
3927
- const shouldAutoStart = this.config.autoStartSession ?? true;
3928
- if (!shouldAutoStart) {
3929
- return;
3930
- }
3931
- try {
3932
- const response = await this._sessionManager.startSession(this.gameId);
3933
- this.internalClientSessionId = response.sessionId;
3934
- log.debug("[Playcademy SDK] Auto-started game session", {
3935
- gameId: this.gameId,
3936
- sessionId: this.internalClientSessionId
3937
- });
3938
- } catch (error) {
3939
- log.error("[Playcademy SDK] Auto-starting session failed for game", {
3940
- gameId: this.gameId,
3941
- error
3942
- });
3943
- }
3944
- }
3945
2978
  users = createUsersNamespace(this);
3946
2979
  }
3947
2980
 
@@ -3949,22 +2982,12 @@ class PlaycademyBaseClient {
3949
2982
  class PlaycademyInternalClient extends PlaycademyBaseClient {
3950
2983
  identity = createIdentityNamespace(this);
3951
2984
  runtime = createRuntimeNamespace(this);
3952
- credits = createCreditsNamespace(this);
3953
- realtime = createRealtimeNamespace(this);
3954
2985
  backend = createBackendNamespace(this);
3955
2986
  auth = createAuthNamespace(this);
3956
2987
  admin = createAdminNamespace(this);
3957
2988
  dev = createDevNamespace(this);
3958
2989
  games = createGamesNamespace(this);
3959
- character = createCharacterNamespace(this);
3960
- achievements = createAchievementsNamespace(this);
3961
2990
  leaderboard = createLeaderboardNamespace(this);
3962
- levels = createLevelsNamespace(this);
3963
- shop = createShopNamespace(this);
3964
- notifications = createNotificationsNamespace(this);
3965
- maps = createMapsNamespace(this);
3966
- sprites = createSpritesNamespace(this);
3967
- telemetry = createTelemetryNamespace(this);
3968
2991
  scores = createScoresNamespace2(this);
3969
2992
  timeback = createTimebackNamespace2(this);
3970
2993
  static init = init;
@@ -3978,7 +3001,5 @@ export {
3978
3001
  PlaycademyError,
3979
3002
  PlaycademyInternalClient as PlaycademyClient,
3980
3003
  MessageEvents,
3981
- ConnectionMonitor,
3982
- ConnectionManager,
3983
3004
  ApiError
3984
3005
  };