hedgequantx 2.6.163 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +15 -88
  2. package/bin/cli.js +0 -11
  3. package/dist/lib/api.jsc +0 -0
  4. package/dist/lib/api2.jsc +0 -0
  5. package/dist/lib/core.jsc +0 -0
  6. package/dist/lib/core2.jsc +0 -0
  7. package/dist/lib/data.js +1 -1
  8. package/dist/lib/data.jsc +0 -0
  9. package/dist/lib/data2.jsc +0 -0
  10. package/dist/lib/decoder.jsc +0 -0
  11. package/dist/lib/m/mod1.jsc +0 -0
  12. package/dist/lib/m/mod2.jsc +0 -0
  13. package/dist/lib/n/r1.jsc +0 -0
  14. package/dist/lib/n/r2.jsc +0 -0
  15. package/dist/lib/n/r3.jsc +0 -0
  16. package/dist/lib/n/r4.jsc +0 -0
  17. package/dist/lib/n/r5.jsc +0 -0
  18. package/dist/lib/n/r6.jsc +0 -0
  19. package/dist/lib/n/r7.jsc +0 -0
  20. package/dist/lib/o/util1.jsc +0 -0
  21. package/dist/lib/o/util2.jsc +0 -0
  22. package/package.json +6 -3
  23. package/src/app.js +40 -162
  24. package/src/config/constants.js +31 -33
  25. package/src/config/propfirms.js +13 -217
  26. package/src/config/settings.js +0 -43
  27. package/src/lib/api.js +198 -0
  28. package/src/lib/api2.js +353 -0
  29. package/src/lib/core.js +539 -0
  30. package/src/lib/core2.js +341 -0
  31. package/src/lib/data.js +555 -0
  32. package/src/lib/data2.js +492 -0
  33. package/src/lib/decoder.js +599 -0
  34. package/src/lib/m/s1.js +804 -0
  35. package/src/lib/m/s2.js +34 -0
  36. package/src/lib/n/r1.js +454 -0
  37. package/src/lib/n/r2.js +514 -0
  38. package/src/lib/n/r3.js +631 -0
  39. package/src/lib/n/r4.js +401 -0
  40. package/src/lib/n/r5.js +335 -0
  41. package/src/lib/n/r6.js +425 -0
  42. package/src/lib/n/r7.js +530 -0
  43. package/src/lib/o/l1.js +44 -0
  44. package/src/lib/o/l2.js +427 -0
  45. package/src/lib/python-bridge.js +206 -0
  46. package/src/menus/connect.js +14 -176
  47. package/src/menus/dashboard.js +65 -110
  48. package/src/pages/accounts.js +18 -18
  49. package/src/pages/algo/copy-trading.js +210 -240
  50. package/src/pages/algo/index.js +41 -104
  51. package/src/pages/algo/one-account.js +386 -33
  52. package/src/pages/algo/ui.js +312 -151
  53. package/src/pages/orders.js +3 -3
  54. package/src/pages/positions.js +3 -3
  55. package/src/pages/stats/chart.js +74 -0
  56. package/src/pages/stats/display.js +228 -0
  57. package/src/pages/stats/index.js +236 -0
  58. package/src/pages/stats/metrics.js +213 -0
  59. package/src/pages/user.js +6 -6
  60. package/src/services/hqx-server/constants.js +55 -0
  61. package/src/services/hqx-server/index.js +401 -0
  62. package/src/services/hqx-server/latency.js +81 -0
  63. package/src/services/index.js +12 -3
  64. package/src/services/rithmic/accounts.js +7 -32
  65. package/src/services/rithmic/connection.js +1 -204
  66. package/src/services/rithmic/contracts.js +116 -99
  67. package/src/services/rithmic/handlers.js +21 -196
  68. package/src/services/rithmic/index.js +63 -120
  69. package/src/services/rithmic/market.js +31 -0
  70. package/src/services/rithmic/orders.js +5 -111
  71. package/src/services/rithmic/protobuf.js +384 -138
  72. package/src/services/session.js +22 -173
  73. package/src/ui/box.js +10 -18
  74. package/src/ui/index.js +1 -3
  75. package/src/ui/menu.js +1 -1
  76. package/src/utils/prompts.js +2 -2
  77. package/dist/lib/m/s1.js +0 -1
  78. package/src/menus/ai-agent-connect.js +0 -181
  79. package/src/menus/ai-agent-models.js +0 -219
  80. package/src/menus/ai-agent-oauth.js +0 -292
  81. package/src/menus/ai-agent-ui.js +0 -141
  82. package/src/menus/ai-agent.js +0 -484
  83. package/src/pages/algo/algo-config.js +0 -195
  84. package/src/pages/algo/algo-multi.js +0 -801
  85. package/src/pages/algo/algo-utils.js +0 -58
  86. package/src/pages/algo/copy-engine.js +0 -449
  87. package/src/pages/algo/custom-strategy.js +0 -459
  88. package/src/pages/algo/logger.js +0 -245
  89. package/src/pages/algo/smart-logs-data.js +0 -218
  90. package/src/pages/algo/smart-logs.js +0 -387
  91. package/src/pages/algo/ui-constants.js +0 -144
  92. package/src/pages/algo/ui-summary.js +0 -184
  93. package/src/pages/stats-calculations.js +0 -191
  94. package/src/pages/stats-ui.js +0 -381
  95. package/src/pages/stats.js +0 -339
  96. package/src/services/ai/client-analysis.js +0 -194
  97. package/src/services/ai/client-models.js +0 -333
  98. package/src/services/ai/client.js +0 -343
  99. package/src/services/ai/index.js +0 -384
  100. package/src/services/ai/oauth-anthropic.js +0 -265
  101. package/src/services/ai/oauth-gemini.js +0 -223
  102. package/src/services/ai/oauth-iflow.js +0 -269
  103. package/src/services/ai/oauth-openai.js +0 -233
  104. package/src/services/ai/oauth-qwen.js +0 -279
  105. package/src/services/ai/providers/direct-providers.js +0 -323
  106. package/src/services/ai/providers/index.js +0 -62
  107. package/src/services/ai/providers/other-providers.js +0 -104
  108. package/src/services/ai/proxy-install.js +0 -249
  109. package/src/services/ai/proxy-manager.js +0 -494
  110. package/src/services/ai/proxy-remote.js +0 -161
  111. package/src/services/ai/strategy-supervisor.js +0 -1312
  112. package/src/services/ai/supervisor-data.js +0 -195
  113. package/src/services/ai/supervisor-optimize.js +0 -215
  114. package/src/services/ai/supervisor-sync.js +0 -178
  115. package/src/services/ai/supervisor-utils.js +0 -158
  116. package/src/services/ai/supervisor.js +0 -484
  117. package/src/services/ai/validation.js +0 -250
  118. package/src/services/hqx-server-events.js +0 -110
  119. package/src/services/hqx-server-handlers.js +0 -217
  120. package/src/services/hqx-server-latency.js +0 -136
  121. package/src/services/hqx-server.js +0 -403
  122. package/src/services/position-constants.js +0 -28
  123. package/src/services/position-exit-logic.js +0 -174
  124. package/src/services/position-manager.js +0 -438
  125. package/src/services/position-momentum.js +0 -206
  126. package/src/services/projectx/accounts.js +0 -142
  127. package/src/services/projectx/index.js +0 -443
  128. package/src/services/projectx/market.js +0 -172
  129. package/src/services/projectx/stats.js +0 -110
  130. package/src/services/projectx/trading.js +0 -180
  131. package/src/services/rithmic/latency-tracker.js +0 -182
  132. package/src/services/rithmic/market-data-decoders.js +0 -229
  133. package/src/services/rithmic/market-data.js +0 -272
  134. package/src/services/rithmic/orders-fast.js +0 -246
  135. package/src/services/rithmic/proto-decoders.js +0 -403
  136. package/src/services/rithmic/specs.js +0 -146
  137. package/src/services/rithmic/trade-history.js +0 -254
  138. package/src/services/session-history.js +0 -475
  139. package/src/services/strategy/hft-signal-calc.js +0 -147
  140. package/src/services/strategy/hft-tick.js +0 -407
  141. package/src/services/strategy/recovery-math.js +0 -402
  142. package/src/services/tradovate/constants.js +0 -109
  143. package/src/services/tradovate/index.js +0 -392
  144. package/src/services/tradovate/market.js +0 -47
  145. package/src/services/tradovate/orders.js +0 -145
  146. package/src/services/tradovate/websocket.js +0 -97
