@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 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
  }
@@ -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
  }
@@ -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
- protected constructor(state: PlaycademyServerClientState);
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 };
@@ -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(baseUrl, apiToken, endpoint, method = "GET", body) {
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
- constructor(state) {
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(this.state.baseUrl, this.state.apiKey, path, method, body);
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
- protected constructor(state: PlaycademyServerClientState);
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(baseUrl, apiToken, endpoint, method = "GET", body) {
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
- constructor(state) {
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(this.state.baseUrl, this.state.apiKey, path, method, body);
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sdk",
3
- "version": "0.11.1-beta.4",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {