@looplay/sdk 0.6.0 → 0.8.1

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.
@@ -24,6 +24,19 @@ export interface GameTrackingSessionDto {
24
24
  export interface StartGameplayAttemptRequest {
25
25
  /** Optional client-generated id. When omitted, the server creates one. */
26
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>;
27
40
  }
28
41
  export interface GameplayAttemptSessionDto {
29
42
  trackingSessionId: string;
@@ -131,9 +144,15 @@ export interface GameOwnedAssetDto {
131
144
  assetName: string;
132
145
  quantity: number;
133
146
  }
134
- /** JWT tier — `POST sdk/games/store/by-store-id/checkout-intent`. */
135
- export interface CreateGameStoreCheckoutIntentRequest {
136
- storeId: string;
147
+ /**
148
+ * Body shape shared by both purchase tiers: self-serve
149
+ * (`POST sdk/games/store/checkout-intent/me`, public `x-game-key` + the
150
+ * purchasing player's own JWT — see `ServiceClient.createMyStoreCheckoutIntent`)
151
+ * and server-to-server (`POST sdk/games/store/checkout-intent`, creator+game
152
+ * secret pair — see `ServiceClient.createGameStoreCheckoutIntent`). Signs a
153
+ * short-lived `checkoutToken`, mirroring the gameplay attempt flow.
154
+ */
155
+ export interface SdkCreateGameStoreCheckoutIntentRequest {
137
156
  offerCode: string;
138
157
  /** Optional client-supplied dedupe key so a retried request returns the same result instead of charging twice. */
139
158
  requestId?: string;
@@ -149,11 +168,40 @@ export interface GameStoreCheckoutIntentDto {
149
168
  issuedAt: string;
150
169
  expiresAt: string;
151
170
  }
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`. */
171
+ /** Shared by both purchase tiers — see `SdkCreateGameStoreCheckoutIntentRequest`. */
172
+ export interface SdkPurchaseGameStoreOfferRuntimeRequest extends SdkCreateGameStoreCheckoutIntentRequest {
173
+ /** Signed by the server when the checkout intent is created — see `SdkCreateGameStoreCheckoutIntentRequest`. */
155
174
  checkoutToken: string;
156
175
  }
176
+ /** Pushed on every balance change — purchases, quest rewards, referral payouts, etc. */
177
+ export interface BalanceChangeEvent {
178
+ balanceType: string;
179
+ changeAmount: number;
180
+ changeType: string;
181
+ /** Present when multiple balance changes were batched into one push. */
182
+ changes?: Array<{
183
+ balanceType: string;
184
+ changeAmount: number;
185
+ changeType: string;
186
+ }>;
187
+ }
188
+ /**
189
+ * Pushed once a store purchase finishes — full result (items granted,
190
+ * quantity, price), unlike `BalanceChangeEvent` which only reports the coin
191
+ * delta. Fires for purchases made via either tier (self-serve or
192
+ * server-to-server), so a hosted-checkout backend flow can complete a
193
+ * purchase and have the game's own client react without polling.
194
+ */
195
+ export interface StorePurchaseCompletedEvent {
196
+ gameId: string;
197
+ offerCode: string;
198
+ name: string;
199
+ quantity: number;
200
+ priceCoin: number;
201
+ totalCoin: number;
202
+ requestId: string;
203
+ items: GameStoreOfferItemDto[];
204
+ }
157
205
  /** Mirrors the backend's `AppPlatform` enum — kept as a literal union to avoid a cross-repo enum dependency. */
158
206
  export type AppPlatform = 'telegram' | 'tiktok' | 'web' | 'base' | 'worldapp';
159
207
  /** Mirrors the backend's `MobileOs` enum. */
@@ -1,4 +1,4 @@
1
- import type { EmitGameEventResponse, GameEventKey, GameStoreDto, GameStoreOfferPurchaseResultDto, PagingResponse, TaskListQuery } 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'> {
@@ -24,9 +24,8 @@ export declare class ApiClient {
24
24
  private gameplayAttempt?;
25
25
  private gameplayAttemptPromise?;
26
26
  constructor(options: ApiClientOptions);
27
- /** Expose the underlying route-level client (requires manual bearerToken passing). */
27
+ /** Expose the underlying route-level client (requires manual bearerToken/gameKey passing). */
28
28
  unsafeRaw(): ServiceClient;
29
- listGames(query?: Record<string, any>): Promise<PagingResponse<any>>;
30
29
  private requireToken;
31
30
  /**
32
31
  * Resolves auth for the tracking endpoints: a real bearer token if logged
@@ -50,11 +49,11 @@ export declare class ApiClient {
50
49
  * are rejected outright without a valid token.
51
50
  */
52
51
  private ensureGameplayAttempt;
53
- getGameDetail(gameId: string): Promise<any>;
54
- private resolveStoreId;
55
- listRecentPlayed(query?: Record<string, any>): Promise<PagingResponse<any>>;
56
- /** Call once when the game view opens, before any play/match tracking. */
57
- trackView(gameId: string): Promise<boolean>;
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>;
58
57
  /**
59
58
  * Starts a new play attempt and returns its id. Call when the user
60
59
  * presses Play; `trackPlay`/`trackMatch`/`emit` reuse this id (and the
@@ -62,27 +61,34 @@ export declare class ApiClient {
62
61
  * `startAttempt()`, auto-starting one on first use if none was started.
63
62
  */
64
63
  startAttempt(): string;
65
- trackPlay(gameId: string, playTimeSeconds?: number): Promise<boolean>;
66
- trackMatch(gameId: string, body: {
64
+ trackPlay(gameKey: string, playTimeSeconds?: number): Promise<boolean>;
65
+ trackMatch(gameKey: string, body: {
67
66
  matchId: string;
68
67
  matchDurationSeconds: number;
69
68
  isCompleted?: boolean;
70
69
  isWin?: boolean;
71
70
  }): Promise<boolean>;
72
- emit(gameId: string, actionCode: GameEventKey | string, opts?: {
71
+ emit(gameKey: string, actionCode: GameEventKey | string, opts?: {
73
72
  value?: number;
74
73
  refId?: string;
75
74
  payload?: Record<string, unknown>;
76
75
  }): Promise<EmitGameEventResponse>;
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[]>;
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[]>;
81
84
  /**
82
85
  * Spends coin balance to purchase a store offer — signs a checkout intent
83
86
  * first, then redeems it, mirroring the gameplay attempt flow above.
87
+ * Self-serve: only the player's own access token is used, no secret pair
88
+ * required, safe to call directly from the game client. Also see
89
+ * `onStorePurchase` to react to the result in real time from any tab.
84
90
  */
85
- purchaseStoreOffer(gameId: string, offerCode: string, opts?: {
91
+ purchaseStoreOffer(gameKey: string, offerCode: string, opts?: {
86
92
  requestId?: string;
87
93
  quantity?: number;
88
94
  }): Promise<GameStoreOfferPurchaseResultDto>;
@@ -1,5 +1,5 @@
1
1
  import { type HttpClientOptions } from './http';
2
- import type { AuthResultDto, CompleteGameplayAttemptRequest, CompleteGameplayMatchRequest, CreateGameStoreCheckoutIntentRequest, EmitGameEventResponse, GameAssetDto, GameOwnedAssetDto, GameplayAttemptSessionDto, GameStoreCheckoutIntentDto, GameStoreDto, GameStoreOfferPurchaseResultDto, GameTrackingSessionDto, PagingResponse, PurchaseGameStoreOfferRuntimeRequest, RecordGameplayActionRequest, StartGameplayAttemptRequest, TaskListQuery } from './LooplaySDK.types';
2
+ import type { AuthResultDto, CompleteGameplayAttemptRequest, CompleteGameplayMatchRequest, EmitGameEventResponse, GameAssetDto, GameOwnedAssetDto, GameplayAttemptSessionDto, GameStoreCheckoutIntentDto, GameStoreDto, GameStoreOfferPurchaseResultDto, GameTrackingSessionDto, PagingResponse, RecordGameplayActionRequest, SdkCreateGameStoreCheckoutIntentRequest, SdkPurchaseGameStoreOfferRuntimeRequest, StartGameplayAttemptRequest, TaskListQuery } from './LooplaySDK.types';
3
3
  export interface ServiceClientOptions extends HttpClientOptions {
4
4
  }
5
5
  /**
@@ -17,6 +17,19 @@ export interface TrackingAuth {
17
17
  trackingSessionId?: string;
18
18
  anonTrackingToken?: string;
19
19
  }
20
+ /**
21
+ * Auth for the creator-secret tier (`createGameStoreCheckoutIntent`/
22
+ * `purchaseGameStoreOffer`) — moves real coin balance. `creatorKey`/
23
+ * `gameSecret` identify your integration (never embed these in
24
+ * client/browser code); `bearerToken` identifies the player you're
25
+ * purchasing on behalf of, same as any other authenticated call.
26
+ */
27
+ export interface SdkKeyPairAuth {
28
+ creatorKey: string;
29
+ gameSecret: string;
30
+ bearerToken: string;
31
+ trackingSessionId?: string;
32
+ }
20
33
  /**
21
34
  * Low-level HTTP client that maps 1:1 to backend routes.
22
35
  * Methods requiring user auth accept `bearerToken` explicitly.
@@ -27,15 +40,12 @@ export declare class ServiceClient {
27
40
  telegramLogin(telegramInitData: string): Promise<AuthResultDto>;
28
41
  refresh(refreshToken: string): Promise<AuthResultDto>;
29
42
  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
- recordGameView(auth: TrackingAuth, gameId: string): Promise<boolean>;
43
+ recordGameView(gameKey: string, auth?: TrackingAuth): Promise<boolean>;
34
44
  /** 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>;
45
+ startGameplayAttempt(gameKey: string, auth?: TrackingAuth, body?: StartGameplayAttemptRequest): Promise<GameplayAttemptSessionDto>;
46
+ completeGameplayAttempt(gameKey: string, auth: TrackingAuth, body: CompleteGameplayAttemptRequest): Promise<boolean>;
47
+ completeGameplayMatch(gameKey: string, auth: TrackingAuth, body: CompleteGameplayMatchRequest): Promise<boolean>;
48
+ recordGameplayAction(gameKey: string, auth: TrackingAuth, body: RecordGameplayActionRequest): Promise<EmitGameEventResponse>;
39
49
  /**
40
50
  * Anonymous runtimes must call this before any tracking call; logged-in
41
51
  * runtimes should still call it to get a `trackingSessionId` for unified
@@ -43,19 +53,20 @@ export declare class ServiceClient {
43
53
  */
44
54
  bootstrapTrackingSession(anonId?: string): Promise<GameTrackingSessionDto>;
45
55
  private trackingHeaders;
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[]>;
56
+ /** Public storefront (sections + offers) for the integration's game. Read-only. */
57
+ getStore(gameKey: string): Promise<GameStoreDto>;
58
+ /** Public catalog of purchasable/ownable assets for the game. Read-only. */
59
+ listGameAssets(gameKey: string): Promise<GameAssetDto[]>;
60
+ /** Assets the authenticated player already owns for this game. */
61
+ listMyGameAssets(bearerToken: string, gameKey: string): Promise<GameOwnedAssetDto[]>;
62
+ /** The authenticated player's past store purchases for this game. */
63
+ listMyStorePurchases(bearerToken: string, gameKey: string): Promise<GameStoreOfferPurchaseResultDto[]>;
64
+ /** Signs a `checkoutToken` for one purchase — required by `purchaseMyStoreOffer`. */
65
+ createMyStoreCheckoutIntent(gameKey: string, bearerToken: string, body: SdkCreateGameStoreCheckoutIntentRequest, trackingSessionId?: string): Promise<GameStoreCheckoutIntentDto>;
66
+ purchaseMyStoreOffer(gameKey: string, bearerToken: string, body: SdkPurchaseGameStoreOfferRuntimeRequest, trackingSessionId?: string): Promise<GameStoreOfferPurchaseResultDto>;
67
+ /** Signs a `checkoutToken` for one purchase — required by `purchaseGameStoreOffer`. */
68
+ createGameStoreCheckoutIntent(auth: SdkKeyPairAuth, body: SdkCreateGameStoreCheckoutIntentRequest): Promise<GameStoreCheckoutIntentDto>;
69
+ purchaseGameStoreOffer(auth: SdkKeyPairAuth, body: SdkPurchaseGameStoreOfferRuntimeRequest): Promise<GameStoreOfferPurchaseResultDto>;
59
70
  getBalances(bearerToken: string, query?: Record<string, any>): Promise<any>;
60
71
  getBalanceHistory(bearerToken: string, query?: Record<string, any>): Promise<PagingResponse<any>>;
61
72
  getMyProfile(bearerToken: string): Promise<any>;
@@ -0,0 +1,35 @@
1
+ import type { BalanceChangeEvent, StorePurchaseCompletedEvent } from './LooplaySDK.types';
2
+ export interface WsClientOptions {
3
+ baseUrl: string;
4
+ /** Resolves the current player's access token — re-checked on every (re)connect. */
5
+ getAccessToken: () => Promise<string | undefined>;
6
+ }
7
+ type Listener<T> = (payload: T) => void;
8
+ /**
9
+ * Lazy-connecting Socket.IO client mirroring gbs-service's AppGateway.
10
+ * Both events it exposes (`balance_change`, `store_purchase_completed`) are
11
+ * pushed only to the authenticated player's own room — there is nothing to
12
+ * receive without an access token, so `subscribe*` is a no-op until one is
13
+ * available.
14
+ *
15
+ * The connection is opened on the first subscription and closed once the
16
+ * last listener unsubscribes — a game that never calls `onBalanceChange`/
17
+ * `onStorePurchase` never opens a socket at all.
18
+ */
19
+ export declare class WsClient {
20
+ private readonly options;
21
+ private socket?;
22
+ private connectPromise?;
23
+ private readonly balanceListeners;
24
+ private readonly purchaseListeners;
25
+ constructor(options: WsClientOptions);
26
+ /** Fires on every balance change — purchases, quest rewards, referral payouts, etc. */
27
+ onBalanceChange(listener: Listener<BalanceChangeEvent>): () => void;
28
+ /** Fires once a store purchase finishes — see `StorePurchaseCompletedEvent`. */
29
+ onStorePurchase(listener: Listener<StorePurchaseCompletedEvent>): () => void;
30
+ /** Closes the socket immediately, regardless of active listeners. */
31
+ disconnect(): void;
32
+ private ensureConnected;
33
+ private disconnectIfIdle;
34
+ }
35
+ export {};
@@ -13,6 +13,3 @@ 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
- }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Optional real-time subpath — `import { WsClient } from '@looplay/sdk/realtime'`.
3
+ * Kept out of the main `@looplay/sdk` entry point (and its browser IIFE
4
+ * bundle) because it pulls in `socket.io-client`, which most integrations
5
+ * — tracking-only or store-browsing-only — don't need.
6
+ */
7
+ export { WsClient } from './apps/ws-client';
8
+ export type { WsClientOptions } from './apps/ws-client';
9
+ export type { BalanceChangeEvent, StorePurchaseCompletedEvent } from './apps/LooplaySDK.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.6.0",
3
+ "version": "0.8.1",
4
4
  "description": "LooplaySDK (auth + points) for game integrations",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -17,6 +17,14 @@
17
17
  },
18
18
  "./browser": {
19
19
  "default": "./dist/looplay-sdk.min.js"
20
+ },
21
+ "./realtime": {
22
+ "types": "./dist/types/realtime.d.ts",
23
+ "import": "./dist/looplay-sdk-realtime.esm.js",
24
+ "require": "./dist/looplay-sdk-realtime.cjs.js"
25
+ },
26
+ "./realtime/browser": {
27
+ "default": "./dist/looplay-sdk-realtime.min.js"
20
28
  }
21
29
  },
22
30
  "files": [
@@ -30,6 +38,14 @@
30
38
  "dependencies": {
31
39
  "@looplay/types": "^0.1.0"
32
40
  },
41
+ "peerDependencies": {
42
+ "socket.io-client": "^4.8.3"
43
+ },
44
+ "peerDependenciesMeta": {
45
+ "socket.io-client": {
46
+ "optional": true
47
+ }
48
+ },
33
49
  "scripts": {
34
50
  "build": "npm run build:js && npm run build:types",
35
51
  "build:js": "tsup",