quickbase-js 0.1.0-beta.10 → 0.1.0-beta.11
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/CHANGELOG.md +9 -0
- package/README.md +49 -9
- package/dist/esm/quickbase.d.ts +1 -0
- package/dist/esm/quickbase.js +60 -64
- package/dist/esm/quickbase.js.map +1 -1
- package/dist/esm/quickbase.min.js +1 -1
- package/dist/esm/quickbase.min.js.map +1 -1
- package/dist/umd/quickbase.umd.js +60 -64
- package/dist/umd/quickbase.umd.js.map +1 -1
- package/dist/umd/quickbase.umd.min.js +1 -1
- package/dist/umd/quickbase.umd.min.js.map +1 -1
- package/examples/quicbase-jsCDN.html +2 -2
- package/package.json +1 -1
- package/src/authorizationStrategy.ts +62 -35
- package/src/generated-unified/QuickbaseClient.ts +1 -1
- package/src/invokeMethod.ts +23 -63
- package/src/quickbaseClient.ts +9 -4
- package/src/tokenCache.ts +4 -0
- package/tests/playwright/qb/auth/getAppWithTempTokenConcurrency.test.ts +208 -0
- package/tests/vitest/qb/auth/ssoTokenConcurrency.test.ts +68 -0
- package/tests/vitest/qb/auth/userTokenConcurrency.test.ts +63 -0
- package/tests/vitest/unit/apps/getAppConcurrency.test.ts +151 -0
- package/tests/vitest/unit/auth/fetchTempToken401.test.ts +20 -2
- package/tests/vitest/unit/auth/userTokenConcurrency.test.ts +62 -0
- package/tests/vitest/unit/index.ts +1 -1
- package/tests/vitest/unit/quickbaseClientHttpMethods.test.ts +3 -3
- package/tools/log-gen.ts +1 -1
- package/tools/log-gen.yaml +130 -78
- package/api-examples.md +0 -49
- package/markdown-examples.md +0 -85
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [0.1.0-beta.11](https://github.com/DrewBradfordXYZ/quickbase-js/compare/v0.1.0-beta.10...v0.1.0-beta.11) (2025-03-23)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* only fetch auth dbid once for concurrent requests. temp tokens and SSO ([12f7f31](https://github.com/DrewBradfordXYZ/quickbase-js/commit/12f7f3117b2fd6e5db35d73e945dff0a79ef0481))
|
|
11
|
+
* SSO concurrency tests passes ([caf5fca](https://github.com/DrewBradfordXYZ/quickbase-js/commit/caf5fca4e86b0dda4a75c2856f9324634369f15a))
|
|
12
|
+
* temp token concurrency prevents redundant token fetches. getAppConcurrency.test.ts unit test passes ([2913e7b](https://github.com/DrewBradfordXYZ/quickbase-js/commit/2913e7b38d38ca0ceaa0af21b2e872f7bafb9d99))
|
|
13
|
+
|
|
5
14
|
## [0.1.0-beta.10](https://github.com/DrewBradfordXYZ/quickbase-js/compare/v0.1.0-beta.9...v0.1.0-beta.10) (2025-03-22)
|
|
6
15
|
|
|
7
16
|
|
package/README.md
CHANGED
|
@@ -1,22 +1,60 @@
|
|
|
1
1
|
# quickbase-js
|
|
2
2
|
|
|
3
|
-
A Typescript
|
|
3
|
+
A Typescript API client for the [Quickbase JSON RESTful API](https://developer.quickbase.com/).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Support for various authentication strategies (`user token`, `temporary tokens`, `SSO token`), `rate limiting`, `429` and `401` error handling retries and automatic `date` conversion. It uses a Proxy to dynamically invoke API methods derived from the OpenAPI specification.
|
|
6
|
+
|
|
7
|
+
Browser and Node.js support. ESM and UMD builds are available.
|
|
8
|
+
|
|
9
|
+
Authentication methods are handled in an opinionated manner. See configuration options for details and available settings.
|
|
6
10
|
|
|
7
11
|
## API Documentation
|
|
8
12
|
|
|
9
|
-
https://quickbase-js.netlify.app/
|
|
13
|
+
<a href="https://quickbase-js.netlify.app/" target="_blank"> Documentation</a> for quickbase-js.
|
|
10
14
|
|
|
11
15
|
## Installation
|
|
12
16
|
|
|
17
|
+
#### Node.js install
|
|
18
|
+
|
|
13
19
|
```bash
|
|
14
20
|
npm install --save quickbase-js
|
|
15
21
|
```
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
#### CDN install.
|
|
24
|
+
|
|
25
|
+
Find and select the latest version. UMD is recommended for code pages.
|
|
26
|
+
|
|
27
|
+
<a href="https://www.jsdelivr.com/package/npm/quickbase-js?tab=files" target="_blank">CDN Files</a> for quickbase-js
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<!DOCTYPE html>
|
|
31
|
+
<html>
|
|
32
|
+
<head>
|
|
33
|
+
<title>Quickbase</title>
|
|
34
|
+
<!-- Replace xx.xx.xx with the latest version from CDN Files-->
|
|
35
|
+
<script src="https://cdn.jsdelivr.net/npm/quickbase-js@xx.xx.xx/dist/umd/quickbase.umd.min.js"></script>
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<script>
|
|
39
|
+
const realm = ""; // Replace with your realm
|
|
40
|
+
const appId = ""; // Replace with your app ID
|
|
41
|
+
|
|
42
|
+
const qb = QuickbaseJS.quickbase({
|
|
43
|
+
realm,
|
|
44
|
+
useTempTokens: true,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
qb.getApp({ appId })
|
|
48
|
+
.then((app) => console.log(app.name))
|
|
49
|
+
.catch((err) => console.log(err.message));
|
|
50
|
+
</script>
|
|
51
|
+
</body>
|
|
52
|
+
</html>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Authentication Examples
|
|
18
56
|
|
|
19
|
-
###
|
|
57
|
+
### User Token
|
|
20
58
|
|
|
21
59
|
```javascript
|
|
22
60
|
import { quickbase } from "quickbase-js"; // Adjust the import path as needed
|
|
@@ -45,7 +83,7 @@ const getAppDetails = async () => {
|
|
|
45
83
|
getAppDetails();
|
|
46
84
|
```
|
|
47
85
|
|
|
48
|
-
###
|
|
86
|
+
### Temporary Tokens (Code Pages)
|
|
49
87
|
|
|
50
88
|
```javascript
|
|
51
89
|
import { quickbase } from "quickbase-js"; // Adjust the import path as needed
|
|
@@ -73,7 +111,7 @@ const getAppDetails = async () => {
|
|
|
73
111
|
getAppDetails();
|
|
74
112
|
```
|
|
75
113
|
|
|
76
|
-
###
|
|
114
|
+
### SSO Token
|
|
77
115
|
|
|
78
116
|
```javascript
|
|
79
117
|
import { quickbase } from "quickbase-js"; // Adjust the import path as needed
|
|
@@ -117,14 +155,14 @@ _User options:_
|
|
|
117
155
|
|
|
118
156
|
_Advanced user options:_
|
|
119
157
|
|
|
120
|
-
- **`tempTokenLifespan`** (`number`, optional): The lifespan (in milliseconds) of temporary tokens in the cache. Defaults to 4 minutes 50 seconds (290000 ms).
|
|
121
|
-
|
|
122
158
|
- **`throttle`** (`{ rate: number; burst: number }`, optional): Configures rate limiting, where `rate` is requests per second and `burst` is the maximum burst of requests. Defaults to `{ rate: 10, burst: 10 }`.
|
|
123
159
|
|
|
124
160
|
- **`maxRetries`** (`number`, optional): The maximum number of retries for failed requests. Defaults to `3`.
|
|
125
161
|
|
|
126
162
|
- **`retryDelay`** (`number`, optional): The base delay (in milliseconds) between retries, which increases exponentially. Defaults to `1000`.
|
|
127
163
|
|
|
164
|
+
- **`tempTokenLifespan`** (`number`, optional): The lifespan (in milliseconds) of temporary tokens in the cache. Defaults to 4 minutes 50 seconds (290000 ms).
|
|
165
|
+
|
|
128
166
|
- **`convertDates`** (`boolean`, optional): Converts ISO date strings to `Date` objects in responses. Defaults to `true`.
|
|
129
167
|
|
|
130
168
|
_Overrides and development options:_
|
|
@@ -149,6 +187,8 @@ _Overrides and development options:_
|
|
|
149
187
|
|
|
150
188
|
- **`tokenCache`**: Temporary tokens are cached with their dbid with a `tempTokenLifespan`. This is automatically setup for you when `useTempTokens` is enabled.
|
|
151
189
|
|
|
190
|
+
---
|
|
191
|
+
|
|
152
192
|
### Development workflow
|
|
153
193
|
|
|
154
194
|
```bash
|
package/dist/esm/quickbase.d.ts
CHANGED
package/dist/esm/quickbase.js
CHANGED
|
@@ -7348,6 +7348,9 @@ class TokenCache {
|
|
|
7348
7348
|
expiresAt: now + (lifespan || this.tempTokenLifespan),
|
|
7349
7349
|
});
|
|
7350
7350
|
}
|
|
7351
|
+
delete(dbid) {
|
|
7352
|
+
this.cache.delete(dbid);
|
|
7353
|
+
}
|
|
7351
7354
|
clear() {
|
|
7352
7355
|
this.cache.clear();
|
|
7353
7356
|
}
|
|
@@ -7402,14 +7405,13 @@ function extractHttpMethod(method) {
|
|
|
7402
7405
|
return match ? match[1].toUpperCase() : "GET"; // Fallback to GET if no match
|
|
7403
7406
|
}
|
|
7404
7407
|
|
|
7405
|
-
// src/authorizationStrategy.ts
|
|
7406
|
-
// src/authorizationStrategy.ts (partial update)
|
|
7407
7408
|
class TempTokenStrategy {
|
|
7408
7409
|
tokenCache;
|
|
7409
7410
|
initialTempToken;
|
|
7410
7411
|
fetchApi;
|
|
7411
7412
|
realm;
|
|
7412
7413
|
baseUrl;
|
|
7414
|
+
pendingFetches = new Map();
|
|
7413
7415
|
constructor(tokenCache, initialTempToken, fetchApi, realm, baseUrl = "https://api.quickbase.com/v1") {
|
|
7414
7416
|
this.tokenCache = tokenCache;
|
|
7415
7417
|
this.initialTempToken = initialTempToken;
|
|
@@ -7444,7 +7446,13 @@ class TempTokenStrategy {
|
|
|
7444
7446
|
async getToken(dbid) {
|
|
7445
7447
|
let token = this.tokenCache.get(dbid) || this.initialTempToken;
|
|
7446
7448
|
if (!token && dbid) {
|
|
7447
|
-
|
|
7449
|
+
if (this.pendingFetches.has(dbid)) {
|
|
7450
|
+
console.log(`[getToken] Waiting for existing fetch for dbid: ${dbid}`);
|
|
7451
|
+
return this.pendingFetches.get(dbid);
|
|
7452
|
+
}
|
|
7453
|
+
const fetchPromise = this.fetchTempToken(dbid).finally(() => this.pendingFetches.delete(dbid));
|
|
7454
|
+
this.pendingFetches.set(dbid, fetchPromise);
|
|
7455
|
+
token = await fetchPromise;
|
|
7448
7456
|
}
|
|
7449
7457
|
return token;
|
|
7450
7458
|
}
|
|
@@ -7464,15 +7472,29 @@ class TempTokenStrategy {
|
|
|
7464
7472
|
}
|
|
7465
7473
|
if (debug)
|
|
7466
7474
|
console.log(`Refreshing temp token for dbid: ${dbid}`);
|
|
7467
|
-
|
|
7468
|
-
this.tokenCache.
|
|
7469
|
-
|
|
7475
|
+
// Invalidate the cache for this dbid to force a fresh fetch
|
|
7476
|
+
this.tokenCache.delete(dbid);
|
|
7477
|
+
try {
|
|
7478
|
+
const newToken = await this.getToken(dbid);
|
|
7479
|
+
if (newToken) {
|
|
7480
|
+
this.tokenCache.set(dbid, newToken);
|
|
7481
|
+
return newToken;
|
|
7482
|
+
}
|
|
7483
|
+
return null;
|
|
7484
|
+
}
|
|
7485
|
+
catch (error) {
|
|
7486
|
+
if (debug)
|
|
7487
|
+
console.log(`[${methodName || "method"}] Failed to refresh token:`, error);
|
|
7488
|
+
throw error;
|
|
7489
|
+
}
|
|
7470
7490
|
}
|
|
7471
7491
|
}
|
|
7472
7492
|
class UserTokenStrategy {
|
|
7473
7493
|
userToken;
|
|
7474
|
-
|
|
7494
|
+
baseUrl;
|
|
7495
|
+
constructor(userToken, baseUrl = "https://api.quickbase.com/v1") {
|
|
7475
7496
|
this.userToken = userToken;
|
|
7497
|
+
this.baseUrl = baseUrl;
|
|
7476
7498
|
}
|
|
7477
7499
|
async getToken(_dbid) {
|
|
7478
7500
|
return this.userToken;
|
|
@@ -7493,21 +7515,31 @@ class SsoTokenStrategy {
|
|
|
7493
7515
|
realm;
|
|
7494
7516
|
fetchApi;
|
|
7495
7517
|
debug;
|
|
7518
|
+
baseUrl;
|
|
7496
7519
|
currentToken;
|
|
7497
|
-
|
|
7520
|
+
pendingFetches = new Map();
|
|
7521
|
+
constructor(samlToken, realm, fetchApi, debug = false, baseUrl = "https://api.quickbase.com/v1") {
|
|
7498
7522
|
this.samlToken = samlToken;
|
|
7499
7523
|
this.realm = realm;
|
|
7500
7524
|
this.fetchApi = fetchApi;
|
|
7501
7525
|
this.debug = debug;
|
|
7526
|
+
this.baseUrl = baseUrl;
|
|
7502
7527
|
}
|
|
7503
7528
|
async getToken(_dbid) {
|
|
7504
7529
|
if (!this.currentToken) {
|
|
7505
|
-
|
|
7530
|
+
if (this.pendingFetches.has("sso")) {
|
|
7531
|
+
if (this.debug)
|
|
7532
|
+
console.log("[getToken] Waiting for existing SSO fetch");
|
|
7533
|
+
return this.pendingFetches.get("sso");
|
|
7534
|
+
}
|
|
7535
|
+
const fetchPromise = this.fetchSsoToken({
|
|
7506
7536
|
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
7507
7537
|
requested_token_type: "urn:quickbase:params:oauth:token-type:temp_token",
|
|
7508
7538
|
subject_token: this.samlToken,
|
|
7509
7539
|
subject_token_type: "urn:ietf:params:oauth:token-type:saml2",
|
|
7510
|
-
});
|
|
7540
|
+
}).finally(() => this.pendingFetches.delete("sso"));
|
|
7541
|
+
this.pendingFetches.set("sso", fetchPromise);
|
|
7542
|
+
this.currentToken = await fetchPromise;
|
|
7511
7543
|
}
|
|
7512
7544
|
return this.currentToken;
|
|
7513
7545
|
}
|
|
@@ -7534,7 +7566,7 @@ class SsoTokenStrategy {
|
|
|
7534
7566
|
subject_token: this.samlToken,
|
|
7535
7567
|
subject_token_type: "urn:ietf:params:oauth:token-type:saml2",
|
|
7536
7568
|
};
|
|
7537
|
-
const response = await this.fetchApi(
|
|
7569
|
+
const response = await this.fetchApi(`${this.baseUrl}/auth/oauth/token`, {
|
|
7538
7570
|
method: "POST",
|
|
7539
7571
|
headers: {
|
|
7540
7572
|
"QB-Realm-Hostname": `${this.realm}.quickbase.com`,
|
|
@@ -7561,7 +7593,7 @@ class SsoTokenStrategy {
|
|
|
7561
7593
|
return newToken;
|
|
7562
7594
|
}
|
|
7563
7595
|
async fetchSsoToken(params) {
|
|
7564
|
-
const response = await this.fetchApi(
|
|
7596
|
+
const response = await this.fetchApi(`${this.baseUrl}/auth/oauth/token`, {
|
|
7565
7597
|
method: "POST",
|
|
7566
7598
|
headers: {
|
|
7567
7599
|
"QB-Realm-Hostname": `${this.realm}.quickbase.com`,
|
|
@@ -7609,8 +7641,7 @@ class RateLimitError extends Error {
|
|
|
7609
7641
|
}
|
|
7610
7642
|
|
|
7611
7643
|
// src/invokeMethod.ts
|
|
7612
|
-
async function invokeMethod(methodName, params, methodMap, baseHeaders, authStrategy, rateLimiter, transformDates, debug, convertDates) {
|
|
7613
|
-
console.log("[invokeMethod] Starting for method:", methodName);
|
|
7644
|
+
async function invokeMethod(methodName, params, methodMap, baseHeaders, authStrategy, rateLimiter, transformDates, debug, convertDates, attempt = 0, maxAttempts = rateLimiter.maxRetries + 1) {
|
|
7614
7645
|
const methodInfo = methodMap[methodName];
|
|
7615
7646
|
if (!methodInfo)
|
|
7616
7647
|
throw new Error(`Method ${methodName} not found`);
|
|
@@ -7628,33 +7659,21 @@ async function invokeMethod(methodName, params, methodMap, baseHeaders, authStra
|
|
|
7628
7659
|
method: methodInfo.httpMethod,
|
|
7629
7660
|
};
|
|
7630
7661
|
let dbid = extractDbid(params);
|
|
7631
|
-
console.log("[invokeMethod] Extracted dbid:", dbid);
|
|
7632
7662
|
let token = dbid
|
|
7633
7663
|
? await authStrategy.getToken(dbid)
|
|
7634
7664
|
: await authStrategy.getToken("");
|
|
7635
|
-
console.log("[invokeMethod] Initial token:", token);
|
|
7636
7665
|
if (token) {
|
|
7637
7666
|
authStrategy.applyHeaders(baseHeaders, token);
|
|
7638
7667
|
requestOptions.headers = { ...baseHeaders };
|
|
7639
|
-
console.log("[invokeMethod] Token found, headers set:", requestOptions.headers);
|
|
7640
7668
|
if (methodName === "getTempTokenDBID") {
|
|
7641
|
-
console.log("[invokeMethod] Using cached token:", token);
|
|
7642
7669
|
return { temporaryAuthorization: token };
|
|
7643
7670
|
}
|
|
7644
7671
|
}
|
|
7645
|
-
if (debug) {
|
|
7646
|
-
console.log(`[${methodName}] requestParameters:`, requestParameters);
|
|
7647
|
-
console.log(`[${methodName}] requestOptions:`, requestOptions);
|
|
7648
|
-
}
|
|
7649
7672
|
async function processResponse(rawResponse) {
|
|
7650
|
-
if (debug)
|
|
7651
|
-
console.log(`[${methodName}] rawResponse:`, rawResponse);
|
|
7652
7673
|
if (rawResponse instanceof Response) {
|
|
7653
7674
|
const contentType = rawResponse.headers
|
|
7654
7675
|
.get("Content-Type")
|
|
7655
7676
|
?.toLowerCase();
|
|
7656
|
-
if (debug)
|
|
7657
|
-
console.log(`[${methodName}] contentType:`, contentType);
|
|
7658
7677
|
if (contentType?.includes("application/json")) {
|
|
7659
7678
|
const jsonResponse = await rawResponse.json();
|
|
7660
7679
|
return transformDates(jsonResponse, convertDates);
|
|
@@ -7663,8 +7682,6 @@ async function invokeMethod(methodName, params, methodMap, baseHeaders, authStra
|
|
|
7663
7682
|
}
|
|
7664
7683
|
if (rawResponse && typeof rawResponse.value === "function") {
|
|
7665
7684
|
const response = await rawResponse.value();
|
|
7666
|
-
if (debug)
|
|
7667
|
-
console.log(`[${methodName}] Resolved JSONApiResponse:`, response);
|
|
7668
7685
|
return transformDates(response, convertDates);
|
|
7669
7686
|
}
|
|
7670
7687
|
return transformDates(rawResponse, convertDates);
|
|
@@ -7692,28 +7709,17 @@ async function invokeMethod(methodName, params, methodMap, baseHeaders, authStra
|
|
|
7692
7709
|
}
|
|
7693
7710
|
}
|
|
7694
7711
|
catch (e) {
|
|
7695
|
-
if (debug)
|
|
7696
|
-
console.log(`[${methodName}] Error parsing response body:`, e);
|
|
7697
7712
|
message = "Failed to parse error response";
|
|
7698
7713
|
}
|
|
7699
|
-
if (debug)
|
|
7700
|
-
console.log("[invokeMethod] Parsed error - status:", status, "message:", message);
|
|
7701
7714
|
return { message, status };
|
|
7702
7715
|
}
|
|
7703
|
-
let attempt = 0;
|
|
7704
|
-
const maxAttempts = rateLimiter.maxRetries + 1;
|
|
7705
7716
|
while (attempt < maxAttempts) {
|
|
7706
|
-
console.log("[invokeMethod] Attempt:", attempt + 1, "of", maxAttempts);
|
|
7707
7717
|
try {
|
|
7708
7718
|
await rateLimiter.throttle();
|
|
7709
|
-
console.log("[invokeMethod] About to call API for method:", methodName);
|
|
7710
7719
|
const response = await methodInfo.method(requestParameters, requestOptions);
|
|
7711
|
-
console.log("[invokeMethod] API call completed for method:", methodName);
|
|
7712
7720
|
return await processResponse(response);
|
|
7713
7721
|
}
|
|
7714
7722
|
catch (error) {
|
|
7715
|
-
if (debug)
|
|
7716
|
-
console.log("[invokeMethod] Caught error:", error);
|
|
7717
7723
|
let status;
|
|
7718
7724
|
let message;
|
|
7719
7725
|
let response;
|
|
@@ -7726,31 +7732,13 @@ async function invokeMethod(methodName, params, methodMap, baseHeaders, authStra
|
|
|
7726
7732
|
({ message, status } = await parseErrorResponse(response));
|
|
7727
7733
|
}
|
|
7728
7734
|
else {
|
|
7729
|
-
|
|
7730
|
-
console.log(`[${methodName}] Unexpected error:`, error);
|
|
7731
|
-
// Attempt to handle as a fetch error with a response
|
|
7732
|
-
if (error instanceof Error && "response" in error) {
|
|
7733
|
-
response = error.response;
|
|
7734
|
-
if (response instanceof Response) {
|
|
7735
|
-
({ message, status } = await parseErrorResponse(response));
|
|
7736
|
-
}
|
|
7737
|
-
else {
|
|
7738
|
-
throw error; // Rethrow if no response
|
|
7739
|
-
}
|
|
7740
|
-
}
|
|
7741
|
-
else {
|
|
7742
|
-
throw error; // Rethrow truly unexpected errors
|
|
7743
|
-
}
|
|
7735
|
+
throw error; // Rethrow truly unexpected errors
|
|
7744
7736
|
}
|
|
7745
|
-
if (debug)
|
|
7746
|
-
console.log("[invokeMethod] Handling error with status:", status);
|
|
7747
7737
|
if (status === 429) {
|
|
7748
7738
|
if (!(error instanceof ResponseError)) {
|
|
7749
7739
|
throw new Error("Expected ResponseError for 429 handling");
|
|
7750
7740
|
}
|
|
7751
7741
|
const delay = await rateLimiter.handle429(error, attempt + 1);
|
|
7752
|
-
if (debug)
|
|
7753
|
-
console.log(`[${methodName}] 429 delay: ${delay}ms`);
|
|
7754
7742
|
if (attempt + 1 === maxAttempts) {
|
|
7755
7743
|
throw new RateLimitError(`API Error: ${message} (Status: ${status})`, status, response?.headers.get("Retry-After")
|
|
7756
7744
|
? parseInt(response.headers.get("Retry-After"), 10)
|
|
@@ -7760,16 +7748,25 @@ async function invokeMethod(methodName, params, methodMap, baseHeaders, authStra
|
|
|
7760
7748
|
attempt++;
|
|
7761
7749
|
continue;
|
|
7762
7750
|
}
|
|
7763
|
-
|
|
7764
|
-
|
|
7751
|
+
// Handle authentication errors
|
|
7752
|
+
let newToken;
|
|
7753
|
+
try {
|
|
7754
|
+
newToken = await authStrategy.handleError(status, params, attempt, maxAttempts, debug, methodName);
|
|
7755
|
+
}
|
|
7756
|
+
catch (authError) {
|
|
7757
|
+
throw authError; // Propagate the fetchTempToken error and exit immediately
|
|
7758
|
+
}
|
|
7759
|
+
// Only proceed with retry if we have a valid new token
|
|
7760
|
+
if (newToken !== null) {
|
|
7765
7761
|
token = newToken;
|
|
7766
7762
|
authStrategy.applyHeaders(baseHeaders, token);
|
|
7767
7763
|
requestOptions.headers = { ...baseHeaders };
|
|
7768
|
-
attempt++;
|
|
7769
7764
|
if (debug)
|
|
7770
7765
|
console.log(`[${methodName}] Retrying with token: ${token.substring(0, 10)}...`);
|
|
7766
|
+
attempt++;
|
|
7771
7767
|
continue;
|
|
7772
7768
|
}
|
|
7769
|
+
// If no new token was provided, throw the original error
|
|
7773
7770
|
throw new Error(`API Error: ${message} (Status: ${status})`);
|
|
7774
7771
|
}
|
|
7775
7772
|
}
|
|
@@ -7838,7 +7835,6 @@ class RateLimiter {
|
|
|
7838
7835
|
}
|
|
7839
7836
|
}
|
|
7840
7837
|
|
|
7841
|
-
// src/quickbaseClient.ts
|
|
7842
7838
|
function quickbase(config) {
|
|
7843
7839
|
const { realm, userToken, tempToken, useTempTokens, useSso, samlToken, fetchApi, debug, convertDates = true, tempTokenLifespan = 290000, throttle = { rate: 10, burst: 10 }, maxRetries = 3, retryDelay = 1000, tokenCache: providedTokenCache, baseUrl = "https://api.quickbase.com/v1", } = config;
|
|
7844
7840
|
const tokenCache = providedTokenCache || new TokenCache(tempTokenLifespan);
|
|
@@ -7854,10 +7850,10 @@ function quickbase(config) {
|
|
|
7854
7850
|
throw new Error("No fetch implementation available");
|
|
7855
7851
|
}
|
|
7856
7852
|
const authStrategy = useSso
|
|
7857
|
-
? new SsoTokenStrategy(samlToken || "", realm, effectiveFetch, debug)
|
|
7853
|
+
? new SsoTokenStrategy(samlToken || "", realm, effectiveFetch, debug, baseUrl)
|
|
7858
7854
|
: useTempTokens
|
|
7859
7855
|
? new TempTokenStrategy(tokenCache, tempToken, effectiveFetch, realm, baseUrl)
|
|
7860
|
-
: new UserTokenStrategy(userToken || "");
|
|
7856
|
+
: new UserTokenStrategy(userToken || "", baseUrl);
|
|
7861
7857
|
const baseHeaders = {
|
|
7862
7858
|
"QB-Realm-Hostname": `${realm}.quickbase.com`,
|
|
7863
7859
|
"Content-Type": "application/json",
|