@finatic/client 0.0.133 → 0.0.135

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