@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.
@@ -137,8 +137,45 @@ var LooplayAuth = class {
137
137
  }
138
138
  };
139
139
 
140
+ // src/constants.ts
141
+ var DEFAULT_API_URL = "https://miniapp-api.looplay.gg";
142
+ function resolveEnvParentOrigin() {
143
+ try {
144
+ if (typeof import.meta !== "undefined" && import.meta.env?.VITE_PARENT_ORIGIN) {
145
+ const val = import.meta.env.VITE_PARENT_ORIGIN;
146
+ if (typeof val === "string" && val.trim().length > 0) return val.trim();
147
+ }
148
+ } catch {
149
+ }
150
+ try {
151
+ if (typeof process !== "undefined" && process.env?.VITE_PARENT_ORIGIN) {
152
+ const val = process.env.VITE_PARENT_ORIGIN;
153
+ if (typeof val === "string" && val.trim().length > 0) return val.trim();
154
+ }
155
+ } catch {
156
+ }
157
+ return void 0;
158
+ }
159
+ function resolveEnvApiUrl() {
160
+ try {
161
+ if (typeof import.meta !== "undefined" && import.meta.env) {
162
+ const val = import.meta.env.VITE_API_URL ?? import.meta.env.VITE_LOOPLAY_API_URL;
163
+ if (typeof val === "string" && val.trim().length > 0) return val.trim();
164
+ }
165
+ } catch {
166
+ }
167
+ try {
168
+ if (typeof process !== "undefined" && process.env) {
169
+ const val = process.env.VITE_API_URL ?? process.env.VITE_LOOPLAY_API_URL;
170
+ if (typeof val === "string" && val.trim().length > 0) return val.trim();
171
+ }
172
+ } catch {
173
+ }
174
+ return void 0;
175
+ }
176
+
140
177
  // src/version.ts
141
- var LOOPLAY_SDK_VERSION = "0.8.5";
178
+ var LOOPLAY_SDK_VERSION = "0.8.11";
142
179
 
143
180
  // src/apps/http.ts
