@hasna/connectors 1.1.12 → 1.1.13
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/bin/index.js +230 -156
- package/bin/mcp.js +115 -41
- package/bin/serve.js +144 -68
- package/dist/lib/lock.d.ts +22 -0
- package/dist/lib/lock.test.d.ts +1 -0
- package/dist/server/auth.d.ts +4 -2
- package/package.json +1 -2
package/bin/serve.js
CHANGED
|
@@ -19,7 +19,7 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
19
19
|
var __require = import.meta.require;
|
|
20
20
|
|
|
21
21
|
// src/server/serve.ts
|
|
22
|
-
import { existsSync as
|
|
22
|
+
import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync5 } from "fs";
|
|
23
23
|
|
|
24
24
|
// src/db/database.ts
|
|
25
25
|
import { Database } from "bun:sqlite";
|
|
@@ -118,9 +118,9 @@ function deleteAgent(id, db) {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
// src/server/serve.ts
|
|
121
|
-
import { join as
|
|
121
|
+
import { join as join6, dirname as dirname3, extname, basename } from "path";
|
|
122
122
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
123
|
-
import { homedir as
|
|
123
|
+
import { homedir as homedir5 } from "os";
|
|
124
124
|
|
|
125
125
|
// src/lib/registry.ts
|
|
126
126
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -6212,10 +6212,80 @@ function removeConnector(name, targetDir = process.cwd()) {
|
|
|
6212
6212
|
}
|
|
6213
6213
|
|
|
6214
6214
|
// src/server/auth.ts
|
|
6215
|
-
import { existsSync as
|
|
6215
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync4, readdirSync as readdirSync2, rmSync as rmSync2, statSync as statSync2 } from "fs";
|
|
6216
6216
|
import { randomBytes } from "crypto";
|
|
6217
|
-
import { homedir as
|
|
6217
|
+
import { homedir as homedir4 } from "os";
|
|
6218
|
+
import { join as join5 } from "path";
|
|
6219
|
+
|
|
6220
|
+
// src/lib/lock.ts
|
|
6221
|
+
import { openSync, closeSync, unlinkSync, existsSync as existsSync3 } from "fs";
|
|
6218
6222
|
import { join as join4 } from "path";
|
|
6223
|
+
import { homedir as homedir3 } from "os";
|
|
6224
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
6225
|
+
var LOCK_TIMEOUT_MS = 5000;
|
|
6226
|
+
var LOCK_RETRY_MS = 100;
|
|
6227
|
+
var STALE_LOCK_MS = 30000;
|
|
6228
|
+
|
|
6229
|
+
class LockTimeoutError extends Error {
|
|
6230
|
+
connector;
|
|
6231
|
+
constructor(connector) {
|
|
6232
|
+
super(`Could not acquire write lock for connector "${connector}" within ${LOCK_TIMEOUT_MS}ms. Another agent may be writing. Try again shortly.`);
|
|
6233
|
+
this.connector = connector;
|
|
6234
|
+
this.name = "LockTimeoutError";
|
|
6235
|
+
}
|
|
6236
|
+
}
|
|
6237
|
+
function lockPath(connector) {
|
|
6238
|
+
const dir = join4(homedir3(), ".connectors", `connect-${connector}`);
|
|
6239
|
+
mkdirSync3(dir, { recursive: true });
|
|
6240
|
+
return join4(dir, ".write.lock");
|
|
6241
|
+
}
|
|
6242
|
+
function isStale(path) {
|
|
6243
|
+
try {
|
|
6244
|
+
const { statSync: statSync2 } = __require("fs");
|
|
6245
|
+
const stat = statSync2(path);
|
|
6246
|
+
return Date.now() - stat.mtimeMs > STALE_LOCK_MS;
|
|
6247
|
+
} catch {
|
|
6248
|
+
return false;
|
|
6249
|
+
}
|
|
6250
|
+
}
|
|
6251
|
+
function tryAcquire(path) {
|
|
6252
|
+
if (existsSync3(path) && isStale(path)) {
|
|
6253
|
+
try {
|
|
6254
|
+
unlinkSync(path);
|
|
6255
|
+
} catch {}
|
|
6256
|
+
}
|
|
6257
|
+
try {
|
|
6258
|
+
const fd = openSync(path, "wx");
|
|
6259
|
+
closeSync(fd);
|
|
6260
|
+
return true;
|
|
6261
|
+
} catch (e) {
|
|
6262
|
+
if (e.code === "EEXIST")
|
|
6263
|
+
return false;
|
|
6264
|
+
throw e;
|
|
6265
|
+
}
|
|
6266
|
+
}
|
|
6267
|
+
function release(path) {
|
|
6268
|
+
try {
|
|
6269
|
+
unlinkSync(path);
|
|
6270
|
+
} catch {}
|
|
6271
|
+
}
|
|
6272
|
+
async function withWriteLock(connector, fn) {
|
|
6273
|
+
const path = lockPath(connector);
|
|
6274
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
6275
|
+
while (Date.now() < deadline) {
|
|
6276
|
+
if (tryAcquire(path)) {
|
|
6277
|
+
try {
|
|
6278
|
+
return await fn();
|
|
6279
|
+
} finally {
|
|
6280
|
+
release(path);
|
|
6281
|
+
}
|
|
6282
|
+
}
|
|
6283
|
+
await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_MS));
|
|
6284
|
+
}
|
|
6285
|
+
throw new LockTimeoutError(connector);
|
|
6286
|
+
}
|
|
6287
|
+
|
|
6288
|
+
// src/server/auth.ts
|
|
6219
6289
|
var FETCH_TIMEOUT = 1e4;
|
|
6220
6290
|
var oauthStateStore = new Map;
|
|
6221
6291
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
@@ -6271,12 +6341,12 @@ function getAuthType(name) {
|
|
|
6271
6341
|
}
|
|
6272
6342
|
function getConnectorConfigDir(name) {
|
|
6273
6343
|
const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
|
|
6274
|
-
return
|
|
6344
|
+
return join5(homedir4(), ".connectors", connectorName);
|
|
6275
6345
|
}
|
|
6276
6346
|
function getCurrentProfile(name) {
|
|
6277
6347
|
const configDir = getConnectorConfigDir(name);
|
|
6278
|
-
const currentProfileFile =
|
|
6279
|
-
if (
|
|
6348
|
+
const currentProfileFile = join5(configDir, "current_profile");
|
|
6349
|
+
if (existsSync4(currentProfileFile)) {
|
|
6280
6350
|
try {
|
|
6281
6351
|
return readFileSync3(currentProfileFile, "utf-8").trim() || "default";
|
|
6282
6352
|
} catch {
|
|
@@ -6290,14 +6360,14 @@ function loadProfileConfig(name) {
|
|
|
6290
6360
|
const profile = getCurrentProfile(name);
|
|
6291
6361
|
let flatConfig = {};
|
|
6292
6362
|
let dirConfig = {};
|
|
6293
|
-
const profileFile =
|
|
6294
|
-
if (
|
|
6363
|
+
const profileFile = join5(configDir, "profiles", `${profile}.json`);
|
|
6364
|
+
if (existsSync4(profileFile)) {
|
|
6295
6365
|
try {
|
|
6296
6366
|
flatConfig = JSON.parse(readFileSync3(profileFile, "utf-8"));
|
|
6297
6367
|
} catch {}
|
|
6298
6368
|
}
|
|
6299
|
-
const profileDirConfig =
|
|
6300
|
-
if (
|
|
6369
|
+
const profileDirConfig = join5(configDir, "profiles", profile, "config.json");
|
|
6370
|
+
if (existsSync4(profileDirConfig)) {
|
|
6301
6371
|
try {
|
|
6302
6372
|
dirConfig = JSON.parse(readFileSync3(profileDirConfig, "utf-8"));
|
|
6303
6373
|
} catch {}
|
|
@@ -6310,8 +6380,8 @@ function loadProfileConfig(name) {
|
|
|
6310
6380
|
function loadTokens(name) {
|
|
6311
6381
|
const configDir = getConnectorConfigDir(name);
|
|
6312
6382
|
const profile = getCurrentProfile(name);
|
|
6313
|
-
const tokensFile =
|
|
6314
|
-
if (
|
|
6383
|
+
const tokensFile = join5(configDir, "profiles", profile, "tokens.json");
|
|
6384
|
+
if (existsSync4(tokensFile)) {
|
|
6315
6385
|
try {
|
|
6316
6386
|
return JSON.parse(readFileSync3(tokensFile, "utf-8"));
|
|
6317
6387
|
} catch {
|
|
@@ -6358,15 +6428,18 @@ function getAuthStatus(name) {
|
|
|
6358
6428
|
envVarTotalCount
|
|
6359
6429
|
};
|
|
6360
6430
|
}
|
|
6361
|
-
function saveApiKey(name, key, field) {
|
|
6431
|
+
async function saveApiKey(name, key, field) {
|
|
6432
|
+
return withWriteLock(name, () => _saveApiKey(name, key, field));
|
|
6433
|
+
}
|
|
6434
|
+
function _saveApiKey(name, key, field) {
|
|
6362
6435
|
const configDir = getConnectorConfigDir(name);
|
|
6363
6436
|
const profile = getCurrentProfile(name);
|
|
6364
6437
|
const keyField = field || guessKeyField(name);
|
|
6365
6438
|
if (keyField === "clientId" || keyField === "clientSecret") {
|
|
6366
|
-
const credentialsFile =
|
|
6367
|
-
|
|
6439
|
+
const credentialsFile = join5(configDir, "credentials.json");
|
|
6440
|
+
mkdirSync4(configDir, { recursive: true });
|
|
6368
6441
|
let creds = {};
|
|
6369
|
-
if (
|
|
6442
|
+
if (existsSync4(credentialsFile)) {
|
|
6370
6443
|
try {
|
|
6371
6444
|
creds = JSON.parse(readFileSync3(credentialsFile, "utf-8"));
|
|
6372
6445
|
} catch {}
|
|
@@ -6375,9 +6448,9 @@ function saveApiKey(name, key, field) {
|
|
|
6375
6448
|
writeFileSync2(credentialsFile, JSON.stringify(creds, null, 2));
|
|
6376
6449
|
return;
|
|
6377
6450
|
}
|
|
6378
|
-
const profileFile =
|
|
6379
|
-
const profileDir =
|
|
6380
|
-
if (
|
|
6451
|
+
const profileFile = join5(configDir, "profiles", `${profile}.json`);
|
|
6452
|
+
const profileDir = join5(configDir, "profiles", profile);
|
|
6453
|
+
if (existsSync4(profileFile)) {
|
|
6381
6454
|
let config = {};
|
|
6382
6455
|
try {
|
|
6383
6456
|
config = JSON.parse(readFileSync3(profileFile, "utf-8"));
|
|
@@ -6386,10 +6459,10 @@ function saveApiKey(name, key, field) {
|
|
|
6386
6459
|
writeFileSync2(profileFile, JSON.stringify(config, null, 2));
|
|
6387
6460
|
return;
|
|
6388
6461
|
}
|
|
6389
|
-
if (
|
|
6390
|
-
const configFile =
|
|
6462
|
+
if (existsSync4(profileDir)) {
|
|
6463
|
+
const configFile = join5(profileDir, "config.json");
|
|
6391
6464
|
let config = {};
|
|
6392
|
-
if (
|
|
6465
|
+
if (existsSync4(configFile)) {
|
|
6393
6466
|
try {
|
|
6394
6467
|
config = JSON.parse(readFileSync3(configFile, "utf-8"));
|
|
6395
6468
|
} catch {}
|
|
@@ -6398,8 +6471,8 @@ function saveApiKey(name, key, field) {
|
|
|
6398
6471
|
writeFileSync2(configFile, JSON.stringify(config, null, 2));
|
|
6399
6472
|
return;
|
|
6400
6473
|
}
|
|
6401
|
-
|
|
6402
|
-
writeFileSync2(
|
|
6474
|
+
mkdirSync4(profileDir, { recursive: true });
|
|
6475
|
+
writeFileSync2(join5(profileDir, "config.json"), JSON.stringify({ [keyField]: key }, null, 2));
|
|
6403
6476
|
}
|
|
6404
6477
|
function guessKeyField(name) {
|
|
6405
6478
|
const docs = getConnectorDocs(name);
|
|
@@ -6417,8 +6490,8 @@ function guessKeyField(name) {
|
|
|
6417
6490
|
}
|
|
6418
6491
|
function getOAuthConfig(name) {
|
|
6419
6492
|
const configDir = getConnectorConfigDir(name);
|
|
6420
|
-
const credentialsFile =
|
|
6421
|
-
if (
|
|
6493
|
+
const credentialsFile = join5(configDir, "credentials.json");
|
|
6494
|
+
if (existsSync4(credentialsFile)) {
|
|
6422
6495
|
try {
|
|
6423
6496
|
const creds = JSON.parse(readFileSync3(credentialsFile, "utf-8"));
|
|
6424
6497
|
return { clientId: creds.clientId, clientSecret: creds.clientSecret };
|
|
@@ -6499,12 +6572,15 @@ async function exchangeOAuthCode(name, code, redirectUri) {
|
|
|
6499
6572
|
function saveOAuthTokens(name, tokens) {
|
|
6500
6573
|
const configDir = getConnectorConfigDir(name);
|
|
6501
6574
|
const profile = getCurrentProfile(name);
|
|
6502
|
-
const profileDir =
|
|
6503
|
-
|
|
6504
|
-
const tokensFile =
|
|
6575
|
+
const profileDir = join5(configDir, "profiles", profile);
|
|
6576
|
+
mkdirSync4(profileDir, { recursive: true });
|
|
6577
|
+
const tokensFile = join5(profileDir, "tokens.json");
|
|
6505
6578
|
writeFileSync2(tokensFile, JSON.stringify(tokens, null, 2), { mode: 384 });
|
|
6506
6579
|
}
|
|
6507
6580
|
async function refreshOAuthToken(name) {
|
|
6581
|
+
return withWriteLock(name, () => _refreshOAuthToken(name));
|
|
6582
|
+
}
|
|
6583
|
+
async function _refreshOAuthToken(name) {
|
|
6508
6584
|
const oauthConfig = getOAuthConfig(name);
|
|
6509
6585
|
const currentTokens = loadTokens(name);
|
|
6510
6586
|
if (!oauthConfig.clientId || !oauthConfig.clientSecret) {
|
|
@@ -6541,14 +6617,14 @@ async function refreshOAuthToken(name) {
|
|
|
6541
6617
|
}
|
|
6542
6618
|
function listProfiles(name) {
|
|
6543
6619
|
const configDir = getConnectorConfigDir(name);
|
|
6544
|
-
const profilesDir =
|
|
6545
|
-
if (!
|
|
6620
|
+
const profilesDir = join5(configDir, "profiles");
|
|
6621
|
+
if (!existsSync4(profilesDir))
|
|
6546
6622
|
return ["default"];
|
|
6547
6623
|
const seen = new Set;
|
|
6548
6624
|
try {
|
|
6549
6625
|
const entries = readdirSync2(profilesDir);
|
|
6550
6626
|
for (const entry of entries) {
|
|
6551
|
-
const fullPath =
|
|
6627
|
+
const fullPath = join5(profilesDir, entry);
|
|
6552
6628
|
const stat = statSync2(fullPath);
|
|
6553
6629
|
if (stat.isDirectory()) {
|
|
6554
6630
|
seen.add(entry);
|
|
@@ -6562,24 +6638,24 @@ function listProfiles(name) {
|
|
|
6562
6638
|
}
|
|
6563
6639
|
function switchProfile(name, profile) {
|
|
6564
6640
|
const configDir = getConnectorConfigDir(name);
|
|
6565
|
-
|
|
6566
|
-
writeFileSync2(
|
|
6641
|
+
mkdirSync4(configDir, { recursive: true });
|
|
6642
|
+
writeFileSync2(join5(configDir, "current_profile"), profile);
|
|
6567
6643
|
}
|
|
6568
6644
|
function deleteProfile(name, profile) {
|
|
6569
6645
|
if (profile === "default")
|
|
6570
6646
|
return false;
|
|
6571
6647
|
const configDir = getConnectorConfigDir(name);
|
|
6572
|
-
const profilesDir =
|
|
6573
|
-
const profileFile =
|
|
6574
|
-
if (
|
|
6648
|
+
const profilesDir = join5(configDir, "profiles");
|
|
6649
|
+
const profileFile = join5(profilesDir, `${profile}.json`);
|
|
6650
|
+
if (existsSync4(profileFile)) {
|
|
6575
6651
|
rmSync2(profileFile);
|
|
6576
6652
|
if (getCurrentProfile(name) === profile) {
|
|
6577
6653
|
switchProfile(name, "default");
|
|
6578
6654
|
}
|
|
6579
6655
|
return true;
|
|
6580
6656
|
}
|
|
6581
|
-
const profileDir =
|
|
6582
|
-
if (
|
|
6657
|
+
const profileDir = join5(profilesDir, profile);
|
|
6658
|
+
if (existsSync4(profileDir)) {
|
|
6583
6659
|
rmSync2(profileDir, { recursive: true });
|
|
6584
6660
|
if (getCurrentProfile(name) === profile) {
|
|
6585
6661
|
switchProfile(name, "default");
|
|
@@ -6602,20 +6678,20 @@ function resolveDashboardDir() {
|
|
|
6602
6678
|
const candidates = [];
|
|
6603
6679
|
try {
|
|
6604
6680
|
const scriptDir = dirname3(fileURLToPath3(import.meta.url));
|
|
6605
|
-
candidates.push(
|
|
6606
|
-
candidates.push(
|
|
6681
|
+
candidates.push(join6(scriptDir, "..", "dashboard", "dist"));
|
|
6682
|
+
candidates.push(join6(scriptDir, "..", "..", "dashboard", "dist"));
|
|
6607
6683
|
} catch {}
|
|
6608
6684
|
if (process.argv[1]) {
|
|
6609
6685
|
const mainDir = dirname3(process.argv[1]);
|
|
6610
|
-
candidates.push(
|
|
6611
|
-
candidates.push(
|
|
6686
|
+
candidates.push(join6(mainDir, "..", "dashboard", "dist"));
|
|
6687
|
+
candidates.push(join6(mainDir, "..", "..", "dashboard", "dist"));
|
|
6612
6688
|
}
|
|
6613
|
-
candidates.push(
|
|
6689
|
+
candidates.push(join6(process.cwd(), "dashboard", "dist"));
|
|
6614
6690
|
for (const candidate of candidates) {
|
|
6615
|
-
if (
|
|
6691
|
+
if (existsSync5(candidate))
|
|
6616
6692
|
return candidate;
|
|
6617
6693
|
}
|
|
6618
|
-
return
|
|
6694
|
+
return join6(process.cwd(), "dashboard", "dist");
|
|
6619
6695
|
}
|
|
6620
6696
|
var MIME_TYPES = {
|
|
6621
6697
|
".html": "text/html; charset=utf-8",
|
|
@@ -6705,7 +6781,7 @@ function oauthPage(type, title, message, hint, extra) {
|
|
|
6705
6781
|
</body></html>`;
|
|
6706
6782
|
}
|
|
6707
6783
|
function serveStaticFile(filePath) {
|
|
6708
|
-
if (!
|
|
6784
|
+
if (!existsSync5(filePath))
|
|
6709
6785
|
return null;
|
|
6710
6786
|
const ext = extname(filePath);
|
|
6711
6787
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -6729,7 +6805,7 @@ async function startServer(requestedPort, options) {
|
|
|
6729
6805
|
const shouldOpen = options?.open ?? true;
|
|
6730
6806
|
loadConnectorVersions();
|
|
6731
6807
|
const dashboardDir = resolveDashboardDir();
|
|
6732
|
-
const dashboardExists =
|
|
6808
|
+
const dashboardExists = existsSync5(dashboardDir);
|
|
6733
6809
|
if (!dashboardExists) {
|
|
6734
6810
|
console.error(`
|
|
6735
6811
|
Dashboard not found at: ${dashboardDir}`);
|
|
@@ -6804,7 +6880,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
6804
6880
|
const body = await req.json();
|
|
6805
6881
|
if (!body.key)
|
|
6806
6882
|
return json({ error: "Missing 'key' in request body" }, 400, port);
|
|
6807
|
-
saveApiKey(name, body.key, body.field);
|
|
6883
|
+
await saveApiKey(name, body.key, body.field);
|
|
6808
6884
|
logActivity("key_saved", name, body.field ? `Field: ${body.field}` : undefined);
|
|
6809
6885
|
return json({ success: true }, 200, port);
|
|
6810
6886
|
} catch (e) {
|
|
@@ -6910,10 +6986,10 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
6910
6986
|
return json({ error: "Invalid connector name" }, 400, port);
|
|
6911
6987
|
try {
|
|
6912
6988
|
const profiles = listProfiles(name);
|
|
6913
|
-
const configDir =
|
|
6914
|
-
const currentProfileFile =
|
|
6989
|
+
const configDir = join6(homedir5(), ".connectors", name.startsWith("connect-") ? name : `connect-${name}`);
|
|
6990
|
+
const currentProfileFile = join6(configDir, "current_profile");
|
|
6915
6991
|
let current = "default";
|
|
6916
|
-
if (
|
|
6992
|
+
if (existsSync5(currentProfileFile)) {
|
|
6917
6993
|
try {
|
|
6918
6994
|
current = readFileSync4(currentProfileFile, "utf-8").trim() || "default";
|
|
6919
6995
|
} catch {}
|
|
@@ -6962,16 +7038,16 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
6962
7038
|
}
|
|
6963
7039
|
if (path === "/api/export" && method === "GET") {
|
|
6964
7040
|
try {
|
|
6965
|
-
const connectDir =
|
|
7041
|
+
const connectDir = join6(homedir5(), ".connectors");
|
|
6966
7042
|
const result = {};
|
|
6967
|
-
if (
|
|
7043
|
+
if (existsSync5(connectDir)) {
|
|
6968
7044
|
const entries = readdirSync3(connectDir, { withFileTypes: true });
|
|
6969
7045
|
for (const entry of entries) {
|
|
6970
7046
|
if (!entry.isDirectory() || !entry.name.startsWith("connect-"))
|
|
6971
7047
|
continue;
|
|
6972
7048
|
const connectorName = entry.name.replace(/^connect-/, "");
|
|
6973
|
-
const profilesDir =
|
|
6974
|
-
if (!
|
|
7049
|
+
const profilesDir = join6(connectDir, entry.name, "profiles");
|
|
7050
|
+
if (!existsSync5(profilesDir))
|
|
6975
7051
|
continue;
|
|
6976
7052
|
const profiles = {};
|
|
6977
7053
|
const profileEntries = readdirSync3(profilesDir, { withFileTypes: true });
|
|
@@ -6979,13 +7055,13 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
6979
7055
|
if (pEntry.isFile() && pEntry.name.endsWith(".json")) {
|
|
6980
7056
|
const profileName = basename(pEntry.name, ".json");
|
|
6981
7057
|
try {
|
|
6982
|
-
const config = JSON.parse(readFileSync4(
|
|
7058
|
+
const config = JSON.parse(readFileSync4(join6(profilesDir, pEntry.name), "utf-8"));
|
|
6983
7059
|
profiles[profileName] = config;
|
|
6984
7060
|
} catch {}
|
|
6985
7061
|
}
|
|
6986
7062
|
if (pEntry.isDirectory()) {
|
|
6987
|
-
const configPath =
|
|
6988
|
-
if (
|
|
7063
|
+
const configPath = join6(profilesDir, pEntry.name, "config.json");
|
|
7064
|
+
if (existsSync5(configPath)) {
|
|
6989
7065
|
try {
|
|
6990
7066
|
const config = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
6991
7067
|
profiles[pEntry.name] = config;
|
|
@@ -7022,19 +7098,19 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
7022
7098
|
return json({ error: "Invalid import format: missing 'connectors' object" }, 400, port);
|
|
7023
7099
|
}
|
|
7024
7100
|
let imported = 0;
|
|
7025
|
-
const connectDir =
|
|
7101
|
+
const connectDir = join6(homedir5(), ".connectors");
|
|
7026
7102
|
for (const [connectorName, data] of Object.entries(body.connectors)) {
|
|
7027
7103
|
if (!isValidConnectorName(connectorName))
|
|
7028
7104
|
continue;
|
|
7029
7105
|
if (!data.profiles || typeof data.profiles !== "object")
|
|
7030
7106
|
continue;
|
|
7031
|
-
const connectorDir =
|
|
7032
|
-
const profilesDir =
|
|
7107
|
+
const connectorDir = join6(connectDir, `connect-${connectorName}`);
|
|
7108
|
+
const profilesDir = join6(connectorDir, "profiles");
|
|
7033
7109
|
for (const [profileName, config] of Object.entries(data.profiles)) {
|
|
7034
7110
|
if (!config || typeof config !== "object")
|
|
7035
7111
|
continue;
|
|
7036
|
-
|
|
7037
|
-
const profileFile =
|
|
7112
|
+
mkdirSync5(profilesDir, { recursive: true });
|
|
7113
|
+
const profileFile = join6(profilesDir, `${profileName}.json`);
|
|
7038
7114
|
writeFileSync3(profileFile, JSON.stringify(config, null, 2));
|
|
7039
7115
|
imported++;
|
|
7040
7116
|
}
|
|
@@ -7090,12 +7166,12 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
7090
7166
|
}
|
|
7091
7167
|
if (dashboardExists && (method === "GET" || method === "HEAD")) {
|
|
7092
7168
|
if (path !== "/") {
|
|
7093
|
-
const filePath =
|
|
7169
|
+
const filePath = join6(dashboardDir, path);
|
|
7094
7170
|
const res2 = serveStaticFile(filePath);
|
|
7095
7171
|
if (res2)
|
|
7096
7172
|
return res2;
|
|
7097
7173
|
}
|
|
7098
|
-
const indexPath =
|
|
7174
|
+
const indexPath = join6(dashboardDir, "index.html");
|
|
7099
7175
|
const res = serveStaticFile(indexPath);
|
|
7100
7176
|
if (res)
|
|
7101
7177
|
return res;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advisory file-based write lock for per-connector operations.
|
|
3
|
+
*
|
|
4
|
+
* Prevents concurrent agents from racing on token refresh and config writes.
|
|
5
|
+
* Reads remain unaffected — only write operations (saveApiKey, refreshOAuthToken,
|
|
6
|
+
* switchProfile) acquire the lock.
|
|
7
|
+
*
|
|
8
|
+
* Strategy: atomic O_EXCL file creation as the lock primitive (works on all
|
|
9
|
+
* platforms including macOS). Lock files live at:
|
|
10
|
+
* ~/.connectors/connect-{name}/.write.lock
|
|
11
|
+
*
|
|
12
|
+
* Callers that cannot acquire the lock within the timeout receive a LockTimeoutError.
|
|
13
|
+
*/
|
|
14
|
+
export declare class LockTimeoutError extends Error {
|
|
15
|
+
readonly connector: string;
|
|
16
|
+
constructor(connector: string);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Acquire a write lock for a connector, run the callback, then release.
|
|
20
|
+
* Throws LockTimeoutError if the lock cannot be acquired within LOCK_TIMEOUT_MS.
|
|
21
|
+
*/
|
|
22
|
+
export declare function withWriteLock<T>(connector: string, fn: () => T | Promise<T>): Promise<T>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/server/auth.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export declare function getEnvVars(name: string): {
|
|
|
42
42
|
/**
|
|
43
43
|
* Save an API key to a connector's profile
|
|
44
44
|
*/
|
|
45
|
-
export declare function saveApiKey(name: string, key: string, field?: string): void
|
|
45
|
+
export declare function saveApiKey(name: string, key: string, field?: string): Promise<void>;
|
|
46
46
|
/**
|
|
47
47
|
* Get OAuth client credentials for a connector
|
|
48
48
|
*/
|
|
@@ -63,7 +63,9 @@ export declare function validateOAuthState(state: string | null, expectedConnect
|
|
|
63
63
|
*/
|
|
64
64
|
export declare function exchangeOAuthCode(name: string, code: string, redirectUri: string): Promise<OAuthTokens>;
|
|
65
65
|
/**
|
|
66
|
-
* Refresh an OAuth token using the stored refresh token
|
|
66
|
+
* Refresh an OAuth token using the stored refresh token.
|
|
67
|
+
* Serialized with a per-connector write lock to prevent concurrent agents
|
|
68
|
+
* from racing on token refresh (double-refresh race condition).
|
|
67
69
|
*/
|
|
68
70
|
export declare function refreshOAuthToken(name: string): Promise<OAuthTokens>;
|
|
69
71
|
/**
|