@looplay/sdk 0.8.9 → 0.8.11

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.
@@ -140,8 +140,45 @@ var LooplayAuth = class {
140
140
  }
141
141
  };
142
142
 
143
+ // src/constants.ts
144
+ var DEFAULT_API_URL = "https://miniapp-api.looplay.gg";
145
+ function resolveEnvParentOrigin() {
146
+ try {
147
+ if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('looplay-sdk.cjs.js', document.baseURI).href)) }) !== "undefined" && undefined?.VITE_PARENT_ORIGIN) {
148
+ const val = undefined.VITE_PARENT_ORIGIN;
149
+ if (typeof val === "string" && val.trim().length > 0) return val.trim();
150
+ }
151
+ } catch {
152
+ }
153
+ try {
154
+ if (typeof process !== "undefined" && process.env?.VITE_PARENT_ORIGIN) {
155
+ const val = process.env.VITE_PARENT_ORIGIN;
156
+ if (typeof val === "string" && val.trim().length > 0) return val.trim();
157
+ }
158
+ } catch {
159
+ }
160
+ return void 0;
161
+ }
162
+ function resolveEnvApiUrl() {
163
+ try {
164
+ if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('looplay-sdk.cjs.js', document.baseURI).href)) }) !== "undefined" && undefined) {
165
+ const val = undefined.VITE_API_URL ?? undefined.VITE_LOOPLAY_API_URL;
166
+ if (typeof val === "string" && val.trim().length > 0) return val.trim();
167
+ }
168
+ } catch {
169
+ }
170
+ try {
171
+ if (typeof process !== "undefined" && process.env) {
172
+ const val = process.env.VITE_API_URL ?? process.env.VITE_LOOPLAY_API_URL;
173
+ if (typeof val === "string" && val.trim().length > 0) return val.trim();
174
+ }
175
+ } catch {
176
+ }
177
+ return void 0;
178
+ }
179
+
143
180
  // src/version.ts
144
- var LOOPLAY_SDK_VERSION = "0.8.5";
181
+ var LOOPLAY_SDK_VERSION = "0.8.11";
145
182
 
146
183
  // src/apps/http.ts
