@looplay/sdk 0.5.0 → 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.
@@ -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,149 @@ 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
+ export interface GameplayAttemptSessionDto {
29
+ trackingSessionId: string;
30
+ attemptId: string;
31
+ attemptToken: string;
32
+ issuedAt: string;
33
+ expiresAt: string;
34
+ }
35
+ export interface CompleteGameplayAttemptRequest {
36
+ /** Total play duration of this attempt, in seconds. */
37
+ playTimeSeconds?: number;
38
+ /** Canonical identifier for one play attempt. */
39
+ attemptId: string;
40
+ /** Signed by the server when the gameplay attempt starts — see `StartGameplayAttemptRequest`. */
41
+ attemptToken: string;
42
+ }
43
+ export interface CompleteGameplayMatchRequest {
44
+ attemptId: string;
45
+ attemptToken: string;
46
+ /** Idempotency anchor — same matchId = same event, dedup-safe. */
47
+ matchId: string;
48
+ matchDurationSeconds: number;
49
+ isCompleted?: boolean;
50
+ isWin?: boolean;
51
+ }
52
+ export interface RecordGameplayActionRequest {
53
+ attemptId: string;
54
+ attemptToken: string;
55
+ matchId?: string;
56
+ actionCode: string;
57
+ value?: number;
58
+ /**
59
+ * Stable refId for dedup. When present the event is idempotent per
60
+ * (userId, gameId, actionCode, refId). When absent a timestamp is used.
61
+ */
62
+ refId?: string;
63
+ /** Preferred name for the action occurrence id. Same semantics as refId. */
64
+ eventId?: string;
65
+ payload?: Record<string, unknown>;
66
+ }
29
67
  export interface PagingResponse<T> {
30
68
  items: T[];
31
- total: number;
32
- page: number;
33
- limit: number;
69
+ totalItems: number;
70
+ pageIndex: number;
71
+ pageSize: number;
72
+ totalPages: number;
34
73
  }
74
+ export interface GameStoreOfferItemDto {
75
+ assetCode: string;
76
+ assetName: string;
77
+ quantity: number;
78
+ }
79
+ export interface GameStoreOfferDto {
80
+ id: string;
81
+ gameId: string;
82
+ storeId?: string;
83
+ offerCode: string;
84
+ name: string;
85
+ sectionCode?: string | null;
86
+ priceCoin: number;
87
+ discountPercent: number;
88
+ effectivePriceCoin: number;
89
+ startAt?: string | null;
90
+ endAt?: string | null;
91
+ sortOrder: number;
92
+ isActive: boolean;
93
+ items: GameStoreOfferItemDto[];
94
+ }
95
+ export interface GameStoreSectionWithOffersDto {
96
+ id: string;
97
+ gameId: string;
98
+ code: string;
99
+ name: string;
100
+ sortOrder: number;
101
+ isActive: boolean;
102
+ offers: GameStoreOfferDto[];
103
+ }
104
+ /** Player-facing storefront for a game. */
105
+ export interface GameStoreDto {
106
+ storeId?: string;
107
+ sections: GameStoreSectionWithOffersDto[];
108
+ /** Active offers with no sectionCode — rendered outside any section. */
109
+ unsectionedOffers: GameStoreOfferDto[];
110
+ }
111
+ export interface GameStoreOfferPurchaseResultDto {
112
+ offerCode: string;
113
+ name: string;
114
+ quantity: number;
115
+ priceCoin: number;
116
+ totalCoin: number;
117
+ requestId: string;
118
+ items: GameStoreOfferItemDto[];
119
+ }
120
+ /** Public asset (SKU) catalog entry — no price; price lives on the offer that sells it. */
35
121
  export interface GameAssetDto {
36
122
  id: string;
37
123
  gameId: string;
38
124
  assetCode: string;
39
125
  name: string;
40
- priceCoin: number;
41
126
  isActive: boolean;
42
127
  }
