@looplay/sdk 0.5.1 → 0.7.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/README.md +141 -4
- package/dist/looplay-sdk.cjs.js +133 -79
- package/dist/looplay-sdk.cjs.js.map +1 -1
- package/dist/looplay-sdk.esm.js +133 -79
- 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 +121 -18
- package/dist/types/apps/api-client.d.ts +36 -17
- package/dist/types/apps/service-client.d.ts +20 -15
- package/dist/types/index.d.ts +0 -1
- package/dist/types/types.d.ts +2 -1
- package/package.json +1 -1
package/dist/looplay-sdk.esm.js
CHANGED
|
@@ -244,47 +244,49 @@ var ServiceClient = class {
|
|
|
244
244
|
return this.http.request("POST", "/account/logout", { body });
|
|
245
245
|
}
|
|
246
246
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
247
|
-
// Games
|
|
247
|
+
// Games — one SDK integration = one game, resolved on every call from the
|
|
248
|
+
// public `x-game-key` header (the game's `appId`). There is no multi-game
|
|
249
|
+
// catalog browsing here (list/search/detail across games) and no `:id`/
|
|
250
|
+
// `storeId` path segments — that's the app-facing surface, out of scope
|
|
251
|
+
// for this per-game SDK. Never use the creator-secret tier
|
|
252
|
+
// (`x-creator-key`/`x-game-secret`) from client/browser code — that would
|
|
253
|
+
// leak the secret to every player.
|
|
248
254
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
249
|
-
async
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return this.http.request("GET", `/sdk/games/${encodeURIComponent(gameId)}`, {
|
|
254
|
-
bearerToken
|
|
255
|
+
async recordGameView(gameKey, auth = {}) {
|
|
256
|
+
await this.http.request("POST", "/sdk/games/views/record", {
|
|
257
|
+
...auth,
|
|
258
|
+
headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) }
|
|
255
259
|
});
|
|
260
|
+
return true;
|
|
256
261
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
async trackView(auth, gameId) {
|
|
261
|
-
await this.http.request("POST", `/sdk/games/${encodeURIComponent(gameId)}/view`, {
|
|
262
|
+
/** Signs a fresh `attemptToken` — required by `completeGameplayAttempt`/`completeGameplayMatch`/`recordGameplayAction`. */
|
|
263
|
+
async startGameplayAttempt(gameKey, auth = {}, body = {}) {
|
|
264
|
+
return this.http.request("POST", "/sdk/games/attempts/start", {
|
|
262
265
|
...auth,
|
|
263
|
-
headers: this.trackingHeaders(auth)
|
|
266
|
+
headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
|
|
267
|
+
body
|
|
264
268
|
});
|
|
265
|
-
return true;
|
|
266
269
|
}
|
|
267
|
-
async
|
|
268
|
-
|
|
269
|
-
await this.http.request("POST", `/sdk/games/${encodeURIComponent(gameId)}/play`, {
|
|
270
|
+
async completeGameplayAttempt(gameKey, auth, body) {
|
|
271
|
+
await this.http.request("POST", "/sdk/games/attempts/complete", {
|
|
270
272
|
...auth,
|
|
271
|
-
headers: this.trackingHeaders(auth),
|
|
273
|
+
headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
|
|
272
274
|
body
|
|
273
275
|
});
|
|
274
276
|
return true;
|
|
275
277
|
}
|
|
276
|
-
async
|
|
277
|
-
await this.http.request("POST",
|
|
278
|
+
async completeGameplayMatch(gameKey, auth, body) {
|
|
279
|
+
await this.http.request("POST", "/sdk/games/matches/complete", {
|
|
278
280
|
...auth,
|
|
279
|
-
headers: this.trackingHeaders(auth),
|
|
281
|
+
headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
|
|
280
282
|
body
|
|
281
283
|
});
|
|
282
284
|
return true;
|
|
283
285
|
}
|
|
284
|
-
async
|
|
285
|
-
return this.http.request("POST",
|
|
286
|
+
async recordGameplayAction(gameKey, auth, body) {
|
|
287
|
+
return this.http.request("POST", "/sdk/games/actions/record", {
|
|
286
288
|
...auth,
|
|
287
|
-
headers: this.trackingHeaders(auth),
|
|
289
|
+
headers: { "x-game-key": gameKey, ...this.trackingHeaders(auth) },
|
|
288
290
|
body
|
|
289
291
|
});
|
|
290
292
|
}
|
|
@@ -293,7 +295,7 @@ var ServiceClient = class {
|
|
|
293
295
|
* runtimes should still call it to get a `trackingSessionId` for unified
|
|
294
296
|
* tracing — see `GameTrackingSessionDto`.
|
|
295
297
|
*/
|
|
296
|
-
async
|
|
298
|
+
async bootstrapTrackingSession(anonId) {
|
|
297
299
|
return this.http.request("POST", "/sdk/games/tracking/bootstrap", {
|
|
298
300
|
headers: { "x-looplay-anon-id": anonId }
|
|
299
301
|
});
|
|
@@ -304,22 +306,36 @@ var ServiceClient = class {
|
|
|
304
306
|
"x-looplay-anon-token": auth.anonTrackingToken
|
|
305
307
|
};
|
|
306
308
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
async listGameAssets(gameId, bearerToken) {
|
|
312
|
-
return this.http.request("GET", `/sdk/games/${encodeURIComponent(gameId)}/assets`, {
|
|
313
|
-
bearerToken
|
|
309
|
+
/** Public storefront (sections + offers) for the integration's game. Read-only. */
|
|
310
|
+
async getStore(gameKey) {
|
|
311
|
+
return this.http.request("GET", "/sdk/games/store/current", {
|
|
312
|
+
headers: { "x-game-key": gameKey }
|
|
314
313
|
});
|
|
315
314
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
);
|
|
315
|
+
/** Public catalog of purchasable/ownable assets for the game. Read-only. */
|
|
316
|
+
async listGameAssets(gameKey) {
|
|
317
|
+
return this.http.request("GET", "/sdk/games/store/assets", {
|
|
318
|
+
headers: { "x-game-key": gameKey }
|
|
319
|
+
});
|
|
322
320
|
}
|
|
321
|
+
/** Assets the authenticated player already owns for this game. */
|
|
322
|
+
async listMyGameAssets(bearerToken, gameKey) {
|
|
323
|
+
return this.http.request("GET", "/sdk/games/store/assets/me", {
|
|
324
|
+
bearerToken,
|
|
325
|
+
headers: { "x-game-key": gameKey }
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
/** The authenticated player's past store purchases for this game. */
|
|
329
|
+
async listMyStorePurchases(bearerToken, gameKey) {
|
|
330
|
+
return this.http.request("GET", "/sdk/games/store/purchases/me", {
|
|
331
|
+
bearerToken,
|
|
332
|
+
headers: { "x-game-key": gameKey }
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
// No purchase route is reachable from client/browser code — spending coin
|
|
336
|
+
// balance always requires the creator+game secret pair
|
|
337
|
+
// (`x-creator-key`/`x-game-secret`), called from the game's own backend.
|
|
338
|
+
// This SDK intentionally never wires that tier up.
|
|
323
339
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
324
340
|
// Balance
|
|
325
341
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -350,11 +366,11 @@ var ServiceClient = class {
|
|
|
350
366
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
351
367
|
// Tasks
|
|
352
368
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
353
|
-
async listTasks(bearerToken) {
|
|
354
|
-
return this.http.request("GET", "/sdk/task", { bearerToken });
|
|
369
|
+
async listTasks(bearerToken, query) {
|
|
370
|
+
return this.http.request("GET", "/sdk/task", { bearerToken, query: { ...query } });
|
|
355
371
|
}
|
|
356
|
-
async listFinishedTasks(bearerToken) {
|
|
357
|
-
return this.http.request("GET", "/sdk/task/finished", { bearerToken });
|
|
372
|
+
async listFinishedTasks(bearerToken, query) {
|
|
373
|
+
return this.http.request("GET", "/sdk/task/finished", { bearerToken, query: { ...query } });
|
|
358
374
|
}
|
|
359
375
|
async startTask(bearerToken, body) {
|
|
360
376
|
return this.http.request("POST", "/sdk/task/start", { bearerToken, body });
|
|
@@ -382,6 +398,8 @@ var ApiClient = class {
|
|
|
382
398
|
trackingSession;
|
|
383
399
|
trackingSessionPromise;
|
|
384
400
|
currentAttemptId;
|
|
401
|
+
gameplayAttempt;
|
|
402
|
+
gameplayAttemptPromise;
|
|
385
403
|
constructor(options) {
|
|
386
404
|
if (!options.baseUrl) throw new MissingBaseUrlError();
|
|
387
405
|
this.raw = new ServiceClient({
|
|
@@ -392,15 +410,10 @@ var ApiClient = class {
|
|
|
392
410
|
this.getAccessToken = options.getAccessToken;
|
|
393
411
|
this.getAnonymousId = options.getAnonymousId;
|
|
394
412
|
}
|
|
395
|
-
/** Expose the underlying route-level client (requires manual bearerToken passing). */
|
|
413
|
+
/** Expose the underlying route-level client (requires manual bearerToken/gameKey passing). */
|
|
396
414
|
unsafeRaw() {
|
|
397
415
|
return this.raw;
|
|
398
416
|
}
|
|
399
|
-
// Public endpoints
|
|
400
|
-
async listGames(query) {
|
|
401
|
-
return this.raw.listGames(query);
|
|
402
|
-
}
|
|
403
|
-
// Authenticated endpoints
|
|
404
417
|
async requireToken() {
|
|
405
418
|
const token = await this.getAccessToken?.();
|
|
406
419
|
if (!token) throw new NotAuthenticatedError();
|
|
@@ -437,7 +450,7 @@ var ApiClient = class {
|
|
|
437
450
|
return this.trackingSession;
|
|
438
451
|
}
|
|
439
452
|
if (!this.trackingSessionPromise) {
|
|
440
|
-
this.trackingSessionPromise = this.raw.
|
|
453
|
+
this.trackingSessionPromise = this.raw.bootstrapTrackingSession(anonId).then((dto) => {
|
|
441
454
|
this.trackingSession = {
|
|
442
455
|
trackingSessionId: dto.trackingSessionId,
|
|
443
456
|
anonTrackingToken: dto.trackingToken,
|
|
@@ -450,57 +463,99 @@ var ApiClient = class {
|
|
|
450
463
|
}
|
|
451
464
|
return this.trackingSessionPromise;
|
|
452
465
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
466
|
+
/**
|
|
467
|
+
* Signs (and caches until near expiry) the `attemptToken` required by
|
|
468
|
+
* `trackPlay`/`trackMatch`/`emit` for the current attempt — does not
|
|
469
|
+
* swallow failures, unlike `ensureTrackingSession`, since these calls
|
|
470
|
+
* are rejected outright without a valid token.
|
|
471
|
+
*/
|
|
472
|
+
async ensureGameplayAttempt(gameKey, auth, attemptId) {
|
|
473
|
+
const now = Date.now();
|
|
474
|
+
if (this.gameplayAttempt && this.gameplayAttempt.gameKey === gameKey && this.gameplayAttempt.attemptId === attemptId && this.gameplayAttempt.expiresAtMs > now + 1e4) {
|
|
475
|
+
return this.gameplayAttempt;
|
|
476
|
+
}
|
|
477
|
+
if (!this.gameplayAttemptPromise) {
|
|
478
|
+
this.gameplayAttemptPromise = this.raw.startGameplayAttempt(gameKey, auth, { attemptId }).then((dto) => {
|
|
479
|
+
this.gameplayAttempt = {
|
|
480
|
+
gameKey,
|
|
481
|
+
attemptId: dto.attemptId,
|
|
482
|
+
attemptToken: dto.attemptToken,
|
|
483
|
+
expiresAtMs: new Date(dto.expiresAt).getTime()
|
|
484
|
+
};
|
|
485
|
+
return this.gameplayAttempt;
|
|
486
|
+
}).finally(() => {
|
|
487
|
+
this.gameplayAttemptPromise = void 0;
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return this.gameplayAttemptPromise;
|
|
460
491
|
}
|
|
461
|
-
/**
|
|
462
|
-
|
|
492
|
+
/**
|
|
493
|
+
* Call once when the game view opens, before any play/match tracking.
|
|
494
|
+
* `gameKey` is the game's public `appId`.
|
|
495
|
+
*/
|
|
496
|
+
async trackView(gameKey) {
|
|
463
497
|
const auth = await this.resolveTrackingAuth();
|
|
464
|
-
return this.raw.
|
|
498
|
+
return this.raw.recordGameView(gameKey, auth);
|
|
465
499
|
}
|
|
466
500
|
/**
|
|
467
501
|
* Starts a new play attempt and returns its id. Call when the user
|
|
468
|
-
* presses Play; `trackPlay`
|
|
502
|
+
* presses Play; `trackPlay`/`trackMatch`/`emit` reuse this id (and the
|
|
503
|
+
* signed `attemptToken` backing it) across calls until the next
|
|
469
504
|
* `startAttempt()`, auto-starting one on first use if none was started.
|
|
470
505
|
*/
|
|
471
506
|
startAttempt() {
|
|
472
507
|
this.currentAttemptId = generateId();
|
|
508
|
+
this.gameplayAttempt = void 0;
|
|
473
509
|
return this.currentAttemptId;
|
|
474
510
|
}
|
|
475
|
-
async trackPlay(
|
|
511
|
+
async trackPlay(gameKey, playTimeSeconds) {
|
|
476
512
|
const auth = await this.resolveTrackingAuth();
|
|
477
513
|
const attemptId = this.currentAttemptId ?? this.startAttempt();
|
|
478
|
-
|
|
514
|
+
const { attemptToken } = await this.ensureGameplayAttempt(gameKey, auth, attemptId);
|
|
515
|
+
return this.raw.completeGameplayAttempt(gameKey, auth, { playTimeSeconds, attemptId, attemptToken });
|
|
479
516
|
}
|
|
480
|
-
async trackMatch(
|
|
517
|
+
async trackMatch(gameKey, body) {
|
|
481
518
|
const auth = await this.resolveTrackingAuth();
|
|
482
|
-
|
|
519
|
+
const attemptId = this.currentAttemptId ?? this.startAttempt();
|
|
520
|
+
const { attemptToken } = await this.ensureGameplayAttempt(gameKey, auth, attemptId);
|
|
521
|
+
return this.raw.completeGameplayMatch(gameKey, auth, { ...body, attemptId, attemptToken });
|
|
483
522
|
}
|
|
484
|
-
async emit(
|
|
523
|
+
async emit(gameKey, actionCode, opts) {
|
|
485
524
|
const auth = await this.resolveTrackingAuth();
|
|
525
|
+
const attemptId = this.currentAttemptId ?? this.startAttempt();
|
|
526
|
+
const { attemptToken } = await this.ensureGameplayAttempt(gameKey, auth, attemptId);
|
|
486
527
|
const body = {
|
|
528
|
+
attemptId,
|
|
529
|
+
attemptToken,
|
|
487
530
|
actionCode,
|
|
488
531
|
value: opts?.value,
|
|
489
532
|
refId: opts?.refId,
|
|
490
533
|
payload: opts?.payload
|
|
491
534
|
};
|
|
492
|
-
return this.raw.
|
|
535
|
+
return this.raw.recordGameplayAction(gameKey, auth, body);
|
|
493
536
|
}
|
|
494
|
-
/**
|
|
495
|
-
async
|
|
496
|
-
|
|
497
|
-
|
|
537
|
+
/** Fetches the storefront (sections + offers) for the integration's game. Read-only, no auth required. */
|
|
538
|
+
async getStore(gameKey) {
|
|
539
|
+
return this.raw.getStore(gameKey);
|
|
540
|
+
}
|
|
541
|
+
/** Public catalog of purchasable/ownable assets for the game. Read-only, no auth required. */
|
|
542
|
+
async listGameAssets(gameKey) {
|
|
543
|
+
return this.raw.listGameAssets(gameKey);
|
|
544
|
+
}
|
|
545
|
+
/** Assets the authenticated player already owns for this game. */
|
|
546
|
+
async listMyGameAssets(gameKey) {
|
|
547
|
+
const token = await this.requireToken();
|
|
548
|
+
return this.raw.listMyGameAssets(token, gameKey);
|
|
498
549
|
}
|
|
499
|
-
/**
|
|
500
|
-
async
|
|
550
|
+
/** Purchase history for the authenticated player in the game's store. */
|
|
551
|
+
async listMyStorePurchases(gameKey) {
|
|
501
552
|
const token = await this.requireToken();
|
|
502
|
-
return this.raw.
|
|
553
|
+
return this.raw.listMyStorePurchases(token, gameKey);
|
|
503
554
|
}
|
|
555
|
+
// There is no purchaseStoreOffer() here by design — spending coin balance
|
|
556
|
+
// requires the creator+game secret pair, which must be called from the
|
|
557
|
+
// game's own backend, never from this client-side SDK. Implement a
|
|
558
|
+
// checkout endpoint on your backend that calls gbs-service directly.
|
|
504
559
|
async getMyProfile() {
|
|
505
560
|
const token = await this.requireToken();
|
|
506
561
|
return this.raw.getMyProfile(token);
|
|
@@ -525,13 +580,13 @@ var ApiClient = class {
|
|
|
525
580
|
const token = await this.requireToken();
|
|
526
581
|
return this.raw.setReferral(token, body);
|
|
527
582
|
}
|
|
528
|
-
async listTasks() {
|
|
583
|
+
async listTasks(query) {
|
|
529
584
|
const token = await this.requireToken();
|
|
530
|
-
return this.raw.listTasks(token);
|
|
585
|
+
return this.raw.listTasks(token, query);
|
|
531
586
|
}
|
|
532
|
-
async listFinishedTasks() {
|
|
587
|
+
async listFinishedTasks(query) {
|
|
533
588
|
const token = await this.requireToken();
|
|
534
|
-
return this.raw.listFinishedTasks(token);
|
|
589
|
+
return this.raw.listFinishedTasks(token, query);
|
|
535
590
|
}
|
|
536
591
|
async startTask(body) {
|
|
537
592
|
const token = await this.requireToken();
|
|
@@ -631,9 +686,8 @@ var LooplaySDK = class {
|
|
|
631
686
|
}
|
|
632
687
|
async verifyGameId(params) {
|
|
633
688
|
if ((params.verifyMode ?? "none") === "none") return;
|
|
634
|
-
if (!this.auth) throw new MissingAuthError();
|
|
635
689
|
if (!this.api) throw new MissingBaseUrlError();
|
|
636
|
-
await this.api.
|
|
690
|
+
await this.api.listGameAssets(params.gameId);
|
|
637
691
|
}
|
|
638
692
|
};
|
|
639
693
|
|