@looplay/sdk 0.5.1 → 0.6.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/looplay-sdk.cjs.js +201 -42
- package/dist/looplay-sdk.cjs.js.map +1 -1
- package/dist/looplay-sdk.esm.js +201 -43
- 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 +130 -17
- package/dist/types/apps/api-client.d.ts +33 -9
- package/dist/types/apps/service-client.d.ts +25 -12
- package/dist/types/errors.d.ts +3 -0
- package/dist/types/index.d.ts +0 -1
- package/package.json +1 -1
package/dist/looplay-sdk.esm.js
CHANGED
|
@@ -29,6 +29,12 @@ var NotAuthenticatedError = class extends LooplaySDKError {
|
|
|
29
29
|
this.name = "NotAuthenticatedError";
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
|
+
var MissingStoreError = class extends LooplaySDKError {
|
|
33
|
+
constructor(gameId) {
|
|
34
|
+
super(`Game ${gameId} has no storefront configured.`);
|
|
35
|
+
this.name = "MissingStoreError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
32
38
|
|
|
33
39
|
// src/auth/storage.ts
|
|
34
40
|
var MemoryAuthStorage = class {
|
|
@@ -257,32 +263,39 @@ var ServiceClient = class {
|
|
|
257
263
|
async listRecentPlayed(bearerToken, query) {
|
|
258
264
|
return this.http.request("GET", "/sdk/games/recent-play", { bearerToken, query });
|
|
259
265
|
}
|
|
260
|
-
async
|
|
261
|
-
await this.http.request("POST", `/sdk/games/${encodeURIComponent(gameId)}/
|
|
266
|
+
async recordGameView(auth, gameId) {
|
|
267
|
+
await this.http.request("POST", `/sdk/games/${encodeURIComponent(gameId)}/views/record`, {
|
|
262
268
|
...auth,
|
|
263
269
|
headers: this.trackingHeaders(auth)
|
|
264
270
|
});
|
|
265
271
|
return true;
|
|
266
272
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
273
|
+
/** Signs a fresh `attemptToken` — required by `completeGameplayAttempt`/`completeGameplayMatch`/`recordGameplayAction`. */
|
|
274
|
+
async startGameplayAttempt(auth, gameId, body = {}) {
|
|
275
|
+
return this.http.request("POST", `/sdk/games/${encodeURIComponent(gameId)}/attempts/start`, {
|
|
276
|
+
...auth,
|
|
277
|
+
headers: this.trackingHeaders(auth),
|
|
278
|
+
body
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
async completeGameplayAttempt(auth, gameId, body) {
|
|
282
|
+
await this.http.request("POST", `/sdk/games/${encodeURIComponent(gameId)}/attempts/complete`, {
|
|
270
283
|
...auth,
|
|
271
284
|
headers: this.trackingHeaders(auth),
|
|
272
285
|
body
|
|
273
286
|
});
|
|
274
287
|
return true;
|
|
275
288
|
}
|
|
276
|
-
async
|
|
277
|
-
await this.http.request("POST", `/sdk/games/${encodeURIComponent(gameId)}/
|
|
289
|
+
async completeGameplayMatch(auth, gameId, body) {
|
|
290
|
+
await this.http.request("POST", `/sdk/games/${encodeURIComponent(gameId)}/matches/complete`, {
|
|
278
291
|
...auth,
|
|
279
292
|
headers: this.trackingHeaders(auth),
|
|
280
293
|
body
|
|
281
294
|
});
|
|
282
295
|
return true;
|
|
283
296
|
}
|
|
284
|
-
async
|
|
285
|
-
return this.http.request("POST", `/sdk/games/${encodeURIComponent(gameId)}/
|
|
297
|
+
async recordGameplayAction(auth, gameId, body) {
|
|
298
|
+
return this.http.request("POST", `/sdk/games/${encodeURIComponent(gameId)}/actions/record`, {
|
|
286
299
|
...auth,
|
|
287
300
|
headers: this.trackingHeaders(auth),
|
|
288
301
|
body
|
|
@@ -293,7 +306,7 @@ var ServiceClient = class {
|
|
|
293
306
|
* runtimes should still call it to get a `trackingSessionId` for unified
|
|
294
307
|
* tracing — see `GameTrackingSessionDto`.
|
|
295
308
|
*/
|
|
296
|
-
async
|
|
309
|
+
async bootstrapTrackingSession(anonId) {
|
|
297
310
|
return this.http.request("POST", "/sdk/games/tracking/bootstrap", {
|
|
298
311
|
headers: { "x-looplay-anon-id": anonId }
|
|
299
312
|
});
|
|
@@ -305,22 +318,104 @@ var ServiceClient = class {
|
|
|
305
318
|
};
|
|
306
319
|
}
|
|
307
320
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
308
|
-
// Game
|
|
309
|
-
//
|
|
321
|
+
// Game store (JWT tier, keyed by `storeId`) — spend coin balance on offers
|
|
322
|
+
// (single asset or bundle) defined by the game's creator. Coin balance
|
|
323
|
+
// itself comes from getBalances()/getMyBalance(). Purchases require a
|
|
324
|
+
// signed `checkoutToken` from `createStoreCheckoutIntent` first.
|
|
310
325
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
311
|
-
async
|
|
312
|
-
return this.http.request("GET", `/sdk/games/${encodeURIComponent(
|
|
326
|
+
async getStoreByStoreId(storeId) {
|
|
327
|
+
return this.http.request("GET", `/sdk/games/store/${encodeURIComponent(storeId)}`);
|
|
328
|
+
}
|
|
329
|
+
async listMyStorePurchasesByStoreId(bearerToken, storeId) {
|
|
330
|
+
return this.http.request("GET", `/sdk/games/store/${encodeURIComponent(storeId)}/purchases/me`, {
|
|
313
331
|
bearerToken
|
|
314
332
|
});
|
|
315
333
|
}
|
|
316
|
-
async
|
|
317
|
-
return this.http.request(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
);
|
|
334
|
+
async createStoreCheckoutIntent(bearerToken, body, trackingSessionId) {
|
|
335
|
+
return this.http.request("POST", "/sdk/games/store/by-store-id/checkout-intent", {
|
|
336
|
+
bearerToken,
|
|
337
|
+
headers: { "x-game-tracking-session-id": trackingSessionId },
|
|
338
|
+
body
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
async purchaseStoreOffer(bearerToken, body, trackingSessionId) {
|
|
342
|
+
return this.http.request("POST", "/sdk/games/store/by-store-id/purchase", {
|
|
343
|
+
bearerToken,
|
|
344
|
+
headers: { "x-game-tracking-session-id": trackingSessionId },
|
|
345
|
+
body
|
|
346
|
+
});
|
|
322
347
|
}
|
|
323
348
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
349
|
+
// Runtime tier (`x-game-key`) — for a game embedding the SDK with its own
|
|
350
|
+
// public `appId`, without needing a resolved `gameId`/`storeId` first or a
|
|
351
|
+
// logged-in user (anonymous tracking still works via `TrackingAuth.anonId`).
|
|
352
|
+
// Never use the creator-secret tier (`x-creator-key`/`x-game-secret`) from
|
|
353
|
+
// client/browser code — that would leak the secret to every player.
|
|
354
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
355
|
+
async recordGameViewForGameKey(gameKey, auth = {}) {
|
|
356
|
+
await this.http.request("POST", "/sdk/games/runtime/views/record", {
|
|
357
|
+
...auth,
|
|
358
|
+
headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) }
|
|
359
|
+
});
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
async startGameplayAttemptForGameKey(gameKey, auth = {}, body = {}) {
|
|
363
|
+
return this.http.request("POST", "/sdk/games/runtime/attempts/start", {
|
|
364
|
+
...auth,
|
|
365
|
+
headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
|
|
366
|
+
body
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
async completeGameplayAttemptForGameKey(gameKey, auth = {}, body) {
|
|
370
|
+
await this.http.request("POST", "/sdk/games/runtime/attempts/complete", {
|
|
371
|
+
...auth,
|
|
372
|
+
headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
|
|
373
|
+
body
|
|
374
|
+
});
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
async completeGameplayMatchForGameKey(gameKey, auth = {}, body) {
|
|
378
|
+
await this.http.request("POST", "/sdk/games/runtime/matches/complete", {
|
|
379
|
+
...auth,
|
|
380
|
+
headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
|
|
381
|
+
body
|
|
382
|
+
});
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
async recordGameplayActionForGameKey(gameKey, auth = {}, body) {
|
|
386
|
+
return this.http.request("POST", "/sdk/games/runtime/actions/record", {
|
|
387
|
+
...auth,
|
|
388
|
+
headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
|
|
389
|
+
body
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
async getStoreForGameKey(gameKey) {
|
|
393
|
+
return this.http.request("GET", "/sdk/games/store/current", {
|
|
394
|
+
headers: { "x-game-key": gameKey }
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
async listGameAssetsForGameKey(gameKey) {
|
|
398
|
+
return this.http.request("GET", "/sdk/games/store/assets", {
|
|
399
|
+
headers: { "x-game-key": gameKey }
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
async listMyOwnedAssetsForGameKey(bearerToken, gameKey) {
|
|
403
|
+
return this.http.request("GET", "/sdk/games/store/assets/me", {
|
|
404
|
+
bearerToken,
|
|
405
|
+
headers: { "x-game-key": gameKey }
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
async listMyStorePurchasesForGameKey(bearerToken, gameKey) {
|
|
409
|
+
return this.http.request("GET", "/sdk/games/store/purchases/me", {
|
|
410
|
+
bearerToken,
|
|
411
|
+
headers: { "x-game-key": gameKey }
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
// No public/anonymous purchase route exists for the runtime tier —
|
|
415
|
+
// purchases move real coin balance and always require the JWT tier
|
|
416
|
+
// (createStoreCheckoutIntent/purchaseStoreOffer above) or the
|
|
417
|
+
// creator-secret tier, which this SDK intentionally never wires up.
|
|
418
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
324
419
|
// Balance
|
|
325
420
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
326
421
|
async getBalances(bearerToken, query) {
|
|
@@ -350,11 +445,11 @@ var ServiceClient = class {
|
|
|
350
445
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
351
446
|
// Tasks
|
|
352
447
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
353
|
-
async listTasks(bearerToken) {
|
|
354
|
-
return this.http.request("GET", "/sdk/task", { bearerToken });
|
|
448
|
+
async listTasks(bearerToken, query) {
|
|
449
|
+
return this.http.request("GET", "/sdk/task", { bearerToken, query: { ...query } });
|
|
355
450
|
}
|
|
356
|
-
async listFinishedTasks(bearerToken) {
|
|
357
|
-
return this.http.request("GET", "/sdk/task/finished", { bearerToken });
|
|
451
|
+
async listFinishedTasks(bearerToken, query) {
|
|
452
|
+
return this.http.request("GET", "/sdk/task/finished", { bearerToken, query: { ...query } });
|
|
358
453
|
}
|
|
359
454
|
async startTask(bearerToken, body) {
|
|
360
455
|
return this.http.request("POST", "/sdk/task/start", { bearerToken, body });
|
|
@@ -382,6 +477,8 @@ var ApiClient = class {
|
|
|
382
477
|
trackingSession;
|
|
383
478
|
trackingSessionPromise;
|
|
384
479
|
currentAttemptId;
|
|
480
|
+
gameplayAttempt;
|
|
481
|
+
gameplayAttemptPromise;
|
|
385
482
|
constructor(options) {
|
|
386
483
|
if (!options.baseUrl) throw new MissingBaseUrlError();
|
|
387
484
|
this.raw = new ServiceClient({
|
|
@@ -437,7 +534,7 @@ var ApiClient = class {
|
|
|
437
534
|
return this.trackingSession;
|
|
438
535
|
}
|
|
439
536
|
if (!this.trackingSessionPromise) {
|
|
440
|
-
this.trackingSessionPromise = this.raw.
|
|
537
|
+
this.trackingSessionPromise = this.raw.bootstrapTrackingSession(anonId).then((dto) => {
|
|
441
538
|
this.trackingSession = {
|
|
442
539
|
trackingSessionId: dto.trackingSessionId,
|
|
443
540
|
anonTrackingToken: dto.trackingToken,
|
|
@@ -450,10 +547,42 @@ var ApiClient = class {
|
|
|
450
547
|
}
|
|
451
548
|
return this.trackingSessionPromise;
|
|
452
549
|
}
|
|
550
|
+
/**
|
|
551
|
+
* Signs (and caches until near expiry) the `attemptToken` required by
|
|
552
|
+
* `trackPlay`/`trackMatch`/`emit` for the current attempt — does not
|
|
553
|
+
* swallow failures, unlike `ensureTrackingSession`, since these calls
|
|
554
|
+
* are rejected outright without a valid token.
|
|
555
|
+
*/
|
|
556
|
+
async ensureGameplayAttempt(auth, gameId, attemptId) {
|
|
557
|
+
const now = Date.now();
|
|
558
|
+
if (this.gameplayAttempt && this.gameplayAttempt.gameId === gameId && this.gameplayAttempt.attemptId === attemptId && this.gameplayAttempt.expiresAtMs > now + 1e4) {
|
|
559
|
+
return this.gameplayAttempt;
|
|
560
|
+
}
|
|
561
|
+
if (!this.gameplayAttemptPromise) {
|
|
562
|
+
this.gameplayAttemptPromise = this.raw.startGameplayAttempt(auth, gameId, { attemptId }).then((dto) => {
|
|
563
|
+
this.gameplayAttempt = {
|
|
564
|
+
gameId,
|
|
565
|
+
attemptId: dto.attemptId,
|
|
566
|
+
attemptToken: dto.attemptToken,
|
|
567
|
+
expiresAtMs: new Date(dto.expiresAt).getTime()
|
|
568
|
+
};
|
|
569
|
+
return this.gameplayAttempt;
|
|
570
|
+
}).finally(() => {
|
|
571
|
+
this.gameplayAttemptPromise = void 0;
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
return this.gameplayAttemptPromise;
|
|
575
|
+
}
|
|
453
576
|
async getGameDetail(gameId) {
|
|
454
577
|
const token = await this.getAccessToken?.();
|
|
455
578
|
return this.raw.getGameDetail(gameId, token);
|
|
456
579
|
}
|
|
580
|
+
async resolveStoreId(gameId) {
|
|
581
|
+
const detail = await this.getGameDetail(gameId);
|
|
582
|
+
const storeId = detail?.storeId;
|
|
583
|
+
if (!storeId) throw new MissingStoreError(gameId);
|
|
584
|
+
return storeId;
|
|
585
|
+
}
|
|
457
586
|
async listRecentPlayed(query) {
|
|
458
587
|
const token = await this.requireToken();
|
|
459
588
|
return this.raw.listRecentPlayed(token, query);
|
|
@@ -461,45 +590,74 @@ var ApiClient = class {
|
|
|
461
590
|
/** Call once when the game view opens, before any play/match tracking. */
|
|
462
591
|
async trackView(gameId) {
|
|
463
592
|
const auth = await this.resolveTrackingAuth();
|
|
464
|
-
return this.raw.
|
|
593
|
+
return this.raw.recordGameView(auth, gameId);
|
|
465
594
|
}
|
|
466
595
|
/**
|
|
467
596
|
* Starts a new play attempt and returns its id. Call when the user
|
|
468
|
-
* presses Play; `trackPlay`
|
|
597
|
+
* presses Play; `trackPlay`/`trackMatch`/`emit` reuse this id (and the
|
|
598
|
+
* signed `attemptToken` backing it) across calls until the next
|
|
469
599
|
* `startAttempt()`, auto-starting one on first use if none was started.
|
|
470
600
|
*/
|
|
471
601
|
startAttempt() {
|
|
472
602
|
this.currentAttemptId = generateId();
|
|
603
|
+
this.gameplayAttempt = void 0;
|
|
473
604
|
return this.currentAttemptId;
|
|
474
605
|
}
|
|
475
606
|
async trackPlay(gameId, playTimeSeconds) {
|
|
476
607
|
const auth = await this.resolveTrackingAuth();
|
|
477
608
|
const attemptId = this.currentAttemptId ?? this.startAttempt();
|
|
478
|
-
|
|
609
|
+
const { attemptToken } = await this.ensureGameplayAttempt(auth, gameId, attemptId);
|
|
610
|
+
return this.raw.completeGameplayAttempt(auth, gameId, { playTimeSeconds, attemptId, attemptToken });
|
|
479
611
|
}
|
|
480
612
|
async trackMatch(gameId, body) {
|
|
481
613
|
const auth = await this.resolveTrackingAuth();
|
|
482
|
-
|
|
614
|
+
const attemptId = this.currentAttemptId ?? this.startAttempt();
|
|
615
|
+
const { attemptToken } = await this.ensureGameplayAttempt(auth, gameId, attemptId);
|
|
616
|
+
return this.raw.completeGameplayMatch(auth, gameId, { ...body, attemptId, attemptToken });
|
|
483
617
|
}
|
|
484
618
|
async emit(gameId, actionCode, opts) {
|
|
485
619
|
const auth = await this.resolveTrackingAuth();
|
|
620
|
+
const attemptId = this.currentAttemptId ?? this.startAttempt();
|
|
621
|
+
const { attemptToken } = await this.ensureGameplayAttempt(auth, gameId, attemptId);
|
|
486
622
|
const body = {
|
|
623
|
+
attemptId,
|
|
624
|
+
attemptToken,
|
|
487
625
|
actionCode,
|
|
488
626
|
value: opts?.value,
|
|
489
627
|
refId: opts?.refId,
|
|
490
628
|
payload: opts?.payload
|
|
491
629
|
};
|
|
492
|
-
return this.raw.
|
|
630
|
+
return this.raw.recordGameplayAction(auth, gameId, body);
|
|
493
631
|
}
|
|
494
|
-
/**
|
|
495
|
-
async
|
|
496
|
-
const
|
|
497
|
-
return this.raw.
|
|
632
|
+
/** Fetches the storefront (sections + offers) for `gameId`, defined by that game's creator. */
|
|
633
|
+
async getStore(gameId) {
|
|
634
|
+
const storeId = await this.resolveStoreId(gameId);
|
|
635
|
+
return this.raw.getStoreByStoreId(storeId);
|
|
498
636
|
}
|
|
499
|
-
/**
|
|
500
|
-
async
|
|
501
|
-
const token = await this.requireToken();
|
|
502
|
-
return this.raw.
|
|
637
|
+
/** Purchase history for the authenticated player in `gameId`'s store. */
|
|
638
|
+
async listMyStorePurchases(gameId) {
|
|
639
|
+
const [token, storeId] = await Promise.all([this.requireToken(), this.resolveStoreId(gameId)]);
|
|
640
|
+
return this.raw.listMyStorePurchasesByStoreId(token, storeId);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Spends coin balance to purchase a store offer — signs a checkout intent
|
|
644
|
+
* first, then redeems it, mirroring the gameplay attempt flow above.
|
|
645
|
+
*/
|
|
646
|
+
async purchaseStoreOffer(gameId, offerCode, opts) {
|
|
647
|
+
const [token, storeId] = await Promise.all([this.requireToken(), this.resolveStoreId(gameId)]);
|
|
648
|
+
const intent = await this.raw.createStoreCheckoutIntent(token, {
|
|
649
|
+
storeId,
|
|
650
|
+
offerCode,
|
|
651
|
+
requestId: opts?.requestId,
|
|
652
|
+
quantity: opts?.quantity
|
|
653
|
+
});
|
|
654
|
+
return this.raw.purchaseStoreOffer(token, {
|
|
655
|
+
storeId,
|
|
656
|
+
offerCode,
|
|
657
|
+
requestId: intent.requestId,
|
|
658
|
+
quantity: intent.quantity,
|
|
659
|
+
checkoutToken: intent.checkoutToken
|
|
660
|
+
});
|
|
503
661
|
}
|
|
504
662
|
async getMyProfile() {
|
|
505
663
|
const token = await this.requireToken();
|
|
@@ -525,13 +683,13 @@ var ApiClient = class {
|
|
|
525
683
|
const token = await this.requireToken();
|
|
526
684
|
return this.raw.setReferral(token, body);
|
|
527
685
|
}
|
|
528
|
-
async listTasks() {
|
|
686
|
+
async listTasks(query) {
|
|
529
687
|
const token = await this.requireToken();
|
|
530
|
-
return this.raw.listTasks(token);
|
|
688
|
+
return this.raw.listTasks(token, query);
|
|
531
689
|
}
|
|
532
|
-
async listFinishedTasks() {
|
|
690
|
+
async listFinishedTasks(query) {
|
|
533
691
|
const token = await this.requireToken();
|
|
534
|
-
return this.raw.listFinishedTasks(token);
|
|
692
|
+
return this.raw.listFinishedTasks(token, query);
|
|
535
693
|
}
|
|
536
694
|
async startTask(body) {
|
|
537
695
|
const token = await this.requireToken();
|
|
@@ -1185,6 +1343,6 @@ ${identity}`;
|
|
|
1185
1343
|
}
|
|
1186
1344
|
};
|
|
1187
1345
|
|
|
1188
|
-
export { ApiClient, BrowserLocalStorageAuthStorage, HttpClient, HttpError, LooplayAuth, LooplayIframeAuth, LooplaySDK, LooplaySDKError, MemoryAuthStorage, MissingAuthError, MissingBaseUrlError, NotAuthenticatedError, NotInitializedError, ServiceClient, TelegramAuthProvider };
|
|
1346
|
+
export { ApiClient, BrowserLocalStorageAuthStorage, HttpClient, HttpError, LooplayAuth, LooplayIframeAuth, LooplaySDK, LooplaySDKError, MemoryAuthStorage, MissingAuthError, MissingBaseUrlError, MissingStoreError, NotAuthenticatedError, NotInitializedError, ServiceClient, TelegramAuthProvider };
|
|
1189
1347
|
//# sourceMappingURL=looplay-sdk.esm.js.map
|
|
1190
1348
|
//# sourceMappingURL=looplay-sdk.esm.js.map
|