43
- export interface PurchaseGameAssetRequest {
128
+ /** An asset the authenticated player currently owns. */
129
+ export interface GameOwnedAssetDto {
130
+ assetCode: string;
131
+ assetName: string;
132
+ quantity: number;
133
+ }
134
+ /** JWT tier — `POST sdk/games/store/by-store-id/checkout-intent`. */
135
+ export interface CreateGameStoreCheckoutIntentRequest {
136
+ storeId: string;
137
+ offerCode: string;
44
138
  /** Optional client-supplied dedupe key so a retried request returns the same result instead of charging twice. */
45
139
  requestId?: string;
46
140
  /** Defaults to 1 on the backend. */
47
141
  quantity?: number;
48
142
  }
49
- export interface GameAssetPurchaseResultDto {
50
- assetCode: string;
51
- name: string;
52
- quantity: number;
53
- priceCoin: number;
54
- totalCoin: number;
143
+ export interface GameStoreCheckoutIntentDto {
144
+ storeId: string;
145
+ offerCode: string;
55
146
  requestId: string;
147
+ quantity: number;
148
+ checkoutToken: string;
149
+ issuedAt: string;
150
+ expiresAt: string;
151
+ }
152
+ /** JWT tier — `POST sdk/games/store/by-store-id/purchase`. */
153
+ export interface PurchaseGameStoreOfferRuntimeRequest extends CreateGameStoreCheckoutIntentRequest {
154
+ /** Signed by the server when the checkout intent is created — see `CreateGameStoreCheckoutIntentRequest`. */
155
+ checkoutToken: string;
156
+ }
157
+ /** Mirrors the backend's `AppPlatform` enum — kept as a literal union to avoid a cross-repo enum dependency. */
158
+ export type AppPlatform = 'telegram' | 'tiktok' | 'web' | 'base' | 'worldapp';
159
+ /** Mirrors the backend's `MobileOs` enum. */
160
+ export type MobileOs = 'ios' | 'android';
161
+ /**
162
+ * Query for `GET sdk/task` / `GET sdk/task/finished`.
163
+ * - `platform` defaults to `'telegram'` on the backend when omitted.
164
+ * - `os` is optional; when set, returns (os-specific + general) tasks.
165
+ */
166
+ export interface TaskListQuery {
167
+ platform?: AppPlatform;
168
+ os?: MobileOs;
56
169
  }
@@ -1,4 +1,4 @@
1
- import type { EmitGameEventResponse, GameAssetDto, GameAssetPurchaseResultDto, GameEventKey, PagingResponse, PurchaseGameAssetRequest, TrackMatchEndRequest } from './LooplaySDK.types';
1
+ import type { EmitGameEventResponse, GameEventKey, 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,6 +21,8 @@ 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
27
  /** Expose the underlying route-level client (requires manual bearerToken passing). */
26
28
  unsafeRaw(): ServiceClient;
@@ -41,35 +43,57 @@ export declare class ApiClient {
41
43
  * just without unified session tracing.
42
44
  */
43
45
  private ensureTrackingSession;
46
+ /**
47
+ * Signs (and caches until near expiry) the `attemptToken` required by
48
+ * `trackPlay`/`trackMatch`/`emit` for the current attempt — does not
49
+ * swallow failures, unlike `ensureTrackingSession`, since these calls
50
+ * are rejected outright without a valid token.
51
+ */
52
+ private ensureGameplayAttempt;
44
53
  getGameDetail(gameId: string): Promise<any>;
54
+ private resolveStoreId;
45
55
  listRecentPlayed(query?: Record<string, any>): Promise<PagingResponse<any>>;
46
56
  /** Call once when the game view opens, before any play/match tracking. */
47
57
  trackView(gameId: string): Promise<boolean>;
48
58
  /**
49
59
  * 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
60
+ * presses Play; `trackPlay`/`trackMatch`/`emit` reuse this id (and the
61
+ * signed `attemptToken` backing it) across calls until the next
51
62
  * `startAttempt()`, auto-starting one on first use if none was started.
52
63
  */
53
64
  startAttempt(): string;
54
65
  trackPlay(gameId: string, playTimeSeconds?: number): Promise<boolean>;
55
- trackMatch(gameId: string, body: TrackMatchEndRequest): Promise<boolean>;
66
+ trackMatch(gameId: string, body: {
67
+ matchId: string;
68
+ matchDurationSeconds: number;
69
+ isCompleted?: boolean;
70
+ isWin?: boolean;
71
+ }): Promise<boolean>;
56
72
  emit(gameId: string, actionCode: GameEventKey | string, opts?: {
57
73
  value?: number;
58
74
  refId?: string;
59
75
  payload?: Record<string, unknown>;
60
76
  }): 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>;
77
+ /** Fetches the storefront (sections + offers) for `gameId`, defined by that game's creator. */
78
+ getStore(gameId: string): Promise<GameStoreDto>;
79
+ /** Purchase history for the authenticated player in `gameId`'s store. */
80
+ listMyStorePurchases(gameId: string): Promise<GameStoreOfferPurchaseResultDto[]>;
81
+ /**
82
+ * Spends coin balance to purchase a store offer — signs a checkout intent
83
+ * first, then redeems it, mirroring the gameplay attempt flow above.
84
+ */
85
+ purchaseStoreOffer(gameId: string, offerCode: string, opts?: {
86
+ requestId?: string;
87
+ quantity?: number;
88
+ }): Promise<GameStoreOfferPurchaseResultDto>;
65
89
  getMyProfile(): Promise<any>;
66
90
  getMyBalance(): Promise<any>;
67
91
  getBalances(query?: Record<string, any>): Promise<any>;
68
92
  getBalanceHistory(query?: Record<string, any>): Promise<PagingResponse<any>>;
69
93
  listReferrals(query?: Record<string, any>): Promise<PagingResponse<any>>;
70
94
  setReferral(body: Record<string, any>): Promise<boolean>;
71
- listTasks(): Promise<any[]>;
72
- listFinishedTasks(): Promise<any[]>;
95
+ listTasks(query?: TaskListQuery): Promise<any[]>;
96
+ listFinishedTasks(query?: TaskListQuery): Promise<any[]>;
73
97
  startTask(body: Record<string, any>): Promise<any>;
74
98
  claimTask(body: Record<string, any>): Promise<any>;
75
99
  }
@@ -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, CreateGameStoreCheckoutIntentRequest, EmitGameEventResponse, GameAssetDto, GameOwnedAssetDto, GameplayAttemptSessionDto, GameStoreCheckoutIntentDto, GameStoreDto, GameStoreOfferPurchaseResultDto, GameTrackingSessionDto, PagingResponse, PurchaseGameStoreOfferRuntimeRequest, 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 {
@@ -30,27 +30,40 @@ export declare class ServiceClient {
30
30
  listGames(query?: Record<string, any>): Promise<PagingResponse<any>>;
31
31
  getGameDetail(gameId: string, bearerToken?: string): Promise<any>;
32
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>;
33
+ recordGameView(auth: TrackingAuth, gameId: string): Promise<boolean>;
34
+ /** Signs a fresh `attemptToken` required by `completeGameplayAttempt`/`completeGameplayMatch`/`recordGameplayAction`. */
35
+ startGameplayAttempt(auth: TrackingAuth, gameId: string, body?: StartGameplayAttemptRequest): Promise<GameplayAttemptSessionDto>;
36
+ completeGameplayAttempt(auth: TrackingAuth, gameId: string, body: CompleteGameplayAttemptRequest): Promise<boolean>;
37
+ completeGameplayMatch(auth: TrackingAuth, gameId: string, body: CompleteGameplayMatchRequest): Promise<boolean>;
38
+ recordGameplayAction(auth: TrackingAuth, gameId: string, body: RecordGameplayActionRequest): Promise<EmitGameEventResponse>;
37
39
  /**
38
40
  * Anonymous runtimes must call this before any tracking call; logged-in
39
41
  * runtimes should still call it to get a `trackingSessionId` for unified
40
42
  * tracing — see `GameTrackingSessionDto`.
41
43
  */
42
- issueTrackingSession(anonId?: string): Promise<GameTrackingSessionDto>;
44
+ bootstrapTrackingSession(anonId?: string): Promise<GameTrackingSessionDto>;
43
45
  private trackingHeaders;
44
- listGameAssets(gameId: string, bearerToken?: string): Promise<GameAssetDto[]>;
45
- purchaseGameAsset(bearerToken: string, gameId: string, assetCode: string, body?: PurchaseGameAssetRequest): Promise<GameAssetPurchaseResultDto>;
46
+ getStoreByStoreId(storeId: string): Promise<GameStoreDto>;
47
+ listMyStorePurchasesByStoreId(bearerToken: string, storeId: string): Promise<GameStoreOfferPurchaseResultDto[]>;
48
+ createStoreCheckoutIntent(bearerToken: string, body: CreateGameStoreCheckoutIntentRequest, trackingSessionId?: string): Promise<GameStoreCheckoutIntentDto>;
49
+ purchaseStoreOffer(bearerToken: string, body: PurchaseGameStoreOfferRuntimeRequest, trackingSessionId?: string): Promise<GameStoreOfferPurchaseResultDto>;
50
+ recordGameViewForGameKey(gameKey: string, auth?: TrackingAuth): Promise<boolean>;
51
+ startGameplayAttemptForGameKey(gameKey: string, auth?: TrackingAuth, body?: StartGameplayAttemptRequest): Promise<GameplayAttemptSessionDto>;
52
+ completeGameplayAttemptForGameKey(gameKey: string, auth: TrackingAuth | undefined, body: CompleteGameplayAttemptRequest): Promise<boolean>;
53
+ completeGameplayMatchForGameKey(gameKey: string, auth: TrackingAuth | undefined, body: CompleteGameplayMatchRequest): Promise<boolean>;
54
+ recordGameplayActionForGameKey(gameKey: string, auth: TrackingAuth | undefined, body: RecordGameplayActionRequest): Promise<EmitGameEventResponse>;
55
+ getStoreForGameKey(gameKey: string): Promise<GameStoreDto>;
56
+ listGameAssetsForGameKey(gameKey: string): Promise<GameAssetDto[]>;
57
+ listMyOwnedAssetsForGameKey(bearerToken: string, gameKey: string): Promise<GameOwnedAssetDto[]>;
58
+ listMyStorePurchasesForGameKey(bearerToken: string, gameKey: string): Promise<GameStoreOfferPurchaseResultDto[]>;
46
59
  getBalances(bearerToken: string, query?: Record<string, any>): Promise<any>;
47
60
  getBalanceHistory(bearerToken: string, query?: Record<string, any>): Promise<PagingResponse<any>>;
48
61
  getMyProfile(bearerToken: string): Promise<any>;
49
62
  getMyBalance(bearerToken: string): Promise<any>;
50
63
  listReferrals(bearerToken: string, query?: Record<string, any>): Promise<PagingResponse<any>>;
51
64
  setReferral(bearerToken: string, body: Record<string, any>): Promise<boolean>;
52
- listTasks(bearerToken: string): Promise<any[]>;
53
- listFinishedTasks(bearerToken: string): Promise<any[]>;
65
+ listTasks(bearerToken: string, query?: TaskListQuery): Promise<any[]>;
66
+ listFinishedTasks(bearerToken: string, query?: TaskListQuery): Promise<any[]>;
54
67
  startTask(bearerToken: string, body: Record<string, any>): Promise<any>;
55
68
  claimTask(bearerToken: string, body: Record<string, any>): Promise<any>;
56
69
  }
@@ -13,3 +13,6 @@ export declare class MissingAuthError extends LooplaySDKError {
13
13
  export declare class NotAuthenticatedError extends LooplaySDKError {
14
14
  constructor();
15
15
  }
16
+ export declare class MissingStoreError extends LooplaySDKError {
17
+ constructor(gameId: string);
18
+ }
@@ -36,9 +36,9 @@ export interface LooplayIframeAuthOptions {
36
36
  /** localStorage key for the persisted anonymous device id. Default `'looplay:anon-id'`. */
37
37
  anonymousIdStorageKey?: string;
38
38
  /**
39
- * Optional auto-trigger for rewarded ads after play/match activity.
39
+ * Optional auto-trigger for rewarded ads after match activity.
40
40
  * When enabled, the SDK may call `requestAds()` after the configured
41
- * total play/match threshold is reached and the random chance passes.
41
+ * match threshold is reached and the random chance passes.
42
42
  */
43
43
  autoRequestAds?: {
44
44
  enabled?: boolean;
@@ -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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@looplay/sdk",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "LooplaySDK (auth + points) for game integrations",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",