@looplay/sdk 0.2.1 → 0.4.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 +164 -62
- package/dist/looplay-sdk.cjs.js.map +1 -1
- package/dist/looplay-sdk.esm.js +164 -62
- 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 +35 -0
- package/dist/types/apps/api-client.d.ts +26 -6
- package/dist/types/apps/service-client.d.ts +21 -10
- package/dist/types/iframe/iframe-auth.d.ts +4 -0
- package/dist/types/looplay-sdk.d.ts +4 -0
- package/package.json +1 -1
package/dist/looplay-sdk.esm.js
CHANGED
|
@@ -247,107 +247,141 @@ var ServiceClient = class {
|
|
|
247
247
|
// Games
|
|
248
248
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
249
249
|
async listGames(query) {
|
|
250
|
-
return this.http.request("GET", "/games", { query });
|
|
250
|
+
return this.http.request("GET", "/sdk/games", { query });
|
|
251
251
|
}
|
|
252
252
|
async getGameDetail(gameId, bearerToken) {
|
|
253
|
-
return this.http.request("GET", `/games/${encodeURIComponent(gameId)}`, {
|
|
253
|
+
return this.http.request("GET", `/sdk/games/${encodeURIComponent(gameId)}`, {
|
|
254
254
|
bearerToken
|
|
255
255
|
});
|
|
256
256
|
}
|
|
257
257
|
async listRecentPlayed(bearerToken, query) {
|
|
258
|
-
return this.http.request("GET", "/games/recent-play", { bearerToken, query });
|
|
258
|
+
return this.http.request("GET", "/sdk/games/recent-play", { bearerToken, query });
|
|
259
259
|
}
|
|
260
|
-
async
|
|
261
|
-
|
|
262
|
-
await this.http.request("POST", `/games/${encodeURIComponent(gameId)}/play`, {
|
|
260
|
+
async trackView(auth, gameId) {
|
|
261
|
+
await this.http.request("POST", `/sdk/games/${encodeURIComponent(gameId)}/view`, {
|
|
263
262
|
...auth,
|
|
263
|
+
headers: this.trackingHeaders(auth)
|
|
264
|
+
});
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
async trackPlay(auth, gameId, attemptId, playTimeSeconds) {
|
|
268
|
+
const body = { playTimeSeconds, attemptId };
|
|
269
|
+
await this.http.request("POST", `/sdk/games/${encodeURIComponent(gameId)}/play`, {
|
|
270
|
+
...auth,
|
|
271
|
+
headers: this.trackingHeaders(auth),
|
|
264
272
|
body
|
|
265
273
|
});
|
|
266
274
|
return true;
|
|
267
275
|
}
|
|
268
276
|
async trackMatchEnd(auth, gameId, body) {
|
|
269
|
-
await this.http.request("POST", `/games/${encodeURIComponent(gameId)}/end`, {
|
|
277
|
+
await this.http.request("POST", `/sdk/games/${encodeURIComponent(gameId)}/end`, {
|
|
270
278
|
...auth,
|
|
279
|
+
headers: this.trackingHeaders(auth),
|
|
271
280
|
body
|
|
272
281
|
});
|
|
273
282
|
return true;
|
|
274
283
|
}
|
|
275
284
|
async emitGameEvent(auth, gameId, body) {
|
|
276
|
-
return this.http.request("POST", `/games/${encodeURIComponent(gameId)}/emit`, {
|
|
285
|
+
return this.http.request("POST", `/sdk/games/${encodeURIComponent(gameId)}/emit`, {
|
|
277
286
|
...auth,
|
|
287
|
+
headers: this.trackingHeaders(auth),
|
|
278
288
|
body
|
|
279
289
|
});
|
|
280
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Anonymous runtimes must call this before any tracking call; logged-in
|
|
293
|
+
* runtimes should still call it to get a `trackingSessionId` for unified
|
|
294
|
+
* tracing — see `GameTrackingSessionDto`.
|
|
295
|
+
*/
|
|
296
|
+
async issueTrackingSession(anonId) {
|
|
297
|
+
return this.http.request("POST", "/sdk/games/tracking/bootstrap", {
|
|
298
|
+
headers: { "x-looplay-anon-id": anonId }
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
trackingHeaders(auth) {
|
|
302
|
+
return {
|
|
303
|
+
"x-game-tracking-session-id": auth.trackingSessionId,
|
|
304
|
+
"x-looplay-anon-token": auth.anonTrackingToken
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
308
|
+
// Game assets — spend coin balance on assets defined by the game's
|
|
309
|
+
// creator. Coin balance itself comes from getBalances()/getMyBalance().
|
|
310
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
311
|
+
async listGameAssets(gameId, bearerToken) {
|
|
312
|
+
return this.http.request("GET", `/sdk/games/${encodeURIComponent(gameId)}/assets`, {
|
|
313
|
+
bearerToken
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
async purchaseGameAsset(bearerToken, gameId, assetCode, body = {}) {
|
|
317
|
+
return this.http.request(
|
|
318
|
+
"POST",
|
|
319
|
+
`/sdk/games/${encodeURIComponent(gameId)}/assets/${encodeURIComponent(assetCode)}/purchase`,
|
|
320
|
+
{ bearerToken, body }
|
|
321
|
+
);
|
|
322
|
+
}
|
|
281
323
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
282
324
|
// Balance
|
|
283
325
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
284
326
|
async getBalances(bearerToken, query) {
|
|
285
|
-
return this.http.request("GET", "/balance", { bearerToken, query });
|
|
327
|
+
return this.http.request("GET", "/sdk/balance", { bearerToken, query });
|
|
286
328
|
}
|
|
287
329
|
async getBalanceHistory(bearerToken, query) {
|
|
288
|
-
return this.http.request("GET", "/balance/history", { bearerToken, query });
|
|
330
|
+
return this.http.request("GET", "/sdk/balance/history", { bearerToken, query });
|
|
289
331
|
}
|
|
290
332
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
291
333
|
// User
|
|
292
334
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
293
335
|
async getMyProfile(bearerToken) {
|
|
294
|
-
return this.http.request("GET", "/user/profile", { bearerToken });
|
|
336
|
+
return this.http.request("GET", "/sdk/user/profile", { bearerToken });
|
|
295
337
|
}
|
|
296
338
|
async getMyBalance(bearerToken) {
|
|
297
|
-
return this.http.request("GET", "/user/balance", { bearerToken });
|
|
339
|
+
return this.http.request("GET", "/sdk/user/balance", { bearerToken });
|
|
298
340
|
}
|
|
299
341
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
300
342
|
// Referrals
|
|
301
343
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
302
344
|
async listReferrals(bearerToken, query) {
|
|
303
|
-
return this.http.request("GET", "/referrals", { bearerToken, query });
|
|
345
|
+
return this.http.request("GET", "/sdk/referrals", { bearerToken, query });
|
|
304
346
|
}
|
|
305
347
|
async setReferral(bearerToken, body) {
|
|
306
|
-
return this.http.request("POST", "/referrals/set", { bearerToken, body });
|
|
348
|
+
return this.http.request("POST", "/sdk/referrals/set", { bearerToken, body });
|
|
307
349
|
}
|
|
308
350
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
309
|
-
// Tasks
|
|
351
|
+
// Tasks
|
|
310
352
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
311
353
|
async listTasks(bearerToken) {
|
|
312
|
-
return this.http.request("GET", "/task", { bearerToken });
|
|
354
|
+
return this.http.request("GET", "/sdk/task", { bearerToken });
|
|
313
355
|
}
|
|
314
356
|
async listFinishedTasks(bearerToken) {
|
|
315
|
-
return this.http.request("GET", "/task/finished", { bearerToken });
|
|
357
|
+
return this.http.request("GET", "/sdk/task/finished", { bearerToken });
|
|
316
358
|
}
|
|
317
359
|
async startTask(bearerToken, body) {
|
|
318
|
-
return this.http.request("POST", "/task/start", { bearerToken, body });
|
|
360
|
+
return this.http.request("POST", "/sdk/task/start", { bearerToken, body });
|
|
319
361
|
}
|
|
320
362
|
async claimTask(bearerToken, body) {
|
|
321
|
-
return this.http.request("POST", "/task/claim", { bearerToken, body });
|
|
322
|
-
}
|
|
323
|
-
async getPlatformQuests(bearerToken, query) {
|
|
324
|
-
return this.http.request("GET", "/quest/platform", { bearerToken, query });
|
|
325
|
-
}
|
|
326
|
-
async getCampaignQuests(bearerToken, query) {
|
|
327
|
-
return this.http.request("GET", "/quest/campaign", { bearerToken, query });
|
|
328
|
-
}
|
|
329
|
-
async claimQuest(bearerToken, questProgressId) {
|
|
330
|
-
return this.http.request("POST", `/quest/${encodeURIComponent(questProgressId)}/claim`, {
|
|
331
|
-
bearerToken
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
async claimMilestone(bearerToken, milestoneId, body) {
|
|
335
|
-
return this.http.request(
|
|
336
|
-
"POST",
|
|
337
|
-
`/quest/milestone/${encodeURIComponent(milestoneId)}/claim`,
|
|
338
|
-
{
|
|
339
|
-
bearerToken,
|
|
340
|
-
body
|
|
341
|
-
}
|
|
342
|
-
);
|
|
363
|
+
return this.http.request("POST", "/sdk/task/claim", { bearerToken, body });
|
|
343
364
|
}
|
|
344
365
|
};
|
|
345
366
|
|
|
346
367
|
// src/apps/api-client.ts
|
|
368
|
+
function generateId() {
|
|
369
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
370
|
+
return crypto.randomUUID();
|
|
371
|
+
}
|
|
372
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
373
|
+
const r = Math.random() * 16 | 0;
|
|
374
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
375
|
+
return v.toString(16);
|
|
376
|
+
});
|
|
377
|
+
}
|
|
347
378
|
var ApiClient = class {
|
|
348
379
|
raw;
|
|
349
380
|
getAccessToken;
|
|
350
381
|
getAnonymousId;
|
|
382
|
+
trackingSession;
|
|
383
|
+
trackingSessionPromise;
|
|
384
|
+
currentAttemptId;
|
|
351
385
|
constructor(options) {
|
|
352
386
|
if (!options.baseUrl) throw new MissingBaseUrlError();
|
|
353
387
|
this.raw = new ServiceClient({
|
|
@@ -375,14 +409,46 @@ var ApiClient = class {
|
|
|
375
409
|
/**
|
|
376
410
|
* Resolves auth for the tracking endpoints: a real bearer token if logged
|
|
377
411
|
* in, otherwise an anonymous device id. Throws only when neither is
|
|
378
|
-
* available — anonymous tracking still requires *some* identity.
|
|
412
|
+
* available — anonymous tracking still requires *some* identity. Also
|
|
413
|
+
* attaches a `trackingSessionId`/`anonTrackingToken` (bootstrapped and
|
|
414
|
+
* cached lazily) — the backend's tracking contract expects these even for
|
|
415
|
+
* logged-in callers, for unified tracing.
|
|
379
416
|
*/
|
|
380
417
|
async resolveTrackingAuth() {
|
|
381
418
|
const bearerToken = await this.getAccessToken?.();
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
419
|
+
const anonId = bearerToken ? void 0 : this.getAnonymousId?.();
|
|
420
|
+
if (!bearerToken && !anonId) throw new NotAuthenticatedError();
|
|
421
|
+
const session = await this.ensureTrackingSession(anonId);
|
|
422
|
+
return {
|
|
423
|
+
bearerToken,
|
|
424
|
+
anonId,
|
|
425
|
+
trackingSessionId: session?.trackingSessionId,
|
|
426
|
+
anonTrackingToken: session?.anonTrackingToken
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Bootstraps (and caches until near expiry) the platform tracking
|
|
431
|
+
* session. Swallows failures — tracking calls still work without it,
|
|
432
|
+
* just without unified session tracing.
|
|
433
|
+
*/
|
|
434
|
+
async ensureTrackingSession(anonId) {
|
|
435
|
+
const now = Date.now();
|
|
436
|
+
if (this.trackingSession && this.trackingSession.expiresAtMs > now + 3e4) {
|
|
437
|
+
return this.trackingSession;
|
|
438
|
+
}
|
|
439
|
+
if (!this.trackingSessionPromise) {
|
|
440
|
+
this.trackingSessionPromise = this.raw.issueTrackingSession(anonId).then((dto) => {
|
|
441
|
+
this.trackingSession = {
|
|
442
|
+
trackingSessionId: dto.trackingSessionId,
|
|
443
|
+
anonTrackingToken: dto.trackingToken,
|
|
444
|
+
expiresAtMs: new Date(dto.expiresAt).getTime()
|
|
445
|
+
};
|
|
446
|
+
return this.trackingSession;
|
|
447
|
+
}).catch(() => void 0).finally(() => {
|
|
448
|
+
this.trackingSessionPromise = void 0;
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
return this.trackingSessionPromise;
|
|
386
452
|
}
|
|
387
453
|
async getGameDetail(gameId) {
|
|
388
454
|
const token = await this.getAccessToken?.();
|
|
@@ -392,9 +458,24 @@ var ApiClient = class {
|
|
|
392
458
|
const token = await this.requireToken();
|
|
393
459
|
return this.raw.listRecentPlayed(token, query);
|
|
394
460
|
}
|
|
461
|
+
/** Call once when the game view opens, before any play/match tracking. */
|
|
462
|
+
async trackView(gameId) {
|
|
463
|
+
const auth = await this.resolveTrackingAuth();
|
|
464
|
+
return this.raw.trackView(auth, gameId);
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Starts a new play attempt and returns its id. Call when the user
|
|
468
|
+
* presses Play; `trackPlay` reuses this id across calls until the next
|
|
469
|
+
* `startAttempt()`, auto-starting one on first use if none was started.
|
|
470
|
+
*/
|
|
471
|
+
startAttempt() {
|
|
472
|
+
this.currentAttemptId = generateId();
|
|
473
|
+
return this.currentAttemptId;
|
|
474
|
+
}
|
|
395
475
|
async trackPlay(gameId, playTimeSeconds) {
|
|
396
476
|
const auth = await this.resolveTrackingAuth();
|
|
397
|
-
|
|
477
|
+
const attemptId = this.currentAttemptId ?? this.startAttempt();
|
|
478
|
+
return this.raw.trackPlay(auth, gameId, attemptId, playTimeSeconds);
|
|
398
479
|
}
|
|
399
480
|
async trackMatch(gameId, body) {
|
|
400
481
|
const auth = await this.resolveTrackingAuth();
|
|
@@ -410,6 +491,16 @@ var ApiClient = class {
|
|
|
410
491
|
};
|
|
411
492
|
return this.raw.emitGameEvent(auth, gameId, body);
|
|
412
493
|
}
|
|
494
|
+
/** Lists assets purchasable in `gameId`, defined by that game's creator. */
|
|
495
|
+
async listGameAssets(gameId) {
|
|
496
|
+
const token = await this.getAccessToken?.();
|
|
497
|
+
return this.raw.listGameAssets(gameId, token);
|
|
498
|
+
}
|
|
499
|
+
/** Spends coin balance to purchase an asset — see PurchaseGameAssetRequest for dedupe/quantity. */
|
|
500
|
+
async purchaseGameAsset(gameId, assetCode, opts) {
|
|
501
|
+
const token = await this.requireToken();
|
|
502
|
+
return this.raw.purchaseGameAsset(token, gameId, assetCode, opts);
|
|
503
|
+
}
|
|
413
504
|
async getMyProfile() {
|
|
414
505
|
const token = await this.requireToken();
|
|
415
506
|
return this.raw.getMyProfile(token);
|
|
@@ -450,22 +541,6 @@ var ApiClient = class {
|
|
|
450
541
|
const token = await this.requireToken();
|
|
451
542
|
return this.raw.claimTask(token, body);
|
|
452
543
|
}
|
|
453
|
-
async getPlatformQuests(query) {
|
|
454
|
-
const token = await this.requireToken();
|
|
455
|
-
return this.raw.getPlatformQuests(token, query);
|
|
456
|
-
}
|
|
457
|
-
async getCampaignQuests(query) {
|
|
458
|
-
const token = await this.requireToken();
|
|
459
|
-
return this.raw.getCampaignQuests(token, query);
|
|
460
|
-
}
|
|
461
|
-
async claimQuest(questProgressId) {
|
|
462
|
-
const token = await this.requireToken();
|
|
463
|
-
return this.raw.claimQuest(token, questProgressId);
|
|
464
|
-
}
|
|
465
|
-
async claimMilestone(milestoneId, body) {
|
|
466
|
-
const token = await this.requireToken();
|
|
467
|
-
return this.raw.claimMilestone(token, milestoneId, body);
|
|
468
|
-
}
|
|
469
544
|
};
|
|
470
545
|
|
|
471
546
|
// src/looplay-sdk.ts
|
|
@@ -505,6 +580,19 @@ var LooplaySDK = class {
|
|
|
505
580
|
if (!this.api) throw new MissingBaseUrlError();
|
|
506
581
|
return this.api.getMyProfile();
|
|
507
582
|
}
|
|
583
|
+
/** Call once when the game view opens, before any play/match tracking. */
|
|
584
|
+
async trackView() {
|
|
585
|
+
this.assertInitialized();
|
|
586
|
+
if (!this.api) throw new MissingBaseUrlError();
|
|
587
|
+
if (!this.gameId) throw new NotInitializedError();
|
|
588
|
+
return this.api.trackView(this.gameId);
|
|
589
|
+
}
|
|
590
|
+
/** Starts a new play attempt; call when the user presses Play. Returns the attempt id. */
|
|
591
|
+
startAttempt() {
|
|
592
|
+
this.assertInitialized();
|
|
593
|
+
if (!this.api) throw new MissingBaseUrlError();
|
|
594
|
+
return this.api.startAttempt();
|
|
595
|
+
}
|
|
508
596
|
async trackPlay(playTimeSeconds) {
|
|
509
597
|
this.assertInitialized();
|
|
510
598
|
if (!this.api) throw new MissingBaseUrlError();
|
|
@@ -800,6 +888,20 @@ var LooplayIframeAuth = class {
|
|
|
800
888
|
});
|
|
801
889
|
return this.client;
|
|
802
890
|
}
|
|
891
|
+
/** Call once when the game view opens, before any play/match tracking. */
|
|
892
|
+
trackView() {
|
|
893
|
+
const gameId = this.getGameId();
|
|
894
|
+
if (!this.canTrack() || !gameId) {
|
|
895
|
+
this.debug("trackView skipped; auth is not ready", { hasGameId: Boolean(gameId) });
|
|
896
|
+
return Promise.resolve(false);
|
|
897
|
+
}
|
|
898
|
+
this.debug("trackView dispatched", { gameId });
|
|
899
|
+
return this.getClient().trackView(gameId);
|
|
900
|
+
}
|
|
901
|
+
/** Starts a new play attempt; call when the user presses Play. Returns the attempt id. */
|
|
902
|
+
startAttempt() {
|
|
903
|
+
return this.getClient().startAttempt();
|
|
904
|
+
}
|
|
803
905
|
trackPlay(playTimeSeconds) {
|
|
804
906
|
const gameId = this.getGameId();
|
|
805
907
|
if (!this.canTrack() || !gameId) {
|