@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 +221 -4
- package/dist/looplay-sdk-realtime.cjs.js +73 -0
- package/dist/looplay-sdk-realtime.cjs.js.map +1 -0
- package/dist/looplay-sdk-realtime.esm.js +71 -0
- package/dist/looplay-sdk-realtime.esm.js.map +1 -0
- package/dist/looplay-sdk-realtime.min.js +3 -0
- package/dist/looplay-sdk-realtime.min.js.map +1 -0
- package/dist/looplay-sdk.cjs.js +133 -162
- package/dist/looplay-sdk.cjs.js.map +1 -1
- package/dist/looplay-sdk.esm.js +134 -162
- package/dist/looplay-sdk.esm.js.map +1 -1
- package/dist/looplay-sdk.min.js +2 -2
- package/dist/looplay-sdk.min.js.map +1 -1
- package/dist/types/apps/LooplaySDK.types.d.ts +54 -6
- package/dist/types/apps/api-client.d.ts +22 -16
- package/dist/types/apps/service-client.d.ts +33 -22
- package/dist/types/apps/ws-client.d.ts +35 -0
- package/dist/types/errors.d.ts +0 -3
- package/dist/types/realtime.d.ts +9 -0
- package/dist/types/types.d.ts +2 -1
- package/package.json +17 -1
package/README.md
CHANGED
|
@@ -1,7 +1,44 @@
|
|
|
1
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|