@looplay/sdk 0.8.3 → 0.8.4
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/README.md +9 -9
- package/dist/looplay-sdk.cjs.js +64 -94
- package/dist/looplay-sdk.cjs.js.map +1 -1
- package/dist/looplay-sdk.esm.js +64 -94
- package/dist/looplay-sdk.esm.js.map +1 -1
- package/dist/looplay-sdk.min.js +2 -2
- package/dist/looplay-sdk.min.js.map +1 -1
- package/dist/types/apps/LooplaySDK.types.d.ts +18 -31
- package/dist/types/apps/api-client.d.ts +13 -18
- package/dist/types/apps/service-client.d.ts +10 -11
- package/dist/types/iframe/iframe-auth.d.ts +2 -2
- package/dist/types/looplay-sdk.d.ts +3 -2
- package/package.json +1 -1
package/dist/looplay-sdk.esm.js
CHANGED
|
@@ -259,16 +259,16 @@ var ServiceClient = class {
|
|
|
259
259
|
});
|
|
260
260
|
return true;
|
|
261
261
|
}
|
|
262
|
-
/** Signs a fresh `
|
|
263
|
-
async
|
|
264
|
-
return this.http.request("POST", "/sdk/games/
|
|
262
|
+
/** Signs a fresh `playSessionToken` — required by `completePlaySession`/`completeGameplayMatch`/`recordGameplayAction`. */
|
|
263
|
+
async startPlaySession(gameKey, auth = {}, body = {}) {
|
|
264
|
+
return this.http.request("POST", "/sdk/games/play-sessions/start", {
|
|
265
265
|
...auth,
|
|
266
266
|
headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
|
|
267
267
|
body
|
|
268
268
|
});
|
|
269
269
|
}
|
|
270
|
-
async
|
|
271
|
-
await this.http.request("POST", "/sdk/games/
|
|
270
|
+
async completePlaySession(gameKey, auth, body) {
|
|
271
|
+
await this.http.request("POST", "/sdk/games/play-sessions/complete", {
|
|
272
272
|
...auth,
|
|
273
273
|
headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
|
|
274
274
|
body
|
|
@@ -300,6 +300,16 @@ var ServiceClient = class {
|
|
|
300
300
|
headers: { "x-looplay-anon-id": anonId }
|
|
301
301
|
});
|
|
302
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Lightweight integration heartbeat for review/liveops. This intentionally
|
|
305
|
+
* does not record a view/play metric.
|
|
306
|
+
*/
|
|
307
|
+
async detectGameIntegration(gameKey) {
|
|
308
|
+
await this.http.request("POST", "/sdk/games/integration/detect", {
|
|
309
|
+
headers: { "x-game-key": gameKey }
|
|
310
|
+
});
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
303
313
|
trackingHeaders(auth) {
|
|
304
314
|
return {
|
|
305
315
|
"x-game-tracking-session-id": auth.trackingSessionId,
|
|
@@ -337,7 +347,7 @@ var ServiceClient = class {
|
|
|
337
347
|
// `x-game-key` + the purchasing player's own bearer token, no backend or
|
|
338
348
|
// secret pair required. Safe to call directly from client/browser code —
|
|
339
349
|
// `checkoutToken` (short-lived, server-signed) plays the same role as
|
|
340
|
-
// `
|
|
350
|
+
// `playSessionToken` on the tracking routes. `ApiClient.purchaseStoreOffer`
|
|
341
351
|
// wraps this pair for you.
|
|
342
352
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
343
353
|
/** Signs a `checkoutToken` for one purchase — required by `purchaseMyStoreOffer`. */
|
|
@@ -408,30 +418,6 @@ var ServiceClient = class {
|
|
|
408
418
|
async getMyBalance(bearerToken) {
|
|
409
419
|
return this.http.request("GET", "/sdk/user/balance", { bearerToken });
|
|
410
420
|
}
|
|
411
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
412
|
-
// Referrals
|
|
413
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
414
|
-
async listReferrals(bearerToken, query) {
|
|
415
|
-
return this.http.request("GET", "/sdk/referrals", { bearerToken, query });
|
|
416
|
-
}
|
|
417
|
-
async setReferral(bearerToken, body) {
|
|
418
|
-
return this.http.request("POST", "/sdk/referrals/set", { bearerToken, body });
|
|
419
|
-
}
|
|
420
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
421
|
-
// Tasks
|
|
422
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
423
|
-
async listTasks(bearerToken, query) {
|
|
424
|
-
return this.http.request("GET", "/sdk/task", { bearerToken, query: { ...query } });
|
|
425
|
-
}
|
|
426
|
-
async listFinishedTasks(bearerToken, query) {
|
|
427
|
-
return this.http.request("GET", "/sdk/task/finished", { bearerToken, query: { ...query } });
|
|
428
|
-
}
|
|
429
|
-
async startTask(bearerToken, body) {
|
|
430
|
-
return this.http.request("POST", "/sdk/task/start", { bearerToken, body });
|
|
431
|
-
}
|
|
432
|
-
async claimTask(bearerToken, body) {
|
|
433
|
-
return this.http.request("POST", "/sdk/task/claim", { bearerToken, body });
|
|
434
|
-
}
|
|
435
421
|
};
|
|
436
422
|
|
|
437
423
|
// src/apps/api-client.ts
|
|
@@ -451,9 +437,9 @@ var ApiClient = class {
|
|
|
451
437
|
getAnonymousId;
|
|
452
438
|
trackingSession;
|
|
453
439
|
trackingSessionPromise;
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
440
|
+
currentPlaySessionId;
|
|
441
|
+
playSession;
|
|
442
|
+
playSessionPromise;
|
|
457
443
|
constructor(options) {
|
|
458
444
|
if (!options.baseUrl) throw new MissingBaseUrlError();
|
|
459
445
|
this.raw = new ServiceClient({
|
|
@@ -468,6 +454,9 @@ var ApiClient = class {
|
|
|
468
454
|
unsafeRaw() {
|
|
469
455
|
return this.raw;
|
|
470
456
|
}
|
|
457
|
+
async detectIntegration(gameKey) {
|
|
458
|
+
return this.raw.detectGameIntegration(gameKey);
|
|
459
|
+
}
|
|
471
460
|
// Real-time (balance_change / store_purchase_completed WebSocket events)
|
|
472
461
|
// deliberately lives outside this class — import `WsClient` from
|
|
473
462
|
// '@looplay/sdk/realtime' and construct it with the same `getAccessToken`
|
|
@@ -523,30 +512,30 @@ var ApiClient = class {
|
|
|
523
512
|
return this.trackingSessionPromise;
|
|
524
513
|
}
|
|
525
514
|
/**
|
|
526
|
-
* Signs (and caches until near expiry) the `
|
|
527
|
-
* `trackPlay`/`trackMatch`/`emit` for the current
|
|
515
|
+
* Signs (and caches until near expiry) the `playSessionToken` required by
|
|
516
|
+
* `trackPlay`/`trackMatch`/`emit` for the current play session — does not
|
|
528
517
|
* swallow failures, unlike `ensureTrackingSession`, since these calls
|
|
529
518
|
* are rejected outright without a valid token.
|
|
530
519
|
*/
|
|
531
|
-
async
|
|
520
|
+
async ensurePlaySession(gameKey, auth, playSessionId) {
|
|
532
521
|
const now = Date.now();
|
|
533
|
-
if (this.
|
|
534
|
-
return this.
|
|
522
|
+
if (this.playSession && this.playSession.gameKey === gameKey && this.playSession.playSessionId === playSessionId && this.playSession.expiresAtMs > now + 1e4) {
|
|
523
|
+
return this.playSession;
|
|
535
524
|
}
|
|
536
|
-
if (!this.
|
|
537
|
-
this.
|
|
538
|
-
this.
|
|
525
|
+
if (!this.playSessionPromise) {
|
|
526
|
+
this.playSessionPromise = this.raw.startPlaySession(gameKey, auth, { playSessionId }).then((dto) => {
|
|
527
|
+
this.playSession = {
|
|
539
528
|
gameKey,
|
|
540
|
-
|
|
541
|
-
|
|
529
|
+
playSessionId: dto.playSessionId,
|
|
530
|
+
playSessionToken: dto.playSessionToken,
|
|
542
531
|
expiresAtMs: new Date(dto.expiresAt).getTime()
|
|
543
532
|
};
|
|
544
|
-
return this.
|
|
533
|
+
return this.playSession;
|
|
545
534
|
}).finally(() => {
|
|
546
|
-
this.
|
|
535
|
+
this.playSessionPromise = void 0;
|
|
547
536
|
});
|
|
548
537
|
}
|
|
549
|
-
return this.
|
|
538
|
+
return this.playSessionPromise;
|
|
550
539
|
}
|
|
551
540
|
/**
|
|
552
541
|
* Call once when the game view opens, before any play/match tracking.
|
|
@@ -557,35 +546,35 @@ var ApiClient = class {
|
|
|
557
546
|
return this.raw.recordGameView(gameKey, auth);
|
|
558
547
|
}
|
|
559
548
|
/**
|
|
560
|
-
* Starts a new play
|
|
549
|
+
* Starts a new play session and returns its id. Call when the user
|
|
561
550
|
* presses Play; `trackPlay`/`trackMatch`/`emit` reuse this id (and the
|
|
562
|
-
* signed `
|
|
563
|
-
* `
|
|
551
|
+
* signed `playSessionToken` backing it) across calls until the next
|
|
552
|
+
* `startPlaySession()`, auto-starting one on first use if none was started.
|
|
564
553
|
*/
|
|
565
|
-
|
|
566
|
-
this.
|
|
567
|
-
this.
|
|
568
|
-
return this.
|
|
554
|
+
startPlaySession() {
|
|
555
|
+
this.currentPlaySessionId = generateId();
|
|
556
|
+
this.playSession = void 0;
|
|
557
|
+
return this.currentPlaySessionId;
|
|
569
558
|
}
|
|
570
559
|
async trackPlay(gameKey, playTimeSeconds) {
|
|
571
560
|
const auth = await this.resolveTrackingAuth();
|
|
572
|
-
const
|
|
573
|
-
const {
|
|
574
|
-
return this.raw.
|
|
561
|
+
const playSessionId = this.currentPlaySessionId ?? this.startPlaySession();
|
|
562
|
+
const { playSessionToken } = await this.ensurePlaySession(gameKey, auth, playSessionId);
|
|
563
|
+
return this.raw.completePlaySession(gameKey, auth, { playTimeSeconds, playSessionId, playSessionToken });
|
|
575
564
|
}
|
|
576
565
|
async trackMatch(gameKey, body) {
|
|
577
566
|
const auth = await this.resolveTrackingAuth();
|
|
578
|
-
const
|
|
579
|
-
const {
|
|
580
|
-
return this.raw.completeGameplayMatch(gameKey, auth, { ...body,
|
|
567
|
+
const playSessionId = this.currentPlaySessionId ?? this.startPlaySession();
|
|
568
|
+
const { playSessionToken } = await this.ensurePlaySession(gameKey, auth, playSessionId);
|
|
569
|
+
return this.raw.completeGameplayMatch(gameKey, auth, { ...body, playSessionId, playSessionToken });
|
|
581
570
|
}
|
|
582
571
|
async emit(gameKey, actionCode, opts) {
|
|
583
572
|
const auth = await this.resolveTrackingAuth();
|
|
584
|
-
const
|
|
585
|
-
const {
|
|
573
|
+
const playSessionId = this.currentPlaySessionId ?? this.startPlaySession();
|
|
574
|
+
const { playSessionToken } = await this.ensurePlaySession(gameKey, auth, playSessionId);
|
|
586
575
|
const body = {
|
|
587
|
-
|
|
588
|
-
|
|
576
|
+
playSessionId,
|
|
577
|
+
playSessionToken,
|
|
589
578
|
actionCode,
|
|
590
579
|
value: opts?.value,
|
|
591
580
|
refId: opts?.refId,
|
|
@@ -613,7 +602,7 @@ var ApiClient = class {
|
|
|
613
602
|
}
|
|
614
603
|
/**
|
|
615
604
|
* Spends coin balance to purchase a store offer — signs a checkout intent
|
|
616
|
-
* first, then redeems it, mirroring the
|
|
605
|
+
* first, then redeems it, mirroring the play session flow above.
|
|
617
606
|
* Self-serve: only the player's own access token is used, no secret pair
|
|
618
607
|
* required, safe to call directly from the game client. Also see
|
|
619
608
|
* `onStorePurchase` to react to the result in real time from any tab.
|
|
@@ -648,30 +637,6 @@ var ApiClient = class {
|
|
|
648
637
|
const token = await this.requireToken();
|
|
649
638
|
return this.raw.getBalanceHistory(token, query);
|
|
650
639
|
}
|
|
651
|
-
async listReferrals(query) {
|
|
652
|
-
const token = await this.requireToken();
|
|
653
|
-
return this.raw.listReferrals(token, query);
|
|
654
|
-
}
|
|
655
|
-
async setReferral(body) {
|
|
656
|
-
const token = await this.requireToken();
|
|
657
|
-
return this.raw.setReferral(token, body);
|
|
658
|
-
}
|
|
659
|
-
async listTasks(query) {
|
|
660
|
-
const token = await this.requireToken();
|
|
661
|
-
return this.raw.listTasks(token, query);
|
|
662
|
-
}
|
|
663
|
-
async listFinishedTasks(query) {
|
|
664
|
-
const token = await this.requireToken();
|
|
665
|
-
return this.raw.listFinishedTasks(token, query);
|
|
666
|
-
}
|
|
667
|
-
async startTask(body) {
|
|
668
|
-
const token = await this.requireToken();
|
|
669
|
-
return this.raw.startTask(token, body);
|
|
670
|
-
}
|
|
671
|
-
async claimTask(body) {
|
|
672
|
-
const token = await this.requireToken();
|
|
673
|
-
return this.raw.claimTask(token, body);
|
|
674
|
-
}
|
|
675
640
|
};
|
|
676
641
|
|
|
677
642
|
// src/looplay-sdk.ts
|
|
@@ -697,6 +662,7 @@ var LooplaySDK = class {
|
|
|
697
662
|
async init(params) {
|
|
698
663
|
this.gameId = params.gameId;
|
|
699
664
|
await this.auth?.init();
|
|
665
|
+
await this.detectIntegration(params);
|
|
700
666
|
await this.verifyGameId(params);
|
|
701
667
|
this.initialized = true;
|
|
702
668
|
}
|
|
@@ -718,11 +684,11 @@ var LooplaySDK = class {
|
|
|
718
684
|
if (!this.gameId) throw new NotInitializedError();
|
|
719
685
|
return this.api.trackView(this.gameId);
|
|
720
686
|
}
|
|
721
|
-
/** Starts a new play
|
|
722
|
-
|
|
687
|
+
/** Starts a new play session; call when the user presses Play. Returns the play session id. */
|
|
688
|
+
startPlaySession() {
|
|
723
689
|
this.assertInitialized();
|
|
724
690
|
if (!this.api) throw new MissingBaseUrlError();
|
|
725
|
-
return this.api.
|
|
691
|
+
return this.api.startPlaySession();
|
|
726
692
|
}
|
|
727
693
|
async trackPlay(playTimeSeconds) {
|
|
728
694
|
this.assertInitialized();
|
|
@@ -765,6 +731,10 @@ var LooplaySDK = class {
|
|
|
765
731
|
if (!this.api) throw new MissingBaseUrlError();
|
|
766
732
|
await this.api.listGameAssets(params.gameId);
|
|
767
733
|
}
|
|
734
|
+
async detectIntegration(params) {
|
|
735
|
+
if (!this.api) return;
|
|
736
|
+
await this.api.detectIntegration(params.gameId);
|
|
737
|
+
}
|
|
768
738
|
};
|
|
769
739
|
|
|
770
740
|
// src/auth/jwt.ts
|
|
@@ -1090,9 +1060,9 @@ var LooplayIframeAuth = class {
|
|
|
1090
1060
|
this.debug("trackView dispatched", { gameId });
|
|
1091
1061
|
return this.getClient().trackView(gameId);
|
|
1092
1062
|
}
|
|
1093
|
-
/** Starts a new play
|
|
1094
|
-
|
|
1095
|
-
return this.getClient().
|
|
1063
|
+
/** Starts a new play session; call when the user presses Play. Returns the play session id. */
|
|
1064
|
+
startPlaySession() {
|
|
1065
|
+
return this.getClient().startPlaySession();
|
|
1096
1066
|
}
|
|
1097
1067
|
trackPlay(playTimeSeconds) {
|
|
1098
1068
|
const gameId = this.getGameId();
|