@@ -1,223 +0,0 @@
1
- /**
2
- * Google Gemini OAuth Authentication
3
- *
4
- * Implements OAuth 2.0 for Google Gemini Advanced subscription.
5
- * Based on the public OAuth flow used by Gemini CLI.
6
- *
7
- * Data source: Google OAuth API
8
- */
9
-
10
- const crypto = require('crypto');
11
- const https = require('https');
12
-
13
- // Public OAuth Client ID and Secret (from Gemini CLI - public credentials)
14
- // Split and reversed to avoid GitHub secret scanning false positives
15
- const _gc = ['rcontent.com', '.apps.googleuse', 'qf6av3hmdib135j', '8ft2oprdrnp9e3a', '68125580939' + '5-oo'];
16
- const CLIENT_ID = _gc.reverse().join('');
17
- const _gs = ['XFsxl', '-geV6Cu5cl', 'gMPm-1o7Sk', 'GOCSPX-4u' + 'H'];
18
- const CLIENT_SECRET = _gs.reverse().join('');
19
- const REDIRECT_URI = 'http://localhost:8085/oauth2callback';
20
- const AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
21
- const TOKEN_URL = 'https://oauth2.googleapis.com/token';
22
- const SCOPES = [
23
- 'https://www.googleapis.com/auth/cloud-platform',
24
- 'https://www.googleapis.com/auth/userinfo.email',
25
- 'https://www.googleapis.com/auth/userinfo.profile'
26
- ];
27
-
28
- /**
29
- * Generate state token
30
- * @returns {string}
31
- */
32
- const generateState = () => {
33
- return crypto.randomBytes(16).toString('hex');
34
- };
35
-
36
- /**
37
- * Generate OAuth authorization URL
38
- * @returns {Object} { url: string, state: string }
39
- */
40
- const authorize = () => {
41
- const state = generateState();
42
-
43
- const url = new URL(AUTH_URL);
44
- url.searchParams.set('client_id', CLIENT_ID);
45
- url.searchParams.set('response_type', 'code');
46
- url.searchParams.set('redirect_uri', REDIRECT_URI);
47
- url.searchParams.set('scope', SCOPES.join(' '));
48
- url.searchParams.set('state', state);
49
- url.searchParams.set('access_type', 'offline');
50
- url.searchParams.set('prompt', 'consent');
51
-
52
- return {
53
- url: url.toString(),
54
- state
55
- };
56
- };
57
-
58
- /**
59
- * Make HTTPS request
60
- */
61
- const makeRequest = (urlStr, options) => {
62
- return new Promise((resolve, reject) => {
63
- const url = new URL(urlStr);
64
- const req = https.request({
65
- hostname: url.hostname,
66
- port: url.port || 443,
67
- path: url.pathname + url.search,
68
- method: options.method || 'POST',
69
- headers: options.headers || {}
70
- }, (res) => {
71
- let data = '';
72
- res.on('data', chunk => data += chunk);
73
- res.on('end', () => {
74
- try {
75
- const json = JSON.parse(data);
76
- if (res.statusCode >= 200 && res.statusCode < 300) {
77
- resolve(json);
78
- } else {
79
- reject(new Error(json.error_description || json.error || `HTTP ${res.statusCode}`));
80
- }
81
- } catch (e) {
82
- reject(new Error(`Invalid JSON response: ${data.substring(0, 200)}`));
83
- }
84
- });
85
- });
86
-
87
- req.on('error', reject);
88
-
89
- if (options.body) {
90
- req.write(options.body);
91
- }
92
- req.end();
93
- });
94
- };
95
-
96
- /**
97
- * Exchange authorization code for tokens
98
- * @param {string} code - Authorization code from callback
99
- * @returns {Promise<Object>}
100
- */
101
- const exchange = async (code) => {
102
- try {
103
- const body = new URLSearchParams({
104
- grant_type: 'authorization_code',
105
- client_id: CLIENT_ID,
106
- client_secret: CLIENT_SECRET,
107
- code: code,
108
- redirect_uri: REDIRECT_URI
109
- }).toString();
110
-
111
- const response = await makeRequest(TOKEN_URL, {
112
- method: 'POST',
113
- headers: {
114
- 'Content-Type': 'application/x-www-form-urlencoded',
115
- 'Accept': 'application/json'
116
- },
117
- body
118
- });
119
-
120
- return {
121
- type: 'success',
122
- access: response.access_token,
123
- refresh: response.refresh_token,
124
- idToken: response.id_token,
125
- expires: Date.now() + (response.expires_in * 1000),
126
- scope: response.scope
127
- };
128
- } catch (error) {
129
- return {
130
- type: 'failed',
131
- error: error.message
132
- };
133
- }
134
- };
135
-
136
- /**
137
- * Refresh access token using refresh token
138
- * @param {string} refreshTokenValue - The refresh token
139
- * @returns {Promise<Object>}
140
- */
141
- const refreshToken = async (refreshTokenValue) => {
142
- try {
143
- const body = new URLSearchParams({
144
- client_id: CLIENT_ID,
145
- client_secret: CLIENT_SECRET,
146
- grant_type: 'refresh_token',
147
- refresh_token: refreshTokenValue
148
- }).toString();
149
-
150
- const response = await makeRequest(TOKEN_URL, {
151
- method: 'POST',
152
- headers: {
153
- 'Content-Type': 'application/x-www-form-urlencoded',
154
- 'Accept': 'application/json'
155
- },
156
- body
157
- });
158
-
159
- return {
160
- type: 'success',
161
- access: response.access_token,
162
- refresh: refreshTokenValue, // Google doesn't always return new refresh token
163
- expires: Date.now() + (response.expires_in * 1000)
164
- };
165
- } catch (error) {
166
- return {
167
- type: 'failed',
168
- error: error.message
169
- };
170
- }
171
- };
172
-
173
- /**
174
- * Get valid access token (refresh if expired)
175
- * @param {Object} oauthData - OAuth data { access, refresh, expires }
176
- * @returns {Promise<Object>}
177
- */
178
- const getValidToken = async (oauthData) => {
179
- if (!oauthData || !oauthData.refresh) {
180
- return null;
181
- }
182
-
183
- const expirationBuffer = 5 * 60 * 1000; // 5 minutes
184
- if (oauthData.expires && oauthData.expires > Date.now() + expirationBuffer) {
185
- return {
186
- ...oauthData,
187
- refreshed: false
188
- };
189
- }
190
-
191
- const result = await refreshToken(oauthData.refresh);
192
- if (result.type === 'success') {
193
- return {
194
- access: result.access,
195
- refresh: result.refresh,
196
- expires: result.expires,
197
- refreshed: true
198
- };
199
- }
200
-
201
- return null;
202
- };
203
-
204
- /**
205
- * Check if credentials are OAuth tokens
206
- */
207
- const isOAuthCredentials = (credentials) => {
208
- return credentials && credentials.oauth && credentials.oauth.refresh;
209
- };
210
-
211
- module.exports = {
212
- CLIENT_ID,
213
- CLIENT_SECRET,
214
- REDIRECT_URI,
215
- CALLBACK_PORT: 8085,
216
- SCOPES,
217
- generateState,
218
- authorize,
219
- exchange,
220
- refreshToken,
221
- getValidToken,
222
- isOAuthCredentials
223
- };
@@ -1,269 +0,0 @@
1
- /**
2
- * iFlow OAuth Authentication
3
- *
4
- * Implements OAuth 2.0 for iFlow subscription.
5
- * Based on the public OAuth flow used by iFlow CLI.
6
- *
7
- * Data source: iFlow OAuth API (https://iflow.cn/oauth)
8
- */
9
-
10
- const crypto = require('crypto');
11
- const https = require('https');
12
-
13
- // Public OAuth Client ID and Secret (from iFlow CLI)
14
- const CLIENT_ID = '10009311001';
15
- const CLIENT_SECRET = '4Z3YjXycVsQvyGF1etiNlIBB4RsqSDtW';
16
- const AUTH_URL = 'https://iflow.cn/oauth';
17
- const TOKEN_URL = 'https://iflow.cn/oauth/token';
18
- const USER_INFO_URL = 'https://iflow.cn/api/oauth/getUserInfo';
19
- const CALLBACK_PORT = 11451;
20
-
21
- /**
22
- * Generate state token
23
- * @returns {string}
24
- */
25
- const generateState = () => {
26
- return crypto.randomBytes(16).toString('hex');
27
- };
28
-
29
- /**
30
- * Generate OAuth authorization URL
31
- * @param {number} port - Callback port (default 11451)
32
- * @returns {Object} { url: string, state: string, redirectUri: string }
33
- */
34
- const authorize = (port = CALLBACK_PORT) => {
35
- const state = generateState();
36
- const redirectUri = `http://localhost:${port}/oauth2callback`;
37
-
38
- const url = new URL(AUTH_URL);
39
- url.searchParams.set('loginMethod', 'phone');
40
- url.searchParams.set('type', 'phone');
41
- url.searchParams.set('redirect', redirectUri);
42
- url.searchParams.set('state', state);
43
- url.searchParams.set('client_id', CLIENT_ID);
44
-
45
- return {
46
- url: url.toString(),
47
- state,
48
- redirectUri
49
- };
50
- };
51
-
52
- /**
53
- * Make HTTPS request
54
- */
55
- const makeRequest = (urlStr, options) => {
56
- return new Promise((resolve, reject) => {
57
- const url = new URL(urlStr);
58
- const req = https.request({
59
- hostname: url.hostname,
60
- port: url.port || 443,
61
- path: url.pathname + url.search,
62
- method: options.method || 'POST',
63
- headers: options.headers || {}
64
- }, (res) => {
65
- let data = '';
66
- res.on('data', chunk => data += chunk);
67
- res.on('end', () => {
68
- try {
69
- const json = JSON.parse(data);
70
- if (res.statusCode >= 200 && res.statusCode < 300) {
71
- resolve(json);
72
- } else {
73
- reject(new Error(json.error_description || json.error || json.message || `HTTP ${res.statusCode}`));
74
- }
75
- } catch (e) {
76
- reject(new Error(`Invalid JSON response: ${data.substring(0, 200)}`));
77
- }
78
- });
79
- });
80
-
81
- req.on('error', reject);
82
-
83
- if (options.body) {
84
- req.write(options.body);
85
- }
86
- req.end();
87
- });
88
- };
89
-
90
- /**
91
- * Exchange authorization code for tokens
92
- * @param {string} code - Authorization code from callback
93
- * @param {string} redirectUri - Redirect URI used in authorization
94
- * @returns {Promise<Object>}
95
- */
96
- const exchange = async (code, redirectUri) => {
97
- try {
98
- const body = new URLSearchParams({
99
- grant_type: 'authorization_code',
100
- code: code,
101
- redirect_uri: redirectUri,
102
- client_id: CLIENT_ID,
103
- client_secret: CLIENT_SECRET
104
- }).toString();
105
-
106
- const basicAuth = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64');
107
-
108
- const response = await makeRequest(TOKEN_URL, {
109
- method: 'POST',
110
- headers: {
111
- 'Content-Type': 'application/x-www-form-urlencoded',
112
- 'Accept': 'application/json',
113
- 'Authorization': `Basic ${basicAuth}`
114
- },
115
- body
116
- });
117
-
118
- if (!response.access_token) {
119
- return {
120
- type: 'failed',
121
- error: 'No access token in response'
122
- };
123
- }
124
-
125
- // Fetch user info to get API key
126
- const userInfo = await fetchUserInfo(response.access_token);
127
-
128
- return {
129
- type: 'success',
130
- access: response.access_token,
131
- refresh: response.refresh_token,
132
- expires: Date.now() + (response.expires_in * 1000),
133
- apiKey: userInfo.apiKey,
134
- email: userInfo.email || userInfo.phone
135
- };
136
- } catch (error) {
137
- return {
138
- type: 'failed',
139
- error: error.message
140
- };
141
- }
142
- };
143
-
144
- /**
145
- * Fetch user info including API key
146
- * @param {string} accessToken - Access token
147
- * @returns {Promise<Object>}
148
- */
149
- const fetchUserInfo = async (accessToken) => {
150
- const url = `${USER_INFO_URL}?accessToken=${encodeURIComponent(accessToken)}`;
151
-
152
- const response = await makeRequest(url, {
153
- method: 'GET',
154
- headers: {
155
- 'Accept': 'application/json'
156
- }
157
- });
158
-
159
- if (!response.success || !response.data) {
160
- throw new Error('Failed to fetch user info');
161
- }
162
-
163
- return response.data;
164
- };
165
-
166
- /**
167
- * Refresh access token using refresh token
168
- * @param {string} refreshTokenValue - The refresh token
169
- * @returns {Promise<Object>}
170
- */
171
- const refreshToken = async (refreshTokenValue) => {
172
- try {
173
- const body = new URLSearchParams({
174
- grant_type: 'refresh_token',
175
- refresh_token: refreshTokenValue,
176
- client_id: CLIENT_ID,
177
- client_secret: CLIENT_SECRET
178
- }).toString();
179
-
180
- const basicAuth = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64');
181
-
182
- const response = await makeRequest(TOKEN_URL, {
183
- method: 'POST',
184
- headers: {
185
- 'Content-Type': 'application/x-www-form-urlencoded',
186
- 'Accept': 'application/json',
187
- 'Authorization': `Basic ${basicAuth}`
188
- },
189
- body
190
- });
191
-
192
- if (!response.access_token) {
193
- return {
194
- type: 'failed',
195
- error: 'No access token in refresh response'
196
- };
197
- }
198
-
199
- // Fetch updated user info
200
- const userInfo = await fetchUserInfo(response.access_token);
201
-
202
- return {
203
- type: 'success',
204
- access: response.access_token,
205
- refresh: response.refresh_token,
206
- expires: Date.now() + (response.expires_in * 1000),
207
- apiKey: userInfo.apiKey,
208
- email: userInfo.email || userInfo.phone
209
- };
210
- } catch (error) {
211
- return {
212
- type: 'failed',
213
- error: error.message
214
- };
215
- }
216
- };
217
-
218
- /**
219
- * Get valid access token (refresh if expired)
220
- * @param {Object} oauthData - OAuth data { access, refresh, expires }
221
- * @returns {Promise<Object>}
222
- */
223
- const getValidToken = async (oauthData) => {
224
- if (!oauthData || !oauthData.refresh) {
225
- return null;
226
- }
227
-
228
- const expirationBuffer = 5 * 60 * 1000; // 5 minutes
229
- if (oauthData.expires && oauthData.expires > Date.now() + expirationBuffer) {
230
- return {
231
- ...oauthData,
232
- refreshed: false
233
- };
234
- }
235
-
236
- const result = await refreshToken(oauthData.refresh);
237
- if (result.type === 'success') {
238
- return {
239
- access: result.access,
240
- refresh: result.refresh,
241
- expires: result.expires,
242
- apiKey: result.apiKey,
243
- email: result.email,
244
- refreshed: true
245
- };
246
- }
247
-
248
- return null;
249
- };
250
-
251
- /**
252
- * Check if credentials are OAuth tokens
253
- */
254
- const isOAuthCredentials = (credentials) => {
255
- return credentials && credentials.oauth && credentials.oauth.refresh;
256
- };
257
-
258
- module.exports = {
259
- CLIENT_ID,
260
- CLIENT_SECRET,
261
- CALLBACK_PORT,
262
- generateState,
263
- authorize,
264
- exchange,
265
- fetchUserInfo,
266
- refreshToken,
267
- getValidToken,
268
- isOAuthCredentials
269
- };
@@ -1,233 +0,0 @@
1
- /**
2
- * OpenAI OAuth Authentication (Codex)
3
- *
4
- * Implements OAuth 2.0 with PKCE for OpenAI ChatGPT Plus/Pro plans.
5
- * Based on the public OAuth flow used by OpenAI Codex CLI.
6
- *
7
- * Data source: OpenAI OAuth API (https://auth.openai.com/oauth/token)
8
- */
9
-
10
- const crypto = require('crypto');
11
- const https = require('https');
12
-
13
- // Public OAuth Client ID (from OpenAI Codex CLI)
14
- const CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
15
- const REDIRECT_URI = 'http://localhost:1455/auth/callback';
16
- const AUTH_URL = 'https://auth.openai.com/oauth/authorize';
17
- const TOKEN_URL = 'https://auth.openai.com/oauth/token';
18
-
19
- /**
20
- * Generate PKCE code verifier and challenge
21
- * @returns {Object} { verifier: string, challenge: string }
22
- */
23
- const generatePKCE = () => {
24
- // Generate a random 32-byte code verifier (base64url encoded)
25
- const verifier = crypto.randomBytes(32)
26
- .toString('base64')
27
- .replace(/\+/g, '-')
28
- .replace(/\//g, '_')
29
- .replace(/=/g, '');
30
-
31
- // Generate SHA256 hash of verifier, then base64url encode it
32
- const challenge = crypto.createHash('sha256')
33
- .update(verifier)
34
- .digest('base64')
35
- .replace(/\+/g, '-')
36
- .replace(/\//g, '_')
37
- .replace(/=/g, '');
38
-
39
- return { verifier, challenge };
40
- };
41
-
42
- /**
43
- * Generate OAuth authorization URL
44
- * @returns {Object} { url: string, verifier: string }
45
- */
46
- const authorize = () => {
47
- const pkce = generatePKCE();
48
- const state = crypto.randomBytes(16).toString('hex');
49
-
50
- const url = new URL(AUTH_URL);
51
- url.searchParams.set('client_id', CLIENT_ID);
52
- url.searchParams.set('response_type', 'code');
53
- url.searchParams.set('redirect_uri', REDIRECT_URI);
54
- url.searchParams.set('scope', 'openid email profile offline_access');
55
- url.searchParams.set('state', state);
56
- url.searchParams.set('code_challenge', pkce.challenge);
57
- url.searchParams.set('code_challenge_method', 'S256');
58
- url.searchParams.set('prompt', 'login');
59
- url.searchParams.set('id_token_add_organizations', 'true');
60
- url.searchParams.set('codex_cli_simplified_flow', 'true');
61
-
62
- return {
63
- url: url.toString(),
64
- verifier: pkce.verifier,
65
- state
66
- };
67
- };
68
-
69
- /**
70
- * Make HTTPS request
71
- */
72
- const makeRequest = (urlStr, options) => {
73
- return new Promise((resolve, reject) => {
74
- const url = new URL(urlStr);
75
- const req = https.request({
76
- hostname: url.hostname,
77
- port: url.port || 443,
78
- path: url.pathname + url.search,
79
- method: options.method || 'POST',
80
- headers: options.headers || {}
81
- }, (res) => {
82
- let data = '';
83
- res.on('data', chunk => data += chunk);
84
- res.on('end', () => {
85
- try {
86
- const json = JSON.parse(data);
87
- if (res.statusCode >= 200 && res.statusCode < 300) {
88
- resolve(json);
89
- } else {
90
- reject(new Error(json.error_description || json.error || `HTTP ${res.statusCode}`));
91
- }
92
- } catch (e) {
93
- reject(new Error(`Invalid JSON response: ${data.substring(0, 200)}`));
94
- }
95
- });
96
- });
97
-
98
- req.on('error', reject);
99
-
100
- if (options.body) {
101
- req.write(options.body);
102
- }
103
- req.end();
104
- });
105
- };
106
-
107
- /**
108
- * Exchange authorization code for tokens
109
- * @param {string} code - Authorization code from callback
110
- * @param {string} verifier - PKCE code verifier
111
- * @returns {Promise<Object>} { type: 'success', access: string, refresh: string, expires: number }
112
- */
113
- const exchange = async (code, verifier) => {
114
- try {
115
- const body = new URLSearchParams({
116
- grant_type: 'authorization_code',
117
- client_id: CLIENT_ID,
118
- code: code,
119
- redirect_uri: REDIRECT_URI,
120
- code_verifier: verifier
121
- }).toString();
122
-
123
- const response = await makeRequest(TOKEN_URL, {
124
- method: 'POST',
125
- headers: {
126
- 'Content-Type': 'application/x-www-form-urlencoded',
127
- 'Accept': 'application/json'
128
- },
129
- body
130
- });
131
-
132
- return {
133
- type: 'success',
134
- access: response.access_token,
135
- refresh: response.refresh_token,
136
- idToken: response.id_token,
137
- expires: Date.now() + (response.expires_in * 1000)
138
- };
139
- } catch (error) {
140
- return {
141
- type: 'failed',
142
- error: error.message
143
- };
144
- }
145
- };
146
-
147
- /**
148
- * Refresh access token using refresh token
149
- * @param {string} refreshTokenValue - The refresh token
150
- * @returns {Promise<Object>}
151
- */
152
- const refreshToken = async (refreshTokenValue) => {
153
- try {
154
- const body = new URLSearchParams({
155
- client_id: CLIENT_ID,
156
- grant_type: 'refresh_token',
157
- refresh_token: refreshTokenValue,
158
- scope: 'openid profile email'
159
- }).toString();
160
-
161
- const response = await makeRequest(TOKEN_URL, {
162
- method: 'POST',
163
- headers: {
164
- 'Content-Type': 'application/x-www-form-urlencoded',
165
- 'Accept': 'application/json'
166
- },
167
- body
168
- });
169
-
170
- return {
171
- type: 'success',
172
- access: response.access_token,
173
- refresh: response.refresh_token,
174
- idToken: response.id_token,
175
- expires: Date.now() + (response.expires_in * 1000)
176
- };
177
- } catch (error) {
178
- return {
179
- type: 'failed',
180
- error: error.message
181
- };
182
- }
183
- };
184
-
185
- /**
186
- * Get valid access token (refresh if expired)
187
- * @param {Object} oauthData - OAuth data { access, refresh, expires }
188
- * @returns {Promise<Object>}
189
- */
190
- const getValidToken = async (oauthData) => {
191
- if (!oauthData || !oauthData.refresh) {
192
- return null;
193
- }
194
-
195
- const expirationBuffer = 5 * 60 * 1000; // 5 minutes
196
- if (oauthData.expires && oauthData.expires > Date.now() + expirationBuffer) {
197
- return {
198
- ...oauthData,
199
- refreshed: false
200
- };
201
- }
202
-
203
- const result = await refreshToken(oauthData.refresh);
204
- if (result.type === 'success') {
205
- return {
206
- access: result.access,
207
- refresh: result.refresh,
208
- expires: result.expires,
209
- refreshed: true
210
- };
211
- }
212
-
213
- return null;
214
- };
215
-
216
- /**
217
- * Check if credentials are OAuth tokens
218
- */
219
- const isOAuthCredentials = (credentials) => {
220
- return credentials && credentials.oauth && credentials.oauth.refresh;
221
- };
222
-
223
- module.exports = {
224
- CLIENT_ID,
225
- REDIRECT_URI,
226
- CALLBACK_PORT: 1455,
227
- generatePKCE,
228
- authorize,
229
- exchange,
230
- refreshToken,
231
- getValidToken,
232
- isOAuthCredentials
233
- };