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