@finatic/client 0.0.141 → 0.0.142
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/dist/index.d.ts +39 -2
- package/dist/index.js +555 -94
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +552 -95
- package/dist/index.mjs.map +1 -1
- package/dist/types/core/client/ApiClient.d.ts +2 -0
- package/dist/types/core/client/FinaticConnect.d.ts +2 -0
- package/dist/types/core/portal/PortalUI.d.ts +2 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/lib/logger/index.d.ts +2 -0
- package/dist/types/lib/logger/logger.d.ts +4 -0
- package/dist/types/lib/logger/logger.types.d.ts +28 -0
- package/dist/types/mocks/MockApiClient.d.ts +2 -0
- package/package.json +1 -1
- package/src/core/client/ApiClient.ts +45 -19
- package/src/core/client/FinaticConnect.ts +79 -30
- package/src/core/portal/PortalUI.ts +58 -23
- package/src/index.ts +1 -0
- package/src/lib/logger/index.ts +3 -0
- package/src/lib/logger/logger.ts +332 -0
- package/src/lib/logger/logger.types.ts +34 -0
- package/src/mocks/MockApiClient.ts +43 -17
- package/src/types/common/pagination.ts +31 -5
- package/src/utils/brokerUtils.ts +21 -2
- package/src/utils/events.ts +13 -1
- package/src/utils/themeUtils.ts +23 -4
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,285 @@
|
|
|
1
1
|
import { v4 } from 'uuid';
|
|
2
2
|
|
|
3
|
+
const LOG_LEVEL_ORDER = {
|
|
4
|
+
error: 0,
|
|
5
|
+
warn: 1,
|
|
6
|
+
info: 2,
|
|
7
|
+
debug: 3,
|
|
8
|
+
};
|
|
9
|
+
const LEVEL_TO_CONSOLE = {
|
|
10
|
+
error: 'error',
|
|
11
|
+
warn: 'warn',
|
|
12
|
+
info: 'info',
|
|
13
|
+
debug: 'debug',
|
|
14
|
+
};
|
|
15
|
+
const DEFAULT_LOGGER_NAME = 'FinaticLogger';
|
|
16
|
+
const parseLogLevel = (value, fallback) => {
|
|
17
|
+
if (typeof value !== 'string') {
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
const normalized = value.toLowerCase().trim();
|
|
21
|
+
if (normalized === 'silent' || normalized === 'error' || normalized === 'warn' || normalized === 'info' || normalized === 'debug') {
|
|
22
|
+
return normalized;
|
|
23
|
+
}
|
|
24
|
+
return fallback;
|
|
25
|
+
};
|
|
26
|
+
const parseVerbosity = (value, fallback) => {
|
|
27
|
+
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
28
|
+
return fallback;
|
|
29
|
+
}
|
|
30
|
+
const numeric = typeof value === 'number' ? value : Number.parseInt(value, 10);
|
|
31
|
+
if (Number.isNaN(numeric)) {
|
|
32
|
+
return fallback;
|
|
33
|
+
}
|
|
34
|
+
if (numeric <= 0) {
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
if (numeric >= 3) {
|
|
38
|
+
return 3;
|
|
39
|
+
}
|
|
40
|
+
return numeric;
|
|
41
|
+
};
|
|
42
|
+
const resolveEnv = (key) => {
|
|
43
|
+
try {
|
|
44
|
+
if (typeof process !== 'undefined' && process.env && typeof process.env[key] === 'string') {
|
|
45
|
+
return process.env[key];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// ignore
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
53
|
+
const metaEnv = typeof import.meta !== 'undefined' ? import.meta.env : undefined;
|
|
54
|
+
if (metaEnv && typeof metaEnv[key] === 'string') {
|
|
55
|
+
return metaEnv[key];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// ignore
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
if (typeof globalThis !== 'undefined') {
|
|
63
|
+
const value = globalThis[key];
|
|
64
|
+
if (typeof value === 'string') {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// ignore
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
};
|
|
74
|
+
const resolveDefaultLogLevel = (explicitLevel) => {
|
|
75
|
+
if (explicitLevel) {
|
|
76
|
+
return explicitLevel;
|
|
77
|
+
}
|
|
78
|
+
const envLevel = resolveEnv('FINATIC_LOG_LEVEL') ||
|
|
79
|
+
resolveEnv('VITE_FINATIC_LOG_LEVEL') ||
|
|
80
|
+
resolveEnv('NEXT_PUBLIC_FINATIC_LOG_LEVEL') ||
|
|
81
|
+
resolveEnv('NEXT_FINATIC_LOG_LEVEL') ||
|
|
82
|
+
resolveEnv('REACT_APP_FINATIC_LOG_LEVEL') ||
|
|
83
|
+
resolveEnv('NUXT_PUBLIC_FINATIC_LOG_LEVEL') ||
|
|
84
|
+
resolveEnv('NX_FINATIC_LOG_LEVEL');
|
|
85
|
+
if (envLevel) {
|
|
86
|
+
return parseLogLevel(envLevel, 'silent');
|
|
87
|
+
}
|
|
88
|
+
return 'silent';
|
|
89
|
+
};
|
|
90
|
+
const resolveVerbosity = () => {
|
|
91
|
+
const envVerbosity = resolveEnv('FINATIC_LOG_VERBOSITY') ||
|
|
92
|
+
resolveEnv('VITE_FINATIC_LOG_VERBOSITY') ||
|
|
93
|
+
resolveEnv('NEXT_PUBLIC_FINATIC_LOG_VERBOSITY') ||
|
|
94
|
+
resolveEnv('NEXT_FINATIC_LOG_VERBOSITY') ||
|
|
95
|
+
resolveEnv('REACT_APP_FINATIC_LOG_VERBOSITY') ||
|
|
96
|
+
resolveEnv('NUXT_PUBLIC_FINATIC_LOG_VERBOSITY') ||
|
|
97
|
+
resolveEnv('NX_FINATIC_LOG_VERBOSITY');
|
|
98
|
+
if (envVerbosity) {
|
|
99
|
+
return parseVerbosity(envVerbosity, 1);
|
|
100
|
+
}
|
|
101
|
+
return 1;
|
|
102
|
+
};
|
|
103
|
+
const resolveBaseMetadata = () => {
|
|
104
|
+
const base = {
|
|
105
|
+
timestamp: new Date().toISOString(),
|
|
106
|
+
};
|
|
107
|
+
try {
|
|
108
|
+
if (typeof globalThis !== 'undefined') {
|
|
109
|
+
if (typeof globalThis.location !== 'undefined') {
|
|
110
|
+
base.host = globalThis.location.hostname;
|
|
111
|
+
}
|
|
112
|
+
if (typeof globalThis.navigator !== 'undefined') {
|
|
113
|
+
base.user_agent = globalThis.navigator.userAgent;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// ignore
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
if (typeof process !== 'undefined') {
|
|
122
|
+
base.pid = process.pid;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// ignore
|
|
127
|
+
}
|
|
128
|
+
return base;
|
|
129
|
+
};
|
|
130
|
+
const normalizeError = (error) => {
|
|
131
|
+
if (!error) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
if (error instanceof Error) {
|
|
135
|
+
return {
|
|
136
|
+
type: error.name,
|
|
137
|
+
message: error.message,
|
|
138
|
+
stacktrace: error.stack,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (typeof error === 'object') {
|
|
142
|
+
return { ...error };
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
type: 'Error',
|
|
146
|
+
message: String(error),
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
const shouldLog = (requestedLevel, currentLevel) => {
|
|
150
|
+
if (currentLevel === 'silent') {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
const currentOrder = LOG_LEVEL_ORDER[currentLevel];
|
|
154
|
+
const requestedOrder = LOG_LEVEL_ORDER[requestedLevel];
|
|
155
|
+
return requestedOrder <= currentOrder;
|
|
156
|
+
};
|
|
157
|
+
const buildPayload = (name, level, message, defaultMetadata, extra, verbosity) => {
|
|
158
|
+
const payload = {
|
|
159
|
+
timestamp: new Date().toISOString(),
|
|
160
|
+
message,
|
|
161
|
+
};
|
|
162
|
+
if (verbosity >= 1 && extra) {
|
|
163
|
+
if (extra.module) {
|
|
164
|
+
payload.module = extra.module;
|
|
165
|
+
}
|
|
166
|
+
if (extra.function) {
|
|
167
|
+
payload.function = extra.function;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (verbosity >= 2) {
|
|
171
|
+
if (extra?.duration_ms !== undefined) {
|
|
172
|
+
payload.duration_ms = extra.duration_ms;
|
|
173
|
+
}
|
|
174
|
+
if (extra?.event) {
|
|
175
|
+
payload.event = extra.event;
|
|
176
|
+
}
|
|
177
|
+
if (extra?.error) {
|
|
178
|
+
payload.error = normalizeError(extra.error);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (verbosity >= 3) {
|
|
182
|
+
payload.level = level.toUpperCase();
|
|
183
|
+
payload.name = name;
|
|
184
|
+
const baseMetadata = resolveBaseMetadata();
|
|
185
|
+
const mergedMetadata = {
|
|
186
|
+
...baseMetadata,
|
|
187
|
+
...(defaultMetadata || {}),
|
|
188
|
+
...(extra?.metadata || {}),
|
|
189
|
+
};
|
|
190
|
+
if (Object.keys(mergedMetadata).length > 0) {
|
|
191
|
+
payload.metadata = mergedMetadata;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const restKeys = ['module', 'function', 'event', 'duration_ms', 'error', 'metadata'];
|
|
195
|
+
if (extra) {
|
|
196
|
+
Object.entries(extra).forEach(([key, value]) => {
|
|
197
|
+
if (!restKeys.includes(key) && value !== undefined) {
|
|
198
|
+
payload[key] = value;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return payload;
|
|
203
|
+
};
|
|
204
|
+
const consoleWrite = (consoleLevel, name, level, message, payload) => {
|
|
205
|
+
if (typeof console === 'undefined' || typeof console[consoleLevel] !== 'function') {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const prefix = `${level.toUpperCase()}: ${name} || ${message}`;
|
|
209
|
+
console[consoleLevel](prefix, payload);
|
|
210
|
+
};
|
|
211
|
+
const setupLogger = (nameOrOptions, level, defaultMetadata) => {
|
|
212
|
+
const options = typeof nameOrOptions === 'string'
|
|
213
|
+
? {
|
|
214
|
+
name: nameOrOptions,
|
|
215
|
+
level,
|
|
216
|
+
defaultMetadata,
|
|
217
|
+
}
|
|
218
|
+
: nameOrOptions;
|
|
219
|
+
const loggerName = options.name || DEFAULT_LOGGER_NAME;
|
|
220
|
+
let currentLevel = resolveDefaultLogLevel(options.level);
|
|
221
|
+
const loggerDefaultMetadata = options.defaultMetadata;
|
|
222
|
+
const verbosity = resolveVerbosity();
|
|
223
|
+
const log = (requestedLevel, message, extra) => {
|
|
224
|
+
if (!shouldLog(requestedLevel, currentLevel)) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const payload = buildPayload(loggerName, requestedLevel, message, loggerDefaultMetadata, extra, verbosity);
|
|
228
|
+
consoleWrite(LEVEL_TO_CONSOLE[requestedLevel], loggerName, requestedLevel, message, payload);
|
|
229
|
+
};
|
|
230
|
+
return {
|
|
231
|
+
getLevel: () => currentLevel,
|
|
232
|
+
setLevel: (nextLevel) => {
|
|
233
|
+
currentLevel = nextLevel;
|
|
234
|
+
},
|
|
235
|
+
debug: (message, extra) => log('debug', message, extra),
|
|
236
|
+
info: (message, extra) => log('info', message, extra),
|
|
237
|
+
warn: (message, extra) => log('warn', message, extra),
|
|
238
|
+
error: (message, extra) => log('error', message, extra),
|
|
239
|
+
exception: (message, error, extra) => {
|
|
240
|
+
log('error', message, {
|
|
241
|
+
...extra,
|
|
242
|
+
error,
|
|
243
|
+
event: extra?.event || 'exception',
|
|
244
|
+
});
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
const buildLoggerExtra = (metadata) => ({
|
|
249
|
+
metadata,
|
|
250
|
+
});
|
|
251
|
+
const logStartEnd = (logger) => (fn) => async (...args) => {
|
|
252
|
+
const start = Date.now();
|
|
253
|
+
const functionName = fn.name || 'anonymous';
|
|
254
|
+
logger.debug('START', { module: 'logStartEnd', function: functionName, event: 'start' });
|
|
255
|
+
try {
|
|
256
|
+
const result = await fn(...args);
|
|
257
|
+
const duration = Date.now() - start;
|
|
258
|
+
logger.info('END', { module: 'logStartEnd', function: functionName, event: 'end', duration_ms: duration });
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
const duration = Date.now() - start;
|
|
263
|
+
logger.exception('EXCEPTION', error, {
|
|
264
|
+
module: 'logStartEnd',
|
|
265
|
+
function: functionName,
|
|
266
|
+
duration_ms: duration,
|
|
267
|
+
});
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
3
272
|
/**
|
|
4
273
|
* Pagination-related types and classes
|
|
5
274
|
*/
|
|
275
|
+
const paginationLogger = setupLogger('FinaticClientSDK.Pagination', undefined, {
|
|
276
|
+
codebase: 'FinaticClientSDK',
|
|
277
|
+
});
|
|
278
|
+
const buildPaginationExtra = (functionName, metadata) => ({
|
|
279
|
+
module: 'PaginatedResult',
|
|
280
|
+
function: functionName,
|
|
281
|
+
...(metadata ? buildLoggerExtra(metadata) : {}),
|
|
282
|
+
});
|
|
6
283
|
class PaginatedResult {
|
|
7
284
|
constructor(data, paginationInfo, navigationCallback) {
|
|
8
285
|
this.data = data;
|
|
@@ -34,7 +311,10 @@ class PaginatedResult {
|
|
|
34
311
|
return await this.navigationCallback(this.metadata.nextOffset, this.metadata.limit);
|
|
35
312
|
}
|
|
36
313
|
catch (error) {
|
|
37
|
-
|
|
314
|
+
paginationLogger.exception('Error fetching next page', error, buildPaginationExtra('nextPage', {
|
|
315
|
+
next_offset: this.metadata.nextOffset,
|
|
316
|
+
limit: this.metadata.limit,
|
|
317
|
+
}));
|
|
38
318
|
return null;
|
|
39
319
|
}
|
|
40
320
|
}
|
|
@@ -47,7 +327,10 @@ class PaginatedResult {
|
|
|
47
327
|
return await this.navigationCallback(previousOffset, this.metadata.limit);
|
|
48
328
|
}
|
|
49
329
|
catch (error) {
|
|
50
|
-
|
|
330
|
+
paginationLogger.exception('Error fetching previous page', error, buildPaginationExtra('previousPage', {
|
|
331
|
+
previous_offset: previousOffset,
|
|
332
|
+
limit: this.metadata.limit,
|
|
333
|
+
}));
|
|
51
334
|
return null;
|
|
52
335
|
}
|
|
53
336
|
}
|
|
@@ -60,7 +343,11 @@ class PaginatedResult {
|
|
|
60
343
|
return await this.navigationCallback(offset, this.metadata.limit);
|
|
61
344
|
}
|
|
62
345
|
catch (error) {
|
|
63
|
-
|
|
346
|
+
paginationLogger.exception('Error fetching page', error, buildPaginationExtra('goToPage', {
|
|
347
|
+
page_number: pageNumber,
|
|
348
|
+
offset,
|
|
349
|
+
limit: this.metadata.limit,
|
|
350
|
+
}));
|
|
64
351
|
return null;
|
|
65
352
|
}
|
|
66
353
|
}
|
|
@@ -72,7 +359,9 @@ class PaginatedResult {
|
|
|
72
359
|
return await this.navigationCallback(0, this.metadata.limit);
|
|
73
360
|
}
|
|
74
361
|
catch (error) {
|
|
75
|
-
|
|
362
|
+
paginationLogger.exception('Error fetching first page', error, buildPaginationExtra('firstPage', {
|
|
363
|
+
limit: this.metadata.limit,
|
|
364
|
+
}));
|
|
76
365
|
return null;
|
|
77
366
|
}
|
|
78
367
|
}
|
|
@@ -94,7 +383,9 @@ class PaginatedResult {
|
|
|
94
383
|
return await findLast(this);
|
|
95
384
|
}
|
|
96
385
|
catch (error) {
|
|
97
|
-
|
|
386
|
+
paginationLogger.exception('Error fetching last page', error, buildPaginationExtra('lastPage', {
|
|
387
|
+
limit: this.metadata.limit,
|
|
388
|
+
}));
|
|
98
389
|
return null;
|
|
99
390
|
}
|
|
100
391
|
}
|
|
@@ -205,6 +496,13 @@ class TradingNotEnabledError extends ApiError {
|
|
|
205
496
|
|
|
206
497
|
// Supabase import removed - SDK no longer depends on Supabase
|
|
207
498
|
class ApiClient {
|
|
499
|
+
buildLoggerExtra(functionName, metadata) {
|
|
500
|
+
return {
|
|
501
|
+
module: 'ApiClient',
|
|
502
|
+
function: functionName,
|
|
503
|
+
...(metadata ? buildLoggerExtra(metadata) : {}),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
208
506
|
constructor(baseUrl, deviceInfo) {
|
|
209
507
|
this.currentSessionState = null;
|
|
210
508
|
this.currentSessionId = null;
|
|
@@ -213,6 +511,9 @@ class ApiClient {
|
|
|
213
511
|
// Session and company context
|
|
214
512
|
this.companyId = null;
|
|
215
513
|
this.csrfToken = null;
|
|
514
|
+
this.logger = setupLogger('FinaticClientSDK.ApiClient', undefined, {
|
|
515
|
+
codebase: 'FinaticClientSDK',
|
|
516
|
+
});
|
|
216
517
|
this.baseUrl = baseUrl;
|
|
217
518
|
this.deviceInfo = deviceInfo;
|
|
218
519
|
// Ensure baseUrl doesn't end with a slash
|
|
@@ -333,29 +634,28 @@ class ApiClient {
|
|
|
333
634
|
safariSafeHeaders[normalizedKey] = normalizedValue;
|
|
334
635
|
}
|
|
335
636
|
});
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
console.log('Session ID:', this.currentSessionId);
|
|
342
|
-
console.log('Company ID:', this.companyId);
|
|
343
|
-
console.log('CSRF Token:', this.csrfToken);
|
|
344
|
-
}
|
|
637
|
+
this.logger.debug('Dispatching API request', this.buildLoggerExtra('request', {
|
|
638
|
+
url: url.toString(),
|
|
639
|
+
method: options.method,
|
|
640
|
+
has_body: Boolean(options.body),
|
|
641
|
+
}));
|
|
345
642
|
const response = await fetch(url.toString(), {
|
|
346
643
|
method: options.method,
|
|
347
644
|
headers: safariSafeHeaders,
|
|
348
645
|
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
349
646
|
});
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
console.log('Request was made with headers:', safariSafeHeaders);
|
|
355
|
-
}
|
|
647
|
+
this.logger.debug('Received API response', this.buildLoggerExtra('request', {
|
|
648
|
+
url: url.toString(),
|
|
649
|
+
status: response.status,
|
|
650
|
+
}));
|
|
356
651
|
if (!response.ok) {
|
|
357
652
|
const error = await response.json();
|
|
358
|
-
|
|
653
|
+
const apiError = this.handleError(response.status, error);
|
|
654
|
+
this.logger.exception('API request failed', apiError, this.buildLoggerExtra('request', {
|
|
655
|
+
url: url.toString(),
|
|
656
|
+
status: response.status,
|
|
657
|
+
}));
|
|
658
|
+
throw apiError;
|
|
359
659
|
}
|
|
360
660
|
const data = await response.json();
|
|
361
661
|
// Check if the response has a success field and it's false
|
|
@@ -366,11 +666,21 @@ class ApiClient {
|
|
|
366
666
|
// Add context that this is an order-related error
|
|
367
667
|
data._isOrderError = true;
|
|
368
668
|
}
|
|
369
|
-
|
|
669
|
+
const apiError = this.handleError(data.status_code || 500, data);
|
|
670
|
+
this.logger.exception('API response indicated failure', apiError, this.buildLoggerExtra('request', {
|
|
671
|
+
url: url.toString(),
|
|
672
|
+
status: data.status_code || 500,
|
|
673
|
+
}));
|
|
674
|
+
throw apiError;
|
|
370
675
|
}
|
|
371
676
|
// Check if the response has a status_code field indicating an error (4xx or 5xx)
|
|
372
677
|
if (data && typeof data === 'object' && 'status_code' in data && data.status_code >= 400) {
|
|
373
|
-
|
|
678
|
+
const apiError = this.handleError(data.status_code, data);
|
|
679
|
+
this.logger.exception('API status code error', apiError, this.buildLoggerExtra('request', {
|
|
680
|
+
url: url.toString(),
|
|
681
|
+
status: data.status_code,
|
|
682
|
+
}));
|
|
683
|
+
throw apiError;
|
|
374
684
|
}
|
|
375
685
|
// Check if the response has errors field with content
|
|
376
686
|
if (data &&
|
|
@@ -379,7 +689,12 @@ class ApiClient {
|
|
|
379
689
|
data.errors &&
|
|
380
690
|
Array.isArray(data.errors) &&
|
|
381
691
|
data.errors.length > 0) {
|
|
382
|
-
|
|
692
|
+
const apiError = this.handleError(data.status_code || 500, data);
|
|
693
|
+
this.logger.exception('API response contained errors', apiError, this.buildLoggerExtra('request', {
|
|
694
|
+
url: url.toString(),
|
|
695
|
+
status: data.status_code || 500,
|
|
696
|
+
}));
|
|
697
|
+
throw apiError;
|
|
383
698
|
}
|
|
384
699
|
return data;
|
|
385
700
|
}
|
|
@@ -1560,6 +1875,9 @@ class ApiClient {
|
|
|
1560
1875
|
}
|
|
1561
1876
|
}
|
|
1562
1877
|
|
|
1878
|
+
const eventsLogger = setupLogger('FinaticClientSDK.Events', undefined, {
|
|
1879
|
+
codebase: 'FinaticClientSDK',
|
|
1880
|
+
});
|
|
1563
1881
|
class EventEmitter {
|
|
1564
1882
|
constructor() {
|
|
1565
1883
|
this.events = new Map();
|
|
@@ -1589,7 +1907,13 @@ class EventEmitter {
|
|
|
1589
1907
|
callback(...args);
|
|
1590
1908
|
}
|
|
1591
1909
|
catch (error) {
|
|
1592
|
-
|
|
1910
|
+
eventsLogger.exception('Error in event handler', error, {
|
|
1911
|
+
module: 'EventEmitter',
|
|
1912
|
+
function: 'emit',
|
|
1913
|
+
...buildLoggerExtra({
|
|
1914
|
+
event_name: event,
|
|
1915
|
+
}),
|
|
1916
|
+
});
|
|
1593
1917
|
}
|
|
1594
1918
|
});
|
|
1595
1919
|
}
|
|
@@ -1610,6 +1934,9 @@ class EventEmitter {
|
|
|
1610
1934
|
}
|
|
1611
1935
|
}
|
|
1612
1936
|
|
|
1937
|
+
const portalLogger = setupLogger('FinaticClientSDK.PortalUI', undefined, {
|
|
1938
|
+
codebase: 'FinaticClientSDK',
|
|
1939
|
+
});
|
|
1613
1940
|
class PortalUI {
|
|
1614
1941
|
constructor(portalUrl) {
|
|
1615
1942
|
this.iframe = null;
|
|
@@ -1618,10 +1945,18 @@ class PortalUI {
|
|
|
1618
1945
|
this.sessionId = null;
|
|
1619
1946
|
this.portalOrigin = null;
|
|
1620
1947
|
this.originalBodyStyle = null;
|
|
1948
|
+
this.logger = portalLogger;
|
|
1621
1949
|
this.createContainer();
|
|
1622
1950
|
}
|
|
1951
|
+
buildLoggerExtra(functionName, metadata) {
|
|
1952
|
+
return {
|
|
1953
|
+
module: 'PortalUI',
|
|
1954
|
+
function: functionName,
|
|
1955
|
+
...(metadata ? buildLoggerExtra(metadata) : {}),
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1623
1958
|
createContainer() {
|
|
1624
|
-
|
|
1959
|
+
this.logger.debug('Creating portal container and iframe', this.buildLoggerExtra('createContainer'));
|
|
1625
1960
|
this.container = document.createElement('div');
|
|
1626
1961
|
this.container.style.cssText = `
|
|
1627
1962
|
position: fixed;
|
|
@@ -1668,7 +2003,7 @@ class PortalUI {
|
|
|
1668
2003
|
this.iframe.contentDocument?.head.appendChild(meta);
|
|
1669
2004
|
this.container.appendChild(this.iframe);
|
|
1670
2005
|
document.body.appendChild(this.container);
|
|
1671
|
-
|
|
2006
|
+
this.logger.debug('Portal container and iframe created successfully', this.buildLoggerExtra('createContainer'));
|
|
1672
2007
|
}
|
|
1673
2008
|
/**
|
|
1674
2009
|
* Lock background scrolling by setting overflow: hidden on body
|
|
@@ -1682,7 +2017,7 @@ class PortalUI {
|
|
|
1682
2017
|
document.body.style.position = 'fixed';
|
|
1683
2018
|
document.body.style.width = '100%';
|
|
1684
2019
|
document.body.style.top = `-${window.scrollY}px`;
|
|
1685
|
-
|
|
2020
|
+
this.logger.debug('Background scroll locked', this.buildLoggerExtra('lockScroll'));
|
|
1686
2021
|
}
|
|
1687
2022
|
}
|
|
1688
2023
|
/**
|
|
@@ -1698,7 +2033,7 @@ class PortalUI {
|
|
|
1698
2033
|
document.body.style.top = '';
|
|
1699
2034
|
window.scrollTo(0, parseInt(scrollY || '0') * -1);
|
|
1700
2035
|
this.originalBodyStyle = null;
|
|
1701
|
-
|
|
2036
|
+
this.logger.debug('Background scroll unlocked', this.buildLoggerExtra('unlockScroll'));
|
|
1702
2037
|
}
|
|
1703
2038
|
}
|
|
1704
2039
|
show(url, sessionId, options = {}) {
|
|
@@ -1740,11 +2075,17 @@ class PortalUI {
|
|
|
1740
2075
|
handleMessage(event) {
|
|
1741
2076
|
// Verify origin matches the portal URL
|
|
1742
2077
|
if (!this.portalOrigin || event.origin !== this.portalOrigin) {
|
|
1743
|
-
|
|
2078
|
+
this.logger.warn('Received message from unauthorized origin', this.buildLoggerExtra('handleMessage', {
|
|
2079
|
+
received_origin: event.origin,
|
|
2080
|
+
expected_origin: this.portalOrigin || 'unknown',
|
|
2081
|
+
}));
|
|
1744
2082
|
return;
|
|
1745
2083
|
}
|
|
1746
2084
|
const { type, userId, access_token, refresh_token, error, height, data } = event.data;
|
|
1747
|
-
|
|
2085
|
+
this.logger.debug('Received portal message', this.buildLoggerExtra('handleMessage', {
|
|
2086
|
+
message_type: type,
|
|
2087
|
+
has_data: Boolean(data),
|
|
2088
|
+
}));
|
|
1748
2089
|
switch (type) {
|
|
1749
2090
|
case 'portal-success': {
|
|
1750
2091
|
// Handle both direct userId and data.userId formats
|
|
@@ -1782,36 +2123,50 @@ class PortalUI {
|
|
|
1782
2123
|
this.handleClose();
|
|
1783
2124
|
break;
|
|
1784
2125
|
default:
|
|
1785
|
-
|
|
2126
|
+
this.logger.warn('Received unhandled message type', this.buildLoggerExtra('handleMessage', {
|
|
2127
|
+
message_type: type,
|
|
2128
|
+
}));
|
|
1786
2129
|
}
|
|
1787
2130
|
}
|
|
1788
2131
|
handlePortalSuccess(userId, tokens) {
|
|
1789
2132
|
if (!userId) {
|
|
1790
|
-
|
|
2133
|
+
this.logger.error('Missing userId in portal-success message', this.buildLoggerExtra('handlePortalSuccess'));
|
|
1791
2134
|
return;
|
|
1792
2135
|
}
|
|
1793
|
-
|
|
2136
|
+
this.logger.info('Portal success - user connected', this.buildLoggerExtra('handlePortalSuccess', {
|
|
2137
|
+
user_id_present: Boolean(userId),
|
|
2138
|
+
tokens_provided: Boolean(tokens?.access_token && tokens?.refresh_token),
|
|
2139
|
+
}));
|
|
1794
2140
|
if (tokens?.access_token && tokens?.refresh_token) {
|
|
1795
|
-
|
|
2141
|
+
this.logger.debug('Tokens received for user', this.buildLoggerExtra('handlePortalSuccess', {
|
|
2142
|
+
tokens_provided: true,
|
|
2143
|
+
}));
|
|
1796
2144
|
}
|
|
1797
2145
|
// Pass userId to parent (SDK will handle tokens internally)
|
|
1798
2146
|
this.options?.onSuccess?.(userId, tokens);
|
|
1799
2147
|
}
|
|
1800
2148
|
handlePortalError(error) {
|
|
1801
|
-
|
|
2149
|
+
this.logger.error('Portal error received', this.buildLoggerExtra('handlePortalError', {
|
|
2150
|
+
error_message: error,
|
|
2151
|
+
}));
|
|
1802
2152
|
this.options?.onError?.(new Error(error || 'Unknown portal error'));
|
|
1803
2153
|
}
|
|
1804
2154
|
handlePortalClose() {
|
|
1805
|
-
|
|
2155
|
+
this.logger.info('Portal closed by user', this.buildLoggerExtra('handlePortalClose'));
|
|
1806
2156
|
this.options?.onClose?.();
|
|
1807
2157
|
this.hide();
|
|
1808
2158
|
}
|
|
1809
2159
|
handleGenericEvent(data) {
|
|
1810
2160
|
if (!data || !data.type) {
|
|
1811
|
-
|
|
2161
|
+
this.logger.warn('Invalid event data received', this.buildLoggerExtra('handleGenericEvent', {
|
|
2162
|
+
has_type: Boolean(data?.type),
|
|
2163
|
+
}));
|
|
1812
2164
|
return;
|
|
1813
2165
|
}
|
|
1814
|
-
|
|
2166
|
+
this.logger.debug('Generic event received', this.buildLoggerExtra('handleGenericEvent', {
|
|
2167
|
+
event_type: data.type,
|
|
2168
|
+
payload_present: Boolean(data.data),
|
|
2169
|
+
}));
|
|
1815
2170
|
// Emit the event to be handled by the SDK
|
|
1816
2171
|
// This will be implemented in FinaticConnect
|
|
1817
2172
|
if (this.options?.onEvent) {
|
|
@@ -1820,24 +2175,28 @@ class PortalUI {
|
|
|
1820
2175
|
}
|
|
1821
2176
|
handleSuccess(userId) {
|
|
1822
2177
|
if (!userId) {
|
|
1823
|
-
|
|
2178
|
+
this.logger.error('Missing required fields in success message', this.buildLoggerExtra('handleSuccess'));
|
|
1824
2179
|
return;
|
|
1825
2180
|
}
|
|
1826
2181
|
// Pass userId to parent
|
|
1827
2182
|
this.options?.onSuccess?.(userId);
|
|
1828
2183
|
}
|
|
1829
2184
|
handleError(error) {
|
|
1830
|
-
|
|
2185
|
+
this.logger.error('Received portal error message', this.buildLoggerExtra('handleError', {
|
|
2186
|
+
error_message: error,
|
|
2187
|
+
}));
|
|
1831
2188
|
this.options?.onError?.(new Error(error || 'Unknown error'));
|
|
1832
2189
|
}
|
|
1833
2190
|
handleClose() {
|
|
1834
|
-
|
|
2191
|
+
this.logger.debug('Received close message', this.buildLoggerExtra('handleClose'));
|
|
1835
2192
|
this.options?.onClose?.();
|
|
1836
2193
|
this.hide();
|
|
1837
2194
|
}
|
|
1838
2195
|
handleResize(height) {
|
|
1839
2196
|
if (height && this.iframe) {
|
|
1840
|
-
|
|
2197
|
+
this.logger.debug('Received resize message', this.buildLoggerExtra('handleResize', {
|
|
2198
|
+
height,
|
|
2199
|
+
}));
|
|
1841
2200
|
this.iframe.style.height = `${height}px`;
|
|
1842
2201
|
}
|
|
1843
2202
|
}
|
|
@@ -2665,11 +3024,21 @@ class MockDataProvider {
|
|
|
2665
3024
|
}
|
|
2666
3025
|
}
|
|
2667
3026
|
|
|
3027
|
+
const mockApiLogger = setupLogger('FinaticClientSDK.MockApiClient', undefined, {
|
|
3028
|
+
codebase: 'FinaticClientSDK',
|
|
3029
|
+
});
|
|
2668
3030
|
/**
|
|
2669
3031
|
* Mock API Client that implements the same interface as the real ApiClient
|
|
2670
3032
|
* but returns mock data instead of making HTTP requests
|
|
2671
3033
|
*/
|
|
2672
3034
|
class MockApiClient {
|
|
3035
|
+
buildLoggerExtra(functionName, metadata) {
|
|
3036
|
+
return {
|
|
3037
|
+
module: 'MockApiClient',
|
|
3038
|
+
function: functionName,
|
|
3039
|
+
...(metadata ? buildLoggerExtra(metadata) : {}),
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
2673
3042
|
constructor(baseUrl, deviceInfo, mockConfig) {
|
|
2674
3043
|
this.currentSessionState = null;
|
|
2675
3044
|
this.currentSessionId = null;
|
|
@@ -2681,16 +3050,21 @@ class MockApiClient {
|
|
|
2681
3050
|
// Session and company context
|
|
2682
3051
|
this.companyId = null;
|
|
2683
3052
|
this.csrfToken = null;
|
|
3053
|
+
this.logger = mockApiLogger;
|
|
2684
3054
|
this.baseUrl = baseUrl;
|
|
2685
3055
|
this.deviceInfo = deviceInfo;
|
|
2686
3056
|
this.mockApiOnly = mockConfig?.mockApiOnly || false;
|
|
2687
3057
|
this.mockDataProvider = new MockDataProvider(mockConfig);
|
|
2688
3058
|
// Log that mocks are being used
|
|
2689
3059
|
if (this.mockApiOnly) {
|
|
2690
|
-
|
|
3060
|
+
this.logger.info('Using mock API client (API only, real portal)', this.buildLoggerExtra('constructor', {
|
|
3061
|
+
mock_api_only: true,
|
|
3062
|
+
}));
|
|
2691
3063
|
}
|
|
2692
3064
|
else {
|
|
2693
|
-
|
|
3065
|
+
this.logger.info('Using mock API client', this.buildLoggerExtra('constructor', {
|
|
3066
|
+
mock_api_only: false,
|
|
3067
|
+
}));
|
|
2694
3068
|
}
|
|
2695
3069
|
}
|
|
2696
3070
|
/**
|
|
@@ -2884,14 +3258,12 @@ class MockApiClient {
|
|
|
2884
3258
|
async placeBrokerOrder(params, extras = {}, connection_id) {
|
|
2885
3259
|
await this.getValidAccessToken();
|
|
2886
3260
|
// Debug logging
|
|
2887
|
-
|
|
2888
|
-
params,
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
contextAccountNumber: this.tradingContext.accountNumber,
|
|
2894
|
-
});
|
|
3261
|
+
this.logger.debug('placeBrokerOrder parameters', this.buildLoggerExtra('placeBrokerOrder', {
|
|
3262
|
+
has_params_broker: Boolean(params.broker),
|
|
3263
|
+
has_context_broker: Boolean(this.tradingContext.broker),
|
|
3264
|
+
has_params_account: Boolean(params.accountNumber),
|
|
3265
|
+
has_context_account: Boolean(this.tradingContext.accountNumber),
|
|
3266
|
+
}));
|
|
2895
3267
|
const fullParams = {
|
|
2896
3268
|
broker: (params.broker || this.tradingContext.broker) ||
|
|
2897
3269
|
(() => {
|
|
@@ -2912,7 +3284,13 @@ class MockApiClient {
|
|
|
2912
3284
|
stopPrice: params.stopPrice,
|
|
2913
3285
|
order_id: params.order_id,
|
|
2914
3286
|
};
|
|
2915
|
-
|
|
3287
|
+
this.logger.debug('placeBrokerOrder normalized parameters', this.buildLoggerExtra('placeBrokerOrder', {
|
|
3288
|
+
broker: fullParams.broker,
|
|
3289
|
+
account_number_present: Boolean(fullParams.accountNumber),
|
|
3290
|
+
symbol: fullParams.symbol,
|
|
3291
|
+
order_type: fullParams.orderType,
|
|
3292
|
+
asset_type: fullParams.assetType,
|
|
3293
|
+
}));
|
|
2916
3294
|
return this.mockDataProvider.mockPlaceOrder(fullParams);
|
|
2917
3295
|
}
|
|
2918
3296
|
async cancelBrokerOrder(orderId, broker, extras = {}, connection_id) {
|
|
@@ -2950,14 +3328,18 @@ class MockApiClient {
|
|
|
2950
3328
|
this.tradingContext.accountId = undefined;
|
|
2951
3329
|
}
|
|
2952
3330
|
setAccount(accountNumber, accountId) {
|
|
2953
|
-
|
|
2954
|
-
accountNumber,
|
|
2955
|
-
accountId,
|
|
2956
|
-
|
|
2957
|
-
});
|
|
3331
|
+
this.logger.debug('setAccount invoked', this.buildLoggerExtra('setAccount', {
|
|
3332
|
+
account_number: accountNumber,
|
|
3333
|
+
account_id_present: Boolean(accountId),
|
|
3334
|
+
had_existing_account: Boolean(this.tradingContext.accountNumber),
|
|
3335
|
+
}));
|
|
2958
3336
|
this.tradingContext.accountNumber = accountNumber;
|
|
2959
3337
|
this.tradingContext.accountId = accountId;
|
|
2960
|
-
|
|
3338
|
+
this.logger.debug('setAccount updated context', this.buildLoggerExtra('setAccount', {
|
|
3339
|
+
broker: this.tradingContext.broker,
|
|
3340
|
+
account_number_present: Boolean(this.tradingContext.accountNumber),
|
|
3341
|
+
account_id_present: Boolean(this.tradingContext.accountId),
|
|
3342
|
+
}));
|
|
2961
3343
|
}
|
|
2962
3344
|
// Stock convenience methods
|
|
2963
3345
|
async placeStockMarketOrder(symbol, orderQty, action, broker, accountNumber, extras = {}) {
|
|
@@ -4739,6 +5121,14 @@ const portalThemePresets = {
|
|
|
4739
5121
|
stockAlgos: stockAlgosTheme,
|
|
4740
5122
|
};
|
|
4741
5123
|
|
|
5124
|
+
const themeLogger = setupLogger('FinaticClientSDK.ThemeUtils', undefined, {
|
|
5125
|
+
codebase: 'FinaticClientSDK',
|
|
5126
|
+
});
|
|
5127
|
+
const buildThemeExtra = (functionName, metadata) => ({
|
|
5128
|
+
module: 'ThemeUtils',
|
|
5129
|
+
function: functionName,
|
|
5130
|
+
...(metadata ? buildLoggerExtra(metadata) : {}),
|
|
5131
|
+
});
|
|
4742
5132
|
/**
|
|
4743
5133
|
* Generate a portal URL with theme parameters
|
|
4744
5134
|
* @param baseUrl The base portal URL
|
|
@@ -4764,7 +5154,10 @@ function generatePortalThemeURL(baseUrl, theme) {
|
|
|
4764
5154
|
return url.toString();
|
|
4765
5155
|
}
|
|
4766
5156
|
catch (error) {
|
|
4767
|
-
|
|
5157
|
+
themeLogger.exception('Failed to generate theme URL', error, buildThemeExtra('generatePortalThemeURL', {
|
|
5158
|
+
base_url: baseUrl,
|
|
5159
|
+
has_theme: Boolean(theme),
|
|
5160
|
+
}));
|
|
4768
5161
|
return baseUrl;
|
|
4769
5162
|
}
|
|
4770
5163
|
}
|
|
@@ -4793,7 +5186,10 @@ function appendThemeToURL(baseUrl, theme) {
|
|
|
4793
5186
|
return url.toString();
|
|
4794
5187
|
}
|
|
4795
5188
|
catch (error) {
|
|
4796
|
-
|
|
5189
|
+
themeLogger.exception('Failed to append theme to URL', error, buildThemeExtra('appendThemeToURL', {
|
|
5190
|
+
base_url: baseUrl,
|
|
5191
|
+
has_theme: Boolean(theme),
|
|
5192
|
+
}));
|
|
4797
5193
|
return baseUrl;
|
|
4798
5194
|
}
|
|
4799
5195
|
}
|
|
@@ -4848,7 +5244,7 @@ function validateCustomTheme(theme) {
|
|
|
4848
5244
|
return true;
|
|
4849
5245
|
}
|
|
4850
5246
|
catch (error) {
|
|
4851
|
-
|
|
5247
|
+
themeLogger.exception('Theme validation error', error, buildThemeExtra('validateCustomTheme'));
|
|
4852
5248
|
return false;
|
|
4853
5249
|
}
|
|
4854
5250
|
}
|
|
@@ -4861,7 +5257,9 @@ function validateCustomTheme(theme) {
|
|
|
4861
5257
|
function createCustomThemeFromPreset(preset, modifications) {
|
|
4862
5258
|
const baseTheme = getThemePreset(preset);
|
|
4863
5259
|
if (!baseTheme) {
|
|
4864
|
-
|
|
5260
|
+
themeLogger.warn('Preset theme not found', buildThemeExtra('createCustomThemeFromPreset', {
|
|
5261
|
+
preset,
|
|
5262
|
+
}));
|
|
4865
5263
|
return null;
|
|
4866
5264
|
}
|
|
4867
5265
|
return {
|
|
@@ -4873,6 +5271,14 @@ function createCustomThemeFromPreset(preset, modifications) {
|
|
|
4873
5271
|
/**
|
|
4874
5272
|
* Broker filtering utility functions
|
|
4875
5273
|
*/
|
|
5274
|
+
const brokerLogger = setupLogger('FinaticClientSDK.BrokerUtils', undefined, {
|
|
5275
|
+
codebase: 'FinaticClientSDK',
|
|
5276
|
+
});
|
|
5277
|
+
const buildBrokerExtra = (functionName, metadata) => ({
|
|
5278
|
+
module: 'BrokerUtils',
|
|
5279
|
+
function: functionName,
|
|
5280
|
+
...(metadata ? buildLoggerExtra(metadata) : {}),
|
|
5281
|
+
});
|
|
4876
5282
|
// Supported broker names and their corresponding IDs (including aliases)
|
|
4877
5283
|
const SUPPORTED_BROKERS = {
|
|
4878
5284
|
'alpaca': 'alpaca',
|
|
@@ -4915,7 +5321,9 @@ function appendBrokerFilterToURL(baseUrl, brokerNames) {
|
|
|
4915
5321
|
const url = new URL(baseUrl);
|
|
4916
5322
|
const { brokerIds, warnings } = convertBrokerNamesToIds(brokerNames);
|
|
4917
5323
|
// Log warnings for unsupported broker names
|
|
4918
|
-
warnings.forEach(warning =>
|
|
5324
|
+
warnings.forEach(warning => brokerLogger.warn('Unsupported broker name provided', buildBrokerExtra('appendBrokerFilterToURL', {
|
|
5325
|
+
warning,
|
|
5326
|
+
})));
|
|
4919
5327
|
// Only add broker filter if we have valid broker IDs
|
|
4920
5328
|
if (brokerIds.length > 0) {
|
|
4921
5329
|
const encodedBrokers = btoa(JSON.stringify(brokerIds));
|
|
@@ -4924,12 +5332,30 @@ function appendBrokerFilterToURL(baseUrl, brokerNames) {
|
|
|
4924
5332
|
return url.toString();
|
|
4925
5333
|
}
|
|
4926
5334
|
catch (error) {
|
|
4927
|
-
|
|
5335
|
+
brokerLogger.exception('Failed to append broker filter to URL', error, buildBrokerExtra('appendBrokerFilterToURL', {
|
|
5336
|
+
base_url: baseUrl,
|
|
5337
|
+
brokers_count: brokerNames?.length ?? 0,
|
|
5338
|
+
}));
|
|
4928
5339
|
return baseUrl;
|
|
4929
5340
|
}
|
|
4930
5341
|
}
|
|
4931
5342
|
|
|
5343
|
+
const finaticConnectLogger = setupLogger('FinaticClientSDK.FinaticConnect', undefined, {
|
|
5344
|
+
codebase: 'FinaticClientSDK',
|
|
5345
|
+
});
|
|
5346
|
+
const makeFinaticConnectExtra = (functionName, metadata) => ({
|
|
5347
|
+
module: 'FinaticConnect',
|
|
5348
|
+
function: functionName,
|
|
5349
|
+
...(metadata ? buildLoggerExtra(metadata) : {}),
|
|
5350
|
+
});
|
|
4932
5351
|
class FinaticConnect extends EventEmitter {
|
|
5352
|
+
buildLoggerExtra(functionName, metadata) {
|
|
5353
|
+
return {
|
|
5354
|
+
module: 'FinaticConnect',
|
|
5355
|
+
function: functionName,
|
|
5356
|
+
...(metadata ? buildLoggerExtra(metadata) : {}),
|
|
5357
|
+
};
|
|
5358
|
+
}
|
|
4933
5359
|
constructor(options, deviceInfo) {
|
|
4934
5360
|
super();
|
|
4935
5361
|
this.userToken = null;
|
|
@@ -4944,6 +5370,7 @@ class FinaticConnect extends EventEmitter {
|
|
|
4944
5370
|
this.SESSION_VALIDATION_TIMEOUT = 1000 * 30; // 30 seconds
|
|
4945
5371
|
this.SESSION_REFRESH_BUFFER_HOURS = 16; // Refresh session at 16 hours
|
|
4946
5372
|
this.sessionStartTime = null;
|
|
5373
|
+
this.logger = finaticConnectLogger;
|
|
4947
5374
|
this.options = options;
|
|
4948
5375
|
this.baseUrl = options.baseUrl || 'https://api.finatic.dev';
|
|
4949
5376
|
this.apiClient = MockFactory.createApiClient(this.baseUrl, deviceInfo);
|
|
@@ -4996,7 +5423,7 @@ class FinaticConnect extends EventEmitter {
|
|
|
4996
5423
|
async linkUserToSession(userId) {
|
|
4997
5424
|
try {
|
|
4998
5425
|
if (!this.sessionId) {
|
|
4999
|
-
|
|
5426
|
+
this.logger.error('No session ID available for user linking', this.buildLoggerExtra('linkUserToSession'));
|
|
5000
5427
|
return false;
|
|
5001
5428
|
}
|
|
5002
5429
|
// Call API endpoint to authenticate user with session
|
|
@@ -5008,14 +5435,20 @@ class FinaticConnect extends EventEmitter {
|
|
|
5008
5435
|
},
|
|
5009
5436
|
});
|
|
5010
5437
|
if (response.error) {
|
|
5011
|
-
|
|
5438
|
+
this.logger.error('Failed to link user to session', {
|
|
5439
|
+
...this.buildLoggerExtra('linkUserToSession', {
|
|
5440
|
+
session_id: this.sessionId,
|
|
5441
|
+
user_id: userId,
|
|
5442
|
+
}),
|
|
5443
|
+
error: response.error,
|
|
5444
|
+
});
|
|
5012
5445
|
return false;
|
|
5013
5446
|
}
|
|
5014
|
-
|
|
5447
|
+
this.logger.info('User linked to session successfully', this.buildLoggerExtra('linkUserToSession', { session_id: this.sessionId, user_id: userId }));
|
|
5015
5448
|
return true;
|
|
5016
5449
|
}
|
|
5017
5450
|
catch (error) {
|
|
5018
|
-
|
|
5451
|
+
this.logger.exception('Error linking user to session', error, this.buildLoggerExtra('linkUserToSession', { session_id: this.sessionId, user_id: userId }));
|
|
5019
5452
|
return false;
|
|
5020
5453
|
}
|
|
5021
5454
|
}
|
|
@@ -5160,7 +5593,7 @@ class FinaticConnect extends EventEmitter {
|
|
|
5160
5593
|
// Safari-specific fix: Clear instance if it exists but has no valid session
|
|
5161
5594
|
// This prevents stale instances from interfering with new requests
|
|
5162
5595
|
if (FinaticConnect.instance && !FinaticConnect.instance.sessionId) {
|
|
5163
|
-
|
|
5596
|
+
finaticConnectLogger.debug('Clearing stale instance for Safari compatibility', makeFinaticConnectExtra('init'));
|
|
5164
5597
|
FinaticConnect.instance = null;
|
|
5165
5598
|
}
|
|
5166
5599
|
if (!FinaticConnect.instance) {
|
|
@@ -5212,7 +5645,7 @@ class FinaticConnect extends EventEmitter {
|
|
|
5212
5645
|
FinaticConnect.instance.emit('success', normalizedUserId);
|
|
5213
5646
|
}
|
|
5214
5647
|
else {
|
|
5215
|
-
|
|
5648
|
+
finaticConnectLogger.warn('Failed to link user to session during initialization', makeFinaticConnectExtra('init', { user_id: normalizedUserId }));
|
|
5216
5649
|
}
|
|
5217
5650
|
}
|
|
5218
5651
|
catch (error) {
|
|
@@ -5242,7 +5675,7 @@ class FinaticConnect extends EventEmitter {
|
|
|
5242
5675
|
// Try to link user to session
|
|
5243
5676
|
const linked = await this.linkUserToSession(userId);
|
|
5244
5677
|
if (!linked) {
|
|
5245
|
-
|
|
5678
|
+
this.logger.warn('Failed to link user to session during initialization', this.buildLoggerExtra('initializeWithUser', { user_id: userId }));
|
|
5246
5679
|
// Don't throw error, just continue without authentication
|
|
5247
5680
|
return;
|
|
5248
5681
|
}
|
|
@@ -5330,7 +5763,7 @@ class FinaticConnect extends EventEmitter {
|
|
|
5330
5763
|
// Try to link user to session via API
|
|
5331
5764
|
const linked = await this.linkUserToSession(userId);
|
|
5332
5765
|
if (!linked) {
|
|
5333
|
-
|
|
5766
|
+
this.logger.warn('Failed to link user to session, continuing with authentication', this.buildLoggerExtra('openPortal.onSuccess', { user_id: userId }));
|
|
5334
5767
|
}
|
|
5335
5768
|
// Emit portal success event
|
|
5336
5769
|
this.emit('portal:success', userId);
|
|
@@ -5364,7 +5797,13 @@ class FinaticConnect extends EventEmitter {
|
|
|
5364
5797
|
options?.onClose?.();
|
|
5365
5798
|
},
|
|
5366
5799
|
onEvent: (type, data) => {
|
|
5367
|
-
|
|
5800
|
+
this.logger.debug('Portal event received', {
|
|
5801
|
+
...this.buildLoggerExtra('openPortal.onEvent', {
|
|
5802
|
+
event_type: type,
|
|
5803
|
+
payload_present: Boolean(data),
|
|
5804
|
+
}),
|
|
5805
|
+
event: 'portal-event',
|
|
5806
|
+
});
|
|
5368
5807
|
// Emit generic event
|
|
5369
5808
|
this.emit('event', type, data);
|
|
5370
5809
|
// Call the event callback
|
|
@@ -5875,7 +6314,7 @@ class FinaticConnect extends EventEmitter {
|
|
|
5875
6314
|
this.sessionKeepAliveInterval = setInterval(() => {
|
|
5876
6315
|
this.validateSessionKeepAlive();
|
|
5877
6316
|
}, this.SESSION_KEEP_ALIVE_INTERVAL);
|
|
5878
|
-
|
|
6317
|
+
this.logger.debug('Session keep-alive started', this.buildLoggerExtra('startSessionKeepAlive', { interval_ms: this.SESSION_KEEP_ALIVE_INTERVAL }));
|
|
5879
6318
|
}
|
|
5880
6319
|
/**
|
|
5881
6320
|
* Stop the session keep-alive mechanism
|
|
@@ -5884,7 +6323,7 @@ class FinaticConnect extends EventEmitter {
|
|
|
5884
6323
|
if (this.sessionKeepAliveInterval) {
|
|
5885
6324
|
clearInterval(this.sessionKeepAliveInterval);
|
|
5886
6325
|
this.sessionKeepAliveInterval = null;
|
|
5887
|
-
|
|
6326
|
+
this.logger.debug('Session keep-alive stopped', this.buildLoggerExtra('stopSessionKeepAlive'));
|
|
5888
6327
|
}
|
|
5889
6328
|
}
|
|
5890
6329
|
/**
|
|
@@ -5892,22 +6331,22 @@ class FinaticConnect extends EventEmitter {
|
|
|
5892
6331
|
*/
|
|
5893
6332
|
async validateSessionKeepAlive() {
|
|
5894
6333
|
if (!this.sessionId || !(await this.isAuthenticated())) {
|
|
5895
|
-
|
|
6334
|
+
this.logger.debug('Session keep-alive skipped - no active session', this.buildLoggerExtra('validateSessionKeepAlive'));
|
|
5896
6335
|
return;
|
|
5897
6336
|
}
|
|
5898
6337
|
try {
|
|
5899
|
-
|
|
6338
|
+
this.logger.debug('Validating session for keep-alive', this.buildLoggerExtra('validateSessionKeepAlive', { session_id: this.sessionId }));
|
|
5900
6339
|
// Check if we need to refresh the session (at 16 hours)
|
|
5901
6340
|
if (this.shouldRefreshSession()) {
|
|
5902
6341
|
await this.refreshSessionAutomatically();
|
|
5903
6342
|
return;
|
|
5904
6343
|
}
|
|
5905
6344
|
// Session keep-alive - assume session is active if we have a session ID
|
|
5906
|
-
|
|
6345
|
+
this.logger.debug('Session keep-alive successful', this.buildLoggerExtra('validateSessionKeepAlive', { session_id: this.sessionId }));
|
|
5907
6346
|
this.currentSessionState = 'active';
|
|
5908
6347
|
}
|
|
5909
6348
|
catch (error) {
|
|
5910
|
-
|
|
6349
|
+
this.logger.exception('Session keep-alive error', error, this.buildLoggerExtra('validateSessionKeepAlive', { session_id: this.sessionId }));
|
|
5911
6350
|
// Don't throw errors during keep-alive - just log them
|
|
5912
6351
|
}
|
|
5913
6352
|
}
|
|
@@ -5921,12 +6360,16 @@ class FinaticConnect extends EventEmitter {
|
|
|
5921
6360
|
const sessionAgeHours = (Date.now() - this.sessionStartTime) / (1000 * 60 * 60);
|
|
5922
6361
|
const hoursUntilRefresh = this.SESSION_REFRESH_BUFFER_HOURS - sessionAgeHours;
|
|
5923
6362
|
if (hoursUntilRefresh <= 0) {
|
|
5924
|
-
|
|
6363
|
+
this.logger.info('Session age threshold exceeded - triggering refresh', this.buildLoggerExtra('shouldRefreshSession', {
|
|
6364
|
+
session_age_hours: Number(sessionAgeHours.toFixed(1)),
|
|
6365
|
+
}));
|
|
5925
6366
|
return true;
|
|
5926
6367
|
}
|
|
5927
6368
|
// Log when refresh will occur (every 5 minutes during keep-alive)
|
|
5928
6369
|
if (hoursUntilRefresh <= 1) {
|
|
5929
|
-
|
|
6370
|
+
this.logger.debug('Session refresh scheduled', this.buildLoggerExtra('shouldRefreshSession', {
|
|
6371
|
+
hours_until_refresh: Number(hoursUntilRefresh.toFixed(1)),
|
|
6372
|
+
}));
|
|
5930
6373
|
}
|
|
5931
6374
|
return false;
|
|
5932
6375
|
}
|
|
@@ -5935,25 +6378,34 @@ class FinaticConnect extends EventEmitter {
|
|
|
5935
6378
|
*/
|
|
5936
6379
|
async refreshSessionAutomatically() {
|
|
5937
6380
|
if (!this.sessionId) {
|
|
5938
|
-
|
|
6381
|
+
this.logger.warn('Cannot refresh session - missing session ID', this.buildLoggerExtra('refreshSessionAutomatically'));
|
|
5939
6382
|
return;
|
|
5940
6383
|
}
|
|
5941
6384
|
try {
|
|
5942
|
-
|
|
6385
|
+
this.logger.info('Automatically refreshing session', this.buildLoggerExtra('refreshSessionAutomatically', {
|
|
6386
|
+
session_id: this.sessionId,
|
|
6387
|
+
}));
|
|
5943
6388
|
const response = await this.apiClient.refreshSession();
|
|
5944
6389
|
if (response.success) {
|
|
5945
|
-
|
|
5946
|
-
|
|
6390
|
+
this.logger.info('Session automatically refreshed successfully', this.buildLoggerExtra('refreshSessionAutomatically', {
|
|
6391
|
+
session_id: this.sessionId,
|
|
6392
|
+
status: response.response_data.status,
|
|
6393
|
+
expires_at: response.response_data.expires_at,
|
|
6394
|
+
}));
|
|
5947
6395
|
this.currentSessionState = response.response_data.status;
|
|
5948
6396
|
// Update session start time to prevent immediate re-refresh
|
|
5949
6397
|
this.sessionStartTime = Date.now();
|
|
5950
6398
|
}
|
|
5951
6399
|
else {
|
|
5952
|
-
|
|
6400
|
+
this.logger.warn('Automatic session refresh failed', this.buildLoggerExtra('refreshSessionAutomatically', {
|
|
6401
|
+
session_id: this.sessionId,
|
|
6402
|
+
}));
|
|
5953
6403
|
}
|
|
5954
6404
|
}
|
|
5955
6405
|
catch (error) {
|
|
5956
|
-
|
|
6406
|
+
this.logger.exception('Automatic session refresh error', error, this.buildLoggerExtra('refreshSessionAutomatically', {
|
|
6407
|
+
session_id: this.sessionId,
|
|
6408
|
+
}));
|
|
5957
6409
|
// Don't throw errors during automatic refresh - just log them
|
|
5958
6410
|
}
|
|
5959
6411
|
}
|
|
@@ -5973,7 +6425,9 @@ class FinaticConnect extends EventEmitter {
|
|
|
5973
6425
|
async handleVisibilityChange() {
|
|
5974
6426
|
// For 24-hour sessions, we don't want to complete sessions on visibility changes
|
|
5975
6427
|
// This prevents sessions from being closed when users switch tabs or apps
|
|
5976
|
-
|
|
6428
|
+
this.logger.debug('Page visibility changed', this.buildLoggerExtra('handleVisibilityChange', {
|
|
6429
|
+
visibility_state: document.visibilityState,
|
|
6430
|
+
}));
|
|
5977
6431
|
// Only pause keep-alive when hidden, but don't complete the session
|
|
5978
6432
|
if (document.visibilityState === 'hidden') {
|
|
5979
6433
|
this.stopSessionKeepAlive();
|
|
@@ -5995,7 +6449,7 @@ class FinaticConnect extends EventEmitter {
|
|
|
5995
6449
|
this.apiClient.isMockClient();
|
|
5996
6450
|
if (isMockMode) {
|
|
5997
6451
|
// Mock the completion response
|
|
5998
|
-
|
|
6452
|
+
this.logger.debug('Mock session completion', this.buildLoggerExtra('completeSession', { session_id: sessionId }));
|
|
5999
6453
|
return;
|
|
6000
6454
|
}
|
|
6001
6455
|
// Real API call
|
|
@@ -6006,15 +6460,18 @@ class FinaticConnect extends EventEmitter {
|
|
|
6006
6460
|
},
|
|
6007
6461
|
});
|
|
6008
6462
|
if (response.ok) {
|
|
6009
|
-
|
|
6463
|
+
this.logger.info('Session completed successfully', this.buildLoggerExtra('completeSession', { session_id: sessionId }));
|
|
6010
6464
|
}
|
|
6011
6465
|
else {
|
|
6012
|
-
|
|
6466
|
+
this.logger.warn('Failed to complete session', this.buildLoggerExtra('completeSession', {
|
|
6467
|
+
session_id: sessionId,
|
|
6468
|
+
response_status: response.status,
|
|
6469
|
+
}));
|
|
6013
6470
|
}
|
|
6014
6471
|
}
|
|
6015
6472
|
catch (error) {
|
|
6016
6473
|
// Silent failure - don't throw errors during cleanup
|
|
6017
|
-
|
|
6474
|
+
this.logger.exception('Session cleanup failed', error, this.buildLoggerExtra('completeSession', { session_id: sessionId }));
|
|
6018
6475
|
}
|
|
6019
6476
|
}
|
|
6020
6477
|
/**
|
|
@@ -6098,5 +6555,5 @@ class FinaticConnect extends EventEmitter {
|
|
|
6098
6555
|
}
|
|
6099
6556
|
FinaticConnect.instance = null;
|
|
6100
6557
|
|
|
6101
|
-
export { ApiClient, ApiError, AuthenticationError, AuthorizationError, BaseError, CompanyAccessError, EventEmitter, FinaticConnect, NetworkError, OrderError, OrderValidationError, PaginatedResult, RateLimitError, SecurityError, SessionError, TokenError, TradingNotEnabledError, ValidationError, appendThemeToURL, createCustomThemeFromPreset, generatePortalThemeURL, getThemePreset, portalThemePresets, validateCustomTheme };
|
|
6558
|
+
export { ApiClient, ApiError, AuthenticationError, AuthorizationError, BaseError, CompanyAccessError, EventEmitter, FinaticConnect, NetworkError, OrderError, OrderValidationError, PaginatedResult, RateLimitError, SecurityError, SessionError, TokenError, TradingNotEnabledError, ValidationError, appendThemeToURL, buildLoggerExtra, createCustomThemeFromPreset, generatePortalThemeURL, getThemePreset, logStartEnd, portalThemePresets, setupLogger, validateCustomTheme };
|
|
6102
6559
|
//# sourceMappingURL=index.mjs.map
|