irdata_js 0.3.0 → 0.4.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.
- package/README.md +92 -27
- package/dist/index.cjs +93 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -18
- package/dist/index.d.ts +54 -18
- package/dist/index.global.js +93 -12
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +93 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# irdata_js
|
|
2
2
|
|
|
3
|
-
JavaScript library to interact with the iRacing /data API.
|
|
3
|
+
A JavaScript library to interact with the iRacing /data API.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,6 +8,20 @@ JavaScript library to interact with the iRacing /data API.
|
|
|
8
8
|
npm install irdata_js
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Compatibility
|
|
12
|
+
|
|
13
|
+
- **Node.js**: v20.0.0 or newer.
|
|
14
|
+
- **Browsers**: Modern browsers supporting ES2022 (Chrome 100+, Firefox 100+, Safari 15.4+).
|
|
15
|
+
|
|
16
|
+
## Client Registration
|
|
17
|
+
|
|
18
|
+
Before using the library, you must register your application with iRacing to obtain a Client ID and configure your Redirect URI.
|
|
19
|
+
|
|
20
|
+
Please refer to the [official iRacing Client Registration documentation](https://oauth.iracing.com/oauth2/book/client_registration.html).
|
|
21
|
+
|
|
22
|
+
> [!NOTE]
|
|
23
|
+
> It may take up to **10 business days** for registration requests to be processed.
|
|
24
|
+
|
|
11
25
|
## CDN Usage
|
|
12
26
|
|
|
13
27
|
For direct usage in the browser without a build step, you can load the library via a CDN. The library is exposed as the global `irdata` variable.
|
|
@@ -16,7 +30,8 @@ For direct usage in the browser without a build step, you can load the library v
|
|
|
16
30
|
<script src="https://unpkg.com/irdata_js/dist/index.global.js"></script>
|
|
17
31
|
<script>
|
|
18
32
|
const client = new irdata.IRacingClient({
|
|
19
|
-
|
|
33
|
+
clientId: 'YOUR_CLIENT_ID',
|
|
34
|
+
redirectUri: 'YOUR_REDIRECT_URI',
|
|
20
35
|
});
|
|
21
36
|
</script>
|
|
22
37
|
```
|
|
@@ -31,18 +46,54 @@ The library supports OAuth 2.0 authentication.
|
|
|
31
46
|
import { IRacingClient } from 'irdata_js';
|
|
32
47
|
|
|
33
48
|
const client = new IRacingClient({
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
redirectUri: 'YOUR_REDIRECT_URI', // Required for OAuth
|
|
37
|
-
},
|
|
49
|
+
clientId: 'YOUR_CLIENT_ID', // Required for OAuth
|
|
50
|
+
redirectUri: 'YOUR_REDIRECT_URI', // Required for OAuth
|
|
38
51
|
});
|
|
39
52
|
```
|
|
40
53
|
|
|
54
|
+
### Configuration
|
|
55
|
+
|
|
56
|
+
The `IRacingClient` constructor accepts two optional configuration objects: `AuthConfig` and `ProxyConfig`.
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
const authConfig = {
|
|
60
|
+
clientId: 'YOUR_CLIENT_ID',
|
|
61
|
+
redirectUri: 'YOUR_REDIRECT_URI',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const proxyConfig = {
|
|
65
|
+
apiUrl: 'https://your-proxy.com/data',
|
|
66
|
+
fileProxyUrl: 'https://your-proxy.com/passthrough',
|
|
67
|
+
authBaseUrl: 'https://your-proxy.com/oauth2',
|
|
68
|
+
tokenEndpoint: 'https://your-proxy.com/token',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const client = new IRacingClient(authConfig, proxyConfig);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### AuthConfig
|
|
75
|
+
|
|
76
|
+
| Property | Type | Required | Description |
|
|
77
|
+
| :------------ | :------- | :------- | :---------------------------------------------------------------- |
|
|
78
|
+
| `clientId` | `string` | **Yes** | Your iRacing OAuth client ID. |
|
|
79
|
+
| `redirectUri` | `string` | **Yes** | The URI iRacing will redirect to after successful authentication. |
|
|
80
|
+
|
|
81
|
+
#### ProxyConfig
|
|
82
|
+
|
|
83
|
+
Since the iRacing API and S3 buckets do not support CORS, you need to use a proxy for browser-based applications. If you provide a `ProxyConfig` object, the following fields are mandatory (except `authBaseUrl`).
|
|
84
|
+
|
|
85
|
+
| Property | Type | Required | Description |
|
|
86
|
+
| :-------------- | :------- | :------- | :-------------------------------------------------------------------------------------------------- |
|
|
87
|
+
| `apiUrl` | `string` | **Yes** | The base URL for API requests. |
|
|
88
|
+
| `fileProxyUrl` | `string` | **Yes** | A proxy URL for fetching S3 files. The original S3 URL will be appended as a `url` query parameter. |
|
|
89
|
+
| `tokenEndpoint` | `string` | **Yes** | The specific endpoint for token exchange. |
|
|
90
|
+
| `authBaseUrl` | `string` | No | The base URL for OAuth authorization. Defaults to `https://oauth.iracing.com/oauth2`. |
|
|
91
|
+
|
|
41
92
|
### 2. Authentication
|
|
42
93
|
|
|
43
94
|
#### Web / Browser (OAuth 2.0 PKCE)
|
|
44
95
|
|
|
45
|
-
To authenticate in the browser, you need to generate an authorization URL, redirect the user, and then handle the
|
|
96
|
+
To authenticate in the browser, you need to generate an authorization URL, redirect the user, and then handle the return.
|
|
46
97
|
|
|
47
98
|
**Step 1: Generate Auth URL and Redirect**
|
|
48
99
|
|
|
@@ -51,18 +102,26 @@ const url = await client.auth.generateAuthUrl();
|
|
|
51
102
|
window.location.href = url;
|
|
52
103
|
```
|
|
53
104
|
|
|
54
|
-
**Step 2: Handle
|
|
105
|
+
**Step 2: Handle Return & Restore Session**
|
|
55
106
|
|
|
56
|
-
|
|
107
|
+
Simply call `handleAuthentication()` on every page that uses the library. This single method handles:
|
|
108
|
+
- Exchanging the authorization code (when returning from the iRacing login page).
|
|
109
|
+
- Refreshing the access token (if a refresh token is stored).
|
|
110
|
+
- Verifying an existing session.
|
|
57
111
|
|
|
58
112
|
```javascript
|
|
59
|
-
|
|
60
|
-
|
|
113
|
+
// This should run on every page load of your application,
|
|
114
|
+
// including the redirectUri page.
|
|
115
|
+
const isAuthenticated = await client.auth.handleAuthentication();
|
|
116
|
+
```
|
|
61
117
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
118
|
+
#### Manual Session Management
|
|
119
|
+
|
|
120
|
+
If you have obtained an access token (and refresh token) through other means (e.g., server-side authentication), you can manually set the session on the client.
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
// Set the access token (and optional refresh token)
|
|
124
|
+
client.auth.setSession('YOUR_ACCESS_TOKEN', 'YOUR_REFRESH_TOKEN');
|
|
66
125
|
```
|
|
67
126
|
|
|
68
127
|
### 3. Fetch Data
|
|
@@ -73,7 +132,7 @@ Once authenticated, you can call any endpoint using `getData`. This method handl
|
|
|
73
132
|
try {
|
|
74
133
|
// Call an endpoint directly
|
|
75
134
|
const { data, metadata } = await client.getData('/member/info');
|
|
76
|
-
|
|
135
|
+
|
|
77
136
|
console.log(data); // The actual API response
|
|
78
137
|
console.log(metadata.contentType); // Response content type (e.g. 'application/json')
|
|
79
138
|
console.log(metadata.sizeBytes); // Response size in bytes
|
|
@@ -108,7 +167,7 @@ For extremely large datasets, you might want to fetch chunks one by one:
|
|
|
108
167
|
```javascript
|
|
109
168
|
if (result.metadata.chunkCount > 0) {
|
|
110
169
|
const totalChunks = result.metadata.chunkCount;
|
|
111
|
-
|
|
170
|
+
|
|
112
171
|
for (let i = 0; i < totalChunks; i++) {
|
|
113
172
|
const { data: chunk } = await client.getChunk(result.data, i);
|
|
114
173
|
console.log(`Processing chunk ${i + 1}/${totalChunks}`);
|
|
@@ -118,6 +177,16 @@ if (result.metadata.chunkCount > 0) {
|
|
|
118
177
|
|
|
119
178
|
> **Note:** iRacing's API incorrectly returns `application/octet-stream` as the `Content-Type` for JSON chunks. This library automatically detects and parses these as JSON.
|
|
120
179
|
|
|
180
|
+
## The Proxy Requirement (CORS)
|
|
181
|
+
|
|
182
|
+
The iRacing API (`members-ng.iracing.com`) and its associated S3 data links do not provide CORS (`Cross-Origin Resource Sharing`) headers for third-party domains. This means that direct requests from a web browser to the API will be blocked by the browser's security policies.
|
|
183
|
+
|
|
184
|
+
This behavior is intentional by iRacing to better protect their business and operations and is unlikely to change (see [this message by their head of operations](https://forums.iracing.com/discussion/comment/772334/#Comment_772334)).
|
|
185
|
+
|
|
186
|
+
To use this library in a web application, you must route your requests through a proxy server that adds the necessary CORS headers or resides on the same domain as your application.
|
|
187
|
+
|
|
188
|
+
For development and as a reference implementation, this repository includes a `proxy_server.js` that demonstrates how to implement such a workaround. See the [Development](#development) section for more details on how to use it.
|
|
189
|
+
|
|
121
190
|
## Development
|
|
122
191
|
|
|
123
192
|
### Build
|
|
@@ -130,7 +199,9 @@ npm run build
|
|
|
130
199
|
|
|
131
200
|
This repository includes a local development proxy server and a demo application to test the OAuth flow and API interaction, avoiding CORS issues during development.
|
|
132
201
|
|
|
133
|
-
1. Create a file named `config.json` in the `demo/` directory (ignored by git) with your configuration
|
|
202
|
+
1. Create a file named `config.json` in the `demo/` directory (ignored by git) with your configuration. See the [Configuration](#configuration) section for details on the `ProxyConfig` and `AuthConfig` structures which map to this JSON file.
|
|
203
|
+
|
|
204
|
+
**Example `demo/config.json`:**
|
|
134
205
|
|
|
135
206
|
```json
|
|
136
207
|
{
|
|
@@ -145,23 +216,17 @@ This repository includes a local development proxy server and a demo application
|
|
|
145
216
|
}
|
|
146
217
|
```
|
|
147
218
|
|
|
148
|
-
* `port`: The port the proxy server will listen on.
|
|
149
|
-
* `basePath`: The path prefix where the static files and proxy endpoints are served from.
|
|
150
|
-
* `redirectPath`: The path the proxy server intercepts for OAuth callbacks.
|
|
151
|
-
* `auth`: Your iRacing API credentials. Note that `tokenEndpoint` should include the `basePath`.
|
|
152
|
-
|
|
153
219
|
2. Start the proxy server:
|
|
154
220
|
|
|
155
221
|
```bash
|
|
156
222
|
npm run dev
|
|
157
223
|
```
|
|
158
224
|
|
|
159
|
-
|
|
160
|
-
|
|
225
|
+
_This command automatically generates the `demo/index.html` from the template using your configuration and starts the proxy server._
|
|
226
|
+
_Depending on your system configuration, you might need elevated privileges (e.g., `sudo`) to listen on port 80._
|
|
161
227
|
|
|
162
228
|
3. Open `http://127.0.0.1/irdata_js/` (or your configured `basePath`) in your browser.
|
|
163
|
-
|
|
164
|
-
|
|
229
|
+
- The demo app is configured to use the local proxy endpoints (e.g., `/irdata_js/token`, `/irdata_js/data`, `/irdata_js/passthrough`) to bypass CORS restrictions.
|
|
165
230
|
|
|
166
231
|
## License
|
|
167
232
|
|
package/dist/index.cjs
CHANGED
|
@@ -268,10 +268,13 @@ var LocalStorageTokenStore = class {
|
|
|
268
268
|
var AuthManager = class {
|
|
269
269
|
tokenStore;
|
|
270
270
|
config;
|
|
271
|
-
|
|
272
|
-
|
|
271
|
+
authBaseUrl;
|
|
272
|
+
tokenEndpoint;
|
|
273
|
+
pkceVerifier = null;
|
|
274
|
+
constructor(config, proxySettings = {}) {
|
|
273
275
|
this.config = config;
|
|
274
|
-
this.
|
|
276
|
+
this.authBaseUrl = proxySettings.authBaseUrl || "https://oauth.iracing.com/oauth2";
|
|
277
|
+
this.tokenEndpoint = proxySettings.tokenEndpoint;
|
|
275
278
|
if (typeof window !== "undefined" && window.localStorage) {
|
|
276
279
|
this.tokenStore = new LocalStorageTokenStore();
|
|
277
280
|
} else {
|
|
@@ -281,6 +284,54 @@ var AuthManager = class {
|
|
|
281
284
|
get accessToken() {
|
|
282
285
|
return this.tokenStore.getAccessToken();
|
|
283
286
|
}
|
|
287
|
+
get refreshToken() {
|
|
288
|
+
return this.tokenStore.getRefreshToken();
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Manually sets the session tokens.
|
|
292
|
+
* Useful for loading a saved session from external storage.
|
|
293
|
+
*/
|
|
294
|
+
setSession(accessToken, refreshToken) {
|
|
295
|
+
this.tokenStore.setAccessToken(accessToken);
|
|
296
|
+
if (refreshToken) {
|
|
297
|
+
this.tokenStore.setRefreshToken(refreshToken);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Returns true if an access token is present.
|
|
302
|
+
*/
|
|
303
|
+
get isLoggedIn() {
|
|
304
|
+
return !!this.accessToken;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Comprehensive method to establish a session.
|
|
308
|
+
*
|
|
309
|
+
* Automatically handles:
|
|
310
|
+
* 1. Existing valid sessions (returns true immediately).
|
|
311
|
+
* 2. OAuth Callback handling (exchanges code for token).
|
|
312
|
+
* 3. Session restoration (uses Refresh Token).
|
|
313
|
+
*
|
|
314
|
+
* @returns Promise<boolean> - true if authenticated, false otherwise.
|
|
315
|
+
*/
|
|
316
|
+
async handleAuthentication() {
|
|
317
|
+
if (this.isLoggedIn) {
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
await this.handleCallback();
|
|
322
|
+
if (this.isLoggedIn) {
|
|
323
|
+
if (typeof window !== "undefined" && window.history && window.history.replaceState) {
|
|
324
|
+
const url = new URL(window.location.href);
|
|
325
|
+
url.searchParams.delete("code");
|
|
326
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
327
|
+
}
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.warn("Authentication: Callback exchange failed", error);
|
|
332
|
+
}
|
|
333
|
+
return await this.refreshAccessToken();
|
|
334
|
+
}
|
|
284
335
|
getAuthHeaders() {
|
|
285
336
|
const headers = {};
|
|
286
337
|
const token = this.tokenStore.getAccessToken();
|
|
@@ -298,6 +349,8 @@ var AuthManager = class {
|
|
|
298
349
|
const challenge = await PKCEHelper.generateChallenge(verifier);
|
|
299
350
|
if (typeof window !== "undefined" && window.sessionStorage) {
|
|
300
351
|
window.sessionStorage.setItem("irdata_pkce_verifier", verifier);
|
|
352
|
+
} else {
|
|
353
|
+
this.pkceVerifier = verifier;
|
|
301
354
|
}
|
|
302
355
|
const params = new URLSearchParams({
|
|
303
356
|
response_type: "code",
|
|
@@ -308,14 +361,42 @@ var AuthManager = class {
|
|
|
308
361
|
code_challenge: challenge,
|
|
309
362
|
code_challenge_method: "S256"
|
|
310
363
|
});
|
|
311
|
-
return `${this.
|
|
364
|
+
return `${this.authBaseUrl}/authorize?${params.toString()}`;
|
|
312
365
|
}
|
|
313
|
-
async handleCallback(
|
|
366
|
+
async handleCallback(codeOrUrl) {
|
|
367
|
+
let input = codeOrUrl;
|
|
368
|
+
if (!input && typeof window !== "undefined") {
|
|
369
|
+
input = window.location.href;
|
|
370
|
+
}
|
|
371
|
+
if (!input) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
let code = input;
|
|
375
|
+
if (input.includes("code=") || input.startsWith("http")) {
|
|
376
|
+
try {
|
|
377
|
+
const url = new URL(input);
|
|
378
|
+
const extractedCode = url.searchParams.get("code");
|
|
379
|
+
if (extractedCode) {
|
|
380
|
+
code = extractedCode;
|
|
381
|
+
} else if (input.startsWith("http")) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
} catch {
|
|
385
|
+
const match = input.match(/[?&]code=([\w-]+)/);
|
|
386
|
+
if (match) {
|
|
387
|
+
code = match[1];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
314
391
|
let verifier = "";
|
|
315
392
|
if (typeof window !== "undefined" && window.sessionStorage) {
|
|
316
393
|
verifier = window.sessionStorage.getItem("irdata_pkce_verifier") || "";
|
|
317
394
|
window.sessionStorage.removeItem("irdata_pkce_verifier");
|
|
318
395
|
}
|
|
396
|
+
if (!verifier && this.pkceVerifier) {
|
|
397
|
+
verifier = this.pkceVerifier;
|
|
398
|
+
this.pkceVerifier = null;
|
|
399
|
+
}
|
|
319
400
|
if (!verifier) {
|
|
320
401
|
throw new Error("No PKCE verifier found");
|
|
321
402
|
}
|
|
@@ -326,7 +407,7 @@ var AuthManager = class {
|
|
|
326
407
|
code,
|
|
327
408
|
code_verifier: verifier
|
|
328
409
|
});
|
|
329
|
-
const tokenUrl = this.
|
|
410
|
+
const tokenUrl = this.tokenEndpoint || `${this.authBaseUrl}/token`;
|
|
330
411
|
const response = await fetch(tokenUrl, {
|
|
331
412
|
method: "POST",
|
|
332
413
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
@@ -368,7 +449,7 @@ var AuthManager = class {
|
|
|
368
449
|
client_id: this.config.clientId,
|
|
369
450
|
refresh_token: refreshToken
|
|
370
451
|
});
|
|
371
|
-
const tokenUrl = this.
|
|
452
|
+
const tokenUrl = this.tokenEndpoint || `${this.authBaseUrl}/token`;
|
|
372
453
|
try {
|
|
373
454
|
const response = await fetch(tokenUrl, {
|
|
374
455
|
method: "POST",
|
|
@@ -400,10 +481,10 @@ var IRacingClient = class {
|
|
|
400
481
|
auth;
|
|
401
482
|
apiUrl;
|
|
402
483
|
fileProxyUrl;
|
|
403
|
-
constructor(
|
|
404
|
-
this.apiUrl =
|
|
405
|
-
this.fileProxyUrl =
|
|
406
|
-
this.auth = new AuthManager(
|
|
484
|
+
constructor(authConfig, proxyConfig) {
|
|
485
|
+
this.apiUrl = proxyConfig?.apiUrl || "https://members-ng.iracing.com/data";
|
|
486
|
+
this.fileProxyUrl = proxyConfig?.fileProxyUrl;
|
|
487
|
+
this.auth = new AuthManager(authConfig, proxyConfig);
|
|
407
488
|
}
|
|
408
489
|
calculateSize(response, data) {
|
|
409
490
|
const cl = response.headers.get("content-length");
|
|
@@ -666,7 +747,7 @@ var IRacingClient = class {
|
|
|
666
747
|
};
|
|
667
748
|
|
|
668
749
|
// src/version.ts
|
|
669
|
-
var VERSION = "0.
|
|
750
|
+
var VERSION = "0.4.0";
|
|
670
751
|
// Annotate the CommonJS export names for ESM import in node:
|
|
671
752
|
0 && (module.exports = {
|
|
672
753
|
AuthManager,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/auth/PKCEHelper.ts","../src/errors.ts","../src/auth/AuthManager.ts","../src/client.ts","../src/version.ts"],"sourcesContent":["export * from './client.js';\nexport * from './auth/AuthManager.js';\nexport * from './auth/PKCEHelper.js';\nexport * from './errors.js';\nexport * from './version.js';\n","export class PKCEHelper {\n /**\n * Generates a random string for the code verifier.\n * @param length Length of the string (43-128 characters recommended)\n */\n static generateVerifier(length: number = 128): string {\n const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n let result = '';\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n const values = new Uint8Array(length);\n crypto.getRandomValues(values);\n for (let i = 0; i < length; i++) {\n result += charset[values[i] % charset.length];\n }\n } else {\n // Fallback for environments without crypto.getRandomValues (though Node 18+ and browsers have it)\n for (let i = 0; i < length; i++) {\n result += charset.charAt(Math.floor(Math.random() * charset.length));\n }\n }\n return result;\n }\n\n /**\n * Generates the code challenge from the verifier using SHA-256.\n * @param verifier The code verifier string\n */\n static async generateChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n\n if (typeof crypto !== 'undefined' && crypto.subtle) {\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n return this.base64URLEncode(hashBuffer);\n } else {\n // Fallback for insecure contexts or environments without crypto.subtle\n const hashBuffer = this.sha256(data);\n return this.base64URLEncode(hashBuffer);\n }\n }\n\n private static base64URLEncode(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.byteLength; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n const base64 = btoa(binary);\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n }\n\n private static sha256(data: Uint8Array): ArrayBuffer {\n const K = [\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,\n 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,\n 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,\n 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,\n 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,\n 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,\n 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,\n 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,\n 0xc67178f2,\n ];\n\n function rotr(n: number, x: number) {\n return (x >>> n) | (x << (32 - n));\n }\n function ch(x: number, y: number, z: number) {\n return (x & y) ^ (~x & z);\n }\n function maj(x: number, y: number, z: number) {\n return (x & y) ^ (x & z) ^ (y & z);\n }\n function sigma0(x: number) {\n return rotr(2, x) ^ rotr(13, x) ^ rotr(22, x);\n }\n function sigma1(x: number) {\n return rotr(6, x) ^ rotr(11, x) ^ rotr(25, x);\n }\n function gamma0(x: number) {\n return rotr(7, x) ^ rotr(18, x) ^ (x >>> 3);\n }\n function gamma1(x: number) {\n return rotr(17, x) ^ rotr(19, x) ^ (x >>> 10);\n }\n\n const bytes = new Uint8Array(data);\n const len = bytes.length * 8;\n\n // Padding\n const paddingLen = (((len + 64) >>> 9) << 4) + 16;\n const words = new Uint32Array(paddingLen);\n for (let i = 0; i < bytes.length; i++) words[i >>> 2] |= bytes[i] << (24 - (i % 4) * 8);\n words[bytes.length >>> 2] |= 0x80 << (24 - (bytes.length % 4) * 8);\n words[paddingLen - 1] = len;\n\n const H = [\n 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,\n 0x5be0cd19,\n ];\n\n const W = new Uint32Array(64);\n for (let i = 0; i < words.length; i += 16) {\n W.fill(0);\n for (let j = 0; j < 16; j++) W[j] = words[i + j];\n for (let j = 16; j < 64; j++)\n W[j] = (gamma1(W[j - 2]) + W[j - 7] + gamma0(W[j - 15]) + W[j - 16]) | 0;\n\n let [a, b, c, d, e, f, g, h] = H;\n\n for (let j = 0; j < 64; j++) {\n const T1 = (h + sigma1(e) + ch(e, f, g) + K[j] + W[j]) | 0;\n const T2 = (sigma0(a) + maj(a, b, c)) | 0;\n h = g;\n g = f;\n f = e;\n e = (d + T1) | 0;\n d = c;\n c = b;\n b = a;\n a = (T1 + T2) | 0;\n }\n\n H[0] = (H[0] + a) | 0;\n H[1] = (H[1] + b) | 0;\n H[2] = (H[2] + c) | 0;\n H[3] = (H[3] + d) | 0;\n H[4] = (H[4] + e) | 0;\n H[5] = (H[5] + f) | 0;\n H[6] = (H[6] + g) | 0;\n H[7] = (H[7] + h) | 0;\n }\n\n const buffer = new ArrayBuffer(32);\n const view = new DataView(buffer);\n H.forEach((h, i) => view.setUint32(i * 4, h, false));\n return buffer;\n }\n}\n","export class IRacingAPIError extends Error {\n constructor(\n message: string,\n public status: number,\n public statusText: string,\n public body?: unknown,\n ) {\n super(message);\n this.name = 'IRacingAPIError';\n }\n}\n","import { PKCEHelper } from './PKCEHelper.js';\nimport { IRacingAPIError } from '../errors.js';\n\ninterface TokenResponse {\n access_token: string;\n expires_in: number; // seconds\n token_type: string;\n refresh_token?: string; // Not always returned?\n // Add other fields if necessary\n}\n\nexport interface AuthConfig {\n clientId?: string; // For OAuth\n redirectUri?: string; // For OAuth\n authBaseUrl?: string;\n tokenEndpoint?: string;\n}\n\nexport interface TokenStore {\n getAccessToken(): string | null;\n setAccessToken(token: string): void;\n getRefreshToken(): string | null;\n setRefreshToken(token: string): void;\n clear(): void;\n}\n\nclass InMemoryTokenStore implements TokenStore {\n private accessToken: string | null = null;\n private refreshToken: string | null = null;\n\n getAccessToken() {\n return this.accessToken;\n }\n setAccessToken(token: string) {\n this.accessToken = token;\n }\n getRefreshToken() {\n return this.refreshToken;\n }\n setRefreshToken(token: string) {\n this.refreshToken = token;\n }\n clear() {\n this.accessToken = null;\n this.refreshToken = null;\n }\n}\n\nclass LocalStorageTokenStore implements TokenStore {\n private prefix = 'irdata_';\n getAccessToken() {\n return localStorage.getItem(this.prefix + 'access_token');\n }\n setAccessToken(token: string) {\n localStorage.setItem(this.prefix + 'access_token', token);\n }\n getRefreshToken() {\n return localStorage.getItem(this.prefix + 'refresh_token');\n }\n setRefreshToken(token: string) {\n localStorage.setItem(this.prefix + 'refresh_token', token);\n }\n clear() {\n localStorage.removeItem(this.prefix + 'access_token');\n localStorage.removeItem(this.prefix + 'refresh_token');\n }\n}\n\nexport class AuthManager {\n private tokenStore: TokenStore;\n private config: AuthConfig;\n private baseUrl = 'https://oauth.iracing.com/oauth2';\n\n constructor(config: AuthConfig = {}) {\n this.config = config;\n this.baseUrl = config.authBaseUrl || 'https://oauth.iracing.com/oauth2';\n if (typeof window !== 'undefined' && window.localStorage) {\n this.tokenStore = new LocalStorageTokenStore();\n } else {\n this.tokenStore = new InMemoryTokenStore();\n }\n }\n\n get accessToken(): string | null {\n return this.tokenStore.getAccessToken();\n }\n\n getAuthHeaders(): HeadersInit {\n const headers: HeadersInit = {};\n const token = this.tokenStore.getAccessToken();\n\n if (token) {\n // OAuth2 Bearer Token\n headers['Authorization'] = `Bearer ${token}`;\n }\n return headers;\n }\n\n // --- Browser OAuth2 PKCE ---\n\n async generateAuthUrl(): Promise<string> {\n if (!this.config.clientId || !this.config.redirectUri) {\n throw new Error('clientId and redirectUri required for OAuth');\n }\n\n const verifier = PKCEHelper.generateVerifier();\n const challenge = await PKCEHelper.generateChallenge(verifier);\n\n // Store verifier for the callback\n if (typeof window !== 'undefined' && window.sessionStorage) {\n window.sessionStorage.setItem('irdata_pkce_verifier', verifier);\n }\n\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: this.config.clientId,\n redirect_uri: this.config.redirectUri,\n scope: 'iracing.auth', // Required based iRacing docs\n code_challenge: challenge,\n code_challenge_method: 'S256',\n });\n\n return `${this.baseUrl}/authorize?${params.toString()}`;\n }\n\n async handleCallback(code: string): Promise<void> {\n let verifier = '';\n if (typeof window !== 'undefined' && window.sessionStorage) {\n verifier = window.sessionStorage.getItem('irdata_pkce_verifier') || '';\n window.sessionStorage.removeItem('irdata_pkce_verifier');\n }\n\n if (!verifier) {\n throw new Error('No PKCE verifier found');\n }\n\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: this.config.clientId!,\n redirect_uri: this.config.redirectUri!,\n code: code,\n code_verifier: verifier,\n });\n\n const tokenUrl = this.config.tokenEndpoint || `${this.baseUrl}/token`;\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n let errorBody: unknown;\n try {\n errorBody = await response.json();\n } catch (_e) {\n try {\n errorBody = await response.text();\n } catch (_e2) {\n /* ignore */\n }\n }\n throw new IRacingAPIError(\n `Failed to exchange code for token: ${response.status} ${response.statusText}`,\n response.status,\n response.statusText,\n errorBody,\n );\n }\n\n const tokens: TokenResponse = await response.json();\n this.tokenStore.setAccessToken(tokens.access_token);\n if (tokens.refresh_token) {\n this.tokenStore.setRefreshToken(tokens.refresh_token);\n }\n }\n\n async refreshAccessToken(): Promise<boolean> {\n const refreshToken = this.tokenStore.getRefreshToken();\n if (!refreshToken) {\n return false;\n }\n\n if (!this.config.clientId) {\n throw new Error('clientId required for token refresh');\n }\n\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: this.config.clientId,\n refresh_token: refreshToken,\n });\n\n const tokenUrl = this.config.tokenEndpoint || `${this.baseUrl}/token`;\n\n try {\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n // If refresh fails (e.g. token expired), clear tokens to force re-login\n this.tokenStore.clear();\n return false;\n }\n\n const tokens: TokenResponse = await response.json();\n this.tokenStore.setAccessToken(tokens.access_token);\n // Update refresh token if a new one is returned\n if (tokens.refresh_token) {\n this.tokenStore.setRefreshToken(tokens.refresh_token);\n }\n return true;\n } catch (error) {\n console.error('Error refreshing token:', error);\n return false;\n }\n }\n\n logout() {\n this.tokenStore.clear();\n }\n}\n","import { AuthManager } from './auth/AuthManager.js';\nimport { IRacingAPIError } from './errors.js';\n\nexport interface ClientConfig {\n apiUrl?: string;\n fileProxyUrl?: string;\n auth?: {\n clientId?: string;\n redirectUri?: string;\n authBaseUrl?: string;\n tokenEndpoint?: string;\n };\n}\n\nexport interface ChunkInfo {\n chunk_size: number;\n num_chunks: number;\n rows: number;\n base_download_url: string;\n chunk_file_names: string[];\n}\n\ninterface DataWithChunkInfo {\n chunk_info?: ChunkInfo;\n}\n\nexport interface DataResult<T> {\n data: T;\n metadata: {\n s3LinkFollowed: boolean;\n chunkCount: number;\n chunkRows?: number;\n sizeBytes: number;\n fetchTimeMs: number;\n contentType?: string;\n };\n}\n\nexport interface ChunkResult<T> {\n data: T;\n metadata: {\n sizeBytes: number;\n fetchTimeMs: number;\n contentType?: string;\n };\n}\n\nexport class IRacingClient {\n public auth: AuthManager;\n private apiUrl: string;\n private fileProxyUrl?: string;\n\n constructor(config: ClientConfig = {}) {\n this.apiUrl = config.apiUrl || 'https://members-ng.iracing.com/data';\n this.fileProxyUrl = config.fileProxyUrl;\n this.auth = new AuthManager(config.auth);\n }\n\n private calculateSize(response: Response, data: unknown): number {\n const cl = response.headers.get('content-length');\n if (cl) return parseInt(cl, 10);\n\n if (typeof data === 'string') {\n return new TextEncoder().encode(data).length;\n }\n\n try {\n return new TextEncoder().encode(JSON.stringify(data)).length;\n } catch {\n return 0;\n }\n }\n\n /**\n * Processes a fetch response based on its Content-Type.\n */\n private async processResponse<T>(\n response: Response,\n ): Promise<{ data: T; contentType: string | null }> {\n let contentType = response.headers.get('content-type');\n let data: T;\n\n if (contentType) {\n const lowerContentType = contentType.toLowerCase();\n // NOTE: iRacing's chunks return with 'application/octet-stream' as Content Type\n if (\n lowerContentType.includes('application/json') ||\n lowerContentType.includes('application/octet-stream')\n ) {\n data = await response.json();\n if (lowerContentType.includes('application/octet-stream')) {\n contentType = contentType.replace(/application\\/octet-stream/i, 'application/json');\n }\n } else {\n data = (await response.text()) as unknown as T;\n }\n } else {\n // If no content type, try JSON, fall back to text\n const clone = response.clone();\n try {\n data = await response.json();\n contentType = 'application/json';\n } catch {\n data = (await clone.text()) as unknown as T;\n contentType = 'text/plain';\n }\n }\n\n return { data, contentType };\n }\n\n /**\n * Internal method to perform request and return data + size.\n */\n private async requestInternal<T>(\n endpoint: string,\n options: RequestInit = {},\n ): Promise<{ data: T; sizeBytes: number; duration: number; contentType: string | null }> {\n const startTime = Date.now();\n // Remove leading slash from endpoint if present\n const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;\n const cleanApiUrl = this.apiUrl.endsWith('/') ? this.apiUrl : `${this.apiUrl}/`;\n const url = `${cleanApiUrl}${cleanEndpoint}`;\n\n const headers = this.auth.getAuthHeaders();\n\n const mergedOptions: RequestInit = {\n ...options,\n headers: {\n ...headers,\n ...options.headers,\n 'Content-Type': 'application/json',\n },\n };\n\n let response = await fetch(url, mergedOptions);\n\n if (!response.ok) {\n if (response.status === 401) {\n // Try to refresh token\n try {\n const refreshed = await this.auth.refreshAccessToken();\n if (refreshed) {\n // Retry request with new token\n const newHeaders = this.auth.getAuthHeaders();\n const retryOptions: RequestInit = {\n ...options,\n headers: {\n ...newHeaders,\n ...options.headers,\n 'Content-Type': 'application/json',\n },\n };\n\n response = await fetch(url, retryOptions);\n }\n } catch (refreshError) {\n console.error('Token refresh failed during request retry:', refreshError);\n }\n }\n\n if (!response.ok) {\n return this.handleErrorResponse(response);\n }\n }\n\n const { data, contentType } = await this.processResponse<T>(response);\n const duration = Date.now() - startTime;\n const sizeBytes = this.calculateSize(response, data);\n return { data, sizeBytes, duration, contentType };\n }\n\n /**\n * Performs a fetch request with authentication headers.\n * Does NOT automatically follow \"link\" responses.\n */\n async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const { data } = await this.requestInternal<T>(endpoint, options);\n return data;\n }\n\n private async handleErrorResponse(response: Response): Promise<never> {\n let body: unknown;\n try {\n const { data } = await this.processResponse<unknown>(response);\n body = data;\n } catch (_e) {\n // ignore\n }\n\n throw new IRacingAPIError(\n `API Request failed: ${response.status} ${response.statusText}`,\n response.status,\n response.statusText,\n body,\n );\n }\n\n /**\n * Helper to fetch data from an external URL (e.g. S3), handling the file proxy if configured.\n */\n private async fetchExternal<T>(\n url: string,\n ): Promise<{ data: T; sizeBytes: number; duration: number; contentType: string | null }> {\n const startTime = Date.now();\n let fetchUrl = url;\n if (this.fileProxyUrl) {\n // If a file proxy is configured, use it\n const separator = this.fileProxyUrl.includes('?') ? '&' : '?';\n fetchUrl = `${this.fileProxyUrl}${separator}url=${encodeURIComponent(url)}`;\n }\n\n const response = await fetch(fetchUrl);\n if (!response.ok) {\n return this.handleErrorResponse(response);\n }\n\n const { data, contentType } = await this.processResponse<T>(response);\n const duration = Date.now() - startTime;\n const sizeBytes = this.calculateSize(response, data);\n return { data, sizeBytes, duration, contentType };\n }\n\n /**\n * Fetches data from an endpoint, automatically following any S3 links returned.\n * Returns metadata about the operation.\n */\n async getData<T>(endpoint: string): Promise<DataResult<T>> {\n const {\n data: initialData,\n sizeBytes: initialSize,\n duration: initialDuration,\n contentType: initialContentType,\n } = await this.requestInternal<T>(endpoint);\n\n // Check if the response contains a generic link to S3 and follow it\n if (\n initialData &&\n typeof initialData === 'object' &&\n 'link' in initialData &&\n typeof (initialData as { link?: unknown }).link === 'string'\n ) {\n const s3Link = (initialData as { link: string }).link;\n if (s3Link.startsWith('http')) {\n const {\n data: externalData,\n sizeBytes: externalSize,\n duration: externalDuration,\n contentType: externalContentType,\n } = await this.fetchExternal<T>(s3Link);\n\n const chunkMetadata = this.extractChunkMetadata(externalData);\n\n return {\n data: externalData,\n metadata: {\n s3LinkFollowed: true,\n chunkCount: chunkMetadata.chunkCount,\n chunkRows: chunkMetadata.chunkRows,\n sizeBytes: externalSize,\n fetchTimeMs: initialDuration + externalDuration,\n contentType: externalContentType || undefined,\n },\n };\n }\n }\n\n const chunkMetadata = this.extractChunkMetadata(initialData);\n\n return {\n data: initialData,\n metadata: {\n s3LinkFollowed: false,\n chunkCount: chunkMetadata.chunkCount,\n chunkRows: chunkMetadata.chunkRows,\n sizeBytes: initialSize,\n fetchTimeMs: initialDuration,\n contentType: initialContentType || undefined,\n },\n };\n }\n\n private extractChunkMetadata(data: unknown): { chunkCount: number; chunkRows?: number } {\n if (data && typeof data === 'object' && 'chunk_info' in data) {\n const chunkInfo = (data as DataWithChunkInfo).chunk_info;\n if (chunkInfo) {\n return {\n chunkCount: typeof chunkInfo.num_chunks === 'number' ? chunkInfo.num_chunks : 0,\n chunkRows: typeof chunkInfo.rows === 'number' ? chunkInfo.rows : undefined,\n };\n }\n }\n return { chunkCount: 0 };\n }\n\n /**\n * Fetches a specific chunk from a response containing chunk_info.\n *\n * @param data The response object containing chunk_info\n * @param chunkIndex The index of the chunk to fetch (0-based)\n * @returns The content of the chunk and metadata\n */\n async getChunk<T>(data: unknown, chunkIndex: number): Promise<ChunkResult<T[]>> {\n const dataWithChunks = data as DataWithChunkInfo;\n if (!dataWithChunks || !dataWithChunks.chunk_info) {\n throw new Error('Response does not contain chunk_info');\n }\n\n const chunkInfo = dataWithChunks.chunk_info;\n const { base_download_url, chunk_file_names } = chunkInfo;\n\n if (\n !base_download_url ||\n !Array.isArray(chunk_file_names) ||\n chunkIndex < 0 ||\n chunkIndex >= chunk_file_names.length\n ) {\n throw new Error(\n `Invalid chunk index: ${chunkIndex} (Total chunks: ${chunk_file_names?.length || 0})`,\n );\n }\n\n const chunkUrl = `${base_download_url}${chunk_file_names[chunkIndex]}`;\n const {\n data: chunkData,\n sizeBytes,\n duration,\n contentType,\n } = await this.fetchExternal<T[]>(chunkUrl);\n return {\n data: chunkData,\n metadata: {\n sizeBytes,\n fetchTimeMs: duration,\n contentType: contentType || undefined,\n },\n };\n }\n\n /**\n * Fetches multiple chunks and merges the results into a single array.\n *\n * @param data The response object containing chunk_info\n * @param options Options for fetching chunks (start index, limit count)\n * @returns A merged array of data from the requested chunks and total size\n */\n async getChunks<T>(\n data: unknown,\n options: { start?: number; limit?: number } = {},\n ): Promise<ChunkResult<T[]>> {\n const dataWithChunks = data as DataWithChunkInfo;\n if (!dataWithChunks || !dataWithChunks.chunk_info) {\n throw new Error('Response does not contain chunk_info');\n }\n\n const chunkInfo = dataWithChunks.chunk_info as ChunkInfo;\n const totalChunks = chunkInfo.chunk_file_names.length;\n const start = options.start || 0;\n const limit = options.limit || totalChunks - start;\n const end = Math.min(start + limit, totalChunks);\n\n if (start < 0 || start >= totalChunks) {\n throw new Error(`Invalid start index: ${start} (Total chunks: ${totalChunks})`);\n }\n\n const chunkPromises: Promise<ChunkResult<T[]>>[] = [];\n for (let i = start; i < end; i++) {\n chunkPromises.push(this.getChunk<T>(data, i));\n }\n\n const results = await Promise.all(chunkPromises);\n const mergedData = results.flatMap((r) => r.data);\n const totalSize = results.reduce((sum, r) => sum + r.metadata.sizeBytes, 0);\n const totalDuration = results.reduce((sum, r) => sum + r.metadata.fetchTimeMs, 0);\n\n return {\n data: mergedData,\n metadata: {\n sizeBytes: totalSize,\n fetchTimeMs: totalDuration,\n },\n };\n }\n}\n","declare const __VERSION__: string;\nexport const VERSION = __VERSION__;\n"],"mappings":"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","names":["chunkMetadata"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/auth/PKCEHelper.ts","../src/errors.ts","../src/auth/AuthManager.ts","../src/client.ts","../src/version.ts"],"sourcesContent":["export * from './client.js';\nexport * from './auth/AuthManager.js';\nexport * from './auth/PKCEHelper.js';\nexport * from './errors.js';\nexport * from './version.js';\n","export class PKCEHelper {\n /**\n * Generates a random string for the code verifier.\n * @param length Length of the string (43-128 characters recommended)\n */\n static generateVerifier(length: number = 128): string {\n const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n let result = '';\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n const values = new Uint8Array(length);\n crypto.getRandomValues(values);\n for (let i = 0; i < length; i++) {\n result += charset[values[i] % charset.length];\n }\n } else {\n // Fallback for environments without crypto.getRandomValues (though Node 18+ and browsers have it)\n for (let i = 0; i < length; i++) {\n result += charset.charAt(Math.floor(Math.random() * charset.length));\n }\n }\n return result;\n }\n\n /**\n * Generates the code challenge from the verifier using SHA-256.\n * @param verifier The code verifier string\n */\n static async generateChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n\n if (typeof crypto !== 'undefined' && crypto.subtle) {\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n return this.base64URLEncode(hashBuffer);\n } else {\n // Fallback for insecure contexts or environments without crypto.subtle\n const hashBuffer = this.sha256(data);\n return this.base64URLEncode(hashBuffer);\n }\n }\n\n private static base64URLEncode(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.byteLength; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n const base64 = btoa(binary);\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n }\n\n private static sha256(data: Uint8Array): ArrayBuffer {\n const K = [\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,\n 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,\n 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,\n 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,\n 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,\n 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,\n 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,\n 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,\n 0xc67178f2,\n ];\n\n function rotr(n: number, x: number) {\n return (x >>> n) | (x << (32 - n));\n }\n function ch(x: number, y: number, z: number) {\n return (x & y) ^ (~x & z);\n }\n function maj(x: number, y: number, z: number) {\n return (x & y) ^ (x & z) ^ (y & z);\n }\n function sigma0(x: number) {\n return rotr(2, x) ^ rotr(13, x) ^ rotr(22, x);\n }\n function sigma1(x: number) {\n return rotr(6, x) ^ rotr(11, x) ^ rotr(25, x);\n }\n function gamma0(x: number) {\n return rotr(7, x) ^ rotr(18, x) ^ (x >>> 3);\n }\n function gamma1(x: number) {\n return rotr(17, x) ^ rotr(19, x) ^ (x >>> 10);\n }\n\n const bytes = new Uint8Array(data);\n const len = bytes.length * 8;\n\n // Padding\n const paddingLen = (((len + 64) >>> 9) << 4) + 16;\n const words = new Uint32Array(paddingLen);\n for (let i = 0; i < bytes.length; i++) words[i >>> 2] |= bytes[i] << (24 - (i % 4) * 8);\n words[bytes.length >>> 2] |= 0x80 << (24 - (bytes.length % 4) * 8);\n words[paddingLen - 1] = len;\n\n const H = [\n 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,\n 0x5be0cd19,\n ];\n\n const W = new Uint32Array(64);\n for (let i = 0; i < words.length; i += 16) {\n W.fill(0);\n for (let j = 0; j < 16; j++) W[j] = words[i + j];\n for (let j = 16; j < 64; j++)\n W[j] = (gamma1(W[j - 2]) + W[j - 7] + gamma0(W[j - 15]) + W[j - 16]) | 0;\n\n let [a, b, c, d, e, f, g, h] = H;\n\n for (let j = 0; j < 64; j++) {\n const T1 = (h + sigma1(e) + ch(e, f, g) + K[j] + W[j]) | 0;\n const T2 = (sigma0(a) + maj(a, b, c)) | 0;\n h = g;\n g = f;\n f = e;\n e = (d + T1) | 0;\n d = c;\n c = b;\n b = a;\n a = (T1 + T2) | 0;\n }\n\n H[0] = (H[0] + a) | 0;\n H[1] = (H[1] + b) | 0;\n H[2] = (H[2] + c) | 0;\n H[3] = (H[3] + d) | 0;\n H[4] = (H[4] + e) | 0;\n H[5] = (H[5] + f) | 0;\n H[6] = (H[6] + g) | 0;\n H[7] = (H[7] + h) | 0;\n }\n\n const buffer = new ArrayBuffer(32);\n const view = new DataView(buffer);\n H.forEach((h, i) => view.setUint32(i * 4, h, false));\n return buffer;\n }\n}\n","export class IRacingAPIError extends Error {\n constructor(\n message: string,\n public status: number,\n public statusText: string,\n public body?: unknown,\n ) {\n super(message);\n this.name = 'IRacingAPIError';\n }\n}\n","import { PKCEHelper } from './PKCEHelper.js';\nimport { IRacingAPIError } from '../errors.js';\n\ninterface TokenResponse {\n access_token: string;\n expires_in: number; // seconds\n token_type: string;\n refresh_token?: string; // Not always returned?\n // Add other fields if necessary\n}\n\nexport interface AuthConfig {\n clientId: string; // For OAuth\n redirectUri: string; // For OAuth\n}\n\nexport interface TokenStore {\n getAccessToken(): string | null;\n setAccessToken(token: string): void;\n getRefreshToken(): string | null;\n setRefreshToken(token: string): void;\n clear(): void;\n}\n\nclass InMemoryTokenStore implements TokenStore {\n private accessToken: string | null = null;\n private refreshToken: string | null = null;\n\n getAccessToken() {\n return this.accessToken;\n }\n setAccessToken(token: string) {\n this.accessToken = token;\n }\n getRefreshToken() {\n return this.refreshToken;\n }\n setRefreshToken(token: string) {\n this.refreshToken = token;\n }\n clear() {\n this.accessToken = null;\n this.refreshToken = null;\n }\n}\n\nclass LocalStorageTokenStore implements TokenStore {\n private prefix = 'irdata_';\n getAccessToken() {\n return localStorage.getItem(this.prefix + 'access_token');\n }\n setAccessToken(token: string) {\n localStorage.setItem(this.prefix + 'access_token', token);\n }\n getRefreshToken() {\n return localStorage.getItem(this.prefix + 'refresh_token');\n }\n setRefreshToken(token: string) {\n localStorage.setItem(this.prefix + 'refresh_token', token);\n }\n clear() {\n localStorage.removeItem(this.prefix + 'access_token');\n localStorage.removeItem(this.prefix + 'refresh_token');\n }\n}\n\nexport class AuthManager {\n private tokenStore: TokenStore;\n private config: AuthConfig;\n private authBaseUrl: string;\n private tokenEndpoint?: string;\n private pkceVerifier: string | null = null;\n\n constructor(\n config: AuthConfig,\n proxySettings: { authBaseUrl?: string; tokenEndpoint?: string } = {},\n ) {\n this.config = config;\n this.authBaseUrl = proxySettings.authBaseUrl || 'https://oauth.iracing.com/oauth2';\n this.tokenEndpoint = proxySettings.tokenEndpoint;\n\n if (typeof window !== 'undefined' && window.localStorage) {\n this.tokenStore = new LocalStorageTokenStore();\n } else {\n this.tokenStore = new InMemoryTokenStore();\n }\n }\n\n get accessToken(): string | null {\n return this.tokenStore.getAccessToken();\n }\n\n get refreshToken(): string | null {\n return this.tokenStore.getRefreshToken();\n }\n\n /**\n * Manually sets the session tokens.\n * Useful for loading a saved session from external storage.\n */\n setSession(accessToken: string, refreshToken?: string) {\n this.tokenStore.setAccessToken(accessToken);\n if (refreshToken) {\n this.tokenStore.setRefreshToken(refreshToken);\n }\n }\n\n /**\n * Returns true if an access token is present.\n */\n get isLoggedIn(): boolean {\n return !!this.accessToken;\n }\n\n /**\n * Comprehensive method to establish a session.\n *\n * Automatically handles:\n * 1. Existing valid sessions (returns true immediately).\n * 2. OAuth Callback handling (exchanges code for token).\n * 3. Session restoration (uses Refresh Token).\n *\n * @returns Promise<boolean> - true if authenticated, false otherwise.\n */\n async handleAuthentication(): Promise<boolean> {\n // 1. Check if we already have a token\n if (this.isLoggedIn) {\n return true;\n }\n\n // 2. Check for OAuth callback (code in URL)\n try {\n await this.handleCallback();\n if (this.isLoggedIn) {\n // success! clean up the URL to avoid re-submitting the code on refresh\n if (typeof window !== 'undefined' && window.history && window.history.replaceState) {\n const url = new URL(window.location.href);\n url.searchParams.delete('code');\n // We also remove 'iss' or 'state' if they exist usually, but 'code' is the critical one.\n window.history.replaceState({}, document.title, url.toString());\n }\n return true;\n }\n } catch (error) {\n console.warn('Authentication: Callback exchange failed', error);\n // Continue to try refresh token...\n }\n\n // 3. Try to refresh the session using a stored refresh token\n return await this.refreshAccessToken();\n }\n\n getAuthHeaders(): HeadersInit {\n const headers: HeadersInit = {};\n const token = this.tokenStore.getAccessToken();\n\n if (token) {\n // OAuth2 Bearer Token\n headers['Authorization'] = `Bearer ${token}`;\n }\n return headers;\n }\n\n // --- Browser OAuth2 PKCE ---\n\n async generateAuthUrl(): Promise<string> {\n if (!this.config.clientId || !this.config.redirectUri) {\n throw new Error('clientId and redirectUri required for OAuth');\n }\n\n const verifier = PKCEHelper.generateVerifier();\n const challenge = await PKCEHelper.generateChallenge(verifier);\n\n // Store verifier for the callback\n if (typeof window !== 'undefined' && window.sessionStorage) {\n window.sessionStorage.setItem('irdata_pkce_verifier', verifier);\n } else {\n this.pkceVerifier = verifier;\n }\n\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: this.config.clientId,\n redirect_uri: this.config.redirectUri,\n scope: 'iracing.auth', // Required based iRacing docs\n code_challenge: challenge,\n code_challenge_method: 'S256',\n });\n\n return `${this.authBaseUrl}/authorize?${params.toString()}`;\n }\n\n async handleCallback(codeOrUrl?: string): Promise<void> {\n let input = codeOrUrl;\n\n // In a browser, default to the current URL if no input is provided\n if (!input && typeof window !== 'undefined') {\n input = window.location.href;\n }\n\n if (!input) {\n return;\n }\n\n let code = input;\n\n // if the user passes a full URL, extract the code\n if (input.includes('code=') || input.startsWith('http')) {\n try {\n const url = new URL(input);\n const extractedCode = url.searchParams.get('code');\n if (extractedCode) {\n code = extractedCode;\n } else if (input.startsWith('http')) {\n // It's a URL but no code found.\n return;\n }\n } catch {\n // Not a valid URL, assume it's the code itself or handled below\n // Fallback: try to extract code via regex if URL parsing failed\n const match = input.match(/[?&]code=([\\w-]+)/);\n if (match) {\n code = match[1];\n }\n }\n }\n\n let verifier = '';\n if (typeof window !== 'undefined' && window.sessionStorage) {\n verifier = window.sessionStorage.getItem('irdata_pkce_verifier') || '';\n window.sessionStorage.removeItem('irdata_pkce_verifier');\n }\n\n if (!verifier && this.pkceVerifier) {\n verifier = this.pkceVerifier;\n this.pkceVerifier = null;\n }\n\n if (!verifier) {\n throw new Error('No PKCE verifier found');\n }\n\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: this.config.clientId!,\n redirect_uri: this.config.redirectUri!,\n code: code,\n code_verifier: verifier,\n });\n\n const tokenUrl = this.tokenEndpoint || `${this.authBaseUrl}/token`;\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n let errorBody: unknown;\n try {\n errorBody = await response.json();\n } catch (_e) {\n try {\n errorBody = await response.text();\n } catch (_e2) {\n /* ignore */\n }\n }\n throw new IRacingAPIError(\n `Failed to exchange code for token: ${response.status} ${response.statusText}`,\n response.status,\n response.statusText,\n errorBody,\n );\n }\n\n const tokens: TokenResponse = await response.json();\n this.tokenStore.setAccessToken(tokens.access_token);\n if (tokens.refresh_token) {\n this.tokenStore.setRefreshToken(tokens.refresh_token);\n }\n }\n\n async refreshAccessToken(): Promise<boolean> {\n const refreshToken = this.tokenStore.getRefreshToken();\n if (!refreshToken) {\n return false;\n }\n\n if (!this.config.clientId) {\n throw new Error('clientId required for token refresh');\n }\n\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: this.config.clientId,\n refresh_token: refreshToken,\n });\n\n const tokenUrl = this.tokenEndpoint || `${this.authBaseUrl}/token`;\n\n try {\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n // If refresh fails (e.g. token expired), clear tokens to force re-login\n this.tokenStore.clear();\n return false;\n }\n\n const tokens: TokenResponse = await response.json();\n this.tokenStore.setAccessToken(tokens.access_token);\n // Update refresh token if a new one is returned\n if (tokens.refresh_token) {\n this.tokenStore.setRefreshToken(tokens.refresh_token);\n }\n return true;\n } catch (error) {\n console.error('Error refreshing token:', error);\n return false;\n }\n }\n\n logout() {\n this.tokenStore.clear();\n }\n}\n","import { AuthManager, AuthConfig } from './auth/AuthManager.js';\nimport { IRacingAPIError } from './errors.js';\n\nexport interface ProxyConfig {\n /**\n * The base URL for API requests.\n * **Required** when using a proxy.\n */\n apiUrl: string;\n /**\n * A proxy URL for fetching S3 files.\n * **Required** when using a proxy.\n */\n fileProxyUrl: string;\n /**\n * The specific endpoint for token exchange.\n * **Required** when using a proxy.\n */\n tokenEndpoint: string;\n /**\n * Optional base URL for OAuth authorization.\n * Defaults to 'https://oauth.iracing.com/oauth2' if not provided.\n */\n authBaseUrl?: string;\n}\n\nexport interface ChunkInfo {\n chunk_size: number;\n num_chunks: number;\n rows: number;\n base_download_url: string;\n chunk_file_names: string[];\n}\n\ninterface DataWithChunkInfo {\n chunk_info?: ChunkInfo;\n}\n\nexport interface DataResult<T> {\n data: T;\n metadata: {\n s3LinkFollowed: boolean;\n chunkCount: number;\n chunkRows?: number;\n sizeBytes: number;\n fetchTimeMs: number;\n contentType?: string;\n };\n}\n\nexport interface ChunkResult<T> {\n data: T;\n metadata: {\n sizeBytes: number;\n fetchTimeMs: number;\n contentType?: string;\n };\n}\n\nexport class IRacingClient {\n public auth: AuthManager;\n private apiUrl: string;\n private fileProxyUrl?: string;\n\n constructor(authConfig: AuthConfig, proxyConfig?: ProxyConfig) {\n this.apiUrl = proxyConfig?.apiUrl || 'https://members-ng.iracing.com/data';\n this.fileProxyUrl = proxyConfig?.fileProxyUrl;\n this.auth = new AuthManager(authConfig, proxyConfig);\n }\n\n private calculateSize(response: Response, data: unknown): number {\n const cl = response.headers.get('content-length');\n if (cl) return parseInt(cl, 10);\n\n if (typeof data === 'string') {\n return new TextEncoder().encode(data).length;\n }\n\n try {\n return new TextEncoder().encode(JSON.stringify(data)).length;\n } catch {\n return 0;\n }\n }\n\n /**\n * Processes a fetch response based on its Content-Type.\n */\n private async processResponse<T>(\n response: Response,\n ): Promise<{ data: T; contentType: string | null }> {\n let contentType = response.headers.get('content-type');\n let data: T;\n\n if (contentType) {\n const lowerContentType = contentType.toLowerCase();\n // NOTE: iRacing's chunks return with 'application/octet-stream' as Content Type\n if (\n lowerContentType.includes('application/json') ||\n lowerContentType.includes('application/octet-stream')\n ) {\n data = await response.json();\n if (lowerContentType.includes('application/octet-stream')) {\n contentType = contentType.replace(/application\\/octet-stream/i, 'application/json');\n }\n } else {\n data = (await response.text()) as unknown as T;\n }\n } else {\n // If no content type, try JSON, fall back to text\n const clone = response.clone();\n try {\n data = await response.json();\n contentType = 'application/json';\n } catch {\n data = (await clone.text()) as unknown as T;\n contentType = 'text/plain';\n }\n }\n\n return { data, contentType };\n }\n\n /**\n * Internal method to perform request and return data + size.\n */\n private async requestInternal<T>(\n endpoint: string,\n options: RequestInit = {},\n ): Promise<{ data: T; sizeBytes: number; duration: number; contentType: string | null }> {\n const startTime = Date.now();\n // Remove leading slash from endpoint if present\n const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;\n const cleanApiUrl = this.apiUrl.endsWith('/') ? this.apiUrl : `${this.apiUrl}/`;\n const url = `${cleanApiUrl}${cleanEndpoint}`;\n\n const headers = this.auth.getAuthHeaders();\n\n const mergedOptions: RequestInit = {\n ...options,\n headers: {\n ...headers,\n ...options.headers,\n 'Content-Type': 'application/json',\n },\n };\n\n let response = await fetch(url, mergedOptions);\n\n if (!response.ok) {\n if (response.status === 401) {\n // Try to refresh token\n try {\n const refreshed = await this.auth.refreshAccessToken();\n if (refreshed) {\n // Retry request with new token\n const newHeaders = this.auth.getAuthHeaders();\n const retryOptions: RequestInit = {\n ...options,\n headers: {\n ...newHeaders,\n ...options.headers,\n 'Content-Type': 'application/json',\n },\n };\n\n response = await fetch(url, retryOptions);\n }\n } catch (refreshError) {\n console.error('Token refresh failed during request retry:', refreshError);\n }\n }\n\n if (!response.ok) {\n return this.handleErrorResponse(response);\n }\n }\n\n const { data, contentType } = await this.processResponse<T>(response);\n const duration = Date.now() - startTime;\n const sizeBytes = this.calculateSize(response, data);\n return { data, sizeBytes, duration, contentType };\n }\n\n /**\n * Performs a fetch request with authentication headers.\n * Does NOT automatically follow \"link\" responses.\n */\n async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const { data } = await this.requestInternal<T>(endpoint, options);\n return data;\n }\n\n private async handleErrorResponse(response: Response): Promise<never> {\n let body: unknown;\n try {\n const { data } = await this.processResponse<unknown>(response);\n body = data;\n } catch (_e) {\n // ignore\n }\n\n throw new IRacingAPIError(\n `API Request failed: ${response.status} ${response.statusText}`,\n response.status,\n response.statusText,\n body,\n );\n }\n\n /**\n * Helper to fetch data from an external URL (e.g. S3), handling the file proxy if configured.\n */\n private async fetchExternal<T>(\n url: string,\n ): Promise<{ data: T; sizeBytes: number; duration: number; contentType: string | null }> {\n const startTime = Date.now();\n let fetchUrl = url;\n if (this.fileProxyUrl) {\n // If a file proxy is configured, use it\n const separator = this.fileProxyUrl.includes('?') ? '&' : '?';\n fetchUrl = `${this.fileProxyUrl}${separator}url=${encodeURIComponent(url)}`;\n }\n\n const response = await fetch(fetchUrl);\n if (!response.ok) {\n return this.handleErrorResponse(response);\n }\n\n const { data, contentType } = await this.processResponse<T>(response);\n const duration = Date.now() - startTime;\n const sizeBytes = this.calculateSize(response, data);\n return { data, sizeBytes, duration, contentType };\n }\n\n /**\n * Fetches data from an endpoint, automatically following any S3 links returned.\n * Returns metadata about the operation.\n */\n async getData<T>(endpoint: string): Promise<DataResult<T>> {\n const {\n data: initialData,\n sizeBytes: initialSize,\n duration: initialDuration,\n contentType: initialContentType,\n } = await this.requestInternal<T>(endpoint);\n\n // Check if the response contains a generic link to S3 and follow it\n if (\n initialData &&\n typeof initialData === 'object' &&\n 'link' in initialData &&\n typeof (initialData as { link?: unknown }).link === 'string'\n ) {\n const s3Link = (initialData as { link: string }).link;\n if (s3Link.startsWith('http')) {\n const {\n data: externalData,\n sizeBytes: externalSize,\n duration: externalDuration,\n contentType: externalContentType,\n } = await this.fetchExternal<T>(s3Link);\n\n const chunkMetadata = this.extractChunkMetadata(externalData);\n\n return {\n data: externalData,\n metadata: {\n s3LinkFollowed: true,\n chunkCount: chunkMetadata.chunkCount,\n chunkRows: chunkMetadata.chunkRows,\n sizeBytes: externalSize,\n fetchTimeMs: initialDuration + externalDuration,\n contentType: externalContentType || undefined,\n },\n };\n }\n }\n\n const chunkMetadata = this.extractChunkMetadata(initialData);\n\n return {\n data: initialData,\n metadata: {\n s3LinkFollowed: false,\n chunkCount: chunkMetadata.chunkCount,\n chunkRows: chunkMetadata.chunkRows,\n sizeBytes: initialSize,\n fetchTimeMs: initialDuration,\n contentType: initialContentType || undefined,\n },\n };\n }\n\n private extractChunkMetadata(data: unknown): { chunkCount: number; chunkRows?: number } {\n if (data && typeof data === 'object' && 'chunk_info' in data) {\n const chunkInfo = (data as DataWithChunkInfo).chunk_info;\n if (chunkInfo) {\n return {\n chunkCount: typeof chunkInfo.num_chunks === 'number' ? chunkInfo.num_chunks : 0,\n chunkRows: typeof chunkInfo.rows === 'number' ? chunkInfo.rows : undefined,\n };\n }\n }\n return { chunkCount: 0 };\n }\n\n /**\n * Fetches a specific chunk from a response containing chunk_info.\n *\n * @param data The response object containing chunk_info\n * @param chunkIndex The index of the chunk to fetch (0-based)\n * @returns The content of the chunk and metadata\n */\n async getChunk<T>(data: unknown, chunkIndex: number): Promise<ChunkResult<T[]>> {\n const dataWithChunks = data as DataWithChunkInfo;\n if (!dataWithChunks || !dataWithChunks.chunk_info) {\n throw new Error('Response does not contain chunk_info');\n }\n\n const chunkInfo = dataWithChunks.chunk_info;\n const { base_download_url, chunk_file_names } = chunkInfo;\n\n if (\n !base_download_url ||\n !Array.isArray(chunk_file_names) ||\n chunkIndex < 0 ||\n chunkIndex >= chunk_file_names.length\n ) {\n throw new Error(\n `Invalid chunk index: ${chunkIndex} (Total chunks: ${chunk_file_names?.length || 0})`,\n );\n }\n\n const chunkUrl = `${base_download_url}${chunk_file_names[chunkIndex]}`;\n const {\n data: chunkData,\n sizeBytes,\n duration,\n contentType,\n } = await this.fetchExternal<T[]>(chunkUrl);\n return {\n data: chunkData,\n metadata: {\n sizeBytes,\n fetchTimeMs: duration,\n contentType: contentType || undefined,\n },\n };\n }\n\n /**\n * Fetches multiple chunks and merges the results into a single array.\n *\n * @param data The response object containing chunk_info\n * @param options Options for fetching chunks (start index, limit count)\n * @returns A merged array of data from the requested chunks and total size\n */\n async getChunks<T>(\n data: unknown,\n options: { start?: number; limit?: number } = {},\n ): Promise<ChunkResult<T[]>> {\n const dataWithChunks = data as DataWithChunkInfo;\n if (!dataWithChunks || !dataWithChunks.chunk_info) {\n throw new Error('Response does not contain chunk_info');\n }\n\n const chunkInfo = dataWithChunks.chunk_info as ChunkInfo;\n const totalChunks = chunkInfo.chunk_file_names.length;\n const start = options.start || 0;\n const limit = options.limit || totalChunks - start;\n const end = Math.min(start + limit, totalChunks);\n\n if (start < 0 || start >= totalChunks) {\n throw new Error(`Invalid start index: ${start} (Total chunks: ${totalChunks})`);\n }\n\n const chunkPromises: Promise<ChunkResult<T[]>>[] = [];\n for (let i = start; i < end; i++) {\n chunkPromises.push(this.getChunk<T>(data, i));\n }\n\n const results = await Promise.all(chunkPromises);\n const mergedData = results.flatMap((r) => r.data);\n const totalSize = results.reduce((sum, r) => sum + r.metadata.sizeBytes, 0);\n const totalDuration = results.reduce((sum, r) => sum + r.metadata.fetchTimeMs, 0);\n\n return {\n data: mergedData,\n metadata: {\n sizeBytes: totalSize,\n fetchTimeMs: totalDuration,\n },\n };\n }\n}\n","declare const __VERSION__: string;\nexport const VERSION = __VERSION__;\n"],"mappings":"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","names":["chunkMetadata"]}
|