mcp-scraper 0.8.0 → 0.10.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/dist/bin/api-server.cjs +682 -7
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +1 -1
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +1 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +133 -1
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/chunk-B7BAJU7K.js +7 -0
- package/dist/chunk-B7BAJU7K.js.map +1 -0
- package/dist/{chunk-ITJC3NN4.js → chunk-IRYBBEZJ.js} +134 -2
- package/dist/chunk-IRYBBEZJ.js.map +1 -0
- package/dist/{server-WBDVVHR6.js → server-KTA7CFX6.js} +544 -8
- package/dist/server-KTA7CFX6.js.map +1 -0
- package/docs/oauth-legal-review.md +38 -0
- package/package.json +1 -1
- package/dist/chunk-3MKSXDJ7.js +0 -7
- package/dist/chunk-3MKSXDJ7.js.map +0 -1
- package/dist/chunk-ITJC3NN4.js.map +0 -1
- package/dist/server-WBDVVHR6.js.map +0 -1
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
registerSerpIntelligenceCaptureTools,
|
|
35
35
|
sanitizeAttempts,
|
|
36
36
|
sanitizeHarvestResult
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-IRYBBEZJ.js";
|
|
38
38
|
import {
|
|
39
39
|
auditImages,
|
|
40
40
|
buildLinkReport,
|
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
RawMapsOverviewSchema,
|
|
71
71
|
RawMapsReviewStatsSchema
|
|
72
72
|
} from "./chunk-XGIPATLV.js";
|
|
73
|
-
import "./chunk-
|
|
73
|
+
import "./chunk-B7BAJU7K.js";
|
|
74
74
|
import {
|
|
75
75
|
completeExtractJob,
|
|
76
76
|
countSuccessfulPages,
|
|
@@ -20393,6 +20393,317 @@ async function dbUsage(identity, plan) {
|
|
|
20393
20393
|
};
|
|
20394
20394
|
}
|
|
20395
20395
|
|
|
20396
|
+
// src/api/nango-control.ts
|
|
20397
|
+
var DEFAULT_NANGO_CONTROL_URL = "https://mcp-scraper-scheduler.vercel.app";
|
|
20398
|
+
var DISABLED_NANGO_TOOLS = {
|
|
20399
|
+
"google-mail": /* @__PURE__ */ new Set(["list-filters"]),
|
|
20400
|
+
slack: /* @__PURE__ */ new Set(["search-files", "search-messages"])
|
|
20401
|
+
};
|
|
20402
|
+
var NangoControlError = class extends Error {
|
|
20403
|
+
status;
|
|
20404
|
+
constructor(message, status = 502) {
|
|
20405
|
+
super(message);
|
|
20406
|
+
this.name = "NangoControlError";
|
|
20407
|
+
this.status = status;
|
|
20408
|
+
}
|
|
20409
|
+
};
|
|
20410
|
+
var ScheduleConnectionValidationError = class extends Error {
|
|
20411
|
+
constructor(message) {
|
|
20412
|
+
super(message);
|
|
20413
|
+
this.name = "ScheduleConnectionValidationError";
|
|
20414
|
+
}
|
|
20415
|
+
};
|
|
20416
|
+
function controlBaseUrl() {
|
|
20417
|
+
return (process.env.NANGO_CONTROL_URL?.trim() || DEFAULT_NANGO_CONTROL_URL).replace(/\/$/, "");
|
|
20418
|
+
}
|
|
20419
|
+
function controlSecret() {
|
|
20420
|
+
const secret2 = process.env.SCHEDULE_INTEGRATIONS_SECRET?.trim();
|
|
20421
|
+
if (!secret2) throw new NangoControlError("Scheduled service connections are not configured.", 503);
|
|
20422
|
+
return secret2;
|
|
20423
|
+
}
|
|
20424
|
+
function isRecord(value) {
|
|
20425
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
20426
|
+
}
|
|
20427
|
+
function unwrapData(value) {
|
|
20428
|
+
if (isRecord(value) && isRecord(value.data)) return value.data;
|
|
20429
|
+
return value;
|
|
20430
|
+
}
|
|
20431
|
+
function cleanString(value, max = 300) {
|
|
20432
|
+
if (typeof value !== "string") return null;
|
|
20433
|
+
const trimmed = value.trim();
|
|
20434
|
+
return trimmed ? trimmed.slice(0, max) : null;
|
|
20435
|
+
}
|
|
20436
|
+
function firstString(record, keys, max = 300) {
|
|
20437
|
+
for (const key of keys) {
|
|
20438
|
+
const value = cleanString(record[key], max);
|
|
20439
|
+
if (value) return value;
|
|
20440
|
+
}
|
|
20441
|
+
return null;
|
|
20442
|
+
}
|
|
20443
|
+
function cleanTools(value) {
|
|
20444
|
+
if (!Array.isArray(value)) return [];
|
|
20445
|
+
const tools = value.map((tool) => cleanString(tool, 200)).filter((tool) => !!tool);
|
|
20446
|
+
return [...new Set(tools)].slice(0, 200);
|
|
20447
|
+
}
|
|
20448
|
+
function cleanStringArray(value, maxItems = 20, maxLength = 100) {
|
|
20449
|
+
if (!Array.isArray(value)) return [];
|
|
20450
|
+
const items = value.map((item) => cleanString(item, maxLength)).filter((item) => !!item);
|
|
20451
|
+
return [...new Set(items)].slice(0, maxItems);
|
|
20452
|
+
}
|
|
20453
|
+
function cleanHttpsUrl(value) {
|
|
20454
|
+
const candidate = cleanString(value, 2e3);
|
|
20455
|
+
if (!candidate) return null;
|
|
20456
|
+
try {
|
|
20457
|
+
const url = new URL(candidate);
|
|
20458
|
+
return url.protocol === "https:" ? url.toString() : null;
|
|
20459
|
+
} catch {
|
|
20460
|
+
return null;
|
|
20461
|
+
}
|
|
20462
|
+
}
|
|
20463
|
+
function arrayFromPayload(value, keys) {
|
|
20464
|
+
const unwrapped = unwrapData(value);
|
|
20465
|
+
if (Array.isArray(unwrapped)) return unwrapped;
|
|
20466
|
+
if (!isRecord(unwrapped)) return [];
|
|
20467
|
+
for (const key of keys) {
|
|
20468
|
+
if (Array.isArray(unwrapped[key])) return unwrapped[key];
|
|
20469
|
+
}
|
|
20470
|
+
return [];
|
|
20471
|
+
}
|
|
20472
|
+
async function controlRequest(path5, init) {
|
|
20473
|
+
const response = await fetch(`${controlBaseUrl()}${path5}`, {
|
|
20474
|
+
...init,
|
|
20475
|
+
headers: {
|
|
20476
|
+
accept: "application/json",
|
|
20477
|
+
authorization: `Bearer ${controlSecret()}`,
|
|
20478
|
+
...init?.body ? { "content-type": "application/json" } : {},
|
|
20479
|
+
...init?.headers ?? {}
|
|
20480
|
+
},
|
|
20481
|
+
signal: AbortSignal.timeout(2e4)
|
|
20482
|
+
}).catch((err) => {
|
|
20483
|
+
throw new NangoControlError(`Scheduled service connection control is unavailable: ${err instanceof Error ? err.message : "network error"}`);
|
|
20484
|
+
});
|
|
20485
|
+
const text = await response.text();
|
|
20486
|
+
let body = {};
|
|
20487
|
+
try {
|
|
20488
|
+
body = text ? JSON.parse(text) : {};
|
|
20489
|
+
} catch {
|
|
20490
|
+
body = {};
|
|
20491
|
+
}
|
|
20492
|
+
if (!response.ok) {
|
|
20493
|
+
const upstream = isRecord(body) ? cleanString(body.error, 300) : null;
|
|
20494
|
+
throw new NangoControlError(upstream || `Scheduled service connection control failed (${response.status}).`);
|
|
20495
|
+
}
|
|
20496
|
+
return body;
|
|
20497
|
+
}
|
|
20498
|
+
function sanitizeScheduleConnectionSelections(value) {
|
|
20499
|
+
if (value == null) return [];
|
|
20500
|
+
if (!Array.isArray(value)) throw new ScheduleConnectionValidationError("connections must be an array.");
|
|
20501
|
+
if (value.length > 20) throw new ScheduleConnectionValidationError("A scheduled action can use at most 20 service connections.");
|
|
20502
|
+
const selections = [];
|
|
20503
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20504
|
+
for (const item of value) {
|
|
20505
|
+
if (!isRecord(item)) throw new ScheduleConnectionValidationError("Each service connection must be an object.");
|
|
20506
|
+
const connectionId = firstString(item, ["connectionId", "connection_id"]);
|
|
20507
|
+
const providerConfigKey = firstString(item, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id"]);
|
|
20508
|
+
const allowedTools = cleanTools(item.allowedTools ?? item.allowed_tools ?? item.allowedCapabilities ?? item.allowed_capabilities);
|
|
20509
|
+
if (!connectionId || !providerConfigKey) throw new ScheduleConnectionValidationError("Each service connection needs a connectionId and providerConfigKey.");
|
|
20510
|
+
if (allowedTools.length === 0) throw new ScheduleConnectionValidationError("Each selected service needs at least one allowed tool.");
|
|
20511
|
+
const key = `${providerConfigKey}:${connectionId}`;
|
|
20512
|
+
if (seen.has(key)) continue;
|
|
20513
|
+
seen.add(key);
|
|
20514
|
+
selections.push({ connectionId, providerConfigKey, allowedTools });
|
|
20515
|
+
}
|
|
20516
|
+
return selections;
|
|
20517
|
+
}
|
|
20518
|
+
async function getNangoCatalog() {
|
|
20519
|
+
const body = await controlRequest("/api/internal/nango/catalog");
|
|
20520
|
+
const rows = arrayFromPayload(body, ["providers", "catalog", "integrations", "services"]);
|
|
20521
|
+
const result = [];
|
|
20522
|
+
for (const row of rows) {
|
|
20523
|
+
if (!isRecord(row)) continue;
|
|
20524
|
+
const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "id"]);
|
|
20525
|
+
if (!providerConfigKey) continue;
|
|
20526
|
+
const provider = firstString(row, ["provider"]);
|
|
20527
|
+
const label = firstString(row, ["label", "displayName", "display_name", "name"]) || providerConfigKey;
|
|
20528
|
+
const description = firstString(row, ["description"], 500);
|
|
20529
|
+
const logoUrl = cleanHttpsUrl(row.logoUrl ?? row.logo_url ?? row.logo);
|
|
20530
|
+
const docsUrl = cleanHttpsUrl(row.docsUrl ?? row.docs_url ?? row.docs);
|
|
20531
|
+
const authMode = firstString(row, ["authMode", "auth_mode"], 100);
|
|
20532
|
+
const categories = cleanStringArray(row.categories);
|
|
20533
|
+
const disabledTools = DISABLED_NANGO_TOOLS[providerConfigKey];
|
|
20534
|
+
const safeDefaultAllowedTools = cleanTools(
|
|
20535
|
+
row.safeDefaultAllowedTools ?? row.safe_default_allowed_tools ?? row.defaultAllowedTools ?? row.default_allowed_tools ?? row.allowedTools ?? row.allowed_tools
|
|
20536
|
+
).filter((tool) => !disabledTools?.has(tool));
|
|
20537
|
+
result.push({
|
|
20538
|
+
providerConfigKey,
|
|
20539
|
+
provider,
|
|
20540
|
+
label,
|
|
20541
|
+
description,
|
|
20542
|
+
logoUrl,
|
|
20543
|
+
docsUrl,
|
|
20544
|
+
authMode,
|
|
20545
|
+
categories,
|
|
20546
|
+
safeDefaultAllowedTools
|
|
20547
|
+
});
|
|
20548
|
+
}
|
|
20549
|
+
return result;
|
|
20550
|
+
}
|
|
20551
|
+
async function getNangoConnections(identity) {
|
|
20552
|
+
const query = new URLSearchParams({ identity }).toString();
|
|
20553
|
+
const body = await controlRequest(`/api/internal/nango/connections?${query}`);
|
|
20554
|
+
const rows = arrayFromPayload(body, ["connections"]);
|
|
20555
|
+
const result = [];
|
|
20556
|
+
for (const row of rows) {
|
|
20557
|
+
if (!isRecord(row)) continue;
|
|
20558
|
+
const connectionId = firstString(row, ["connectionId", "connection_id", "id"]);
|
|
20559
|
+
const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
|
|
20560
|
+
if (!connectionId || !providerConfigKey) continue;
|
|
20561
|
+
const provider = firstString(row, ["provider"]);
|
|
20562
|
+
const label = firstString(row, ["label", "accountLabel", "account_label", "displayName", "display_name", "endUserEmail", "end_user_email"]) || providerConfigKey;
|
|
20563
|
+
const rawStatus = firstString(row, ["status"], 64) || "connected";
|
|
20564
|
+
const reconnectRequired = row.reconnectRequired === true || row.reconnect_required === true || rawStatus === "needs_reauth" || rawStatus === "reauth_required" || rawStatus === "invalid" || rawStatus === "error" || rawStatus === "revoked" || rawStatus === "disabled";
|
|
20565
|
+
result.push({
|
|
20566
|
+
connectionId,
|
|
20567
|
+
providerConfigKey,
|
|
20568
|
+
provider,
|
|
20569
|
+
label,
|
|
20570
|
+
status: reconnectRequired ? "needs_reauth" : "connected",
|
|
20571
|
+
reconnectRequired,
|
|
20572
|
+
actionsEnabled: row.actionsEnabled === true || row.actions_enabled === true,
|
|
20573
|
+
readTools: cleanTools(row.readTools ?? row.read_tools),
|
|
20574
|
+
vaultName: firstString(row, ["vaultName", "vault_name"], 100),
|
|
20575
|
+
tableName: firstString(row, ["tableName", "table_name"], 100),
|
|
20576
|
+
createdAt: firstString(row, ["createdAt", "created_at"], 100),
|
|
20577
|
+
updatedAt: firstString(row, ["updatedAt", "updated_at"], 100)
|
|
20578
|
+
});
|
|
20579
|
+
}
|
|
20580
|
+
return result;
|
|
20581
|
+
}
|
|
20582
|
+
function sanitizeConnectSession(body) {
|
|
20583
|
+
const data = unwrapData(body);
|
|
20584
|
+
if (!isRecord(data)) throw new NangoControlError("The connection service returned an invalid connect session.");
|
|
20585
|
+
const connectLink = firstString(data, ["connectLink", "connect_link"], 2e3);
|
|
20586
|
+
if (!connectLink) throw new NangoControlError("The connection service did not return a connect link.");
|
|
20587
|
+
return {
|
|
20588
|
+
connectLink,
|
|
20589
|
+
sessionToken: firstString(data, ["sessionToken", "session_token", "token"], 2e3),
|
|
20590
|
+
expiresAt: firstString(data, ["expiresAt", "expires_at"], 100)
|
|
20591
|
+
};
|
|
20592
|
+
}
|
|
20593
|
+
async function createNangoConnectSession(identity, providerConfigKey) {
|
|
20594
|
+
const body = await controlRequest("/api/internal/nango/connect-session", {
|
|
20595
|
+
method: "POST",
|
|
20596
|
+
body: JSON.stringify({ identity, email: identity, provider: providerConfigKey })
|
|
20597
|
+
});
|
|
20598
|
+
return sanitizeConnectSession(body);
|
|
20599
|
+
}
|
|
20600
|
+
async function createNangoReconnectSession(identity, connectionId) {
|
|
20601
|
+
const body = await controlRequest("/api/internal/nango/reconnect-session", {
|
|
20602
|
+
method: "POST",
|
|
20603
|
+
body: JSON.stringify({ identity, connectionId, email: identity })
|
|
20604
|
+
});
|
|
20605
|
+
return sanitizeConnectSession(body);
|
|
20606
|
+
}
|
|
20607
|
+
function sanitizeBindingRows(body, defaultScheduleActionId) {
|
|
20608
|
+
const data = unwrapData(body);
|
|
20609
|
+
const flattened = [];
|
|
20610
|
+
if (isRecord(data) && isRecord(data.bindings) && !Array.isArray(data.bindings)) {
|
|
20611
|
+
for (const [scheduleActionId, value] of Object.entries(data.bindings)) {
|
|
20612
|
+
if (Array.isArray(value)) value.forEach((row) => flattened.push({ row, scheduleActionId }));
|
|
20613
|
+
}
|
|
20614
|
+
} else {
|
|
20615
|
+
const rows = arrayFromPayload(data, ["bindings", "connections"]);
|
|
20616
|
+
for (const row of rows) {
|
|
20617
|
+
if (isRecord(row) && Array.isArray(row.connections)) {
|
|
20618
|
+
const scheduleActionId = firstString(row, ["scheduleActionId", "schedule_action_id", "scheduleId", "schedule_id"]) || defaultScheduleActionId;
|
|
20619
|
+
row.connections.forEach((connection) => flattened.push({ row: connection, scheduleActionId }));
|
|
20620
|
+
} else {
|
|
20621
|
+
flattened.push({ row, scheduleActionId: defaultScheduleActionId });
|
|
20622
|
+
}
|
|
20623
|
+
}
|
|
20624
|
+
}
|
|
20625
|
+
const result = [];
|
|
20626
|
+
for (const item of flattened) {
|
|
20627
|
+
if (!isRecord(item.row)) continue;
|
|
20628
|
+
const connectionId = firstString(item.row, ["connectionId", "connection_id"]);
|
|
20629
|
+
const providerConfigKey = firstString(item.row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
|
|
20630
|
+
if (!connectionId || !providerConfigKey) continue;
|
|
20631
|
+
result.push({
|
|
20632
|
+
scheduleActionId: firstString(item.row, ["scheduleActionId", "schedule_action_id", "scheduleId", "schedule_id"]) || item.scheduleActionId,
|
|
20633
|
+
connectionId,
|
|
20634
|
+
providerConfigKey,
|
|
20635
|
+
allowedTools: cleanTools(item.row.allowedTools ?? item.row.allowed_tools ?? item.row.allowedCapabilities ?? item.row.allowed_capabilities),
|
|
20636
|
+
label: firstString(item.row, ["label", "accountLabel", "account_label", "displayName", "display_name"])
|
|
20637
|
+
});
|
|
20638
|
+
}
|
|
20639
|
+
return result;
|
|
20640
|
+
}
|
|
20641
|
+
async function getScheduleConnectionBindings(identity, scheduleActionId) {
|
|
20642
|
+
const query = new URLSearchParams({ identity, scheduleId: scheduleActionId });
|
|
20643
|
+
const body = await controlRequest(`/api/internal/nango/bindings?${query.toString()}`);
|
|
20644
|
+
return sanitizeBindingRows(body, scheduleActionId);
|
|
20645
|
+
}
|
|
20646
|
+
async function putScheduleConnectionBindings(identity, scheduleActionId, connections) {
|
|
20647
|
+
const body = await controlRequest("/api/internal/nango/bindings", {
|
|
20648
|
+
method: "PUT",
|
|
20649
|
+
body: JSON.stringify({
|
|
20650
|
+
identity,
|
|
20651
|
+
scheduleId: scheduleActionId,
|
|
20652
|
+
connections: connections.map((connection) => ({
|
|
20653
|
+
connectionId: connection.connectionId,
|
|
20654
|
+
allowedTools: connection.allowedTools
|
|
20655
|
+
}))
|
|
20656
|
+
})
|
|
20657
|
+
});
|
|
20658
|
+
const sanitized = sanitizeBindingRows(body, scheduleActionId);
|
|
20659
|
+
const expectedByConnectionId = new Map(connections.map((connection) => [
|
|
20660
|
+
connection.connectionId,
|
|
20661
|
+
{
|
|
20662
|
+
providerConfigKey: connection.providerConfigKey,
|
|
20663
|
+
allowedTools: [...connection.allowedTools].sort()
|
|
20664
|
+
}
|
|
20665
|
+
]));
|
|
20666
|
+
const responseMatchesRequest = sanitized.length === connections.length && sanitized.every((binding) => {
|
|
20667
|
+
const expected = expectedByConnectionId.get(binding.connectionId);
|
|
20668
|
+
return !!expected && binding.scheduleActionId === scheduleActionId && binding.providerConfigKey === expected.providerConfigKey && [...binding.allowedTools].sort().join("\0") === expected.allowedTools.join("\0");
|
|
20669
|
+
});
|
|
20670
|
+
if (!responseMatchesRequest) {
|
|
20671
|
+
throw new NangoControlError("Scheduled service connection control returned an invalid binding response.");
|
|
20672
|
+
}
|
|
20673
|
+
return sanitized;
|
|
20674
|
+
}
|
|
20675
|
+
async function deleteScheduleConnectionBindings(identity, scheduleActionId) {
|
|
20676
|
+
await controlRequest("/api/internal/nango/bindings", {
|
|
20677
|
+
method: "DELETE",
|
|
20678
|
+
body: JSON.stringify({ identity, scheduleId: scheduleActionId })
|
|
20679
|
+
});
|
|
20680
|
+
}
|
|
20681
|
+
async function setScheduleConnectionActionsEnabled(identity, connectionId, enabled) {
|
|
20682
|
+
const body = await controlRequest("/api/internal/nango/connections/actions/enable", {
|
|
20683
|
+
method: "POST",
|
|
20684
|
+
body: JSON.stringify({ identity, connectionId, enabled })
|
|
20685
|
+
});
|
|
20686
|
+
const data = unwrapData(body);
|
|
20687
|
+
if (!isRecord(data) || !isRecord(data.connection)) return enabled;
|
|
20688
|
+
return data.connection.actionsEnabled === true;
|
|
20689
|
+
}
|
|
20690
|
+
async function callScheduleConnectionAction(identity, connectionId, input) {
|
|
20691
|
+
const body = await controlRequest("/api/internal/nango/connections/actions/call", {
|
|
20692
|
+
method: "POST",
|
|
20693
|
+
body: JSON.stringify({ identity, connectionId, input })
|
|
20694
|
+
});
|
|
20695
|
+
const data = unwrapData(body);
|
|
20696
|
+
return isRecord(data) ? data.result ?? data : data;
|
|
20697
|
+
}
|
|
20698
|
+
async function callScheduleConnectionRead(identity, connectionId, tool, args) {
|
|
20699
|
+
const body = await controlRequest("/api/internal/nango/connections/actions/read", {
|
|
20700
|
+
method: "POST",
|
|
20701
|
+
body: JSON.stringify({ identity, connectionId, tool, args: args ?? {} })
|
|
20702
|
+
});
|
|
20703
|
+
const data = unwrapData(body);
|
|
20704
|
+
return isRecord(data) ? data.result ?? data : data;
|
|
20705
|
+
}
|
|
20706
|
+
|
|
20396
20707
|
// src/api/server.ts
|
|
20397
20708
|
var secureCookies2 = process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
|
|
20398
20709
|
var isProduction2 = secureCookies2;
|
|
@@ -21005,6 +21316,20 @@ app.post("/schedule-checkout", auth2, async (c) => {
|
|
|
21005
21316
|
return c.json({ error: message }, 500);
|
|
21006
21317
|
}
|
|
21007
21318
|
});
|
|
21319
|
+
function scheduleConnectionError(c, err, fallback) {
|
|
21320
|
+
if (err instanceof ScheduleConnectionValidationError) return c.json({ ok: false, error: err.message }, 400);
|
|
21321
|
+
if (err instanceof NangoControlError) return c.json({ ok: false, error: err.message }, err.status);
|
|
21322
|
+
console.error("[schedule-connections]", err instanceof Error ? err.message : String(err));
|
|
21323
|
+
return c.json({ ok: false, error: fallback }, 502);
|
|
21324
|
+
}
|
|
21325
|
+
function providerConfigKeyFrom(value) {
|
|
21326
|
+
if (typeof value !== "string") return null;
|
|
21327
|
+
const key = value.trim();
|
|
21328
|
+
return key && key.length <= 200 ? key : null;
|
|
21329
|
+
}
|
|
21330
|
+
function bindingsForSchedule(bindings, scheduleActionId) {
|
|
21331
|
+
return bindings.filter((binding) => binding.scheduleActionId === scheduleActionId);
|
|
21332
|
+
}
|
|
21008
21333
|
app.get("/schedule-status", auth2, async (c) => {
|
|
21009
21334
|
try {
|
|
21010
21335
|
const user = c.get("user");
|
|
@@ -21018,6 +21343,139 @@ app.get("/schedule-status", auth2, async (c) => {
|
|
|
21018
21343
|
return c.json({ error: message }, 500);
|
|
21019
21344
|
}
|
|
21020
21345
|
});
|
|
21346
|
+
app.get("/schedule-connection-catalog", auth2, async (c) => {
|
|
21347
|
+
try {
|
|
21348
|
+
return c.json({ ok: true, catalog: await getNangoCatalog() });
|
|
21349
|
+
} catch (err) {
|
|
21350
|
+
return scheduleConnectionError(c, err, "Unable to load the service connection catalog.");
|
|
21351
|
+
}
|
|
21352
|
+
});
|
|
21353
|
+
app.get("/schedule-connections", auth2, async (c) => {
|
|
21354
|
+
try {
|
|
21355
|
+
const user = c.get("user");
|
|
21356
|
+
return c.json({ ok: true, connections: await getNangoConnections(user.email) });
|
|
21357
|
+
} catch (err) {
|
|
21358
|
+
return scheduleConnectionError(c, err, "Unable to load service connections.");
|
|
21359
|
+
}
|
|
21360
|
+
});
|
|
21361
|
+
app.post("/schedule-connections/session", auth2, async (c) => {
|
|
21362
|
+
const user = c.get("user");
|
|
21363
|
+
const body = await c.req.json().catch(() => ({}));
|
|
21364
|
+
const providerConfigKey = providerConfigKeyFrom(body.providerConfigKey);
|
|
21365
|
+
if (!providerConfigKey) return c.json({ ok: false, error: "providerConfigKey is required." }, 400);
|
|
21366
|
+
try {
|
|
21367
|
+
const session = await createNangoConnectSession(user.email, providerConfigKey);
|
|
21368
|
+
return c.json({ ok: true, ...session });
|
|
21369
|
+
} catch (err) {
|
|
21370
|
+
return scheduleConnectionError(c, err, "Unable to start the service connection flow.");
|
|
21371
|
+
}
|
|
21372
|
+
});
|
|
21373
|
+
app.post("/schedule-connections/:id/reconnect-session", auth2, async (c) => {
|
|
21374
|
+
const user = c.get("user");
|
|
21375
|
+
try {
|
|
21376
|
+
const session = await createNangoReconnectSession(user.email, c.req.param("id"));
|
|
21377
|
+
return c.json({ ok: true, ...session });
|
|
21378
|
+
} catch (err) {
|
|
21379
|
+
return scheduleConnectionError(c, err, "Unable to start the service reconnection flow.");
|
|
21380
|
+
}
|
|
21381
|
+
});
|
|
21382
|
+
app.post("/schedule-connections/:id/enable-actions", auth2, async (c) => {
|
|
21383
|
+
const user = c.get("user");
|
|
21384
|
+
const body = await c.req.json().catch(() => ({}));
|
|
21385
|
+
if (typeof body.enabled !== "boolean") return c.json({ ok: false, error: "enabled must be a boolean." }, 400);
|
|
21386
|
+
try {
|
|
21387
|
+
const actionsEnabled = await setScheduleConnectionActionsEnabled(user.email, c.req.param("id"), body.enabled);
|
|
21388
|
+
return c.json({ ok: true, actionsEnabled });
|
|
21389
|
+
} catch (err) {
|
|
21390
|
+
return scheduleConnectionError(c, err, "Unable to update this connection's action setting.");
|
|
21391
|
+
}
|
|
21392
|
+
});
|
|
21393
|
+
app.post("/schedule-connections/actions/slack/send-message", auth2, async (c) => {
|
|
21394
|
+
const user = c.get("user");
|
|
21395
|
+
const body = await c.req.json().catch(() => ({}));
|
|
21396
|
+
const connectionId = providerConfigKeyFrom(body.connectionId);
|
|
21397
|
+
if (!connectionId || typeof body.channel !== "string" || typeof body.text !== "string") {
|
|
21398
|
+
return c.json({ ok: false, error: "connectionId, channel, and text are required." }, 400);
|
|
21399
|
+
}
|
|
21400
|
+
try {
|
|
21401
|
+
const result = await callScheduleConnectionAction(user.email, connectionId, { channel: body.channel, text: body.text });
|
|
21402
|
+
return c.json({ ok: true, result });
|
|
21403
|
+
} catch (err) {
|
|
21404
|
+
return scheduleConnectionError(c, err, "Unable to send the Slack message.");
|
|
21405
|
+
}
|
|
21406
|
+
});
|
|
21407
|
+
app.post("/schedule-connections/actions/gmail/send-message", auth2, async (c) => {
|
|
21408
|
+
const user = c.get("user");
|
|
21409
|
+
const body = await c.req.json().catch(() => ({}));
|
|
21410
|
+
const connectionId = providerConfigKeyFrom(body.connectionId);
|
|
21411
|
+
if (!connectionId || typeof body.to !== "string" || typeof body.subject !== "string" || typeof body.body !== "string") {
|
|
21412
|
+
return c.json({ ok: false, error: "connectionId, to, subject, and body are required." }, 400);
|
|
21413
|
+
}
|
|
21414
|
+
try {
|
|
21415
|
+
const result = await callScheduleConnectionAction(user.email, connectionId, { to: body.to, subject: body.subject, body: body.body });
|
|
21416
|
+
return c.json({ ok: true, result });
|
|
21417
|
+
} catch (err) {
|
|
21418
|
+
return scheduleConnectionError(c, err, "Unable to send the email.");
|
|
21419
|
+
}
|
|
21420
|
+
});
|
|
21421
|
+
app.post("/schedule-connections/actions/google-calendar/create-event", auth2, async (c) => {
|
|
21422
|
+
const user = c.get("user");
|
|
21423
|
+
const body = await c.req.json().catch(() => ({}));
|
|
21424
|
+
const connectionId = providerConfigKeyFrom(body.connectionId);
|
|
21425
|
+
if (!connectionId || typeof body.summary !== "string" || typeof body.startDateTime !== "string" || typeof body.endDateTime !== "string") {
|
|
21426
|
+
return c.json({ ok: false, error: "connectionId, summary, startDateTime, and endDateTime are required." }, 400);
|
|
21427
|
+
}
|
|
21428
|
+
const timeZone = typeof body.timeZone === "string" ? body.timeZone : void 0;
|
|
21429
|
+
try {
|
|
21430
|
+
const result = await callScheduleConnectionAction(user.email, connectionId, {
|
|
21431
|
+
calendarId: typeof body.calendarId === "string" ? body.calendarId : "primary",
|
|
21432
|
+
summary: body.summary,
|
|
21433
|
+
description: typeof body.description === "string" ? body.description : void 0,
|
|
21434
|
+
location: typeof body.location === "string" ? body.location : void 0,
|
|
21435
|
+
start: { dateTime: body.startDateTime, timeZone },
|
|
21436
|
+
end: { dateTime: body.endDateTime, timeZone }
|
|
21437
|
+
});
|
|
21438
|
+
return c.json({ ok: true, result });
|
|
21439
|
+
} catch (err) {
|
|
21440
|
+
return scheduleConnectionError(c, err, "Unable to create the calendar event.");
|
|
21441
|
+
}
|
|
21442
|
+
});
|
|
21443
|
+
app.post("/schedule-connections/actions/zoom/create-meeting", auth2, async (c) => {
|
|
21444
|
+
const user = c.get("user");
|
|
21445
|
+
const body = await c.req.json().catch(() => ({}));
|
|
21446
|
+
const connectionId = providerConfigKeyFrom(body.connectionId);
|
|
21447
|
+
if (!connectionId || typeof body.topic !== "string" || typeof body.startDateTime !== "string") {
|
|
21448
|
+
return c.json({ ok: false, error: "connectionId, topic, and startDateTime are required." }, 400);
|
|
21449
|
+
}
|
|
21450
|
+
try {
|
|
21451
|
+
const result = await callScheduleConnectionAction(user.email, connectionId, {
|
|
21452
|
+
topic: body.topic,
|
|
21453
|
+
startDateTime: body.startDateTime,
|
|
21454
|
+
durationMinutes: typeof body.durationMinutes === "number" ? body.durationMinutes : 30,
|
|
21455
|
+
timezone: typeof body.timezone === "string" ? body.timezone : void 0,
|
|
21456
|
+
agenda: typeof body.agenda === "string" ? body.agenda : void 0
|
|
21457
|
+
});
|
|
21458
|
+
return c.json({ ok: true, result });
|
|
21459
|
+
} catch (err) {
|
|
21460
|
+
return scheduleConnectionError(c, err, "Unable to create the Zoom meeting.");
|
|
21461
|
+
}
|
|
21462
|
+
});
|
|
21463
|
+
app.post("/schedule-connections/actions/read", auth2, async (c) => {
|
|
21464
|
+
const user = c.get("user");
|
|
21465
|
+
const body = await c.req.json().catch(() => ({}));
|
|
21466
|
+
const connectionId = providerConfigKeyFrom(body.connectionId);
|
|
21467
|
+
const tool = providerConfigKeyFrom(body.tool);
|
|
21468
|
+
if (!connectionId || !tool) {
|
|
21469
|
+
return c.json({ ok: false, error: "connectionId and tool are required." }, 400);
|
|
21470
|
+
}
|
|
21471
|
+
const args = body.args && typeof body.args === "object" && !Array.isArray(body.args) ? body.args : {};
|
|
21472
|
+
try {
|
|
21473
|
+
const result = await callScheduleConnectionRead(user.email, connectionId, tool, args);
|
|
21474
|
+
return c.json({ ok: true, result });
|
|
21475
|
+
} catch (err) {
|
|
21476
|
+
return scheduleConnectionError(c, err, "Unable to read from this connection.");
|
|
21477
|
+
}
|
|
21478
|
+
});
|
|
21021
21479
|
app.post("/schedule-link", auth2, async (c) => {
|
|
21022
21480
|
try {
|
|
21023
21481
|
const user = c.get("user");
|
|
@@ -21036,15 +21494,58 @@ app.get("/schedule-actions", auth2, async (c) => {
|
|
|
21036
21494
|
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
21037
21495
|
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
21038
21496
|
const r = await memoryCall("listScheduledActionsTool", {}, key);
|
|
21039
|
-
return c.json(r);
|
|
21497
|
+
if (!r.ok || !Array.isArray(r.actions)) return c.json(r);
|
|
21498
|
+
try {
|
|
21499
|
+
const actions = await Promise.all(r.actions.map(async (action) => {
|
|
21500
|
+
const id = typeof action.id === "string" ? action.id : "";
|
|
21501
|
+
if (!id) return { ...action, connections: [] };
|
|
21502
|
+
const bindings = await getScheduleConnectionBindings(user.email, id);
|
|
21503
|
+
return { ...action, connections: bindingsForSchedule(bindings, id) };
|
|
21504
|
+
}));
|
|
21505
|
+
return c.json({
|
|
21506
|
+
...r,
|
|
21507
|
+
actions
|
|
21508
|
+
});
|
|
21509
|
+
} catch (err) {
|
|
21510
|
+
console.warn("[schedule-actions] connection bindings unavailable:", err instanceof Error ? err.message : String(err));
|
|
21511
|
+
return c.json({
|
|
21512
|
+
...r,
|
|
21513
|
+
actions: r.actions.map((action) => ({ ...action, connections: [] })),
|
|
21514
|
+
connectionBindingsUnavailable: true
|
|
21515
|
+
});
|
|
21516
|
+
}
|
|
21040
21517
|
});
|
|
21041
21518
|
app.post("/schedule-actions", auth2, async (c) => {
|
|
21042
21519
|
const user = c.get("user");
|
|
21043
21520
|
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
21044
21521
|
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
21045
21522
|
const body = await c.req.json().catch(() => ({}));
|
|
21046
|
-
const
|
|
21047
|
-
|
|
21523
|
+
const hasConnections = Object.prototype.hasOwnProperty.call(body, "connections");
|
|
21524
|
+
let connections;
|
|
21525
|
+
try {
|
|
21526
|
+
connections = sanitizeScheduleConnectionSelections(body.connections);
|
|
21527
|
+
} catch (err) {
|
|
21528
|
+
return scheduleConnectionError(c, err, "Invalid service connections.");
|
|
21529
|
+
}
|
|
21530
|
+
const actionBody = { ...body };
|
|
21531
|
+
delete actionBody.connections;
|
|
21532
|
+
const r = await memoryCall("createScheduledActionTool", actionBody, key);
|
|
21533
|
+
if (!r.ok || !hasConnections) return c.json(r);
|
|
21534
|
+
if (connections.length === 0) return c.json({ ...r, connections: [] });
|
|
21535
|
+
if (!r.id) {
|
|
21536
|
+
console.error("[schedule-actions] create returned no id while service connections were requested");
|
|
21537
|
+
return c.json({ ok: false, error: "The scheduled action was created without a usable id; service connections were not attached." }, 502);
|
|
21538
|
+
}
|
|
21539
|
+
try {
|
|
21540
|
+
const bound = await putScheduleConnectionBindings(user.email, r.id, connections);
|
|
21541
|
+
return c.json({ ...r, connections: bound });
|
|
21542
|
+
} catch (err) {
|
|
21543
|
+
const rollback = await memoryCall("deleteScheduledActionTool", { id: r.id }, key).catch(() => ({ ok: false }));
|
|
21544
|
+
if (!rollback.ok) console.error("[schedule-actions] failed to compensate action after connection binding failure", r.id);
|
|
21545
|
+
const response = scheduleConnectionError(c, err, "The scheduled action was not created because its service connections could not be attached.");
|
|
21546
|
+
response.headers.set("x-schedule-create-rolled-back", rollback.ok ? "true" : "false");
|
|
21547
|
+
return response;
|
|
21548
|
+
}
|
|
21048
21549
|
});
|
|
21049
21550
|
app.post("/schedule-actions/propose", auth2, async (c) => {
|
|
21050
21551
|
const user = c.get("user");
|
|
@@ -21054,6 +21555,33 @@ app.post("/schedule-actions/propose", auth2, async (c) => {
|
|
|
21054
21555
|
const r = await memoryCall("proposeScheduledActionTool", body, key);
|
|
21055
21556
|
return c.json(r);
|
|
21056
21557
|
});
|
|
21558
|
+
app.get("/schedule-actions/:id/connections", auth2, async (c) => {
|
|
21559
|
+
try {
|
|
21560
|
+
const user = c.get("user");
|
|
21561
|
+
const scheduleActionId = c.req.param("id");
|
|
21562
|
+
const bindings = await getScheduleConnectionBindings(user.email, scheduleActionId);
|
|
21563
|
+
return c.json({ ok: true, connections: bindingsForSchedule(bindings, scheduleActionId) });
|
|
21564
|
+
} catch (err) {
|
|
21565
|
+
return scheduleConnectionError(c, err, "Unable to load scheduled action service connections.");
|
|
21566
|
+
}
|
|
21567
|
+
});
|
|
21568
|
+
app.put("/schedule-actions/:id/connections", auth2, async (c) => {
|
|
21569
|
+
const user = c.get("user");
|
|
21570
|
+
const body = await c.req.json().catch(() => ({}));
|
|
21571
|
+
let connections;
|
|
21572
|
+
try {
|
|
21573
|
+
connections = sanitizeScheduleConnectionSelections(body.connections);
|
|
21574
|
+
} catch (err) {
|
|
21575
|
+
return scheduleConnectionError(c, err, "Invalid service connections.");
|
|
21576
|
+
}
|
|
21577
|
+
try {
|
|
21578
|
+
const scheduleActionId = c.req.param("id");
|
|
21579
|
+
const bindings = await putScheduleConnectionBindings(user.email, scheduleActionId, connections);
|
|
21580
|
+
return c.json({ ok: true, connections: bindings });
|
|
21581
|
+
} catch (err) {
|
|
21582
|
+
return scheduleConnectionError(c, err, "Unable to update scheduled action service connections.");
|
|
21583
|
+
}
|
|
21584
|
+
});
|
|
21057
21585
|
app.post("/schedule-actions/:id/pause", auth2, async (c) => {
|
|
21058
21586
|
const user = c.get("user");
|
|
21059
21587
|
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
@@ -21072,8 +21600,16 @@ app.delete("/schedule-actions/:id", auth2, async (c) => {
|
|
|
21072
21600
|
const user = c.get("user");
|
|
21073
21601
|
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
21074
21602
|
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
21075
|
-
const
|
|
21076
|
-
|
|
21603
|
+
const scheduleActionId = c.req.param("id");
|
|
21604
|
+
const r = await memoryCall("deleteScheduledActionTool", { id: scheduleActionId }, key);
|
|
21605
|
+
if (!r.ok) return c.json(r);
|
|
21606
|
+
try {
|
|
21607
|
+
await deleteScheduleConnectionBindings(user.email, scheduleActionId);
|
|
21608
|
+
return c.json(r);
|
|
21609
|
+
} catch (err) {
|
|
21610
|
+
console.error("[schedule-actions] action deleted but connection binding cleanup failed", scheduleActionId, err instanceof Error ? err.message : String(err));
|
|
21611
|
+
return c.json({ ...r, connectionBindingCleanupPending: true });
|
|
21612
|
+
}
|
|
21077
21613
|
});
|
|
21078
21614
|
app.post("/schedule-link/revoke", auth2, async (c) => {
|
|
21079
21615
|
try {
|
|
@@ -22126,4 +22662,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
22126
22662
|
export {
|
|
22127
22663
|
app
|
|
22128
22664
|
};
|
|
22129
|
-
//# sourceMappingURL=server-
|
|
22665
|
+
//# sourceMappingURL=server-KTA7CFX6.js.map
|