@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.
@@ -1,4 +1,4 @@
1
- export type { GameEventKey, TrackMatchEndRequest, EmitGameEventRequest, EmitGameEventResponse } from '@looplay/types';
1
+ export type { GameEventKey, EmitGameEventResponse } from '@looplay/types';
2
2
  export interface AuthResultDto {
3
3
  accessToken: string;
4
4
  refreshToken: string;
@@ -10,11 +10,6 @@ export interface TelegramLoginRequest {
10
10
  export interface RefreshTokenRequest {
11
11
  refreshToken: string;
12
12
  }
13
- export interface TrackGamePlayRequest {
14
- playTimeSeconds?: number;
15
- /** Canonical identifier for one play attempt — required by the backend, stable for retries. */
16
- attemptId: string;
17
- }
18
13
  /**
19
14
  * Returned by `POST /sdk/games/tracking/bootstrap`. Anonymous runtimes must
20
15
  * fetch this first; logged-in runtimes should still attach `trackingSessionId`
@@ -26,31 +21,139 @@ export interface GameTrackingSessionDto {
26
21
  trackingToken: string;
27
22
  expiresAt: string;
28
23
  }
24
+ export interface StartGameplayAttemptRequest {
25
+ /** Optional client-generated id. When omitted, the server creates one. */
26
+ attemptId?: string;
27
+ /**
28
+ * One-call convenience: when set, the server also records this action
29
+ * (e.g. `"GAME_STARTED"`) right after issuing the attempt, using the
30
+ * freshly signed `attemptId`/`attemptToken` — skips a separate
31
+ * `recordGameplayAction` call. See `matchId`/`value`/`refId`/`eventId`/
32
+ * `payload` below for the action's own fields.
33
+ */
34
+ actionCode?: string;
35
+ matchId?: string;
36
+ value?: number;
37
+ refId?: string;
38
+ eventId?: string;
39
+ payload?: Record<string, unknown>;
40
+ }
41
+ export interface GameplayAttemptSessionDto {
42
+ trackingSessionId: string;
43
+ attemptId: string;
44
+ attemptToken: string;
45
+ issuedAt: string;
46
+ expiresAt: string;
47
+ }
48
+ export interface CompleteGameplayAttemptRequest {
49
+ /** Total play duration of this attempt, in seconds. */
50
+ playTimeSeconds?: number;
51
+ /** Canonical identifier for one play attempt. */
52
+ attemptId: string;
53
+ /** Signed by the server when the gameplay attempt starts — see `StartGameplayAttemptRequest`. */
54
+ attemptToken: string;
55
+ }
56
+ export interface CompleteGameplayMatchRequest {
57
+ attemptId: string;
58
+ attemptToken: string;
59
+ /** Idempotency anchor — same matchId = same event, dedup-safe. */
60
+ matchId: string;
61
+ matchDurationSeconds: number;
62
+ isCompleted?: boolean;
63
+ isWin?: boolean;
64
+ }
65
+ export interface RecordGameplayActionRequest {
66
+ attemptId: string;
67
+ attemptToken: string;
68
+ matchId?: string;
69
+ actionCode: string;
70
+ value?: number;
71
+ /**
72
+ * Stable refId for dedup. When present the event is idempotent per
73
+ * (userId, gameId, actionCode, refId). When absent a timestamp is used.
74
+ */
75
+ refId?: string;
76
+ /** Preferred name for the action occurrence id. Same semantics as refId. */
77
+ eventId?: string;
78
+ payload?: Record<string, unknown>;
79
+ }
29
80
  export interface PagingResponse<T> {
30
81
  items: T[];
31
- total: number;
32
- page: number;
33
- limit: number;
82
+ totalItems: number;
83
+ pageIndex: number;
84
+ pageSize: number;
85
+ totalPages: number;
34
86
  }
35
- export interface GameAssetDto {
87
+ export interface GameStoreOfferItemDto {
88
+ assetCode: string;
89
+ assetName: string;
90
+ quantity: number;
91
+ }
92
+ export interface GameStoreOfferDto {
36
93
  id: string;
37
94
  gameId: string;
38
- assetCode: string;
95
+ storeId?: string;
96
+ offerCode: string;
39
97
  name: string;
98
+ sectionCode?: string | null;
40
99
  priceCoin: number;
100
+ discountPercent: number;
101
+ effectivePriceCoin: number;
102
+ startAt?: string | null;
103
+ endAt?: string | null;
104
+ sortOrder: number;
41
105
  isActive: boolean;
106
+ items: GameStoreOfferItemDto[];
42
107
  }
43
- export interface PurchaseGameAssetRequest {
44
- /** Optional client-supplied dedupe key so a retried request returns the same result instead of charging twice. */
45
- requestId?: string;
46
- /** Defaults to 1 on the backend. */
47
- quantity?: number;
108
+ export interface GameStoreSectionWithOffersDto {
109
+ id: string;
110
+ gameId: string;
111
+ code: string;
112
+ name: string;
113
+ sortOrder: number;
114
+ isActive: boolean;
115
+ offers: GameStoreOfferDto[];
48
116
  }
49
- export interface GameAssetPurchaseResultDto {
50
- assetCode: string;
117
+ /** Player-facing storefront for a game. */
118
+ export interface GameStoreDto {
119
+ storeId?: string;
120
+ sections: GameStoreSectionWithOffersDto[];
121
+ /** Active offers with no sectionCode — rendered outside any section. */
122
+ unsectionedOffers: GameStoreOfferDto[];
123
+ }
124
+ export interface GameStoreOfferPurchaseResultDto {
125
+ offerCode: string;
51
126
  name: string;
52
127
  quantity: number;
53
128
  priceCoin: number;
54
129
  totalCoin: number;
55
130
  requestId: string;
131
+ items: GameStoreOfferItemDto[];
132
+ }
133
+ /** Public asset (SKU) catalog entry — no price; price lives on the offer that sells it. */
134
+ export interface GameAssetDto {
135
+ id: string;
136
+ gameId: string;
137
+ assetCode: string;
138
+ name: string;
139
+ isActive: boolean;
140
+ }
141
+ /** An asset the authenticated player currently owns. */
142
+ export interface GameOwnedAssetDto {
143
+ assetCode: string;
144
+ assetName: string;
145
+ quantity: number;
146
+ }
147
+ /** Mirrors the backend's `AppPlatform` enum — kept as a literal union to avoid a cross-repo enum dependency. */
148
+ export type AppPlatform = 'telegram' | 'tiktok' | 'web' | 'base' | 'worldapp';
149
+ /** Mirrors the backend's `MobileOs` enum. */
150
+ export type MobileOs = 'ios' | 'android';
151
+ /**
152
+ * Query for `GET sdk/task` / `GET sdk/task/finished`.
153
+ * - `platform` defaults to `'telegram'` on the backend when omitted.
154
+ * - `os` is optional; when set, returns (os-specific + general) tasks.
155
+ */
156
+ export interface TaskListQuery {
157
+ platform?: AppPlatform;
158
+ os?: MobileOs;
56
159
  }
@@ -1,4 +1,4 @@
1
- import type { EmitGameEventResponse, GameAssetDto, GameAssetPurchaseResultDto, GameEventKey, PagingResponse, PurchaseGameAssetRequest, TrackMatchEndRequest } from './LooplaySDK.types';
1
+ import type { EmitGameEventResponse, GameAssetDto, GameEventKey, GameOwnedAssetDto, GameStoreDto, GameStoreOfferPurchaseResultDto, PagingResponse, TaskListQuery } from './LooplaySDK.types';
2
2
  import { ServiceClient } from './service-client';
3
3
  import type { ServiceClientOptions } from './service-client';
4
4
  export interface ApiClientOptions extends Omit<ServiceClientOptions, 'baseUrl'> {
@@ -21,10 +21,11 @@ export declare class ApiClient {
21
21
  private trackingSession?;
22
22
  private trackingSessionPromise?;
23
23
  private currentAttemptId?;
24
+ private gameplayAttempt?;
25
+ private gameplayAttemptPromise?;
24
26
  constructor(options: ApiClientOptions);
25
- /** Expose the underlying route-level client (requires manual bearerToken passing). */
27
+ /** Expose the underlying route-level client (requires manual bearerToken/gameKey passing). */
26
28
  unsafeRaw(): ServiceClient;
27
- listGames(query?: Record<string, any>): Promise<PagingResponse<any>>;
28
29
  private requireToken;
29
30
  /**
30
31
  * Resolves auth for the tracking endpoints: a real bearer token if logged
@@ -41,35 +42,53 @@ export declare class ApiClient {
41
42
  * just without unified session tracing.
42
43
  */
43
44
  private ensureTrackingSession;
44
- getGameDetail(gameId: string): Promise<any>;
45
- listRecentPlayed(query?: Record<string, any>): Promise<PagingResponse<any>>;
46
- /** Call once when the game view opens, before any play/match tracking. */
47
- trackView(gameId: string): Promise<boolean>;
45
+ /**
46
+ * Signs (and caches until near expiry) the `attemptToken` required by
47
+ * `trackPlay`/`trackMatch`/`emit` for the current attempt does not
48
+ * swallow failures, unlike `ensureTrackingSession`, since these calls
49
+ * are rejected outright without a valid token.
50
+ */
51
+ private ensureGameplayAttempt;
52
+ /**
53
+ * Call once when the game view opens, before any play/match tracking.
54
+ * `gameKey` is the game's public `appId`.
55
+ */
56
+ trackView(gameKey: string): Promise<boolean>;
48
57
  /**
49
58
  * Starts a new play attempt and returns its id. Call when the user
50
- * presses Play; `trackPlay` reuses this id across calls until the next
59
+ * presses Play; `trackPlay`/`trackMatch`/`emit` reuse this id (and the
60
+ * signed `attemptToken` backing it) across calls until the next
51
61
  * `startAttempt()`, auto-starting one on first use if none was started.
52
62
  */
53
63
  startAttempt(): string;
54
- trackPlay(gameId: string, playTimeSeconds?: number): Promise<boolean>;
55
- trackMatch(gameId: string, body: TrackMatchEndRequest): Promise<boolean>;
56
- emit(gameId: string, actionCode: GameEventKey | string, opts?: {
64
+ trackPlay(gameKey: string, playTimeSeconds?: number): Promise<boolean>;
65
+ trackMatch(gameKey: string, body: {
66
+ matchId: string;
67
+ matchDurationSeconds: number;
68
+ isCompleted?: boolean;
69
+ isWin?: boolean;
70
+ }): Promise<boolean>;
71
+ emit(gameKey: string, actionCode: GameEventKey | string, opts?: {
57
72
  value?: number;
58
73
  refId?: string;
59
74
  payload?: Record<string, unknown>;
60
75
  }): Promise<EmitGameEventResponse>;
61
- /** Lists assets purchasable in `gameId`, defined by that game's creator. */
62
- listGameAssets(gameId: string): Promise<GameAssetDto[]>;
63
- /** Spends coin balance to purchase an asset see PurchaseGameAssetRequest for dedupe/quantity. */
64
- purchaseGameAsset(gameId: string, assetCode: string, opts?: PurchaseGameAssetRequest): Promise<GameAssetPurchaseResultDto>;
76
+ /** Fetches the storefront (sections + offers) for the integration's game. Read-only, no auth required. */
77
+ getStore(gameKey: string): Promise<GameStoreDto>;
78
+ /** Public catalog of purchasable/ownable assets for the game. Read-only, no auth required. */
79
+ listGameAssets(gameKey: string): Promise<GameAssetDto[]>;
80
+ /** Assets the authenticated player already owns for this game. */
81
+ listMyGameAssets(gameKey: string): Promise<GameOwnedAssetDto[]>;
82
+ /** Purchase history for the authenticated player in the game's store. */
83
+ listMyStorePurchases(gameKey: string): Promise<GameStoreOfferPurchaseResultDto[]>;
65
84
  getMyProfile(): Promise<any>;
66
85
  getMyBalance(): Promise<any>;
67
86
  getBalances(query?: Record<string, any>): Promise<any>;
68
87
  getBalanceHistory(query?: Record<string, any>): Promise<PagingResponse<any>>;
69
88
  listReferrals(query?: Record<string, any>): Promise<PagingResponse<any>>;
70
89
  setReferral(body: Record<string, any>): Promise<boolean>;
71
- listTasks(): Promise<any[]>;
72
- listFinishedTasks(): Promise<any[]>;
90
+ listTasks(query?: TaskListQuery): Promise<any[]>;
91
+ listFinishedTasks(query?: TaskListQuery): Promise<any[]>;
73
92
  startTask(body: Record<string, any>): Promise<any>;
74
93
  claimTask(body: Record<string, any>): Promise<any>;
75
94
  }
@@ -1,14 +1,14 @@
1
1
  import { type HttpClientOptions } from './http';
2
- import type { AuthResultDto, EmitGameEventRequest, EmitGameEventResponse, GameAssetDto, GameAssetPurchaseResultDto, GameTrackingSessionDto, PagingResponse, PurchaseGameAssetRequest, TrackMatchEndRequest } from './LooplaySDK.types';
2
+ import type { AuthResultDto, CompleteGameplayAttemptRequest, CompleteGameplayMatchRequest, EmitGameEventResponse, GameAssetDto, GameOwnedAssetDto, GameplayAttemptSessionDto, GameStoreDto, GameStoreOfferPurchaseResultDto, GameTrackingSessionDto, PagingResponse, RecordGameplayActionRequest, StartGameplayAttemptRequest, TaskListQuery } from './LooplaySDK.types';
3
3
  export interface ServiceClientOptions extends HttpClientOptions {
4
4
  }
5
5
  /**
6
- * Auth for the tracking endpoints (`view`/`play`/`end`/`emit`), which accept
6
+ * Auth for the tracking endpoints (view/attempt/match/action), which accept
7
7
  * either a real user's `bearerToken` (full tracking, eligible for
8
8
  * quest/reward) or an `anonId` for an unauthenticated device
9
9
  * (analytics-only — the backend never awards quest/reward progress to an
10
10
  * anonymous id). `trackingSessionId`/`anonTrackingToken` come from
11
- * `issueTrackingSession()` and should be attached either way, per the
11
+ * `bootstrapTrackingSession()` and should be attached either way, per the
12
12
  * platform's tracking contract.
13
13
  */
14
14
  export interface TrackingAuth {
@@ -27,30 +27,35 @@ export declare class ServiceClient {
27
27
  telegramLogin(telegramInitData: string): Promise<AuthResultDto>;
28
28
  refresh(refreshToken: string): Promise<AuthResultDto>;
29
29
  logout(refreshToken: string): Promise<boolean>;
30
- listGames(query?: Record<string, any>): Promise<PagingResponse<any>>;
31
- getGameDetail(gameId: string, bearerToken?: string): Promise<any>;
32
- listRecentPlayed(bearerToken: string, query?: Record<string, any>): Promise<PagingResponse<any>>;
33
- trackView(auth: TrackingAuth, gameId: string): Promise<boolean>;
34
- trackPlay(auth: TrackingAuth, gameId: string, attemptId: string, playTimeSeconds?: number): Promise<boolean>;
35
- trackMatchEnd(auth: TrackingAuth, gameId: string, body: TrackMatchEndRequest): Promise<boolean>;
36
- emitGameEvent(auth: TrackingAuth, gameId: string, body: EmitGameEventRequest): Promise<EmitGameEventResponse>;
30
+ recordGameView(gameKey: string, auth?: TrackingAuth): Promise<boolean>;
31
+ /** Signs a fresh `attemptToken` — required by `completeGameplayAttempt`/`completeGameplayMatch`/`recordGameplayAction`. */
32
+ startGameplayAttempt(gameKey: string, auth?: TrackingAuth, body?: StartGameplayAttemptRequest): Promise<GameplayAttemptSessionDto>;
33
+ completeGameplayAttempt(gameKey: string, auth: TrackingAuth, body: CompleteGameplayAttemptRequest): Promise<boolean>;
34
+ completeGameplayMatch(gameKey: string, auth: TrackingAuth, body: CompleteGameplayMatchRequest): Promise<boolean>;
35
+ recordGameplayAction(gameKey: string, auth: TrackingAuth, body: RecordGameplayActionRequest): Promise<EmitGameEventResponse>;
37
36
  /**
38
37
  * Anonymous runtimes must call this before any tracking call; logged-in
39
38
  * runtimes should still call it to get a `trackingSessionId` for unified
40
39
  * tracing — see `GameTrackingSessionDto`.
41
40
  */
42
- issueTrackingSession(anonId?: string): Promise<GameTrackingSessionDto>;
41
+ bootstrapTrackingSession(anonId?: string): Promise<GameTrackingSessionDto>;
43
42
  private trackingHeaders;
44
- listGameAssets(gameId: string, bearerToken?: string): Promise<GameAssetDto[]>;
45
- purchaseGameAsset(bearerToken: string, gameId: string, assetCode: string, body?: PurchaseGameAssetRequest): Promise<GameAssetPurchaseResultDto>;
43
+ /** Public storefront (sections + offers) for the integration's game. Read-only. */
44
+ getStore(gameKey: string): Promise<GameStoreDto>;
45
+ /** Public catalog of purchasable/ownable assets for the game. Read-only. */
46
+ listGameAssets(gameKey: string): Promise<GameAssetDto[]>;
47
+ /** Assets the authenticated player already owns for this game. */
48
+ listMyGameAssets(bearerToken: string, gameKey: string): Promise<GameOwnedAssetDto[]>;
49
+ /** The authenticated player's past store purchases for this game. */
50
+ listMyStorePurchases(bearerToken: string, gameKey: string): Promise<GameStoreOfferPurchaseResultDto[]>;
46
51
  getBalances(bearerToken: string, query?: Record<string, any>): Promise<any>;
47
52
  getBalanceHistory(bearerToken: string, query?: Record<string, any>): Promise<PagingResponse<any>>;
48
53
  getMyProfile(bearerToken: string): Promise<any>;
49
54
  getMyBalance(bearerToken: string): Promise<any>;
50
55
  listReferrals(bearerToken: string, query?: Record<string, any>): Promise<PagingResponse<any>>;
51
56
  setReferral(bearerToken: string, body: Record<string, any>): Promise<boolean>;
52
- listTasks(bearerToken: string): Promise<any[]>;
53
- listFinishedTasks(bearerToken: string): Promise<any[]>;
57
+ listTasks(bearerToken: string, query?: TaskListQuery): Promise<any[]>;
58
+ listFinishedTasks(bearerToken: string, query?: TaskListQuery): Promise<any[]>;
54
59
  startTask(bearerToken: string, body: Record<string, any>): Promise<any>;
55
60
  claimTask(bearerToken: string, body: Record<string, any>): Promise<any>;
56
61
  }
@@ -5,4 +5,3 @@ export * from './auth';
5
5
  export * from './apps';
6
6
  export * from './iframe';
7
7
  export type { GameEventKey } from '@looplay/types';
8
- export type { TrackMatchEndRequest, EmitGameEventRequest, EmitGameEventResponse } from '@looplay/types';
@@ -7,7 +7,8 @@ export interface LooplaySDKCreateOptions {
7
7
  defaultHeaders?: Record<string, string>;
8
8
  }
9
9
  export interface LooplayInitParams {
10
+ /** The game's public `appId` — same value as `x-game-key`, from your creator dashboard. */
10
11
  gameId: string;
11
- /** 'none': skip verification | 'strict': verify via /games/:id (requires auth) */
12
+ /** 'none': skip verification | 'strict': resolve gameId via a read-only, unauthenticated call */
12
13
  verifyMode?: 'none' | 'strict';
13
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@looplay/sdk",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "description": "LooplaySDK (auth + points) for game integrations",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",