@playcademy/sdk 0.11.1-beta.4 → 0.12.0
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 +4 -8
- package/dist/index.js +22 -10
- package/dist/internal.d.ts +5 -4
- package/dist/internal.js +25 -11
- package/dist/server/edge.d.ts +18 -2
- package/dist/server/edge.js +38 -6
- package/dist/server.d.ts +17 -1
- package/dist/server.js +38 -6
- package/dist/types.d.ts +4 -3
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -226,6 +226,7 @@ declare abstract class PlaycademyBaseClient {
|
|
|
226
226
|
};
|
|
227
227
|
protected initPayload?: InitPayload;
|
|
228
228
|
protected launchId?: string;
|
|
229
|
+
protected gameOrigin?: string;
|
|
229
230
|
constructor(config?: Partial<ClientConfig>);
|
|
230
231
|
/**
|
|
231
232
|
* Gets the effective base URL for API requests.
|
|
@@ -306,6 +307,7 @@ declare abstract class PlaycademyBaseClient {
|
|
|
306
307
|
* Ensures a gameId is available, throwing an error if not.
|
|
307
308
|
*/
|
|
308
309
|
protected _ensureGameId(): string;
|
|
310
|
+
private _parseOrigin;
|
|
309
311
|
/**
|
|
310
312
|
* Detects and sets the authentication context (iframe vs standalone).
|
|
311
313
|
*/
|
|
@@ -970,9 +972,6 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
|
|
|
970
972
|
* - `on('pause')`, `on('resume')` - Handle visibility changes
|
|
971
973
|
*/
|
|
972
974
|
runtime: {
|
|
973
|
-
getGameToken: (gameId: string, options?: {
|
|
974
|
-
apply?: boolean;
|
|
975
|
-
}) => Promise<GameTokenResponse>;
|
|
976
975
|
exit: () => void;
|
|
977
976
|
onInit: (handler: (context: GameContextPayload) => void) => void;
|
|
978
977
|
onTokenRefresh: (handler: (data: {
|
|
@@ -1441,6 +1440,8 @@ interface InitPayload {
|
|
|
1441
1440
|
timeback?: TimebackInitContext;
|
|
1442
1441
|
/** Runtime mode for the game client */
|
|
1443
1442
|
mode?: PlaycademyMode;
|
|
1443
|
+
/** Launch session correlation ID (UUID, set by platform on game launch) */
|
|
1444
|
+
launchId?: string;
|
|
1444
1445
|
}
|
|
1445
1446
|
interface GameContextPayload {
|
|
1446
1447
|
token: string;
|
|
@@ -1553,11 +1554,6 @@ interface DemoEndPayload extends DemoEndOptions {
|
|
|
1553
1554
|
interface LoginResponse {
|
|
1554
1555
|
token: string;
|
|
1555
1556
|
}
|
|
1556
|
-
interface GameTokenResponse {
|
|
1557
|
-
token: string;
|
|
1558
|
-
exp: number;
|
|
1559
|
-
baseUrl?: string;
|
|
1560
|
-
}
|
|
1561
1557
|
|
|
1562
1558
|
/**
|
|
1563
1559
|
* Scores namespace types
|
package/dist/index.js
CHANGED
|
@@ -209,7 +209,8 @@ async function init(options) {
|
|
|
209
209
|
gameUrl: config.gameUrl,
|
|
210
210
|
token: config.token,
|
|
211
211
|
gameId: config.gameId,
|
|
212
|
-
mode: config.mode
|
|
212
|
+
mode: config.mode,
|
|
213
|
+
launchId: config.launchId
|
|
213
214
|
});
|
|
214
215
|
client["initPayload"] = config;
|
|
215
216
|
messaging.listen("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, ({ token }) => client.setToken(token));
|
|
@@ -1001,13 +1002,6 @@ function createRuntimeNamespace(client) {
|
|
|
1001
1002
|
});
|
|
1002
1003
|
}
|
|
1003
1004
|
return {
|
|
1004
|
-
getGameToken: async (gameId, options) => {
|
|
1005
|
-
const res = await client["request"](`/games/${gameId}/token`, "POST");
|
|
1006
|
-
if (options?.apply) {
|
|
1007
|
-
client.setToken(res.token);
|
|
1008
|
-
}
|
|
1009
|
-
return res;
|
|
1010
|
-
},
|
|
1011
1005
|
exit: () => {
|
|
1012
1006
|
messaging.send("PLAYCADEMY_EXIT" /* EXIT */, undefined);
|
|
1013
1007
|
},
|
|
@@ -2323,6 +2317,9 @@ async function request({
|
|
|
2323
2317
|
const rawText = await res.text().catch(() => "");
|
|
2324
2318
|
return rawText && rawText.length > 0 ? rawText : undefined;
|
|
2325
2319
|
}
|
|
2320
|
+
// src/version.ts
|
|
2321
|
+
var SDK_VERSION = "0.12.0";
|
|
2322
|
+
|
|
2326
2323
|
// src/clients/base.ts
|
|
2327
2324
|
class PlaycademyBaseClient {
|
|
2328
2325
|
baseUrl;
|
|
@@ -2335,6 +2332,7 @@ class PlaycademyBaseClient {
|
|
|
2335
2332
|
authContext;
|
|
2336
2333
|
initPayload;
|
|
2337
2334
|
launchId;
|
|
2335
|
+
gameOrigin;
|
|
2338
2336
|
constructor(config) {
|
|
2339
2337
|
this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
|
|
2340
2338
|
this.gameUrl = config?.gameUrl;
|
|
@@ -2342,6 +2340,7 @@ class PlaycademyBaseClient {
|
|
|
2342
2340
|
this.gameId = config?.gameId;
|
|
2343
2341
|
this.launchId = config?.launchId ?? undefined;
|
|
2344
2342
|
this.config = config || {};
|
|
2343
|
+
this.gameOrigin = this._parseOrigin(config?.gameUrl);
|
|
2345
2344
|
this.authStrategy = createAuthStrategy(config?.token ?? null, config?.tokenType);
|
|
2346
2345
|
this._detectAuthContext();
|
|
2347
2346
|
}
|
|
@@ -2408,7 +2407,9 @@ class PlaycademyBaseClient {
|
|
|
2408
2407
|
const effectiveHeaders = {
|
|
2409
2408
|
...options?.headers,
|
|
2410
2409
|
...this.authStrategy.getHeaders(),
|
|
2411
|
-
...this.launchId ? { "x-playcademy-launch-id": this.launchId } : {}
|
|
2410
|
+
...this.launchId ? { "x-playcademy-launch-id": this.launchId } : {},
|
|
2411
|
+
...this.gameOrigin ? { "x-playcademy-game-origin": this.gameOrigin } : {},
|
|
2412
|
+
"x-playcademy-sdk-version": SDK_VERSION
|
|
2412
2413
|
};
|
|
2413
2414
|
return request({
|
|
2414
2415
|
path,
|
|
@@ -2423,7 +2424,8 @@ class PlaycademyBaseClient {
|
|
|
2423
2424
|
async requestGameBackend(path, method, body, headers, options) {
|
|
2424
2425
|
const effectiveHeaders = {
|
|
2425
2426
|
...headers,
|
|
2426
|
-
...this.authStrategy.getHeaders()
|
|
2427
|
+
...this.authStrategy.getHeaders(),
|
|
2428
|
+
...this.launchId ? { "x-playcademy-launch-id": this.launchId } : {}
|
|
2427
2429
|
};
|
|
2428
2430
|
return request({
|
|
2429
2431
|
path,
|
|
@@ -2441,6 +2443,16 @@ class PlaycademyBaseClient {
|
|
|
2441
2443
|
}
|
|
2442
2444
|
return this.gameId;
|
|
2443
2445
|
}
|
|
2446
|
+
_parseOrigin(url) {
|
|
2447
|
+
if (!url) {
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2450
|
+
try {
|
|
2451
|
+
return new URL(url).origin;
|
|
2452
|
+
} catch {
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2444
2456
|
_detectAuthContext() {
|
|
2445
2457
|
this.authContext = { isInIframe: isInIframe() };
|
|
2446
2458
|
}
|
package/dist/internal.d.ts
CHANGED
|
@@ -768,6 +768,7 @@ declare abstract class PlaycademyBaseClient {
|
|
|
768
768
|
};
|
|
769
769
|
protected initPayload?: InitPayload;
|
|
770
770
|
protected launchId?: string;
|
|
771
|
+
protected gameOrigin?: string;
|
|
771
772
|
constructor(config?: Partial<ClientConfig>);
|
|
772
773
|
/**
|
|
773
774
|
* Gets the effective base URL for API requests.
|
|
@@ -848,6 +849,7 @@ declare abstract class PlaycademyBaseClient {
|
|
|
848
849
|
* Ensures a gameId is available, throwing an error if not.
|
|
849
850
|
*/
|
|
850
851
|
protected _ensureGameId(): string;
|
|
852
|
+
private _parseOrigin;
|
|
851
853
|
/**
|
|
852
854
|
* Detects and sets the authentication context (iframe vs standalone).
|
|
853
855
|
*/
|
|
@@ -1289,6 +1291,8 @@ interface InitPayload {
|
|
|
1289
1291
|
timeback?: TimebackInitContext;
|
|
1290
1292
|
/** Runtime mode for the game client */
|
|
1291
1293
|
mode?: PlaycademyMode;
|
|
1294
|
+
/** Launch session correlation ID (UUID, set by platform on game launch) */
|
|
1295
|
+
launchId?: string;
|
|
1292
1296
|
}
|
|
1293
1297
|
/**
|
|
1294
1298
|
* Simplified user data passed to games via InitPayload
|
|
@@ -2745,7 +2749,6 @@ interface PlaycademyServerClientState {
|
|
|
2745
2749
|
*/
|
|
2746
2750
|
courseId?: string;
|
|
2747
2751
|
}
|
|
2748
|
-
|
|
2749
2752
|
/**
|
|
2750
2753
|
* Resource bindings for backend deployment
|
|
2751
2754
|
* Provider-agnostic abstraction for cloud resources
|
|
@@ -2818,9 +2821,6 @@ declare class PlaycademyInternalClient extends PlaycademyBaseClient {
|
|
|
2818
2821
|
* - `assets.url()`, `assets.json()`, `assets.fetch()` - Load game assets
|
|
2819
2822
|
*/
|
|
2820
2823
|
runtime: {
|
|
2821
|
-
getGameToken: (gameId: string, options?: {
|
|
2822
|
-
apply?: boolean;
|
|
2823
|
-
}) => Promise<GameTokenResponse>;
|
|
2824
2824
|
exit: () => void;
|
|
2825
2825
|
onInit: (handler: (context: GameContextPayload) => void) => void;
|
|
2826
2826
|
onTokenRefresh: (handler: (data: {
|
|
@@ -2996,6 +2996,7 @@ declare class PlaycademyInternalClient extends PlaycademyBaseClient {
|
|
|
2996
2996
|
token: {
|
|
2997
2997
|
create: (gameId: string, options?: {
|
|
2998
2998
|
apply?: boolean;
|
|
2999
|
+
reason?: 'initial' | 'refresh_scheduled' | 'refresh_stale';
|
|
2999
3000
|
}) => Promise<GameTokenResponse>;
|
|
3000
3001
|
};
|
|
3001
3002
|
leaderboard: {
|
package/dist/internal.js
CHANGED
|
@@ -209,7 +209,8 @@ async function init(options) {
|
|
|
209
209
|
gameUrl: config.gameUrl,
|
|
210
210
|
token: config.token,
|
|
211
211
|
gameId: config.gameId,
|
|
212
|
-
mode: config.mode
|
|
212
|
+
mode: config.mode,
|
|
213
|
+
launchId: config.launchId
|
|
213
214
|
});
|
|
214
215
|
client["initPayload"] = config;
|
|
215
216
|
messaging.listen("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, ({ token }) => client.setToken(token));
|
|
@@ -1001,13 +1002,6 @@ function createRuntimeNamespace(client) {
|
|
|
1001
1002
|
});
|
|
1002
1003
|
}
|
|
1003
1004
|
return {
|
|
1004
|
-
getGameToken: async (gameId, options) => {
|
|
1005
|
-
const res = await client["request"](`/games/${gameId}/token`, "POST");
|
|
1006
|
-
if (options?.apply) {
|
|
1007
|
-
client.setToken(res.token);
|
|
1008
|
-
}
|
|
1009
|
-
return res;
|
|
1010
|
-
},
|
|
1011
1005
|
exit: () => {
|
|
1012
1006
|
messaging.send("PLAYCADEMY_EXIT" /* EXIT */, undefined);
|
|
1013
1007
|
},
|
|
@@ -2534,7 +2528,9 @@ function createGamesNamespace(client) {
|
|
|
2534
2528
|
getSubjects: () => client["request"]("/games/subjects", "GET"),
|
|
2535
2529
|
token: {
|
|
2536
2530
|
create: async (gameId, options) => {
|
|
2537
|
-
const res = await client["request"](`/games/${gameId}/token`, "POST"
|
|
2531
|
+
const res = await client["request"](`/games/${gameId}/token`, "POST", {
|
|
2532
|
+
body: options?.reason ? { reason: options.reason } : undefined
|
|
2533
|
+
});
|
|
2538
2534
|
if (options?.apply) {
|
|
2539
2535
|
client.setToken(res.token);
|
|
2540
2536
|
}
|
|
@@ -3011,6 +3007,9 @@ async function request({
|
|
|
3011
3007
|
const rawText = await res.text().catch(() => "");
|
|
3012
3008
|
return rawText && rawText.length > 0 ? rawText : undefined;
|
|
3013
3009
|
}
|
|
3010
|
+
// src/version.ts
|
|
3011
|
+
var SDK_VERSION = "0.12.0";
|
|
3012
|
+
|
|
3014
3013
|
// src/clients/base.ts
|
|
3015
3014
|
class PlaycademyBaseClient {
|
|
3016
3015
|
baseUrl;
|
|
@@ -3023,6 +3022,7 @@ class PlaycademyBaseClient {
|
|
|
3023
3022
|
authContext;
|
|
3024
3023
|
initPayload;
|
|
3025
3024
|
launchId;
|
|
3025
|
+
gameOrigin;
|
|
3026
3026
|
constructor(config) {
|
|
3027
3027
|
this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
|
|
3028
3028
|
this.gameUrl = config?.gameUrl;
|
|
@@ -3030,6 +3030,7 @@ class PlaycademyBaseClient {
|
|
|
3030
3030
|
this.gameId = config?.gameId;
|
|
3031
3031
|
this.launchId = config?.launchId ?? undefined;
|
|
3032
3032
|
this.config = config || {};
|
|
3033
|
+
this.gameOrigin = this._parseOrigin(config?.gameUrl);
|
|
3033
3034
|
this.authStrategy = createAuthStrategy(config?.token ?? null, config?.tokenType);
|
|
3034
3035
|
this._detectAuthContext();
|
|
3035
3036
|
}
|
|
@@ -3096,7 +3097,9 @@ class PlaycademyBaseClient {
|
|
|
3096
3097
|
const effectiveHeaders = {
|
|
3097
3098
|
...options?.headers,
|
|
3098
3099
|
...this.authStrategy.getHeaders(),
|
|
3099
|
-
...this.launchId ? { "x-playcademy-launch-id": this.launchId } : {}
|
|
3100
|
+
...this.launchId ? { "x-playcademy-launch-id": this.launchId } : {},
|
|
3101
|
+
...this.gameOrigin ? { "x-playcademy-game-origin": this.gameOrigin } : {},
|
|
3102
|
+
"x-playcademy-sdk-version": SDK_VERSION
|
|
3100
3103
|
};
|
|
3101
3104
|
return request({
|
|
3102
3105
|
path,
|
|
@@ -3111,7 +3114,8 @@ class PlaycademyBaseClient {
|
|
|
3111
3114
|
async requestGameBackend(path, method, body, headers, options) {
|
|
3112
3115
|
const effectiveHeaders = {
|
|
3113
3116
|
...headers,
|
|
3114
|
-
...this.authStrategy.getHeaders()
|
|
3117
|
+
...this.authStrategy.getHeaders(),
|
|
3118
|
+
...this.launchId ? { "x-playcademy-launch-id": this.launchId } : {}
|
|
3115
3119
|
};
|
|
3116
3120
|
return request({
|
|
3117
3121
|
path,
|
|
@@ -3129,6 +3133,16 @@ class PlaycademyBaseClient {
|
|
|
3129
3133
|
}
|
|
3130
3134
|
return this.gameId;
|
|
3131
3135
|
}
|
|
3136
|
+
_parseOrigin(url) {
|
|
3137
|
+
if (!url) {
|
|
3138
|
+
return;
|
|
3139
|
+
}
|
|
3140
|
+
try {
|
|
3141
|
+
return new URL(url).origin;
|
|
3142
|
+
} catch {
|
|
3143
|
+
return;
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3132
3146
|
_detectAuthContext() {
|
|
3133
3147
|
this.authContext = { isInIframe: isInIframe() };
|
|
3134
3148
|
}
|
package/dist/server/edge.d.ts
CHANGED
|
@@ -195,6 +195,12 @@ interface PlaycademyServerClientState {
|
|
|
195
195
|
*/
|
|
196
196
|
courseId?: string;
|
|
197
197
|
}
|
|
198
|
+
interface RequestContext {
|
|
199
|
+
/** Per-request launch ID for cross-surface trace correlation */
|
|
200
|
+
launchId?: string;
|
|
201
|
+
/** Game origin URL derived from the inbound request */
|
|
202
|
+
gameOrigin?: string;
|
|
203
|
+
}
|
|
198
204
|
|
|
199
205
|
/**
|
|
200
206
|
* Resource bindings for backend deployment
|
|
@@ -247,7 +253,8 @@ interface BackendDeploymentBundle {
|
|
|
247
253
|
*/
|
|
248
254
|
declare class PlaycademyClient {
|
|
249
255
|
private state;
|
|
250
|
-
|
|
256
|
+
private requestCtx;
|
|
257
|
+
protected constructor(state: PlaycademyServerClientState, requestCtx?: RequestContext);
|
|
251
258
|
/**
|
|
252
259
|
* Initialize a new PlaycademyClient instance.
|
|
253
260
|
*
|
|
@@ -264,6 +271,12 @@ declare class PlaycademyClient {
|
|
|
264
271
|
* @throws {Error} If config is not provided
|
|
265
272
|
*/
|
|
266
273
|
static init(config: PlaycademyServerClientConfig): Promise<PlaycademyClient>;
|
|
274
|
+
/**
|
|
275
|
+
* Creates a request-scoped client that shares this client's config
|
|
276
|
+
* but carries per-request context (launchId, gameOrigin) for outbound
|
|
277
|
+
* API calls. Safe for concurrent use — each wrapper has its own context.
|
|
278
|
+
*/
|
|
279
|
+
forRequest(ctx: RequestContext): PlaycademyClient;
|
|
267
280
|
private fetchGameId;
|
|
268
281
|
/**
|
|
269
282
|
* Makes an authenticated HTTP request to the API.
|
|
@@ -333,6 +346,7 @@ interface VerifyGameTokenResponse {
|
|
|
333
346
|
* @param gameToken - The game JWT token string to verify
|
|
334
347
|
* @param options - Optional configuration
|
|
335
348
|
* @param options.baseUrl - Optional base URL override (defaults to env vars or production)
|
|
349
|
+
* @param options.gameOrigin - Optional game origin for observability (e.g., 'https://math-raiders.playcademy.gg')
|
|
336
350
|
* @returns Promise resolving to verified token payload
|
|
337
351
|
* @returns claims - Arbitrary claims from the JWT payload
|
|
338
352
|
* @returns gameId - The game ID this token was issued for
|
|
@@ -365,7 +379,9 @@ interface VerifyGameTokenResponse {
|
|
|
365
379
|
*/
|
|
366
380
|
declare function verifyGameToken(gameToken: string, options?: {
|
|
367
381
|
baseUrl?: string;
|
|
382
|
+
gameOrigin?: string;
|
|
383
|
+
launchId?: string;
|
|
368
384
|
}): Promise<VerifyGameTokenResponse>;
|
|
369
385
|
|
|
370
386
|
export { PlaycademyClient, verifyGameToken };
|
|
371
|
-
export type { BackendDeploymentBundle, BackendResourceBindings, IntegrationsConfig, PlaycademyConfig, PlaycademyServerClientConfig, PlaycademyServerClientState, QueueConfig, TimebackBaseConfig, TimebackCourseConfigWithOverrides, TimebackIntegrationConfig };
|
|
387
|
+
export type { BackendDeploymentBundle, BackendResourceBindings, IntegrationsConfig, PlaycademyConfig, PlaycademyServerClientConfig, PlaycademyServerClientState, QueueConfig, RequestContext, TimebackBaseConfig, TimebackCourseConfigWithOverrides, TimebackIntegrationConfig };
|
package/dist/server/edge.js
CHANGED
|
@@ -232,12 +232,23 @@ function extractApiErrorInfo(error) {
|
|
|
232
232
|
};
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
// src/version.ts
|
|
236
|
+
var SDK_VERSION = "0.12.0";
|
|
237
|
+
|
|
235
238
|
// src/server/request.ts
|
|
236
|
-
async function makeApiRequest(
|
|
239
|
+
async function makeApiRequest(opts) {
|
|
240
|
+
const { baseUrl, apiToken, endpoint, method = "GET", body, gameOrigin, launchId } = opts;
|
|
237
241
|
const url = `${baseUrl}${endpoint}`;
|
|
238
242
|
const headers = new Headers;
|
|
239
243
|
headers.set("Content-Type", "application/json");
|
|
240
244
|
headers.set("x-api-key", apiToken);
|
|
245
|
+
headers.set("x-playcademy-sdk-version", SDK_VERSION);
|
|
246
|
+
if (gameOrigin) {
|
|
247
|
+
headers.set("x-playcademy-game-origin", gameOrigin);
|
|
248
|
+
}
|
|
249
|
+
if (launchId) {
|
|
250
|
+
headers.set("x-playcademy-launch-id", launchId);
|
|
251
|
+
}
|
|
241
252
|
const options = {
|
|
242
253
|
method,
|
|
243
254
|
headers
|
|
@@ -263,8 +274,10 @@ async function makeApiRequest(baseUrl, apiToken, endpoint, method = "GET", body)
|
|
|
263
274
|
// src/server/client-base.ts
|
|
264
275
|
class PlaycademyClient {
|
|
265
276
|
state;
|
|
266
|
-
|
|
277
|
+
requestCtx = {};
|
|
278
|
+
constructor(state, requestCtx) {
|
|
267
279
|
this.state = state;
|
|
280
|
+
this.requestCtx = requestCtx ?? {};
|
|
268
281
|
}
|
|
269
282
|
static async init(config) {
|
|
270
283
|
const { apiKey, baseUrl, gameId } = config;
|
|
@@ -288,11 +301,22 @@ class PlaycademyClient {
|
|
|
288
301
|
}
|
|
289
302
|
return client;
|
|
290
303
|
}
|
|
304
|
+
forRequest(ctx) {
|
|
305
|
+
return new PlaycademyClient(this.state, ctx);
|
|
306
|
+
}
|
|
291
307
|
async fetchGameId() {
|
|
292
308
|
throw new Error("[Playcademy SDK] gameId is required. Please provide it in the config or implement gameId fetching.");
|
|
293
309
|
}
|
|
294
310
|
async request(path, method = "GET", body) {
|
|
295
|
-
return makeApiRequest(
|
|
311
|
+
return makeApiRequest({
|
|
312
|
+
baseUrl: this.state.baseUrl,
|
|
313
|
+
apiToken: this.state.apiKey,
|
|
314
|
+
endpoint: path,
|
|
315
|
+
method,
|
|
316
|
+
body,
|
|
317
|
+
gameOrigin: this.requestCtx.gameOrigin,
|
|
318
|
+
launchId: this.requestCtx.launchId
|
|
319
|
+
});
|
|
296
320
|
}
|
|
297
321
|
get gameId() {
|
|
298
322
|
return this.state.gameId;
|
|
@@ -313,11 +337,19 @@ async function verifyGameToken(gameToken, options) {
|
|
|
313
337
|
Please set the PLAYCADEMY_BASE_URL environment variable`);
|
|
314
338
|
}
|
|
315
339
|
try {
|
|
340
|
+
const headers = {
|
|
341
|
+
"Content-Type": "application/json",
|
|
342
|
+
"x-playcademy-sdk-version": SDK_VERSION
|
|
343
|
+
};
|
|
344
|
+
if (options?.gameOrigin) {
|
|
345
|
+
headers["x-playcademy-game-origin"] = options.gameOrigin;
|
|
346
|
+
}
|
|
347
|
+
if (options?.launchId) {
|
|
348
|
+
headers["x-playcademy-launch-id"] = options.launchId;
|
|
349
|
+
}
|
|
316
350
|
const response = await fetch(`${baseUrl}/api/games/verify`, {
|
|
317
351
|
method: "POST",
|
|
318
|
-
headers
|
|
319
|
-
"Content-Type": "application/json"
|
|
320
|
-
},
|
|
352
|
+
headers,
|
|
321
353
|
body: JSON.stringify({ token: gameToken })
|
|
322
354
|
});
|
|
323
355
|
if (!response.ok) {
|
package/dist/server.d.ts
CHANGED
|
@@ -195,6 +195,12 @@ interface PlaycademyServerClientState {
|
|
|
195
195
|
*/
|
|
196
196
|
courseId?: string;
|
|
197
197
|
}
|
|
198
|
+
interface RequestContext {
|
|
199
|
+
/** Per-request launch ID for cross-surface trace correlation */
|
|
200
|
+
launchId?: string;
|
|
201
|
+
/** Game origin URL derived from the inbound request */
|
|
202
|
+
gameOrigin?: string;
|
|
203
|
+
}
|
|
198
204
|
|
|
199
205
|
/**
|
|
200
206
|
* Resource bindings for backend deployment
|
|
@@ -247,7 +253,8 @@ interface BackendDeploymentBundle {
|
|
|
247
253
|
*/
|
|
248
254
|
declare class PlaycademyClient$1 {
|
|
249
255
|
private state;
|
|
250
|
-
|
|
256
|
+
private requestCtx;
|
|
257
|
+
protected constructor(state: PlaycademyServerClientState, requestCtx?: RequestContext);
|
|
251
258
|
/**
|
|
252
259
|
* Initialize a new PlaycademyClient instance.
|
|
253
260
|
*
|
|
@@ -264,6 +271,12 @@ declare class PlaycademyClient$1 {
|
|
|
264
271
|
* @throws {Error} If config is not provided
|
|
265
272
|
*/
|
|
266
273
|
static init(config: PlaycademyServerClientConfig): Promise<PlaycademyClient$1>;
|
|
274
|
+
/**
|
|
275
|
+
* Creates a request-scoped client that shares this client's config
|
|
276
|
+
* but carries per-request context (launchId, gameOrigin) for outbound
|
|
277
|
+
* API calls. Safe for concurrent use — each wrapper has its own context.
|
|
278
|
+
*/
|
|
279
|
+
forRequest(ctx: RequestContext): PlaycademyClient$1;
|
|
267
280
|
private fetchGameId;
|
|
268
281
|
/**
|
|
269
282
|
* Makes an authenticated HTTP request to the API.
|
|
@@ -379,6 +392,7 @@ interface VerifyGameTokenResponse {
|
|
|
379
392
|
* @param gameToken - The game JWT token string to verify
|
|
380
393
|
* @param options - Optional configuration
|
|
381
394
|
* @param options.baseUrl - Optional base URL override (defaults to env vars or production)
|
|
395
|
+
* @param options.gameOrigin - Optional game origin for observability (e.g., 'https://math-raiders.playcademy.gg')
|
|
382
396
|
* @returns Promise resolving to verified token payload
|
|
383
397
|
* @returns claims - Arbitrary claims from the JWT payload
|
|
384
398
|
* @returns gameId - The game ID this token was issued for
|
|
@@ -411,6 +425,8 @@ interface VerifyGameTokenResponse {
|
|
|
411
425
|
*/
|
|
412
426
|
declare function verifyGameToken(gameToken: string, options?: {
|
|
413
427
|
baseUrl?: string;
|
|
428
|
+
gameOrigin?: string;
|
|
429
|
+
launchId?: string;
|
|
414
430
|
}): Promise<VerifyGameTokenResponse>;
|
|
415
431
|
|
|
416
432
|
export { PlaycademyClient, verifyGameToken };
|
package/dist/server.js
CHANGED
|
@@ -421,12 +421,23 @@ function extractApiErrorInfo(error) {
|
|
|
421
421
|
};
|
|
422
422
|
}
|
|
423
423
|
|
|
424
|
+
// src/version.ts
|
|
425
|
+
var SDK_VERSION = "0.12.0";
|
|
426
|
+
|
|
424
427
|
// src/server/request.ts
|
|
425
|
-
async function makeApiRequest(
|
|
428
|
+
async function makeApiRequest(opts) {
|
|
429
|
+
const { baseUrl, apiToken, endpoint, method = "GET", body, gameOrigin, launchId } = opts;
|
|
426
430
|
const url = `${baseUrl}${endpoint}`;
|
|
427
431
|
const headers = new Headers;
|
|
428
432
|
headers.set("Content-Type", "application/json");
|
|
429
433
|
headers.set("x-api-key", apiToken);
|
|
434
|
+
headers.set("x-playcademy-sdk-version", SDK_VERSION);
|
|
435
|
+
if (gameOrigin) {
|
|
436
|
+
headers.set("x-playcademy-game-origin", gameOrigin);
|
|
437
|
+
}
|
|
438
|
+
if (launchId) {
|
|
439
|
+
headers.set("x-playcademy-launch-id", launchId);
|
|
440
|
+
}
|
|
430
441
|
const options = {
|
|
431
442
|
method,
|
|
432
443
|
headers
|
|
@@ -452,8 +463,10 @@ async function makeApiRequest(baseUrl, apiToken, endpoint, method = "GET", body)
|
|
|
452
463
|
// src/server/client-base.ts
|
|
453
464
|
class PlaycademyClient {
|
|
454
465
|
state;
|
|
455
|
-
|
|
466
|
+
requestCtx = {};
|
|
467
|
+
constructor(state, requestCtx) {
|
|
456
468
|
this.state = state;
|
|
469
|
+
this.requestCtx = requestCtx ?? {};
|
|
457
470
|
}
|
|
458
471
|
static async init(config) {
|
|
459
472
|
const { apiKey, baseUrl, gameId } = config;
|
|
@@ -477,11 +490,22 @@ class PlaycademyClient {
|
|
|
477
490
|
}
|
|
478
491
|
return client;
|
|
479
492
|
}
|
|
493
|
+
forRequest(ctx) {
|
|
494
|
+
return new PlaycademyClient(this.state, ctx);
|
|
495
|
+
}
|
|
480
496
|
async fetchGameId() {
|
|
481
497
|
throw new Error("[Playcademy SDK] gameId is required. Please provide it in the config or implement gameId fetching.");
|
|
482
498
|
}
|
|
483
499
|
async request(path, method = "GET", body) {
|
|
484
|
-
return makeApiRequest(
|
|
500
|
+
return makeApiRequest({
|
|
501
|
+
baseUrl: this.state.baseUrl,
|
|
502
|
+
apiToken: this.state.apiKey,
|
|
503
|
+
endpoint: path,
|
|
504
|
+
method,
|
|
505
|
+
body,
|
|
506
|
+
gameOrigin: this.requestCtx.gameOrigin,
|
|
507
|
+
launchId: this.requestCtx.launchId
|
|
508
|
+
});
|
|
485
509
|
}
|
|
486
510
|
get gameId() {
|
|
487
511
|
return this.state.gameId;
|
|
@@ -514,11 +538,19 @@ async function verifyGameToken(gameToken, options) {
|
|
|
514
538
|
Please set the PLAYCADEMY_BASE_URL environment variable`);
|
|
515
539
|
}
|
|
516
540
|
try {
|
|
541
|
+
const headers = {
|
|
542
|
+
"Content-Type": "application/json",
|
|
543
|
+
"x-playcademy-sdk-version": SDK_VERSION
|
|
544
|
+
};
|
|
545
|
+
if (options?.gameOrigin) {
|
|
546
|
+
headers["x-playcademy-game-origin"] = options.gameOrigin;
|
|
547
|
+
}
|
|
548
|
+
if (options?.launchId) {
|
|
549
|
+
headers["x-playcademy-launch-id"] = options.launchId;
|
|
550
|
+
}
|
|
517
551
|
const response = await fetch(`${baseUrl}/api/games/verify`, {
|
|
518
552
|
method: "POST",
|
|
519
|
-
headers
|
|
520
|
-
"Content-Type": "application/json"
|
|
521
|
-
},
|
|
553
|
+
headers,
|
|
522
554
|
body: JSON.stringify({ token: gameToken })
|
|
523
555
|
});
|
|
524
556
|
if (!response.ok) {
|
package/dist/types.d.ts
CHANGED
|
@@ -1148,6 +1148,7 @@ declare abstract class PlaycademyBaseClient {
|
|
|
1148
1148
|
};
|
|
1149
1149
|
protected initPayload?: InitPayload;
|
|
1150
1150
|
protected launchId?: string;
|
|
1151
|
+
protected gameOrigin?: string;
|
|
1151
1152
|
constructor(config?: Partial<ClientConfig>);
|
|
1152
1153
|
/**
|
|
1153
1154
|
* Gets the effective base URL for API requests.
|
|
@@ -1228,6 +1229,7 @@ declare abstract class PlaycademyBaseClient {
|
|
|
1228
1229
|
* Ensures a gameId is available, throwing an error if not.
|
|
1229
1230
|
*/
|
|
1230
1231
|
protected _ensureGameId(): string;
|
|
1232
|
+
private _parseOrigin;
|
|
1231
1233
|
/**
|
|
1232
1234
|
* Detects and sets the authentication context (iframe vs standalone).
|
|
1233
1235
|
*/
|
|
@@ -1327,9 +1329,6 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
|
|
|
1327
1329
|
* - `on('pause')`, `on('resume')` - Handle visibility changes
|
|
1328
1330
|
*/
|
|
1329
1331
|
runtime: {
|
|
1330
|
-
getGameToken: (gameId: string, options?: {
|
|
1331
|
-
apply?: boolean;
|
|
1332
|
-
}) => Promise<GameTokenResponse>;
|
|
1333
1332
|
exit: () => void;
|
|
1334
1333
|
onInit: (handler: (context: GameContextPayload) => void) => void;
|
|
1335
1334
|
onTokenRefresh: (handler: (data: {
|
|
@@ -1798,6 +1797,8 @@ interface InitPayload {
|
|
|
1798
1797
|
timeback?: TimebackInitContext;
|
|
1799
1798
|
/** Runtime mode for the game client */
|
|
1800
1799
|
mode?: PlaycademyMode;
|
|
1800
|
+
/** Launch session correlation ID (UUID, set by platform on game launch) */
|
|
1801
|
+
launchId?: string;
|
|
1801
1802
|
}
|
|
1802
1803
|
/**
|
|
1803
1804
|
* Simplified user data passed to games via InitPayload
|