@finatic/client 0.0.139 → 0.0.140
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 +278 -461
- package/dist/index.d.ts +55 -515
- package/dist/index.js +326 -456
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +327 -456
- package/dist/index.mjs.map +1 -1
- package/dist/types/core/client/ApiClient.d.ts +12 -26
- package/dist/types/core/client/FinaticConnect.d.ts +20 -103
- package/dist/types/index.d.ts +1 -2
- package/dist/types/mocks/MockApiClient.d.ts +2 -4
- package/dist/types/mocks/utils.d.ts +0 -5
- package/dist/types/types/api/auth.d.ts +12 -30
- package/dist/types/types/api/broker.d.ts +1 -1
- package/package.json +7 -3
- package/src/core/client/ApiClient.ts +1721 -0
- package/src/core/client/FinaticConnect.ts +1476 -0
- package/src/core/portal/PortalUI.ts +300 -0
- package/src/index.d.ts +23 -0
- package/src/index.ts +87 -0
- package/src/mocks/MockApiClient.ts +1032 -0
- package/src/mocks/MockDataProvider.ts +986 -0
- package/src/mocks/MockFactory.ts +97 -0
- package/src/mocks/utils.ts +133 -0
- package/src/themes/portalPresets.ts +1307 -0
- package/src/types/api/auth.ts +112 -0
- package/src/types/api/broker.ts +330 -0
- package/src/types/api/core.ts +53 -0
- package/src/types/api/errors.ts +35 -0
- package/src/types/api/orders.ts +45 -0
- package/src/types/api/portfolio.ts +59 -0
- package/src/types/common/pagination.ts +138 -0
- package/src/types/connect.ts +56 -0
- package/src/types/index.ts +25 -0
- package/src/types/portal.ts +214 -0
- package/src/types/ui/theme.ts +105 -0
- package/src/utils/brokerUtils.ts +85 -0
- package/src/utils/errors.ts +104 -0
- package/src/utils/events.ts +54 -0
- package/src/utils/themeUtils.ts +146 -0
|
@@ -0,0 +1,1032 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
+
|
|
3
|
+
// Import types from the main API types
|
|
4
|
+
import {
|
|
5
|
+
SessionResponse,
|
|
6
|
+
OtpRequestResponse,
|
|
7
|
+
OtpVerifyResponse,
|
|
8
|
+
PortalUrlResponse,
|
|
9
|
+
SessionValidationResponse,
|
|
10
|
+
SessionAuthenticateResponse,
|
|
11
|
+
UserToken,
|
|
12
|
+
Order,
|
|
13
|
+
BrokerInfo,
|
|
14
|
+
BrokerAccount,
|
|
15
|
+
BrokerOrder,
|
|
16
|
+
BrokerPosition,
|
|
17
|
+
BrokerBalance,
|
|
18
|
+
BrokerConnection,
|
|
19
|
+
BrokerDataOptions,
|
|
20
|
+
BrokerOrderParams,
|
|
21
|
+
BrokerExtras,
|
|
22
|
+
CryptoOrderOptions,
|
|
23
|
+
OptionsOrderOptions,
|
|
24
|
+
OrderResponse,
|
|
25
|
+
TradingContext,
|
|
26
|
+
RefreshTokenRequest,
|
|
27
|
+
RefreshTokenResponse,
|
|
28
|
+
OrdersFilter,
|
|
29
|
+
PositionsFilter,
|
|
30
|
+
AccountsFilter,
|
|
31
|
+
BalancesFilter,
|
|
32
|
+
BrokerDataOrder,
|
|
33
|
+
BrokerDataPosition,
|
|
34
|
+
BrokerDataAccount,
|
|
35
|
+
DisconnectCompanyResponse,
|
|
36
|
+
} from '../types';
|
|
37
|
+
import { PaginatedResult } from '../types';
|
|
38
|
+
import { DeviceInfo, SessionState, TokenInfo } from '../types/api/auth';
|
|
39
|
+
import {
|
|
40
|
+
ApiError,
|
|
41
|
+
SessionError,
|
|
42
|
+
AuthenticationError,
|
|
43
|
+
AuthorizationError,
|
|
44
|
+
RateLimitError,
|
|
45
|
+
CompanyAccessError,
|
|
46
|
+
OrderError,
|
|
47
|
+
OrderValidationError,
|
|
48
|
+
} from '../utils/errors';
|
|
49
|
+
import { MockDataProvider, MockConfig } from './MockDataProvider';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Mock API Client that implements the same interface as the real ApiClient
|
|
53
|
+
* but returns mock data instead of making HTTP requests
|
|
54
|
+
*/
|
|
55
|
+
export class MockApiClient {
|
|
56
|
+
private readonly baseUrl: string;
|
|
57
|
+
protected readonly deviceInfo?: DeviceInfo;
|
|
58
|
+
protected currentSessionState: SessionState | null = null;
|
|
59
|
+
protected currentSessionId: string | null = null;
|
|
60
|
+
private tradingContext: TradingContext = {};
|
|
61
|
+
|
|
62
|
+
// Token management
|
|
63
|
+
private tokenInfo: TokenInfo | null = null;
|
|
64
|
+
private refreshPromise: Promise<TokenInfo> | null = null;
|
|
65
|
+
private readonly REFRESH_BUFFER_MINUTES = 5;
|
|
66
|
+
|
|
67
|
+
// Session and company context
|
|
68
|
+
private companyId: string | null = null;
|
|
69
|
+
private csrfToken: string | null = null;
|
|
70
|
+
|
|
71
|
+
// Mock data provider
|
|
72
|
+
private mockDataProvider: MockDataProvider;
|
|
73
|
+
private readonly mockApiOnly: boolean;
|
|
74
|
+
|
|
75
|
+
constructor(baseUrl: string, deviceInfo?: DeviceInfo, mockConfig?: MockConfig) {
|
|
76
|
+
this.baseUrl = baseUrl;
|
|
77
|
+
this.deviceInfo = deviceInfo;
|
|
78
|
+
this.mockApiOnly = mockConfig?.mockApiOnly || false;
|
|
79
|
+
this.mockDataProvider = new MockDataProvider(mockConfig);
|
|
80
|
+
|
|
81
|
+
// Log that mocks are being used
|
|
82
|
+
if (this.mockApiOnly) {
|
|
83
|
+
console.log('🔧 Finatic SDK: Using MOCK API Client (API only - real portal)');
|
|
84
|
+
} else {
|
|
85
|
+
console.log('🔧 Finatic SDK: Using MOCK API Client');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Store tokens after successful authentication
|
|
91
|
+
*/
|
|
92
|
+
setTokens(accessToken: string, refreshToken: string, expiresAt: string, userId?: string): void {
|
|
93
|
+
this.tokenInfo = {
|
|
94
|
+
accessToken,
|
|
95
|
+
refreshToken,
|
|
96
|
+
expiresAt,
|
|
97
|
+
userId,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get the current access token, refreshing if necessary
|
|
103
|
+
*/
|
|
104
|
+
async getValidAccessToken(): Promise<string> {
|
|
105
|
+
if (!this.tokenInfo) {
|
|
106
|
+
throw new AuthenticationError('No tokens available. Please authenticate first.');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check if token is expired or about to expire
|
|
110
|
+
if (this.isTokenExpired()) {
|
|
111
|
+
await this.refreshTokens();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return this.tokenInfo.accessToken;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check if the current token is expired or about to expire
|
|
119
|
+
*/
|
|
120
|
+
private isTokenExpired(): boolean {
|
|
121
|
+
if (!this.tokenInfo) return true;
|
|
122
|
+
|
|
123
|
+
const expiryTime = new Date(this.tokenInfo.expiresAt).getTime();
|
|
124
|
+
const currentTime = Date.now();
|
|
125
|
+
const bufferTime = this.REFRESH_BUFFER_MINUTES * 60 * 1000;
|
|
126
|
+
|
|
127
|
+
return currentTime >= expiryTime - bufferTime;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Refresh the access token using the refresh token
|
|
132
|
+
*/
|
|
133
|
+
private async refreshTokens(): Promise<void> {
|
|
134
|
+
if (!this.tokenInfo) {
|
|
135
|
+
throw new AuthenticationError('No refresh token available.');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// If a refresh is already in progress, wait for it
|
|
139
|
+
if (this.refreshPromise) {
|
|
140
|
+
await this.refreshPromise;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Start a new refresh
|
|
145
|
+
this.refreshPromise = this.performTokenRefresh();
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
await this.refreshPromise;
|
|
149
|
+
} finally {
|
|
150
|
+
this.refreshPromise = null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Perform the actual token refresh request
|
|
156
|
+
*/
|
|
157
|
+
private async performTokenRefresh(): Promise<TokenInfo> {
|
|
158
|
+
if (!this.tokenInfo) {
|
|
159
|
+
throw new AuthenticationError('No refresh token available.');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const response = await this.mockDataProvider.mockRefreshToken(this.tokenInfo.refreshToken);
|
|
164
|
+
|
|
165
|
+
// Update stored tokens
|
|
166
|
+
this.tokenInfo = {
|
|
167
|
+
accessToken: response.response_data.access_token,
|
|
168
|
+
refreshToken: response.response_data.refresh_token,
|
|
169
|
+
expiresAt: response.response_data.expires_at,
|
|
170
|
+
userId: this.tokenInfo.userId,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
return this.tokenInfo;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
// Clear tokens on refresh failure
|
|
176
|
+
this.tokenInfo = null;
|
|
177
|
+
throw new AuthenticationError(
|
|
178
|
+
'Token refresh failed. Please re-authenticate.',
|
|
179
|
+
error as Record<string, any>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Clear stored tokens (useful for logout)
|
|
186
|
+
*/
|
|
187
|
+
clearTokens(): void {
|
|
188
|
+
this.tokenInfo = null;
|
|
189
|
+
this.refreshPromise = null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get current token info (for debugging/testing)
|
|
194
|
+
*/
|
|
195
|
+
getTokenInfo(): TokenInfo | null {
|
|
196
|
+
return this.tokenInfo ? { ...this.tokenInfo } : null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Set session context (session ID, company ID, CSRF token)
|
|
201
|
+
*/
|
|
202
|
+
setSessionContext(sessionId: string, companyId: string, csrfToken?: string): void {
|
|
203
|
+
this.currentSessionId = sessionId;
|
|
204
|
+
this.companyId = companyId;
|
|
205
|
+
this.csrfToken = csrfToken || null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get the current session ID
|
|
210
|
+
*/
|
|
211
|
+
getCurrentSessionId(): string | null {
|
|
212
|
+
return this.currentSessionId;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get the current company ID
|
|
217
|
+
*/
|
|
218
|
+
getCurrentCompanyId(): string | null {
|
|
219
|
+
return this.companyId;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get the current CSRF token
|
|
224
|
+
*/
|
|
225
|
+
getCurrentCsrfToken(): string | null {
|
|
226
|
+
return this.csrfToken;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Session Management
|
|
230
|
+
async startSession(token: string, userId?: string): Promise<SessionResponse> {
|
|
231
|
+
const response = await this.mockDataProvider.mockStartSession(token, userId);
|
|
232
|
+
|
|
233
|
+
// Store session ID and set state to ACTIVE
|
|
234
|
+
this.currentSessionId = response.data.session_id;
|
|
235
|
+
this.currentSessionState = SessionState.ACTIVE;
|
|
236
|
+
|
|
237
|
+
return response;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// OTP Flow
|
|
241
|
+
async requestOtp(sessionId: string, email: string): Promise<OtpRequestResponse> {
|
|
242
|
+
return this.mockDataProvider.mockRequestOtp(sessionId, email);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async verifyOtp(sessionId: string, otp: string): Promise<OtpVerifyResponse> {
|
|
246
|
+
const response = await this.mockDataProvider.mockVerifyOtp(sessionId, otp);
|
|
247
|
+
|
|
248
|
+
// Store tokens after successful OTP verification
|
|
249
|
+
if (response.success && response.data) {
|
|
250
|
+
const expiresAt = new Date(Date.now() + response.data.expires_in * 1000).toISOString();
|
|
251
|
+
this.setTokens(
|
|
252
|
+
response.data.access_token,
|
|
253
|
+
response.data.refresh_token,
|
|
254
|
+
expiresAt,
|
|
255
|
+
response.data.user_id
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return response;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Direct Authentication
|
|
263
|
+
async authenticateDirectly(
|
|
264
|
+
sessionId: string,
|
|
265
|
+
userId: string
|
|
266
|
+
): Promise<SessionAuthenticateResponse> {
|
|
267
|
+
// Ensure session is active before authenticating
|
|
268
|
+
if (this.currentSessionState !== SessionState.ACTIVE) {
|
|
269
|
+
throw new SessionError('Session must be in ACTIVE state to authenticate');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const response = await this.mockDataProvider.mockAuthenticateDirectly(sessionId, userId);
|
|
273
|
+
|
|
274
|
+
// Store tokens after successful direct authentication
|
|
275
|
+
if (response.success && response.data) {
|
|
276
|
+
// For direct auth, we don't get expires_in, so we'll set a default 1-hour expiry
|
|
277
|
+
const expiresAt = new Date(Date.now() + 60 * 60 * 1000).toISOString(); // 1 hour
|
|
278
|
+
this.setTokens(response.data.access_token, response.data.refresh_token, expiresAt, userId);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return response;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Portal Management
|
|
285
|
+
async getPortalUrl(sessionId: string): Promise<PortalUrlResponse> {
|
|
286
|
+
if (this.currentSessionState !== SessionState.ACTIVE) {
|
|
287
|
+
throw new SessionError('Session must be in ACTIVE state to get portal URL');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// If in mockApiOnly mode, return the real portal URL
|
|
291
|
+
if (this.mockApiOnly) {
|
|
292
|
+
return {
|
|
293
|
+
success: true,
|
|
294
|
+
message: 'Portal URL retrieved successfully',
|
|
295
|
+
data: {
|
|
296
|
+
portal_url: 'http://localhost:5173/companies',
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return this.mockDataProvider.mockGetPortalUrl(sessionId);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async validatePortalSession(
|
|
305
|
+
sessionId: string,
|
|
306
|
+
signature: string
|
|
307
|
+
): Promise<SessionValidationResponse> {
|
|
308
|
+
return this.mockDataProvider.mockValidatePortalSession(sessionId, signature);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async completePortalSession(sessionId: string): Promise<PortalUrlResponse> {
|
|
312
|
+
return this.mockDataProvider.mockCompletePortalSession(sessionId);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Portfolio Management
|
|
316
|
+
|
|
317
|
+
async getOrders(filter?: OrdersFilter): Promise<{ data: Order[] }> {
|
|
318
|
+
const accessToken = await this.getValidAccessToken();
|
|
319
|
+
return this.mockDataProvider.mockGetOrders(filter);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async placeOrder(order: BrokerOrderParams): Promise<void> {
|
|
323
|
+
const accessToken = await this.getValidAccessToken();
|
|
324
|
+
await this.mockDataProvider.mockPlaceOrder(order);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Enhanced Trading Methods with Session Management
|
|
328
|
+
async placeBrokerOrder(
|
|
329
|
+
params: Partial<BrokerOrderParams> & {
|
|
330
|
+
symbol: string;
|
|
331
|
+
orderQty: number;
|
|
332
|
+
action: 'Buy' | 'Sell';
|
|
333
|
+
orderType: 'Market' | 'Limit' | 'Stop' | 'StopLimit';
|
|
334
|
+
assetType: 'equity' | 'equity_option' | 'crypto' | 'forex' | 'future' | 'future_option';
|
|
335
|
+
},
|
|
336
|
+
extras: BrokerExtras = {},
|
|
337
|
+
connection_id?: string
|
|
338
|
+
): Promise<OrderResponse> {
|
|
339
|
+
const accessToken = await this.getValidAccessToken();
|
|
340
|
+
|
|
341
|
+
// Debug logging
|
|
342
|
+
console.log('MockApiClient.placeBrokerOrder Debug:', {
|
|
343
|
+
params,
|
|
344
|
+
tradingContext: this.tradingContext,
|
|
345
|
+
paramsBroker: params.broker,
|
|
346
|
+
contextBroker: this.tradingContext.broker,
|
|
347
|
+
paramsAccountNumber: params.accountNumber,
|
|
348
|
+
contextAccountNumber: this.tradingContext.accountNumber,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const fullParams: BrokerOrderParams = {
|
|
352
|
+
broker:
|
|
353
|
+
((params.broker || this.tradingContext.broker) as
|
|
354
|
+
| 'robinhood'
|
|
355
|
+
| 'tasty_trade'
|
|
356
|
+
| 'ninja_trader') ||
|
|
357
|
+
(() => {
|
|
358
|
+
throw new Error('Broker not set. Call setBroker() or pass broker parameter.');
|
|
359
|
+
})(),
|
|
360
|
+
accountNumber:
|
|
361
|
+
params.accountNumber ||
|
|
362
|
+
this.tradingContext.accountNumber ||
|
|
363
|
+
(() => {
|
|
364
|
+
throw new Error('Account not set. Call setAccount() or pass accountNumber parameter.');
|
|
365
|
+
})(),
|
|
366
|
+
symbol: params.symbol,
|
|
367
|
+
orderQty: params.orderQty,
|
|
368
|
+
action: params.action,
|
|
369
|
+
orderType: params.orderType,
|
|
370
|
+
assetType: params.assetType,
|
|
371
|
+
timeInForce: params.timeInForce || 'day',
|
|
372
|
+
price: params.price,
|
|
373
|
+
stopPrice: params.stopPrice,
|
|
374
|
+
order_id: params.order_id,
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
console.log('MockApiClient.placeBrokerOrder Debug - Final params:', fullParams);
|
|
378
|
+
return this.mockDataProvider.mockPlaceOrder(fullParams);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async cancelBrokerOrder(
|
|
382
|
+
orderId: string,
|
|
383
|
+
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
384
|
+
extras: any = {},
|
|
385
|
+
connection_id?: string
|
|
386
|
+
): Promise<OrderResponse> {
|
|
387
|
+
// Mock successful cancellation
|
|
388
|
+
return {
|
|
389
|
+
success: true,
|
|
390
|
+
response_data: {
|
|
391
|
+
orderId: orderId,
|
|
392
|
+
status: 'cancelled',
|
|
393
|
+
broker: broker || this.tradingContext.broker,
|
|
394
|
+
},
|
|
395
|
+
message: 'Order cancelled successfully',
|
|
396
|
+
status_code: 200,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async modifyBrokerOrder(
|
|
401
|
+
orderId: string,
|
|
402
|
+
params: Partial<BrokerOrderParams>,
|
|
403
|
+
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
404
|
+
extras: any = {},
|
|
405
|
+
connection_id?: string
|
|
406
|
+
): Promise<OrderResponse> {
|
|
407
|
+
// Mock successful modification
|
|
408
|
+
return {
|
|
409
|
+
success: true,
|
|
410
|
+
response_data: {
|
|
411
|
+
orderId: orderId,
|
|
412
|
+
status: 'modified',
|
|
413
|
+
broker: broker || this.tradingContext.broker,
|
|
414
|
+
...params,
|
|
415
|
+
},
|
|
416
|
+
message: 'Order modified successfully',
|
|
417
|
+
status_code: 200,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Context management methods
|
|
422
|
+
setBroker(broker: 'robinhood' | 'tasty_trade' | 'ninja_trader'): void {
|
|
423
|
+
this.tradingContext.broker = broker;
|
|
424
|
+
// Clear account when broker changes
|
|
425
|
+
this.tradingContext.accountNumber = undefined;
|
|
426
|
+
this.tradingContext.accountId = undefined;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
setAccount(accountNumber: string, accountId?: string): void {
|
|
430
|
+
console.log('MockApiClient.setAccount Debug:', {
|
|
431
|
+
accountNumber,
|
|
432
|
+
accountId,
|
|
433
|
+
previousContext: { ...this.tradingContext },
|
|
434
|
+
});
|
|
435
|
+
this.tradingContext.accountNumber = accountNumber;
|
|
436
|
+
this.tradingContext.accountId = accountId;
|
|
437
|
+
console.log('MockApiClient.setAccount Debug - Updated context:', this.tradingContext);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
// Stock convenience methods
|
|
442
|
+
async placeStockMarketOrder(
|
|
443
|
+
symbol: string,
|
|
444
|
+
orderQty: number,
|
|
445
|
+
action: 'Buy' | 'Sell',
|
|
446
|
+
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
447
|
+
accountNumber?: string,
|
|
448
|
+
extras: BrokerExtras = {}
|
|
449
|
+
): Promise<OrderResponse> {
|
|
450
|
+
return this.placeBrokerOrder(
|
|
451
|
+
{
|
|
452
|
+
broker,
|
|
453
|
+
accountNumber,
|
|
454
|
+
symbol,
|
|
455
|
+
orderQty,
|
|
456
|
+
action,
|
|
457
|
+
orderType: 'Market',
|
|
458
|
+
assetType: 'equity',
|
|
459
|
+
timeInForce: 'day',
|
|
460
|
+
},
|
|
461
|
+
extras
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async placeStockLimitOrder(
|
|
466
|
+
symbol: string,
|
|
467
|
+
orderQty: number,
|
|
468
|
+
action: 'Buy' | 'Sell',
|
|
469
|
+
price: number,
|
|
470
|
+
timeInForce: 'day' | 'gtc' = 'gtc',
|
|
471
|
+
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
472
|
+
accountNumber?: string,
|
|
473
|
+
extras: BrokerExtras = {}
|
|
474
|
+
): Promise<OrderResponse> {
|
|
475
|
+
return this.placeBrokerOrder(
|
|
476
|
+
{
|
|
477
|
+
broker,
|
|
478
|
+
accountNumber,
|
|
479
|
+
symbol,
|
|
480
|
+
orderQty,
|
|
481
|
+
action,
|
|
482
|
+
orderType: 'Limit',
|
|
483
|
+
assetType: 'equity',
|
|
484
|
+
timeInForce,
|
|
485
|
+
price,
|
|
486
|
+
},
|
|
487
|
+
extras
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async placeStockStopOrder(
|
|
492
|
+
symbol: string,
|
|
493
|
+
orderQty: number,
|
|
494
|
+
action: 'Buy' | 'Sell',
|
|
495
|
+
stopPrice: number,
|
|
496
|
+
timeInForce: 'day' | 'gtc' = 'day',
|
|
497
|
+
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
498
|
+
accountNumber?: string,
|
|
499
|
+
extras: BrokerExtras = {}
|
|
500
|
+
): Promise<OrderResponse> {
|
|
501
|
+
return this.placeBrokerOrder(
|
|
502
|
+
{
|
|
503
|
+
broker,
|
|
504
|
+
accountNumber,
|
|
505
|
+
symbol,
|
|
506
|
+
orderQty,
|
|
507
|
+
action,
|
|
508
|
+
orderType: 'Stop',
|
|
509
|
+
assetType: 'equity',
|
|
510
|
+
timeInForce,
|
|
511
|
+
stopPrice,
|
|
512
|
+
},
|
|
513
|
+
extras
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Crypto convenience methods
|
|
518
|
+
async placeCryptoMarketOrder(
|
|
519
|
+
symbol: string,
|
|
520
|
+
orderQty: number,
|
|
521
|
+
action: 'Buy' | 'Sell',
|
|
522
|
+
options: CryptoOrderOptions = {},
|
|
523
|
+
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
524
|
+
accountNumber?: string,
|
|
525
|
+
extras: BrokerExtras = {}
|
|
526
|
+
): Promise<OrderResponse> {
|
|
527
|
+
const orderParams: Partial<BrokerOrderParams> & {
|
|
528
|
+
symbol: string;
|
|
529
|
+
orderQty: number;
|
|
530
|
+
action: 'Buy' | 'Sell';
|
|
531
|
+
orderType: 'Market';
|
|
532
|
+
assetType: 'crypto';
|
|
533
|
+
} = {
|
|
534
|
+
broker,
|
|
535
|
+
accountNumber,
|
|
536
|
+
symbol,
|
|
537
|
+
orderQty: options.quantity || orderQty,
|
|
538
|
+
action,
|
|
539
|
+
orderType: 'Market',
|
|
540
|
+
assetType: 'crypto',
|
|
541
|
+
timeInForce: 'gtc', // Crypto typically uses GTC
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
return this.placeBrokerOrder(orderParams, extras);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async placeCryptoLimitOrder(
|
|
548
|
+
symbol: string,
|
|
549
|
+
orderQty: number,
|
|
550
|
+
action: 'Buy' | 'Sell',
|
|
551
|
+
price: number,
|
|
552
|
+
timeInForce: 'day' | 'gtc' = 'gtc',
|
|
553
|
+
options: CryptoOrderOptions = {},
|
|
554
|
+
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
555
|
+
accountNumber?: string,
|
|
556
|
+
extras: BrokerExtras = {}
|
|
557
|
+
): Promise<OrderResponse> {
|
|
558
|
+
const orderParams: Partial<BrokerOrderParams> & {
|
|
559
|
+
symbol: string;
|
|
560
|
+
orderQty: number;
|
|
561
|
+
action: 'Buy' | 'Sell';
|
|
562
|
+
orderType: 'Limit';
|
|
563
|
+
assetType: 'crypto';
|
|
564
|
+
} = {
|
|
565
|
+
broker,
|
|
566
|
+
accountNumber,
|
|
567
|
+
symbol,
|
|
568
|
+
orderQty: options.quantity || orderQty,
|
|
569
|
+
action,
|
|
570
|
+
orderType: 'Limit',
|
|
571
|
+
assetType: 'crypto',
|
|
572
|
+
timeInForce,
|
|
573
|
+
price,
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
return this.placeBrokerOrder(orderParams, extras);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Options convenience methods
|
|
580
|
+
async placeOptionsMarketOrder(
|
|
581
|
+
symbol: string,
|
|
582
|
+
orderQty: number,
|
|
583
|
+
action: 'Buy' | 'Sell',
|
|
584
|
+
options: OptionsOrderOptions,
|
|
585
|
+
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
586
|
+
accountNumber?: string,
|
|
587
|
+
extras: BrokerExtras = {}
|
|
588
|
+
): Promise<OrderResponse> {
|
|
589
|
+
const orderParams: Partial<BrokerOrderParams> & {
|
|
590
|
+
symbol: string;
|
|
591
|
+
orderQty: number;
|
|
592
|
+
action: 'Buy' | 'Sell';
|
|
593
|
+
orderType: 'Market';
|
|
594
|
+
assetType: 'equity_option';
|
|
595
|
+
} = {
|
|
596
|
+
broker,
|
|
597
|
+
accountNumber,
|
|
598
|
+
symbol,
|
|
599
|
+
orderQty,
|
|
600
|
+
action,
|
|
601
|
+
orderType: 'Market',
|
|
602
|
+
assetType: 'equity_option',
|
|
603
|
+
timeInForce: 'day',
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
return this.placeBrokerOrder(orderParams, extras);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
async placeOptionsLimitOrder(
|
|
610
|
+
symbol: string,
|
|
611
|
+
orderQty: number,
|
|
612
|
+
action: 'Buy' | 'Sell',
|
|
613
|
+
price: number,
|
|
614
|
+
options: OptionsOrderOptions,
|
|
615
|
+
timeInForce: 'day' | 'gtc' = 'gtc',
|
|
616
|
+
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
617
|
+
accountNumber?: string,
|
|
618
|
+
extras: BrokerExtras = {}
|
|
619
|
+
): Promise<OrderResponse> {
|
|
620
|
+
const orderParams: Partial<BrokerOrderParams> & {
|
|
621
|
+
symbol: string;
|
|
622
|
+
orderQty: number;
|
|
623
|
+
action: 'Buy' | 'Sell';
|
|
624
|
+
orderType: 'Limit';
|
|
625
|
+
assetType: 'equity_option';
|
|
626
|
+
} = {
|
|
627
|
+
broker,
|
|
628
|
+
accountNumber,
|
|
629
|
+
symbol,
|
|
630
|
+
orderQty,
|
|
631
|
+
action,
|
|
632
|
+
orderType: 'Limit',
|
|
633
|
+
assetType: 'equity_option',
|
|
634
|
+
timeInForce,
|
|
635
|
+
price,
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
return this.placeBrokerOrder(orderParams, extras);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Futures convenience methods
|
|
642
|
+
async placeFuturesMarketOrder(
|
|
643
|
+
symbol: string,
|
|
644
|
+
orderQty: number,
|
|
645
|
+
action: 'Buy' | 'Sell',
|
|
646
|
+
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
647
|
+
accountNumber?: string,
|
|
648
|
+
extras: BrokerExtras = {}
|
|
649
|
+
): Promise<OrderResponse> {
|
|
650
|
+
return this.placeBrokerOrder(
|
|
651
|
+
{
|
|
652
|
+
broker,
|
|
653
|
+
accountNumber,
|
|
654
|
+
symbol,
|
|
655
|
+
orderQty,
|
|
656
|
+
action,
|
|
657
|
+
orderType: 'Market',
|
|
658
|
+
assetType: 'future',
|
|
659
|
+
timeInForce: 'day',
|
|
660
|
+
},
|
|
661
|
+
extras
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
async placeFuturesLimitOrder(
|
|
666
|
+
symbol: string,
|
|
667
|
+
orderQty: number,
|
|
668
|
+
action: 'Buy' | 'Sell',
|
|
669
|
+
price: number,
|
|
670
|
+
timeInForce: 'day' | 'gtc' = 'gtc',
|
|
671
|
+
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
672
|
+
accountNumber?: string,
|
|
673
|
+
extras: BrokerExtras = {}
|
|
674
|
+
): Promise<OrderResponse> {
|
|
675
|
+
return this.placeBrokerOrder(
|
|
676
|
+
{
|
|
677
|
+
broker,
|
|
678
|
+
accountNumber,
|
|
679
|
+
symbol,
|
|
680
|
+
orderQty,
|
|
681
|
+
action,
|
|
682
|
+
orderType: 'Limit',
|
|
683
|
+
assetType: 'future',
|
|
684
|
+
timeInForce,
|
|
685
|
+
price,
|
|
686
|
+
},
|
|
687
|
+
extras
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
async getUserToken(sessionId: string): Promise<UserToken> {
|
|
692
|
+
const token = this.mockDataProvider.getUserToken(sessionId);
|
|
693
|
+
if (!token) {
|
|
694
|
+
throw new AuthenticationError('User token not found');
|
|
695
|
+
}
|
|
696
|
+
return token;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
getCurrentSessionState(): SessionState | null {
|
|
700
|
+
return this.currentSessionState;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Broker Data Management
|
|
704
|
+
async getBrokerList(): Promise<{
|
|
705
|
+
_id: string;
|
|
706
|
+
response_data: BrokerInfo[];
|
|
707
|
+
message: string;
|
|
708
|
+
status_code: number;
|
|
709
|
+
warnings: null;
|
|
710
|
+
errors: null;
|
|
711
|
+
}> {
|
|
712
|
+
// Public in mock mode as well - no auth required
|
|
713
|
+
return this.mockDataProvider.mockGetBrokerList();
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async getBrokerAccounts(options?: BrokerDataOptions): Promise<{
|
|
717
|
+
_id: string;
|
|
718
|
+
response_data: BrokerAccount[];
|
|
719
|
+
message: string;
|
|
720
|
+
status_code: number;
|
|
721
|
+
warnings: null;
|
|
722
|
+
errors: null;
|
|
723
|
+
}> {
|
|
724
|
+
const accessToken = await this.getValidAccessToken();
|
|
725
|
+
return this.mockDataProvider.mockGetBrokerAccounts();
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
async getBrokerOrders(options?: BrokerDataOptions): Promise<{
|
|
729
|
+
_id: string;
|
|
730
|
+
response_data: BrokerOrder[];
|
|
731
|
+
message: string;
|
|
732
|
+
status_code: number;
|
|
733
|
+
warnings: null;
|
|
734
|
+
errors: null;
|
|
735
|
+
}> {
|
|
736
|
+
const accessToken = await this.getValidAccessToken();
|
|
737
|
+
// Return empty orders for now - keeping original interface
|
|
738
|
+
return {
|
|
739
|
+
_id: uuidv4(),
|
|
740
|
+
response_data: [],
|
|
741
|
+
message: 'Broker orders retrieved successfully',
|
|
742
|
+
status_code: 200,
|
|
743
|
+
warnings: null,
|
|
744
|
+
errors: null,
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
async getBrokerPositions(options?: BrokerDataOptions): Promise<{
|
|
749
|
+
_id: string;
|
|
750
|
+
response_data: BrokerPosition[];
|
|
751
|
+
message: string;
|
|
752
|
+
status_code: number;
|
|
753
|
+
warnings: null;
|
|
754
|
+
errors: null;
|
|
755
|
+
}> {
|
|
756
|
+
const accessToken = await this.getValidAccessToken();
|
|
757
|
+
// Return empty positions for now - keeping original interface
|
|
758
|
+
return {
|
|
759
|
+
_id: uuidv4(),
|
|
760
|
+
response_data: [],
|
|
761
|
+
message: 'Broker positions retrieved successfully',
|
|
762
|
+
status_code: 200,
|
|
763
|
+
warnings: null,
|
|
764
|
+
errors: null,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// New broker data methods with filtering support
|
|
769
|
+
async getBrokerOrdersWithFilter(filter?: OrdersFilter): Promise<{ data: BrokerDataOrder[] }> {
|
|
770
|
+
return this.mockDataProvider.mockGetBrokerOrders(filter);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
async getBrokerPositionsWithFilter(
|
|
774
|
+
filter?: PositionsFilter
|
|
775
|
+
): Promise<{ data: BrokerDataPosition[] }> {
|
|
776
|
+
return this.mockDataProvider.mockGetBrokerPositions(filter);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async getBrokerBalancesWithFilter(filter?: BalancesFilter): Promise<{ data: BrokerBalance[] }> {
|
|
780
|
+
return this.mockDataProvider.mockGetBrokerBalances(filter);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
async getBrokerDataAccountsWithFilter(
|
|
784
|
+
filter?: AccountsFilter
|
|
785
|
+
): Promise<{ data: BrokerAccount[] }> {
|
|
786
|
+
return this.mockDataProvider.mockGetBrokerDataAccounts(filter);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Page-based pagination methods
|
|
790
|
+
async getBrokerOrdersPage(
|
|
791
|
+
page: number = 1,
|
|
792
|
+
perPage: number = 100,
|
|
793
|
+
filters?: OrdersFilter
|
|
794
|
+
): Promise<PaginatedResult<BrokerDataOrder[]>> {
|
|
795
|
+
const mockOrders = await this.mockDataProvider.mockGetBrokerOrders(filters);
|
|
796
|
+
const orders = mockOrders.data;
|
|
797
|
+
|
|
798
|
+
// Simulate pagination
|
|
799
|
+
const startIndex = (page - 1) * perPage;
|
|
800
|
+
const endIndex = startIndex + perPage;
|
|
801
|
+
const paginatedOrders = orders.slice(startIndex, endIndex);
|
|
802
|
+
|
|
803
|
+
const hasMore = endIndex < orders.length;
|
|
804
|
+
const nextOffset = hasMore ? endIndex : startIndex;
|
|
805
|
+
|
|
806
|
+
// Create navigation callback for mock pagination
|
|
807
|
+
const navigationCallback = async (
|
|
808
|
+
newOffset: number,
|
|
809
|
+
newLimit: number
|
|
810
|
+
): Promise<PaginatedResult<BrokerDataOrder[]>> => {
|
|
811
|
+
const newStartIndex = newOffset;
|
|
812
|
+
const newEndIndex = newStartIndex + newLimit;
|
|
813
|
+
const newPaginatedOrders = orders.slice(newStartIndex, newEndIndex);
|
|
814
|
+
const newHasMore = newEndIndex < orders.length;
|
|
815
|
+
const newNextOffset = newHasMore ? newEndIndex : newStartIndex;
|
|
816
|
+
|
|
817
|
+
return new PaginatedResult(
|
|
818
|
+
newPaginatedOrders,
|
|
819
|
+
{
|
|
820
|
+
has_more: newHasMore,
|
|
821
|
+
next_offset: newNextOffset,
|
|
822
|
+
current_offset: newStartIndex,
|
|
823
|
+
limit: newLimit,
|
|
824
|
+
},
|
|
825
|
+
navigationCallback
|
|
826
|
+
);
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
return new PaginatedResult(
|
|
830
|
+
paginatedOrders,
|
|
831
|
+
{
|
|
832
|
+
has_more: hasMore,
|
|
833
|
+
next_offset: nextOffset,
|
|
834
|
+
current_offset: startIndex,
|
|
835
|
+
limit: perPage,
|
|
836
|
+
},
|
|
837
|
+
navigationCallback
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
async getBrokerAccountsPage(
|
|
842
|
+
page: number = 1,
|
|
843
|
+
perPage: number = 100,
|
|
844
|
+
filters?: AccountsFilter
|
|
845
|
+
): Promise<PaginatedResult<BrokerAccount[]>> {
|
|
846
|
+
const mockAccounts = await this.mockDataProvider.mockGetBrokerDataAccounts(filters);
|
|
847
|
+
const accounts = mockAccounts.data;
|
|
848
|
+
|
|
849
|
+
// Simulate pagination
|
|
850
|
+
const startIndex = (page - 1) * perPage;
|
|
851
|
+
const endIndex = startIndex + perPage;
|
|
852
|
+
const paginatedAccounts = accounts.slice(startIndex, endIndex);
|
|
853
|
+
|
|
854
|
+
const hasMore = endIndex < accounts.length;
|
|
855
|
+
const nextOffset = hasMore ? endIndex : startIndex;
|
|
856
|
+
|
|
857
|
+
// Create navigation callback for mock pagination
|
|
858
|
+
const navigationCallback = async (
|
|
859
|
+
newOffset: number,
|
|
860
|
+
newLimit: number
|
|
861
|
+
): Promise<PaginatedResult<BrokerAccount[]>> => {
|
|
862
|
+
const newStartIndex = newOffset;
|
|
863
|
+
const newEndIndex = newStartIndex + newLimit;
|
|
864
|
+
const newPaginatedAccounts = accounts.slice(newStartIndex, newEndIndex);
|
|
865
|
+
const newHasMore = newEndIndex < accounts.length;
|
|
866
|
+
const newNextOffset = newHasMore ? newEndIndex : newStartIndex;
|
|
867
|
+
|
|
868
|
+
return new PaginatedResult(
|
|
869
|
+
newPaginatedAccounts,
|
|
870
|
+
{
|
|
871
|
+
has_more: newHasMore,
|
|
872
|
+
next_offset: newNextOffset,
|
|
873
|
+
current_offset: newStartIndex,
|
|
874
|
+
limit: newLimit,
|
|
875
|
+
},
|
|
876
|
+
navigationCallback
|
|
877
|
+
);
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
return new PaginatedResult(
|
|
881
|
+
paginatedAccounts,
|
|
882
|
+
{
|
|
883
|
+
has_more: hasMore,
|
|
884
|
+
next_offset: nextOffset,
|
|
885
|
+
current_offset: startIndex,
|
|
886
|
+
limit: perPage,
|
|
887
|
+
},
|
|
888
|
+
navigationCallback
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
async getBrokerPositionsPage(
|
|
893
|
+
page: number = 1,
|
|
894
|
+
perPage: number = 100,
|
|
895
|
+
filters?: PositionsFilter
|
|
896
|
+
): Promise<PaginatedResult<BrokerDataPosition[]>> {
|
|
897
|
+
const mockPositions = await this.mockDataProvider.mockGetBrokerPositions(filters);
|
|
898
|
+
const positions = mockPositions.data;
|
|
899
|
+
|
|
900
|
+
// Simulate pagination
|
|
901
|
+
const startIndex = (page - 1) * perPage;
|
|
902
|
+
const endIndex = startIndex + perPage;
|
|
903
|
+
const paginatedPositions = positions.slice(startIndex, endIndex);
|
|
904
|
+
|
|
905
|
+
const hasMore = endIndex < positions.length;
|
|
906
|
+
const nextOffset = hasMore ? endIndex : startIndex;
|
|
907
|
+
|
|
908
|
+
// Create navigation callback for mock pagination
|
|
909
|
+
const navigationCallback = async (
|
|
910
|
+
newOffset: number,
|
|
911
|
+
newLimit: number
|
|
912
|
+
): Promise<PaginatedResult<BrokerDataPosition[]>> => {
|
|
913
|
+
const newStartIndex = newOffset;
|
|
914
|
+
const newEndIndex = newStartIndex + newLimit;
|
|
915
|
+
const newPaginatedPositions = positions.slice(newStartIndex, newEndIndex);
|
|
916
|
+
const newHasMore = newEndIndex < positions.length;
|
|
917
|
+
const newNextOffset = newHasMore ? newEndIndex : newStartIndex;
|
|
918
|
+
|
|
919
|
+
return new PaginatedResult(
|
|
920
|
+
newPaginatedPositions,
|
|
921
|
+
{
|
|
922
|
+
has_more: newHasMore,
|
|
923
|
+
next_offset: newNextOffset,
|
|
924
|
+
current_offset: newStartIndex,
|
|
925
|
+
limit: newLimit,
|
|
926
|
+
},
|
|
927
|
+
navigationCallback
|
|
928
|
+
);
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
return new PaginatedResult(
|
|
932
|
+
paginatedPositions,
|
|
933
|
+
{
|
|
934
|
+
has_more: hasMore,
|
|
935
|
+
next_offset: nextOffset,
|
|
936
|
+
current_offset: startIndex,
|
|
937
|
+
limit: perPage,
|
|
938
|
+
},
|
|
939
|
+
navigationCallback
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
async getBrokerBalancesPage(
|
|
944
|
+
page: number = 1,
|
|
945
|
+
perPage: number = 100,
|
|
946
|
+
filters?: BalancesFilter
|
|
947
|
+
): Promise<PaginatedResult<BrokerBalance[]>> {
|
|
948
|
+
const mockBalances = await this.mockDataProvider.mockGetBrokerBalances(filters);
|
|
949
|
+
const balances = mockBalances.data;
|
|
950
|
+
|
|
951
|
+
// Simulate pagination
|
|
952
|
+
const startIndex = (page - 1) * perPage;
|
|
953
|
+
const endIndex = startIndex + perPage;
|
|
954
|
+
const paginatedBalances = balances.slice(startIndex, endIndex);
|
|
955
|
+
|
|
956
|
+
const hasMore = endIndex < balances.length;
|
|
957
|
+
const nextOffset = hasMore ? endIndex : startIndex;
|
|
958
|
+
|
|
959
|
+
// Create navigation callback for mock pagination
|
|
960
|
+
const navigationCallback = async (
|
|
961
|
+
newOffset: number,
|
|
962
|
+
newLimit: number
|
|
963
|
+
): Promise<PaginatedResult<BrokerBalance[]>> => {
|
|
964
|
+
const newStartIndex = newOffset;
|
|
965
|
+
const newEndIndex = newStartIndex + newLimit;
|
|
966
|
+
const newPaginatedBalances = balances.slice(newStartIndex, newEndIndex);
|
|
967
|
+
const newHasMore = newEndIndex < balances.length;
|
|
968
|
+
const newNextOffset = newHasMore ? newEndIndex : newStartIndex;
|
|
969
|
+
|
|
970
|
+
return new PaginatedResult(
|
|
971
|
+
newPaginatedBalances,
|
|
972
|
+
{
|
|
973
|
+
has_more: newHasMore,
|
|
974
|
+
next_offset: newNextOffset,
|
|
975
|
+
current_offset: newStartIndex,
|
|
976
|
+
limit: newLimit,
|
|
977
|
+
},
|
|
978
|
+
navigationCallback
|
|
979
|
+
);
|
|
980
|
+
};
|
|
981
|
+
|
|
982
|
+
return new PaginatedResult(
|
|
983
|
+
paginatedBalances,
|
|
984
|
+
{
|
|
985
|
+
has_more: hasMore,
|
|
986
|
+
next_offset: nextOffset,
|
|
987
|
+
current_offset: startIndex,
|
|
988
|
+
limit: perPage,
|
|
989
|
+
},
|
|
990
|
+
navigationCallback
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
async getBrokerConnections(): Promise<{
|
|
995
|
+
_id: string;
|
|
996
|
+
response_data: BrokerConnection[];
|
|
997
|
+
message: string;
|
|
998
|
+
status_code: number;
|
|
999
|
+
warnings: null;
|
|
1000
|
+
errors: null;
|
|
1001
|
+
}> {
|
|
1002
|
+
const accessToken = await this.getValidAccessToken();
|
|
1003
|
+
return this.mockDataProvider.mockGetBrokerConnections();
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* Mock disconnect company method
|
|
1008
|
+
* @param connectionId - The connection ID to disconnect
|
|
1009
|
+
* @returns Promise with mock disconnect response
|
|
1010
|
+
*/
|
|
1011
|
+
async disconnectCompany(connectionId: string): Promise<DisconnectCompanyResponse> {
|
|
1012
|
+
const accessToken = await this.getValidAccessToken();
|
|
1013
|
+
return this.mockDataProvider.mockDisconnectCompany(connectionId);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// Utility methods for mock system
|
|
1017
|
+
getMockDataProvider(): MockDataProvider {
|
|
1018
|
+
return this.mockDataProvider;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
clearMockData(): void {
|
|
1022
|
+
this.mockDataProvider.clearData();
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* Check if this is a mock client
|
|
1027
|
+
* @returns true if this is a mock client
|
|
1028
|
+
*/
|
|
1029
|
+
isMockClient(): boolean {
|
|
1030
|
+
return true;
|
|
1031
|
+
}
|
|
1032
|
+
}
|