147
184
  function getFetchFn(fetchOverride) {
@@ -167,8 +204,9 @@ var HttpClient = class {
167
204
  baseUrl;
168
205
  fetchFn;
169
206
  defaultHeaders;
170
- constructor(options) {
171
- this.baseUrl = options.baseUrl.replace(/\/$/, "");
207
+ constructor(options = {}) {
208
+ const rawBaseUrl = typeof options.baseUrl === "string" && options.baseUrl.trim().length > 0 ? options.baseUrl.trim() : resolveEnvApiUrl() ?? DEFAULT_API_URL;
209
+ this.baseUrl = rawBaseUrl.replace(/\/$/, "");
172
210
  this.fetchFn = getFetchFn(options.fetch);
173
211
  this.defaultHeaders = {
174
212
  "x-looplay-sdk-version": LOOPLAY_SDK_VERSION,
@@ -234,9 +272,18 @@ var HttpClient = class {
234
272
  // src/apps/service-client.ts
235
273
  var ServiceClient = class {
236
274
  http;
275
+ playSessionTokens = /* @__PURE__ */ new Map();
276
+ onPlaySessionIssuedListener;
237
277
  constructor(options) {
238
278
  this.http = new HttpClient(options);
239
279
  }
280
+ /**
281
+ * Internal hook for ApiClient to subscribe to issued/renewed play sessions.
282
+ * @internal
283
+ */
284
+ _setOnPlaySessionIssued(listener) {
285
+ this.onPlaySessionIssuedListener = listener;
286
+ }
240
287
  // ─────────────────────────────────────────────────────────────────────────────
241
288
  // Auth
242
289
  // ─────────────────────────────────────────────────────────────────────────────
@@ -270,11 +317,16 @@ var ServiceClient = class {
270
317
  }
271
318
  /** Signs a fresh `playSessionToken` — required by `completePlaySession`/`completeGameplayMatch`/`recordGameplayAction`. */
272
319
  async startPlaySession(gameKey, auth = {}, body = {}) {
273
- return this.http.request("POST", "/sdk/games/play-sessions/start", {
320
+ const dto = await this.http.request("POST", "/sdk/games/play-sessions/start", {
274
321
  ...auth,
275
322
  headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
276
323
  body
277
324
  });
325
+ if (dto?.playSessionId && dto?.playSessionToken) {
326
+ this.playSessionTokens.set(dto.playSessionId, dto.playSessionToken);
327
+ }
328
+ this.onPlaySessionIssuedListener?.(dto, gameKey, auth);
329
+ return dto;
278
330
  }
279
331
  /**
280
332
  * Re-issues a fresh `playSessionToken` for the SAME `playSessionId` — call
@@ -283,33 +335,44 @@ var ServiceClient = class {
283
335
  * long-running sessions keep a single `playSessionId` throughout.
284
336
  */
285
337
  async renewPlaySession(gameKey, auth = {}, body) {
286
- return this.http.request("POST", "/sdk/games/play-sessions/renew", {
338
+ const dto = await this.http.request("POST", "/sdk/games/play-sessions/renew", {
287
339
  ...auth,
288
340
  headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
289
341
  body
290
342
  });
343
+ if (dto?.playSessionId && dto?.playSessionToken) {
344
+ this.playSessionTokens.set(dto.playSessionId, dto.playSessionToken);
345
+ }
346
+ this.onPlaySessionIssuedListener?.(dto, gameKey, auth);
347
+ return dto;
291
348
  }
292
349
  async completePlaySession(gameKey, auth, body) {
350
+ const playSessionToken = body.playSessionToken || (body.playSessionId ? this.playSessionTokens.get(body.playSessionId) : void 0);
351
+ const resolvedBody = playSessionToken ? { ...body, playSessionToken } : body;
293
352
  await this.http.request("POST", "/sdk/games/play-sessions/complete", {
294
353
  ...auth,
295
354
  headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
296
- body
355
+ body: resolvedBody
297
356
  });
298
357
  return true;
299
358
  }
300
359
  async completeGameplayMatch(gameKey, auth, body) {
360
+ const playSessionToken = body.playSessionToken || (body.playSessionId ? this.playSessionTokens.get(body.playSessionId) : void 0);
361
+ const resolvedBody = playSessionToken ? { ...body, playSessionToken } : body;
301
362
  await this.http.request("POST", "/sdk/games/matches/complete", {
302
363
  ...auth,
303
364
  headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
304
- body
365
+ body: resolvedBody
305
366
  });
306
367
  return true;
307
368
  }
308
369
  async recordGameplayAction(gameKey, auth, body) {
370
+ const playSessionToken = body.playSessionToken || (body.playSessionId ? this.playSessionTokens.get(body.playSessionId) : void 0);
371
+ const resolvedBody = playSessionToken ? { ...body, playSessionToken } : body;
309
372
  return this.http.request("POST", "/sdk/games/actions/record", {
310
373
  ...auth,
311
374
  headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
312
- body
375
+ body: resolvedBody
313
376
  });
314
377
  }
315
378
  /**
@@ -464,13 +527,26 @@ var ApiClient = class {
464
527
  playSession;
465
528
  playSessionPromiseKey;
466
529
  playSessionPromise;
467
- constructor(options) {
468
- if (!options.baseUrl) throw new MissingBaseUrlError();
530
+ constructor(options = {}) {
531
+ const baseUrl = typeof options.baseUrl === "string" && options.baseUrl.trim().length > 0 ? options.baseUrl.trim() : resolveEnvApiUrl() ?? DEFAULT_API_URL;
532
+ if (!baseUrl) throw new MissingBaseUrlError();
469
533
  this.raw = new ServiceClient({
470
- baseUrl: options.baseUrl,
534
+ baseUrl,
471
535
  fetch: options.fetch,
472
536
  defaultHeaders: options.defaultHeaders
473
537
  });
538
+ this.raw._setOnPlaySessionIssued((dto, gameKey, auth) => {
539
+ if (dto?.playSessionId && dto?.playSessionToken) {
540
+ this.currentPlaySessionId = dto.playSessionId;
541
+ this.playSession = {
542
+ cacheKey: [gameKey, dto.playSessionId, this.getTrackingAuthKey(auth)].join(":"),
543
+ gameKey,
544
+ playSessionId: dto.playSessionId,
545
+ playSessionToken: dto.playSessionToken,
546
+ expiresAtMs: dto.expiresAt ? new Date(dto.expiresAt).getTime() : Date.now() + 2 * 3600 * 1e3
547
+ };
548
+ }
549
+ });
474
550
  this.getAccessToken = options.getAccessToken;
475
551
  this.getAnonymousId = options.getAnonymousId;
476
552
  }
@@ -602,6 +678,31 @@ var ApiClient = class {
602
678
  this.playSession = void 0;
603
679
  return this.currentPlaySessionId;
604
680
  }
681
+ /**
682
+ * Starts a new play session on the server and caches the signed immediately.
683
+ */
684
+ async startPlaySessionAsync(gameKey, body = {}) {
685
+ const auth = await this.resolveTrackingAuth();
686
+ const playSessionId = body.playSessionId ?? this.currentPlaySessionId ?? this.startPlaySession();
687
+ return this.raw.startPlaySession(gameKey, auth, { ...body, playSessionId });
688
+ }
689
+ /**
690
+ * Explicitly sets or updates the active play session token cache.
691
+ */
692
+ setPlaySession(playSession) {
693
+ this.currentPlaySessionId = playSession.playSessionId;
694
+ this.playSession = {
695
+ cacheKey: [playSession.gameKey, playSession.playSessionId].join(":"),
696
+ gameKey: playSession.gameKey,
697
+ playSessionId: playSession.playSessionId,
698
+ playSessionToken: playSession.playSessionToken,
699
+ expiresAtMs: playSession.expiresAtMs ?? Date.now() + 2 * 3600 * 1e3
700
+ };
701
+ }
702
+ /** Returns the active cached play session, or undefined if none has been issued yet. */
703
+ getCurrentPlaySession() {
704
+ return this.playSession;
705
+ }
605
706
  async trackPlay(gameKey, playTimeSeconds) {
606
707
  const auth = await this.resolveTrackingAuth();
607
708
  const playSessionId = this.currentPlaySessionId ?? this.startPlaySession();
@@ -685,43 +786,6 @@ var ApiClient = class {
685
786
  }
686
787
  };
687
788
 
688
- // src/constants.ts
689
- var DEFAULT_API_URL = "https://miniapp-api.looplay.gg";
690
- function resolveEnvParentOrigin() {
691
- try {
692
- if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('looplay-sdk.cjs.js', document.baseURI).href)) }) !== "undefined" && undefined?.VITE_PARENT_ORIGIN) {
693
- const val = undefined.VITE_PARENT_ORIGIN;
694
- if (typeof val === "string" && val.trim().length > 0) return val.trim();
695
- }
696
- } catch {
697
- }
698
- try {
699
- if (typeof process !== "undefined" && process.env?.VITE_PARENT_ORIGIN) {
700
- const val = process.env.VITE_PARENT_ORIGIN;
701
- if (typeof val === "string" && val.trim().length > 0) return val.trim();
702
- }
703
- } catch {
704
- }
705
- return void 0;
706
- }
707
- function resolveEnvApiUrl() {
708
- try {
709
- if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('looplay-sdk.cjs.js', document.baseURI).href)) }) !== "undefined" && undefined) {
710
- const val = undefined.VITE_API_URL ?? undefined.VITE_LOOPLAY_API_URL;
711
- if (typeof val === "string" && val.trim().length > 0) return val.trim();
712
- }
713
- } catch {
714
- }
715
- try {
716
- if (typeof process !== "undefined" && process.env) {
717
- const val = process.env.VITE_API_URL ?? process.env.VITE_LOOPLAY_API_URL;
718
- if (typeof val === "string" && val.trim().length > 0) return val.trim();
719
- }
720
- } catch {
721
- }
722
- return void 0;
723
- }
724
-
725
789
  // src/looplay-sdk.ts
726
790
  var LooplaySDK = class {
727
791
  auth;
@@ -731,21 +795,19 @@ var LooplaySDK = class {
731
795
  constructor(options = {}) {
732
796
  const {
733
797
  auth,
734
- baseUrl = resolveEnvApiUrl() ?? DEFAULT_API_URL,
735
798
  fetch,
736
799
  defaultHeaders
737
800
  } = options;
801
+ const baseUrl = typeof options.baseUrl === "string" && options.baseUrl.trim().length > 0 ? options.baseUrl.trim() : resolveEnvApiUrl() ?? DEFAULT_API_URL;
738
802
  if (auth) {
739
803
  this.auth = new LooplayAuth(auth);
740
804
  }
741
- if (baseUrl) {
742
- this.api = new ApiClient({
743
- baseUrl,
744
- fetch,
745
- defaultHeaders,
746
- getAccessToken: async () => this.auth?.getAccessToken()
747
- });
748
- }
805
+ this.api = new ApiClient({
806
+ baseUrl,
807
+ fetch,
808
+ defaultHeaders,
809
+ getAccessToken: async () => this.auth?.getAccessToken()
810
+ });
749
811
  }
750
812
  async init(params) {
751
813
  this.gameId = params.gameId;
@@ -760,6 +822,15 @@ var LooplaySDK = class {
760
822
  getGameId() {
761
823
  return this.gameId;
762
824
  }
825
+ get currentPlaySessionId() {
826
+ return this.api?.currentPlaySessionId;
827
+ }
828
+ get playSession() {
829
+ return this.api?.playSession;
830
+ }
831
+ unsafeRaw() {
832
+ return this.api?.unsafeRaw();
833
+ }
763
834
  async getMyProfile() {
764
835
  this.assertInitialized();
765
836
  if (!this.api) throw new MissingBaseUrlError();
@@ -778,6 +849,16 @@ var LooplaySDK = class {
778
849
  if (!this.api) throw new MissingBaseUrlError();
779
850
  return this.api.startPlaySession();
780
851
  }
852
+ /**
853
+ * Starts a new play session asynchronously on the backend; returns the issued PlaySessionDto
854
+ * (containing playSessionId and playSessionToken).
855
+ */
856
+ async startPlaySessionAsync(body) {
857
+ this.assertInitialized();
858
+ if (!this.api) throw new MissingBaseUrlError();
859
+ if (!this.gameId) throw new NotInitializedError();
860
+ return this.api.startPlaySessionAsync(this.gameId, body);
861
+ }
781
862
  async trackPlay(playTimeSeconds) {
782
863
  this.assertInitialized();
783
864
  if (!this.api) throw new MissingBaseUrlError();
@@ -1007,7 +1088,8 @@ var LooplayIframeAuth = class {
1007
1088
  return this.state.jwt;
1008
1089
  }
1009
1090
  getApiUrl() {
1010
- return this.state.apiUrl ?? this.options.apiUrl ?? resolveEnvApiUrl() ?? DEFAULT_API_URL;
1091
+ const raw = toOptionalString(this.state.apiUrl) ?? toOptionalString(this.options.apiUrl) ?? resolveEnvApiUrl() ?? DEFAULT_API_URL;
1092
+ return raw.trim();
1011
1093
  }
1012
1094
  /** Resolves to the configured `appId`, falling back to whatever the parent pushed as `gameId`. */
1013
1095
  getGameId() {
@@ -1153,6 +1235,15 @@ var LooplayIframeAuth = class {
1153
1235
  });
1154
1236
  return this.client;
1155
1237
  }
1238
+ get currentPlaySessionId() {
1239
+ return this.getClient().currentPlaySessionId;
1240
+ }
1241
+ get playSession() {
1242
+ return this.getClient().playSession;
1243
+ }
1244
+ unsafeRaw() {
1245
+ return this.getClient().unsafeRaw();
1246
+ }
1156
1247
  /** Call once when the game view opens, before any play/match tracking. */
1157
1248
  trackView() {
1158
1249
  const gameId = this.getGameId();
@@ -1169,6 +1260,20 @@ var LooplayIframeAuth = class {
1169
1260
  startPlaySession() {
1170
1261
  return this.getClient().startPlaySession();
1171
1262
  }
1263
+ /**
1264
+ * Starts a new play session asynchronously on the backend; returns the issued PlaySessionDto
1265
+ * (containing playSessionId and playSessionToken) or null if auth/tracking is not ready.
1266
+ */
1267
+ async startPlaySessionAsync(body) {
1268
+ const gameId = this.getGameId();
1269
+ if (!this.canTrack() || !gameId) {
1270
+ this.debug("startPlaySessionAsync skipped; auth is not ready", {
1271
+ hasGameId: Boolean(gameId)
1272
+ });
1273
+ return null;
1274
+ }
1275
+ return this.getClient().startPlaySessionAsync(gameId, body);
1276
+ }
1172
1277
  trackPlay(playTimeSeconds) {
1173
1278
  const gameId = this.getGameId();
1174
1279
  if (!this.canTrack() || !gameId) {
@@ -1382,7 +1487,10 @@ ${gameId}`;
1382
1487
  }
1383
1488
  }
1384
1489
  resolveParentOrigin() {
1385
- if (this.options.parentOrigin) return this.options.parentOrigin;
1490
+ if (this.options.parentOrigin) {
1491
+ const opt = toOptionalString(this.options.parentOrigin);
1492
+ if (opt) return opt;
1493
+ }
1386
1494
  const envOrigin = resolveEnvParentOrigin();
1387
1495
  if (envOrigin) return envOrigin;
1388
1496
  if (typeof document === "undefined" || !document.referrer) return null;