@playcademy/sdk 0.6.1-beta.3 → 0.6.1-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +31 -16
- package/dist/index.js +223 -150
- package/dist/internal.d.ts +172 -504
- package/dist/internal.js +223 -150
- package/dist/server/edge.d.ts +4 -4
- package/dist/server.d.ts +4 -4
- package/dist/types.d.ts +339 -324
- package/package.json +6 -1
package/dist/index.d.ts
CHANGED
|
@@ -476,6 +476,14 @@ interface EndActivityScoreData {
|
|
|
476
476
|
* @module types/timeback/api
|
|
477
477
|
*/
|
|
478
478
|
|
|
479
|
+
type TimebackPromotionStatus = 'promoted' | 'no-next-course' | 'already-promoted' | 'not-enrolled' | 'not-mastered';
|
|
480
|
+
interface TimebackPromotionResult {
|
|
481
|
+
status: TimebackPromotionStatus;
|
|
482
|
+
currentCourseId: string;
|
|
483
|
+
nextCourseId?: string;
|
|
484
|
+
masteredUnits?: number;
|
|
485
|
+
masterableUnits?: number;
|
|
486
|
+
}
|
|
479
487
|
interface EndActivityResponse {
|
|
480
488
|
status: 'ok';
|
|
481
489
|
courseId: string;
|
|
@@ -485,6 +493,10 @@ interface EndActivityResponse {
|
|
|
485
493
|
scoreStatus?: string;
|
|
486
494
|
inProgress?: string;
|
|
487
495
|
}
|
|
496
|
+
interface AdvanceCourseResponse {
|
|
497
|
+
status: 'ok';
|
|
498
|
+
promotion: TimebackPromotionResult;
|
|
499
|
+
}
|
|
488
500
|
|
|
489
501
|
/**
|
|
490
502
|
* User Types
|
|
@@ -775,7 +787,7 @@ declare const items: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
775
787
|
generated: undefined;
|
|
776
788
|
}, {}, {}>;
|
|
777
789
|
};
|
|
778
|
-
dialect:
|
|
790
|
+
dialect: 'pg';
|
|
779
791
|
}>;
|
|
780
792
|
declare const inventoryItems: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
781
793
|
name: "inventory_items";
|
|
@@ -867,7 +879,7 @@ declare const inventoryItems: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
867
879
|
generated: undefined;
|
|
868
880
|
}, {}, {}>;
|
|
869
881
|
};
|
|
870
|
-
dialect:
|
|
882
|
+
dialect: 'pg';
|
|
871
883
|
}>;
|
|
872
884
|
|
|
873
885
|
type ItemRow = typeof items.$inferSelect;
|
|
@@ -1748,8 +1760,8 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
|
|
|
1748
1760
|
*/
|
|
1749
1761
|
runtime: {
|
|
1750
1762
|
getGameToken: (gameId: string, options?: {
|
|
1751
|
-
apply?: boolean
|
|
1752
|
-
}
|
|
1763
|
+
apply?: boolean;
|
|
1764
|
+
}) => Promise<GameTokenResponse>;
|
|
1753
1765
|
exit: () => Promise<void>;
|
|
1754
1766
|
onInit: (handler: (context: GameContextPayload) => void) => void;
|
|
1755
1767
|
onTokenRefresh: (handler: (data: {
|
|
@@ -1773,7 +1785,7 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
|
|
|
1773
1785
|
getListenerCounts: () => Record<string, number>;
|
|
1774
1786
|
assets: {
|
|
1775
1787
|
url(pathOrStrings: string | TemplateStringsArray, ...values: unknown[]): string;
|
|
1776
|
-
fetch: (path: string, options?: RequestInit
|
|
1788
|
+
fetch: (path: string, options?: RequestInit) => Promise<Response>;
|
|
1777
1789
|
json: <T = unknown>(path: string) => Promise<T>;
|
|
1778
1790
|
blob: (path: string) => Promise<Blob>;
|
|
1779
1791
|
text: (path: string) => Promise<string>;
|
|
@@ -1798,10 +1810,13 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
|
|
|
1798
1810
|
*/
|
|
1799
1811
|
timeback: {
|
|
1800
1812
|
readonly user: TimebackUser;
|
|
1801
|
-
startActivity: (metadata: ActivityData, options?: StartActivityOptions
|
|
1813
|
+
startActivity: (metadata: ActivityData, options?: StartActivityOptions) => void;
|
|
1802
1814
|
pauseActivity: () => void;
|
|
1803
1815
|
resumeActivity: () => void;
|
|
1804
1816
|
endActivity: (data: EndActivityScoreData) => Promise<EndActivityResponse>;
|
|
1817
|
+
advanceCourse: (options?: {
|
|
1818
|
+
subject?: TimebackSubject;
|
|
1819
|
+
}) => Promise<AdvanceCourseResponse>;
|
|
1805
1820
|
};
|
|
1806
1821
|
/**
|
|
1807
1822
|
* Playcademy Credits (platform currency) management.
|
|
@@ -1818,14 +1833,14 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
|
|
|
1818
1833
|
* - `submit(score, metadata?)` - Record a game score
|
|
1819
1834
|
*/
|
|
1820
1835
|
scores: {
|
|
1821
|
-
submit: (score: number, metadata?: Record<string, unknown>
|
|
1836
|
+
submit: (score: number, metadata?: Record<string, unknown>) => Promise<ScoreSubmission>;
|
|
1822
1837
|
};
|
|
1823
1838
|
/**
|
|
1824
1839
|
* Read-only leaderboard access for the current game scope.
|
|
1825
1840
|
* - `fetch(options?)` - Fetch leaderboard entries
|
|
1826
1841
|
*/
|
|
1827
1842
|
leaderboard: {
|
|
1828
|
-
fetch: (options?: LeaderboardOptions
|
|
1843
|
+
fetch: (options?: LeaderboardOptions) => Promise<GameLeaderboardEntry[]>;
|
|
1829
1844
|
};
|
|
1830
1845
|
/**
|
|
1831
1846
|
* Demo-mode helpers. Methods throw when called outside `client.mode === 'demo'`,
|
|
@@ -1839,7 +1854,7 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
|
|
|
1839
1854
|
get: () => Promise<DemoProfile>;
|
|
1840
1855
|
update: (updates: DemoProfileUpdate) => Promise<DemoProfile>;
|
|
1841
1856
|
};
|
|
1842
|
-
end: (score: number, options?: DemoEndOptions
|
|
1857
|
+
end: (score: number, options?: DemoEndOptions) => void;
|
|
1843
1858
|
};
|
|
1844
1859
|
/**
|
|
1845
1860
|
* Realtime multiplayer authentication.
|
|
@@ -1856,13 +1871,13 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
|
|
|
1856
1871
|
* - Routes are relative to your game's deployment (e.g., '/hello' → your-game.playcademy.gg/api/hello)
|
|
1857
1872
|
*/
|
|
1858
1873
|
backend: {
|
|
1859
|
-
get<T = unknown>(path: string, headers?: Record<string, string>
|
|
1860
|
-
post<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>
|
|
1861
|
-
put<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>
|
|
1862
|
-
patch<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>
|
|
1863
|
-
delete<T = unknown>(path: string, headers?: Record<string, string>
|
|
1864
|
-
request<T = unknown>(path: string, method: Method, body?: unknown, headers?: Record<string, string>
|
|
1865
|
-
download(path: string, method?: Method, body?: unknown, headers?: Record<string, string>
|
|
1874
|
+
get<T = unknown>(path: string, headers?: Record<string, string>): Promise<T>;
|
|
1875
|
+
post<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
1876
|
+
put<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
1877
|
+
patch<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
1878
|
+
delete<T = unknown>(path: string, headers?: Record<string, string>): Promise<T>;
|
|
1879
|
+
request<T = unknown>(path: string, method: Method, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
1880
|
+
download(path: string, method?: Method, body?: unknown, headers?: Record<string, string>): Promise<Response>;
|
|
1866
1881
|
url(pathOrStrings: string | TemplateStringsArray, ...values: unknown[]): string;
|
|
1867
1882
|
};
|
|
1868
1883
|
/** Auto-initializes a PlaycademyClient with context from the environment */
|
package/dist/index.js
CHANGED
|
@@ -321,7 +321,7 @@ function formatJSONSingleLine(level, message, context, scope) {
|
|
|
321
321
|
...context && Object.keys(context).length > 0 && { context }
|
|
322
322
|
};
|
|
323
323
|
const consoleMethod = getConsoleMethod(level);
|
|
324
|
-
consoleMethod(
|
|
324
|
+
consoleMethod(safeJSONStringify(logEntry));
|
|
325
325
|
}
|
|
326
326
|
function formatJSONPretty(level, message, context, scope) {
|
|
327
327
|
const timestamp = new Date().toISOString();
|
|
@@ -333,7 +333,30 @@ function formatJSONPretty(level, message, context, scope) {
|
|
|
333
333
|
...context && Object.keys(context).length > 0 && { context }
|
|
334
334
|
};
|
|
335
335
|
const consoleMethod = getConsoleMethod(level);
|
|
336
|
-
consoleMethod(
|
|
336
|
+
consoleMethod(safeJSONStringify(logEntry, 2));
|
|
337
|
+
}
|
|
338
|
+
function safeJSONStringify(value, space) {
|
|
339
|
+
const seen = new WeakSet;
|
|
340
|
+
try {
|
|
341
|
+
return JSON.stringify(value, (_key, currentValue) => {
|
|
342
|
+
if (typeof currentValue === "bigint") {
|
|
343
|
+
return currentValue.toString();
|
|
344
|
+
}
|
|
345
|
+
if (typeof currentValue === "object" && currentValue !== null) {
|
|
346
|
+
if (seen.has(currentValue)) {
|
|
347
|
+
return "[Circular]";
|
|
348
|
+
}
|
|
349
|
+
seen.add(currentValue);
|
|
350
|
+
}
|
|
351
|
+
return currentValue;
|
|
352
|
+
}, space);
|
|
353
|
+
} catch {
|
|
354
|
+
return JSON.stringify({
|
|
355
|
+
timestamp: new Date().toISOString(),
|
|
356
|
+
level: "ERROR",
|
|
357
|
+
message: "[Logger] Failed to serialize log entry"
|
|
358
|
+
});
|
|
359
|
+
}
|
|
337
360
|
}
|
|
338
361
|
function getConsoleMethod(level) {
|
|
339
362
|
switch (level) {
|
|
@@ -1005,7 +1028,7 @@ function createAssetsNamespace(client) {
|
|
|
1005
1028
|
fetch: fetchAsset,
|
|
1006
1029
|
json: async (path) => {
|
|
1007
1030
|
const response = await fetchAsset(path);
|
|
1008
|
-
return response.json();
|
|
1031
|
+
return await response.json();
|
|
1009
1032
|
},
|
|
1010
1033
|
blob: async (path) => {
|
|
1011
1034
|
const response = await fetchAsset(path);
|
|
@@ -1297,10 +1320,10 @@ var ACHIEVEMENT_DEFINITIONS = [
|
|
|
1297
1320
|
var TypeScriptPackages = {
|
|
1298
1321
|
tsc: "tsc",
|
|
1299
1322
|
nativePreview: "@typescript/native-preview",
|
|
1300
|
-
|
|
1323
|
+
nativePreviewBeta: "@typescript/native-preview@beta"
|
|
1301
1324
|
};
|
|
1302
1325
|
var TYPESCRIPT_RUNNER = {
|
|
1303
|
-
package: TypeScriptPackages.
|
|
1326
|
+
package: TypeScriptPackages.nativePreviewBeta,
|
|
1304
1327
|
bin: "tsgo"
|
|
1305
1328
|
};
|
|
1306
1329
|
// ../constants/src/overworld.ts
|
|
@@ -1330,7 +1353,8 @@ var BADGES = {
|
|
|
1330
1353
|
var TIMEBACK_ROUTES = {
|
|
1331
1354
|
END_ACTIVITY: "/integrations/timeback/end-activity",
|
|
1332
1355
|
GET_XP: "/integrations/timeback/xp",
|
|
1333
|
-
HEARTBEAT: "/integrations/timeback/heartbeat"
|
|
1356
|
+
HEARTBEAT: "/integrations/timeback/heartbeat",
|
|
1357
|
+
ADVANCE_COURSE: "/integrations/timeback/advance-course"
|
|
1334
1358
|
};
|
|
1335
1359
|
// src/core/cache/singleton-cache.ts
|
|
1336
1360
|
function createSingletonCache() {
|
|
@@ -1441,6 +1465,99 @@ function createRealtimeNamespace(client) {
|
|
|
1441
1465
|
}
|
|
1442
1466
|
};
|
|
1443
1467
|
}
|
|
1468
|
+
// src/core/guards.ts
|
|
1469
|
+
var VALID_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
|
1470
|
+
var VALID_SUBJECTS = [
|
|
1471
|
+
"Reading",
|
|
1472
|
+
"Language",
|
|
1473
|
+
"Vocabulary",
|
|
1474
|
+
"Social Studies",
|
|
1475
|
+
"Writing",
|
|
1476
|
+
"Science",
|
|
1477
|
+
"FastMath",
|
|
1478
|
+
"Math",
|
|
1479
|
+
"None"
|
|
1480
|
+
];
|
|
1481
|
+
function isValidGrade(value) {
|
|
1482
|
+
return typeof value === "number" && Number.isInteger(value) && VALID_GRADES.includes(value);
|
|
1483
|
+
}
|
|
1484
|
+
function isValidSubject(value) {
|
|
1485
|
+
return typeof value === "string" && VALID_SUBJECTS.includes(value);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
// src/core/cache/ttl-cache.ts
|
|
1489
|
+
function createTTLCache(options) {
|
|
1490
|
+
const cache = new Map;
|
|
1491
|
+
const { ttl: defaultTTL, keyPrefix = "", onClear } = options;
|
|
1492
|
+
async function get(key, loader, config) {
|
|
1493
|
+
const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
|
|
1494
|
+
const now = Date.now();
|
|
1495
|
+
const effectiveTTL = config?.ttl !== undefined ? config.ttl : defaultTTL;
|
|
1496
|
+
const force = config?.force || false;
|
|
1497
|
+
const skipCache = config?.skipCache || false;
|
|
1498
|
+
if (effectiveTTL === 0 || skipCache) {
|
|
1499
|
+
return loader();
|
|
1500
|
+
}
|
|
1501
|
+
if (!force) {
|
|
1502
|
+
const cached = cache.get(fullKey);
|
|
1503
|
+
if (cached && cached.expiresAt > now) {
|
|
1504
|
+
return cached.value;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
const promise = loader().catch((error) => {
|
|
1508
|
+
cache.delete(fullKey);
|
|
1509
|
+
throw error;
|
|
1510
|
+
});
|
|
1511
|
+
cache.set(fullKey, {
|
|
1512
|
+
value: promise,
|
|
1513
|
+
expiresAt: now + effectiveTTL
|
|
1514
|
+
});
|
|
1515
|
+
return promise;
|
|
1516
|
+
}
|
|
1517
|
+
function clear(key) {
|
|
1518
|
+
if (key === undefined) {
|
|
1519
|
+
cache.clear();
|
|
1520
|
+
onClear?.();
|
|
1521
|
+
} else {
|
|
1522
|
+
const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
|
|
1523
|
+
cache.delete(fullKey);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
function size() {
|
|
1527
|
+
return cache.size;
|
|
1528
|
+
}
|
|
1529
|
+
function prune() {
|
|
1530
|
+
const now = Date.now();
|
|
1531
|
+
for (const [key, entry] of cache.entries()) {
|
|
1532
|
+
if (entry.expiresAt <= now) {
|
|
1533
|
+
cache.delete(key);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
function getKeys() {
|
|
1538
|
+
const keys = [];
|
|
1539
|
+
const prefixLen = keyPrefix ? keyPrefix.length + 1 : 0;
|
|
1540
|
+
for (const fullKey of cache.keys()) {
|
|
1541
|
+
keys.push(fullKey.substring(prefixLen));
|
|
1542
|
+
}
|
|
1543
|
+
return keys;
|
|
1544
|
+
}
|
|
1545
|
+
function has(key) {
|
|
1546
|
+
const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
|
|
1547
|
+
const cached = cache.get(fullKey);
|
|
1548
|
+
if (!cached) {
|
|
1549
|
+
return false;
|
|
1550
|
+
}
|
|
1551
|
+
const now = Date.now();
|
|
1552
|
+
if (cached.expiresAt <= now) {
|
|
1553
|
+
cache.delete(fullKey);
|
|
1554
|
+
return false;
|
|
1555
|
+
}
|
|
1556
|
+
return true;
|
|
1557
|
+
}
|
|
1558
|
+
return { get, clear, size, prune, getKeys, has };
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1444
1561
|
// ../utils/src/uuid.ts
|
|
1445
1562
|
var UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
1446
1563
|
function isValidUUID(value) {
|
|
@@ -1450,7 +1567,7 @@ function isValidUUID(value) {
|
|
|
1450
1567
|
return UUID_REGEX.test(value);
|
|
1451
1568
|
}
|
|
1452
1569
|
|
|
1453
|
-
// src/core/activity-tracker.ts
|
|
1570
|
+
// src/core/timeback/activity-tracker.ts
|
|
1454
1571
|
var DEFAULT_PAUSED_HEARTBEAT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
1455
1572
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 15000;
|
|
1456
1573
|
var DEFAULT_INACTIVITY_TIMEOUT_MS = 10 * 60 * 1000;
|
|
@@ -1923,102 +2040,29 @@ function createTimebackActivityTracker(client) {
|
|
|
1923
2040
|
};
|
|
1924
2041
|
}
|
|
1925
2042
|
|
|
1926
|
-
// src/core/
|
|
1927
|
-
function
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
return cached.value;
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
const promise = loader().catch((error) => {
|
|
1946
|
-
cache.delete(fullKey);
|
|
1947
|
-
throw error;
|
|
1948
|
-
});
|
|
1949
|
-
cache.set(fullKey, {
|
|
1950
|
-
value: promise,
|
|
1951
|
-
expiresAt: now + effectiveTTL
|
|
1952
|
-
});
|
|
1953
|
-
return promise;
|
|
1954
|
-
}
|
|
1955
|
-
function clear(key) {
|
|
1956
|
-
if (key === undefined) {
|
|
1957
|
-
cache.clear();
|
|
1958
|
-
onClear?.();
|
|
1959
|
-
} else {
|
|
1960
|
-
const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
|
|
1961
|
-
cache.delete(fullKey);
|
|
1962
|
-
}
|
|
1963
|
-
}
|
|
1964
|
-
function size() {
|
|
1965
|
-
return cache.size;
|
|
1966
|
-
}
|
|
1967
|
-
function prune() {
|
|
1968
|
-
const now = Date.now();
|
|
1969
|
-
for (const [key, entry] of cache.entries()) {
|
|
1970
|
-
if (entry.expiresAt <= now) {
|
|
1971
|
-
cache.delete(key);
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
function getKeys() {
|
|
1976
|
-
const keys = [];
|
|
1977
|
-
const prefixLen = keyPrefix ? keyPrefix.length + 1 : 0;
|
|
1978
|
-
for (const fullKey of cache.keys()) {
|
|
1979
|
-
keys.push(fullKey.substring(prefixLen));
|
|
1980
|
-
}
|
|
1981
|
-
return keys;
|
|
1982
|
-
}
|
|
1983
|
-
function has(key) {
|
|
1984
|
-
const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
|
|
1985
|
-
const cached = cache.get(fullKey);
|
|
1986
|
-
if (!cached) {
|
|
1987
|
-
return false;
|
|
1988
|
-
}
|
|
1989
|
-
const now = Date.now();
|
|
1990
|
-
if (cached.expiresAt <= now) {
|
|
1991
|
-
cache.delete(fullKey);
|
|
1992
|
-
return false;
|
|
2043
|
+
// src/core/timeback/user.ts
|
|
2044
|
+
function createTimebackUserStore(client) {
|
|
2045
|
+
let override;
|
|
2046
|
+
return {
|
|
2047
|
+
snapshot() {
|
|
2048
|
+
return override ?? client["initPayload"]?.timeback;
|
|
2049
|
+
},
|
|
2050
|
+
async refresh() {
|
|
2051
|
+
const response = await client["request"]("/timeback/user", "GET");
|
|
2052
|
+
override = response;
|
|
2053
|
+
return {
|
|
2054
|
+
id: response.id,
|
|
2055
|
+
role: response.role,
|
|
2056
|
+
enrollments: response.enrollments,
|
|
2057
|
+
organizations: response.organizations
|
|
2058
|
+
};
|
|
1993
2059
|
}
|
|
1994
|
-
|
|
1995
|
-
}
|
|
1996
|
-
return { get, clear, size, prune, getKeys, has };
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
// src/core/guards.ts
|
|
2000
|
-
var VALID_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
|
2001
|
-
var VALID_SUBJECTS = [
|
|
2002
|
-
"Reading",
|
|
2003
|
-
"Language",
|
|
2004
|
-
"Vocabulary",
|
|
2005
|
-
"Social Studies",
|
|
2006
|
-
"Writing",
|
|
2007
|
-
"Science",
|
|
2008
|
-
"FastMath",
|
|
2009
|
-
"Math",
|
|
2010
|
-
"None"
|
|
2011
|
-
];
|
|
2012
|
-
function isValidGrade(value) {
|
|
2013
|
-
return typeof value === "number" && Number.isInteger(value) && VALID_GRADES.includes(value);
|
|
2014
|
-
}
|
|
2015
|
-
function isValidSubject(value) {
|
|
2016
|
-
return typeof value === "string" && VALID_SUBJECTS.includes(value);
|
|
2060
|
+
};
|
|
2017
2061
|
}
|
|
2018
2062
|
|
|
2019
|
-
// src/
|
|
2020
|
-
function
|
|
2021
|
-
const
|
|
2063
|
+
// src/core/timeback/engine.ts
|
|
2064
|
+
function createTimebackEngine(client) {
|
|
2065
|
+
const userStore = createTimebackUserStore(client);
|
|
2022
2066
|
const userCache = createTTLCache({
|
|
2023
2067
|
ttl: 5 * 60 * 1000,
|
|
2024
2068
|
keyPrefix: "game.timeback.user"
|
|
@@ -2027,42 +2071,84 @@ function createTimebackNamespace(client) {
|
|
|
2027
2071
|
ttl: 5000,
|
|
2028
2072
|
keyPrefix: "game.timeback.xp"
|
|
2029
2073
|
});
|
|
2030
|
-
function
|
|
2031
|
-
|
|
2074
|
+
async function applyPromotion(promotion) {
|
|
2075
|
+
if (promotion.status !== "promoted" && promotion.status !== "already-promoted") {
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
userCache.clear("current");
|
|
2079
|
+
try {
|
|
2080
|
+
await userStore.refresh();
|
|
2081
|
+
} catch {}
|
|
2032
2082
|
}
|
|
2083
|
+
const activityTracker = createTimebackActivityTracker(client);
|
|
2084
|
+
return {
|
|
2085
|
+
user: {
|
|
2086
|
+
snapshot: () => userStore.snapshot(),
|
|
2087
|
+
fetch: (options) => userCache.get("current", () => userStore.refresh(), options),
|
|
2088
|
+
xp: {
|
|
2089
|
+
fetch: (options) => {
|
|
2090
|
+
const cacheKey = [
|
|
2091
|
+
options.grade ?? "",
|
|
2092
|
+
options.subject ?? "",
|
|
2093
|
+
options.include?.toSorted().join(",") ?? ""
|
|
2094
|
+
].join(":");
|
|
2095
|
+
return xpCache.get(cacheKey, async () => {
|
|
2096
|
+
const params = new URLSearchParams;
|
|
2097
|
+
if (options.grade !== undefined) {
|
|
2098
|
+
params.set("grade", String(options.grade));
|
|
2099
|
+
}
|
|
2100
|
+
if (options.subject !== undefined) {
|
|
2101
|
+
params.set("subject", options.subject);
|
|
2102
|
+
}
|
|
2103
|
+
if (options.include?.length) {
|
|
2104
|
+
params.set("include", options.include.join(","));
|
|
2105
|
+
}
|
|
2106
|
+
const queryString = params.toString();
|
|
2107
|
+
const endpoint = `${TIMEBACK_ROUTES.GET_XP}${queryString ? `?${queryString}` : ""}`;
|
|
2108
|
+
return client["requestGameBackend"](endpoint, "GET");
|
|
2109
|
+
}, { force: options.force });
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
},
|
|
2113
|
+
activity: {
|
|
2114
|
+
start: activityTracker.startActivity,
|
|
2115
|
+
pause: activityTracker.pauseActivity,
|
|
2116
|
+
resume: activityTracker.resumeActivity,
|
|
2117
|
+
end: activityTracker.endActivity
|
|
2118
|
+
},
|
|
2119
|
+
async advanceCourse(params) {
|
|
2120
|
+
const response = await client["requestGameBackend"](TIMEBACK_ROUTES.ADVANCE_COURSE, "POST", params?.subject !== undefined ? { subject: params.subject } : {});
|
|
2121
|
+
await applyPromotion(response.promotion);
|
|
2122
|
+
return response;
|
|
2123
|
+
}
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
// src/namespaces/game/timeback.ts
|
|
2128
|
+
var VALID_INCLUDE_OPTIONS = ["perCourse", "today"];
|
|
2129
|
+
function createTimebackNamespace(client) {
|
|
2130
|
+
const engine = createTimebackEngine(client);
|
|
2033
2131
|
return {
|
|
2034
2132
|
get user() {
|
|
2035
2133
|
assertPlatformMode(client, "timeback.user");
|
|
2036
2134
|
return {
|
|
2037
2135
|
get id() {
|
|
2038
|
-
return
|
|
2136
|
+
return engine.user.snapshot()?.id;
|
|
2039
2137
|
},
|
|
2040
2138
|
get role() {
|
|
2041
|
-
return
|
|
2139
|
+
return engine.user.snapshot()?.role;
|
|
2042
2140
|
},
|
|
2043
2141
|
get enrollments() {
|
|
2044
|
-
return
|
|
2142
|
+
return engine.user.snapshot()?.enrollments ?? [];
|
|
2045
2143
|
},
|
|
2046
2144
|
get organizations() {
|
|
2047
|
-
return
|
|
2145
|
+
return engine.user.snapshot()?.organizations ?? [];
|
|
2048
2146
|
},
|
|
2049
|
-
fetch:
|
|
2050
|
-
const response = await client["request"]("/timeback/user", "GET");
|
|
2051
|
-
const initPayload = client["initPayload"];
|
|
2052
|
-
if (initPayload) {
|
|
2053
|
-
initPayload.timeback = response;
|
|
2054
|
-
}
|
|
2055
|
-
return {
|
|
2056
|
-
id: response.id,
|
|
2057
|
-
role: response.role,
|
|
2058
|
-
enrollments: response.enrollments,
|
|
2059
|
-
organizations: response.organizations
|
|
2060
|
-
};
|
|
2061
|
-
}, options),
|
|
2147
|
+
fetch: (options) => engine.user.fetch(options),
|
|
2062
2148
|
xp: {
|
|
2063
|
-
fetch: async (options) => {
|
|
2064
|
-
const hasGrade = options
|
|
2065
|
-
const hasSubject = options
|
|
2149
|
+
fetch: async (options = {}) => {
|
|
2150
|
+
const hasGrade = options.grade !== undefined;
|
|
2151
|
+
const hasSubject = options.subject !== undefined;
|
|
2066
2152
|
if (hasGrade !== hasSubject) {
|
|
2067
2153
|
throw new Error("Both grade and subject must be provided together");
|
|
2068
2154
|
}
|
|
@@ -2072,53 +2158,40 @@ function createTimebackNamespace(client) {
|
|
|
2072
2158
|
if (hasSubject && !isValidSubject(options.subject)) {
|
|
2073
2159
|
throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
|
|
2074
2160
|
}
|
|
2075
|
-
|
|
2076
|
-
if (options?.include?.length) {
|
|
2161
|
+
if (options.include?.length) {
|
|
2077
2162
|
for (const opt of options.include) {
|
|
2078
|
-
if (!
|
|
2079
|
-
throw new Error(`Invalid include option: ${opt}. Valid options: ${
|
|
2163
|
+
if (!VALID_INCLUDE_OPTIONS.includes(opt)) {
|
|
2164
|
+
throw new Error(`Invalid include option: ${opt}. Valid options: ${VALID_INCLUDE_OPTIONS.join(", ")}`);
|
|
2080
2165
|
}
|
|
2081
2166
|
}
|
|
2082
2167
|
}
|
|
2083
|
-
|
|
2084
|
-
options?.grade ?? "",
|
|
2085
|
-
options?.subject ?? "",
|
|
2086
|
-
options?.include?.toSorted().join(",") ?? ""
|
|
2087
|
-
].join(":");
|
|
2088
|
-
return xpCache.get(cacheKey, async () => {
|
|
2089
|
-
const params = new URLSearchParams;
|
|
2090
|
-
if (hasGrade) {
|
|
2091
|
-
params.set("grade", String(options.grade));
|
|
2092
|
-
}
|
|
2093
|
-
if (hasSubject) {
|
|
2094
|
-
params.set("subject", options.subject);
|
|
2095
|
-
}
|
|
2096
|
-
if (options?.include?.length) {
|
|
2097
|
-
params.set("include", options.include.join(","));
|
|
2098
|
-
}
|
|
2099
|
-
const queryString = params.toString();
|
|
2100
|
-
const endpoint = `${TIMEBACK_ROUTES.GET_XP}${queryString ? `?${queryString}` : ""}`;
|
|
2101
|
-
return client["requestGameBackend"](endpoint, "GET");
|
|
2102
|
-
}, { force: options?.force });
|
|
2168
|
+
return engine.user.xp.fetch(options);
|
|
2103
2169
|
}
|
|
2104
2170
|
}
|
|
2105
2171
|
};
|
|
2106
2172
|
},
|
|
2107
2173
|
startActivity: (metadata, options) => {
|
|
2108
2174
|
assertPlatformMode(client, "timeback.startActivity()");
|
|
2109
|
-
|
|
2175
|
+
engine.activity.start(metadata, options);
|
|
2110
2176
|
},
|
|
2111
2177
|
pauseActivity: () => {
|
|
2112
2178
|
assertPlatformMode(client, "timeback.pauseActivity()");
|
|
2113
|
-
|
|
2179
|
+
engine.activity.pause();
|
|
2114
2180
|
},
|
|
2115
2181
|
resumeActivity: () => {
|
|
2116
2182
|
assertPlatformMode(client, "timeback.resumeActivity()");
|
|
2117
|
-
|
|
2183
|
+
engine.activity.resume();
|
|
2118
2184
|
},
|
|
2119
2185
|
endActivity: async (data) => {
|
|
2120
2186
|
assertPlatformMode(client, "timeback.endActivity()");
|
|
2121
|
-
return
|
|
2187
|
+
return engine.activity.end(data);
|
|
2188
|
+
},
|
|
2189
|
+
advanceCourse: async (options) => {
|
|
2190
|
+
assertPlatformMode(client, "timeback.advanceCourse()");
|
|
2191
|
+
if (options?.subject !== undefined && !isValidSubject(options.subject)) {
|
|
2192
|
+
throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
|
|
2193
|
+
}
|
|
2194
|
+
return engine.advanceCourse(options);
|
|
2122
2195
|
}
|
|
2123
2196
|
};
|
|
2124
2197
|
}
|