@finatic/client 0.0.132 → 0.0.134

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.
Files changed (50) hide show
  1. package/README.md +87 -4
  2. package/dist/index.d.ts +471 -735
  3. package/dist/index.js +862 -756
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +863 -754
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/types/core/client/ApiClient.d.ts +206 -0
  8. package/dist/types/{client → core/client}/FinaticConnect.d.ts +54 -18
  9. package/dist/types/{portal → core/portal}/PortalUI.d.ts +1 -1
  10. package/dist/types/index.d.ts +6 -11
  11. package/dist/types/mocks/MockApiClient.d.ts +36 -90
  12. package/dist/types/mocks/MockDataProvider.d.ts +13 -3
  13. package/dist/types/mocks/MockFactory.d.ts +2 -2
  14. package/dist/types/{shared/themes → themes}/portalPresets.d.ts +1 -1
  15. package/dist/types/types/api/auth.d.ts +111 -0
  16. package/dist/types/types/{api.d.ts → api/broker.d.ts} +56 -284
  17. package/dist/types/types/api/core.d.ts +46 -0
  18. package/dist/types/types/api/errors.d.ts +23 -0
  19. package/dist/types/types/api/orders.d.ts +39 -0
  20. package/dist/types/types/{shared.d.ts → api/portfolio.d.ts} +26 -21
  21. package/dist/types/types/common/pagination.d.ts +33 -0
  22. package/dist/types/types/connect.d.ts +4 -2
  23. package/dist/types/types/index.d.ts +13 -0
  24. package/dist/types/types/{theme.d.ts → ui/theme.d.ts} +3 -0
  25. package/dist/types/utils/brokerUtils.d.ts +30 -0
  26. package/package.json +4 -3
  27. package/dist/types/client/ApiClient.d.ts +0 -234
  28. package/dist/types/mocks/index.d.ts +0 -5
  29. package/dist/types/security/ApiSecurity.d.ts +0 -24
  30. package/dist/types/security/RuntimeSecurity.d.ts +0 -28
  31. package/dist/types/security/SecurityUtils.d.ts +0 -21
  32. package/dist/types/security/index.d.ts +0 -2
  33. package/dist/types/services/AnalyticsService.d.ts +0 -18
  34. package/dist/types/services/ApiClient.d.ts +0 -121
  35. package/dist/types/services/PortalService.d.ts +0 -24
  36. package/dist/types/services/TradingService.d.ts +0 -55
  37. package/dist/types/services/api.d.ts +0 -23
  38. package/dist/types/services/auth.d.ts +0 -9
  39. package/dist/types/services/index.d.ts +0 -4
  40. package/dist/types/services/portfolio.d.ts +0 -10
  41. package/dist/types/services/trading.d.ts +0 -10
  42. package/dist/types/shared/index.d.ts +0 -2
  43. package/dist/types/shared/themes/index.d.ts +0 -2
  44. package/dist/types/shared/themes/presets.d.ts +0 -3
  45. package/dist/types/shared/themes/system.d.ts +0 -2
  46. package/dist/types/shared/types/index.d.ts +0 -110
  47. package/dist/types/types/config.d.ts +0 -12
  48. package/dist/types/types/errors.d.ts +0 -47
  49. package/dist/types/types/security.d.ts +0 -35
  50. package/dist/types/types.d.ts +0 -157
package/dist/index.mjs CHANGED
@@ -1,23 +1,12 @@
1
1
  import { v4 } from 'uuid';
2
2
 
3
3
  /**
4
- * API-related types for Finatic Connect SDK
5
- */
6
- var SessionState;
7
- (function (SessionState) {
8
- SessionState["PENDING"] = "PENDING";
9
- SessionState["AUTHENTICATING"] = "AUTHENTICATING";
10
- SessionState["ACTIVE"] = "ACTIVE";
11
- SessionState["COMPLETED"] = "COMPLETED";
12
- SessionState["EXPIRED"] = "EXPIRED";
13
- })(SessionState || (SessionState = {}));
14
-
15
- /**
16
- * Core types for Finatic Connect SDK
4
+ * Pagination-related types and classes
17
5
  */
