@taphubhq/sdk-core 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +119 -0
- package/dist/index.cjs +2513 -0
- package/dist/index.d.mts +780 -0
- package/dist/index.d.ts +780 -0
- package/dist/index.js +2448 -0
- package/package.json +49 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
import EventEmitter from 'eventemitter3';
|
|
2
|
+
|
|
3
|
+
interface TaphubStorageAdapter {
|
|
4
|
+
get(key: string): string | null;
|
|
5
|
+
set(key: string, value: string): void;
|
|
6
|
+
remove(key: string): void;
|
|
7
|
+
}
|
|
8
|
+
declare function autoDetectStorage(): TaphubStorageAdapter;
|
|
9
|
+
declare const TaphubStorage: {
|
|
10
|
+
memory(): TaphubStorageAdapter;
|
|
11
|
+
localStorage(): TaphubStorageAdapter;
|
|
12
|
+
sessionStorage(): TaphubStorageAdapter;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
interface TaphubClientConfig {
|
|
16
|
+
agencyId: string;
|
|
17
|
+
endpoint: string;
|
|
18
|
+
mqttEndpoint?: string;
|
|
19
|
+
storage?: TaphubStorageAdapter;
|
|
20
|
+
fetch?: typeof globalThis.fetch;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type SignalSource = 'graphql' | 'rest' | 'mqtt' | 'browser' | 'connection';
|
|
24
|
+
type SampleReason = 'ok' | 'network' | 'timeout' | 'offline' | 'backend5xx' | 'backend4xx' | 'gqlError';
|
|
25
|
+
type Sample = {
|
|
26
|
+
ts: number;
|
|
27
|
+
rtt: number;
|
|
28
|
+
source: SignalSource;
|
|
29
|
+
reason: SampleReason;
|
|
30
|
+
};
|
|
31
|
+
type NetworkLevel = 'good' | 'fair' | 'poor' | 'offline';
|
|
32
|
+
type BackendHealth = 'ok' | 'degraded' | 'down';
|
|
33
|
+
type NetworkQuality = {
|
|
34
|
+
network: NetworkLevel;
|
|
35
|
+
backend: BackendHealth;
|
|
36
|
+
rtt: number;
|
|
37
|
+
jitter: number;
|
|
38
|
+
lossRate: number;
|
|
39
|
+
mqttConnected: boolean;
|
|
40
|
+
samplesInWindow: number;
|
|
41
|
+
lastUpdated: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type TaphubEventMap = {
|
|
45
|
+
'auth-expired': void;
|
|
46
|
+
'network:change': {
|
|
47
|
+
previous: NetworkQuality;
|
|
48
|
+
current: NetworkQuality;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
type EventHandler<T> = (payload: T) => void;
|
|
52
|
+
declare class TaphubEventBus<EventMap extends Record<string, unknown> = TaphubEventMap> {
|
|
53
|
+
private target;
|
|
54
|
+
on<K extends keyof EventMap & string>(event: K, handler: EventHandler<EventMap[K]>): void;
|
|
55
|
+
off<K extends keyof EventMap & string>(event: K, handler: EventHandler<EventMap[K]>): void;
|
|
56
|
+
emit<K extends keyof EventMap & string>(event: K, payload?: EventMap[K]): void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface GraphQLRequestOpts {
|
|
60
|
+
signal?: AbortSignal;
|
|
61
|
+
}
|
|
62
|
+
interface GraphQLTransport {
|
|
63
|
+
request<T>(query: string, variables?: Record<string, unknown>, opts?: GraphQLRequestOpts): Promise<T>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface RestRequestOpts {
|
|
67
|
+
signal?: AbortSignal;
|
|
68
|
+
}
|
|
69
|
+
interface RestTransport {
|
|
70
|
+
get<T>(path: string, opts?: RestRequestOpts): Promise<T>;
|
|
71
|
+
post<T>(path: string, body?: unknown, opts?: RestRequestOpts): Promise<T>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface Wallet$1 {
|
|
75
|
+
id: string | null;
|
|
76
|
+
balance: string;
|
|
77
|
+
currency: string;
|
|
78
|
+
isEnabled: boolean;
|
|
79
|
+
}
|
|
80
|
+
interface User {
|
|
81
|
+
id: string;
|
|
82
|
+
displayName: string;
|
|
83
|
+
agencyUid: string | null;
|
|
84
|
+
currency: string;
|
|
85
|
+
wallet: Wallet$1;
|
|
86
|
+
}
|
|
87
|
+
interface LoginResult {
|
|
88
|
+
accessToken: string;
|
|
89
|
+
user: User;
|
|
90
|
+
isDemo: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface AuthModuleDeps {
|
|
94
|
+
rest: RestTransport;
|
|
95
|
+
graphql: GraphQLTransport;
|
|
96
|
+
graphqlUser: GraphQLTransport;
|
|
97
|
+
setToken: (token: string | null, opts?: {
|
|
98
|
+
isDemo?: boolean;
|
|
99
|
+
}) => void;
|
|
100
|
+
agencyId: string;
|
|
101
|
+
onLoginSuccess?: () => Promise<void>;
|
|
102
|
+
onLogout?: () => void | Promise<void>;
|
|
103
|
+
}
|
|
104
|
+
declare class AuthModule {
|
|
105
|
+
#private;
|
|
106
|
+
constructor(deps: AuthModuleDeps);
|
|
107
|
+
/** @deprecated REST auth endpoints are being retired; use `loginWithSession` instead. */
|
|
108
|
+
loginWithGoogle(idToken: string): Promise<LoginResult>;
|
|
109
|
+
/** @deprecated REST auth endpoints are being retired; use `loginWithSession` or `createDemoUser` instead. */
|
|
110
|
+
demoLogin(username?: string): Promise<LoginResult>;
|
|
111
|
+
createDemoUser(username?: string, opts?: {
|
|
112
|
+
signal?: AbortSignal;
|
|
113
|
+
}): Promise<LoginResult>;
|
|
114
|
+
loginWithSession(sessionToken: string, opts?: {
|
|
115
|
+
signal?: AbortSignal;
|
|
116
|
+
}): Promise<LoginResult>;
|
|
117
|
+
logout(): Promise<void>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
type BidStatus = 'pending' | 'win' | 'lose' | 'cancelled';
|
|
121
|
+
interface Bid {
|
|
122
|
+
id: string;
|
|
123
|
+
userId: string;
|
|
124
|
+
gameId: string;
|
|
125
|
+
currency: string;
|
|
126
|
+
amount: string;
|
|
127
|
+
coefficient: string;
|
|
128
|
+
time1: number;
|
|
129
|
+
time2: number;
|
|
130
|
+
price1: string;
|
|
131
|
+
price2: string;
|
|
132
|
+
status: BidStatus;
|
|
133
|
+
payout: string | null;
|
|
134
|
+
slippage: number;
|
|
135
|
+
/** ISO-8601 string passthrough from the server. */
|
|
136
|
+
createdAt: string | null;
|
|
137
|
+
}
|
|
138
|
+
interface CancelBidResult {
|
|
139
|
+
bid: Bid;
|
|
140
|
+
refundAmount: string;
|
|
141
|
+
newBalance: string;
|
|
142
|
+
}
|
|
143
|
+
interface PlaceBidInput {
|
|
144
|
+
gameId: string;
|
|
145
|
+
/**
|
|
146
|
+
* UUID of the user's wallet to debit. Required (non-optional) since
|
|
147
|
+
* grid-api's `PlaceBidInput.wallet_id` is NON_NULL (`ID!`). The SDK stays
|
|
148
|
+
* builder-agnostic — it doesn't know where the caller sourced this id
|
|
149
|
+
* (user-service, builder BFF, etc.); it just forwards to grid-api.
|
|
150
|
+
*
|
|
151
|
+
* See openspec/changes/bid-260518-wallet-id (design D1, D8).
|
|
152
|
+
*/
|
|
153
|
+
walletId: string;
|
|
154
|
+
time1: number;
|
|
155
|
+
time2: number;
|
|
156
|
+
price1: string;
|
|
157
|
+
price2: string;
|
|
158
|
+
coefficient: string;
|
|
159
|
+
amount: string;
|
|
160
|
+
slippage: number;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
declare const isPending: (bid: Bid) => boolean;
|
|
164
|
+
declare const isTerminal: (bid: Bid) => boolean;
|
|
165
|
+
declare const isWin: (bid: Bid) => boolean;
|
|
166
|
+
declare const isLoss: (bid: Bid) => boolean;
|
|
167
|
+
declare const isCancelled: (bid: Bid) => boolean;
|
|
168
|
+
|
|
169
|
+
interface BidModuleDeps {
|
|
170
|
+
graphql: GraphQLTransport;
|
|
171
|
+
}
|
|
172
|
+
declare class BidModule {
|
|
173
|
+
#private;
|
|
174
|
+
constructor(deps: BidModuleDeps);
|
|
175
|
+
placeBid(input: PlaceBidInput, opts?: {
|
|
176
|
+
signal?: AbortSignal;
|
|
177
|
+
}): Promise<Bid>;
|
|
178
|
+
cancelBid(bidId: string, opts?: {
|
|
179
|
+
signal?: AbortSignal;
|
|
180
|
+
}): Promise<CancelBidResult>;
|
|
181
|
+
listBids(opts?: {
|
|
182
|
+
status?: BidStatus;
|
|
183
|
+
limit?: number;
|
|
184
|
+
offset?: number;
|
|
185
|
+
gameId?: string;
|
|
186
|
+
signal?: AbortSignal;
|
|
187
|
+
}): Promise<Bid[]>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
interface Game {
|
|
191
|
+
id: string;
|
|
192
|
+
pair: string;
|
|
193
|
+
status: string;
|
|
194
|
+
config: GameConfig;
|
|
195
|
+
/** ISO-8601 string passthrough from the server. */
|
|
196
|
+
createdAt: string | null;
|
|
197
|
+
}
|
|
198
|
+
interface GameConfig {
|
|
199
|
+
gridConfig: GridConfig;
|
|
200
|
+
constraints: Constraints;
|
|
201
|
+
acceptableBids: number[];
|
|
202
|
+
minBidAmount: number;
|
|
203
|
+
maxBidAmount: number;
|
|
204
|
+
}
|
|
205
|
+
interface GridConfig {
|
|
206
|
+
cellSizeTime: number;
|
|
207
|
+
cellSizeValue: number;
|
|
208
|
+
candleSize: number;
|
|
209
|
+
baseline: number;
|
|
210
|
+
baselineTime: number;
|
|
211
|
+
}
|
|
212
|
+
interface Constraints {
|
|
213
|
+
minBetTime: number;
|
|
214
|
+
maxBetTime: number;
|
|
215
|
+
slippage: number;
|
|
216
|
+
priceMinRange: number;
|
|
217
|
+
priceMaxRange: number;
|
|
218
|
+
coefMults: number[];
|
|
219
|
+
maxCoef: number;
|
|
220
|
+
}
|
|
221
|
+
interface GamePairInfo {
|
|
222
|
+
/** game_pairs.id — catalog identifier for this pair within a gameplay. */
|
|
223
|
+
id: string;
|
|
224
|
+
/** Trading pair symbol, e.g. "BTC/USD". */
|
|
225
|
+
pair: string;
|
|
226
|
+
/** ID of the gameplay this pair belongs to. Use as `gameplaySlug` in game.get(). */
|
|
227
|
+
gameplayId: string;
|
|
228
|
+
/** Human-readable gameplay name. */
|
|
229
|
+
gameplayName: string;
|
|
230
|
+
/** Price feed source, e.g. "binance". */
|
|
231
|
+
source: string;
|
|
232
|
+
/**
|
|
233
|
+
* agency_game_pairs.id — the runtime game ID for this agency.
|
|
234
|
+
* Present only when the caller is authenticated with a valid JWT.
|
|
235
|
+
* Use directly as `gameId` in MQTT topic `game/{gameId}/candle`.
|
|
236
|
+
* Null when called without authentication.
|
|
237
|
+
*/
|
|
238
|
+
gameId: string | null;
|
|
239
|
+
}
|
|
240
|
+
interface Candle {
|
|
241
|
+
/** Unix epoch SECONDS (not ms). Multiply by 1000 for JS Date. */
|
|
242
|
+
time: number;
|
|
243
|
+
o: number;
|
|
244
|
+
h: number;
|
|
245
|
+
l: number;
|
|
246
|
+
c: number;
|
|
247
|
+
volatility: number;
|
|
248
|
+
coefMults: number[];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
interface GameModuleDeps {
|
|
252
|
+
graphql: GraphQLTransport;
|
|
253
|
+
}
|
|
254
|
+
declare class GameModule {
|
|
255
|
+
#private;
|
|
256
|
+
constructor(deps: GameModuleDeps);
|
|
257
|
+
get(pair: string, opts?: {
|
|
258
|
+
gameplaySlug?: string;
|
|
259
|
+
signal?: AbortSignal;
|
|
260
|
+
}): Promise<Game>;
|
|
261
|
+
/**
|
|
262
|
+
* Returns available game pairs, optionally filtered by gameplay.
|
|
263
|
+
*
|
|
264
|
+
* When called with a valid JWT (authenticated builder), each entry includes
|
|
265
|
+
* `gameId` — use it directly as the MQTT topic `game/{gameId}/candle`.
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* const pairs = await client.game.availableGamePairs({ gameplayId: 'taptrading' });
|
|
269
|
+
* const game = await client.game.get(pairs[0].pair, { gameplaySlug: pairs[0].gameplayId });
|
|
270
|
+
* const ch = client.realtime?.subscribe(pairs[0].gameId ?? game.id, userId);
|
|
271
|
+
*/
|
|
272
|
+
availableGamePairs(opts?: {
|
|
273
|
+
gameplayId?: string;
|
|
274
|
+
signal?: AbortSignal;
|
|
275
|
+
}): Promise<GamePairInfo[]>;
|
|
276
|
+
chartHistory(gameId: string, limit?: number, opts?: {
|
|
277
|
+
signal?: AbortSignal;
|
|
278
|
+
}): Promise<Candle[]>;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
type LeaderboardPeriod = '24h' | '7d' | '30d' | 'all';
|
|
282
|
+
type LeaderboardSortBy = 'gain' | 'wins' | 'total_payout' | 'total_wagered' | 'total_bids';
|
|
283
|
+
interface LeaderboardEntry {
|
|
284
|
+
userId: string;
|
|
285
|
+
username: string;
|
|
286
|
+
rank: number;
|
|
287
|
+
totalBids: number;
|
|
288
|
+
totalWins: number;
|
|
289
|
+
totalWagered: string;
|
|
290
|
+
totalPayout: string;
|
|
291
|
+
gain: string;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
interface LeaderboardModuleDeps {
|
|
295
|
+
graphql: GraphQLTransport;
|
|
296
|
+
}
|
|
297
|
+
declare class LeaderboardModule {
|
|
298
|
+
#private;
|
|
299
|
+
constructor(deps: LeaderboardModuleDeps);
|
|
300
|
+
list(args: {
|
|
301
|
+
period: LeaderboardPeriod;
|
|
302
|
+
sortBy: LeaderboardSortBy;
|
|
303
|
+
signal?: AbortSignal;
|
|
304
|
+
}): Promise<LeaderboardEntry[]>;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Public-facing types for the Locale module.
|
|
309
|
+
*
|
|
310
|
+
* Backend contract: a server-side translations store (typically backed by a
|
|
311
|
+
* spreadsheet or CMS) served via GraphQL `Query.locales` with `version`-based
|
|
312
|
+
* not-modified semantics — the canonical hash of the canonical translation
|
|
313
|
+
* body is returned to the client, which echoes it back on the next request to
|
|
314
|
+
* short-circuit unchanged content.
|
|
315
|
+
*/
|
|
316
|
+
/** Flat translation map for a single language: key → value. */
|
|
317
|
+
type LocaleTranslations = Record<string, string>;
|
|
318
|
+
/**
|
|
319
|
+
* Public response shape from LocaleModule.get().
|
|
320
|
+
*
|
|
321
|
+
* - `translations` is null when `notModified` is true (caller keeps prior state).
|
|
322
|
+
* - `version` is a bare hex string (sha256 of canonical body). Pass back as
|
|
323
|
+
* `knownVersion` on next poll to short-circuit unchanged content.
|
|
324
|
+
*/
|
|
325
|
+
interface LocaleResponse {
|
|
326
|
+
lang: string;
|
|
327
|
+
version: string;
|
|
328
|
+
notModified: boolean;
|
|
329
|
+
translations: LocaleTranslations | null;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Public response shape from LocaleModule.refresh().
|
|
333
|
+
*
|
|
334
|
+
* - `refreshed` is the sorted list of language codes the backend repopulated.
|
|
335
|
+
* - `versions` maps each refreshed language to its new bare-hex version.
|
|
336
|
+
*/
|
|
337
|
+
interface LocaleRefreshResult {
|
|
338
|
+
refreshed: string[];
|
|
339
|
+
versions: Record<string, string>;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
interface LocaleModuleDeps {
|
|
343
|
+
graphql: GraphQLTransport;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* LocaleModule wraps the backend `Query.locales` + `Mutation.refreshLocales`
|
|
347
|
+
* GraphQL operations. Framework-agnostic — sdk-react (or any other consumer)
|
|
348
|
+
* decides what to render when calls succeed or fail.
|
|
349
|
+
*
|
|
350
|
+
* Caller passes the last-seen `version` as `knownVersion` to opt into
|
|
351
|
+
* not-modified short-circuit semantics; backend returns `notModified: true`
|
|
352
|
+
* with `translations: null` when the cached body still matches.
|
|
353
|
+
*/
|
|
354
|
+
declare class LocaleModule {
|
|
355
|
+
#private;
|
|
356
|
+
constructor(deps: LocaleModuleDeps);
|
|
357
|
+
/**
|
|
358
|
+
* Fetch translations for a single language.
|
|
359
|
+
*
|
|
360
|
+
* Pass the last-seen `version` as `knownVersion` to opt into not-modified
|
|
361
|
+
* short-circuit semantics: backend returns `{notModified: true, translations: null}`
|
|
362
|
+
* when the cached body still matches, and the caller keeps prior state.
|
|
363
|
+
*
|
|
364
|
+
* Errors surface as TaphubError subclasses with `extensions.code` codes from
|
|
365
|
+
* the backend (e.g. `LangNotSupported`, `InsufficientUpstream`, `FeatureDisabled`).
|
|
366
|
+
* Caller decides the fallback strategy.
|
|
367
|
+
*/
|
|
368
|
+
get(lang: string, knownVersion?: string, opts?: {
|
|
369
|
+
signal?: AbortSignal;
|
|
370
|
+
}): Promise<LocaleResponse>;
|
|
371
|
+
/**
|
|
372
|
+
* Trigger an admin refresh — backend re-pulls the source Sheet, invalidates
|
|
373
|
+
* its cache, and writes fresh entries for every supported language.
|
|
374
|
+
*
|
|
375
|
+
* Requires `X-API-Key` header equal to backend `InternalApiKey`. The transport
|
|
376
|
+
* layer is responsible for attaching the header; this method does not handle
|
|
377
|
+
* auth concerns directly.
|
|
378
|
+
*/
|
|
379
|
+
refresh(opts?: {
|
|
380
|
+
signal?: AbortSignal;
|
|
381
|
+
}): Promise<LocaleRefreshResult>;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
interface MqttWireCandle {
|
|
385
|
+
type: 'new' | 'update';
|
|
386
|
+
time: number;
|
|
387
|
+
o: number;
|
|
388
|
+
h: number;
|
|
389
|
+
l: number;
|
|
390
|
+
c: number;
|
|
391
|
+
volatility?: number;
|
|
392
|
+
coef_mults?: number[];
|
|
393
|
+
}
|
|
394
|
+
interface MqttWireAcceptedBid {
|
|
395
|
+
id: string;
|
|
396
|
+
userId: string;
|
|
397
|
+
gameUuid: string;
|
|
398
|
+
currency: string;
|
|
399
|
+
amount: string;
|
|
400
|
+
coefficient: number;
|
|
401
|
+
coordinates: {
|
|
402
|
+
time1: number;
|
|
403
|
+
time2: number;
|
|
404
|
+
price1: number;
|
|
405
|
+
price2: number;
|
|
406
|
+
};
|
|
407
|
+
createdAt: number;
|
|
408
|
+
}
|
|
409
|
+
interface MqttWireBidAccepted {
|
|
410
|
+
type: 'accepted';
|
|
411
|
+
bid: MqttWireAcceptedBid;
|
|
412
|
+
balance?: string;
|
|
413
|
+
}
|
|
414
|
+
interface MqttWireBidWon {
|
|
415
|
+
type: 'won';
|
|
416
|
+
bidId: string;
|
|
417
|
+
payout: string;
|
|
418
|
+
user_id: string;
|
|
419
|
+
balance?: string;
|
|
420
|
+
}
|
|
421
|
+
interface MqttWireBidLost {
|
|
422
|
+
type: 'lost';
|
|
423
|
+
bidId: string;
|
|
424
|
+
}
|
|
425
|
+
type MqttWireBidResult = MqttWireBidAccepted | MqttWireBidWon | MqttWireBidLost;
|
|
426
|
+
interface MqttWireBalanceUpdate {
|
|
427
|
+
type: 'balance_update';
|
|
428
|
+
userId: string;
|
|
429
|
+
balance: string;
|
|
430
|
+
}
|
|
431
|
+
interface MqttWireConfig {
|
|
432
|
+
min_bid_amount: number;
|
|
433
|
+
max_bid_amount: number;
|
|
434
|
+
acceptable_bids: number[];
|
|
435
|
+
}
|
|
436
|
+
type MqttWirePayload = MqttWireCandle | MqttWireBidResult | MqttWireBalanceUpdate | MqttWireConfig;
|
|
437
|
+
type MqttMessageHandler = (gameId: string, topic: string, payload: MqttWirePayload) => void;
|
|
438
|
+
type MqttErrorHandler = (err: Error) => void;
|
|
439
|
+
type MqttLifecycleEvent = {
|
|
440
|
+
kind: 'connect';
|
|
441
|
+
rttMs: number;
|
|
442
|
+
} | {
|
|
443
|
+
kind: 'reconnect';
|
|
444
|
+
rttMs: number;
|
|
445
|
+
} | {
|
|
446
|
+
kind: 'disconnect';
|
|
447
|
+
} | {
|
|
448
|
+
kind: 'error';
|
|
449
|
+
err: unknown;
|
|
450
|
+
} | {
|
|
451
|
+
kind: 'ping';
|
|
452
|
+
rttMs: number;
|
|
453
|
+
};
|
|
454
|
+
type MqttLifecycleHook = (event: MqttLifecycleEvent) => void;
|
|
455
|
+
interface MqttTransport {
|
|
456
|
+
/**
|
|
457
|
+
* Subscribe to a game's MQTT topics keyed by the tuple `(gameId, userId)`.
|
|
458
|
+
* When `userId` is nullish (including the empty string `''`), only
|
|
459
|
+
* game-scoped topics are subscribed and the entry is stored under the
|
|
460
|
+
* anonymous slot for that gameId. Two calls with the same `(gameId, userId)`
|
|
461
|
+
* tuple are idempotent; calls with the same `gameId` but a different
|
|
462
|
+
* `userId` create an independent entry with its own user-scoped topics.
|
|
463
|
+
*/
|
|
464
|
+
subscribe(gameId: string, userId: string | null | undefined, onMessage: MqttMessageHandler, onError: MqttErrorHandler): void;
|
|
465
|
+
/**
|
|
466
|
+
* Tear down one subscription entry for `gameId`. If `userId` is provided,
|
|
467
|
+
* removes the matching `(gameId, userId)` entry. If omitted and exactly
|
|
468
|
+
* one entry exists for `gameId`, removes that entry. If omitted and
|
|
469
|
+
* multiple entries exist for `gameId`, throws `TaphubError` with
|
|
470
|
+
* `code='AmbiguousUnsubscribe'`.
|
|
471
|
+
*/
|
|
472
|
+
unsubscribeAll(gameId: string, userId?: string | null): void;
|
|
473
|
+
close(): void;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Candle tick from MQTT.
|
|
478
|
+
* WARNING: `time` is Unix epoch MILLISECONDS.
|
|
479
|
+
* This differs from GQL chartHistory candles where `time` is seconds.
|
|
480
|
+
* Do NOT multiply MQTT candle time by 1000.
|
|
481
|
+
*/
|
|
482
|
+
interface MqttCandleEvent {
|
|
483
|
+
type: 'new' | 'update';
|
|
484
|
+
/** Unix epoch **milliseconds** — differs from GQL chartHistory (seconds) */
|
|
485
|
+
time: number;
|
|
486
|
+
o: number;
|
|
487
|
+
h: number;
|
|
488
|
+
l: number;
|
|
489
|
+
c: number;
|
|
490
|
+
volatility?: number;
|
|
491
|
+
coefMults?: number[];
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Bid object emitted on `bidAccepted` events. Shape mirrors the grid-api
|
|
495
|
+
* MQTT wire shape verbatim — NOT the same as the `Bid` returned by `myBids`
|
|
496
|
+
* GraphQL. `coordinates` is nested; `coefficient` / `price1` / `price2` are
|
|
497
|
+
* numeric floats; `createdAt` is unix epoch milliseconds.
|
|
498
|
+
*/
|
|
499
|
+
interface MqttAcceptedBid {
|
|
500
|
+
id: string;
|
|
501
|
+
userId: string;
|
|
502
|
+
gameUuid: string;
|
|
503
|
+
currency: string;
|
|
504
|
+
amount: string;
|
|
505
|
+
coefficient: number;
|
|
506
|
+
coordinates: {
|
|
507
|
+
time1: number;
|
|
508
|
+
time2: number;
|
|
509
|
+
price1: number;
|
|
510
|
+
price2: number;
|
|
511
|
+
};
|
|
512
|
+
/** Unix epoch milliseconds */
|
|
513
|
+
createdAt: number;
|
|
514
|
+
}
|
|
515
|
+
interface MqttBidAcceptedEvent {
|
|
516
|
+
bid: MqttAcceptedBid;
|
|
517
|
+
balance?: string;
|
|
518
|
+
}
|
|
519
|
+
interface MqttBidWonEvent {
|
|
520
|
+
bidId: string;
|
|
521
|
+
payout: string;
|
|
522
|
+
userId: string;
|
|
523
|
+
balance?: string;
|
|
524
|
+
}
|
|
525
|
+
interface MqttBidLostEvent {
|
|
526
|
+
bidId: string;
|
|
527
|
+
}
|
|
528
|
+
interface MqttBidCancelledEvent {
|
|
529
|
+
bidId: string;
|
|
530
|
+
}
|
|
531
|
+
interface MqttBalanceEvent {
|
|
532
|
+
userId: string;
|
|
533
|
+
balance: string;
|
|
534
|
+
}
|
|
535
|
+
interface MqttConfigEvent {
|
|
536
|
+
minBidAmount: number;
|
|
537
|
+
maxBidAmount: number;
|
|
538
|
+
acceptableBids: number[];
|
|
539
|
+
}
|
|
540
|
+
interface MqttIdealConfigEvent {
|
|
541
|
+
cellSizeValue: number;
|
|
542
|
+
currentPrice: number;
|
|
543
|
+
reason: string;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
interface GameChannelEvents {
|
|
547
|
+
candle: [MqttCandleEvent];
|
|
548
|
+
bidAccepted: [MqttBidAcceptedEvent];
|
|
549
|
+
bidWon: [MqttBidWonEvent];
|
|
550
|
+
bidLost: [MqttBidLostEvent];
|
|
551
|
+
bidCancelled: [MqttBidCancelledEvent];
|
|
552
|
+
balanceUpdate: [MqttBalanceEvent];
|
|
553
|
+
configUpdate: [MqttConfigEvent];
|
|
554
|
+
idealConfigUpdate: [MqttIdealConfigEvent];
|
|
555
|
+
error: [Error];
|
|
556
|
+
}
|
|
557
|
+
declare class GameChannel extends EventEmitter<GameChannelEvents> {
|
|
558
|
+
readonly gameId: string;
|
|
559
|
+
constructor(gameId: string);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
interface RealtimeModuleOptions {
|
|
563
|
+
mqttEndpoint: string;
|
|
564
|
+
/** @internal For testing only */
|
|
565
|
+
transport?: MqttTransport;
|
|
566
|
+
/**
|
|
567
|
+
* Optional MQTT lifecycle hook. When provided, fires on connect /
|
|
568
|
+
* reconnect / disconnect / error so the network-quality monitor (or
|
|
569
|
+
* any other observer) can track stability.
|
|
570
|
+
*/
|
|
571
|
+
onMqttLifecycle?: MqttLifecycleHook;
|
|
572
|
+
}
|
|
573
|
+
interface RealtimeModuleEvents {
|
|
574
|
+
error: [Error];
|
|
575
|
+
}
|
|
576
|
+
declare class RealtimeModule extends EventEmitter<RealtimeModuleEvents> {
|
|
577
|
+
#private;
|
|
578
|
+
constructor(mqttEndpointOrOptions: string | RealtimeModuleOptions);
|
|
579
|
+
/** @internal Access transport for testing */
|
|
580
|
+
get _transport(): MqttTransport;
|
|
581
|
+
subscribe(gameId: string, userId?: string | null): GameChannel;
|
|
582
|
+
unsubscribe(gameId: string, userId?: string | null): void;
|
|
583
|
+
disconnect(): void;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
interface Me {
|
|
587
|
+
id: string;
|
|
588
|
+
username: string;
|
|
589
|
+
walletAddress: string | null;
|
|
590
|
+
balance: string;
|
|
591
|
+
isDemo: boolean;
|
|
592
|
+
}
|
|
593
|
+
interface Currency {
|
|
594
|
+
code: string;
|
|
595
|
+
unit: string;
|
|
596
|
+
unitSymbol: string;
|
|
597
|
+
}
|
|
598
|
+
interface Wallet {
|
|
599
|
+
id: string;
|
|
600
|
+
amount: string;
|
|
601
|
+
currency: string;
|
|
602
|
+
isEnable: boolean;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
interface UserModuleDeps {
|
|
606
|
+
rest: RestTransport;
|
|
607
|
+
graphql: GraphQLTransport;
|
|
608
|
+
graphqlUser: GraphQLTransport;
|
|
609
|
+
}
|
|
610
|
+
interface RequestOpts {
|
|
611
|
+
signal?: AbortSignal;
|
|
612
|
+
}
|
|
613
|
+
declare class UserModule {
|
|
614
|
+
#private;
|
|
615
|
+
constructor(deps: UserModuleDeps);
|
|
616
|
+
me(): Promise<Me>;
|
|
617
|
+
wallets(opts?: RequestOpts): Promise<Wallet[]>;
|
|
618
|
+
walletByCurrency(currency: string, opts?: RequestOpts): Promise<Wallet>;
|
|
619
|
+
get currencies(): Currency[] | null;
|
|
620
|
+
refreshCurrencies(): Promise<Currency[]>;
|
|
621
|
+
clearCurrencies(): void;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
type NetworkChangeEvent = {
|
|
625
|
+
previous: NetworkQuality;
|
|
626
|
+
current: NetworkQuality;
|
|
627
|
+
};
|
|
628
|
+
type EventMapWithNetwork = Record<string, unknown> & {
|
|
629
|
+
'network:change': NetworkChangeEvent;
|
|
630
|
+
};
|
|
631
|
+
type NetworkQualityListener = (event: NetworkChangeEvent) => void;
|
|
632
|
+
type ConnectionSeed = {
|
|
633
|
+
rtt?: number;
|
|
634
|
+
effectiveType?: 'slow-2g' | '2g' | '3g' | '4g' | string;
|
|
635
|
+
};
|
|
636
|
+
declare class NetworkQualityMonitor {
|
|
637
|
+
private readonly bus;
|
|
638
|
+
private readonly buckets;
|
|
639
|
+
private readonly smoother;
|
|
640
|
+
private mqttConnected;
|
|
641
|
+
private mqttDisconnectedAt;
|
|
642
|
+
private lastSuccessfulHttpAt;
|
|
643
|
+
private committedNetwork;
|
|
644
|
+
private committedBackend;
|
|
645
|
+
private tickHandle;
|
|
646
|
+
private onlineListener;
|
|
647
|
+
private offlineListener;
|
|
648
|
+
private disposed;
|
|
649
|
+
constructor(opts: {
|
|
650
|
+
bus: TaphubEventBus<EventMapWithNetwork>;
|
|
651
|
+
});
|
|
652
|
+
addSample(sample: Sample, prevMqttConnectedOverride?: boolean): void;
|
|
653
|
+
seedFromConnection(seed: ConnectionSeed): void;
|
|
654
|
+
notifyMqttConnect(rttMs: number): void;
|
|
655
|
+
notifyMqttDisconnect(): void;
|
|
656
|
+
notifyMqttPing(rttMs: number): void;
|
|
657
|
+
subscribe(listener: NetworkQualityListener): () => void;
|
|
658
|
+
getCurrent(): NetworkQuality;
|
|
659
|
+
reset(): void;
|
|
660
|
+
tick(): void;
|
|
661
|
+
dispose(): void;
|
|
662
|
+
private resolveSeedRtt;
|
|
663
|
+
private startTick;
|
|
664
|
+
private attachBrowserListeners;
|
|
665
|
+
private detachBrowserListeners;
|
|
666
|
+
private pruneWindow;
|
|
667
|
+
private recompute;
|
|
668
|
+
private deriveMetrics;
|
|
669
|
+
private deriveMetricsFrom;
|
|
670
|
+
private isHardOffline;
|
|
671
|
+
private allHttpAndMqttSamples;
|
|
672
|
+
private snapshot;
|
|
673
|
+
private now;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
declare class TaphubClient {
|
|
677
|
+
#private;
|
|
678
|
+
readonly agencyId: string;
|
|
679
|
+
readonly endpoint: string;
|
|
680
|
+
readonly storage: TaphubStorageAdapter;
|
|
681
|
+
/** @readonly User module — reassignment has no effect at runtime. */
|
|
682
|
+
user: UserModule;
|
|
683
|
+
/** @readonly Auth module — reassignment has no effect at runtime. */
|
|
684
|
+
auth: AuthModule;
|
|
685
|
+
/** @readonly Game module — reassignment has no effect at runtime. */
|
|
686
|
+
game: GameModule;
|
|
687
|
+
/** @readonly Bid module — reassignment has no effect at runtime. */
|
|
688
|
+
bid: BidModule;
|
|
689
|
+
/** @readonly Leaderboard module — reassignment has no effect at runtime. */
|
|
690
|
+
leaderboard: LeaderboardModule;
|
|
691
|
+
/** @readonly Locale module — reassignment has no effect at runtime. */
|
|
692
|
+
locale: LocaleModule;
|
|
693
|
+
/** @readonly Realtime module — undefined when mqttEndpoint not configured. */
|
|
694
|
+
realtime: RealtimeModule | undefined;
|
|
695
|
+
/** @readonly Network quality monitor — always present (operates passively). */
|
|
696
|
+
network: NetworkQualityMonitor;
|
|
697
|
+
private bus;
|
|
698
|
+
constructor(config: TaphubClientConfig);
|
|
699
|
+
getToken(): string | null;
|
|
700
|
+
setToken(token: string | null, opts?: {
|
|
701
|
+
isDemo?: boolean;
|
|
702
|
+
}): void;
|
|
703
|
+
isDemo(): boolean;
|
|
704
|
+
/** @internal Used by module integrations to access the REST transport. Not part of the public API. */
|
|
705
|
+
get _rest(): RestTransport;
|
|
706
|
+
/** @internal Used by module integrations to access the GraphQL transport. Not part of the public API. */
|
|
707
|
+
get _graphql(): GraphQLTransport;
|
|
708
|
+
on<K extends keyof TaphubEventMap & string>(event: K, handler: (payload: TaphubEventMap[K]) => void): void;
|
|
709
|
+
off<K extends keyof TaphubEventMap & string>(event: K, handler: (payload: TaphubEventMap[K]) => void): void;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
declare class TaphubError extends Error {
|
|
713
|
+
readonly code: string;
|
|
714
|
+
readonly details?: unknown;
|
|
715
|
+
constructor(message: string, options: {
|
|
716
|
+
code: string;
|
|
717
|
+
details?: unknown;
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
declare class TaphubAuthError extends TaphubError {
|
|
721
|
+
constructor(message: string, options: {
|
|
722
|
+
code: string;
|
|
723
|
+
details?: unknown;
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
declare class TaphubNetworkError extends TaphubError {
|
|
727
|
+
constructor(message: string, options: {
|
|
728
|
+
code: string;
|
|
729
|
+
details?: unknown;
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
declare class TaphubValidationError extends TaphubError {
|
|
733
|
+
constructor(message: string, options: {
|
|
734
|
+
code: string;
|
|
735
|
+
details?: unknown;
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
declare class TaphubServerError extends TaphubError {
|
|
739
|
+
constructor(message: string, options: {
|
|
740
|
+
code: string;
|
|
741
|
+
details?: unknown;
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Thrown when grid-api rejects a bid because the requested coefficient
|
|
746
|
+
* does not match the server-enforced value.
|
|
747
|
+
*
|
|
748
|
+
* Recommended catch pattern:
|
|
749
|
+
* ```ts
|
|
750
|
+
* try { await client.bid.placeBid(input); }
|
|
751
|
+
* catch (e) {
|
|
752
|
+
* if (e instanceof TaphubSlippageError) {
|
|
753
|
+
* showSlippageBanner(e.clientCoef, e.serverCoef);
|
|
754
|
+
* } else if (e instanceof TaphubValidationError) {
|
|
755
|
+
* showGenericError(e.code);
|
|
756
|
+
* }
|
|
757
|
+
* }
|
|
758
|
+
* ```
|
|
759
|
+
*/
|
|
760
|
+
declare class TaphubSlippageError extends TaphubValidationError {
|
|
761
|
+
readonly clientCoef: number;
|
|
762
|
+
readonly serverCoef: number;
|
|
763
|
+
readonly slippage: number;
|
|
764
|
+
constructor(message: string, options: {
|
|
765
|
+
code: string;
|
|
766
|
+
clientCoef: number;
|
|
767
|
+
serverCoef: number;
|
|
768
|
+
slippage: number;
|
|
769
|
+
details?: unknown;
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
declare function errorFunction(x: number): number;
|
|
774
|
+
declare function normalCDF(x: number): number;
|
|
775
|
+
declare function normalPDF(x: number): number;
|
|
776
|
+
declare function adaptiveSimpson(f: (x: number) => number, a: number, b: number, tolerance?: number, maxDepth?: number): number;
|
|
777
|
+
declare function calculateProbWin(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
778
|
+
declare function calculateProbWin_v2(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
779
|
+
|
|
780
|
+
export { AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, type CancelBidResult, type Candle, type Constraints, type Currency, type Game, GameChannel, type GameConfig, GameModule, type GamePairInfo, type GridConfig, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, type MqttAcceptedBid, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type PlaceBidInput, RealtimeModule, type Sample, type SampleReason, type SignalSource, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserModule, type Wallet as UserWallet, type Wallet$1 as Wallet, adaptiveSimpson, autoDetectStorage, calculateProbWin, calculateProbWin_v2, errorFunction, isCancelled, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF };
|