@taphubhq/sdk-core 0.25.5 → 0.26.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/README.md +351 -9
- package/dist/index.cjs +686 -31
- package/dist/index.d.mts +428 -12
- package/dist/index.d.ts +428 -12
- package/dist/index.js +674 -30
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
import EventEmitter from 'eventemitter3';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Home-region → API base-URL resolution (multi-geo, design D4: domain-per-region).
|
|
5
|
+
*
|
|
6
|
+
* The region → domain map is caller-supplied config (see
|
|
7
|
+
* {@link TaphubClientConfig.regionDomains}) — hosts are NEVER hardcoded here. An
|
|
8
|
+
* unknown or missing region value degrades to the default region rather than
|
|
9
|
+
* crashing the client (defensive client coding): a server may one day return a
|
|
10
|
+
* region a shipped build does not yet recognise, and that must not break the app.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Region set for Phase 1 (design D4: `sg`, `eu`, `jp`). Kept as a `const` tuple so
|
|
14
|
+
* {@link KnownRegion} is derived from it and the two never drift apart.
|
|
15
|
+
*/
|
|
16
|
+
declare const KNOWN_REGIONS: readonly ["sg", "eu", "jp"];
|
|
17
|
+
type KnownRegion = (typeof KNOWN_REGIONS)[number];
|
|
18
|
+
/** Default home region (design/tasks: default `sg`) — also the enum-fallback target. */
|
|
19
|
+
declare const DEFAULT_REGION: KnownRegion;
|
|
20
|
+
/**
|
|
21
|
+
* Region → API base URL. Config-driven; supplied by the host app from env, never
|
|
22
|
+
* hardcoded. Keyed by region string so an unknown key is simply absent (and
|
|
23
|
+
* resolution falls back), rather than a type error.
|
|
24
|
+
*/
|
|
25
|
+
type RegionDomainMap = Partial<Record<string, string>>;
|
|
26
|
+
/** Narrowing guard: is `region` one of the build's known regions? */
|
|
27
|
+
declare function isKnownRegion(region: string | null | undefined): region is KnownRegion;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the API base URL for a home region.
|
|
30
|
+
*
|
|
31
|
+
* - **Absent region** (`null`/`undefined`/`''`) → `fallbackBaseUrl` unchanged. A
|
|
32
|
+
* login response that carries no region keeps the client on its construction-time
|
|
33
|
+
* endpoint (additive / backward-compatible).
|
|
34
|
+
* - **Known region** present in `domains` → that region's domain.
|
|
35
|
+
* - **Unknown non-empty region** value → the default region's domain (enum-fallback,
|
|
36
|
+
* defensive client coding — a server-sent region a shipped build does not
|
|
37
|
+
* recognise must not crash it).
|
|
38
|
+
* - Known region absent from the map → the default region's domain when configured,
|
|
39
|
+
* otherwise `fallbackBaseUrl`.
|
|
40
|
+
*
|
|
41
|
+
* Never throws. When `domains` is undefined/empty (the pre-multi-geo default) it
|
|
42
|
+
* always returns `fallbackBaseUrl`, so a client with no region config behaves
|
|
43
|
+
* exactly as before.
|
|
44
|
+
*/
|
|
45
|
+
declare function resolveRegionBaseUrl(region: string | null | undefined, domains: RegionDomainMap | undefined, fallbackBaseUrl: string): string;
|
|
46
|
+
|
|
3
47
|
interface TaphubStorageAdapter {
|
|
4
48
|
get(key: string): string | null;
|
|
5
49
|
set(key: string, value: string): void;
|
|
@@ -28,10 +72,27 @@ interface TaphubClientConfig {
|
|
|
28
72
|
agencyId: string;
|
|
29
73
|
endpoint: string;
|
|
30
74
|
mqttEndpoint?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Multi-geo (design D4): region → MQTT broker endpoint. Same shape/semantics as
|
|
77
|
+
* {@link regionDomains} but for the realtime (MQTT) connection. When present, the
|
|
78
|
+
* realtime module targets the endpoint resolved from the user's home region; a
|
|
79
|
+
* login that changes the region reconnects to the new region's broker. When
|
|
80
|
+
* omitted, the single `mqttEndpoint` is used for every region — i.e. the
|
|
81
|
+
* pre-multi-geo behaviour is unchanged.
|
|
82
|
+
*/
|
|
83
|
+
regionMqttEndpoints?: RegionDomainMap;
|
|
31
84
|
/** Optional MQTT broker credentials. Ignored when `mqttEndpoint` is absent. */
|
|
32
85
|
mqttAuth?: MqttAuthConfig;
|
|
33
86
|
storage?: TaphubStorageAdapter;
|
|
34
87
|
fetch?: typeof globalThis.fetch;
|
|
88
|
+
/**
|
|
89
|
+
* Multi-geo (design D4): region → API base URL. Supplied by the host app from
|
|
90
|
+
* env (never hardcoded hosts). When present, once login resolves the user's home
|
|
91
|
+
* region, subsequent GraphQL/REST calls target that region's domain; an unknown
|
|
92
|
+
* region falls back to the default region. When omitted, the client always uses
|
|
93
|
+
* `endpoint` — i.e. the pre-multi-geo behaviour is unchanged.
|
|
94
|
+
*/
|
|
95
|
+
regionDomains?: RegionDomainMap;
|
|
35
96
|
}
|
|
36
97
|
|
|
37
98
|
type SignalSource = 'graphql' | 'rest' | 'mqtt' | 'browser' | 'connection';
|
|
@@ -155,16 +216,64 @@ interface LoginResult {
|
|
|
155
216
|
accessToken: string;
|
|
156
217
|
user: User;
|
|
157
218
|
isDemo: boolean;
|
|
219
|
+
/**
|
|
220
|
+
* The user's home region (multi-geo, design D4/R6), when the login response
|
|
221
|
+
* carries it. Optional and additive: a BE that does not yet return a region
|
|
222
|
+
* leaves this `undefined`, and the client keeps using its default base URL.
|
|
223
|
+
*/
|
|
224
|
+
homeRegion?: string;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Result of a successful {@link AuthModule.refreshTapHubToken}: the rotated token
|
|
228
|
+
* pair. The rotation contract requires presenting the NEW refresh token on the
|
|
229
|
+
* next refresh, so callers must persist both. Demo users never reach this path —
|
|
230
|
+
* `refreshTapHubToken` rejects with `Refresh_DemoNotSupported` for them.
|
|
231
|
+
*/
|
|
232
|
+
interface RefreshTokenResult {
|
|
233
|
+
taphubToken: string;
|
|
234
|
+
refreshToken: string;
|
|
158
235
|
}
|
|
159
236
|
|
|
160
237
|
interface AuthModuleDeps {
|
|
161
238
|
rest: RestTransport;
|
|
162
239
|
graphql: GraphQLTransport;
|
|
163
240
|
graphqlUser: GraphQLTransport;
|
|
241
|
+
/**
|
|
242
|
+
* Region-routed, Auth_WrongRegion-retrying variant of `graphqlUser`, used by
|
|
243
|
+
* {@link AuthModule.refreshTapHubToken}. Login keeps using the raw `graphqlUser`
|
|
244
|
+
* because it runs its OWN wrong-region retry (`loginWithSession`); wiring the
|
|
245
|
+
* wrapped transport there would double-retry. Optional: falls back to
|
|
246
|
+
* `graphqlUser` (unwrapped) when not provided.
|
|
247
|
+
*/
|
|
248
|
+
graphqlUserData?: GraphQLTransport;
|
|
164
249
|
setToken: (token: string | null, opts?: {
|
|
165
250
|
isDemo?: boolean;
|
|
251
|
+
homeRegion?: string;
|
|
166
252
|
}) => void;
|
|
253
|
+
/**
|
|
254
|
+
* Whether the current session is a demo user. Demo users have no refresh path
|
|
255
|
+
* (see design D2/D4/D7), so {@link AuthModule.refreshTapHubToken} short-circuits
|
|
256
|
+
* for them instead of hitting the network. Optional: defaults to a non-demo
|
|
257
|
+
* session (`() => false`).
|
|
258
|
+
*/
|
|
259
|
+
isDemo?: () => boolean;
|
|
167
260
|
agencyId: string;
|
|
261
|
+
/**
|
|
262
|
+
* Multi-geo: the client's CURRENT region (e.g. persisted from a previous login),
|
|
263
|
+
* sent with the login request. First join → the server registers it as the home
|
|
264
|
+
* region; existing user → the stored home region wins. Optional (single-region
|
|
265
|
+
* clients never set a region and the server falls back to its default).
|
|
266
|
+
*/
|
|
267
|
+
getRegion?: () => string | null;
|
|
268
|
+
/**
|
|
269
|
+
* Multi-geo: switch the client's home region (and persist it). Called by the
|
|
270
|
+
* silent Auth_WrongRegion redirect — the server rejected a login on the wrong
|
|
271
|
+
* cluster and told us the user's home region, so we set it here and retry the
|
|
272
|
+
* SAME login once (the user-service base-URL thunk then re-resolves to the home
|
|
273
|
+
* region's domain). Kept distinct from `setToken` so a redirect never touches
|
|
274
|
+
* the token/isDemo state. Optional: single-region clients never wire it.
|
|
275
|
+
*/
|
|
276
|
+
setRegion?: (region: string) => void;
|
|
168
277
|
onLoginSuccess?: () => Promise<void>;
|
|
169
278
|
onLogout?: () => void | Promise<void>;
|
|
170
279
|
}
|
|
@@ -180,7 +289,32 @@ declare class AuthModule {
|
|
|
180
289
|
}): Promise<LoginResult>;
|
|
181
290
|
loginWithSession(sessionToken: string, opts?: {
|
|
182
291
|
signal?: AbortSignal;
|
|
292
|
+
region?: string;
|
|
183
293
|
}): Promise<LoginResult>;
|
|
294
|
+
/**
|
|
295
|
+
* Exchange a refresh token for a rotated (taphubToken, refreshToken) pair via
|
|
296
|
+
* the user-service (region-routed through `graphqlUserData`, so a user pinned
|
|
297
|
+
* to a non-default region refreshes against THEIR cluster — the bug this change
|
|
298
|
+
* fixes). On success the SDK's own token is updated and the rotated pair is
|
|
299
|
+
* returned so the caller can persist it (rotation contract: present the NEW
|
|
300
|
+
* refresh token next time).
|
|
301
|
+
*
|
|
302
|
+
* - **Demo-skips-refresh:** demo users have no refresh path and reject with
|
|
303
|
+
* `Refresh_DemoNotSupported` (no network call). The caller reacts by clearing
|
|
304
|
+
* auth / re-creating the demo user.
|
|
305
|
+
* - **Single-flight:** concurrent callers share one in-flight request, keyed by
|
|
306
|
+
* the current region. A region change mid-flight re-targets the next caller.
|
|
307
|
+
* - **Wrong-region backstop:** `graphqlUserData` retries once on
|
|
308
|
+
* `Auth_WrongRegion`, so a stale region self-corrects.
|
|
309
|
+
*
|
|
310
|
+
* Invalid/expired/reused refresh tokens surface as the transport's typed error
|
|
311
|
+
* (e.g. `ErrRefreshTokenInvalid`); network failures surface as
|
|
312
|
+
* `TaphubNetworkError`. The caller decides how to react (clear vs retry-later).
|
|
313
|
+
*/
|
|
314
|
+
refreshTapHubToken(refreshToken: string, opts?: {
|
|
315
|
+
clientMeta?: string;
|
|
316
|
+
signal?: AbortSignal;
|
|
317
|
+
}): Promise<RefreshTokenResult>;
|
|
184
318
|
logout(): Promise<void>;
|
|
185
319
|
}
|
|
186
320
|
|
|
@@ -691,21 +825,39 @@ interface Constraints {
|
|
|
691
825
|
maxCoef: number;
|
|
692
826
|
}
|
|
693
827
|
interface PairInfo {
|
|
694
|
-
/**
|
|
828
|
+
/**
|
|
829
|
+
* game_pairs.id — the catalog identifier for this pair, always the bare
|
|
830
|
+
* slash-free form (e.g. `"grid-ETH-USD"`), never the agency composite.
|
|
831
|
+
*
|
|
832
|
+
* The server returns `"{agencyId}:{pairId}"` here for callers with an agency
|
|
833
|
+
* JWT; the SDK strips that prefix so this value is identical across auth
|
|
834
|
+
* modes and can be passed straight back as a `pairId` argument. The composite
|
|
835
|
+
* is preserved in `agencyPairId`.
|
|
836
|
+
*/
|
|
695
837
|
id: string;
|
|
696
838
|
/** Trading pair symbol, e.g. "BTC/USD". */
|
|
697
839
|
pair: string;
|
|
698
|
-
/**
|
|
840
|
+
/**
|
|
841
|
+
* ID of the gameplay this pair belongs to.
|
|
842
|
+
* @deprecated grid-api retired this field from `type Pair`; it is always `''`.
|
|
843
|
+
*/
|
|
699
844
|
gameplayId: string;
|
|
700
|
-
/**
|
|
845
|
+
/**
|
|
846
|
+
* Human-readable gameplay name.
|
|
847
|
+
* @deprecated grid-api retired this field from `type Pair`; it is always `''`.
|
|
848
|
+
*/
|
|
701
849
|
gameplayName: string;
|
|
702
850
|
/** Price feed source, e.g. "binance". */
|
|
703
851
|
source: string;
|
|
704
852
|
/**
|
|
705
|
-
* agency_game_pairs.id — the runtime game ID for this agency
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
853
|
+
* agency_game_pairs.id — the runtime game ID for this agency, shaped
|
|
854
|
+
* `"{agencyId}:{pairId}"`. Use directly as `gameId` in MQTT topic
|
|
855
|
+
* `game/{gameId}/candle`.
|
|
856
|
+
*
|
|
857
|
+
* grid-api retired the dedicated wire field and now returns this value as
|
|
858
|
+
* `Pair.id` instead, but only for callers with an agency JWT. Anonymous
|
|
859
|
+
* callers receive the bare catalog id in `id`, and this property is `null` —
|
|
860
|
+
* do not substitute `id` for it, that id has no MQTT topic.
|
|
709
861
|
*/
|
|
710
862
|
agencyPairId: string | null;
|
|
711
863
|
}
|
|
@@ -740,8 +892,11 @@ declare class PairModule {
|
|
|
740
892
|
/**
|
|
741
893
|
* Returns available game pairs, optionally filtered by gameplay.
|
|
742
894
|
*
|
|
743
|
-
*
|
|
744
|
-
* `
|
|
895
|
+
* Each entry's `id` is the bare catalog pair id, safe to pass straight back
|
|
896
|
+
* as a `pairId` argument. When called with a valid JWT (authenticated
|
|
897
|
+
* builder), `agencyPairId` additionally carries the agency composite — use
|
|
898
|
+
* THAT one as the MQTT topic `game/{gameId}/candle`. It is `null` for
|
|
899
|
+
* anonymous callers.
|
|
745
900
|
*
|
|
746
901
|
* @example
|
|
747
902
|
* const pairs = await client.pair.availableGamePairs({ gameplayId: 'taptrading' });
|
|
@@ -834,7 +989,20 @@ interface MqttWireWalletBalance {
|
|
|
834
989
|
currency: string;
|
|
835
990
|
reason: string;
|
|
836
991
|
}
|
|
837
|
-
|
|
992
|
+
/**
|
|
993
|
+
* Wire payload on the user-scoped migration topic `user/{userId}/migration`,
|
|
994
|
+
* published by `taphub-user-service` when a home-region migration completes
|
|
995
|
+
* (ux-260730-migration-complete-notify). camelCase on the wire; `completedAt`
|
|
996
|
+
* is unix epoch SECONDS.
|
|
997
|
+
*/
|
|
998
|
+
interface MqttWireMigrationCompleted {
|
|
999
|
+
type: 'migration_completed';
|
|
1000
|
+
migrationId: string;
|
|
1001
|
+
fromRegion: string;
|
|
1002
|
+
toRegion: string;
|
|
1003
|
+
completedAt: number;
|
|
1004
|
+
}
|
|
1005
|
+
type MqttWirePayload = MqttWireCandle | MqttWireBidResult | MqttWireBalanceUpdate | MqttWireConfig | MqttWireAgencyPairStats | MqttWireWalletBalance | MqttWireMigrationCompleted;
|
|
838
1006
|
type MqttMessageHandler = (gameId: string, topic: string, payload: MqttWirePayload) => void;
|
|
839
1007
|
type MqttErrorHandler = (err: Error) => void;
|
|
840
1008
|
type MqttLifecycleEvent = {
|
|
@@ -857,6 +1025,7 @@ type MqttCandleHandler = (topic: string, payload: MqttWirePayload) => void;
|
|
|
857
1025
|
type MqttAgencyPairStatsHandler = (topic: string, payload: MqttWirePayload) => void;
|
|
858
1026
|
type MqttWalletMessageHandler = (topic: string, payload: MqttWirePayload) => void;
|
|
859
1027
|
type MqttUserBidsMessageHandler = (topic: string, payload: MqttWirePayload) => void;
|
|
1028
|
+
type MqttMigrationMessageHandler = (topic: string, payload: MqttWirePayload) => void;
|
|
860
1029
|
interface MqttTransport {
|
|
861
1030
|
/**
|
|
862
1031
|
* Subscribe to a game's MQTT topics (config, ideal_config, bid_result, balance)
|
|
@@ -910,6 +1079,25 @@ interface MqttTransport {
|
|
|
910
1079
|
subscribeUserBids(userId: string, onMessage: MqttUserBidsMessageHandler, onError: MqttErrorHandler): void;
|
|
911
1080
|
/** Tear down the cross-pair user-bids subscription for a `userId`. No-op if absent. */
|
|
912
1081
|
unsubscribeUserBids(userId: string): void;
|
|
1082
|
+
/**
|
|
1083
|
+
* Subscribe to the user-scoped migration completion stream, topic
|
|
1084
|
+
* `user/{userId}/migration` (ux-260730). Independent of any game subscription —
|
|
1085
|
+
* NOT torn down by `unsubscribeAll`. Idempotent per `userId`. Subscribed at
|
|
1086
|
+
* QoS 1 to match the publisher (redelivery on reconnect beats silent loss);
|
|
1087
|
+
* the publisher also retains the message so a late subscriber still sees it.
|
|
1088
|
+
*/
|
|
1089
|
+
subscribeMigration(userId: string, onMessage: MqttMigrationMessageHandler, onError: MqttErrorHandler): void;
|
|
1090
|
+
/** Tear down the migration subscription for a `userId`. No-op if absent. */
|
|
1091
|
+
unsubscribeMigration(userId: string): void;
|
|
1092
|
+
/**
|
|
1093
|
+
* Reconnect the underlying MQTT socket, re-resolving the endpoint (multi-geo:
|
|
1094
|
+
* the endpoint may be a thunk that now points at a different region's broker).
|
|
1095
|
+
* All tracked subscriptions are preserved and re-declared on the fresh
|
|
1096
|
+
* connection's `connect` event. No-op when not currently connected — the next
|
|
1097
|
+
* `ensureConnected` (triggered by any subscribe) already resolves the fresh
|
|
1098
|
+
* endpoint.
|
|
1099
|
+
*/
|
|
1100
|
+
reconnect(): void;
|
|
913
1101
|
close(): void;
|
|
914
1102
|
}
|
|
915
1103
|
|
|
@@ -1045,6 +1233,22 @@ interface MqttIdealConfigEvent {
|
|
|
1045
1233
|
currentPrice: number;
|
|
1046
1234
|
reason: string;
|
|
1047
1235
|
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Home-region migration completion event from the user-scoped MQTT topic
|
|
1238
|
+
* `user/{userId}/migration` (ux-260730-migration-complete-notify). Published by
|
|
1239
|
+
* taphub-user-service (QoS 1, retained with TTL) when the migration saga reaches
|
|
1240
|
+
* COMPLETED — by then the user's home region already points at `toRegion`.
|
|
1241
|
+
* Consumers match `toRegion` against their pending migration target and then
|
|
1242
|
+
* complete the cutover via `TaphubClient.completeRegionMigration(toRegion)`.
|
|
1243
|
+
*/
|
|
1244
|
+
interface MigrationCompletedEvent {
|
|
1245
|
+
type: 'migration_completed';
|
|
1246
|
+
migrationId: string;
|
|
1247
|
+
fromRegion: string;
|
|
1248
|
+
toRegion: string;
|
|
1249
|
+
/** Unix epoch **seconds**. */
|
|
1250
|
+
completedAt: number;
|
|
1251
|
+
}
|
|
1048
1252
|
/**
|
|
1049
1253
|
* Live per-pair stats tick from the public stream
|
|
1050
1254
|
* `public/agency/{aid}/pair/{pairId}/stats`. The backend suppresses
|
|
@@ -1075,6 +1279,22 @@ declare class GameChannel extends EventEmitter<GameChannelEvents> {
|
|
|
1075
1279
|
constructor(gameId: string);
|
|
1076
1280
|
}
|
|
1077
1281
|
|
|
1282
|
+
interface MigrationChannelEvents {
|
|
1283
|
+
migrationCompleted: [MigrationCompletedEvent];
|
|
1284
|
+
error: [Error];
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* User-scoped migration channel (ux-260730). Emits `migrationCompleted` when the
|
|
1288
|
+
* user's home-region migration finishes, from `user/{userId}/migration`. Mirror
|
|
1289
|
+
* of {@link WalletChannel}: keyed by `userId`, independent of any game
|
|
1290
|
+
* subscription. Intended to be held only while the client is in the migrating
|
|
1291
|
+
* state (subscribe on trigger/resume, unsubscribe on completion/logout).
|
|
1292
|
+
*/
|
|
1293
|
+
declare class MigrationChannel extends EventEmitter<MigrationChannelEvents> {
|
|
1294
|
+
readonly userId: string;
|
|
1295
|
+
constructor(userId: string);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1078
1298
|
interface UserBidsChannelEvents {
|
|
1079
1299
|
bidWon: [MqttBidWonEvent];
|
|
1080
1300
|
bidLost: [MqttBidLostEvent];
|
|
@@ -1108,7 +1328,12 @@ declare class WalletChannel extends EventEmitter<WalletChannelEvents> {
|
|
|
1108
1328
|
}
|
|
1109
1329
|
|
|
1110
1330
|
interface RealtimeModuleOptions {
|
|
1111
|
-
|
|
1331
|
+
/**
|
|
1332
|
+
* MQTT broker endpoint. May be a thunk (multi-geo, design D4): resolved at
|
|
1333
|
+
* connect time so {@link RealtimeModule.reconnect} can re-target the broker to
|
|
1334
|
+
* the user's home-region endpoint after a login region switch.
|
|
1335
|
+
*/
|
|
1336
|
+
mqttEndpoint: string | (() => string);
|
|
1112
1337
|
/**
|
|
1113
1338
|
* The client's own agency id. Threaded so `subscribeAgencyPairStats` can
|
|
1114
1339
|
* scope the stats topic to this agency without accepting a target agency from
|
|
@@ -1179,6 +1404,23 @@ declare class RealtimeModule extends EventEmitter<RealtimeModuleEvents> {
|
|
|
1179
1404
|
* down the wildcard topic and removes the channel only at zero. No-op if absent.
|
|
1180
1405
|
*/
|
|
1181
1406
|
unsubscribeUserBids(userId: string): void;
|
|
1407
|
+
/**
|
|
1408
|
+
* Subscribe to the user-scoped migration completion stream for `userId`,
|
|
1409
|
+
* topic `user/{userId}/migration` (ux-260730). Returns a `MigrationChannel`
|
|
1410
|
+
* that emits `migrationCompleted` when the user's home-region migration
|
|
1411
|
+
* finishes. Malformed or schema-mismatched payloads are dropped silently.
|
|
1412
|
+
*
|
|
1413
|
+
* Reference-counted by `userId`, mirroring `subscribeWallet`. Intended
|
|
1414
|
+
* lifecycle (design D7): hold the channel only while the client is in the
|
|
1415
|
+
* migrating state; unsubscribe on completion/logout. The publisher retains
|
|
1416
|
+
* the completion message, so subscribing after the fact still delivers it.
|
|
1417
|
+
*/
|
|
1418
|
+
subscribeMigration(userId: string): MigrationChannel;
|
|
1419
|
+
/**
|
|
1420
|
+
* Decrement the migration subscription refcount for `userId`. Tears down the
|
|
1421
|
+
* MQTT topic and removes the channel only at zero. No-op if absent.
|
|
1422
|
+
*/
|
|
1423
|
+
unsubscribeMigration(userId: string): void;
|
|
1182
1424
|
/**
|
|
1183
1425
|
* Subscribe to public market candle data for a pair, keyed by pairId
|
|
1184
1426
|
* (the #2 game_pairs.id, e.g. "grid-ETH-USD"). Independent of game/agency —
|
|
@@ -1195,6 +1437,13 @@ declare class RealtimeModule extends EventEmitter<RealtimeModuleEvents> {
|
|
|
1195
1437
|
subscribeAgencyPairStats(pairId: string, handler: (event: MqttAgencyPairStatsEvent) => void): void;
|
|
1196
1438
|
/** Unsubscribe from the per-pair stats stream for the client's own agency. */
|
|
1197
1439
|
unsubscribeAgencyPairStats(pairId: string): void;
|
|
1440
|
+
/**
|
|
1441
|
+
* Reconnect the MQTT transport, re-resolving its (possibly region-scoped)
|
|
1442
|
+
* endpoint. All active subscriptions are preserved and re-declared on the new
|
|
1443
|
+
* connection (multi-geo home-region switch). No-op when not currently
|
|
1444
|
+
* connected. Delegates to the transport's `reconnect`.
|
|
1445
|
+
*/
|
|
1446
|
+
reconnect(): void;
|
|
1198
1447
|
disconnect(): void;
|
|
1199
1448
|
}
|
|
1200
1449
|
|
|
@@ -1256,6 +1505,17 @@ declare class UserModule {
|
|
|
1256
1505
|
}): Promise<UserPnL>;
|
|
1257
1506
|
wallets(opts?: RequestOpts): Promise<Wallet[]>;
|
|
1258
1507
|
walletByCurrency(currency: string, opts?: RequestOpts): Promise<Wallet>;
|
|
1508
|
+
/**
|
|
1509
|
+
* Start migrating the authenticated user's home region (multi-geo Phase 2).
|
|
1510
|
+
* User-service validates the JWT, checks the feature flag, and forwards to the
|
|
1511
|
+
* region directory (cooldown / single-flight / region-set validation happen
|
|
1512
|
+
* server-side). Typed rejections keep their `extensions.code`
|
|
1513
|
+
* (Migration_Unavailable, Migration_CooldownActive, …) on the error's `code`
|
|
1514
|
+
* so hosts can localise them.
|
|
1515
|
+
*/
|
|
1516
|
+
requestRegionMigration(toRegion: string, opts?: RequestOpts): Promise<{
|
|
1517
|
+
migrationId: string;
|
|
1518
|
+
}>;
|
|
1259
1519
|
get currencies(): Currency[] | null;
|
|
1260
1520
|
refreshCurrencies(): Promise<Currency[]>;
|
|
1261
1521
|
clearCurrencies(): void;
|
|
@@ -1338,8 +1598,29 @@ declare class TaphubClient {
|
|
|
1338
1598
|
private bus;
|
|
1339
1599
|
constructor(config: TaphubClientConfig);
|
|
1340
1600
|
getToken(): string | null;
|
|
1601
|
+
/** The user's home region, once resolved from a login response. */
|
|
1602
|
+
getRegion(): string | null;
|
|
1603
|
+
/**
|
|
1604
|
+
* Complete a home-region migration by cutting the client over to `toRegion`
|
|
1605
|
+
* (ux-260730-migration-complete-notify). Intended to be called after a
|
|
1606
|
+
* `migrationCompleted` event (realtime `subscribeMigration`) or an equivalent
|
|
1607
|
+
* poll result confirmed the migration finished — by then the server-side home
|
|
1608
|
+
* region already points at `toRegion`.
|
|
1609
|
+
*
|
|
1610
|
+
* Purpose-named public wrapper over the private `#setRegion` primitive (the
|
|
1611
|
+
* generic region setter stays private so third-party builders cannot
|
|
1612
|
+
* arbitrarily re-target regions). Persists the region and re-targets the
|
|
1613
|
+
* GQL/REST transports; MQTT reconnects only when the resolved broker endpoint
|
|
1614
|
+
* actually changes. Region validation is lenient, matching `region.ts`: an
|
|
1615
|
+
* unknown region is stored as-is and resolves to the default region's
|
|
1616
|
+
* endpoints (enum-fallback, never throws). Idempotent for the already-current
|
|
1617
|
+
* region (no reconnect), and a safe endpoint-level no-op when no region
|
|
1618
|
+
* domain/MQTT maps are configured.
|
|
1619
|
+
*/
|
|
1620
|
+
completeRegionMigration(toRegion: KnownRegion | (string & {})): void;
|
|
1341
1621
|
setToken(token: string | null, opts?: {
|
|
1342
1622
|
isDemo?: boolean;
|
|
1623
|
+
homeRegion?: string;
|
|
1343
1624
|
}): void;
|
|
1344
1625
|
isDemo(): boolean;
|
|
1345
1626
|
/** @internal Used by module integrations to access the REST transport. Not part of the public API. */
|
|
@@ -1350,6 +1631,73 @@ declare class TaphubClient {
|
|
|
1350
1631
|
off<K extends keyof TaphubEventMap & string>(event: K, handler: (payload: TaphubEventMap[K]) => void): void;
|
|
1351
1632
|
}
|
|
1352
1633
|
|
|
1634
|
+
/**
|
|
1635
|
+
* Nearest-region latency probe (multi-geo, Solution 3 — first-join geo routing).
|
|
1636
|
+
*
|
|
1637
|
+
* The browser is the only vantage point that can measure real per-region latency
|
|
1638
|
+
* (it holds a network path to every region domain). This module runs one cheap,
|
|
1639
|
+
* parallel round of requests and proposes the **first region to answer with a
|
|
1640
|
+
* valid body** — an approximate lowest-latency signal, which is sufficient because
|
|
1641
|
+
* exact nearness is a non-goal (a fast-responding region is what we want, not the
|
|
1642
|
+
* provably-nearest one).
|
|
1643
|
+
*
|
|
1644
|
+
* What it measures: the `{serverTime}` query on each region's grid-api GraphQL
|
|
1645
|
+
* endpoint — the cheapest call that proves the request reached that region's
|
|
1646
|
+
* backend rather than an edge in front of it. The winner must return a PARSEABLE
|
|
1647
|
+
* body carrying a numeric `serverTime`, not merely respond first: an edge
|
|
1648
|
+
* rejection (404 page, WAF challenge, gateway error) comes back in milliseconds
|
|
1649
|
+
* from the nearest PoP and would otherwise beat every genuine round trip and win
|
|
1650
|
+
* the race.
|
|
1651
|
+
*
|
|
1652
|
+
* The result is only ever a *hint*: the caller sends it to the builder backend
|
|
1653
|
+
* (`taptrading-api`) inside `clientMeta.preferredRegion`, where it is validated
|
|
1654
|
+
* against the deployed-region set before it can influence the user's home-region
|
|
1655
|
+
* pin. This module never decides anything on its own and never throws — geo must
|
|
1656
|
+
* not be able to fail a login.
|
|
1657
|
+
*/
|
|
1658
|
+
|
|
1659
|
+
/** Stable storage key for the throttle cache. Not agency-scoped — the nearest
|
|
1660
|
+
* region is a property of the user's network geography, the same for every agency. */
|
|
1661
|
+
declare const REGION_PROBE_CACHE_KEY = "taphub:region-probe";
|
|
1662
|
+
/** Default throttle window — re-probe at most once per hour (config overrides it). */
|
|
1663
|
+
declare const DEFAULT_PROBE_TTL_SECONDS = 3600;
|
|
1664
|
+
interface ProbeNearestRegionOptions {
|
|
1665
|
+
/** Fetch implementation (injectable for tests / non-browser hosts). */
|
|
1666
|
+
fetch?: typeof globalThis.fetch;
|
|
1667
|
+
/** Storage for the throttle cache (defaults to the SDK's auto-detected storage). */
|
|
1668
|
+
storage?: TaphubStorageAdapter;
|
|
1669
|
+
/** Throttle window in seconds — a cached result younger than this skips the probe. */
|
|
1670
|
+
ttlSeconds?: number;
|
|
1671
|
+
/** Overall cap for one probe round; nothing responding in time → `null`. */
|
|
1672
|
+
timeoutMs?: number;
|
|
1673
|
+
/** Probe path appended to each region base URL. */
|
|
1674
|
+
probePath?: string;
|
|
1675
|
+
/** Clock, injectable for deterministic cache-expiry tests. */
|
|
1676
|
+
now?: () => number;
|
|
1677
|
+
/** Cache key override (tests / multi-tenant hosts). */
|
|
1678
|
+
cacheKey?: string;
|
|
1679
|
+
}
|
|
1680
|
+
/**
|
|
1681
|
+
* Probe the configured regions and return the fastest-responding one, or `null`.
|
|
1682
|
+
*
|
|
1683
|
+
* Behaviour:
|
|
1684
|
+
* - Fewer than two configured regions → `null` (nothing to choose; the caller
|
|
1685
|
+
* sends no hint and the server uses its default region).
|
|
1686
|
+
* - A fresh cached result (younger than `ttlSeconds`) is reused without any
|
|
1687
|
+
* network probe — this throttles repeated app boots / login-modal opens by an
|
|
1688
|
+
* un-pinned user to at most one probe round per TTL window.
|
|
1689
|
+
* - Otherwise one parallel round of `POST <domain><probePath>` runs, each request
|
|
1690
|
+
* carrying the `{serverTime}` query; the first region to return a parseable body
|
|
1691
|
+
* with a numeric `serverTime` wins and is cached. A region that answers fast but
|
|
1692
|
+
* unparseably (an edge 404/error page) loses the race rather than winning it.
|
|
1693
|
+
* The response body has to be readable, so this is a normal CORS request — the
|
|
1694
|
+
* region endpoints must allow the app origin (they already do: the app posts to
|
|
1695
|
+
* this same endpoint).
|
|
1696
|
+
* - Any failure — all endpoints erroring, a timeout, a thrown fetch, a storage
|
|
1697
|
+
* error — resolves to `null`. This function never rejects.
|
|
1698
|
+
*/
|
|
1699
|
+
declare function probeNearestRegion(domains: RegionDomainMap | undefined, opts?: ProbeNearestRegionOptions): Promise<string | null>;
|
|
1700
|
+
|
|
1353
1701
|
declare class TaphubError extends Error {
|
|
1354
1702
|
readonly code: string;
|
|
1355
1703
|
readonly details?: unknown;
|
|
@@ -1429,7 +1777,75 @@ declare function errorFunction(x: number): number;
|
|
|
1429
1777
|
declare function normalCDF(x: number): number;
|
|
1430
1778
|
declare function normalPDF(x: number): number;
|
|
1431
1779
|
declare function adaptiveSimpson(f: (x: number) => number, a: number, b: number, tolerance?: number, maxDepth?: number): number;
|
|
1780
|
+
/**
|
|
1781
|
+
* @deprecated Does NOT match the model grid-api validates a bid against. Use
|
|
1782
|
+
* {@link calculateCoefficientWrapper} to derive the `coefficient` argument for
|
|
1783
|
+
* `placeBid`.
|
|
1784
|
+
*
|
|
1785
|
+
* This computes a one-sided exceedance probability — roughly "will the price end up
|
|
1786
|
+
* past `price2`" — and takes `price1` without using it. The game pays out when the
|
|
1787
|
+
* price *visits the band* `[price1, price2]`, which is a first-passage probability;
|
|
1788
|
+
* {@link calculateProbHit} computes that one. Measured against grid-api's reference
|
|
1789
|
+
* inputs, this function returns only 1.0, 0.5 or 0.0 and does not move at all when
|
|
1790
|
+
* `price1` goes from 2869.75 to 2800.00.
|
|
1791
|
+
*
|
|
1792
|
+
* The consequence is not a rejected bid. `placeBid`'s slippage check is
|
|
1793
|
+
* one-directional — it rejects a coefficient *higher* than the server's, never a
|
|
1794
|
+
* lower one — so a coefficient derived from this function (e.g. 1.0 where the server
|
|
1795
|
+
* computes 6.1) is accepted and locked, and the player is paid at the lower
|
|
1796
|
+
* coefficient on a win, with no error raised anywhere.
|
|
1797
|
+
*
|
|
1798
|
+
* Retained unchanged because it is published API (TH-491, Add-Deprecate-Remove).
|
|
1799
|
+
*/
|
|
1432
1800
|
declare function calculateProbWin(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1801
|
+
/**
|
|
1802
|
+
* @deprecated Does NOT match the model grid-api validates a bid against. Use
|
|
1803
|
+
* {@link calculateCoefficientWrapper} to derive the `coefficient` argument for
|
|
1804
|
+
* `placeBid`.
|
|
1805
|
+
*
|
|
1806
|
+
* This computes a one-sided exceedance probability — roughly "will the price end up
|
|
1807
|
+
* past `price2`" — and takes `price1` without using it. The game pays out when the
|
|
1808
|
+
* price *visits the band* `[price1, price2]`, which is a first-passage probability;
|
|
1809
|
+
* {@link calculateProbHit} computes that one. Measured against grid-api's reference
|
|
1810
|
+
* inputs, this function returns only 1.0, 0.5 or 0.0 and does not move at all when
|
|
1811
|
+
* `price1` goes from 2869.75 to 2800.00.
|
|
1812
|
+
*
|
|
1813
|
+
* The consequence is not a rejected bid. `placeBid`'s slippage check is
|
|
1814
|
+
* one-directional — it rejects a coefficient *higher* than the server's, never a
|
|
1815
|
+
* lower one — so a coefficient derived from this function (e.g. 1.0 where the server
|
|
1816
|
+
* computes 6.1) is accepted and locked, and the player is paid at the lower
|
|
1817
|
+
* coefficient on a win, with no error raised anywhere.
|
|
1818
|
+
*
|
|
1819
|
+
* Retained unchanged because it is published API (TH-491, Add-Deprecate-Remove).
|
|
1820
|
+
*/
|
|
1433
1821
|
declare function calculateProbWin_v2(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1822
|
+
interface CoefficientInput {
|
|
1823
|
+
time1: number;
|
|
1824
|
+
time2: number;
|
|
1825
|
+
price1: number;
|
|
1826
|
+
price2: number;
|
|
1827
|
+
candleTime: number;
|
|
1828
|
+
candleClose: number;
|
|
1829
|
+
volatility: number;
|
|
1830
|
+
coefMults: number[];
|
|
1831
|
+
cellSizeTime: number;
|
|
1832
|
+
candleSize: number;
|
|
1833
|
+
minCoef?: number;
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* First-passage hitting probability: chance the price visits [price1, price2]
|
|
1837
|
+
* at any point during [time1, time2], given GBM dynamics.
|
|
1838
|
+
*
|
|
1839
|
+
* P_hit = P_inside + P_from_above + P_from_below
|
|
1840
|
+
*
|
|
1841
|
+
* Matches Go backend algorithm.
|
|
1842
|
+
*/
|
|
1843
|
+
declare function calculateProbHit(time1: number, time2: number, price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1844
|
+
declare function calculateCoefficientWrapper(params: CoefficientInput): number;
|
|
1845
|
+
declare function roundCoefToSignificantDigits(value: number): number;
|
|
1846
|
+
declare function computeBaseline(candleClose: number, candleTimeSec: number, cellSizeValue: number, cellSizeTime: number, candleSize: number): {
|
|
1847
|
+
baseline: number;
|
|
1848
|
+
baselineTime: number;
|
|
1849
|
+
};
|
|
1434
1850
|
|
|
1435
|
-
export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, CANDLE_EVENT, type CancelBidResult, type Candle, type CandleEventType, type Constraints, type Currency, DEFAULT_CHART_HISTORY_LIMIT, type ErrorMessageCatalog, type ErrorMessageMeta, type ErrorMessagesResponse, GameChannel, type GameConfig, type GridConfig, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, type MqttAcceptedBid, type MqttAgencyPairStatsEvent, type MqttAuthConfig, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type MqttWalletBalanceEvent, type MyRank, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type Pair, type PairInfo, PairModule, type PlaceBidInput, type RankBoard, type RankEntry, type RankPeriod, type RankSort, RealtimeModule, type Sample, type SampleReason, type SignalSource, type SortDir, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserBidsChannel, type UserBidsChannelEvents, UserModule, type UserPnL, type UserPnLPeriod, type Wallet as UserWallet, type Wallet$1 as Wallet, type WalletBalanceReason, WalletChannel, adaptiveSimpson, autoDetectStorage, calculateProbWin, calculateProbWin_v2, errorFunction, isCancelled, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF, normaliseLang, pairIdFromBidResultTopic };
|
|
1851
|
+
export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, CANDLE_EVENT, type CancelBidResult, type Candle, type CandleEventType, type CoefficientInput, type Constraints, type Currency, DEFAULT_CHART_HISTORY_LIMIT, DEFAULT_PROBE_TTL_SECONDS, DEFAULT_REGION, type ErrorMessageCatalog, type ErrorMessageMeta, type ErrorMessagesResponse, GameChannel, type GameConfig, type GridConfig, KNOWN_REGIONS, type KnownRegion, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, MigrationChannel, type MigrationChannelEvents, type MigrationCompletedEvent, type MqttAcceptedBid, type MqttAgencyPairStatsEvent, type MqttAuthConfig, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type MqttWalletBalanceEvent, type MyRank, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type Pair, type PairInfo, PairModule, type PlaceBidInput, type ProbeNearestRegionOptions, REGION_PROBE_CACHE_KEY, type RankBoard, type RankEntry, type RankPeriod, type RankSort, RealtimeModule, type RefreshTokenResult, type RegionDomainMap, type Sample, type SampleReason, type SignalSource, type SortDir, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserBidsChannel, type UserBidsChannelEvents, UserModule, type UserPnL, type UserPnLPeriod, type Wallet as UserWallet, type Wallet$1 as Wallet, type WalletBalanceReason, WalletChannel, adaptiveSimpson, autoDetectStorage, calculateCoefficientWrapper, calculateProbHit, calculateProbWin, calculateProbWin_v2, computeBaseline, errorFunction, isCancelled, isKnownRegion, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF, normaliseLang, pairIdFromBidResultTopic, probeNearestRegion, resolveRegionBaseUrl, roundCoefToSignificantDigits };
|