@hasna/connectors 1.3.28 → 1.3.30
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 +11 -1
- package/bin/index.js +83 -25
- package/bin/mcp.js +52 -16
- package/bin/serve.js +52 -15
- package/dist/cli/auth-no-browser.test.d.ts +1 -0
- package/dist/cli/commands/auth.d.ts +3 -0
- package/dist/index.js +52 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Open-source connector platform for enabling, authenticating, and running API con
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
|
|
11
|
+
bun install -g @hasna/connectors
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
## What It Is
|
|
@@ -56,6 +56,16 @@ connectors-mcp
|
|
|
56
56
|
connectors-serve
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
The local REST API is served by the one-product runtime at
|
|
60
|
+
`http://localhost:9876`. Use `@hasna/connectors-sdk` with
|
|
61
|
+
`ConnectorsClient` or `LocalConnectorsClient` for this local
|
|
62
|
+
`connectors-serve` API.
|
|
63
|
+
|
|
64
|
+
Hosted SaaS products should use `HostedConnectorsClient` from
|
|
65
|
+
`@hasna/connectors-sdk`. The hosted client talks to a platform
|
|
66
|
+
`/api/v1` endpoint with bearer API keys and does not require local connector
|
|
67
|
+
installs or individual connector packages.
|
|
68
|
+
|
|
59
69
|
## Project Layout
|
|
60
70
|
|
|
61
71
|
Project-local enablement is lightweight:
|
package/bin/index.js
CHANGED
|
@@ -1909,7 +1909,7 @@ var package_default;
|
|
|
1909
1909
|
var init_package = __esm(() => {
|
|
1910
1910
|
package_default = {
|
|
1911
1911
|
name: "@hasna/connectors",
|
|
1912
|
-
version: "1.3.
|
|
1912
|
+
version: "1.3.30",
|
|
1913
1913
|
description: "Open source connector library - Install API connectors with a single command",
|
|
1914
1914
|
type: "module",
|
|
1915
1915
|
bin: {
|
|
@@ -7017,22 +7017,58 @@ async function requestJson(profile, path, params, options = {}) {
|
|
|
7017
7017
|
if (value !== undefined && value !== null && value !== "")
|
|
7018
7018
|
url.searchParams.append(key, String(value));
|
|
7019
7019
|
}
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7020
|
+
let lastError;
|
|
7021
|
+
for (let attempt = 0;attempt <= MAX_GMAIL_RETRIES; attempt++) {
|
|
7022
|
+
let response;
|
|
7023
|
+
try {
|
|
7024
|
+
response = await fetch(url, {
|
|
7025
|
+
method: options.method ?? "GET",
|
|
7026
|
+
headers: {
|
|
7027
|
+
Authorization: `Bearer ${token}`,
|
|
7028
|
+
Accept: "application/json",
|
|
7029
|
+
...options.body ? { "Content-Type": "application/json" } : {}
|
|
7030
|
+
},
|
|
7031
|
+
body: options.body ? JSON.stringify(options.body) : undefined
|
|
7032
|
+
});
|
|
7033
|
+
} catch (error2) {
|
|
7034
|
+
lastError = error2 instanceof Error ? error2.message : String(error2);
|
|
7035
|
+
if (attempt >= MAX_GMAIL_RETRIES)
|
|
7036
|
+
throw error2;
|
|
7037
|
+
await sleep(gmailBackoffDelayMs(attempt));
|
|
7038
|
+
continue;
|
|
7039
|
+
}
|
|
7040
|
+
const text = await response.text();
|
|
7041
|
+
const data = text ? JSON.parse(text) : {};
|
|
7042
|
+
if (response.ok)
|
|
7043
|
+
return data;
|
|
7032
7044
|
const error = data;
|
|
7033
|
-
|
|
7045
|
+
lastError = error.error?.message ?? response.statusText;
|
|
7046
|
+
if (!isRetryableGmailResponse(response.status, error) || attempt >= MAX_GMAIL_RETRIES) {
|
|
7047
|
+
throw new Error(`Gmail request failed (${response.status}): ${lastError}`);
|
|
7048
|
+
}
|
|
7049
|
+
await sleep(gmailBackoffDelayMs(attempt, response.headers.get("retry-after")));
|
|
7034
7050
|
}
|
|
7035
|
-
|
|
7051
|
+
throw new Error(`Gmail request failed: ${lastError ?? "unknown error"}`);
|
|
7052
|
+
}
|
|
7053
|
+
function isRetryableGmailResponse(status, error) {
|
|
7054
|
+
if ([429, 500, 502, 503, 504].includes(status))
|
|
7055
|
+
return true;
|
|
7056
|
+
const reasons = error.error?.errors?.map((entry) => entry.reason) ?? [];
|
|
7057
|
+
return reasons.some((reason) => reason === "rateLimitExceeded" || reason === "userRateLimitExceeded");
|
|
7058
|
+
}
|
|
7059
|
+
function gmailBackoffDelayMs(attempt, retryAfter = null) {
|
|
7060
|
+
if (retryAfter) {
|
|
7061
|
+
const seconds = Number(retryAfter);
|
|
7062
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
7063
|
+
return seconds * 1000;
|
|
7064
|
+
}
|
|
7065
|
+
const base = Number(process.env.CONNECTORS_GMAIL_RETRY_BASE_MS ?? "1000");
|
|
7066
|
+
const baseMs = Number.isFinite(base) && base >= 0 ? base : 1000;
|
|
7067
|
+
const jitterMs = Math.floor(Math.random() * 1000);
|
|
7068
|
+
return Math.min(2 ** attempt * baseMs + jitterMs, 64000);
|
|
7069
|
+
}
|
|
7070
|
+
function sleep(ms) {
|
|
7071
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7036
7072
|
}
|
|
7037
7073
|
async function getValidAccessToken(profile) {
|
|
7038
7074
|
if (process.env.GMAIL_ACCESS_TOKEN)
|
|
@@ -7210,7 +7246,7 @@ ${input.body}`;
|
|
|
7210
7246
|
function safeFilename(filename) {
|
|
7211
7247
|
return basename(filename.replace(/[\u00A0\u2000-\u200B\u202F\u205F\u3000]/g, " ")).replace(/[\/\\]/g, "_");
|
|
7212
7248
|
}
|
|
7213
|
-
var GMAIL_API_BASE = "https://gmail.googleapis.com/gmail/v1", TOKEN_URL = "https://oauth2.googleapis.com/token", REFRESH_BUFFER_MS, listMessagesSchema, messageIdSchema, readMessageSchema, attachmentListSchema, attachmentDownloadSchema, historyListSchema, replySchema, gmailConnector;
|
|
7249
|
+
var GMAIL_API_BASE = "https://gmail.googleapis.com/gmail/v1", TOKEN_URL = "https://oauth2.googleapis.com/token", REFRESH_BUFFER_MS, MAX_GMAIL_RETRIES = 5, listMessagesSchema, messageIdSchema, readMessageSchema, attachmentListSchema, attachmentDownloadSchema, historyListSchema, replySchema, gmailConnector;
|
|
7214
7250
|
var init_gmail = __esm(() => {
|
|
7215
7251
|
init_zod();
|
|
7216
7252
|
init_connector();
|
|
@@ -34282,6 +34318,27 @@ function getOAuthTokenState(name) {
|
|
|
34282
34318
|
})();
|
|
34283
34319
|
return { hasTokens: true, expired: isExpired, expiresIn };
|
|
34284
34320
|
}
|
|
34321
|
+
function getCurrentOAuthProfile(name, connectorsHome = getConnectorsHome()) {
|
|
34322
|
+
for (const dir of getConnectorConfigReadDirs(name, connectorsHome)) {
|
|
34323
|
+
const currentProfilePath = join20(dir, "current_profile");
|
|
34324
|
+
if (!existsSync20(currentProfilePath))
|
|
34325
|
+
continue;
|
|
34326
|
+
const profile = readFileSync11(currentProfilePath, "utf8").trim();
|
|
34327
|
+
if (profile)
|
|
34328
|
+
return profile;
|
|
34329
|
+
}
|
|
34330
|
+
return "default";
|
|
34331
|
+
}
|
|
34332
|
+
function getOAuthTokenPathsForProfile(name, connectorsHome = getConnectorsHome(), profile = getCurrentOAuthProfile(name, connectorsHome)) {
|
|
34333
|
+
return getConnectorConfigReadDirs(name, connectorsHome).map((dir) => join20(dir, "profiles", profile, "tokens.json"));
|
|
34334
|
+
}
|
|
34335
|
+
function hasOAuthTokenFileUpdatedSince(tokenPaths, sinceMs) {
|
|
34336
|
+
return tokenPaths.some((tokensPath) => {
|
|
34337
|
+
if (!existsSync20(tokensPath))
|
|
34338
|
+
return false;
|
|
34339
|
+
return statSync8(tokensPath).mtimeMs >= sinceMs;
|
|
34340
|
+
});
|
|
34341
|
+
}
|
|
34285
34342
|
function registerCommands4(program2) {
|
|
34286
34343
|
program2.command("auth").argument("<connector>", "Connector name to configure auth for").option("-k, --key <value>", "API key or bearer token value (non-interactive)").option("-f, --field <field>", "Which field to set (for multi-field connectors)").option("--json", "Output as JSON", false).option("--no-browser", "Print OAuth URL without opening a browser (agent-friendly)", false).option("--refresh", "Refresh expired OAuth tokens", false).option("--port <port>", "OAuth server port (default: 9876)", "9876").description("Configure authentication for a connector").action(async (connector, options) => {
|
|
34287
34344
|
const meta = getConnector(connector);
|
|
@@ -34407,10 +34464,11 @@ ${meta.displayName} \u2014 Auth Configuration
|
|
|
34407
34464
|
console.log(chalk5.dim(`Starting OAuth server on port ${port}...`));
|
|
34408
34465
|
const { spawn: spawn3 } = await import("child_process");
|
|
34409
34466
|
const scriptPath = process.argv[1];
|
|
34410
|
-
const serverProc = spawn3(
|
|
34467
|
+
const serverProc = spawn3(process.execPath, [scriptPath, "serve", "--port", String(port)], {
|
|
34411
34468
|
detached: true,
|
|
34412
34469
|
stdio: "ignore"
|
|
34413
34470
|
});
|
|
34471
|
+
const startedAt = Date.now();
|
|
34414
34472
|
serverProc.unref();
|
|
34415
34473
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
34416
34474
|
try {
|
|
@@ -34427,12 +34485,13 @@ ${meta.displayName} \u2014 Auth Configuration
|
|
|
34427
34485
|
`);
|
|
34428
34486
|
console.log(chalk5.dim("Waiting for authentication to complete..."));
|
|
34429
34487
|
const connectorsHome = getConnectorsHome();
|
|
34430
|
-
const
|
|
34488
|
+
const activeProfile = getCurrentOAuthProfile(connector, connectorsHome);
|
|
34489
|
+
const tokenPaths = getOAuthTokenPathsForProfile(connector, connectorsHome, activeProfile);
|
|
34431
34490
|
let attempts = 0;
|
|
34432
34491
|
const maxAttempts = 360;
|
|
34433
34492
|
while (attempts < maxAttempts) {
|
|
34434
34493
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
34435
|
-
if (tokenPaths
|
|
34494
|
+
if (hasOAuthTokenFileUpdatedSince(tokenPaths, startedAt)) {
|
|
34436
34495
|
break;
|
|
34437
34496
|
}
|
|
34438
34497
|
attempts++;
|
|
@@ -35586,7 +35645,6 @@ Testing connector credentials...
|
|
|
35586
35645
|
init_registry2();
|
|
35587
35646
|
init_auth();
|
|
35588
35647
|
init_runner();
|
|
35589
|
-
init_connector_resolver();
|
|
35590
35648
|
import chalk7 from "chalk";
|
|
35591
35649
|
function registerCommands6(program2) {
|
|
35592
35650
|
program2.command("ops").description("List available API operations for a connector").argument("<name>", "Connector name (e.g. stripe, gmail)").argument("[command]", "Get detailed help for a specific subcommand").option("--json", "Output as JSON").action(async (name, command, options) => {
|
|
@@ -35750,13 +35808,12 @@ Setting up ${meta.displayName}...
|
|
|
35750
35808
|
`);
|
|
35751
35809
|
const { spawn: spawn3 } = await import("child_process");
|
|
35752
35810
|
const { getConnectorsHome: getConnectorsHome2 } = await Promise.resolve().then(() => (init_database(), exports_database));
|
|
35753
|
-
const { existsSync: existsSync8 } = await import("fs");
|
|
35754
|
-
const { join: join8 } = await import("path");
|
|
35755
35811
|
const scriptPath = process.argv[1];
|
|
35756
|
-
const serverProc = spawn3(
|
|
35812
|
+
const serverProc = spawn3(process.execPath, [scriptPath, "serve", "--port", String(port)], {
|
|
35757
35813
|
detached: true,
|
|
35758
35814
|
stdio: "ignore"
|
|
35759
35815
|
});
|
|
35816
|
+
const startedAt = Date.now();
|
|
35760
35817
|
serverProc.unref();
|
|
35761
35818
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
35762
35819
|
try {
|
|
@@ -35768,12 +35825,13 @@ Setting up ${meta.displayName}...
|
|
|
35768
35825
|
}
|
|
35769
35826
|
console.log(chalk7.dim(" Waiting for authentication to complete..."));
|
|
35770
35827
|
const connectorsHome = getConnectorsHome2();
|
|
35771
|
-
const
|
|
35828
|
+
const activeProfile = getCurrentOAuthProfile(name, connectorsHome);
|
|
35829
|
+
const tokenPaths = getOAuthTokenPathsForProfile(name, connectorsHome, activeProfile);
|
|
35772
35830
|
let attempts = 0;
|
|
35773
35831
|
const maxAttempts = 360;
|
|
35774
35832
|
while (attempts < maxAttempts) {
|
|
35775
35833
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
35776
|
-
if (tokenPaths
|
|
35834
|
+
if (hasOAuthTokenFileUpdatedSince(tokenPaths, startedAt))
|
|
35777
35835
|
break;
|
|
35778
35836
|
attempts++;
|
|
35779
35837
|
if (attempts % 6 === 0)
|
package/bin/mcp.js
CHANGED
|
@@ -11542,22 +11542,58 @@ async function requestJson(profile, path, params, options = {}) {
|
|
|
11542
11542
|
if (value !== undefined && value !== null && value !== "")
|
|
11543
11543
|
url.searchParams.append(key, String(value));
|
|
11544
11544
|
}
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
|
|
11545
|
+
let lastError;
|
|
11546
|
+
for (let attempt = 0;attempt <= MAX_GMAIL_RETRIES; attempt++) {
|
|
11547
|
+
let response;
|
|
11548
|
+
try {
|
|
11549
|
+
response = await fetch(url, {
|
|
11550
|
+
method: options.method ?? "GET",
|
|
11551
|
+
headers: {
|
|
11552
|
+
Authorization: `Bearer ${token}`,
|
|
11553
|
+
Accept: "application/json",
|
|
11554
|
+
...options.body ? { "Content-Type": "application/json" } : {}
|
|
11555
|
+
},
|
|
11556
|
+
body: options.body ? JSON.stringify(options.body) : undefined
|
|
11557
|
+
});
|
|
11558
|
+
} catch (error3) {
|
|
11559
|
+
lastError = error3 instanceof Error ? error3.message : String(error3);
|
|
11560
|
+
if (attempt >= MAX_GMAIL_RETRIES)
|
|
11561
|
+
throw error3;
|
|
11562
|
+
await sleep(gmailBackoffDelayMs(attempt));
|
|
11563
|
+
continue;
|
|
11564
|
+
}
|
|
11565
|
+
const text = await response.text();
|
|
11566
|
+
const data = text ? JSON.parse(text) : {};
|
|
11567
|
+
if (response.ok)
|
|
11568
|
+
return data;
|
|
11557
11569
|
const error2 = data;
|
|
11558
|
-
|
|
11570
|
+
lastError = error2.error?.message ?? response.statusText;
|
|
11571
|
+
if (!isRetryableGmailResponse(response.status, error2) || attempt >= MAX_GMAIL_RETRIES) {
|
|
11572
|
+
throw new Error(`Gmail request failed (${response.status}): ${lastError}`);
|
|
11573
|
+
}
|
|
11574
|
+
await sleep(gmailBackoffDelayMs(attempt, response.headers.get("retry-after")));
|
|
11575
|
+
}
|
|
11576
|
+
throw new Error(`Gmail request failed: ${lastError ?? "unknown error"}`);
|
|
11577
|
+
}
|
|
11578
|
+
function isRetryableGmailResponse(status, error2) {
|
|
11579
|
+
if ([429, 500, 502, 503, 504].includes(status))
|
|
11580
|
+
return true;
|
|
11581
|
+
const reasons = error2.error?.errors?.map((entry) => entry.reason) ?? [];
|
|
11582
|
+
return reasons.some((reason) => reason === "rateLimitExceeded" || reason === "userRateLimitExceeded");
|
|
11583
|
+
}
|
|
11584
|
+
function gmailBackoffDelayMs(attempt, retryAfter = null) {
|
|
11585
|
+
if (retryAfter) {
|
|
11586
|
+
const seconds = Number(retryAfter);
|
|
11587
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
11588
|
+
return seconds * 1000;
|
|
11559
11589
|
}
|
|
11560
|
-
|
|
11590
|
+
const base = Number(process.env.CONNECTORS_GMAIL_RETRY_BASE_MS ?? "1000");
|
|
11591
|
+
const baseMs = Number.isFinite(base) && base >= 0 ? base : 1000;
|
|
11592
|
+
const jitterMs = Math.floor(Math.random() * 1000);
|
|
11593
|
+
return Math.min(2 ** attempt * baseMs + jitterMs, 64000);
|
|
11594
|
+
}
|
|
11595
|
+
function sleep(ms) {
|
|
11596
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11561
11597
|
}
|
|
11562
11598
|
async function getValidAccessToken(profile) {
|
|
11563
11599
|
if (process.env.GMAIL_ACCESS_TOKEN)
|
|
@@ -11735,7 +11771,7 @@ ${input.body}`;
|
|
|
11735
11771
|
function safeFilename(filename) {
|
|
11736
11772
|
return basename(filename.replace(/[\u00A0\u2000-\u200B\u202F\u205F\u3000]/g, " ")).replace(/[\/\\]/g, "_");
|
|
11737
11773
|
}
|
|
11738
|
-
var GMAIL_API_BASE = "https://gmail.googleapis.com/gmail/v1", TOKEN_URL = "https://oauth2.googleapis.com/token", REFRESH_BUFFER_MS, listMessagesSchema, messageIdSchema, readMessageSchema, attachmentListSchema, attachmentDownloadSchema, historyListSchema, replySchema, gmailConnector;
|
|
11774
|
+
var GMAIL_API_BASE = "https://gmail.googleapis.com/gmail/v1", TOKEN_URL = "https://oauth2.googleapis.com/token", REFRESH_BUFFER_MS, MAX_GMAIL_RETRIES = 5, listMessagesSchema, messageIdSchema, readMessageSchema, attachmentListSchema, attachmentDownloadSchema, historyListSchema, replySchema, gmailConnector;
|
|
11739
11775
|
var init_gmail = __esm(() => {
|
|
11740
11776
|
init_zod();
|
|
11741
11777
|
init_connector();
|
|
@@ -12240,7 +12276,7 @@ var package_default;
|
|
|
12240
12276
|
var init_package = __esm(() => {
|
|
12241
12277
|
package_default = {
|
|
12242
12278
|
name: "@hasna/connectors",
|
|
12243
|
-
version: "1.3.
|
|
12279
|
+
version: "1.3.30",
|
|
12244
12280
|
description: "Open source connector library - Install API connectors with a single command",
|
|
12245
12281
|
type: "module",
|
|
12246
12282
|
bin: {
|
package/bin/serve.js
CHANGED
|
@@ -15200,6 +15200,7 @@ import { basename, join as join9 } from "path";
|
|
|
15200
15200
|
var GMAIL_API_BASE = "https://gmail.googleapis.com/gmail/v1";
|
|
15201
15201
|
var TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
15202
15202
|
var REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
15203
|
+
var MAX_GMAIL_RETRIES = 5;
|
|
15203
15204
|
var listMessagesSchema = exports_external2.object({
|
|
15204
15205
|
max: exports_external2.coerce.number().int().positive().max(500).optional(),
|
|
15205
15206
|
maxResults: exports_external2.coerce.number().int().positive().max(500).optional(),
|
|
@@ -15430,22 +15431,58 @@ async function requestJson(profile, path, params, options = {}) {
|
|
|
15430
15431
|
if (value !== undefined && value !== null && value !== "")
|
|
15431
15432
|
url.searchParams.append(key, String(value));
|
|
15432
15433
|
}
|
|
15433
|
-
|
|
15434
|
-
|
|
15435
|
-
|
|
15436
|
-
|
|
15437
|
-
|
|
15438
|
-
|
|
15439
|
-
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15434
|
+
let lastError;
|
|
15435
|
+
for (let attempt = 0;attempt <= MAX_GMAIL_RETRIES; attempt++) {
|
|
15436
|
+
let response;
|
|
15437
|
+
try {
|
|
15438
|
+
response = await fetch(url, {
|
|
15439
|
+
method: options.method ?? "GET",
|
|
15440
|
+
headers: {
|
|
15441
|
+
Authorization: `Bearer ${token}`,
|
|
15442
|
+
Accept: "application/json",
|
|
15443
|
+
...options.body ? { "Content-Type": "application/json" } : {}
|
|
15444
|
+
},
|
|
15445
|
+
body: options.body ? JSON.stringify(options.body) : undefined
|
|
15446
|
+
});
|
|
15447
|
+
} catch (error2) {
|
|
15448
|
+
lastError = error2 instanceof Error ? error2.message : String(error2);
|
|
15449
|
+
if (attempt >= MAX_GMAIL_RETRIES)
|
|
15450
|
+
throw error2;
|
|
15451
|
+
await sleep(gmailBackoffDelayMs(attempt));
|
|
15452
|
+
continue;
|
|
15453
|
+
}
|
|
15454
|
+
const text = await response.text();
|
|
15455
|
+
const data = text ? JSON.parse(text) : {};
|
|
15456
|
+
if (response.ok)
|
|
15457
|
+
return data;
|
|
15445
15458
|
const error = data;
|
|
15446
|
-
|
|
15459
|
+
lastError = error.error?.message ?? response.statusText;
|
|
15460
|
+
if (!isRetryableGmailResponse(response.status, error) || attempt >= MAX_GMAIL_RETRIES) {
|
|
15461
|
+
throw new Error(`Gmail request failed (${response.status}): ${lastError}`);
|
|
15462
|
+
}
|
|
15463
|
+
await sleep(gmailBackoffDelayMs(attempt, response.headers.get("retry-after")));
|
|
15447
15464
|
}
|
|
15448
|
-
|
|
15465
|
+
throw new Error(`Gmail request failed: ${lastError ?? "unknown error"}`);
|
|
15466
|
+
}
|
|
15467
|
+
function isRetryableGmailResponse(status, error) {
|
|
15468
|
+
if ([429, 500, 502, 503, 504].includes(status))
|
|
15469
|
+
return true;
|
|
15470
|
+
const reasons = error.error?.errors?.map((entry) => entry.reason) ?? [];
|
|
15471
|
+
return reasons.some((reason) => reason === "rateLimitExceeded" || reason === "userRateLimitExceeded");
|
|
15472
|
+
}
|
|
15473
|
+
function gmailBackoffDelayMs(attempt, retryAfter = null) {
|
|
15474
|
+
if (retryAfter) {
|
|
15475
|
+
const seconds = Number(retryAfter);
|
|
15476
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
15477
|
+
return seconds * 1000;
|
|
15478
|
+
}
|
|
15479
|
+
const base = Number(process.env.CONNECTORS_GMAIL_RETRY_BASE_MS ?? "1000");
|
|
15480
|
+
const baseMs = Number.isFinite(base) && base >= 0 ? base : 1000;
|
|
15481
|
+
const jitterMs = Math.floor(Math.random() * 1000);
|
|
15482
|
+
return Math.min(2 ** attempt * baseMs + jitterMs, 64000);
|
|
15483
|
+
}
|
|
15484
|
+
function sleep(ms) {
|
|
15485
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
15449
15486
|
}
|
|
15450
15487
|
async function getValidAccessToken(profile) {
|
|
15451
15488
|
if (process.env.GMAIL_ACCESS_TOKEN)
|
|
@@ -15953,7 +15990,7 @@ function extractGoogleError(body) {
|
|
|
15953
15990
|
// package.json
|
|
15954
15991
|
var package_default = {
|
|
15955
15992
|
name: "@hasna/connectors",
|
|
15956
|
-
version: "1.3.
|
|
15993
|
+
version: "1.3.30",
|
|
15957
15994
|
description: "Open source connector library - Install API connectors with a single command",
|
|
15958
15995
|
type: "module",
|
|
15959
15996
|
bin: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
export declare function getCurrentOAuthProfile(name: string, connectorsHome?: string): string;
|
|
3
|
+
export declare function getOAuthTokenPathsForProfile(name: string, connectorsHome?: string, profile?: string): string[];
|
|
4
|
+
export declare function hasOAuthTokenFileUpdatedSince(tokenPaths: string[], sinceMs: number): boolean;
|
|
2
5
|
export declare function registerCommands(program: Command): void;
|
package/dist/index.js
CHANGED
|
@@ -4974,6 +4974,7 @@ import { basename, join as join2 } from "path";
|
|
|
4974
4974
|
var GMAIL_API_BASE = "https://gmail.googleapis.com/gmail/v1";
|
|
4975
4975
|
var TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
4976
4976
|
var REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
4977
|
+
var MAX_GMAIL_RETRIES = 5;
|
|
4977
4978
|
var listMessagesSchema = exports_external.object({
|
|
4978
4979
|
max: exports_external.coerce.number().int().positive().max(500).optional(),
|
|
4979
4980
|
maxResults: exports_external.coerce.number().int().positive().max(500).optional(),
|
|
@@ -5204,22 +5205,58 @@ async function requestJson(profile, path, params, options = {}) {
|
|
|
5204
5205
|
if (value !== undefined && value !== null && value !== "")
|
|
5205
5206
|
url.searchParams.append(key, String(value));
|
|
5206
5207
|
}
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5208
|
+
let lastError;
|
|
5209
|
+
for (let attempt = 0;attempt <= MAX_GMAIL_RETRIES; attempt++) {
|
|
5210
|
+
let response;
|
|
5211
|
+
try {
|
|
5212
|
+
response = await fetch(url, {
|
|
5213
|
+
method: options.method ?? "GET",
|
|
5214
|
+
headers: {
|
|
5215
|
+
Authorization: `Bearer ${token}`,
|
|
5216
|
+
Accept: "application/json",
|
|
5217
|
+
...options.body ? { "Content-Type": "application/json" } : {}
|
|
5218
|
+
},
|
|
5219
|
+
body: options.body ? JSON.stringify(options.body) : undefined
|
|
5220
|
+
});
|
|
5221
|
+
} catch (error2) {
|
|
5222
|
+
lastError = error2 instanceof Error ? error2.message : String(error2);
|
|
5223
|
+
if (attempt >= MAX_GMAIL_RETRIES)
|
|
5224
|
+
throw error2;
|
|
5225
|
+
await sleep(gmailBackoffDelayMs(attempt));
|
|
5226
|
+
continue;
|
|
5227
|
+
}
|
|
5228
|
+
const text = await response.text();
|
|
5229
|
+
const data = text ? JSON.parse(text) : {};
|
|
5230
|
+
if (response.ok)
|
|
5231
|
+
return data;
|
|
5219
5232
|
const error = data;
|
|
5220
|
-
|
|
5233
|
+
lastError = error.error?.message ?? response.statusText;
|
|
5234
|
+
if (!isRetryableGmailResponse(response.status, error) || attempt >= MAX_GMAIL_RETRIES) {
|
|
5235
|
+
throw new Error(`Gmail request failed (${response.status}): ${lastError}`);
|
|
5236
|
+
}
|
|
5237
|
+
await sleep(gmailBackoffDelayMs(attempt, response.headers.get("retry-after")));
|
|
5221
5238
|
}
|
|
5222
|
-
|
|
5239
|
+
throw new Error(`Gmail request failed: ${lastError ?? "unknown error"}`);
|
|
5240
|
+
}
|
|
5241
|
+
function isRetryableGmailResponse(status, error) {
|
|
5242
|
+
if ([429, 500, 502, 503, 504].includes(status))
|
|
5243
|
+
return true;
|
|
5244
|
+
const reasons = error.error?.errors?.map((entry) => entry.reason) ?? [];
|
|
5245
|
+
return reasons.some((reason) => reason === "rateLimitExceeded" || reason === "userRateLimitExceeded");
|
|
5246
|
+
}
|
|
5247
|
+
function gmailBackoffDelayMs(attempt, retryAfter = null) {
|
|
5248
|
+
if (retryAfter) {
|
|
5249
|
+
const seconds = Number(retryAfter);
|
|
5250
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
5251
|
+
return seconds * 1000;
|
|
5252
|
+
}
|
|
5253
|
+
const base = Number(process.env.CONNECTORS_GMAIL_RETRY_BASE_MS ?? "1000");
|
|
5254
|
+
const baseMs = Number.isFinite(base) && base >= 0 ? base : 1000;
|
|
5255
|
+
const jitterMs = Math.floor(Math.random() * 1000);
|
|
5256
|
+
return Math.min(2 ** attempt * baseMs + jitterMs, 64000);
|
|
5257
|
+
}
|
|
5258
|
+
function sleep(ms) {
|
|
5259
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5223
5260
|
}
|
|
5224
5261
|
async function getValidAccessToken(profile) {
|
|
5225
5262
|
if (process.env.GMAIL_ACCESS_TOKEN)
|
|
@@ -5727,7 +5764,7 @@ function extractGoogleError(body) {
|
|
|
5727
5764
|
// package.json
|
|
5728
5765
|
var package_default = {
|
|
5729
5766
|
name: "@hasna/connectors",
|
|
5730
|
-
version: "1.3.
|
|
5767
|
+
version: "1.3.30",
|
|
5731
5768
|
description: "Open source connector library - Install API connectors with a single command",
|
|
5732
5769
|
type: "module",
|
|
5733
5770
|
bin: {
|