144
181
  function getFetchFn(fetchOverride) {
@@ -164,8 +201,9 @@ var HttpClient = class {
164
201
  baseUrl;
165
202
  fetchFn;
166
203
  defaultHeaders;
167
- constructor(options) {
168
- this.baseUrl = options.baseUrl.replace(/\/$/, "");
204
+ constructor(options = {}) {
205
+ const rawBaseUrl = typeof options.baseUrl === "string" && options.baseUrl.trim().length > 0 ? options.baseUrl.trim() : resolveEnvApiUrl() ?? DEFAULT_API_URL;
206
+ this.baseUrl = rawBaseUrl.replace(/\/$/, "");
169
207
  this.fetchFn = getFetchFn(options.fetch);
170
208
  this.defaultHeaders = {
171
209
  "x-looplay-sdk-version": LOOPLAY_SDK_VERSION,
@@ -231,9 +269,18 @@ var HttpClient = class {
231
269
  // src/apps/service-client.ts
232
270
  var ServiceClient = class {
233
271
  http;
272
+ playSessionTokens = /* @__PURE__ */ new Map();
273
+ onPlaySessionIssuedListener;
234
274
  constructor(options) {
235
275
  this.http = new HttpClient(options);
236
276
  }
277
+ /**
278
+ * Internal hook for ApiClient to subscribe to issued/renewed play sessions.
279
+ * @internal
280
+ */
281
+ _setOnPlaySessionIssued(listener) {
282
+ this.onPlaySessionIssuedListener = listener;
283
+ }
237
284
  // ─────────────────────────────────────────────────────────────────────────────
238
285
  // Auth
239
286
  // ─────────────────────────────────────────────────────────────────────────────
@@ -267,11 +314,16 @@ var ServiceClient = class {
267
314
  }
268
315
  /** Signs a fresh `playSessionToken` — required by `completePlaySession`/`completeGameplayMatch`/`recordGameplayAction`. */
269
316
  async startPlaySession(gameKey, auth = {}, body = {}) {
270
- return this.http.request("POST", "/sdk/games/play-sessions/start", {
317
+ const dto = await this.http.request("POST", "/sdk/games/play-sessions/start", {
271
318
  ...auth,
272
319
  headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
273
320
  body
274
321
  });
322
+ if (dto?.playSessionId && dto?.playSessionToken) {
323
+ this.playSessionTokens.set(dto.playSessionId, dto.playSessionToken);
324
+ }
325
+ this.onPlaySessionIssuedListener?.(dto, gameKey, auth);
326
+ return dto;
275
327
  }
276
328
  /**
277
329
  * Re-issues a fresh `playSessionToken` for the SAME `playSessionId` — call
@@ -280,33 +332,44 @@ var ServiceClient = class {
280
332
  * long-running sessions keep a single `playSessionId` throughout.
281
333
  */
282
334
  async renewPlaySession(gameKey, auth = {}, body) {
283
- return this.http.request("POST", "/sdk/games/play-sessions/renew", {
335
+ const dto = await this.http.request("POST", "/sdk/games/play-sessions/renew", {
284
336
  ...auth,
285
337
  headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
286
338
  body
287
339
  });
340
+ if (dto?.playSessionId && dto?.playSessionToken) {
341
+ this.playSessionTokens.set(dto.playSessionId, dto.playSessionToken);
342
+ }
343
+ this.onPlaySessionIssuedListener?.(dto, gameKey, auth);
344
+ return dto;
288
345
  }
289
346
  async completePlaySession(gameKey, auth, body) {
347
+ const playSessionToken = body.playSessionToken || (body.playSessionId ? this.playSessionTokens.get(body.playSessionId) : void 0);
348
+ const resolvedBody = playSessionToken ? { ...body, playSessionToken } : body;
290
349
  await this.http.request("POST", "/sdk/games/play-sessions/complete", {
291
350
  ...auth,
292
351
  headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
293
- body
352
+ body: resolvedBody
294
353
  });
295
354
  return true;
296
355
  }
297
356
  async completeGameplayMatch(gameKey, auth, body) {
357
+ const playSessionToken = body.playSessionToken || (body.playSessionId ? this.playSessionTokens.get(body.playSessionId) : void 0);
358
+ const resolvedBody = playSessionToken ? { ...body, playSessionToken } : body;
298
359
  await this.http.request("POST", "/sdk/games/matches/complete", {
299
360
  ...auth,
300
361
  headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
301
- body
362
+ body: resolvedBody
302
363
  });
303
364
  return true;
304
365
  }
305
366
  async recordGameplayAction(gameKey, auth, body) {
367
+ const playSessionToken = body.playSessionToken || (body.playSessionId ? this.playSessionTokens.get(body.playSessionId) : void 0);
368
+ const resolvedBody = playSessionToken ? { ...body, playSessionToken } : body;
306
369
  return this.http.request("POST", "/sdk/games/actions/record", {
307
370
  ...auth,
308
371
  headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
309
- body
372
+ body: resolvedBody
310
373
  });
311
374
  }
312
375
  /**
@@ -461,13 +524,26 @@ var ApiClient = class {
461
524
  playSession;
462
525
  playSessionPromiseKey;
463
526
  playSessionPromise;
464
- constructor(options) {
465
- if (!options.baseUrl) throw new MissingBaseUrlError();
527
+ constructor(options = {}) {
528
+ const baseUrl = typeof options.baseUrl === "string" && options.baseUrl.trim().length > 0 ? options.baseUrl.trim() : resolveEnvApiUrl() ?? DEFAULT_API_URL;
529
+ if (!baseUrl) throw new MissingBaseUrlError();
466
530
  this.raw = new ServiceClient({
467
- baseUrl: options.baseUrl,
531
+ baseUrl,
468
532
  fetch: options.fetch,
469
533
  defaultHeaders: options.defaultHeaders
470
534
  });
535
+ this.raw._setOnPlaySessionIssued((dto, gameKey, auth) => {
536
+ if (dto?.playSessionId && dto?.playSessionToken) {
537
+ this.currentPlaySessionId = dto.playSessionId;
538
+ this.playSession = {
539
+ cacheKey: [gameKey, dto.playSessionId, this.getTrackingAuthKey(auth)].join(":"),
540
+ gameKey,
541
+ playSessionId: dto.playSessionId,
542
+ playSessionToken: dto.playSessionToken,
543
+ expiresAtMs: dto.expiresAt ? new Date(dto.expiresAt).getTime() : Date.now() + 2 * 3600 * 1e3
544
+ };
545
+ }
546
+ });
471
547
  this.getAccessToken = options.getAccessToken;
472
548
  this.getAnonymousId = options.getAnonymousId;
473
549
  }
@@ -599,6 +675,31 @@ var ApiClient = class {
599
675
  this.playSession = void 0;
600
676
  return this.currentPlaySessionId;
601
677
  }
678
+ /**
679
+ * Starts a new play session on the server and caches the signed immediately.
680
+ */
681
+ async startPlaySessionAsync(gameKey, body = {}) {
682
+ const auth = await this.resolveTrackingAuth();
683
+ const playSessionId = body.playSessionId ?? this.currentPlaySessionId ?? this.startPlaySession();
684
+ return this.raw.startPlaySession(gameKey, auth, { ...body, playSessionId });
685
+ }
686
+ /**
687
+ * Explicitly sets or updates the active play session token cache.
688
+ */
689
+ setPlaySession(playSession) {
690
+ this.currentPlaySessionId = playSession.playSessionId;
691
+ this.playSession = {
692
+ cacheKey: [playSession.gameKey, playSession.playSessionId].join(":"),
693
+ gameKey: playSession.gameKey,
694
+ playSessionId: playSession.playSessionId,
695
+ playSessionToken: playSession.playSessionToken,
696
+ expiresAtMs: playSession.expiresAtMs ?? Date.now() + 2 * 3600 * 1e3
697
+ };
698
+ }
699
+ /** Returns the active cached play session, or undefined if none has been issued yet. */
700
+ getCurrentPlaySession() {
701
+ return this.playSession;
702
+ }
602
703
  async trackPlay(gameKey, playTimeSeconds) {
603
704
  const auth = await this.resolveTrackingAuth();
604
705
  const playSessionId = this.currentPlaySessionId ?? this.startPlaySession();
@@ -682,43 +783,6 @@ var ApiClient = class {
682
783
  }
683
784
  };
684
785
 
685
- // src/constants.ts
686
- var DEFAULT_API_URL = "https://miniapp-api.looplay.gg";
687
- function resolveEnvParentOrigin() {
688
- try {
689
- if (typeof import.meta !== "undefined" && import.meta.env?.VITE_PARENT_ORIGIN) {
690
- const val = import.meta.env.VITE_PARENT_ORIGIN;
691
- if (typeof val === "string" && val.trim().length > 0) return val.trim();
692
- }
693
- } catch {
694
- }
695
- try {
696
- if (typeof process !== "undefined" && process.env?.VITE_PARENT_ORIGIN) {
697
- const val = process.env.VITE_PARENT_ORIGIN;
698
- if (typeof val === "string" && val.trim().length > 0) return val.trim();
699
- }
700
- } catch {
701
- }
702
- return void 0;
703
- }
704
- function resolveEnvApiUrl() {
705
- try {
706
- if (typeof import.meta !== "undefined" && import.meta.env) {
707
- const val = import.meta.env.VITE_API_URL ?? import.meta.env.VITE_LOOPLAY_API_URL;
708
- if (typeof val === "string" && val.trim().length > 0) return val.trim();
709
- }
710
- } catch {
711
- }
712
- try {
713
- if (typeof process !== "undefined" && process.env) {
714
- const val = process.env.VITE_API_URL ?? process.env.VITE_LOOPLAY_API_URL;
715
- if (typeof val === "string" && val.trim().length > 0) return val.trim();
716
- }
717
- } catch {
718
- }
719
- return void 0;
720
- }
721
-
722
786
  // src/looplay-sdk.ts
723
787
  var LooplaySDK = class {
724
788
  auth;
@@ -728,21 +792,19 @@ var LooplaySDK = class {
728
792
  constructor(options = {}) {
729
793
  const {
730
794
  auth,
731
- baseUrl = resolveEnvApiUrl() ?? DEFAULT_API_URL,
732
795
  fetch,
733
796
  defaultHeaders
734
797
  } = options;
798
+ const baseUrl = typeof options.baseUrl === "string" && options.baseUrl.trim().length > 0 ? options.baseUrl.trim() : resolveEnvApiUrl() ?? DEFAULT_API_URL;
735
799
  if (auth) {
736
800
  this.auth = new LooplayAuth(auth);
737
801
  }
738
- if (baseUrl) {
739
- this.api = new ApiClient({
740
- baseUrl,
741
- fetch,
742
- defaultHeaders,
743
- getAccessToken: async () => this.auth?.getAccessToken()
744
- });
745
- }
802
+ this.api = new ApiClient({
803
+ baseUrl,
804
+ fetch,
805
+ defaultHeaders,
806
+ getAccessToken: async () => this.auth?.getAccessToken()
807
+ });
746
808
  }
747
809
  async init(params) {
748
810
  this.gameId = params.gameId;
@@ -757,6 +819,15 @@ var LooplaySDK = class {
757
819
  getGameId() {
758
820
  return this.gameId;
759
821
  }
822
+ get currentPlaySessionId() {
823
+ return this.api?.currentPlaySessionId;
824
+ }
825
+ get playSession() {
826
+ return this.api?.playSession;
827
+ }
828
+ unsafeRaw() {
829
+ return this.api?.unsafeRaw();
830
+ }
760
831
  async getMyProfile() {
761
832
  this.assertInitialized();
762
833
  if (!this.api) throw new MissingBaseUrlError();
@@ -775,6 +846,16 @@ var LooplaySDK = class {
775
846
  if (!this.api) throw new MissingBaseUrlError();
776
847
  return this.api.startPlaySession();
777
848
  }
849
+ /**
850
+ * Starts a new play session asynchronously on the backend; returns the issued PlaySessionDto
851
+ * (containing playSessionId and playSessionToken).
852
+ */
853
+ async startPlaySessionAsync(body) {
854
+ this.assertInitialized();
855
+ if (!this.api) throw new MissingBaseUrlError();
856
+ if (!this.gameId) throw new NotInitializedError();
857
+ return this.api.startPlaySessionAsync(this.gameId, body);
858
+ }
778
859
  async trackPlay(playTimeSeconds) {
779
860
  this.assertInitialized();
780
861
  if (!this.api) throw new MissingBaseUrlError();
@@ -1004,7 +1085,8 @@ var LooplayIframeAuth = class {
1004
1085
  return this.state.jwt;
1005
1086
  }
1006
1087
  getApiUrl() {
1007
- return this.state.apiUrl ?? this.options.apiUrl ?? resolveEnvApiUrl() ?? DEFAULT_API_URL;
1088
+ const raw = toOptionalString(this.state.apiUrl) ?? toOptionalString(this.options.apiUrl) ?? resolveEnvApiUrl() ?? DEFAULT_API_URL;
1089
+ return raw.trim();
1008
1090
  }
1009
1091
  /** Resolves to the configured `appId`, falling back to whatever the parent pushed as `gameId`. */
1010
1092
  getGameId() {
@@ -1150,6 +1232,15 @@ var LooplayIframeAuth = class {
1150
1232
  });
1151
1233
  return this.client;
1152
1234
  }
1235
+ get currentPlaySessionId() {
1236
+ return this.getClient().currentPlaySessionId;
1237
+ }
1238
+ get playSession() {
1239
+ return this.getClient().playSession;
1240
+ }
1241
+ unsafeRaw() {
1242
+ return this.getClient().unsafeRaw();
1243
+ }
1153
1244
  /** Call once when the game view opens, before any play/match tracking. */
1154
1245
  trackView() {
1155
1246
  const gameId = this.getGameId();
@@ -1166,6 +1257,20 @@ var LooplayIframeAuth = class {
1166
1257
  startPlaySession() {
1167
1258
  return this.getClient().startPlaySession();
1168
1259
  }
1260
+ /**
1261
+ * Starts a new play session asynchronously on the backend; returns the issued PlaySessionDto
1262
+ * (containing playSessionId and playSessionToken) or null if auth/tracking is not ready.
1263
+ */
1264
+ async startPlaySessionAsync(body) {
1265
+ const gameId = this.getGameId();
1266
+ if (!this.canTrack() || !gameId) {
1267
+ this.debug("startPlaySessionAsync skipped; auth is not ready", {
1268
+ hasGameId: Boolean(gameId)
1269
+ });
1270
+ return null;
1271
+ }
1272
+ return this.getClient().startPlaySessionAsync(gameId, body);
1273
+ }
1169
1274
  trackPlay(playTimeSeconds) {
1170
1275
  const gameId = this.getGameId();
1171
1276
  if (!this.canTrack() || !gameId) {
@@ -1379,7 +1484,10 @@ ${gameId}`;
1379
1484
  }
1380
1485
  }
1381
1486
  resolveParentOrigin() {
1382
- if (this.options.parentOrigin) return this.options.parentOrigin;
1487
+ if (this.options.parentOrigin) {
1488
+ const opt = toOptionalString(this.options.parentOrigin);
1489
+ if (opt) return opt;
1490
+ }
1383
1491
  const envOrigin = resolveEnvParentOrigin();
1384
1492
  if (envOrigin) return envOrigin;
1385
1493
  if (typeof document === "undefined" || !document.referrer) return null;