@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.
package/README.md CHANGED
@@ -1,7 +1,44 @@
1
- # Looplay
1
+ # @looplay/sdk
2
+
3
+ Client SDK for games integrating with Looplay: player tracking (views, play
4
+ attempts, matches, custom actions), the game's coin store (read-only), and
5
+ authentication.
2
6
 
3
7
  API Reference: https://docs.looplay.gg/build-on-loopplay/looplay-sdk
4
8
 
9
+ - [Install](#install)
10
+ - [Core concept: one SDK integration = one game](#core-concept-one-sdk-integration--one-game)
11
+ - [Tracking a game — hosted on Looplay or published anywhere else](#tracking-a-game--hosted-on-looplay-or-published-anywhere-else)
12
+ - [`LooplaySDK` — direct integration (your own backend/auth)](#looplaysdk--direct-integration-your-own-backendauth)
13
+ - [Store — browsing and purchases](#store--browsing-and-purchases)
14
+ - [Rewarded ads via parent window](#rewarded-ads-via-parent-window)
15
+ - [Drop-in `<script>` tag (no build step required)](#drop-in-script-tag-no-build-step-required)
16
+ - [Auth storage tradeoff](#auth-storage-tradeoff)
17
+ - [Errors](#errors)
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ npm install @looplay/sdk
23
+ ```
24
+
25
+ ## Core concept: one SDK integration = one game
26
+
27
+ Every tracking and store call is scoped to **one game**, identified by its
28
+ public `appId` (also called `gameKey` — the same value, sent as the
29
+ `x-game-key` header under the hood). There is no multi-game catalog
30
+ browsing in this SDK; register your game with a Looplay creator account
31
+ first and use that `appId` everywhere below.
32
+
33
+ ```ts
34
+ import { LooplayIframeAuth } from '@looplay/sdk';
35
+
36
+ export const looplayAuth = new LooplayIframeAuth({
37
+ appId: 'YOUR_APP_ID', // from your creator dashboard — required
38
+ apiUrl: 'https://api.looplay.gg',
39
+ });
40
+ ```
41
+
5
42
  ## Tracking a game — hosted on Looplay or published anywhere else
6
43
 
7
44
  `LooplayIframeAuth` is the single class for tracking, in two modes that the
@@ -38,13 +75,154 @@ looplayAuth.initLifecycleTracking(); // auto-emits GAME_STARTED once per session
38
75
  // Anywhere in the game — identical code whether hosted on Looplay or not:
39
76
  looplayAuth.canTrack();
40
77
  looplayAuth.subscribe((state) => { /* re-render when auth arrives/clears */ });
78
+ await looplayAuth.trackView();
79
+ looplayAuth.startAttempt(); // call once when the user presses Play
41
80
  await looplayAuth.trackPlay(playTimeSeconds);
42
81
  await looplayAuth.trackMatch(matchId, { durationSeconds, isWin: true });
43
82
  await looplayAuth.emitGameEvent('CUSTOM_ACTION', { value: 1 });
44
83
  await looplayAuth.requestAds();
45
84
  ```
46
85
 
47
- ### Rewarded ads via parent window
86
+ Every play attempt is backed by a short-lived, server-signed token — the SDK
87
+ fetches and caches it automatically the first time `trackPlay`/`trackMatch`/
88
+ `emitGameEvent` needs it after `startAttempt()`. You never see or manage
89
+ this token directly.
90
+
91
+ ### Tracking call reference
92
+
93
+ | Call | When |
94
+ | --- | --- |
95
+ | `trackView()` | Once when the game view opens, before any play/match tracking |
96
+ | `startAttempt()` | Once when the user presses Play. Synchronous — returns an id immediately |
97
+ | `trackPlay(playTimeSeconds?)` | Periodically / on pause, to report accumulated play time for the current attempt |
98
+ | `trackMatch(matchId, { durationSeconds, isCompleted?, isWin? })` | Once per round/match end. `matchId` is the idempotency anchor — safe to call twice |
99
+ | `emitGameEvent(actionCode, { value?, refId?, payload? })` | For quest/task progress and custom analytics — e.g. `'LEVEL_UP'`, `'ITEM_COLLECTED'` |
100
+
101
+ ## `LooplaySDK` — direct integration (your own backend/auth)
102
+
103
+ If you're not embedding via iframe (e.g. a native/Telegram Mini App
104
+ integration with your own auth flow), use `LooplaySDK` directly:
105
+
106
+ ```ts
107
+ import { LooplaySDK } from '@looplay/sdk';
108
+
109
+ const sdk = new LooplaySDK({
110
+ baseUrl: 'https://api.looplay.gg',
111
+ auth: { provider: telegramAuthProvider }, // any AuthProvider — see auth/types.ts
112
+ });
113
+
114
+ await sdk.init({ gameId: 'YOUR_APP_ID', verifyMode: 'strict' });
115
+ // `gameId` here is the game's public appId (same as `x-game-key`/`appId` above).
116
+
117
+ await sdk.trackView();
118
+ sdk.startAttempt();
119
+ await sdk.trackPlay(playTimeSeconds);
120
+ await sdk.trackMatch(matchId, { durationSeconds, isWin: true });
121
+ await sdk.emit('CUSTOM_ACTION', { value: 1 });
122
+
123
+ const profile = await sdk.getMyProfile();
124
+ ```
125
+
126
+ `verifyMode: 'strict'` resolves `gameId` against the backend on `init()` and
127
+ throws if the game doesn't exist or isn't live — a lightweight, read-only,
128
+ unauthenticated check. Use `'none'` (default) to skip it.
129
+
130
+ ## Store — browsing and purchases
131
+
132
+ The SDK exposes the game's coin store — storefront, public asset catalog,
133
+ the caller's own owned assets/purchase history, and (for a logged-in
134
+ player) purchasing an offer:
135
+
136
+ ```ts
137
+ const store = await sdk.api!.getStore(gameId); // sections + offers, no auth required
138
+ const assets = await sdk.api!.listGameAssets(gameId); // public asset catalog, no auth required
139
+ const owned = await sdk.api!.listMyGameAssets(gameId); // requires a logged-in user
140
+ const purchases = await sdk.api!.listMyStorePurchases(gameId); // requires a logged-in user
141
+
142
+ // Spends the player's coin balance — requires a logged-in user, nothing
143
+ // else. No backend or secret needed: safe to call directly from the game.
144
+ const result = await sdk.api!.purchaseStoreOffer(gameId, 'starter_pack', { quantity: 1 });
145
+ ```
146
+
147
+ `purchaseStoreOffer` signs a short-lived checkout token and redeems it in
148
+ one call — the same safety mechanism as the play-attempt token used for
149
+ tracking, not a static secret. This is enough for creators with no backend
150
+ of their own.
151
+
152
+ ### Real-time updates (optional, separate import)
153
+
154
+ Purchases (and any other balance change — quest rewards, referral payouts,
155
+ ...) also push a WebSocket event, useful for reacting from a different tab
156
+ than the one that made the call, or after a purchase completes out-of-band
157
+ (e.g. a hosted-checkout redirect flow). This lives in a **separate
158
+ `@looplay/sdk/realtime` subpath**, not the main `@looplay/sdk` import — it
159
+ pulls in `socket.io-client`, which most integrations (tracking-only or
160
+ store-browsing-only) don't need:
161
+
162
+ ```bash
163
+ npm install socket.io-client # peer dependency, only needed if you import '@looplay/sdk/realtime'
164
+ ```
165
+
166
+ ```ts
167
+ import { WsClient } from '@looplay/sdk/realtime';
168
+
169
+ const ws = new WsClient({
170
+ baseUrl: 'https://api.looplay.gg',
171
+ getAccessToken: () => sdk.getAccessToken(), // reuse whatever you already pass to LooplaySDK/ApiClient
172
+ });
173
+
174
+ const unsubscribe = ws.onStorePurchase((event) => {
175
+ console.log(`Got ${event.quantity}x ${event.name}`, event.items);
176
+ });
177
+
178
+ ws.onBalanceChange((event) => {
179
+ console.log('Balance changed', event.changeAmount, event.balanceType);
180
+ });
181
+
182
+ // later
183
+ unsubscribe();
184
+ ```
185
+
186
+ The connection is opened lazily on the first `onBalanceChange`/
187
+ `onStorePurchase` call and closed once the last listener unsubscribes — a
188
+ `WsClient` you never subscribe on never opens a socket. Call
189
+ `ws.disconnect()` to close it immediately regardless of active listeners.
190
+
191
+ ### Backend-initiated purchases (optional)
192
+
193
+ If you *do* have your own backend and want to initiate a purchase without
194
+ the player's browser involved (e.g. an admin grant, or a payment webhook),
195
+ use `ServiceClient`'s creator+game secret-pair methods instead — never
196
+ embed `x-creator-key`/`x-game-secret` in client/browser code:
197
+
198
+ ```ts
199
+ import { ServiceClient } from '@looplay/sdk';
200
+
201
+ const service = new ServiceClient({ baseUrl: 'https://api.looplay.gg' });
202
+
203
+ const auth = {
204
+ creatorKey: process.env.LOOPLAY_CREATOR_KEY!,
205
+ gameSecret: process.env.LOOPLAY_GAME_SECRET!,
206
+ bearerToken: playerAccessToken, // the specific player you're purchasing on behalf of
207
+ };
208
+
209
+ const intent = await service.createGameStoreCheckoutIntent(auth, {
210
+ offerCode: 'starter_pack',
211
+ quantity: 1,
212
+ });
213
+
214
+ const result = await service.purchaseGameStoreOffer(auth, {
215
+ offerCode: intent.offerCode,
216
+ requestId: intent.requestId,
217
+ quantity: intent.quantity,
218
+ checkoutToken: intent.checkoutToken,
219
+ });
220
+ ```
221
+
222
+ The player (if connected) receives the same `store_purchase_completed`
223
+ WebSocket event either way.
224
+
225
+ ## Rewarded ads via parent window
48
226
 
49
227
  When the game runs inside an iframe, you can ask the parent page to open a
50
228
  rewarded ad by sending `LOOPLAY_REQUEST_ADS`. This is the right pattern for a
@@ -88,7 +266,7 @@ window.addEventListener('message', async (event) => {
88
266
  });
89
267
  ```
90
268
 
91
- ### Drop-in `<script>` tag (no build step required)
269
+ ## Drop-in `<script>` tag (no build step required)
92
270
 
93
271
  Games not built from a Looplay template — plain HTML5, Construct, GameMaker
94
272
  exports, a Unity WebGL wrapper page, etc. — can use the browser (IIFE) bundle
@@ -104,7 +282,23 @@ directly, no bundler needed:
104
282
  looplayAuth.init();
105
283
  looplayAuth.initLifecycleTracking();
106
284
 
107
- // call looplayAuth.trackPlay(...) / trackMatch(...) / emitGameEvent(...) from your game code
285
+ // call looplayAuth.trackView() / startAttempt() / trackPlay(...) /
286
+ // trackMatch(...) / emitGameEvent(...) from your game code
287
+ </script>
288
+ ```
289
+
290
+ This bundle does **not** include real-time (`WsClient`)/`socket.io-client` —
291
+ that's a separate opt-in browser bundle, only needed if you want
292
+ `onBalanceChange`/`onStorePurchase`:
293
+
294
+ ```html
295
+ <script src="https://unpkg.com/@looplay/sdk/realtime/browser"></script>
296
+ <script>
297
+ const ws = new LooplaySDKRealtime.WsClient({
298
+ baseUrl: 'https://api.looplay.gg',
299
+ getAccessToken: async () => myAccessToken,
300
+ });
301
+ ws.onStorePurchase((event) => console.log('purchased', event));
108
302
  </script>
109
303
  ```
110
304
 
@@ -131,3 +325,26 @@ but readable by any script on the page (XSS exposure). Prefer it only for
131
325
  games where that tradeoff is acceptable; for higher-security needs, keep the
132
326
  default in-memory storage or implement an `AuthStorage` backed by a more
133
327
  restrictive mechanism.
328
+
329
+ ## Errors
330
+
331
+ All SDK-thrown errors extend `LooplaySDKError`:
332
+
333
+ | Error | Thrown when |
334
+ | --- | --- |
335
+ | `NotInitializedError` | Calling `LooplaySDK` methods before `init()` |
336
+ | `MissingBaseUrlError` | Constructing without `baseUrl` |
337
+ | `MissingAuthError` | An operation needs `auth` and none was configured |
338
+ | `NotAuthenticatedError` | Calling an authenticated endpoint with no access token and (for tracking) no anonymous id |
339
+
340
+ ```ts
341
+ import { NotAuthenticatedError } from '@looplay/sdk';
342
+
343
+ try {
344
+ await sdk.getMyProfile();
345
+ } catch (err) {
346
+ if (err instanceof NotAuthenticatedError) {
347
+ // prompt login
348
+ }
349
+ }
350
+ ```
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ var socket_ioClient = require('socket.io-client');
4
+
5
+ // src/apps/ws-client.ts
6
+ var WsClient = class {
7
+ constructor(options) {
8
+ this.options = options;
9
+ }
10
+ options;
11
+ socket;
12
+ connectPromise;
13
+ balanceListeners = /* @__PURE__ */ new Set();
14
+ purchaseListeners = /* @__PURE__ */ new Set();
15
+ /** Fires on every balance change — purchases, quest rewards, referral payouts, etc. */
16
+ onBalanceChange(listener) {
17
+ this.balanceListeners.add(listener);
18
+ void this.ensureConnected();
19
+ return () => {
20
+ this.balanceListeners.delete(listener);
21
+ this.disconnectIfIdle();
22
+ };
23
+ }
24
+ /** Fires once a store purchase finishes — see `StorePurchaseCompletedEvent`. */
25
+ onStorePurchase(listener) {
26
+ this.purchaseListeners.add(listener);
27
+ void this.ensureConnected();
28
+ return () => {
29
+ this.purchaseListeners.delete(listener);
30
+ this.disconnectIfIdle();
31
+ };
32
+ }
33
+ /** Closes the socket immediately, regardless of active listeners. */
34
+ disconnect() {
35
+ this.socket?.disconnect();
36
+ this.socket = void 0;
37
+ this.connectPromise = void 0;
38
+ }
39
+ async ensureConnected() {
40
+ if (this.socket?.connected) return this.socket;
41
+ if (this.connectPromise) return this.connectPromise;
42
+ this.connectPromise = (async () => {
43
+ const token = await this.options.getAccessToken();
44
+ if (!token) return void 0;
45
+ const socket = socket_ioClient.io(this.options.baseUrl, {
46
+ transports: ["websocket"],
47
+ auth: { token: `Bearer ${token}` }
48
+ });
49
+ socket.on("balance_change", (payload) => {
50
+ for (const listener of this.balanceListeners) listener(payload);
51
+ });
52
+ socket.on("store_purchase_completed", (payload) => {
53
+ for (const listener of this.purchaseListeners) listener(payload);
54
+ });
55
+ this.socket = socket;
56
+ return socket;
57
+ })();
58
+ try {
59
+ return await this.connectPromise;
60
+ } finally {
61
+ this.connectPromise = void 0;
62
+ }
63
+ }
64
+ disconnectIfIdle() {
65
+ if (this.balanceListeners.size === 0 && this.purchaseListeners.size === 0) {
66
+ this.disconnect();
67
+ }
68
+ }
69
+ };
70
+
71
+ exports.WsClient = WsClient;
72
+ //# sourceMappingURL=looplay-sdk-realtime.cjs.js.map
73
+ //# sourceMappingURL=looplay-sdk-realtime.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/apps/ws-client.ts"],"names":["io"],"mappings":";;;;;AAsBO,IAAM,WAAN,MAAe;AAAA,EAMpB,YAA6B,OAAA,EAA0B;AAA1B,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAAA,EAA2B;AAAA,EAA3B,OAAA;AAAA,EALrB,MAAA;AAAA,EACA,cAAA;AAAA,EACS,gBAAA,uBAAuB,GAAA,EAAkC;AAAA,EACzD,iBAAA,uBAAwB,GAAA,EAA2C;AAAA;AAAA,EAKpF,gBAAgB,QAAA,EAAoD;AAClE,IAAA,IAAA,CAAK,gBAAA,CAAiB,IAAI,QAAQ,CAAA;AAClC,IAAA,KAAK,KAAK,eAAA,EAAgB;AAC1B,IAAA,OAAO,MAAM;AACX,MAAA,IAAA,CAAK,gBAAA,CAAiB,OAAO,QAAQ,CAAA;AACrC,MAAA,IAAA,CAAK,gBAAA,EAAiB;AAAA,IACxB,CAAA;AAAA,EACF;AAAA;AAAA,EAGA,gBAAgB,QAAA,EAA6D;AAC3E,IAAA,IAAA,CAAK,iBAAA,CAAkB,IAAI,QAAQ,CAAA;AACnC,IAAA,KAAK,KAAK,eAAA,EAAgB;AAC1B,IAAA,OAAO,MAAM;AACX,MAAA,IAAA,CAAK,iBAAA,CAAkB,OAAO,QAAQ,CAAA;AACtC,MAAA,IAAA,CAAK,gBAAA,EAAiB;AAAA,IACxB,CAAA;AAAA,EACF;AAAA;AAAA,EAGA,UAAA,GAAmB;AACjB,IAAA,IAAA,CAAK,QAAQ,UAAA,EAAW;AACxB,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,cAAA,GAAiB,MAAA;AAAA,EACxB;AAAA,EAEA,MAAc,eAAA,GAA+C;AAC3D,IAAA,IAAI,IAAA,CAAK,MAAA,EAAQ,SAAA,EAAW,OAAO,IAAA,CAAK,MAAA;AACxC,IAAA,IAAI,IAAA,CAAK,cAAA,EAAgB,OAAO,IAAA,CAAK,cAAA;AAErC,IAAA,IAAA,CAAK,kBAAkB,YAAY;AACjC,MAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,OAAA,CAAQ,cAAA,EAAe;AAGhD,MAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AAEnB,MAAA,MAAM,MAAA,GAASA,kBAAA,CAAG,IAAA,CAAK,OAAA,CAAQ,OAAA,EAAS;AAAA,QACtC,UAAA,EAAY,CAAC,WAAW,CAAA;AAAA,QACxB,IAAA,EAAM,EAAE,KAAA,EAAO,CAAA,OAAA,EAAU,KAAK,CAAA,CAAA;AAAG,OAClC,CAAA;AAED,MAAA,MAAA,CAAO,EAAA,CAAG,gBAAA,EAAkB,CAAC,OAAA,KAAgC;AAC3D,QAAA,KAAA,MAAW,QAAA,IAAY,IAAA,CAAK,gBAAA,EAAkB,QAAA,CAAS,OAAO,CAAA;AAAA,MAChE,CAAC,CAAA;AACD,MAAA,MAAA,CAAO,EAAA,CAAG,0BAAA,EAA4B,CAAC,OAAA,KAAyC;AAC9E,QAAA,KAAA,MAAW,QAAA,IAAY,IAAA,CAAK,iBAAA,EAAmB,QAAA,CAAS,OAAO,CAAA;AAAA,MACjE,CAAC,CAAA;AAED,MAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,MAAA,OAAO,MAAA;AAAA,IACT,CAAA,GAAG;AAEH,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,IAAA,CAAK,cAAA;AAAA,IACpB,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,cAAA,GAAiB,MAAA;AAAA,IACxB;AAAA,EACF;AAAA,EAEQ,gBAAA,GAAyB;AAC/B,IAAA,IAAI,KAAK,gBAAA,CAAiB,IAAA,KAAS,KAAK,IAAA,CAAK,iBAAA,CAAkB,SAAS,CAAA,EAAG;AACzE,MAAA,IAAA,CAAK,UAAA,EAAW;AAAA,IAClB;AAAA,EACF;AACF","file":"looplay-sdk-realtime.cjs.js","sourcesContent":["import { io, type Socket } from 'socket.io-client';\nimport type { BalanceChangeEvent, StorePurchaseCompletedEvent } from './LooplaySDK.types';\n\nexport interface WsClientOptions {\n baseUrl: string;\n /** Resolves the current player's access token — re-checked on every (re)connect. */\n getAccessToken: () => Promise<string | undefined>;\n}\n\ntype Listener<T> = (payload: T) => void;\n\n/**\n * Lazy-connecting Socket.IO client mirroring gbs-service's AppGateway.\n * Both events it exposes (`balance_change`, `store_purchase_completed`) are\n * pushed only to the authenticated player's own room — there is nothing to\n * receive without an access token, so `subscribe*` is a no-op until one is\n * available.\n *\n * The connection is opened on the first subscription and closed once the\n * last listener unsubscribes — a game that never calls `onBalanceChange`/\n * `onStorePurchase` never opens a socket at all.\n */\nexport class WsClient {\n private socket?: Socket;\n private connectPromise?: Promise<Socket | undefined>;\n private readonly balanceListeners = new Set<Listener<BalanceChangeEvent>>();\n private readonly purchaseListeners = new Set<Listener<StorePurchaseCompletedEvent>>();\n\n constructor(private readonly options: WsClientOptions) {}\n\n /** Fires on every balance change — purchases, quest rewards, referral payouts, etc. */\n onBalanceChange(listener: Listener<BalanceChangeEvent>): () => void {\n this.balanceListeners.add(listener);\n void this.ensureConnected();\n return () => {\n this.balanceListeners.delete(listener);\n this.disconnectIfIdle();\n };\n }\n\n /** Fires once a store purchase finishes — see `StorePurchaseCompletedEvent`. */\n onStorePurchase(listener: Listener<StorePurchaseCompletedEvent>): () => void {\n this.purchaseListeners.add(listener);\n void this.ensureConnected();\n return () => {\n this.purchaseListeners.delete(listener);\n this.disconnectIfIdle();\n };\n }\n\n /** Closes the socket immediately, regardless of active listeners. */\n disconnect(): void {\n this.socket?.disconnect();\n this.socket = undefined;\n this.connectPromise = undefined;\n }\n\n private async ensureConnected(): Promise<Socket | undefined> {\n if (this.socket?.connected) return this.socket;\n if (this.connectPromise) return this.connectPromise;\n\n this.connectPromise = (async () => {\n const token = await this.options.getAccessToken();\n // Both events are user-room-scoped — an unauthenticated socket would\n // never receive anything, so don't bother opening one.\n if (!token) return undefined;\n\n const socket = io(this.options.baseUrl, {\n transports: ['websocket'],\n auth: { token: `Bearer ${token}` },\n });\n\n socket.on('balance_change', (payload: BalanceChangeEvent) => {\n for (const listener of this.balanceListeners) listener(payload);\n });\n socket.on('store_purchase_completed', (payload: StorePurchaseCompletedEvent) => {\n for (const listener of this.purchaseListeners) listener(payload);\n });\n\n this.socket = socket;\n return socket;\n })();\n\n try {\n return await this.connectPromise;\n } finally {\n this.connectPromise = undefined;\n }\n }\n\n private disconnectIfIdle(): void {\n if (this.balanceListeners.size === 0 && this.purchaseListeners.size === 0) {\n this.disconnect();\n }\n }\n}\n"]}
@@ -0,0 +1,71 @@
1
+ import { io } from 'socket.io-client';
2
+
3
+ // src/apps/ws-client.ts
4
+ var WsClient = class {
5
+ constructor(options) {
6
+ this.options = options;
7
+ }
8
+ options;
9
+ socket;
10
+ connectPromise;
11
+ balanceListeners = /* @__PURE__ */ new Set();
12
+ purchaseListeners = /* @__PURE__ */ new Set();
13
+ /** Fires on every balance change — purchases, quest rewards, referral payouts, etc. */
14
+ onBalanceChange(listener) {
15
+ this.balanceListeners.add(listener);
16
+ void this.ensureConnected();
17
+ return () => {
18
+ this.balanceListeners.delete(listener);
19
+ this.disconnectIfIdle();
20
+ };
21
+ }
22
+ /** Fires once a store purchase finishes — see `StorePurchaseCompletedEvent`. */
23
+ onStorePurchase(listener) {
24
+ this.purchaseListeners.add(listener);
25
+ void this.ensureConnected();
26
+ return () => {
27
+ this.purchaseListeners.delete(listener);
28
+ this.disconnectIfIdle();
29
+ };
30
+ }
31
+ /** Closes the socket immediately, regardless of active listeners. */
32
+ disconnect() {
33
+ this.socket?.disconnect();
34
+ this.socket = void 0;
35
+ this.connectPromise = void 0;
36
+ }
37
+ async ensureConnected() {
38
+ if (this.socket?.connected) return this.socket;
39
+ if (this.connectPromise) return this.connectPromise;
40
+ this.connectPromise = (async () => {
41
+ const token = await this.options.getAccessToken();
42
+ if (!token) return void 0;
43
+ const socket = io(this.options.baseUrl, {
44
+ transports: ["websocket"],
45
+ auth: { token: `Bearer ${token}` }
46
+ });
47
+ socket.on("balance_change", (payload) => {
48
+ for (const listener of this.balanceListeners) listener(payload);
49
+ });
50
+ socket.on("store_purchase_completed", (payload) => {
51
+ for (const listener of this.purchaseListeners) listener(payload);
52
+ });
53
+ this.socket = socket;
54
+ return socket;
55
+ })();
56
+ try {
57
+ return await this.connectPromise;
58
+ } finally {
59
+ this.connectPromise = void 0;
60
+ }
61
+ }
62
+ disconnectIfIdle() {
63
+ if (this.balanceListeners.size === 0 && this.purchaseListeners.size === 0) {
64
+ this.disconnect();
65
+ }
66
+ }
67
+ };
68
+
69
+ export { WsClient };
70
+ //# sourceMappingURL=looplay-sdk-realtime.esm.js.map
71
+ //# sourceMappingURL=looplay-sdk-realtime.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/apps/ws-client.ts"],"names":[],"mappings":";;;AAsBO,IAAM,WAAN,MAAe;AAAA,EAMpB,YAA6B,OAAA,EAA0B;AAA1B,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAAA,EAA2B;AAAA,EAA3B,OAAA;AAAA,EALrB,MAAA;AAAA,EACA,cAAA;AAAA,EACS,gBAAA,uBAAuB,GAAA,EAAkC;AAAA,EACzD,iBAAA,uBAAwB,GAAA,EAA2C;AAAA;AAAA,EAKpF,gBAAgB,QAAA,EAAoD;AAClE,IAAA,IAAA,CAAK,gBAAA,CAAiB,IAAI,QAAQ,CAAA;AAClC,IAAA,KAAK,KAAK,eAAA,EAAgB;AAC1B,IAAA,OAAO,MAAM;AACX,MAAA,IAAA,CAAK,gBAAA,CAAiB,OAAO,QAAQ,CAAA;AACrC,MAAA,IAAA,CAAK,gBAAA,EAAiB;AAAA,IACxB,CAAA;AAAA,EACF;AAAA;AAAA,EAGA,gBAAgB,QAAA,EAA6D;AAC3E,IAAA,IAAA,CAAK,iBAAA,CAAkB,IAAI,QAAQ,CAAA;AACnC,IAAA,KAAK,KAAK,eAAA,EAAgB;AAC1B,IAAA,OAAO,MAAM;AACX,MAAA,IAAA,CAAK,iBAAA,CAAkB,OAAO,QAAQ,CAAA;AACtC,MAAA,IAAA,CAAK,gBAAA,EAAiB;AAAA,IACxB,CAAA;AAAA,EACF;AAAA;AAAA,EAGA,UAAA,GAAmB;AACjB,IAAA,IAAA,CAAK,QAAQ,UAAA,EAAW;AACxB,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,cAAA,GAAiB,MAAA;AAAA,EACxB;AAAA,EAEA,MAAc,eAAA,GAA+C;AAC3D,IAAA,IAAI,IAAA,CAAK,MAAA,EAAQ,SAAA,EAAW,OAAO,IAAA,CAAK,MAAA;AACxC,IAAA,IAAI,IAAA,CAAK,cAAA,EAAgB,OAAO,IAAA,CAAK,cAAA;AAErC,IAAA,IAAA,CAAK,kBAAkB,YAAY;AACjC,MAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,OAAA,CAAQ,cAAA,EAAe;AAGhD,MAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AAEnB,MAAA,MAAM,MAAA,GAAS,EAAA,CAAG,IAAA,CAAK,OAAA,CAAQ,OAAA,EAAS;AAAA,QACtC,UAAA,EAAY,CAAC,WAAW,CAAA;AAAA,QACxB,IAAA,EAAM,EAAE,KAAA,EAAO,CAAA,OAAA,EAAU,KAAK,CAAA,CAAA;AAAG,OAClC,CAAA;AAED,MAAA,MAAA,CAAO,EAAA,CAAG,gBAAA,EAAkB,CAAC,OAAA,KAAgC;AAC3D,QAAA,KAAA,MAAW,QAAA,IAAY,IAAA,CAAK,gBAAA,EAAkB,QAAA,CAAS,OAAO,CAAA;AAAA,MAChE,CAAC,CAAA;AACD,MAAA,MAAA,CAAO,EAAA,CAAG,0BAAA,EAA4B,CAAC,OAAA,KAAyC;AAC9E,QAAA,KAAA,MAAW,QAAA,IAAY,IAAA,CAAK,iBAAA,EAAmB,QAAA,CAAS,OAAO,CAAA;AAAA,MACjE,CAAC,CAAA;AAED,MAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,MAAA,OAAO,MAAA;AAAA,IACT,CAAA,GAAG;AAEH,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,IAAA,CAAK,cAAA;AAAA,IACpB,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,cAAA,GAAiB,MAAA;AAAA,IACxB;AAAA,EACF;AAAA,EAEQ,gBAAA,GAAyB;AAC/B,IAAA,IAAI,KAAK,gBAAA,CAAiB,IAAA,KAAS,KAAK,IAAA,CAAK,iBAAA,CAAkB,SAAS,CAAA,EAAG;AACzE,MAAA,IAAA,CAAK,UAAA,EAAW;AAAA,IAClB;AAAA,EACF;AACF","file":"looplay-sdk-realtime.esm.js","sourcesContent":["import { io, type Socket } from 'socket.io-client';\nimport type { BalanceChangeEvent, StorePurchaseCompletedEvent } from './LooplaySDK.types';\n\nexport interface WsClientOptions {\n baseUrl: string;\n /** Resolves the current player's access token — re-checked on every (re)connect. */\n getAccessToken: () => Promise<string | undefined>;\n}\n\ntype Listener<T> = (payload: T) => void;\n\n/**\n * Lazy-connecting Socket.IO client mirroring gbs-service's AppGateway.\n * Both events it exposes (`balance_change`, `store_purchase_completed`) are\n * pushed only to the authenticated player's own room — there is nothing to\n * receive without an access token, so `subscribe*` is a no-op until one is\n * available.\n *\n * The connection is opened on the first subscription and closed once the\n * last listener unsubscribes — a game that never calls `onBalanceChange`/\n * `onStorePurchase` never opens a socket at all.\n */\nexport class WsClient {\n private socket?: Socket;\n private connectPromise?: Promise<Socket | undefined>;\n private readonly balanceListeners = new Set<Listener<BalanceChangeEvent>>();\n private readonly purchaseListeners = new Set<Listener<StorePurchaseCompletedEvent>>();\n\n constructor(private readonly options: WsClientOptions) {}\n\n /** Fires on every balance change — purchases, quest rewards, referral payouts, etc. */\n onBalanceChange(listener: Listener<BalanceChangeEvent>): () => void {\n this.balanceListeners.add(listener);\n void this.ensureConnected();\n return () => {\n this.balanceListeners.delete(listener);\n this.disconnectIfIdle();\n };\n }\n\n /** Fires once a store purchase finishes — see `StorePurchaseCompletedEvent`. */\n onStorePurchase(listener: Listener<StorePurchaseCompletedEvent>): () => void {\n this.purchaseListeners.add(listener);\n void this.ensureConnected();\n return () => {\n this.purchaseListeners.delete(listener);\n this.disconnectIfIdle();\n };\n }\n\n /** Closes the socket immediately, regardless of active listeners. */\n disconnect(): void {\n this.socket?.disconnect();\n this.socket = undefined;\n this.connectPromise = undefined;\n }\n\n private async ensureConnected(): Promise<Socket | undefined> {\n if (this.socket?.connected) return this.socket;\n if (this.connectPromise) return this.connectPromise;\n\n this.connectPromise = (async () => {\n const token = await this.options.getAccessToken();\n // Both events are user-room-scoped — an unauthenticated socket would\n // never receive anything, so don't bother opening one.\n if (!token) return undefined;\n\n const socket = io(this.options.baseUrl, {\n transports: ['websocket'],\n auth: { token: `Bearer ${token}` },\n });\n\n socket.on('balance_change', (payload: BalanceChangeEvent) => {\n for (const listener of this.balanceListeners) listener(payload);\n });\n socket.on('store_purchase_completed', (payload: StorePurchaseCompletedEvent) => {\n for (const listener of this.purchaseListeners) listener(payload);\n });\n\n this.socket = socket;\n return socket;\n })();\n\n try {\n return await this.connectPromise;\n } finally {\n this.connectPromise = undefined;\n }\n }\n\n private disconnectIfIdle(): void {\n if (this.balanceListeners.size === 0 && this.purchaseListeners.size === 0) {\n this.disconnect();\n }\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ var LooplaySDKRealtime=(function(exports){'use strict';var Ve=Object.defineProperty;var Me=(s,e)=>{for(var t in e)Ve(s,t,{get:e[t],enumerable:true});};var d=Object.create(null);d.open="0";d.close="1";d.ping="2";d.pong="3";d.message="4";d.upgrade="5";d.noop="6";var B=Object.create(null);Object.keys(d).forEach(s=>{B[d[s]]=s;});var N={type:"error",data:"parser error"};var le=typeof Blob=="function"||typeof Blob<"u"&&Object.prototype.toString.call(Blob)==="[object BlobConstructor]",pe=typeof ArrayBuffer=="function",de=s=>typeof ArrayBuffer.isView=="function"?ArrayBuffer.isView(s):s&&s.buffer instanceof ArrayBuffer,L=({type:s,data:e},t,r)=>le&&e instanceof Blob?t?r(e):fe(e,r):pe&&(e instanceof ArrayBuffer||de(e))?t?r(e):fe(new Blob([e]),r):r(d[s]+(e||"")),fe=(s,e)=>{let t=new FileReader;return t.onload=function(){let r=t.result.split(",")[1];e("b"+(r||""));},t.readAsDataURL(s)};function ue(s){return s instanceof Uint8Array?s:s instanceof ArrayBuffer?new Uint8Array(s):new Uint8Array(s.buffer,s.byteOffset,s.byteLength)}var Q;function me(s,e){if(le&&s.data instanceof Blob)return s.data.arrayBuffer().then(ue).then(e);if(pe&&(s.data instanceof ArrayBuffer||de(s.data)))return e(ue(s.data));L(s,false,t=>{Q||(Q=new TextEncoder),e(Q.encode(t));});}var ye="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",P=typeof Uint8Array>"u"?[]:new Uint8Array(256);for(let s=0;s<ye.length;s++)P[ye.charCodeAt(s)]=s;var ge=s=>{let e=s.length*.75,t=s.length,r,i=0,n,o,c,h;s[s.length-1]==="="&&(e--,s[s.length-2]==="="&&e--);let m=new ArrayBuffer(e),p=new Uint8Array(m);for(r=0;r<t;r+=4)n=P[s.charCodeAt(r)],o=P[s.charCodeAt(r+1)],c=P[s.charCodeAt(r+2)],h=P[s.charCodeAt(r+3)],p[i++]=n<<2|o>>4,p[i++]=(o&15)<<4|c>>2,p[i++]=(c&3)<<6|h&63;return m};var He=typeof ArrayBuffer=="function",q=(s,e)=>{if(typeof s!="string")return {type:"message",data:_e(s,e)};let t=s.charAt(0);return t==="b"?{type:"message",data:We(s.substring(1),e)}:B[t]?s.length>1?{type:B[t],data:s.substring(1)}:{type:B[t]}:N},We=(s,e)=>{if(He){let t=ge(s);return _e(t,e)}else return {base64:true,data:s}},_e=(s,e)=>e==="blob"?s instanceof Blob?s:new Blob([s]):s instanceof ArrayBuffer?s:s.buffer;var be="",we=(s,e)=>{let t=s.length,r=new Array(t),i=0;s.forEach((n,o)=>{L(n,false,c=>{r[o]=c,++i===t&&e(r.join(be));});});},Ee=(s,e)=>{let t=s.split(be),r=[];for(let i=0;i<t.length;i++){let n=q(t[i],e);if(r.push(n),n.type==="error")break}return r};function ve(){return new TransformStream({transform(s,e){me(s,t=>{let r=t.length,i;if(r<126)i=new Uint8Array(1),new DataView(i.buffer).setUint8(0,r);else if(r<65536){i=new Uint8Array(3);let n=new DataView(i.buffer);n.setUint8(0,126),n.setUint16(1,r);}else {i=new Uint8Array(9);let n=new DataView(i.buffer);n.setUint8(0,127),n.setBigUint64(1,BigInt(r));}s.data&&typeof s.data!="string"&&(i[0]|=128),e.enqueue(i),e.enqueue(t);});}})}var G;function M(s){return s.reduce((e,t)=>e+t.length,0)}function H(s,e){if(s[0].length===e)return s.shift();let t=new Uint8Array(e),r=0;for(let i=0;i<e;i++)t[i]=s[0][r++],r===s[0].length&&(s.shift(),r=0);return s.length&&r<s[0].length&&(s[0]=s[0].slice(r)),t}function ke(s,e){G||(G=new TextDecoder);let t=[],r=0,i=-1,n=false;return new TransformStream({transform(o,c){for(t.push(o);;){if(r===0){if(M(t)<1)break;let h=H(t,1);n=(h[0]&128)===128,i=h[0]&127,i<126?r=3:i===126?r=1:r=2;}else if(r===1){if(M(t)<2)break;let h=H(t,2);i=new DataView(h.buffer,h.byteOffset,h.length).getUint16(0),r=3;}else if(r===2){if(M(t)<8)break;let h=H(t,8),m=new DataView(h.buffer,h.byteOffset,h.length),p=m.getUint32(0);if(p>Math.pow(2,21)-1){c.enqueue(N);break}i=p*Math.pow(2,32)+m.getUint32(4),r=3;}else {if(M(t)<i)break;let h=H(t,i);c.enqueue(q(n?h:G.decode(h),e)),r=0;}if(i===0||i>s){c.enqueue(N);break}}}})}var j=4;function f(s){if(s)return Ke(s)}function Ke(s){for(var e in f.prototype)s[e]=f.prototype[e];return s}f.prototype.on=f.prototype.addEventListener=function(s,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+s]=this._callbacks["$"+s]||[]).push(e),this};f.prototype.once=function(s,e){function t(){this.off(s,t),e.apply(this,arguments);}return t.fn=e,this.on(s,t),this};f.prototype.off=f.prototype.removeListener=f.prototype.removeAllListeners=f.prototype.removeEventListener=function(s,e){if(this._callbacks=this._callbacks||{},arguments.length==0)return this._callbacks={},this;var t=this._callbacks["$"+s];if(!t)return this;if(arguments.length==1)return delete this._callbacks["$"+s],this;for(var r,i=0;i<t.length;i++)if(r=t[i],r===e||r.fn===e){t.splice(i,1);break}return t.length===0&&delete this._callbacks["$"+s],this};f.prototype.emit=function(s){this._callbacks=this._callbacks||{};for(var e=new Array(arguments.length-1),t=this._callbacks["$"+s],r=1;r<arguments.length;r++)e[r-1]=arguments[r];if(t){t=t.slice(0);for(var r=0,i=t.length;r<i;++r)t[r].apply(this,e);}return this};f.prototype.emitReserved=f.prototype.emit;f.prototype.listeners=function(s){return this._callbacks=this._callbacks||{},this._callbacks["$"+s]||[]};f.prototype.hasListeners=function(s){return !!this.listeners(s).length};var y=typeof Promise=="function"&&typeof Promise.resolve=="function"?e=>Promise.resolve().then(e):(e,t)=>t(e,0),u=typeof self<"u"?self:typeof window<"u"?window:Function("return this")(),xe="arraybuffer";function W(s,...e){return e.reduce((t,r)=>(s.hasOwnProperty(r)&&(t[r]=s[r]),t),{})}var Ye=u.setTimeout,Je=u.clearTimeout;function g(s,e){e.useNativeTimers?(s.setTimeoutFn=Ye.bind(u),s.clearTimeoutFn=Je.bind(u)):(s.setTimeoutFn=u.setTimeout.bind(u),s.clearTimeoutFn=u.clearTimeout.bind(u));}var ze=1.33;function Te(s){return typeof s=="string"?Xe(s):Math.ceil((s.byteLength||s.size)*ze)}function Xe(s){let e=0,t=0;for(let r=0,i=s.length;r<i;r++)e=s.charCodeAt(r),e<128?t+=1:e<2048?t+=2:e<55296||e>=57344?t+=3:(r++,t+=4);return t}function K(){return Date.now().toString(36).substring(3)+Math.random().toString(36).substring(2,5)}function Ae(s){let e="";for(let t in s)s.hasOwnProperty(t)&&(e.length&&(e+="&"),e+=encodeURIComponent(t)+"="+encodeURIComponent(s[t]));return e}function Ce(s){let e={},t=s.split("&");for(let r=0,i=t.length;r<i;r++){let n=t[r].split("=");e[decodeURIComponent(n[0])]=decodeURIComponent(n[1]);}return e}var Y=class extends Error{constructor(e,t,r){super(e),this.description=t,this.context=r,this.type="TransportError";}},_=class extends f{constructor(e){super(),this.writable=false,g(this,e),this.opts=e,this.query=e.query,this.socket=e.socket,this.supportsBinary=!e.forceBase64;}onError(e,t,r){return super.emitReserved("error",new Y(e,t,r)),this}open(){return this.readyState="opening",this.doOpen(),this}close(){return (this.readyState==="opening"||this.readyState==="open")&&(this.doClose(),this.onClose()),this}send(e){this.readyState==="open"&&this.write(e);}onOpen(){this.readyState="open",this.writable=true,super.emitReserved("open");}onData(e){let t=q(e,this.socket.binaryType);this.onPacket(t);}onPacket(e){super.emitReserved("packet",e);}onClose(e){this.readyState="closed",super.emitReserved("close",e);}pause(e){}createUri(e,t={}){return e+"://"+this._hostname()+this._port()+this.opts.path+this._query(t)}_hostname(){let e=this.opts.hostname;return e.indexOf(":")===-1?e:"["+e+"]"}_port(){return this.opts.port&&(this.opts.secure&&Number(this.opts.port)!==443||!this.opts.secure&&Number(this.opts.port)!==80)?":"+this.opts.port:""}_query(e){let t=Ae(e);return t.length?"?"+t:""}};var D=class extends _{constructor(){super(...arguments),this._polling=false;}get name(){return "polling"}doOpen(){this._poll();}pause(e){this.readyState="pausing";let t=()=>{this.readyState="paused",e();};if(this._polling||!this.writable){let r=0;this._polling&&(r++,this.once("pollComplete",function(){--r||t();})),this.writable||(r++,this.once("drain",function(){--r||t();}));}else t();}_poll(){this._polling=true,this.doPoll(),this.emitReserved("poll");}onData(e){let t=r=>{if(this.readyState==="opening"&&r.type==="open"&&this.onOpen(),r.type==="close")return this.onClose({description:"transport closed by the server"}),false;this.onPacket(r);};Ee(e,this.socket.binaryType).forEach(t),this.readyState!=="closed"&&(this._polling=false,this.emitReserved("pollComplete"),this.readyState==="open"&&this._poll());}doClose(){let e=()=>{this.write([{type:"close"}]);};this.readyState==="open"?e():this.once("open",e);}write(e){this.writable=false,we(e,t=>{this.doWrite(t,()=>{this.writable=true,this.emitReserved("drain");});});}uri(){let e=this.opts.secure?"https":"http",t=this.query||{};return this.opts.timestampRequests!==false&&(t[this.opts.timestampParam]=K()),!this.supportsBinary&&!t.sid&&(t.b64=1),this.createUri(e,t)}};var Se=false;try{Se=typeof XMLHttpRequest<"u"&&"withCredentials"in new XMLHttpRequest;}catch{}var Re=Se;function $e(){}var Z=class extends D{constructor(e){if(super(e),typeof location<"u"){let t=location.protocol==="https:",r=location.port;r||(r=t?"443":"80"),this.xd=typeof location<"u"&&e.hostname!==location.hostname||r!==e.port;}}doWrite(e,t){let r=this.request({method:"POST",data:e});r.on("success",t),r.on("error",(i,n)=>{this.onError("xhr post error",i,n);});}doPoll(){let e=this.request();e.on("data",this.onData.bind(this)),e.on("error",(t,r)=>{this.onError("xhr poll error",t,r);}),this.pollXhr=e;}},w=class s extends f{constructor(e,t,r){super(),this.createRequest=e,g(this,r),this._opts=r,this._method=r.method||"GET",this._uri=t,this._data=r.data!==void 0?r.data:null,this._create();}_create(){var e;let t=W(this._opts,"agent","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","autoUnref");t.xdomain=!!this._opts.xd;let r=this._xhr=this.createRequest(t);try{r.open(this._method,this._uri,!0);try{if(this._opts.extraHeaders){r.setDisableHeaderCheck&&r.setDisableHeaderCheck(!0);for(let i in this._opts.extraHeaders)this._opts.extraHeaders.hasOwnProperty(i)&&r.setRequestHeader(i,this._opts.extraHeaders[i]);}}catch{}if(this._method==="POST")try{r.setRequestHeader("Content-type","text/plain;charset=UTF-8");}catch{}try{r.setRequestHeader("Accept","*/*");}catch{}(e=this._opts.cookieJar)===null||e===void 0||e.addCookies(r),"withCredentials"in r&&(r.withCredentials=this._opts.withCredentials),this._opts.requestTimeout&&(r.timeout=this._opts.requestTimeout),r.onreadystatechange=()=>{var i;r.readyState===3&&((i=this._opts.cookieJar)===null||i===void 0||i.parseCookies(r.getResponseHeader("set-cookie"))),r.readyState===4&&(r.status===200||r.status===1223?this._onLoad():this.setTimeoutFn(()=>{this._onError(typeof r.status=="number"?r.status:0);},0));},r.send(this._data);}catch(i){this.setTimeoutFn(()=>{this._onError(i);},0);return}typeof document<"u"&&(this._index=s.requestsCount++,s.requests[this._index]=this);}_onError(e){this.emitReserved("error",e,this._xhr),this._cleanup(true);}_cleanup(e){if(!(typeof this._xhr>"u"||this._xhr===null)){if(this._xhr.onreadystatechange=$e,e)try{this._xhr.abort();}catch{}typeof document<"u"&&delete s.requests[this._index],this._xhr=null;}}_onLoad(){let e=this._xhr.responseText;e!==null&&(this.emitReserved("data",e),this.emitReserved("success"),this._cleanup());}abort(){this._cleanup();}};w.requestsCount=0;w.requests={};if(typeof document<"u"){if(typeof attachEvent=="function")attachEvent("onunload",Oe);else if(typeof addEventListener=="function"){let s="onpagehide"in u?"pagehide":"unload";addEventListener(s,Oe,false);}}function Oe(){for(let s in w.requests)w.requests.hasOwnProperty(s)&&w.requests[s].abort();}var Qe=(function(){let s=Be({xdomain:false});return s&&s.responseType!==null})(),E=class extends Z{constructor(e){super(e);let t=e&&e.forceBase64;this.supportsBinary=Qe&&!t;}request(e={}){return Object.assign(e,{xd:this.xd},this.opts),new w(Be,this.uri(),e)}};function Be(s){let e=s.xdomain;try{if(typeof XMLHttpRequest<"u"&&(!e||Re))return new XMLHttpRequest}catch{}if(!e)try{return new u[["Active"].concat("Object").join("X")]("Microsoft.XMLHTTP")}catch{}}var Ne=typeof navigator<"u"&&typeof navigator.product=="string"&&navigator.product.toLowerCase()==="reactnative",te=class extends _{get name(){return "websocket"}doOpen(){let e=this.uri(),t=this.opts.protocols,r=Ne?{}:W(this.opts,"agent","perMessageDeflate","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","localAddress","protocolVersion","origin","maxPayload","family","checkServerIdentity");this.opts.extraHeaders&&(r.headers=this.opts.extraHeaders);try{this.ws=this.createSocket(e,t,r);}catch(i){return this.emitReserved("error",i)}this.ws.binaryType=this.socket.binaryType,this.addEventListeners();}addEventListeners(){this.ws.onopen=()=>{this.opts.autoUnref&&this.ws._socket.unref(),this.onOpen();},this.ws.onclose=e=>this.onClose({description:"websocket connection closed",context:e}),this.ws.onmessage=e=>this.onData(e.data),this.ws.onerror=e=>this.onError("websocket error",e);}write(e){this.writable=false;for(let t=0;t<e.length;t++){let r=e[t],i=t===e.length-1;L(r,this.supportsBinary,n=>{try{this.doWrite(r,n);}catch{}i&&y(()=>{this.writable=true,this.emitReserved("drain");},this.setTimeoutFn);});}}doClose(){typeof this.ws<"u"&&(this.ws.onerror=()=>{},this.ws.close(),this.ws=null);}uri(){let e=this.opts.secure?"wss":"ws",t=this.query||{};return this.opts.timestampRequests&&(t[this.opts.timestampParam]=K()),this.supportsBinary||(t.b64=1),this.createUri(e,t)}},ee=u.WebSocket||u.MozWebSocket,v=class extends te{createSocket(e,t,r){return Ne?new ee(e,t,r):t?new ee(e,t):new ee(e)}doWrite(e,t){this.ws.send(t);}};var T=class extends _{get name(){return "webtransport"}doOpen(){try{this._transport=new WebTransport(this.createUri("https"),this.opts.transportOptions[this.name]);}catch(e){return this.emitReserved("error",e)}this._transport.closed.then(()=>{this.onClose();}).catch(e=>{this.onError("webtransport error",e);}),this._transport.ready.then(()=>{this._transport.createBidirectionalStream().then(e=>{let t=ke(Number.MAX_SAFE_INTEGER,this.socket.binaryType),r=e.readable.pipeThrough(t).getReader(),i=ve();i.readable.pipeTo(e.writable),this._writer=i.writable.getWriter();let n=()=>{r.read().then(({done:c,value:h})=>{c||(this.onPacket(h),n());}).catch(c=>{});};n();let o={type:"open"};this.query.sid&&(o.data=`{"sid":"${this.query.sid}"}`),this._writer.write(o).then(()=>this.onOpen());});});}write(e){this.writable=false;for(let t=0;t<e.length;t++){let r=e[t],i=t===e.length-1;this._writer.write(r).then(()=>{i&&y(()=>{this.writable=true,this.emitReserved("drain");},this.setTimeoutFn);});}}doClose(){var e;(e=this._transport)===null||e===void 0||e.close();}};var se={websocket:v,webtransport:T,polling:E};var Ge=/^(?:(?![^:@\/?#]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,je=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];function A(s){if(s.length>8e3)throw "URI too long";let e=s,t=s.indexOf("["),r=s.indexOf("]");t!=-1&&r!=-1&&(s=s.substring(0,t)+s.substring(t,r).replace(/:/g,";")+s.substring(r,s.length));let i=Ge.exec(s||""),n={},o=14;for(;o--;)n[je[o]]=i[o]||"";return t!=-1&&r!=-1&&(n.source=e,n.host=n.host.substring(1,n.host.length-1).replace(/;/g,":"),n.authority=n.authority.replace("[","").replace("]","").replace(/;/g,":"),n.ipv6uri=true),n.pathNames=Ze(n,n.path),n.queryKey=et(n,n.query),n}function Ze(s,e){let t=/\/{2,9}/g,r=e.replace(t,"/").split("/");return (e.slice(0,1)=="/"||e.length===0)&&r.splice(0,1),e.slice(-1)=="/"&&r.splice(r.length-1,1),r}function et(s,e){let t={};return e.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,function(r,i,n){i&&(t[i]=n);}),t}var re=typeof addEventListener=="function"&&typeof removeEventListener=="function",J=[];re&&addEventListener("offline",()=>{J.forEach(s=>s());},false);var k=class s extends f{constructor(e,t){if(super(),this.binaryType=xe,this.writeBuffer=[],this._prevBufferLen=0,this._pingInterval=-1,this._pingTimeout=-1,this._maxPayload=-1,this._pingTimeoutTime=1/0,e&&typeof e=="object"&&(t=e,e=null),e){let r=A(e);t.hostname=r.host,t.secure=r.protocol==="https"||r.protocol==="wss",t.port=r.port,r.query&&(t.query=r.query);}else t.host&&(t.hostname=A(t.host).host);g(this,t),this.secure=t.secure!=null?t.secure:typeof location<"u"&&location.protocol==="https:",t.hostname&&!t.port&&(t.port=this.secure?"443":"80"),this.hostname=t.hostname||(typeof location<"u"?location.hostname:"localhost"),this.port=t.port||(typeof location<"u"&&location.port?location.port:this.secure?"443":"80"),this.transports=[],this._transportsByName={},t.transports.forEach(r=>{let i=r.prototype.name;this.transports.push(i),this._transportsByName[i]=r;}),this.opts=Object.assign({path:"/engine.io",agent:false,withCredentials:false,upgrade:true,timestampParam:"t",rememberUpgrade:false,addTrailingSlash:true,rejectUnauthorized:true,perMessageDeflate:{threshold:1024},transportOptions:{},closeOnBeforeunload:false},t),this.opts.path=this.opts.path.replace(/\/$/,"")+(this.opts.addTrailingSlash?"/":""),typeof this.opts.query=="string"&&(this.opts.query=Ce(this.opts.query)),re&&(this.opts.closeOnBeforeunload&&(this._beforeunloadEventListener=()=>{this.transport&&(this.transport.removeAllListeners(),this.transport.close());},addEventListener("beforeunload",this._beforeunloadEventListener,false)),this.hostname!=="localhost"&&(this._offlineEventListener=()=>{this._onClose("transport close",{description:"network connection lost"});},J.push(this._offlineEventListener))),this.opts.withCredentials&&(this._cookieJar=void 0),this._open();}createTransport(e){let t=Object.assign({},this.opts.query);t.EIO=j,t.transport=e,this.id&&(t.sid=this.id);let r=Object.assign({},this.opts,{query:t,socket:this,hostname:this.hostname,secure:this.secure,port:this.port},this.opts.transportOptions[e]);return new this._transportsByName[e](r)}_open(){if(this.transports.length===0){this.setTimeoutFn(()=>{this.emitReserved("error","No transports available");},0);return}let e=this.opts.rememberUpgrade&&s.priorWebsocketSuccess&&this.transports.indexOf("websocket")!==-1?"websocket":this.transports[0];this.readyState="opening";let t=this.createTransport(e);t.open(),this.setTransport(t);}setTransport(e){this.transport&&this.transport.removeAllListeners(),this.transport=e,e.on("drain",this._onDrain.bind(this)).on("packet",this._onPacket.bind(this)).on("error",this._onError.bind(this)).on("close",t=>this._onClose("transport close",t));}onOpen(){this.readyState="open",s.priorWebsocketSuccess=this.transport.name==="websocket",this.emitReserved("open"),this.flush();}_onPacket(e){if(this.readyState==="opening"||this.readyState==="open"||this.readyState==="closing")switch(this.emitReserved("packet",e),this.emitReserved("heartbeat"),e.type){case "open":this.onHandshake(JSON.parse(e.data));break;case "ping":this._sendPacket("pong"),this.emitReserved("ping"),this.emitReserved("pong"),this._resetPingTimeout();break;case "error":let t=new Error("server error");t.code=e.data,this._onError(t);break;case "message":this.emitReserved("data",e.data),this.emitReserved("message",e.data);break}}onHandshake(e){this.emitReserved("handshake",e),this.id=e.sid,this.transport.query.sid=e.sid,this._pingInterval=e.pingInterval,this._pingTimeout=e.pingTimeout,this._maxPayload=e.maxPayload,this.onOpen(),this.readyState!=="closed"&&this._resetPingTimeout();}_resetPingTimeout(){this.clearTimeoutFn(this._pingTimeoutTimer);let e=this._pingInterval+this._pingTimeout;this._pingTimeoutTime=Date.now()+e,this._pingTimeoutTimer=this.setTimeoutFn(()=>{this._onClose("ping timeout");},e),this.opts.autoUnref&&this._pingTimeoutTimer.unref();}_onDrain(){this.writeBuffer.splice(0,this._prevBufferLen),this._prevBufferLen=0,this.writeBuffer.length===0?this.emitReserved("drain"):this.flush();}flush(){if(this.readyState!=="closed"&&this.transport.writable&&!this.upgrading&&this.writeBuffer.length){let e=this._getWritablePackets();this.transport.send(e),this._prevBufferLen=e.length,this.emitReserved("flush");}}_getWritablePackets(){if(!(this._maxPayload&&this.transport.name==="polling"&&this.writeBuffer.length>1))return this.writeBuffer;let t=1;for(let r=0;r<this.writeBuffer.length;r++){let i=this.writeBuffer[r].data;if(i&&(t+=Te(i)),r>0&&t>this._maxPayload)return this.writeBuffer.slice(0,r);t+=2;}return this.writeBuffer}_hasPingExpired(){if(!this._pingTimeoutTime)return true;let e=Date.now()>this._pingTimeoutTime;return e&&(this._pingTimeoutTime=0,y(()=>{this._onClose("ping timeout");},this.setTimeoutFn)),e}write(e,t,r){return this._sendPacket("message",e,t,r),this}send(e,t,r){return this._sendPacket("message",e,t,r),this}_sendPacket(e,t,r,i){if(typeof t=="function"&&(i=t,t=void 0),typeof r=="function"&&(i=r,r=null),this.readyState==="closing"||this.readyState==="closed")return;r=r||{},r.compress=r.compress!==false;let n={type:e,data:t,options:r};this.emitReserved("packetCreate",n),this.writeBuffer.push(n),i&&this.once("flush",i),this.flush();}close(){let e=()=>{this._onClose("forced close"),this.transport.close();},t=()=>{this.off("upgrade",t),this.off("upgradeError",t),e();},r=()=>{this.once("upgrade",t),this.once("upgradeError",t);};return (this.readyState==="opening"||this.readyState==="open")&&(this.readyState="closing",this.writeBuffer.length?this.once("drain",()=>{this.upgrading?r():e();}):this.upgrading?r():e()),this}_onError(e){if(s.priorWebsocketSuccess=false,this.opts.tryAllTransports&&this.transports.length>1&&this.readyState==="opening")return this.transports.shift(),this._open();this.emitReserved("error",e),this._onClose("transport error",e);}_onClose(e,t){if(this.readyState==="opening"||this.readyState==="open"||this.readyState==="closing"){if(this.clearTimeoutFn(this._pingTimeoutTimer),this.transport.removeAllListeners("close"),this.transport.close(),this.transport.removeAllListeners(),re&&(this._beforeunloadEventListener&&removeEventListener("beforeunload",this._beforeunloadEventListener,false),this._offlineEventListener)){let r=J.indexOf(this._offlineEventListener);r!==-1&&J.splice(r,1);}this.readyState="closed",this.id=null,this.emitReserved("close",e,t),this.writeBuffer=[],this._prevBufferLen=0;}}};k.protocol=j;var z=class extends k{constructor(){super(...arguments),this._upgrades=[];}onOpen(){if(super.onOpen(),this.readyState==="open"&&this.opts.upgrade)for(let e=0;e<this._upgrades.length;e++)this._probe(this._upgrades[e]);}_probe(e){let t=this.createTransport(e),r=false;k.priorWebsocketSuccess=false;let i=()=>{r||(t.send([{type:"ping",data:"probe"}]),t.once("packet",b=>{if(!r)if(b.type==="pong"&&b.data==="probe"){if(this.upgrading=true,this.emitReserved("upgrading",t),!t)return;k.priorWebsocketSuccess=t.name==="websocket",this.transport.pause(()=>{r||this.readyState!=="closed"&&(p(),this.setTransport(t),t.send([{type:"upgrade"}]),this.emitReserved("upgrade",t),t=null,this.upgrading=false,this.flush());});}else {let O=new Error("probe error");O.transport=t.name,this.emitReserved("upgradeError",O);}}));};function n(){r||(r=true,p(),t.close(),t=null);}let o=b=>{let O=new Error("probe error: "+b);O.transport=t.name,n(),this.emitReserved("upgradeError",O);};function c(){o("transport closed");}function h(){o("socket closed");}function m(b){t&&b.name!==t.name&&n();}let p=()=>{t.removeListener("open",i),t.removeListener("error",o),t.removeListener("close",c),this.off("close",h),this.off("upgrading",m);};t.once("open",i),t.once("error",o),t.once("close",c),this.once("close",h),this.once("upgrading",m),this._upgrades.indexOf("webtransport")!==-1&&e!=="webtransport"?this.setTimeoutFn(()=>{r||t.open();},200):t.open();}onHandshake(e){this._upgrades=this._filterUpgrades(e.upgrades),super.onHandshake(e);}_filterUpgrades(e){let t=[];for(let r=0;r<e.length;r++)~this.transports.indexOf(e[r])&&t.push(e[r]);return t}},C=class extends z{constructor(e,t={}){let r=typeof e=="object",i=r?{...e}:{...t};(!i.transports||i.transports&&typeof i.transports[0]=="string")&&(i.transports=(i.transports||["polling","websocket","webtransport"]).map(n=>se[n]).filter(n=>!!n)),super(r?i:e,i);}};function Le(s,e="",t){let r=s;t=t||typeof location<"u"&&location,s==null&&(s=t.protocol+"//"+t.host),typeof s=="string"&&(s.charAt(0)==="/"&&(s.charAt(1)==="/"?s=t.protocol+s:s=t.host+s),/^(https?|wss?):\/\//.test(s)||(typeof t<"u"?s=t.protocol+"//"+s:s="https://"+s),r=A(s)),r.port||(/^(http|ws)$/.test(r.protocol)?r.port="80":/^(http|ws)s$/.test(r.protocol)&&(r.port="443")),r.path=r.path||"/";let n=r.host.indexOf(":")!==-1?"["+r.host+"]":r.host;return r.id=r.protocol+"://"+n+":"+r.port+e,r.href=r.protocol+"://"+n+(t&&t.port===r.port?"":":"+r.port),r}var ce={};Me(ce,{Decoder:()=>oe,Encoder:()=>ne,PacketType:()=>a,isPacketValid:()=>ht,protocol:()=>Ue});var st=typeof ArrayBuffer=="function",rt=s=>typeof ArrayBuffer.isView=="function"?ArrayBuffer.isView(s):s.buffer instanceof ArrayBuffer,Pe=Object.prototype.toString,it=typeof Blob=="function"||typeof Blob<"u"&&Pe.call(Blob)==="[object BlobConstructor]",nt=typeof File=="function"||typeof File<"u"&&Pe.call(File)==="[object FileConstructor]";function U(s){return st&&(s instanceof ArrayBuffer||rt(s))||it&&s instanceof Blob||nt&&s instanceof File}function I(s,e){if(!s||typeof s!="object")return false;if(Array.isArray(s)){for(let t=0,r=s.length;t<r;t++)if(I(s[t]))return true;return false}if(U(s))return true;if(s.toJSON&&typeof s.toJSON=="function"&&arguments.length===1)return I(s.toJSON(),true);for(let t in s)if(Object.prototype.hasOwnProperty.call(s,t)&&I(s[t]))return true;return false}function qe(s){let e=[],t=s.data,r=s;return r.data=X(t,e),r.attachments=e.length,{packet:r,buffers:e}}function X(s,e,t){if(!s)return s;if(U(s)){let r={_placeholder:true,num:e.length};return e.push(s),r}else if(Array.isArray(s)){let r=new Array(s.length);for(let i=0;i<s.length;i++)r[i]=X(s[i],e);return r}else if(typeof s=="object"&&!(s instanceof Date)){if(s.toJSON&&typeof s.toJSON=="function"&&!t)return X(s.toJSON(),e,true);let r={};for(let i in s)Object.prototype.hasOwnProperty.call(s,i)&&(r[i]=X(s[i],e));return r}return s}function De(s,e){return s.data=ie(s.data,e),delete s.attachments,s}function ie(s,e){if(!s)return s;if(s&&s._placeholder===true){if(typeof s.num=="number"&&s.num>=0&&s.num<e.length)return e[s.num];throw new Error("illegal attachments")}else if(Array.isArray(s))for(let t=0;t<s.length;t++)s[t]=ie(s[t],e);else if(typeof s=="object")for(let t in s)Object.prototype.hasOwnProperty.call(s,t)&&(s[t]=ie(s[t],e));return s}var Ie=["connect","connect_error","disconnect","disconnecting","newListener","removeListener"],Ue=5,a;(function(s){s[s.CONNECT=0]="CONNECT",s[s.DISCONNECT=1]="DISCONNECT",s[s.EVENT=2]="EVENT",s[s.ACK=3]="ACK",s[s.CONNECT_ERROR=4]="CONNECT_ERROR",s[s.BINARY_EVENT=5]="BINARY_EVENT",s[s.BINARY_ACK=6]="BINARY_ACK";})(a||(a={}));var ne=class{constructor(e){this.replacer=e;}encode(e){return (e.type===a.EVENT||e.type===a.ACK)&&I(e)?this.encodeAsBinary({type:e.type===a.EVENT?a.BINARY_EVENT:a.BINARY_ACK,nsp:e.nsp,data:e.data,id:e.id}):[this.encodeAsString(e)]}encodeAsString(e){let t=""+e.type;return (e.type===a.BINARY_EVENT||e.type===a.BINARY_ACK)&&(t+=e.attachments+"-"),e.nsp&&e.nsp!=="/"&&(t+=e.nsp+","),e.id!=null&&(t+=e.id),e.data!=null&&(t+=JSON.stringify(e.data,this.replacer)),t}encodeAsBinary(e){let t=qe(e),r=this.encodeAsString(t.packet),i=t.buffers;return i.unshift(r),i}},oe=class s extends f{constructor(e){super(),this.opts=Object.assign({reviver:void 0,maxAttachments:10},typeof e=="function"?{reviver:e}:e);}add(e){let t;if(typeof e=="string"){if(this.reconstructor)throw new Error("got plaintext data when reconstructing a packet");t=this.decodeString(e);let r=t.type===a.BINARY_EVENT;r||t.type===a.BINARY_ACK?(t.type=r?a.EVENT:a.ACK,this.reconstructor=new ae(t)):super.emitReserved("decoded",t);}else if(U(e)||e.base64)if(this.reconstructor)t=this.reconstructor.takeBinaryData(e),t&&(this.reconstructor=null,super.emitReserved("decoded",t));else throw new Error("got binary data when not reconstructing a packet");else throw new Error("Unknown type: "+e)}decodeString(e){let t=0,r={type:Number(e.charAt(0))};if(a[r.type]===void 0)throw new Error("unknown packet type "+r.type);if(r.type===a.BINARY_EVENT||r.type===a.BINARY_ACK){let n=t+1;for(;e.charAt(++t)!=="-"&&t!=e.length;);let o=e.substring(n,t);if(o!=Number(o)||e.charAt(t)!=="-")throw new Error("Illegal attachments");let c=Number(o);if(!Fe(c)||c<1)throw new Error("Illegal attachments");if(c>this.opts.maxAttachments)throw new Error("too many attachments");r.attachments=c;}if(e.charAt(t+1)==="/"){let n=t+1;for(;++t&&!(e.charAt(t)===","||t===e.length););r.nsp=e.substring(n,t);}else r.nsp="/";let i=e.charAt(t+1);if(i!==""&&Number(i)==i){let n=t+1;for(;++t;){let o=e.charAt(t);if(o==null||Number(o)!=o){--t;break}if(t===e.length)break}r.id=Number(e.substring(n,t+1));}if(e.charAt(++t)){let n=this.tryParse(e.substr(t));if(s.isPayloadValid(r.type,n))r.data=n;else throw new Error("invalid payload")}return r}tryParse(e){try{return JSON.parse(e,this.opts.reviver)}catch{return false}}static isPayloadValid(e,t){switch(e){case a.CONNECT:return $(t);case a.DISCONNECT:return t===void 0;case a.CONNECT_ERROR:return typeof t=="string"||$(t);case a.EVENT:case a.BINARY_EVENT:return Array.isArray(t)&&(typeof t[0]=="number"||typeof t[0]=="string"&&Ie.indexOf(t[0])===-1);case a.ACK:case a.BINARY_ACK:return Array.isArray(t)}}destroy(){this.reconstructor&&(this.reconstructor.finishedReconstruction(),this.reconstructor=null);}},ae=class{constructor(e){this.packet=e,this.buffers=[],this.reconPack=e;}takeBinaryData(e){if(this.buffers.push(e),this.buffers.length===this.reconPack.attachments){let t=De(this.reconPack,this.buffers);return this.finishedReconstruction(),t}return null}finishedReconstruction(){this.reconPack=null,this.buffers=[];}};function ot(s){return typeof s=="string"}var Fe=Number.isInteger||function(s){return typeof s=="number"&&isFinite(s)&&Math.floor(s)===s};function at(s){return s===void 0||Fe(s)}function $(s){return Object.prototype.toString.call(s)==="[object Object]"}function ct(s,e){switch(s){case a.CONNECT:return e===void 0||$(e);case a.DISCONNECT:return e===void 0;case a.EVENT:return Array.isArray(e)&&(typeof e[0]=="number"||typeof e[0]=="string"&&Ie.indexOf(e[0])===-1);case a.ACK:return Array.isArray(e);case a.CONNECT_ERROR:return typeof e=="string"||$(e);default:return false}}function ht(s){return ot(s.nsp)&&at(s.id)&&ct(s.type,s.data)}function l(s,e,t){return s.on(e,t),function(){s.off(e,t);}}var ft=Object.freeze({connect:1,connect_error:1,disconnect:1,disconnecting:1,newListener:1,removeListener:1}),S=class extends f{constructor(e,t,r){super(),this.connected=false,this.recovered=false,this.receiveBuffer=[],this.sendBuffer=[],this._queue=[],this._queueSeq=0,this.ids=0,this.acks={},this.flags={},this.io=e,this.nsp=t,r&&r.auth&&(this.auth=r.auth),this._opts=Object.assign({},r),this.io._autoConnect&&this.open();}get disconnected(){return !this.connected}subEvents(){if(this.subs)return;let e=this.io;this.subs=[l(e,"open",this.onopen.bind(this)),l(e,"packet",this.onpacket.bind(this)),l(e,"error",this.onerror.bind(this)),l(e,"close",this.onclose.bind(this))];}get active(){return !!this.subs}connect(){return this.connected?this:(this.subEvents(),this.io._reconnecting||this.io.open(),this.io._readyState==="open"&&this.onopen(),this)}open(){return this.connect()}send(...e){return e.unshift("message"),this.emit.apply(this,e),this}emit(e,...t){var r,i,n;if(ft.hasOwnProperty(e))throw new Error('"'+e.toString()+'" is a reserved event name');if(t.unshift(e),this._opts.retries&&!this.flags.fromQueue&&!this.flags.volatile)return this._addToQueue(t),this;let o={type:a.EVENT,data:t};if(o.options={},o.options.compress=this.flags.compress!==false,typeof t[t.length-1]=="function"){let p=this.ids++,b=t.pop();this._registerAckCallback(p,b),o.id=p;}let c=(i=(r=this.io.engine)===null||r===void 0?void 0:r.transport)===null||i===void 0?void 0:i.writable,h=this.connected&&!(!((n=this.io.engine)===null||n===void 0)&&n._hasPingExpired());return this.flags.volatile&&!c||(h?(this.notifyOutgoingListeners(o),this.packet(o)):this.sendBuffer.push(o)),this.flags={},this}_registerAckCallback(e,t){var r;let i=(r=this.flags.timeout)!==null&&r!==void 0?r:this._opts.ackTimeout;if(i===void 0){this.acks[e]=t;return}let n=this.io.setTimeoutFn(()=>{delete this.acks[e];for(let c=0;c<this.sendBuffer.length;c++)this.sendBuffer[c].id===e&&this.sendBuffer.splice(c,1);t.call(this,new Error("operation has timed out"));},i),o=(...c)=>{this.io.clearTimeoutFn(n),t.apply(this,c);};o.withError=true,this.acks[e]=o;}emitWithAck(e,...t){return new Promise((r,i)=>{let n=(o,c)=>o?i(o):r(c);n.withError=true,t.push(n),this.emit(e,...t);})}_addToQueue(e){let t;typeof e[e.length-1]=="function"&&(t=e.pop());let r={id:this._queueSeq++,tryCount:0,pending:false,args:e,flags:Object.assign({fromQueue:true},this.flags)};e.push((i,...n)=>(this._queue[0],i!==null?r.tryCount>this._opts.retries&&(this._queue.shift(),t&&t(i)):(this._queue.shift(),t&&t(null,...n)),r.pending=false,this._drainQueue())),this._queue.push(r),this._drainQueue();}_drainQueue(e=false){if(!this.connected||this._queue.length===0)return;let t=this._queue[0];t.pending&&!e||(t.pending=true,t.tryCount++,this.flags=t.flags,this.emit.apply(this,t.args));}packet(e){e.nsp=this.nsp,this.io._packet(e);}onopen(){typeof this.auth=="function"?this.auth(e=>{this._sendConnectPacket(e);}):this._sendConnectPacket(this.auth);}_sendConnectPacket(e){this.packet({type:a.CONNECT,data:this._pid?Object.assign({pid:this._pid,offset:this._lastOffset},e):e});}onerror(e){this.connected||this.emitReserved("connect_error",e);}onclose(e,t){this.connected=false,delete this.id,this.emitReserved("disconnect",e,t),this._clearAcks();}_clearAcks(){Object.keys(this.acks).forEach(e=>{if(!this.sendBuffer.some(r=>String(r.id)===e)){let r=this.acks[e];delete this.acks[e],r.withError&&r.call(this,new Error("socket has been disconnected"));}});}onpacket(e){if(e.nsp===this.nsp)switch(e.type){case a.CONNECT:e.data&&e.data.sid?this.onconnect(e.data.sid,e.data.pid):this.emitReserved("connect_error",new Error("It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)"));break;case a.EVENT:case a.BINARY_EVENT:this.onevent(e);break;case a.ACK:case a.BINARY_ACK:this.onack(e);break;case a.DISCONNECT:this.ondisconnect();break;case a.CONNECT_ERROR:this.destroy();let r=new Error(e.data.message);r.data=e.data.data,this.emitReserved("connect_error",r);break}}onevent(e){let t=e.data||[];e.id!=null&&t.push(this.ack(e.id)),this.connected?this.emitEvent(t):this.receiveBuffer.push(Object.freeze(t));}emitEvent(e){if(this._anyListeners&&this._anyListeners.length){let t=this._anyListeners.slice();for(let r of t)r.apply(this,e);}super.emit.apply(this,e),this._pid&&e.length&&typeof e[e.length-1]=="string"&&(this._lastOffset=e[e.length-1]);}ack(e){let t=this,r=false;return function(...i){r||(r=true,t.packet({type:a.ACK,id:e,data:i}));}}onack(e){let t=this.acks[e.id];typeof t=="function"&&(delete this.acks[e.id],t.withError&&e.data.unshift(null),t.apply(this,e.data));}onconnect(e,t){this.id=e,this.recovered=t&&this._pid===t,this._pid=t,this.connected=true,this.emitBuffered(),this._drainQueue(true),this.emitReserved("connect");}emitBuffered(){this.receiveBuffer.forEach(e=>this.emitEvent(e)),this.receiveBuffer=[],this.sendBuffer.forEach(e=>{this.notifyOutgoingListeners(e),this.packet(e);}),this.sendBuffer=[];}ondisconnect(){this.destroy(),this.onclose("io server disconnect");}destroy(){this.subs&&(this.subs.forEach(e=>e()),this.subs=void 0),this.io._destroy(this);}disconnect(){return this.connected&&this.packet({type:a.DISCONNECT}),this.destroy(),this.connected&&this.onclose("io client disconnect"),this}close(){return this.disconnect()}compress(e){return this.flags.compress=e,this}get volatile(){return this.flags.volatile=true,this}timeout(e){return this.flags.timeout=e,this}onAny(e){return this._anyListeners=this._anyListeners||[],this._anyListeners.push(e),this}prependAny(e){return this._anyListeners=this._anyListeners||[],this._anyListeners.unshift(e),this}offAny(e){if(!this._anyListeners)return this;if(e){let t=this._anyListeners;for(let r=0;r<t.length;r++)if(e===t[r])return t.splice(r,1),this}else this._anyListeners=[];return this}listenersAny(){return this._anyListeners||[]}onAnyOutgoing(e){return this._anyOutgoingListeners=this._anyOutgoingListeners||[],this._anyOutgoingListeners.push(e),this}prependAnyOutgoing(e){return this._anyOutgoingListeners=this._anyOutgoingListeners||[],this._anyOutgoingListeners.unshift(e),this}offAnyOutgoing(e){if(!this._anyOutgoingListeners)return this;if(e){let t=this._anyOutgoingListeners;for(let r=0;r<t.length;r++)if(e===t[r])return t.splice(r,1),this}else this._anyOutgoingListeners=[];return this}listenersAnyOutgoing(){return this._anyOutgoingListeners||[]}notifyOutgoingListeners(e){if(this._anyOutgoingListeners&&this._anyOutgoingListeners.length){let t=this._anyOutgoingListeners.slice();for(let r of t)r.apply(this,e.data);}}};function x(s){s=s||{},this.ms=s.min||100,this.max=s.max||1e4,this.factor=s.factor||2,this.jitter=s.jitter>0&&s.jitter<=1?s.jitter:0,this.attempts=0;}x.prototype.duration=function(){var s=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),t=Math.floor(e*this.jitter*s);s=(Math.floor(e*10)&1)==0?s-t:s+t;}return Math.min(s,this.max)|0};x.prototype.reset=function(){this.attempts=0;};x.prototype.setMin=function(s){this.ms=s;};x.prototype.setMax=function(s){this.max=s;};x.prototype.setJitter=function(s){this.jitter=s;};var R=class extends f{constructor(e,t){var r;super(),this.nsps={},this.subs=[],e&&typeof e=="object"&&(t=e,e=void 0),t=t||{},t.path=t.path||"/socket.io",this.opts=t,g(this,t),this.reconnection(t.reconnection!==false),this.reconnectionAttempts(t.reconnectionAttempts||1/0),this.reconnectionDelay(t.reconnectionDelay||1e3),this.reconnectionDelayMax(t.reconnectionDelayMax||5e3),this.randomizationFactor((r=t.randomizationFactor)!==null&&r!==void 0?r:.5),this.backoff=new x({min:this.reconnectionDelay(),max:this.reconnectionDelayMax(),jitter:this.randomizationFactor()}),this.timeout(t.timeout==null?2e4:t.timeout),this._readyState="closed",this.uri=e;let i=t.parser||ce;this.encoder=new i.Encoder,this.decoder=new i.Decoder,this._autoConnect=t.autoConnect!==false,this._autoConnect&&this.open();}reconnection(e){return arguments.length?(this._reconnection=!!e,e||(this.skipReconnect=true),this):this._reconnection}reconnectionAttempts(e){return e===void 0?this._reconnectionAttempts:(this._reconnectionAttempts=e,this)}reconnectionDelay(e){var t;return e===void 0?this._reconnectionDelay:(this._reconnectionDelay=e,(t=this.backoff)===null||t===void 0||t.setMin(e),this)}randomizationFactor(e){var t;return e===void 0?this._randomizationFactor:(this._randomizationFactor=e,(t=this.backoff)===null||t===void 0||t.setJitter(e),this)}reconnectionDelayMax(e){var t;return e===void 0?this._reconnectionDelayMax:(this._reconnectionDelayMax=e,(t=this.backoff)===null||t===void 0||t.setMax(e),this)}timeout(e){return arguments.length?(this._timeout=e,this):this._timeout}maybeReconnectOnOpen(){!this._reconnecting&&this._reconnection&&this.backoff.attempts===0&&this.reconnect();}open(e){if(~this._readyState.indexOf("open"))return this;this.engine=new C(this.uri,this.opts);let t=this.engine,r=this;this._readyState="opening",this.skipReconnect=false;let i=l(t,"open",function(){r.onopen(),e&&e();}),n=c=>{this.cleanup(),this._readyState="closed",this.emitReserved("error",c),e?e(c):this.maybeReconnectOnOpen();},o=l(t,"error",n);if(this._timeout!==false){let c=this._timeout,h=this.setTimeoutFn(()=>{i(),n(new Error("timeout")),t.close();},c);this.opts.autoUnref&&h.unref(),this.subs.push(()=>{this.clearTimeoutFn(h);});}return this.subs.push(i),this.subs.push(o),this}connect(e){return this.open(e)}onopen(){this.cleanup(),this._readyState="open",this.emitReserved("open");let e=this.engine;this.subs.push(l(e,"ping",this.onping.bind(this)),l(e,"data",this.ondata.bind(this)),l(e,"error",this.onerror.bind(this)),l(e,"close",this.onclose.bind(this)),l(this.decoder,"decoded",this.ondecoded.bind(this)));}onping(){this.emitReserved("ping");}ondata(e){try{this.decoder.add(e);}catch(t){this.onclose("parse error",t);}}ondecoded(e){y(()=>{this.emitReserved("packet",e);},this.setTimeoutFn);}onerror(e){this.emitReserved("error",e);}socket(e,t){let r=this.nsps[e];return r?this._autoConnect&&!r.active&&r.connect():(r=new S(this,e,t),this.nsps[e]=r),r}_destroy(e){let t=Object.keys(this.nsps);for(let r of t)if(this.nsps[r].active)return;this._close();}_packet(e){let t=this.encoder.encode(e);for(let r=0;r<t.length;r++)this.engine.write(t[r],e.options);}cleanup(){this.subs.forEach(e=>e()),this.subs.length=0,this.decoder.destroy();}_close(){this.skipReconnect=true,this._reconnecting=false,this.onclose("forced close");}disconnect(){return this._close()}onclose(e,t){var r;this.cleanup(),(r=this.engine)===null||r===void 0||r.close(),this.backoff.reset(),this._readyState="closed",this.emitReserved("close",e,t),this._reconnection&&!this.skipReconnect&&this.reconnect();}reconnect(){if(this._reconnecting||this.skipReconnect)return this;let e=this;if(this.backoff.attempts>=this._reconnectionAttempts)this.backoff.reset(),this.emitReserved("reconnect_failed"),this._reconnecting=false;else {let t=this.backoff.duration();this._reconnecting=true;let r=this.setTimeoutFn(()=>{e.skipReconnect||(this.emitReserved("reconnect_attempt",e.backoff.attempts),!e.skipReconnect&&e.open(i=>{i?(e._reconnecting=false,e.reconnect(),this.emitReserved("reconnect_error",i)):e.onreconnect();}));},t);this.opts.autoUnref&&r.unref(),this.subs.push(()=>{this.clearTimeoutFn(r);});}}onreconnect(){let e=this.backoff.attempts;this._reconnecting=false,this.backoff.reset(),this.emitReserved("reconnect",e);}};var F={};function V(s,e){typeof s=="object"&&(e=s,s=void 0),e=e||{};let t=Le(s,e.path||"/socket.io"),r=t.source,i=t.id,n=t.path,o=F[i]&&n in F[i].nsps,c=e.forceNew||e["force new connection"]||e.multiplex===false||o,h;return c?h=new R(r,e):(F[i]||(F[i]=new R(r,e)),h=F[i]),t.query&&!e.query&&(e.query=t.queryKey),h.socket(t.path,e)}Object.assign(V,{Manager:R,Socket:S,io:V,connect:V});var he=class{constructor(e){this.options=e;}options;socket;connectPromise;balanceListeners=new Set;purchaseListeners=new Set;onBalanceChange(e){return this.balanceListeners.add(e),this.ensureConnected(),()=>{this.balanceListeners.delete(e),this.disconnectIfIdle();}}onStorePurchase(e){return this.purchaseListeners.add(e),this.ensureConnected(),()=>{this.purchaseListeners.delete(e),this.disconnectIfIdle();}}disconnect(){this.socket?.disconnect(),this.socket=void 0,this.connectPromise=void 0;}async ensureConnected(){if(this.socket?.connected)return this.socket;if(this.connectPromise)return this.connectPromise;this.connectPromise=(async()=>{let e=await this.options.getAccessToken();if(!e)return;let t=V(this.options.baseUrl,{transports:["websocket"],auth:{token:`Bearer ${e}`}});return t.on("balance_change",r=>{for(let i of this.balanceListeners)i(r);}),t.on("store_purchase_completed",r=>{for(let i of this.purchaseListeners)i(r);}),this.socket=t,t})();try{return await this.connectPromise}finally{this.connectPromise=void 0;}}disconnectIfIdle(){this.balanceListeners.size===0&&this.purchaseListeners.size===0&&this.disconnect();}};
2
+ exports.WsClient=he;return exports;})({});//# sourceMappingURL=looplay-sdk-realtime.min.js.map
3
+ //# sourceMappingURL=looplay-sdk-realtime.min.js.map