@looplay/sdk 0.8.6 → 0.8.8

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.
@@ -137,6 +137,9 @@ var LooplayAuth = class {
137
137
  }
138
138
  };
139
139
 
140
+ // src/version.ts
141
+ var LOOPLAY_SDK_VERSION = "0.8.5";
142
+
140
143
  // src/apps/http.ts
141
144
  function getFetchFn(fetchOverride) {
142
145
  if (fetchOverride) {
@@ -164,7 +167,10 @@ var HttpClient = class {
164
167
  constructor(options) {
165
168
  this.baseUrl = options.baseUrl.replace(/\/$/, "");
166
169
  this.fetchFn = getFetchFn(options.fetch);
167
- this.defaultHeaders = options.defaultHeaders ?? {};
170
+ this.defaultHeaders = {
171
+ "x-looplay-sdk-version": LOOPLAY_SDK_VERSION,
172
+ ...options.defaultHeaders ?? {}
173
+ };
168
174
  }
169
175
  async request(method, path, options) {
170
176
  const url = this.buildUrl(path, options?.query);
@@ -267,6 +273,19 @@ var ServiceClient = class {
267
273
  body
268
274
  });
269
275
  }
276
+ /**
277
+ * Re-issues a fresh `playSessionToken` for the SAME `playSessionId` — call
278
+ * this instead of `startPlaySession` when refreshing an about-to-expire
279
+ * (or just-expired, within the server's renewal grace window) token, so
280
+ * long-running sessions keep a single `playSessionId` throughout.
281
+ */
282
+ async renewPlaySession(gameKey, auth = {}, body) {
283
+ return this.http.request("POST", "/sdk/games/play-sessions/renew", {
284
+ ...auth,
285
+ headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
286
+ body
287
+ });
288
+ }
270
289
  async completePlaySession(gameKey, auth, body) {
271
290
  await this.http.request("POST", "/sdk/games/play-sessions/complete", {
272
291
  ...auth,
@@ -436,9 +455,11 @@ var ApiClient = class {
436
455
  getAccessToken;
437
456
  getAnonymousId;
438
457
  trackingSession;
458
+ trackingSessionPromiseKey;
439
459
  trackingSessionPromise;
440
460
  currentPlaySessionId;
441
461
  playSession;
462
+ playSessionPromiseKey;
442
463
  playSessionPromise;
443
464
  constructor(options) {
444
465
  if (!options.baseUrl) throw new MissingBaseUrlError();
@@ -479,7 +500,10 @@ var ApiClient = class {
479
500
  const bearerToken = await this.getAccessToken?.();
480
501
  const anonId = bearerToken ? void 0 : this.getAnonymousId?.();
481
502
  if (!bearerToken && !anonId) throw new NotAuthenticatedError();
482
- const session = await this.ensureTrackingSession(anonId);
503
+ const session = await this.ensureTrackingSession(
504
+ bearerToken ? `bearer:${bearerToken}` : `anon:${anonId}`,
505
+ anonId
506
+ );
483
507
  return {
484
508
  bearerToken,
485
509
  anonId,
@@ -492,14 +516,16 @@ var ApiClient = class {
492
516
  * session. Swallows failures — tracking calls still work without it,
493
517
  * just without unified session tracing.
494
518
  */
495
- async ensureTrackingSession(anonId) {
519
+ async ensureTrackingSession(cacheKey, anonId) {
496
520
  const now = Date.now();
497
- if (this.trackingSession && this.trackingSession.expiresAtMs > now + 3e4) {
521
+ if (this.trackingSession && this.trackingSession.cacheKey === cacheKey && this.trackingSession.expiresAtMs > now + 3e4) {
498
522
  return this.trackingSession;
499
523
  }
500
- if (!this.trackingSessionPromise) {
524
+ if (!this.trackingSessionPromise || this.trackingSessionPromiseKey !== cacheKey) {
525
+ this.trackingSessionPromiseKey = cacheKey;
501
526
  this.trackingSessionPromise = this.raw.bootstrapTrackingSession(anonId).then((dto) => {
502
527
  this.trackingSession = {
528
+ cacheKey,
503
529
  trackingSessionId: dto.trackingSessionId,
504
530
  anonTrackingToken: dto.trackingToken,
505
531
  expiresAtMs: new Date(dto.expiresAt).getTime()
@@ -507,10 +533,18 @@ var ApiClient = class {
507
533
  return this.trackingSession;
508
534
  }).catch(() => void 0).finally(() => {
509
535
  this.trackingSessionPromise = void 0;
536
+ this.trackingSessionPromiseKey = void 0;
510
537
  });
511
538
  }
512
539
  return this.trackingSessionPromise;
513
540
  }
541
+ getTrackingAuthKey(auth) {
542
+ return [
543
+ auth.bearerToken ? `bearer:${auth.bearerToken}` : `anon:${auth.anonId ?? ""}`,
544
+ auth.trackingSessionId ?? "",
545
+ auth.anonTrackingToken ?? ""
546
+ ].join("\n");
547
+ }
514
548
  /**
515
549
  * Signs (and caches until near expiry) the `playSessionToken` required by
516
550
  * `trackPlay`/`trackMatch`/`emit` for the current play session — does not
@@ -519,12 +553,20 @@ var ApiClient = class {
519
553
  */
520
554
  async ensurePlaySession(gameKey, auth, playSessionId) {
521
555
  const now = Date.now();
522
- if (this.playSession && this.playSession.gameKey === gameKey && this.playSession.playSessionId === playSessionId && this.playSession.expiresAtMs > now + 1e4) {
556
+ const cacheKey = [gameKey, playSessionId, this.getTrackingAuthKey(auth)].join("\n");
557
+ if (this.playSession && this.playSession.cacheKey === cacheKey && this.playSession.gameKey === gameKey && this.playSession.playSessionId === playSessionId && this.playSession.expiresAtMs > now + 1e4) {
523
558
  return this.playSession;
524
559
  }
525
- if (!this.playSessionPromise) {
526
- this.playSessionPromise = this.raw.startPlaySession(gameKey, auth, { playSessionId }).then((dto) => {
560
+ const previous = this.playSession && this.playSession.cacheKey === cacheKey && this.playSession.gameKey === gameKey && this.playSession.playSessionId === playSessionId ? this.playSession : void 0;
561
+ if (!this.playSessionPromise || this.playSessionPromiseKey !== cacheKey) {
562
+ this.playSessionPromiseKey = cacheKey;
563
+ const issue = previous ? this.raw.renewPlaySession(gameKey, auth, {
564
+ playSessionId,
565
+ playSessionToken: previous.playSessionToken
566
+ }).catch(() => this.raw.startPlaySession(gameKey, auth, { playSessionId })) : this.raw.startPlaySession(gameKey, auth, { playSessionId });
567
+ this.playSessionPromise = issue.then((dto) => {
527
568
  this.playSession = {
569
+ cacheKey,
528
570
  gameKey,
529
571
  playSessionId: dto.playSessionId,
530
572
  playSessionToken: dto.playSessionToken,
@@ -533,6 +575,7 @@ var ApiClient = class {
533
575
  return this.playSession;
534
576
  }).finally(() => {
535
577
  this.playSessionPromise = void 0;
578
+ this.playSessionPromiseKey = void 0;
536
579
  });
537
580
  }
538
581
  return this.playSessionPromise;
@@ -731,9 +774,23 @@ var LooplaySDK = class {
731
774
  if (!this.api) throw new MissingBaseUrlError();
732
775
  await this.api.listGameAssets(params.gameId);
733
776
  }
777
+ /**
778
+ * Best-effort self-report — must never block init(). A missing/misconfigured
779
+ * whitelist (a separate setup step from SDK integration) makes the backend
780
+ * reject this call; if that rejection propagated, the whole init() would
781
+ * reject too and every other SDK method would throw NotInitializedError,
782
+ * breaking a game over what's really just an unfinished onboarding step.
783
+ */
734
784
  async detectIntegration(params) {
735
785
  if (!this.api) return;
736
- await this.api.detectIntegration(params.gameId);
786
+ try {
787
+ await this.api.detectIntegration(params.gameId);
788
+ } catch (error) {
789
+ console.warn(
790
+ "[LooplaySDK] detectIntegration failed (SDK will still initialize) \u2014 check that this game has a whitelisted origin/domain configured:",
791
+ error
792
+ );
793
+ }
737
794
  }
738
795
  };
739
796
 
@@ -1105,7 +1162,16 @@ var LooplayIframeAuth = class {
1105
1162
  this.debug("emitGameEvent dispatched", { gameId, actionCode, ...opts });
1106
1163
  return this.getClient().emit(gameId, actionCode, opts);
1107
1164
  }
1108
- /** Idempotent; auto-emits GAME_STARTED exactly once per distinct auth session (bearer or anonymous). */
1165
+ /**
1166
+ * Idempotent; auto-emits GAME_STARTED exactly once per distinct auth
1167
+ * session (bearer or anonymous). SDK integration self-reporting for the
1168
+ * iframe path lives in `detectIntegrationOnce()` (fired from `setState()`
1169
+ * on every auth/config change, not gated behind lifecycle tracking being
1170
+ * enabled at all) — without it, games embedded via iframe would never
1171
+ * trigger the active detect call, only the passive heartbeat that
1172
+ * tracking calls leave behind (which doesn't fire while the game is still
1173
+ * IN_REVIEW/not LIVE, since tracking is blocked until then).
1174
+ */
1109
1175
  initLifecycleTracking() {
1110
1176
  if (this.lifecycleTrackingInitialized || typeof window === "undefined")
1111
1177
  return;
@@ -1234,8 +1300,11 @@ ${identity}`;
1234
1300
  }
1235
1301
  }
1236
1302
  setState(next) {
1303
+ const currentApiUrl = this.getApiUrl();
1237
1304
  this.state = next;
1238
- this.client = null;
1305
+ if (this.getApiUrl() !== currentApiUrl) {
1306
+ this.client = null;
1307
+ }
1239
1308
  this.debug("auth state updated", {
1240
1309
  gameId: this.getGameId(),
1241
1310
  apiUrl: this.getApiUrl(),
@@ -1310,6 +1379,6 @@ ${gameId}`;
1310
1379
  }
1311
1380
  };
1312
1381
 
1313
- export { ApiClient, BrowserLocalStorageAuthStorage, HttpClient, HttpError, LooplayAuth, LooplayIframeAuth, LooplaySDK, LooplaySDKError, MemoryAuthStorage, MissingAuthError, MissingBaseUrlError, NotAuthenticatedError, NotInitializedError, ServiceClient, TelegramAuthProvider };
1382
+ export { ApiClient, BrowserLocalStorageAuthStorage, HttpClient, HttpError, LOOPLAY_SDK_VERSION, LooplayAuth, LooplayIframeAuth, LooplaySDK, LooplaySDKError, MemoryAuthStorage, MissingAuthError, MissingBaseUrlError, NotAuthenticatedError, NotInitializedError, ServiceClient, TelegramAuthProvider };
1314
1383
  //# sourceMappingURL=looplay-sdk.esm.js.map
1315
1384
  //# sourceMappingURL=looplay-sdk.esm.js.map