18
6
  class PaginatedResult {
19
7
  constructor(data, paginationInfo, navigationCallback) {
20
8
  this.data = data;
9
+ this.navigationCallback = navigationCallback;
21
10
  this.metadata = {
22
11
  hasMore: paginationInfo.has_more,
23
12
  nextOffset: paginationInfo.next_offset,
@@ -27,7 +16,6 @@ class PaginatedResult {
27
16
  hasNext: paginationInfo.has_more,
28
17
  hasPrevious: paginationInfo.current_offset > 0,
29
18
  };
30
- this.navigationCallback = navigationCallback;
31
19
  }
32
20
  get hasNext() {
33
21
  return this.metadata.hasNext;
@@ -38,10 +26,6 @@ class PaginatedResult {
38
26
  get currentPage() {
39
27
  return this.metadata.currentPage;
40
28
  }
41
- /**
42
- * Navigate to the next page
43
- * @returns Promise<PaginatedResult<T> | null> - Next page result or null if no next page
44
- */
45
29
  async nextPage() {
46
30
  if (!this.hasNext || !this.navigationCallback) {
47
31
  return null;
@@ -50,14 +34,10 @@ class PaginatedResult {
50
34
  return await this.navigationCallback(this.metadata.nextOffset, this.metadata.limit);
51
35
  }
52
36
  catch (error) {
53
- console.error('Error navigating to next page:', error);
37
+ console.error('Error fetching next page:', error);
54
38
  return null;
55
39
  }
56
40
  }
57
- /**
58
- * Navigate to the previous page
59
- * @returns Promise<PaginatedResult<T> | null> - Previous page result or null if no previous page
60
- */
61
41
  async previousPage() {
62
42
  if (!this.hasPrevious || !this.navigationCallback) {
63
43
  return null;
@@ -67,61 +47,74 @@ class PaginatedResult {
67
47
  return await this.navigationCallback(previousOffset, this.metadata.limit);
68
48
  }
69
49
  catch (error) {
70
- console.error('Error navigating to previous page:', error);
50
+ console.error('Error fetching previous page:', error);
71
51
  return null;
72
52
  }
73
53
  }
74
- /**
75
- * Navigate to a specific page
76
- * @param pageNumber - The page number to navigate to (1-based)
77
- * @returns Promise<PaginatedResult<T> | null> - Page result or null if page doesn't exist
78
- */
79
54
  async goToPage(pageNumber) {
80
- if (pageNumber < 1 || !this.navigationCallback) {
55
+ if (!this.navigationCallback || pageNumber < 1) {
81
56
  return null;
82
57
  }
83
- const targetOffset = (pageNumber - 1) * this.metadata.limit;
58
+ const offset = (pageNumber - 1) * this.metadata.limit;
84
59
  try {
85
- return await this.navigationCallback(targetOffset, this.metadata.limit);
60
+ return await this.navigationCallback(offset, this.metadata.limit);
86
61
  }
87
62
  catch (error) {
88
- console.error('Error navigating to page:', pageNumber, error);
63
+ console.error('Error fetching page:', pageNumber, error);
89
64
  return null;
90
65
  }
91
66
  }
92
- /**
93
- * Get the first page
94
- * @returns Promise<PaginatedResult<T> | null> - First page result or null if error
95
- */
96
67
  async firstPage() {
97
- return this.goToPage(1);
68
+ if (!this.navigationCallback) {
69
+ return null;
70
+ }
71
+ try {
72
+ return await this.navigationCallback(0, this.metadata.limit);
73
+ }
74
+ catch (error) {
75
+ console.error('Error fetching first page:', error);
76
+ return null;
77
+ }
98
78
  }
99
- /**
100
- * Get the last page (this is a best effort - we don't know the total count)
101
- * @returns Promise<PaginatedResult<T> | null> - Last page result or null if error
102
- */
103
79
  async lastPage() {
104
80
  if (!this.navigationCallback) {
105
81
  return null;
106
82
  }
107
83
  const findLast = async (page) => {
108
- const next = await page.nextPage();
109
- if (next) {
110
- return findLast(next);
84
+ if (!page.hasNext) {
85
+ return page;
111
86
  }
112
- return page;
87
+ const nextPage = await page.nextPage();
88
+ if (!nextPage) {
89
+ return page;
90
+ }
91
+ return findLast(nextPage);
113
92
  };
114
- return findLast(this);
93
+ try {
94
+ return await findLast(this);
95
+ }
96
+ catch (error) {
97
+ console.error('Error fetching last page:', error);
98
+ return null;
99
+ }
115
100
  }
116
- /**
117
- * Get pagination info as a string
118
- * @returns string - Human readable pagination info
119
- */
120
101
  getPaginationInfo() {
121
- return `Page ${this.currentPage} (${this.metadata.currentOffset + 1}-${this.metadata.currentOffset + this.metadata.limit}) - ${this.hasNext ? 'Has more' : 'Last page'}`;
102
+ return `Page ${this.currentPage} (${this.metadata.currentOffset + 1}-${this.metadata.currentOffset + this.metadata.limit})`;
122
103
  }
123
104
  }
124
105
 
106
+ /**
107
+ * Authentication-related types
108
+ */
109
+ var SessionState;
110
+ (function (SessionState) {
111
+ SessionState["PENDING"] = "PENDING";
112
+ SessionState["AUTHENTICATING"] = "AUTHENTICATING";
113
+ SessionState["ACTIVE"] = "ACTIVE";
114
+ SessionState["COMPLETED"] = "COMPLETED";
115
+ SessionState["EXPIRED"] = "EXPIRED";
116
+ })(SessionState || (SessionState = {}));
117
+
125
118
  class BaseError extends Error {
126
119
  constructor(message, code = 'UNKNOWN_ERROR') {
127
120
  super(message);
@@ -364,11 +357,68 @@ class ApiClient {
364
357
  url.searchParams.append(key, value);
365
358
  });
366
359
  }
360
+ // Build comprehensive headers object with all available session data
361
+ const comprehensiveHeaders = {
362
+ 'Content-Type': 'application/json',
363
+ };
364
+ // Add device info if available
365
+ if (this.deviceInfo) {
366
+ comprehensiveHeaders['X-Device-Info'] = JSON.stringify({
367
+ ip_address: this.deviceInfo.ip_address || '',
368
+ user_agent: this.deviceInfo.user_agent || '',
369
+ fingerprint: this.deviceInfo.fingerprint || '',
370
+ });
371
+ }
372
+ // Add session headers if available (filter out empty values)
373
+ if (this.currentSessionId && this.currentSessionId.trim() !== '') {
374
+ comprehensiveHeaders['X-Session-ID'] = this.currentSessionId;
375
+ comprehensiveHeaders['Session-ID'] = this.currentSessionId;
376
+ }
377
+ if (this.companyId && this.companyId.trim() !== '') {
378
+ comprehensiveHeaders['X-Company-ID'] = this.companyId;
379
+ }
380
+ if (this.csrfToken && this.csrfToken.trim() !== '') {
381
+ comprehensiveHeaders['X-CSRF-Token'] = this.csrfToken;
382
+ }
383
+ // Add any additional headers from options (these will override defaults)
384
+ if (options.headers) {
385
+ Object.entries(options.headers).forEach(([key, value]) => {
386
+ if (value !== undefined && value !== null && value.trim() !== '') {
387
+ comprehensiveHeaders[key] = value;
388
+ }
389
+ });
390
+ }
391
+ // Safari-specific fix: Ensure all headers are explicitly set and not empty
392
+ // Safari can be strict about header formatting and empty values
393
+ const safariSafeHeaders = {};
394
+ Object.entries(comprehensiveHeaders).forEach(([key, value]) => {
395
+ if (value && value.trim() !== '') {
396
+ // Ensure header names are properly formatted for Safari
397
+ const normalizedKey = key.trim();
398
+ const normalizedValue = value.trim();
399
+ safariSafeHeaders[normalizedKey] = normalizedValue;
400
+ }
401
+ });
402
+ // Debug logging for development
403
+ if (typeof window !== 'undefined' && window.location.hostname === 'localhost') {
404
+ console.log('Request to:', url.toString());
405
+ console.log('Safari-safe headers:', safariSafeHeaders);
406
+ console.log('Browser:', navigator.userAgent);
407
+ console.log('Session ID:', this.currentSessionId);
408
+ console.log('Company ID:', this.companyId);
409
+ console.log('CSRF Token:', this.csrfToken);
410
+ }
367
411
  const response = await fetch(url.toString(), {
368
412
  method: options.method,
369
- headers: options.headers,
413
+ headers: safariSafeHeaders,
370
414
  body: options.body ? JSON.stringify(options.body) : undefined,
371
415
  });
416
+ // Debug logging for response
417
+ if (typeof window !== 'undefined' && window.location.hostname === 'localhost') {
418
+ console.log('Response status:', response.status);
419
+ console.log('Response headers:', Object.fromEntries(response.headers.entries()));
420
+ console.log('Request was made with headers:', safariSafeHeaders);
421
+ }
372
422
  if (!response.ok) {
373
423
  const error = await response.json();
374
424
  throw this.handleError(response.status, error);
@@ -617,63 +667,47 @@ class ApiClient {
617
667
  });
618
668
  }
619
669
  // Portfolio Management
620
- async getHoldings(accessToken) {
670
+ async getHoldings() {
671
+ const accessToken = await this.getValidAccessToken();
621
672
  return this.request('/portfolio/holdings', {
622
673
  method: 'GET',
623
674
  headers: {
624
- 'Content-Type': 'application/json',
625
- Authorization: `Bearer ${accessToken}`,
675
+ 'Authorization': `Bearer ${accessToken}`,
626
676
  },
627
677
  });
628
678
  }
629
- async getOrders(accessToken) {
630
- return this.request('/orders/', {
679
+ async getOrders() {
680
+ const accessToken = await this.getValidAccessToken();
681
+ return this.request('/data/orders', {
631
682
  method: 'GET',
632
683
  headers: {
633
- 'Content-Type': 'application/json',
634
- Authorization: `Bearer ${accessToken}`,
684
+ 'Authorization': `Bearer ${accessToken}`,
635
685
  },
636
686
  });
637
687
  }
638
- async getPortfolio(accessToken) {
688
+ async getPortfolio() {
689
+ const accessToken = await this.getValidAccessToken();
639
690
  const response = await this.request('/portfolio/', {
640
691
  method: 'GET',
641
692
  headers: {
642
- Authorization: `Bearer ${accessToken}`,
693
+ 'Authorization': `Bearer ${accessToken}`,
643
694
  'Content-Type': 'application/json',
644
695
  },
645
696
  });
646
697
  return response;
647
698
  }
648
- async placeOrder(accessToken, order) {
649
- await this.request('/orders/', {
650
- method: 'POST',
651
- headers: {
652
- Authorization: `Bearer ${accessToken}`,
653
- 'Content-Type': 'application/json',
654
- },
655
- body: order,
656
- });
657
- }
658
- // New methods with automatic token management
659
- async getHoldingsAuto() {
660
- const accessToken = await this.getValidAccessToken();
661
- return this.getHoldings(accessToken);
662
- }
663
- async getOrdersAuto() {
664
- const accessToken = await this.getValidAccessToken();
665
- return this.getOrders(accessToken);
666
- }
667
- async getPortfolioAuto() {
668
- const accessToken = await this.getValidAccessToken();
669
- return this.getPortfolio(accessToken);
670
- }
671
- async placeOrderAuto(order) {
672
- const accessToken = await this.getValidAccessToken();
673
- return this.placeOrder(accessToken, order);
674
- }
675
699
  // Enhanced Trading Methods with Session Management
676
- async placeBrokerOrder(accessToken, params, extras = {}) {
700
+ async placeBrokerOrder(params, extras = {}, connection_id) {
701
+ const accessToken = await this.getValidAccessToken();
702
+ // Get broker and account from context or params
703
+ const broker = params.broker || this.tradingContext.broker;
704
+ const accountNumber = params.accountNumber || this.tradingContext.accountNumber;
705
+ if (!broker) {
706
+ throw new Error('Broker not set. Call setBroker() or pass broker parameter.');
707
+ }
708
+ if (!accountNumber) {
709
+ throw new Error('Account not set. Call setAccount() or pass accountNumber parameter.');
710
+ }
677
711
  // Merge context with provided parameters
678
712
  const fullParams = {
679
713
  broker: (params.broker || this.tradingContext.broker) ||
@@ -693,9 +727,15 @@ class ApiClient {
693
727
  timeInForce: params.timeInForce || 'day',
694
728
  price: params.price,
695
729
  stopPrice: params.stopPrice,
730
+ order_id: params.order_id,
696
731
  };
697
- // Build request body with snake_case parameter names
732
+ // Build request body with camelCase parameter names
698
733
  const requestBody = this.buildOrderRequestBody(fullParams, extras);
734
+ // Add query parameters if connection_id is provided
735
+ const queryParams = {};
736
+ if (connection_id) {
737
+ queryParams.connection_id = connection_id;
738
+ }
699
739
  return this.request('/brokers/orders', {
700
740
  method: 'POST',
701
741
  headers: {
@@ -706,17 +746,41 @@ class ApiClient {
706
746
  'X-Device-Info': JSON.stringify(this.deviceInfo),
707
747
  },
708
748
  body: requestBody,
749
+ params: queryParams,
709
750
  });
710
751
  }
711
- async cancelBrokerOrder(orderId, broker, extras = {}) {
712
- const targetBroker = broker ||
713
- this.tradingContext.broker ||
714
- (() => {
715
- throw new Error('Broker not set. Call setBroker() or pass broker parameter.');
716
- })();
717
- // Build optional body for extras if provided
718
- const body = Object.keys(extras).length > 0 ? { order: extras } : undefined;
719
- return this.request(`/brokers/orders/${targetBroker}/${orderId}`, {
752
+ async cancelBrokerOrder(orderId, broker, extras = {}, connection_id) {
753
+ await this.getValidAccessToken();
754
+ const selectedBroker = broker || this.tradingContext.broker;
755
+ if (!selectedBroker) {
756
+ throw new Error('Broker not set. Call setBroker() or pass broker parameter.');
757
+ }
758
+ const accountNumber = this.tradingContext.accountNumber;
759
+ // Build query parameters as required by API documentation
760
+ const queryParams = {
761
+ broker: selectedBroker,
762
+ order_id: orderId,
763
+ };
764
+ // Add optional parameters if available
765
+ if (accountNumber) {
766
+ queryParams.account_number = accountNumber.toString();
767
+ }
768
+ if (connection_id) {
769
+ queryParams.connection_id = connection_id;
770
+ }
771
+ // Build optional request body if extras are provided
772
+ let body = undefined;
773
+ if (Object.keys(extras).length > 0) {
774
+ body = {
775
+ broker: selectedBroker,
776
+ order: {
777
+ order_id: orderId,
778
+ account_number: accountNumber,
779
+ ...extras
780
+ }
781
+ };
782
+ }
783
+ return this.request('/brokers/orders', {
720
784
  method: 'DELETE',
721
785
  headers: {
722
786
  'Content-Type': 'application/json',
@@ -725,16 +789,22 @@ class ApiClient {
725
789
  'X-Device-Info': JSON.stringify(this.deviceInfo),
726
790
  },
727
791
  body,
792
+ params: queryParams,
728
793
  });
729
794
  }
730
- async modifyBrokerOrder(orderId, params, broker, extras = {}) {
731
- const targetBroker = broker ||
732
- this.tradingContext.broker ||
733
- (() => {
734
- throw new Error('Broker not set. Call setBroker() or pass broker parameter.');
735
- })();
736
- // Build request body with snake_case parameter names and include broker
737
- const requestBody = this.buildModifyRequestBody(params, extras, targetBroker);
795
+ async modifyBrokerOrder(orderId, params, broker, extras = {}, connection_id) {
796
+ await this.getValidAccessToken();
797
+ const selectedBroker = broker || this.tradingContext.broker;
798
+ if (!selectedBroker) {
799
+ throw new Error('Broker not set. Call setBroker() or pass broker parameter.');
800
+ }
801
+ // Build request body with camelCase parameter names and include broker
802
+ const requestBody = this.buildModifyRequestBody(params, extras, selectedBroker);
803
+ // Add query parameters if connection_id is provided
804
+ const queryParams = {};
805
+ if (connection_id) {
806
+ queryParams.connection_id = connection_id;
807
+ }
738
808
  return this.request(`/brokers/orders/${orderId}`, {
739
809
  method: 'PATCH',
740
810
  headers: {
@@ -744,6 +814,7 @@ class ApiClient {
744
814
  'X-Device-Info': JSON.stringify(this.deviceInfo),
745
815
  },
746
816
  body: requestBody,
817
+ params: queryParams,
747
818
  });
748
819
  }
749
820
  // Context management methods
@@ -764,392 +835,340 @@ class ApiClient {
764
835
  this.tradingContext = {};
765
836
  }
766
837
  // Stock convenience methods
767
- async placeStockMarketOrder(accessToken, symbol, orderQty, action, broker, accountNumber, extras = {}) {
768
- return this.placeBrokerOrder(accessToken, {
769
- broker,
770
- accountNumber,
838
+ async placeStockMarketOrder(symbol, orderQty, action, broker, accountNumber, extras = {}, connection_id) {
839
+ return this.placeBrokerOrder({
771
840
  symbol,
772
841
  orderQty,
773
842
  action,
774
843
  orderType: 'Market',
775
844
  assetType: 'Stock',
776
845
  timeInForce: 'day',
777
- }, extras);
778
- }
779
- async placeStockLimitOrder(accessToken, symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
780
- return this.placeBrokerOrder(accessToken, {
781
846
  broker,
782
847
  accountNumber,
848
+ }, extras, connection_id);
849
+ }
850
+ async placeStockLimitOrder(symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}, connection_id) {
851
+ return this.placeBrokerOrder({
783
852
  symbol,
784
853
  orderQty,
785
854
  action,
786
855
  orderType: 'Limit',
787
856
  assetType: 'Stock',
788
- timeInForce,
789
857
  price,
790
- }, extras);
791
- }
792
- async placeStockStopOrder(accessToken, symbol, orderQty, action, stopPrice, timeInForce = 'day', broker, accountNumber, extras = {}) {
793
- return this.placeBrokerOrder(accessToken, {
858
+ timeInForce,
794
859
  broker,
795
860
  accountNumber,
861
+ }, extras, connection_id);
862
+ }
863
+ async placeStockStopOrder(symbol, orderQty, action, stopPrice, timeInForce = 'day', broker, accountNumber, extras = {}, connection_id) {
864
+ return this.placeBrokerOrder({
796
865
  symbol,
797
866
  orderQty,
798
867
  action,
799
868
  orderType: 'Stop',
800
869
  assetType: 'Stock',
801
- timeInForce,
802
870
  stopPrice,
803
- }, extras);
804
- }
805
- // Crypto convenience methods
806
- async placeCryptoMarketOrder(accessToken, symbol, orderQty, action, options = {}, broker, accountNumber, extras = {}) {
807
- const orderParams = {
871
+ timeInForce,
808
872
  broker,
809
873
  accountNumber,
874
+ }, extras, connection_id);
875
+ }
876
+ // Crypto convenience methods
877
+ async placeCryptoMarketOrder(symbol, orderQty, action, options = {}, broker, accountNumber, extras = {}) {
878
+ return this.placeBrokerOrder({
810
879
  symbol,
811
- orderQty: options.quantity || orderQty,
880
+ orderQty,
812
881
  action,
813
882
  orderType: 'Market',
814
883
  assetType: 'Crypto',
815
- timeInForce: 'gtc', // Crypto typically uses GTC
816
- };
817
- // Add notional if provided
818
- if (options.notional) {
819
- orderParams.notional = options.notional;
820
- }
821
- return this.placeBrokerOrder(accessToken, orderParams, extras);
822
- }
823
- async placeCryptoLimitOrder(accessToken, symbol, orderQty, action, price, timeInForce = 'gtc', options = {}, broker, accountNumber, extras = {}) {
824
- const orderParams = {
884
+ timeInForce: 'day',
825
885
  broker,
826
886
  accountNumber,
887
+ ...options,
888
+ }, extras);
889
+ }
890
+ async placeCryptoLimitOrder(symbol, orderQty, action, price, timeInForce = 'gtc', options = {}, broker, accountNumber, extras = {}) {
891
+ return this.placeBrokerOrder({
827
892
  symbol,
828
- orderQty: options.quantity || orderQty,
893
+ orderQty,
829
894
  action,
830
895
  orderType: 'Limit',
831
896
  assetType: 'Crypto',
832
- timeInForce,
833
897
  price,
834
- };
835
- // Add notional if provided
836
- if (options.notional) {
837
- orderParams.notional = options.notional;
838
- }
839
- return this.placeBrokerOrder(accessToken, orderParams, extras);
840
- }
841
- // Options convenience methods
842
- async placeOptionsMarketOrder(accessToken, symbol, orderQty, action, options, broker, accountNumber, extras = {}) {
843
- const orderParams = {
898
+ timeInForce,
844
899
  broker,
845
900
  accountNumber,
901
+ ...options,
902
+ }, extras);
903
+ }
904
+ // Options convenience methods
905
+ async placeOptionsMarketOrder(symbol, orderQty, action, options, broker, accountNumber, extras = {}) {
906
+ return this.placeBrokerOrder({
846
907
  symbol,
847
908
  orderQty,
848
909
  action,
849
910
  orderType: 'Market',
850
911
  assetType: 'Option',
851
912
  timeInForce: 'day',
852
- };
853
- // Add options-specific parameters to extras
854
- const targetBroker = broker || this.tradingContext.broker;
855
- const optionsExtras = {
856
- ...extras,
857
- ...(targetBroker && {
858
- [targetBroker]: {
859
- ...extras[targetBroker],
860
- strikePrice: options.strikePrice,
861
- expirationDate: options.expirationDate,
862
- optionType: options.optionType,
863
- contractSize: options.contractSize || 100,
864
- },
865
- }),
866
- };
867
- return this.placeBrokerOrder(accessToken, orderParams, optionsExtras);
868
- }
869
- async placeOptionsLimitOrder(accessToken, symbol, orderQty, action, price, options, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
870
- const orderParams = {
871
913
  broker,
872
914
  accountNumber,
915
+ ...options,
916
+ }, extras);
917
+ }
918
+ async placeOptionsLimitOrder(symbol, orderQty, action, price, options, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
919
+ return this.placeBrokerOrder({
873
920
  symbol,
874
921
  orderQty,
875
922
  action,
876
923
  orderType: 'Limit',
877
924
  assetType: 'Option',
878
- timeInForce,
879
925
  price,
880
- };
881
- // Add options-specific parameters to extras
882
- const targetBroker = broker || this.tradingContext.broker;
883
- const optionsExtras = {
884
- ...extras,
885
- ...(targetBroker && {
886
- [targetBroker]: {
887
- ...extras[targetBroker],
888
- strikePrice: options.strikePrice,
889
- expirationDate: options.expirationDate,
890
- optionType: options.optionType,
891
- contractSize: options.contractSize || 100,
892
- },
893
- }),
894
- };
895
- return this.placeBrokerOrder(accessToken, orderParams, optionsExtras);
896
- }
897
- // Futures convenience methods
898
- async placeFuturesMarketOrder(accessToken, symbol, orderQty, action, broker, accountNumber, extras = {}) {
899
- return this.placeBrokerOrder(accessToken, {
926
+ timeInForce,
900
927
  broker,
901
928
  accountNumber,
929
+ ...options,
930
+ }, extras);
931
+ }
932
+ // Futures convenience methods
933
+ async placeFuturesMarketOrder(symbol, orderQty, action, broker, accountNumber, extras = {}) {
934
+ return this.placeBrokerOrder({
902
935
  symbol,
903
936
  orderQty,
904
937
  action,
905
938
  orderType: 'Market',
906
- assetType: 'Futures',
939
+ assetType: 'Future',
907
940
  timeInForce: 'day',
908
- }, extras);
909
- }
910
- async placeFuturesLimitOrder(accessToken, symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
911
- return this.placeBrokerOrder(accessToken, {
912
941
  broker,
913
942
  accountNumber,
943
+ }, extras);
944
+ }
945
+ async placeFuturesLimitOrder(symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
946
+ return this.placeBrokerOrder({
914
947
  symbol,
915
948
  orderQty,
916
949
  action,
917
950
  orderType: 'Limit',
918
- assetType: 'Futures',
919
- timeInForce,
951
+ assetType: 'Future',
920
952
  price,
953
+ timeInForce,
954
+ broker,
955
+ accountNumber,
921
956
  }, extras);
922
957
  }
923
958
  buildOrderRequestBody(params, extras = {}) {
924
959
  const baseOrder = {
925
- order_type: params.orderType,
926
- asset_type: params.assetType,
960
+ order_id: params.order_id,
961
+ orderType: params.orderType,
962
+ assetType: params.assetType,
927
963
  action: params.action,
928
- time_in_force: params.timeInForce.toLowerCase(),
929
- account_number: params.accountNumber,
964
+ timeInForce: params.timeInForce,
965
+ accountNumber: params.accountNumber,
930
966
  symbol: params.symbol,
931
- order_qty: params.orderQty,
967
+ orderQty: params.orderQty,
932
968
  };
933
969
  if (params.price !== undefined)
934
970
  baseOrder.price = params.price;
935
971
  if (params.stopPrice !== undefined)
936
- baseOrder.stop_price = params.stopPrice;
937
- // Apply broker-specific defaults
938
- const brokerExtras = this.applyBrokerDefaults(params.broker, extras[params.broker] || {});
972
+ baseOrder.stopPrice = params.stopPrice;
973
+ // Apply broker-specific defaults – map camelCase extras property keys to snake_case before merging
974
+ const brokerExtras = this.applyBrokerDefaults(params.broker, extras);
939
975
  return {
940
976
  broker: params.broker,
941
- ...baseOrder,
942
- ...brokerExtras,
977
+ order: {
978
+ ...baseOrder,
979
+ ...brokerExtras,
980
+ },
943
981
  };
944
982
  }
945
983
  buildModifyRequestBody(params, extras, broker) {
946
- const requestBody = {
947
- broker: broker,
948
- };
984
+ const order = {};
985
+ if (params.order_id !== undefined)
986
+ order.order_id = params.order_id;
949
987
  if (params.orderType !== undefined)
950
- requestBody.order_type = params.orderType;
988
+ order.orderType = params.orderType;
951
989
  if (params.assetType !== undefined)
952
- requestBody.asset_type = params.assetType;
990
+ order.assetType = params.assetType;
953
991
  if (params.action !== undefined)
954
- requestBody.action = params.action;
992
+ order.action = params.action;
955
993
  if (params.timeInForce !== undefined)
956
- requestBody.time_in_force = params.timeInForce.toLowerCase();
994
+ order.timeInForce = params.timeInForce;
957
995
  if (params.accountNumber !== undefined)
958
- requestBody.account_number = params.accountNumber;
996
+ order.accountNumber = params.accountNumber;
959
997
  if (params.symbol !== undefined)
960
- requestBody.symbol = params.symbol;
998
+ order.symbol = params.symbol;
961
999
  if (params.orderQty !== undefined)
962
- requestBody.order_qty = params.orderQty;
1000
+ order.orderQty = params.orderQty;
963
1001
  if (params.price !== undefined)
964
- requestBody.price = params.price;
1002
+ order.price = params.price;
965
1003
  if (params.stopPrice !== undefined)
966
- requestBody.stop_price = params.stopPrice;
967
- // Apply broker-specific defaults
968
- const brokerExtras = this.applyBrokerDefaults(broker, extras[broker] || {});
1004
+ order.stopPrice = params.stopPrice;
1005
+ // Apply broker-specific defaults (handles snake_case conversion)
1006
+ const brokerExtras = this.applyBrokerDefaults(broker, extras);
969
1007
  return {
970
- ...requestBody,
971
- ...brokerExtras,
1008
+ broker,
1009
+ order: {
1010
+ ...order,
1011
+ ...brokerExtras,
1012
+ },
972
1013
  };
973
1014
  }
974
1015
  applyBrokerDefaults(broker, extras) {
1016
+ // If the caller provided a broker-scoped extras object (e.g. { ninjaTrader: { ... } })
1017
+ // pull the nested object for easier processing.
1018
+ if (extras && typeof extras === 'object') {
1019
+ const scoped = broker === 'robinhood'
1020
+ ? extras.robinhood
1021
+ : broker === 'ninja_trader'
1022
+ ? extras.ninjaTrader
1023
+ : broker === 'tasty_trade'
1024
+ ? extras.tastyTrade
1025
+ : undefined;
1026
+ if (scoped) {
1027
+ extras = scoped;
1028
+ }
1029
+ }
975
1030
  switch (broker) {
976
1031
  case 'robinhood':
977
1032
  return {
978
- extendedHours: extras.extendedHours ?? true,
979
- marketHours: extras.marketHours ?? 'regular_hours',
980
- trailType: extras.trailType ?? 'percentage',
981
1033
  ...extras,
1034
+ extendedHours: extras?.extendedHours ?? extras?.extended_hours ?? true,
1035
+ marketHours: extras?.marketHours ?? extras?.market_hours ?? 'regular_hours',
1036
+ trailType: extras?.trailType ?? extras?.trail_type ?? 'percentage',
982
1037
  };
983
1038
  case 'ninja_trader':
984
1039
  return {
985
- accountSpec: extras.accountSpec ?? '',
986
- isAutomated: extras.isAutomated ?? true,
987
1040
  ...extras,
1041
+ accountSpec: extras?.accountSpec ?? extras?.account_spec ?? '',
1042
+ isAutomated: extras?.isAutomated ?? extras?.is_automated ?? true,
988
1043
  };
989
1044
  case 'tasty_trade':
990
1045
  return {
991
- automatedSource: extras.automatedSource ?? true,
992
1046
  ...extras,
1047
+ automatedSource: extras?.automatedSource ?? extras?.automated_source ?? true,
993
1048
  };
994
1049
  default:
995
1050
  return extras;
996
1051
  }
997
1052
  }
998
- async revokeToken(accessToken) {
999
- return this.request('/auth/token/revoke', {
1053
+ async revokeToken() {
1054
+ const accessToken = await this.getValidAccessToken();
1055
+ await this.request('/auth/token/revoke', {
1000
1056
  method: 'POST',
1001
1057
  headers: {
1002
- 'Content-Type': 'application/json',
1003
- Authorization: `Bearer ${accessToken}`,
1058
+ 'Authorization': `Bearer ${accessToken}`,
1004
1059
  },
1005
1060
  });
1061
+ this.clearTokens();
1006
1062
  }
1007
1063
  async getUserToken(userId) {
1008
- return this.request('/auth/token', {
1009
- method: 'POST',
1064
+ const accessToken = await this.getValidAccessToken();
1065
+ return this.request(`/auth/token/user/${userId}`, {
1066
+ method: 'GET',
1010
1067
  headers: {
1011
- 'Content-Type': 'application/json',
1068
+ 'Authorization': `Bearer ${accessToken}`,
1012
1069
  },
1013
- body: { userId },
1014
1070
  });
1015
1071
  }
1072
+ /**
1073
+ * Get the current session state
1074
+ */
1016
1075
  getCurrentSessionState() {
1017
1076
  return this.currentSessionState;
1018
1077
  }
1078
+ /**
1079
+ * Refresh the current session to extend its lifetime
1080
+ */
1081
+ async refreshSession() {
1082
+ if (!this.currentSessionId || !this.companyId) {
1083
+ throw new SessionError('No active session to refresh');
1084
+ }
1085
+ return this.request('/auth/session/refresh', {
1086
+ method: 'POST',
1087
+ headers: {
1088
+ 'Session-ID': this.currentSessionId,
1089
+ 'X-Company-ID': this.companyId,
1090
+ },
1091
+ });
1092
+ }
1019
1093
  // Broker Data Management
1020
- async getBrokerList(accessToken) {
1021
- return this.request('/brokers/', {
1094
+ async getBrokerList() {
1095
+ const accessToken = await this.getValidAccessToken();
1096
+ return this.request('/brokers/list', {
1022
1097
  method: 'GET',
1023
1098
  headers: {
1024
- 'Content-Type': 'application/json',
1025
- Authorization: `Bearer ${accessToken}`,
1026
- 'Session-ID': this.currentSessionId || '',
1027
- 'X-Session-ID': this.currentSessionId || '',
1028
- 'X-Device-Info': JSON.stringify({
1029
- ip_address: this.deviceInfo?.ip_address || '',
1030
- user_agent: this.deviceInfo?.user_agent || '',
1031
- fingerprint: this.deviceInfo?.fingerprint || '',
1032
- }),
1099
+ 'Authorization': `Bearer ${accessToken}`,
1033
1100
  },
1034
1101
  });
1035
1102
  }
1036
- async getBrokerAccounts(accessToken, options) {
1103
+ async getBrokerAccounts(options) {
1104
+ const accessToken = await this.getValidAccessToken();
1037
1105
  const params = {};
1038
- if (options?.broker_name)
1039
- params.broker_name = options.broker_name;
1040
- if (options?.account_id)
1106
+ if (options?.broker_name) {
1107
+ params.broker_id = options.broker_name;
1108
+ }
1109
+ if (options?.account_id) {
1041
1110
  params.account_id = options.account_id;
1111
+ }
1112
+ if (options?.symbol) {
1113
+ params.symbol = options.symbol;
1114
+ }
1042
1115
  return this.request('/brokers/data/accounts', {
1043
1116
  method: 'GET',
1044
1117
  headers: {
1045
- 'Content-Type': 'application/json',
1046
- Authorization: `Bearer ${accessToken}`,
1047
- 'Session-ID': this.currentSessionId || '',
1048
- 'X-Session-ID': this.currentSessionId || '',
1049
- 'X-Device-Info': JSON.stringify({
1050
- ip_address: this.deviceInfo?.ip_address || '',
1051
- user_agent: this.deviceInfo?.user_agent || '',
1052
- fingerprint: this.deviceInfo?.fingerprint || '',
1053
- }),
1118
+ 'Authorization': `Bearer ${accessToken}`,
1054
1119
  },
1055
1120
  params,
1056
1121
  });
1057
1122
  }
1058
- async getBrokerOrders(accessToken, options) {
1123
+ async getBrokerOrders(options) {
1124
+ const accessToken = await this.getValidAccessToken();
1059
1125
  const params = {};
1060
- if (options?.broker_name)
1061
- params.broker_name = options.broker_name;
1062
- if (options?.account_id)
1126
+ if (options?.broker_name) {
1127
+ params.broker_id = options.broker_name;
1128
+ }
1129
+ if (options?.account_id) {
1063
1130
  params.account_id = options.account_id;
1064
- if (options?.symbol)
1131
+ }
1132
+ if (options?.symbol) {
1065
1133
  params.symbol = options.symbol;
1134
+ }
1066
1135
  return this.request('/brokers/data/orders', {
1067
1136
  method: 'GET',
1068
1137
  headers: {
1069
- 'Content-Type': 'application/json',
1070
- Authorization: `Bearer ${accessToken}`,
1071
- 'Session-ID': this.currentSessionId || '',
1072
- 'X-Session-ID': this.currentSessionId || '',
1073
- 'X-Device-Info': JSON.stringify({
1074
- ip_address: this.deviceInfo?.ip_address || '',
1075
- user_agent: this.deviceInfo?.user_agent || '',
1076
- fingerprint: this.deviceInfo?.fingerprint || '',
1077
- }),
1138
+ 'Authorization': `Bearer ${accessToken}`,
1078
1139
  },
1079
1140
  params,
1080
1141
  });
1081
1142
  }
1082
- async getBrokerPositions(accessToken, options) {
1083
- const queryParams = {};
1084
- if (options?.broker_name)
1085
- queryParams.broker_name = options.broker_name;
1086
- if (options?.account_id)
1087
- queryParams.account_id = options.account_id;
1088
- if (options?.symbol)
1089
- queryParams.symbol = options.symbol;
1143
+ async getBrokerPositions(options) {
1144
+ const accessToken = await this.getValidAccessToken();
1145
+ const params = {};
1146
+ if (options?.broker_name) {
1147
+ params.broker_id = options.broker_name;
1148
+ }
1149
+ if (options?.account_id) {
1150
+ params.account_id = options.account_id;
1151
+ }
1152
+ if (options?.symbol) {
1153
+ params.symbol = options.symbol;
1154
+ }
1090
1155
  return this.request('/brokers/data/positions', {
1091
1156
  method: 'GET',
1092
1157
  headers: {
1093
- Authorization: `Bearer ${accessToken}`,
1094
- 'Content-Type': 'application/json',
1095
- 'Session-ID': this.currentSessionId || '',
1096
- 'X-Session-ID': this.currentSessionId || '',
1097
- 'X-Device-Info': JSON.stringify({
1098
- ip_address: this.deviceInfo?.ip_address || '',
1099
- user_agent: this.deviceInfo?.user_agent || '',
1100
- fingerprint: this.deviceInfo?.fingerprint || '',
1101
- }),
1158
+ 'Authorization': `Bearer ${accessToken}`,
1102
1159
  },
1103
- params: queryParams,
1160
+ params,
1104
1161
  });
1105
1162
  }
1106
- async getBrokerConnections(accessToken) {
1163
+ async getBrokerConnections() {
1164
+ const accessToken = await this.getValidAccessToken();
1107
1165
  return this.request('/brokers/connections', {
1108
1166
  method: 'GET',
1109
1167
  headers: {
1110
- Authorization: `Bearer ${accessToken}`,
1111
- 'Content-Type': 'application/json',
1168
+ 'Authorization': `Bearer ${accessToken}`,
1112
1169
  },
1113
1170
  });
1114
1171
  }
1115
- // Automatic token management versions of broker methods
1116
- async getBrokerListAuto() {
1117
- const accessToken = await this.getValidAccessToken();
1118
- return this.getBrokerList(accessToken);
1119
- }
1120
- async getBrokerAccountsAuto(options) {
1121
- const accessToken = await this.getValidAccessToken();
1122
- return this.getBrokerAccounts(accessToken, options);
1123
- }
1124
- async getBrokerOrdersAuto(options) {
1125
- const accessToken = await this.getValidAccessToken();
1126
- return this.getBrokerOrders(accessToken, options);
1127
- }
1128
- async getBrokerPositionsAuto(options) {
1129
- const accessToken = await this.getValidAccessToken();
1130
- return this.getBrokerPositions(accessToken, options);
1131
- }
1132
- async getBrokerConnectionsAuto() {
1133
- const accessToken = await this.getValidAccessToken();
1134
- return this.getBrokerConnections(accessToken);
1135
- }
1136
- // Automatic token management versions of trading methods
1137
- async placeBrokerOrderAuto(params, extras = {}) {
1138
- const accessToken = await this.getValidAccessToken();
1139
- return this.placeBrokerOrder(accessToken, params, extras);
1140
- }
1141
- async placeStockMarketOrderAuto(symbol, orderQty, action, broker, accountNumber, extras = {}) {
1142
- const accessToken = await this.getValidAccessToken();
1143
- return this.placeStockMarketOrder(accessToken, symbol, orderQty, action, broker, accountNumber, extras);
1144
- }
1145
- async placeStockLimitOrderAuto(symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
1146
- const accessToken = await this.getValidAccessToken();
1147
- return this.placeStockLimitOrder(accessToken, symbol, orderQty, action, price, timeInForce, broker, accountNumber, extras);
1148
- }
1149
- async placeStockStopOrderAuto(symbol, orderQty, action, stopPrice, timeInForce = 'day', broker, accountNumber, extras = {}) {
1150
- const accessToken = await this.getValidAccessToken();
1151
- return this.placeStockStopOrder(accessToken, symbol, orderQty, action, stopPrice, timeInForce, broker, accountNumber, extras);
1152
- }
1153
1172
  // Page-based pagination methods
1154
1173
  async getBrokerOrdersPage(page = 1, perPage = 100, filters) {
1155
1174
  const accessToken = await this.getValidAccessToken();
@@ -1182,25 +1201,57 @@ class ApiClient {
1182
1201
  const response = await this.request('/brokers/data/orders', {
1183
1202
  method: 'GET',
1184
1203
  headers: {
1185
- 'Content-Type': 'application/json',
1186
1204
  Authorization: `Bearer ${accessToken}`,
1187
- 'X-Company-ID': this.companyId || '',
1188
- 'X-Session-ID': this.currentSessionId || '',
1189
- 'X-CSRF-Token': this.csrfToken || '',
1190
- 'X-Device-Info': JSON.stringify({
1191
- ip_address: this.deviceInfo?.ip_address || '',
1192
- user_agent: this.deviceInfo?.user_agent || '',
1193
- fingerprint: this.deviceInfo?.fingerprint || '',
1194
- }),
1195
1205
  },
1196
1206
  params,
1197
1207
  });
1208
+ // Create navigation callback for pagination
1209
+ const navigationCallback = async (newOffset, newLimit) => {
1210
+ const newParams = {
1211
+ limit: newLimit.toString(),
1212
+ offset: newOffset.toString(),
1213
+ };
1214
+ // Add filter parameters
1215
+ if (filters) {
1216
+ if (filters.broker_id)
1217
+ newParams.broker_id = filters.broker_id;
1218
+ if (filters.connection_id)
1219
+ newParams.connection_id = filters.connection_id;
1220
+ if (filters.account_id)
1221
+ newParams.account_id = filters.account_id;
1222
+ if (filters.symbol)
1223
+ newParams.symbol = filters.symbol;
1224
+ if (filters.status)
1225
+ newParams.status = filters.status;
1226
+ if (filters.side)
1227
+ newParams.side = filters.side;
1228
+ if (filters.asset_type)
1229
+ newParams.asset_type = filters.asset_type;
1230
+ if (filters.created_after)
1231
+ newParams.created_after = filters.created_after;
1232
+ if (filters.created_before)
1233
+ newParams.created_before = filters.created_before;
1234
+ }
1235
+ const newResponse = await this.request('/brokers/data/orders', {
1236
+ method: 'GET',
1237
+ headers: {
1238
+ Authorization: `Bearer ${accessToken}`,
1239
+ },
1240
+ params: newParams,
1241
+ });
1242
+ return new PaginatedResult(newResponse.response_data, newResponse.pagination || {
1243
+ has_more: false,
1244
+ next_offset: newOffset,
1245
+ current_offset: newOffset,
1246
+ limit: newLimit,
1247
+ }, navigationCallback);
1248
+ };
1198
1249
  return new PaginatedResult(response.response_data, response.pagination || {
1199
1250
  has_more: false,
1200
1251
  next_offset: offset,
1201
1252
  current_offset: offset,
1202
1253
  limit: perPage,
1203
- });
1254
+ }, navigationCallback);
1204
1255
  }
1205
1256
  async getBrokerAccountsPage(page = 1, perPage = 100, filters) {
1206
1257
  const accessToken = await this.getValidAccessToken();
@@ -1225,25 +1276,49 @@ class ApiClient {
1225
1276
  const response = await this.request('/brokers/data/accounts', {
1226
1277
  method: 'GET',
1227
1278
  headers: {
1228
- 'Content-Type': 'application/json',
1229
1279
  Authorization: `Bearer ${accessToken}`,
1230
- 'X-Company-ID': this.companyId || '',
1231
- 'X-Session-ID': this.currentSessionId || '',
1232
- 'X-CSRF-Token': this.csrfToken || '',
1233
- 'X-Device-Info': JSON.stringify({
1234
- ip_address: this.deviceInfo?.ip_address || '',
1235
- user_agent: this.deviceInfo?.user_agent || '',
1236
- fingerprint: this.deviceInfo?.fingerprint || '',
1237
- }),
1238
1280
  },
1239
1281
  params,
1240
1282
  });
1283
+ // Create navigation callback for pagination
1284
+ const navigationCallback = async (newOffset, newLimit) => {
1285
+ const newParams = {
1286
+ limit: newLimit.toString(),
1287
+ offset: newOffset.toString(),
1288
+ };
1289
+ // Add filter parameters
1290
+ if (filters) {
1291
+ if (filters.broker_id)
1292
+ newParams.broker_id = filters.broker_id;
1293
+ if (filters.connection_id)
1294
+ newParams.connection_id = filters.connection_id;
1295
+ if (filters.account_type)
1296
+ newParams.account_type = filters.account_type;
1297
+ if (filters.status)
1298
+ newParams.status = filters.status;
1299
+ if (filters.currency)
1300
+ newParams.currency = filters.currency;
1301
+ }
1302
+ const newResponse = await this.request('/brokers/data/accounts', {
1303
+ method: 'GET',
1304
+ headers: {
1305
+ Authorization: `Bearer ${accessToken}`,
1306
+ },
1307
+ params: newParams,
1308
+ });
1309
+ return new PaginatedResult(newResponse.response_data, newResponse.pagination || {
1310
+ has_more: false,
1311
+ next_offset: newOffset,
1312
+ current_offset: newOffset,
1313
+ limit: newLimit,
1314
+ }, navigationCallback);
1315
+ };
1241
1316
  return new PaginatedResult(response.response_data, response.pagination || {
1242
1317
  has_more: false,
1243
1318
  next_offset: offset,
1244
1319
  current_offset: offset,
1245
1320
  limit: perPage,
1246
- });
1321
+ }, navigationCallback);
1247
1322
  }
1248
1323
  async getBrokerPositionsPage(page = 1, perPage = 100, filters) {
1249
1324
  const accessToken = await this.getValidAccessToken();
@@ -1268,25 +1343,49 @@ class ApiClient {
1268
1343
  const response = await this.request('/brokers/data/positions', {
1269
1344
  method: 'GET',
1270
1345
  headers: {
1271
- 'Content-Type': 'application/json',
1272
1346
  Authorization: `Bearer ${accessToken}`,
1273
- 'X-Company-ID': this.companyId || '',
1274
- 'X-Session-ID': this.currentSessionId || '',
1275
- 'X-CSRF-Token': this.csrfToken || '',
1276
- 'X-Device-Info': JSON.stringify({
1277
- ip_address: this.deviceInfo?.ip_address || '',
1278
- user_agent: this.deviceInfo?.user_agent || '',
1279
- fingerprint: this.deviceInfo?.fingerprint || '',
1280
- }),
1281
1347
  },
1282
1348
  params,
1283
1349
  });
1350
+ // Create navigation callback for pagination
1351
+ const navigationCallback = async (newOffset, newLimit) => {
1352
+ const newParams = {
1353
+ limit: newLimit.toString(),
1354
+ offset: newOffset.toString(),
1355
+ };
1356
+ // Add filter parameters
1357
+ if (filters) {
1358
+ if (filters.broker_id)
1359
+ newParams.broker_id = filters.broker_id;
1360
+ if (filters.account_id)
1361
+ newParams.account_id = filters.account_id;
1362
+ if (filters.symbol)
1363
+ newParams.symbol = filters.symbol;
1364
+ if (filters.position_status)
1365
+ newParams.position_status = filters.position_status;
1366
+ if (filters.side)
1367
+ newParams.side = filters.side;
1368
+ }
1369
+ const newResponse = await this.request('/brokers/data/positions', {
1370
+ method: 'GET',
1371
+ headers: {
1372
+ Authorization: `Bearer ${accessToken}`,
1373
+ },
1374
+ params: newParams,
1375
+ });
1376
+ return new PaginatedResult(newResponse.response_data, newResponse.pagination || {
1377
+ has_more: false,
1378
+ next_offset: newOffset,
1379
+ current_offset: newOffset,
1380
+ limit: newLimit,
1381
+ }, navigationCallback);
1382
+ };
1284
1383
  return new PaginatedResult(response.response_data, response.pagination || {
1285
1384
  has_more: false,
1286
1385
  next_offset: offset,
1287
1386
  current_offset: offset,
1288
1387
  limit: perPage,
1289
- });
1388
+ }, navigationCallback);
1290
1389
  }
1291
1390
  // Navigation methods
1292
1391
  async getNextPage(previousResult, fetchFunction) {
@@ -1302,6 +1401,20 @@ class ApiClient {
1302
1401
  isMockClient() {
1303
1402
  return false;
1304
1403
  }
1404
+ /**
1405
+ * Disconnect a company from a broker connection
1406
+ * @param connectionId - The connection ID to disconnect
1407
+ * @returns Promise with disconnect response
1408
+ */
1409
+ async disconnectCompany(connectionId) {
1410
+ const accessToken = await this.getValidAccessToken();
1411
+ return this.request(`/brokers/disconnect-company/${connectionId}`, {
1412
+ method: 'DELETE',
1413
+ headers: {
1414
+ 'Authorization': `Bearer ${accessToken}`,
1415
+ },
1416
+ });
1417
+ }
1305
1418
  }
1306
1419
 
1307
1420
  class EventEmitter {
@@ -1807,6 +1920,17 @@ class MockDataProvider {
1807
1920
  async mockGetBrokerList() {
1808
1921
  await this.simulateDelay();
1809
1922
  const brokers = [
1923
+ {
1924
+ id: 'alpaca',
1925
+ name: 'alpaca',
1926
+ display_name: 'Alpaca',
1927
+ description: 'Commission-free stock trading and crypto',
1928
+ website: 'https://alpaca.markets',
1929
+ features: ['stocks', 'crypto', 'fractional_shares', 'api_trading'],
1930
+ auth_type: 'api_key',
1931
+ logo_path: '/logos/alpaca.png',
1932
+ is_active: true,
1933
+ },
1810
1934
  {
1811
1935
  id: 'robinhood',
1812
1936
  name: 'robinhood',
@@ -2120,14 +2244,25 @@ class MockDataProvider {
2120
2244
  };
2121
2245
  }
2122
2246
  async mockPlaceOrder(order) {
2123
- await this.simulateDelay();
2247
+ // Simulate API delay
2248
+ await new Promise(resolve => setTimeout(resolve, 100));
2249
+ // Generate a mock order ID
2250
+ const orderId = `mock_order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2124
2251
  return {
2125
2252
  success: true,
2126
2253
  response_data: {
2127
- orderId: v4(),
2128
- status: 'submitted',
2129
- broker: 'robinhood',
2130
- accountNumber: '123456789',
2254
+ orderId: orderId,
2255
+ status: 'pending',
2256
+ broker: order.broker,
2257
+ accountNumber: order.accountNumber,
2258
+ symbol: order.symbol,
2259
+ orderType: order.orderType,
2260
+ assetType: order.assetType,
2261
+ action: order.action,
2262
+ quantity: order.orderQty,
2263
+ price: order.price,
2264
+ stopPrice: order.stopPrice,
2265
+ timeInForce: order.timeInForce,
2131
2266
  },
2132
2267
  message: 'Order placed successfully',
2133
2268
  status_code: 200,
@@ -2223,9 +2358,9 @@ class MockDataProvider {
2223
2358
  }
2224
2359
  applyBrokerAccountFilters(accounts, filter) {
2225
2360
  return accounts.filter(account => {
2226
- if (filter.broker_id && account.broker_id !== filter.broker_id)
2361
+ if (filter.broker_id && account.user_broker_connection_id !== filter.connection_id)
2227
2362
  return false;
2228
- if (filter.connection_id && account.connection_id !== filter.connection_id)
2363
+ if (filter.connection_id && account.user_broker_connection_id !== filter.connection_id)
2229
2364
  return false;
2230
2365
  if (filter.account_type && account.account_type !== filter.account_type)
2231
2366
  return false;
@@ -2365,7 +2500,6 @@ class MockDataProvider {
2365
2500
  */
2366
2501
  generateMockAccounts(count) {
2367
2502
  const accounts = [];
2368
- const brokers = ['robinhood', 'alpaca', 'tasty_trade', 'ninja_trader'];
2369
2503
  const accountTypes = ['margin', 'cash', 'crypto_wallet', 'live', 'sim'];
2370
2504
  const statuses = ['active', 'inactive'];
2371
2505
  const currencies = ['USD', 'EUR', 'GBP', 'CAD'];
@@ -2380,26 +2514,22 @@ class MockDataProvider {
2380
2514
  'Investment Account',
2381
2515
  ];
2382
2516
  for (let i = 0; i < count; i++) {
2383
- const broker = brokers[Math.floor(Math.random() * brokers.length)];
2384
2517
  const accountType = accountTypes[Math.floor(Math.random() * accountTypes.length)];
2385
2518
  const status = statuses[Math.floor(Math.random() * statuses.length)];
2386
2519
  const currency = currencies[Math.floor(Math.random() * currencies.length)];
2387
2520
  const accountName = accountNames[Math.floor(Math.random() * accountNames.length)];
2388
2521
  const cashBalance = Math.random() * 100000 + 1000;
2389
2522
  const buyingPower = cashBalance * (1 + Math.random() * 2); // 1-3x cash balance
2390
- const equity = buyingPower * (0.8 + Math.random() * 0.4); // ±20% variation
2391
2523
  accounts.push({
2392
2524
  id: v4(),
2393
- broker_id: broker,
2394
- connection_id: v4(),
2395
- account_id: `account_${Math.floor(Math.random() * 1000000)}`,
2525
+ user_broker_connection_id: v4(),
2526
+ broker_provided_account_id: `account_${Math.floor(Math.random() * 1000000)}`,
2396
2527
  account_name: `${accountName} ${i + 1}`,
2397
2528
  account_type: accountType,
2398
- status,
2399
2529
  currency,
2400
2530
  cash_balance: cashBalance,
2401
2531
  buying_power: buyingPower,
2402
- equity,
2532
+ status,
2403
2533
  created_at: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000).toISOString(), // Random date within last year
2404
2534
  updated_at: new Date().toISOString(),
2405
2535
  last_synced_at: new Date().toISOString(),
@@ -2407,6 +2537,42 @@ class MockDataProvider {
2407
2537
  }
2408
2538
  return accounts;
2409
2539
  }
2540
+ /**
2541
+ * Mock disconnect company method
2542
+ * @param connectionId - The connection ID to disconnect
2543
+ * @returns Promise with mock disconnect response
2544
+ */
2545
+ async mockDisconnectCompany(connectionId) {
2546
+ // Simulate API delay
2547
+ await new Promise(resolve => setTimeout(resolve, 200));
2548
+ // Randomly decide if this will delete the connection or just remove company access
2549
+ const willDeleteConnection = Math.random() > 0.5;
2550
+ if (willDeleteConnection) {
2551
+ return {
2552
+ success: true,
2553
+ response_data: {
2554
+ connection_id: connectionId,
2555
+ action: 'connection_deleted',
2556
+ message: 'Company access removed and connection deleted (no companies remaining)',
2557
+ },
2558
+ message: 'Company access removed and connection deleted (no companies remaining)',
2559
+ status_code: 200,
2560
+ };
2561
+ }
2562
+ else {
2563
+ return {
2564
+ success: true,
2565
+ response_data: {
2566
+ connection_id: connectionId,
2567
+ action: 'company_access_removed',
2568
+ remaining_companies: Math.floor(Math.random() * 5) + 1, // 1-5 remaining companies
2569
+ message: 'Company access removed from connection',
2570
+ },
2571
+ message: 'Company access removed from connection',
2572
+ status_code: 200,
2573
+ };
2574
+ }
2575
+ }
2410
2576
  }
2411
2577
 
2412
2578
  /**
@@ -2616,38 +2782,34 @@ class MockApiClient {
2616
2782
  return this.mockDataProvider.mockCompletePortalSession(sessionId);
2617
2783
  }
2618
2784
  // Portfolio Management
2619
- async getHoldings(accessToken) {
2785
+ async getHoldings(filter) {
2786
+ await this.getValidAccessToken();
2620
2787
  return this.mockDataProvider.mockGetHoldings();
2621
2788
  }
2622
- async getOrders(accessToken, filter) {
2789
+ async getOrders(filter) {
2790
+ await this.getValidAccessToken();
2623
2791
  return this.mockDataProvider.mockGetOrders(filter);
2624
2792
  }
2625
- async getPortfolio(accessToken) {
2793
+ async getPortfolio() {
2794
+ await this.getValidAccessToken();
2626
2795
  return this.mockDataProvider.mockGetPortfolio();
2627
2796
  }
2628
- async placeOrder(accessToken, order) {
2797
+ async placeOrder(order) {
2798
+ await this.getValidAccessToken();
2629
2799
  await this.mockDataProvider.mockPlaceOrder(order);
2630
2800
  }
2631
- // New methods with automatic token management
2632
- async getHoldingsAuto() {
2633
- const accessToken = await this.getValidAccessToken();
2634
- return this.getHoldings(accessToken);
2635
- }
2636
- async getOrdersAuto() {
2637
- const accessToken = await this.getValidAccessToken();
2638
- return this.getOrders(accessToken);
2639
- }
2640
- async getPortfolioAuto() {
2641
- const accessToken = await this.getValidAccessToken();
2642
- return this.getPortfolio(accessToken);
2643
- }
2644
- async placeOrderAuto(order) {
2645
- const accessToken = await this.getValidAccessToken();
2646
- return this.placeOrder(accessToken, order);
2647
- }
2648
2801
  // Enhanced Trading Methods with Session Management
2649
- async placeBrokerOrder(accessToken, params, extras = {}) {
2650
- // Merge context with provided parameters
2802
+ async placeBrokerOrder(params, extras = {}, connection_id) {
2803
+ await this.getValidAccessToken();
2804
+ // Debug logging
2805
+ console.log('MockApiClient.placeBrokerOrder Debug:', {
2806
+ params,
2807
+ tradingContext: this.tradingContext,
2808
+ paramsBroker: params.broker,
2809
+ contextBroker: this.tradingContext.broker,
2810
+ paramsAccountNumber: params.accountNumber,
2811
+ contextAccountNumber: this.tradingContext.accountNumber
2812
+ });
2651
2813
  const fullParams = {
2652
2814
  broker: (params.broker || this.tradingContext.broker) ||
2653
2815
  (() => {
@@ -2666,40 +2828,33 @@ class MockApiClient {
2666
2828
  timeInForce: params.timeInForce || 'day',
2667
2829
  price: params.price,
2668
2830
  stopPrice: params.stopPrice,
2831
+ order_id: params.order_id,
2669
2832
  };
2670
- // Convert timeInForce to the Order interface format
2671
- const timeInForce = fullParams.timeInForce === 'gtd' ? 'gtc' : fullParams.timeInForce;
2672
- return this.mockDataProvider.mockPlaceOrder({
2673
- symbol: fullParams.symbol,
2674
- side: fullParams.action.toLowerCase(),
2675
- quantity: fullParams.orderQty,
2676
- type_: fullParams.orderType.toLowerCase(),
2677
- timeInForce: timeInForce,
2678
- price: fullParams.price,
2679
- stopPrice: fullParams.stopPrice,
2680
- });
2833
+ console.log('MockApiClient.placeBrokerOrder Debug - Final params:', fullParams);
2834
+ return this.mockDataProvider.mockPlaceOrder(fullParams);
2681
2835
  }
2682
- async cancelBrokerOrder(orderId, broker, extras = {}) {
2836
+ async cancelBrokerOrder(orderId, broker, extras = {}, connection_id) {
2837
+ // Mock successful cancellation
2683
2838
  return {
2684
2839
  success: true,
2685
2840
  response_data: {
2686
- orderId,
2841
+ orderId: orderId,
2687
2842
  status: 'cancelled',
2688
- broker: broker || this.tradingContext.broker || 'robinhood',
2689
- accountNumber: this.tradingContext.accountNumber || '123456789',
2843
+ broker: broker || this.tradingContext.broker,
2690
2844
  },
2691
2845
  message: 'Order cancelled successfully',
2692
2846
  status_code: 200,
2693
2847
  };
2694
2848
  }
2695
- async modifyBrokerOrder(orderId, params, broker, extras = {}) {
2849
+ async modifyBrokerOrder(orderId, params, broker, extras = {}, connection_id) {
2850
+ // Mock successful modification
2696
2851
  return {
2697
2852
  success: true,
2698
2853
  response_data: {
2699
- orderId,
2854
+ orderId: orderId,
2700
2855
  status: 'modified',
2701
- broker: broker || this.tradingContext.broker || 'robinhood',
2702
- accountNumber: this.tradingContext.accountNumber || '123456789',
2856
+ broker: broker || this.tradingContext.broker,
2857
+ ...params,
2703
2858
  },
2704
2859
  message: 'Order modified successfully',
2705
2860
  status_code: 200,
@@ -2713,8 +2868,14 @@ class MockApiClient {
2713
2868
  this.tradingContext.accountId = undefined;
2714
2869
  }
2715
2870
  setAccount(accountNumber, accountId) {
2871
+ console.log('MockApiClient.setAccount Debug:', {
2872
+ accountNumber,
2873
+ accountId,
2874
+ previousContext: { ...this.tradingContext }
2875
+ });
2716
2876
  this.tradingContext.accountNumber = accountNumber;
2717
2877
  this.tradingContext.accountId = accountId;
2878
+ console.log('MockApiClient.setAccount Debug - Updated context:', this.tradingContext);
2718
2879
  }
2719
2880
  getTradingContext() {
2720
2881
  return { ...this.tradingContext };
@@ -2723,8 +2884,8 @@ class MockApiClient {
2723
2884
  this.tradingContext = {};
2724
2885
  }
2725
2886
  // Stock convenience methods
2726
- async placeStockMarketOrder(accessToken, symbol, orderQty, action, broker, accountNumber, extras = {}) {
2727
- return this.placeBrokerOrder(accessToken, {
2887
+ async placeStockMarketOrder(symbol, orderQty, action, broker, accountNumber, extras = {}) {
2888
+ return this.placeBrokerOrder({
2728
2889
  broker,
2729
2890
  accountNumber,
2730
2891
  symbol,
@@ -2735,8 +2896,8 @@ class MockApiClient {
2735
2896
  timeInForce: 'day',
2736
2897
  }, extras);
2737
2898
  }
2738
- async placeStockLimitOrder(accessToken, symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
2739
- return this.placeBrokerOrder(accessToken, {
2899
+ async placeStockLimitOrder(symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
2900
+ return this.placeBrokerOrder({
2740
2901
  broker,
2741
2902
  accountNumber,
2742
2903
  symbol,
@@ -2748,8 +2909,8 @@ class MockApiClient {
2748
2909
  price,
2749
2910
  }, extras);
2750
2911
  }
2751
- async placeStockStopOrder(accessToken, symbol, orderQty, action, stopPrice, timeInForce = 'day', broker, accountNumber, extras = {}) {
2752
- return this.placeBrokerOrder(accessToken, {
2912
+ async placeStockStopOrder(symbol, orderQty, action, stopPrice, timeInForce = 'day', broker, accountNumber, extras = {}) {
2913
+ return this.placeBrokerOrder({
2753
2914
  broker,
2754
2915
  accountNumber,
2755
2916
  symbol,
@@ -2762,7 +2923,7 @@ class MockApiClient {
2762
2923
  }, extras);
2763
2924
  }
2764
2925
  // Crypto convenience methods
2765
- async placeCryptoMarketOrder(accessToken, symbol, orderQty, action, options = {}, broker, accountNumber, extras = {}) {
2926
+ async placeCryptoMarketOrder(symbol, orderQty, action, options = {}, broker, accountNumber, extras = {}) {
2766
2927
  const orderParams = {
2767
2928
  broker,
2768
2929
  accountNumber,
@@ -2773,9 +2934,9 @@ class MockApiClient {
2773
2934
  assetType: 'Crypto',
2774
2935
  timeInForce: 'gtc', // Crypto typically uses GTC
2775
2936
  };
2776
- return this.placeBrokerOrder(accessToken, orderParams, extras);
2937
+ return this.placeBrokerOrder(orderParams, extras);
2777
2938
  }
2778
- async placeCryptoLimitOrder(accessToken, symbol, orderQty, action, price, timeInForce = 'gtc', options = {}, broker, accountNumber, extras = {}) {
2939
+ async placeCryptoLimitOrder(symbol, orderQty, action, price, timeInForce = 'gtc', options = {}, broker, accountNumber, extras = {}) {
2779
2940
  const orderParams = {
2780
2941
  broker,
2781
2942
  accountNumber,
@@ -2787,10 +2948,10 @@ class MockApiClient {
2787
2948
  timeInForce,
2788
2949
  price,
2789
2950
  };
2790
- return this.placeBrokerOrder(accessToken, orderParams, extras);
2951
+ return this.placeBrokerOrder(orderParams, extras);
2791
2952
  }
2792
2953
  // Options convenience methods
2793
- async placeOptionsMarketOrder(accessToken, symbol, orderQty, action, options, broker, accountNumber, extras = {}) {
2954
+ async placeOptionsMarketOrder(symbol, orderQty, action, options, broker, accountNumber, extras = {}) {
2794
2955
  const orderParams = {
2795
2956
  broker,
2796
2957
  accountNumber,
@@ -2801,9 +2962,9 @@ class MockApiClient {
2801
2962
  assetType: 'Option',
2802
2963
  timeInForce: 'day',
2803
2964
  };
2804
- return this.placeBrokerOrder(accessToken, orderParams, extras);
2965
+ return this.placeBrokerOrder(orderParams, extras);
2805
2966
  }
2806
- async placeOptionsLimitOrder(accessToken, symbol, orderQty, action, price, options, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
2967
+ async placeOptionsLimitOrder(symbol, orderQty, action, price, options, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
2807
2968
  const orderParams = {
2808
2969
  broker,
2809
2970
  accountNumber,
@@ -2815,30 +2976,30 @@ class MockApiClient {
2815
2976
  timeInForce,
2816
2977
  price,
2817
2978
  };
2818
- return this.placeBrokerOrder(accessToken, orderParams, extras);
2979
+ return this.placeBrokerOrder(orderParams, extras);
2819
2980
  }
2820
2981
  // Futures convenience methods
2821
- async placeFuturesMarketOrder(accessToken, symbol, orderQty, action, broker, accountNumber, extras = {}) {
2822
- return this.placeBrokerOrder(accessToken, {
2982
+ async placeFuturesMarketOrder(symbol, orderQty, action, broker, accountNumber, extras = {}) {
2983
+ return this.placeBrokerOrder({
2823
2984
  broker,
2824
2985
  accountNumber,
2825
2986
  symbol,
2826
2987
  orderQty,
2827
2988
  action,
2828
2989
  orderType: 'Market',
2829
- assetType: 'Futures',
2990
+ assetType: 'Future',
2830
2991
  timeInForce: 'day',
2831
2992
  }, extras);
2832
2993
  }
2833
- async placeFuturesLimitOrder(accessToken, symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
2834
- return this.placeBrokerOrder(accessToken, {
2994
+ async placeFuturesLimitOrder(symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
2995
+ return this.placeBrokerOrder({
2835
2996
  broker,
2836
2997
  accountNumber,
2837
2998
  symbol,
2838
2999
  orderQty,
2839
3000
  action,
2840
3001
  orderType: 'Limit',
2841
- assetType: 'Futures',
3002
+ assetType: 'Future',
2842
3003
  timeInForce,
2843
3004
  price,
2844
3005
  }, extras);
@@ -2858,13 +3019,16 @@ class MockApiClient {
2858
3019
  return this.currentSessionState;
2859
3020
  }
2860
3021
  // Broker Data Management
2861
- async getBrokerList(accessToken) {
3022
+ async getBrokerList() {
3023
+ await this.getValidAccessToken();
2862
3024
  return this.mockDataProvider.mockGetBrokerList();
2863
3025
  }
2864
- async getBrokerAccounts(accessToken, options) {
3026
+ async getBrokerAccounts(options) {
3027
+ await this.getValidAccessToken();
2865
3028
  return this.mockDataProvider.mockGetBrokerAccounts();
2866
3029
  }
2867
- async getBrokerOrders(accessToken, options) {
3030
+ async getBrokerOrders(options) {
3031
+ await this.getValidAccessToken();
2868
3032
  // Return empty orders for now - keeping original interface
2869
3033
  return {
2870
3034
  _id: v4(),
@@ -2875,7 +3039,8 @@ class MockApiClient {
2875
3039
  errors: null,
2876
3040
  };
2877
3041
  }
2878
- async getBrokerPositions(accessToken, options) {
3042
+ async getBrokerPositions(options) {
3043
+ await this.getValidAccessToken();
2879
3044
  // Return empty positions for now - keeping original interface
2880
3045
  return {
2881
3046
  _id: v4(),
@@ -2987,46 +3152,18 @@ class MockApiClient {
2987
3152
  limit: perPage,
2988
3153
  }, navigationCallback);
2989
3154
  }
2990
- async getBrokerConnections(accessToken) {
3155
+ async getBrokerConnections() {
3156
+ await this.getValidAccessToken();
2991
3157
  return this.mockDataProvider.mockGetBrokerConnections();
2992
3158
  }
2993
- // Automatic token management versions of broker methods
2994
- async getBrokerListAuto() {
2995
- const accessToken = await this.getValidAccessToken();
2996
- return this.getBrokerList(accessToken);
2997
- }
2998
- async getBrokerAccountsAuto(options) {
2999
- const accessToken = await this.getValidAccessToken();
3000
- return this.getBrokerAccounts(accessToken, options);
3001
- }
3002
- async getBrokerOrdersAuto(options) {
3003
- const accessToken = await this.getValidAccessToken();
3004
- return this.getBrokerOrders(accessToken, options);
3005
- }
3006
- async getBrokerPositionsAuto(options) {
3007
- const accessToken = await this.getValidAccessToken();
3008
- return this.getBrokerPositions(accessToken, options);
3009
- }
3010
- async getBrokerConnectionsAuto() {
3011
- const accessToken = await this.getValidAccessToken();
3012
- return this.getBrokerConnections(accessToken);
3013
- }
3014
- // Automatic token management versions of trading methods
3015
- async placeBrokerOrderAuto(params, extras = {}) {
3016
- const accessToken = await this.getValidAccessToken();
3017
- return this.placeBrokerOrder(accessToken, params, extras);
3018
- }
3019
- async placeStockMarketOrderAuto(symbol, orderQty, action, broker, accountNumber, extras = {}) {
3020
- const accessToken = await this.getValidAccessToken();
3021
- return this.placeStockMarketOrder(accessToken, symbol, orderQty, action, broker, accountNumber, extras);
3022
- }
3023
- async placeStockLimitOrderAuto(symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
3024
- const accessToken = await this.getValidAccessToken();
3025
- return this.placeStockLimitOrder(accessToken, symbol, orderQty, action, price, timeInForce, broker, accountNumber, extras);
3026
- }
3027
- async placeStockStopOrderAuto(symbol, orderQty, action, stopPrice, timeInForce = 'day', broker, accountNumber, extras = {}) {
3028
- const accessToken = await this.getValidAccessToken();
3029
- return this.placeStockStopOrder(accessToken, symbol, orderQty, action, stopPrice, timeInForce, broker, accountNumber, extras);
3159
+ /**
3160
+ * Mock disconnect company method
3161
+ * @param connectionId - The connection ID to disconnect
3162
+ * @returns Promise with mock disconnect response
3163
+ */
3164
+ async disconnectCompany(connectionId) {
3165
+ await this.getValidAccessToken();
3166
+ return this.mockDataProvider.mockDisconnectCompany(connectionId);
3030
3167
  }
3031
3168
  // Utility methods for mock system
3032
3169
  getMockDataProvider() {
@@ -3674,6 +3811,63 @@ function createCustomThemeFromPreset(preset, modifications) {
3674
3811
  };
3675
3812
  }
3676
3813
 
3814
+ /**
3815
+ * Broker filtering utility functions
3816
+ */
3817
+ // Supported broker names and their corresponding IDs
3818
+ const SUPPORTED_BROKERS = {
3819
+ 'alpaca': 'alpaca',
3820
+ 'robinhood': 'robinhood',
3821
+ 'tasty_trade': 'tasty_trade',
3822
+ 'ninja_trader': 'ninja_trader'
3823
+ };
3824
+ /**
3825
+ * Convert broker names to broker IDs, filtering out unsupported ones
3826
+ * @param brokerNames Array of broker names to convert
3827
+ * @returns Object containing valid broker IDs and any warnings about unsupported names
3828
+ */
3829
+ function convertBrokerNamesToIds(brokerNames) {
3830
+ const brokerIds = [];
3831
+ const warnings = [];
3832
+ for (const brokerName of brokerNames) {
3833
+ const brokerId = SUPPORTED_BROKERS[brokerName.toLowerCase()];
3834
+ if (brokerId) {
3835
+ brokerIds.push(brokerId);
3836
+ }
3837
+ else {
3838
+ warnings.push(`Broker name '${brokerName}' is not supported. Supported brokers: ${Object.keys(SUPPORTED_BROKERS).join(', ')}`);
3839
+ }
3840
+ }
3841
+ return { brokerIds, warnings };
3842
+ }
3843
+ /**
3844
+ * Append broker filter parameters to a portal URL
3845
+ * @param baseUrl The base portal URL (may already have query parameters)
3846
+ * @param brokerNames Array of broker names to filter by
3847
+ * @returns The portal URL with broker filter parameters appended
3848
+ */
3849
+ function appendBrokerFilterToURL(baseUrl, brokerNames) {
3850
+ if (!brokerNames || brokerNames.length === 0) {
3851
+ return baseUrl;
3852
+ }
3853
+ try {
3854
+ const url = new URL(baseUrl);
3855
+ const { brokerIds, warnings } = convertBrokerNamesToIds(brokerNames);
3856
+ // Log warnings for unsupported broker names
3857
+ warnings.forEach(warning => console.warn(`[FinaticConnect] ${warning}`));
3858
+ // Only add broker filter if we have valid broker IDs
3859
+ if (brokerIds.length > 0) {
3860
+ const encodedBrokers = btoa(JSON.stringify(brokerIds));
3861
+ url.searchParams.set('brokers', encodedBrokers);
3862
+ }
3863
+ return url.toString();
3864
+ }
3865
+ catch (error) {
3866
+ console.error('Failed to append broker filter to URL:', error);
3867
+ return baseUrl;
3868
+ }
3869
+ }
3870
+
3677
3871
  class FinaticConnect extends EventEmitter {
3678
3872
  constructor(options, deviceInfo) {
3679
3873
  super();
@@ -3683,6 +3877,12 @@ class FinaticConnect extends EventEmitter {
3683
3877
  this.BROKER_LIST_CACHE_VERSION = '1.0';
3684
3878
  this.BROKER_LIST_CACHE_DURATION = 1000 * 60 * 60 * 24; // 24 hours in milliseconds
3685
3879
  this.currentSessionState = null;
3880
+ // Session keep-alive mechanism
3881
+ this.sessionKeepAliveInterval = null;
3882
+ this.SESSION_KEEP_ALIVE_INTERVAL = 1000 * 60 * 5; // 5 minutes
3883
+ this.SESSION_VALIDATION_TIMEOUT = 1000 * 30; // 30 seconds
3884
+ this.SESSION_REFRESH_BUFFER_HOURS = 16; // Refresh session at 16 hours
3885
+ this.sessionStartTime = null;
3686
3886
  this.options = options;
3687
3887
  this.baseUrl = options.baseUrl || 'https://api.finatic.dev';
3688
3888
  this.apiClient = MockFactory.createApiClient(this.baseUrl, deviceInfo);
@@ -3750,13 +3950,6 @@ class FinaticConnect extends EventEmitter {
3750
3950
  const expiresAt = new Date(Date.now() + 3600 * 1000).toISOString(); // 1 hour from now
3751
3951
  this.apiClient.setTokens(tokens.access_token, tokens.refresh_token, expiresAt, userId);
3752
3952
  }
3753
- /**
3754
- * Check if the user is authenticated
3755
- * @returns True if the user has a valid access token
3756
- */
3757
- isAuthenticated() {
3758
- return this.isAuthed();
3759
- }
3760
3953
  /**
3761
3954
  * Check if the user is fully authenticated (has userId, access token, and refresh token)
3762
3955
  * @returns True if the user is fully authenticated and ready for API calls
@@ -3772,7 +3965,7 @@ class FinaticConnect extends EventEmitter {
3772
3965
  * @returns Promise with paginated result that supports navigation
3773
3966
  */
3774
3967
  async getOrders(params) {
3775
- if (!this.isAuthenticated()) {
3968
+ if (!this.isAuthed()) {
3776
3969
  throw new AuthenticationError('User is not authenticated');
3777
3970
  }
3778
3971
  const page = params?.page || 1;
@@ -3786,7 +3979,7 @@ class FinaticConnect extends EventEmitter {
3786
3979
  * @returns Promise with paginated result that supports navigation
3787
3980
  */
3788
3981
  async getPositions(params) {
3789
- if (!this.isAuthenticated()) {
3982
+ if (!this.isAuthed()) {
3790
3983
  throw new AuthenticationError('User is not authenticated');
3791
3984
  }
3792
3985
  const page = params?.page || 1;
@@ -3800,7 +3993,7 @@ class FinaticConnect extends EventEmitter {
3800
3993
  * @returns Promise with paginated result that supports navigation
3801
3994
  */
3802
3995
  async getAccounts(params) {
3803
- if (!this.isAuthenticated()) {
3996
+ if (!this.isAuthed()) {
3804
3997
  throw new AuthenticationError('User is not authenticated');
3805
3998
  }
3806
3999
  const page = params?.page || 1;
@@ -3832,6 +4025,12 @@ class FinaticConnect extends EventEmitter {
3832
4025
  * @returns FinaticConnect instance
3833
4026
  */
3834
4027
  static async init(token, userId, options) {
4028
+ // Safari-specific fix: Clear instance if it exists but has no valid session
4029
+ // This prevents stale instances from interfering with new requests
4030
+ if (FinaticConnect.instance && !FinaticConnect.instance.sessionId) {
4031
+ console.log('[FinaticConnect] Clearing stale instance for Safari compatibility');
4032
+ FinaticConnect.instance = null;
4033
+ }
3835
4034
  if (!FinaticConnect.instance) {
3836
4035
  const connectOptions = {
3837
4036
  token,
@@ -3861,6 +4060,8 @@ class FinaticConnect extends EventEmitter {
3861
4060
  const startResponse = await FinaticConnect.instance.apiClient.startSession(token, normalizedUserId);
3862
4061
  FinaticConnect.instance.sessionId = startResponse.data.session_id;
3863
4062
  FinaticConnect.instance.companyId = startResponse.data.company_id || '';
4063
+ // Record session start time for automatic refresh
4064
+ FinaticConnect.instance.sessionStartTime = Date.now();
3864
4065
  // Set session context in API client
3865
4066
  if (FinaticConnect.instance.apiClient &&
3866
4067
  typeof FinaticConnect.instance.apiClient.setSessionContext === 'function') {
@@ -3970,7 +4171,9 @@ class FinaticConnect extends EventEmitter {
3970
4171
  throw new Error('Failed to get portal URL');
3971
4172
  }
3972
4173
  // Apply theme to portal URL if provided
3973
- const themedPortalUrl = appendThemeToURL(portalResponse.data.portal_url, options?.theme);
4174
+ let themedPortalUrl = appendThemeToURL(portalResponse.data.portal_url, options?.theme);
4175
+ // Apply broker filter to portal URL if provided
4176
+ themedPortalUrl = appendBrokerFilterToURL(themedPortalUrl, options?.brokers);
3974
4177
  // Create portal UI if not exists
3975
4178
  if (!this.portalUI) {
3976
4179
  this.portalUI = new PortalUI(this.baseUrl);
@@ -4110,15 +4313,16 @@ class FinaticConnect extends EventEmitter {
4110
4313
  ? 'Limit'
4111
4314
  : order.orderType === 'stop'
4112
4315
  ? 'Stop'
4113
- : 'TrailingStop',
4316
+ : 'StopLimit',
4114
4317
  assetType: order.assetType || 'Stock',
4115
4318
  timeInForce: order.timeInForce,
4116
4319
  price: order.price,
4117
4320
  stopPrice: order.stopPrice,
4118
4321
  broker: order.broker,
4119
4322
  accountNumber: order.accountNumber,
4323
+ order_id: order.order_id,
4120
4324
  };
4121
- return await this.apiClient.placeBrokerOrder(this.userToken.accessToken, brokerOrder);
4325
+ return await this.apiClient.placeBrokerOrder(this.userToken.accessToken, brokerOrder, {}, order.connection_id);
4122
4326
  }
4123
4327
  catch (error) {
4124
4328
  this.emit('error', error);
@@ -4129,13 +4333,14 @@ class FinaticConnect extends EventEmitter {
4129
4333
  * Cancel a broker order
4130
4334
  * @param orderId - The order ID to cancel
4131
4335
  * @param broker - Optional broker override
4336
+ * @param connection_id - Optional connection ID for testing bypass
4132
4337
  */
4133
- async cancelOrder(orderId, broker) {
4338
+ async cancelOrder(orderId, broker, connection_id) {
4134
4339
  if (!this.userToken) {
4135
4340
  throw new Error('Not initialized with user');
4136
4341
  }
4137
4342
  try {
4138
- return await this.apiClient.cancelBrokerOrder(orderId, broker);
4343
+ return await this.apiClient.cancelBrokerOrder(orderId, broker, {}, connection_id);
4139
4344
  }
4140
4345
  catch (error) {
4141
4346
  this.emit('error', error);
@@ -4147,8 +4352,9 @@ class FinaticConnect extends EventEmitter {
4147
4352
  * @param orderId - The order ID to modify
4148
4353
  * @param modifications - The modifications to apply
4149
4354
  * @param broker - Optional broker override
4355
+ * @param connection_id - Optional connection ID for testing bypass
4150
4356
  */
4151
- async modifyOrder(orderId, modifications, broker) {
4357
+ async modifyOrder(orderId, modifications, broker, connection_id) {
4152
4358
  if (!this.userToken) {
4153
4359
  throw new Error('Not initialized with user');
4154
4360
  }
@@ -4165,7 +4371,13 @@ class FinaticConnect extends EventEmitter {
4165
4371
  brokerModifications.stopPrice = modifications.stopPrice;
4166
4372
  if (modifications.timeInForce)
4167
4373
  brokerModifications.timeInForce = modifications.timeInForce;
4168
- return await this.apiClient.modifyBrokerOrder(orderId, brokerModifications, broker);
4374
+ if (modifications.orderType)
4375
+ brokerModifications.orderType = modifications.orderType;
4376
+ if (modifications.side)
4377
+ brokerModifications.action = modifications.side;
4378
+ if (modifications.order_id)
4379
+ brokerModifications.order_id = modifications.order_id;
4380
+ return await this.apiClient.modifyBrokerOrder(orderId, brokerModifications, broker, {}, connection_id);
4169
4381
  }
4170
4382
  catch (error) {
4171
4383
  this.emit('error', error);
@@ -4207,7 +4419,7 @@ class FinaticConnect extends EventEmitter {
4207
4419
  throw new Error('Not initialized with user');
4208
4420
  }
4209
4421
  try {
4210
- return await this.apiClient.placeStockMarketOrder(this.userToken.accessToken, symbol, quantity, side === 'buy' ? 'Buy' : 'Sell', broker, accountNumber);
4422
+ return await this.apiClient.placeStockMarketOrder(symbol, quantity, side === 'buy' ? 'Buy' : 'Sell', broker, accountNumber);
4211
4423
  }
4212
4424
  catch (error) {
4213
4425
  this.emit('error', error);
@@ -4222,7 +4434,7 @@ class FinaticConnect extends EventEmitter {
4222
4434
  throw new Error('Not initialized with user');
4223
4435
  }
4224
4436
  try {
4225
- return await this.apiClient.placeStockLimitOrder(this.userToken.accessToken, symbol, quantity, side === 'buy' ? 'Buy' : 'Sell', price, timeInForce, broker, accountNumber);
4437
+ return await this.apiClient.placeStockLimitOrder(symbol, quantity, side === 'buy' ? 'Buy' : 'Sell', price, timeInForce, broker, accountNumber);
4226
4438
  }
4227
4439
  catch (error) {
4228
4440
  this.emit('error', error);
@@ -4235,7 +4447,7 @@ class FinaticConnect extends EventEmitter {
4235
4447
  * @throws AuthenticationError if user is not authenticated
4236
4448
  */
4237
4449
  getUserId() {
4238
- if (!this.isAuthenticated()) {
4450
+ if (!this.isAuthed()) {
4239
4451
  return null;
4240
4452
  }
4241
4453
  if (!this.userToken?.user_id) {
@@ -4248,10 +4460,10 @@ class FinaticConnect extends EventEmitter {
4248
4460
  * @returns Promise with array of broker information
4249
4461
  */
4250
4462
  async getBrokerList() {
4251
- if (!this.isAuthenticated()) {
4463
+ if (!this.isAuthed()) {
4252
4464
  throw new AuthenticationError('Not authenticated');
4253
4465
  }
4254
- const response = await this.apiClient.getBrokerListAuto();
4466
+ const response = await this.apiClient.getBrokerList();
4255
4467
  const baseUrl = this.baseUrl.replace('/api/v1', ''); // Remove /api/v1 to get the base URL
4256
4468
  // Transform the broker list to include full logo URLs
4257
4469
  return response.response_data.map((broker) => ({
@@ -4265,13 +4477,13 @@ class FinaticConnect extends EventEmitter {
4265
4477
  * @throws AuthenticationError if user is not authenticated
4266
4478
  */
4267
4479
  async getBrokerConnections() {
4268
- if (!this.isAuthenticated()) {
4480
+ if (!this.isAuthed()) {
4269
4481
  throw new AuthenticationError('User is not authenticated. Please connect a broker first.');
4270
4482
  }
4271
4483
  if (!this.userToken?.user_id) {
4272
4484
  throw new AuthenticationError('No user ID available. Please connect a broker first.');
4273
4485
  }
4274
- const response = await this.apiClient.getBrokerConnectionsAuto();
4486
+ const response = await this.apiClient.getBrokerConnections();
4275
4487
  if (response.status_code !== 200) {
4276
4488
  throw new Error(response.message || 'Failed to retrieve broker connections');
4277
4489
  }
@@ -4347,7 +4559,7 @@ class FinaticConnect extends EventEmitter {
4347
4559
  * @returns Promise with paginated orders result
4348
4560
  */
4349
4561
  async getOrdersPage(page = 1, perPage = 100, filter) {
4350
- if (!this.isAuthenticated()) {
4562
+ if (!this.isAuthed()) {
4351
4563
  throw new AuthenticationError('User is not authenticated');
4352
4564
  }
4353
4565
  return this.apiClient.getBrokerOrdersPage(page, perPage, filter);
@@ -4360,7 +4572,7 @@ class FinaticConnect extends EventEmitter {
4360
4572
  * @returns Promise with paginated positions result
4361
4573
  */
4362
4574
  async getPositionsPage(page = 1, perPage = 100, filter) {
4363
- if (!this.isAuthenticated()) {
4575
+ if (!this.isAuthed()) {
4364
4576
  throw new AuthenticationError('User is not authenticated');
4365
4577
  }
4366
4578
  return this.apiClient.getBrokerPositionsPage(page, perPage, filter);
@@ -4373,7 +4585,7 @@ class FinaticConnect extends EventEmitter {
4373
4585
  * @returns Promise with paginated accounts result
4374
4586
  */
4375
4587
  async getAccountsPage(page = 1, perPage = 100, filter) {
4376
- if (!this.isAuthenticated()) {
4588
+ if (!this.isAuthed()) {
4377
4589
  throw new AuthenticationError('User is not authenticated');
4378
4590
  }
4379
4591
  return this.apiClient.getBrokerAccountsPage(page, perPage, filter);
@@ -4384,7 +4596,7 @@ class FinaticConnect extends EventEmitter {
4384
4596
  * @returns Promise with next page of orders or null if no more pages
4385
4597
  */
4386
4598
  async getNextOrdersPage(previousResult) {
4387
- if (!this.isAuthenticated()) {
4599
+ if (!this.isAuthed()) {
4388
4600
  throw new AuthenticationError('User is not authenticated');
4389
4601
  }
4390
4602
  return this.apiClient.getNextPage(previousResult, (offset, limit) => {
@@ -4398,7 +4610,7 @@ class FinaticConnect extends EventEmitter {
4398
4610
  * @returns Promise with next page of positions or null if no more pages
4399
4611
  */
4400
4612
  async getNextPositionsPage(previousResult) {
4401
- if (!this.isAuthenticated()) {
4613
+ if (!this.isAuthed()) {
4402
4614
  throw new AuthenticationError('User is not authenticated');
4403
4615
  }
4404
4616
  return this.apiClient.getNextPage(previousResult, (offset, limit) => {
@@ -4412,7 +4624,7 @@ class FinaticConnect extends EventEmitter {
4412
4624
  * @returns Promise with next page of accounts or null if no more pages
4413
4625
  */
4414
4626
  async getNextAccountsPage(previousResult) {
4415
- if (!this.isAuthenticated()) {
4627
+ if (!this.isAuthed()) {
4416
4628
  throw new AuthenticationError('User is not authenticated');
4417
4629
  }
4418
4630
  return this.apiClient.getNextPage(previousResult, (offset, limit) => {
@@ -4426,7 +4638,7 @@ class FinaticConnect extends EventEmitter {
4426
4638
  * @returns Promise with all orders
4427
4639
  */
4428
4640
  async getAllOrders(filter) {
4429
- if (!this.isAuthenticated()) {
4641
+ if (!this.isAuthed()) {
4430
4642
  throw new AuthenticationError('User is not authenticated');
4431
4643
  }
4432
4644
  const allData = [];
@@ -4448,7 +4660,7 @@ class FinaticConnect extends EventEmitter {
4448
4660
  * @returns Promise with all positions
4449
4661
  */
4450
4662
  async getAllPositions(filter) {
4451
- if (!this.isAuthenticated()) {
4663
+ if (!this.isAuthed()) {
4452
4664
  throw new AuthenticationError('User is not authenticated');
4453
4665
  }
4454
4666
  const allData = [];
@@ -4470,7 +4682,7 @@ class FinaticConnect extends EventEmitter {
4470
4682
  * @returns Promise with all accounts
4471
4683
  */
4472
4684
  async getAllAccounts(filter) {
4473
- if (!this.isAuthenticated()) {
4685
+ if (!this.isAuthed()) {
4474
4686
  throw new AuthenticationError('User is not authenticated');
4475
4687
  }
4476
4688
  const allData = [];
@@ -4487,28 +4699,144 @@ class FinaticConnect extends EventEmitter {
4487
4699
  return allData;
4488
4700
  }
4489
4701
  /**
4490
- * Register automatic session cleanup on page unload/visibility change
4702
+ * Register session management (but don't auto-cleanup for 24-hour sessions)
4491
4703
  */
4492
4704
  registerSessionCleanup() {
4493
- // Cleanup on page unload (refresh, close tab, navigate away)
4705
+ // Only cleanup on actual page unload (not visibility changes)
4706
+ // This prevents sessions from being closed when users switch tabs or apps
4494
4707
  window.addEventListener('beforeunload', this.handleSessionCleanup.bind(this));
4495
- // Cleanup on page visibility change (mobile browsers, app switching)
4708
+ // Handle visibility changes for keep-alive management (but don't complete sessions)
4496
4709
  document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
4710
+ // Start session keep-alive mechanism
4711
+ this.startSessionKeepAlive();
4712
+ }
4713
+ /**
4714
+ * Start the session keep-alive mechanism
4715
+ */
4716
+ startSessionKeepAlive() {
4717
+ if (this.sessionKeepAliveInterval) {
4718
+ clearInterval(this.sessionKeepAliveInterval);
4719
+ }
4720
+ this.sessionKeepAliveInterval = setInterval(() => {
4721
+ this.validateSessionKeepAlive();
4722
+ }, this.SESSION_KEEP_ALIVE_INTERVAL);
4723
+ console.log('[FinaticConnect] Session keep-alive started (5-minute intervals)');
4724
+ }
4725
+ /**
4726
+ * Stop the session keep-alive mechanism
4727
+ */
4728
+ stopSessionKeepAlive() {
4729
+ if (this.sessionKeepAliveInterval) {
4730
+ clearInterval(this.sessionKeepAliveInterval);
4731
+ this.sessionKeepAliveInterval = null;
4732
+ console.log('[FinaticConnect] Session keep-alive stopped');
4733
+ }
4734
+ }
4735
+ /**
4736
+ * Validate session for keep-alive purposes and handle automatic refresh
4737
+ */
4738
+ async validateSessionKeepAlive() {
4739
+ if (!this.sessionId || !this.isAuthed()) {
4740
+ console.log('[FinaticConnect] Session keep-alive skipped - no active session');
4741
+ return;
4742
+ }
4743
+ try {
4744
+ console.log('[FinaticConnect] Validating session for keep-alive...');
4745
+ // Check if we need to refresh the session (at 16 hours)
4746
+ if (this.shouldRefreshSession()) {
4747
+ await this.refreshSessionAutomatically();
4748
+ return;
4749
+ }
4750
+ // Use a timeout to prevent hanging requests
4751
+ const timeoutPromise = new Promise((_, reject) => {
4752
+ setTimeout(() => reject(new Error('Session validation timeout')), this.SESSION_VALIDATION_TIMEOUT);
4753
+ });
4754
+ const validationPromise = this.apiClient.validatePortalSession(this.sessionId, '');
4755
+ const response = await Promise.race([validationPromise, timeoutPromise]);
4756
+ if (response && response.status === 'active') {
4757
+ console.log('[FinaticConnect] Session keep-alive successful');
4758
+ this.currentSessionState = 'active';
4759
+ }
4760
+ else {
4761
+ console.warn('[FinaticConnect] Session keep-alive failed - session not active');
4762
+ this.currentSessionState = response?.status || 'unknown';
4763
+ }
4764
+ }
4765
+ catch (error) {
4766
+ console.warn('[FinaticConnect] Session keep-alive error:', error);
4767
+ // Don't throw errors during keep-alive - just log them
4768
+ }
4769
+ }
4770
+ /**
4771
+ * Check if the session should be refreshed (after 16 hours)
4772
+ */
4773
+ shouldRefreshSession() {
4774
+ if (!this.sessionStartTime) {
4775
+ return false;
4776
+ }
4777
+ const sessionAgeHours = (Date.now() - this.sessionStartTime) / (1000 * 60 * 60);
4778
+ const hoursUntilRefresh = this.SESSION_REFRESH_BUFFER_HOURS - sessionAgeHours;
4779
+ if (hoursUntilRefresh <= 0) {
4780
+ console.log(`[FinaticConnect] Session is ${sessionAgeHours.toFixed(1)} hours old - triggering refresh`);
4781
+ return true;
4782
+ }
4783
+ // Log when refresh will occur (every 5 minutes during keep-alive)
4784
+ if (hoursUntilRefresh <= 1) {
4785
+ console.log(`[FinaticConnect] Session will refresh in ${hoursUntilRefresh.toFixed(1)} hours`);
4786
+ }
4787
+ return false;
4788
+ }
4789
+ /**
4790
+ * Automatically refresh the session to extend its lifetime
4791
+ */
4792
+ async refreshSessionAutomatically() {
4793
+ if (!this.sessionId) {
4794
+ console.warn('[FinaticConnect] Cannot refresh session - no session ID');
4795
+ return;
4796
+ }
4797
+ try {
4798
+ console.log('[FinaticConnect] Automatically refreshing session (16+ hours old)...');
4799
+ const response = await this.apiClient.refreshSession();
4800
+ if (response.success) {
4801
+ console.log('[FinaticConnect] Session automatically refreshed successfully');
4802
+ console.log('[FinaticConnect] New session expires at:', response.response_data.expires_at);
4803
+ this.currentSessionState = response.response_data.status;
4804
+ // Update session start time to prevent immediate re-refresh
4805
+ this.sessionStartTime = Date.now();
4806
+ }
4807
+ else {
4808
+ console.warn('[FinaticConnect] Automatic session refresh failed');
4809
+ }
4810
+ }
4811
+ catch (error) {
4812
+ console.warn('[FinaticConnect] Automatic session refresh error:', error);
4813
+ // Don't throw errors during automatic refresh - just log them
4814
+ }
4497
4815
  }
4498
4816
  /**
4499
4817
  * Handle session cleanup when page is unloading
4500
4818
  */
4501
4819
  async handleSessionCleanup() {
4820
+ this.stopSessionKeepAlive();
4502
4821
  if (this.sessionId) {
4503
4822
  await this.completeSession(this.sessionId);
4504
4823
  }
4505
4824
  }
4506
4825
  /**
4507
4826
  * Handle visibility change (mobile browsers)
4827
+ * Note: We don't complete sessions on visibility change for 24-hour sessions
4508
4828
  */
4509
4829
  async handleVisibilityChange() {
4510
- if (document.visibilityState === 'hidden' && this.sessionId) {
4511
- await this.completeSession(this.sessionId);
4830
+ // For 24-hour sessions, we don't want to complete sessions on visibility changes
4831
+ // This prevents sessions from being closed when users switch tabs or apps
4832
+ console.log('[FinaticConnect] Page visibility changed to:', document.visibilityState);
4833
+ // Only pause keep-alive when hidden, but don't complete the session
4834
+ if (document.visibilityState === 'hidden') {
4835
+ this.stopSessionKeepAlive();
4836
+ }
4837
+ else if (document.visibilityState === 'visible') {
4838
+ // Restart keep-alive when page becomes visible again
4839
+ this.startSessionKeepAlive();
4512
4840
  }
4513
4841
  }
4514
4842
  /**
@@ -4546,242 +4874,23 @@ class FinaticConnect extends EventEmitter {
4546
4874
  console.warn('[FinaticConnect] Session cleanup failed:', error);
4547
4875
  }
4548
4876
  }
4549
- }
4550
- FinaticConnect.instance = null;
4551
-
4552
- class CoreTradingService {
4553
- constructor(apiClient) {
4554
- this.apiClient = apiClient;
4555
- }
4556
- async getAccounts(filter) {
4557
- return this.getAllAccounts(filter);
4558
- }
4559
- async getOrders(filter) {
4560
- return this.getAllOrders(filter);
4561
- }
4562
- async getPositions(filter) {
4563
- return this.getAllPositions(filter);
4564
- }
4565
- async placeOrder(order) {
4566
- const response = await this.apiClient.placeOrder(order);
4567
- return response.data.orderId;
4568
- }
4569
- async cancelOrder(orderId) {
4570
- const response = await this.apiClient.cancelOrder(orderId);
4571
- return response.data.success;
4572
- }
4573
- async placeOptionsOrder(order) {
4574
- const response = await this.apiClient.placeOptionsOrder(order);
4575
- return response.data.orderId;
4576
- }
4577
- // Pagination methods
4578
- async getAccountsPage(page = 1, perPage = 100, filter) {
4579
- return this.apiClient.getBrokerAccountsPage(page, perPage, filter);
4580
- }
4581
- async getOrdersPage(page = 1, perPage = 100, filter) {
4582
- return this.apiClient.getBrokerOrdersPage(page, perPage, filter);
4583
- }
4584
- async getPositionsPage(page = 1, perPage = 100, filter) {
4585
- return this.apiClient.getBrokerPositionsPage(page, perPage, filter);
4586
- }
4587
- // Navigation methods
4588
- async getNextAccountsPage(previousResult) {
4589
- return this.apiClient.getNextPage(previousResult, (offset, limit) => this.apiClient.getBrokerAccountsPage(1, limit, { offset }));
4590
- }
4591
- async getNextOrdersPage(previousResult) {
4592
- return this.apiClient.getNextPage(previousResult, (offset, limit) => this.apiClient.getBrokerOrdersPage(1, limit, { offset }));
4593
- }
4594
- async getNextPositionsPage(previousResult) {
4595
- return this.apiClient.getNextPage(previousResult, (offset, limit) => this.apiClient.getBrokerPositionsPage(1, limit, { offset }));
4596
- }
4597
- // Get all pages convenience methods
4598
- async getAllAccounts(filter) {
4599
- const allData = [];
4600
- let currentResult = await this.getAccountsPage(1, 100, filter);
4601
- while (currentResult) {
4602
- allData.push(...currentResult.data);
4603
- if (!currentResult.hasNext)
4604
- break;
4605
- const nextResult = await this.getNextAccountsPage(currentResult);
4606
- if (!nextResult)
4607
- break;
4608
- currentResult = nextResult;
4609
- }
4610
- return allData;
4611
- }
4612
- async getAllOrders(filter) {
4613
- const allData = [];
4614
- let currentResult = await this.getOrdersPage(1, 100, filter);
4615
- while (currentResult) {
4616
- allData.push(...currentResult.data);
4617
- if (!currentResult.hasNext)
4618
- break;
4619
- const nextResult = await this.getNextOrdersPage(currentResult);
4620
- if (!nextResult)
4621
- break;
4622
- currentResult = nextResult;
4623
- }
4624
- return allData;
4625
- }
4626
- async getAllPositions(filter) {
4627
- const allData = [];
4628
- let currentResult = await this.getPositionsPage(1, 100, filter);
4629
- while (currentResult) {
4630
- allData.push(...currentResult.data);
4631
- if (!currentResult.hasNext)
4632
- break;
4633
- const nextResult = await this.getNextPositionsPage(currentResult);
4634
- if (!nextResult)
4635
- break;
4636
- currentResult = nextResult;
4637
- }
4638
- return allData;
4639
- }
4640
- // Abstract convenience methods
4641
- async getOpenPositions() {
4642
- return this.getPositions({ position_status: 'open' });
4643
- }
4644
- async getFilledOrders() {
4645
- return this.getOrders({ status: 'filled' });
4646
- }
4647
- async getPendingOrders() {
4648
- return this.getOrders({ status: 'pending' });
4649
- }
4650
- async getActiveAccounts() {
4651
- return this.getAccounts({ status: 'active' });
4652
- }
4653
- async getOrdersBySymbol(symbol) {
4654
- return this.getOrders({ symbol });
4655
- }
4656
- async getPositionsBySymbol(symbol) {
4657
- return this.getPositions({ symbol });
4658
- }
4659
- async getOrdersByBroker(brokerId) {
4660
- return this.getOrders({ broker_id: brokerId });
4661
- }
4662
- async getPositionsByBroker(brokerId) {
4663
- return this.getPositions({ broker_id: brokerId });
4664
- }
4665
- }
4666
-
4667
- class CoreAnalyticsService {
4668
- constructor(apiClient) {
4669
- this.apiClient = apiClient;
4670
- }
4671
- async getPerformance() {
4672
- const response = await this.apiClient.getPerformance();
4673
- return response.data.performance;
4674
- }
4675
- async getDailyHistory() {
4676
- const response = await this.apiClient.getDailyHistory();
4677
- return response.data.history;
4678
- }
4679
- async getWeeklySnapshots() {
4680
- const response = await this.apiClient.getWeeklySnapshots();
4681
- return response.data.snapshots;
4682
- }
4683
- async getPortfolioDeltas() {
4684
- const response = await this.apiClient.getPortfolioDeltas();
4685
- return response.data.deltas;
4686
- }
4687
- async getUserLogs(userId) {
4688
- const response = await this.apiClient.getUserLogs(userId);
4689
- return response.data.logs;
4690
- }
4691
- }
4692
-
4693
- class CorePortalService {
4694
- constructor(apiClient) {
4695
- this.apiClient = apiClient;
4696
- this.iframe = null;
4697
- }
4698
- async createPortal(config) {
4699
- // Create iframe
4700
- this.iframe = document.createElement('iframe');
4701
- this.iframe.style.display = 'none';
4702
- document.body.appendChild(this.iframe);
4703
- // Get portal content from API
4704
- const response = await this.apiClient.getPortalContent();
4705
- const content = response.data.content;
4706
- // Set iframe content
4707
- if (this.iframe.contentWindow) {
4708
- this.iframe.contentWindow.document.open();
4709
- this.iframe.contentWindow.document.write(content);
4710
- this.iframe.contentWindow.document.close();
4711
- }
4712
- // Position and style iframe
4713
- this.positionIframe(config);
4714
- this.styleIframe(config);
4715
- // Show iframe
4716
- this.iframe.style.display = 'block';
4717
- }
4718
- async closePortal() {
4719
- if (this.iframe) {
4720
- this.iframe.remove();
4721
- this.iframe = null;
4722
- }
4723
- }
4724
- async updateTheme(theme) {
4725
- if (this.iframe?.contentWindow) {
4726
- this.iframe.contentWindow.postMessage({ type: 'update_theme', theme }, '*');
4727
- }
4728
- }
4729
- async getBrokerAccounts() {
4730
- const response = await this.apiClient.getBrokerAccounts();
4731
- return response.data.accounts;
4732
- }
4733
- async linkBrokerAccount(brokerId) {
4734
- await this.apiClient.connectBroker(brokerId);
4735
- }
4736
- async unlinkBrokerAccount(brokerId) {
4737
- await this.apiClient.disconnectBroker(brokerId);
4738
- }
4739
- positionIframe(config) {
4740
- if (!this.iframe)
4741
- return;
4742
- const position = config.position || 'center';
4743
- const width = config.width || '600px';
4744
- const height = config.height || '800px';
4745
- this.iframe.style.width = width;
4746
- this.iframe.style.height = height;
4747
- switch (position) {
4748
- case 'center':
4749
- this.iframe.style.position = 'fixed';
4750
- this.iframe.style.top = '50%';
4751
- this.iframe.style.left = '50%';
4752
- this.iframe.style.transform = 'translate(-50%, -50%)';
4753
- break;
4754
- case 'top':
4755
- this.iframe.style.position = 'fixed';
4756
- this.iframe.style.top = '0';
4757
- this.iframe.style.left = '50%';
4758
- this.iframe.style.transform = 'translateX(-50%)';
4759
- break;
4760
- case 'bottom':
4761
- this.iframe.style.position = 'fixed';
4762
- this.iframe.style.bottom = '0';
4763
- this.iframe.style.left = '50%';
4764
- this.iframe.style.transform = 'translateX(-50%)';
4765
- break;
4766
- // Note: 'left' and 'right' positions removed from PortalConfig interface
4767
- // Default to center if invalid position is provided
4768
- default:
4769
- this.iframe.style.position = 'fixed';
4770
- this.iframe.style.top = '50%';
4771
- this.iframe.style.left = '50%';
4772
- this.iframe.style.transform = 'translate(-50%, -50%)';
4773
- break;
4877
+ /**
4878
+ * Disconnect a company from a broker connection
4879
+ * @param connectionId - The connection ID to disconnect
4880
+ * @returns Promise with disconnect response
4881
+ * @throws AuthenticationError if user is not authenticated
4882
+ */
4883
+ async disconnectCompany(connectionId) {
4884
+ if (!this.isAuthed()) {
4885
+ throw new AuthenticationError('User is not authenticated. Please connect a broker first.');
4774
4886
  }
4775
- }
4776
- styleIframe(config) {
4777
- if (!this.iframe)
4778
- return;
4779
- // Apply z-index if specified
4780
- if (config.zIndex) {
4781
- this.iframe.style.zIndex = config.zIndex.toString();
4887
+ if (!this.userToken?.user_id) {
4888
+ throw new AuthenticationError('No user ID available. Please connect a broker first.');
4782
4889
  }
4890
+ return this.apiClient.disconnectCompany(connectionId);
4783
4891
  }
4784
4892
  }
4893
+ FinaticConnect.instance = null;
4785
4894
 
4786
- export { ApiClient, ApiError, AuthenticationError, AuthorizationError, BaseError, CompanyAccessError, CoreAnalyticsService, CorePortalService, CoreTradingService, EventEmitter, FinaticConnect, MockFactory, NetworkError, OrderError, OrderValidationError, PaginatedResult, RateLimitError, SecurityError, SessionError, TokenError, ValidationError, appendThemeToURL, createCustomThemeFromPreset, generatePortalThemeURL, getThemePreset, portalThemePresets, validateCustomTheme };
4895
+ export { ApiClient, ApiError, AuthenticationError, AuthorizationError, BaseError, CompanyAccessError, EventEmitter, FinaticConnect, MockFactory, NetworkError, OrderError, OrderValidationError, PaginatedResult, RateLimitError, SecurityError, SessionError, TokenError, ValidationError, appendThemeToURL, createCustomThemeFromPreset, generatePortalThemeURL, getThemePreset, portalThemePresets, validateCustomTheme };
4787
4896
  //# sourceMappingURL=index.mjs.map