mcp-scraper 0.66.3 → 0.66.5
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 +19 -1
- package/README.md +1 -1
- package/dist/bin/api-server.cjs +304 -28
- package/dist/bin/api-server.js +1 -1
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +2 -2
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +22 -12
- package/dist/bin/mcp-stdio-server.js +3 -3
- package/dist/{chunk-LNLMOBP6.js → chunk-OJFWURGD.js} +21 -11
- package/dist/{chunk-JBFLOJGM.js → chunk-RSFMR7O7.js} +1 -1
- package/dist/{chunk-2XVDLF6H.js → chunk-TCOP5BT7.js} +1 -1
- package/dist/{server-D47PNEXR.js → server-LESUYFDI.js} +283 -18
- package/package.json +1 -1
|
@@ -199,7 +199,7 @@ import {
|
|
|
199
199
|
resolveDeploymentProfile,
|
|
200
200
|
resolveLocalSourcebookSchemaType,
|
|
201
201
|
transcribeMediaUrl
|
|
202
|
-
} from "./chunk-
|
|
202
|
+
} from "./chunk-OJFWURGD.js";
|
|
203
203
|
import {
|
|
204
204
|
auditImageUrls,
|
|
205
205
|
auditImages,
|
|
@@ -256,7 +256,7 @@ import {
|
|
|
256
256
|
} from "./chunk-GGZEC22A.js";
|
|
257
257
|
import {
|
|
258
258
|
PACKAGE_VERSION
|
|
259
|
-
} from "./chunk-
|
|
259
|
+
} from "./chunk-RSFMR7O7.js";
|
|
260
260
|
import {
|
|
261
261
|
SITE_EXTRACT_ARTIFACT_PREFIX,
|
|
262
262
|
abandonExtractSettlement,
|
|
@@ -13665,12 +13665,39 @@ function localSourcebookPublicUrls(category, state, slug4) {
|
|
|
13665
13665
|
};
|
|
13666
13666
|
}
|
|
13667
13667
|
function localSourcebookPublicationLinks(submission) {
|
|
13668
|
+
const isLive = submission.publishedRevision !== null && submission.status !== "rejected" && submission.status !== "unpublished";
|
|
13668
13669
|
return {
|
|
13669
13670
|
...localSourcebookPublicUrls(submission.category, submission.state, submission.slug),
|
|
13670
13671
|
publicationStatus: submission.status,
|
|
13671
|
-
isLive
|
|
13672
|
+
isLive
|
|
13672
13673
|
};
|
|
13673
13674
|
}
|
|
13675
|
+
function localSourcebookPublicationMessage(submission) {
|
|
13676
|
+
const { profileUrl } = localSourcebookPublicUrls(submission.category, submission.state, submission.slug);
|
|
13677
|
+
if (submission.status === "published" && submission.publishedRevision !== null) {
|
|
13678
|
+
return `Your Local Sourcebook listing is live: ${profileUrl}`;
|
|
13679
|
+
}
|
|
13680
|
+
if (submission.status === "failed") {
|
|
13681
|
+
if (submission.publishedRevision !== null) {
|
|
13682
|
+
return `We couldn't refresh this Local Sourcebook listing, but the previous version is still live: ${profileUrl}`;
|
|
13683
|
+
}
|
|
13684
|
+
return "We couldn't publish this Local Sourcebook listing because evidence collection failed. Review the reported issue before trying again.";
|
|
13685
|
+
}
|
|
13686
|
+
if (submission.status === "rejected") {
|
|
13687
|
+
return "This Local Sourcebook listing was rejected and is not live.";
|
|
13688
|
+
}
|
|
13689
|
+
if (submission.status === "unpublished") {
|
|
13690
|
+
return "This Local Sourcebook listing has been unpublished and is not live.";
|
|
13691
|
+
}
|
|
13692
|
+
if (submission.publishedRevision !== null) {
|
|
13693
|
+
return `We are collecting and compiling refreshed evidence. The current Local Sourcebook listing remains live: ${profileUrl}`;
|
|
13694
|
+
}
|
|
13695
|
+
return "Submission received. We are collecting and compiling evidence, so this Local Sourcebook listing is not live yet. Check the submission status again in about 10 seconds.";
|
|
13696
|
+
}
|
|
13697
|
+
function localSourcebookStatusCheck(submission) {
|
|
13698
|
+
const required = submission.status === "queued" || submission.status === "enriching" || submission.status === "needs_review";
|
|
13699
|
+
return required ? { required: true, submissionId: submission.id, pollAfterSeconds: 10 } : { required: false, submissionId: submission.id };
|
|
13700
|
+
}
|
|
13674
13701
|
|
|
13675
13702
|
// src/api/local-sourcebook-compiler.ts
|
|
13676
13703
|
var CATEGORY_LABELS = {
|
|
@@ -24864,7 +24891,7 @@ localSourcebookApp.post("/capture", createApiKeyAuth(), async (c) => {
|
|
|
24864
24891
|
persisted = true;
|
|
24865
24892
|
await persistLocalSourcebookTags(submission.id, Number(user.id), validation.normalizedTags, parsed.data.tagDecisions);
|
|
24866
24893
|
const dispatched = await dispatchLocalSourcebookAcquisition(submission.id).catch(() => false);
|
|
24867
|
-
return c.json({ ok: true, valid: true, submission, publicUrls: localSourcebookPublicationLinks(submission), normalizedTags: validation.normalizedTags, acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(identity.websiteUrl), billing: publicListingWriteBilling(billing) }, 202);
|
|
24894
|
+
return c.json({ ok: true, valid: true, message: localSourcebookPublicationMessage(submission), statusCheck: localSourcebookStatusCheck(submission), submission, publicUrls: localSourcebookPublicationLinks(submission), normalizedTags: validation.normalizedTags, acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(identity.websiteUrl), billing: publicListingWriteBilling(billing) }, 202);
|
|
24868
24895
|
} catch (error) {
|
|
24869
24896
|
if (billing && !persisted) await refundListingWrite(billing, "Local Sourcebook creation failed before persistence.").catch(() => null);
|
|
24870
24897
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -24913,7 +24940,7 @@ localSourcebookApp.post("/submissions", createApiKeyAuth(), async (c) => {
|
|
|
24913
24940
|
});
|
|
24914
24941
|
persisted = true;
|
|
24915
24942
|
const dispatched = await dispatchLocalSourcebookAcquisition(submission.id).catch(() => false);
|
|
24916
|
-
return c.json({ ok: true, submission, publicUrls: localSourcebookPublicationLinks(submission), acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(input.websiteUrl), billing: publicListingWriteBilling(billing) }, 202);
|
|
24943
|
+
return c.json({ ok: true, message: localSourcebookPublicationMessage(submission), statusCheck: localSourcebookStatusCheck(submission), submission, publicUrls: localSourcebookPublicationLinks(submission), acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(input.websiteUrl), billing: publicListingWriteBilling(billing) }, 202);
|
|
24917
24944
|
} catch (error) {
|
|
24918
24945
|
if (billing && !persisted) await refundListingWrite(billing, "Local Sourcebook creation failed before persistence.").catch(() => null);
|
|
24919
24946
|
return c.json({ error: "submission_conflict", message: error instanceof Error ? error.message : String(error) }, 409);
|
|
@@ -24923,7 +24950,7 @@ localSourcebookApp.get("/submissions/:id", createApiKeyAuth(), async (c) => {
|
|
|
24923
24950
|
const userId = Number(c.get("user").id);
|
|
24924
24951
|
const submission = await getLocalSourcebookSubmission(c.req.param("id"), userId);
|
|
24925
24952
|
if (!submission) return c.json({ error: "not_found" }, 404);
|
|
24926
|
-
return c.json({ ok: true, submission, publicUrls: localSourcebookPublicationLinks(submission), draft: await getLocalSourcebookDraft(submission.id, userId) });
|
|
24953
|
+
return c.json({ ok: true, message: localSourcebookPublicationMessage(submission), statusCheck: localSourcebookStatusCheck(submission), submission, publicUrls: localSourcebookPublicationLinks(submission), draft: await getLocalSourcebookDraft(submission.id, userId) });
|
|
24927
24954
|
});
|
|
24928
24955
|
localSourcebookApp.on(["PATCH", "POST"], "/submissions/:id/draft", createApiKeyAuth(), (c) => c.json({
|
|
24929
24956
|
error: "evidence_refresh_required",
|
|
@@ -24958,7 +24985,7 @@ localSourcebookApp.post("/submissions/:id/refresh", createApiKeyAuth(), async (c
|
|
|
24958
24985
|
return c.json({ error: "not_found" }, 404);
|
|
24959
24986
|
}
|
|
24960
24987
|
const dispatched = await dispatchLocalSourcebookAcquisition(submission.id).catch(() => false);
|
|
24961
|
-
return c.json({ ok: true, submission, publicUrls: localSourcebookPublicationLinks(submission), acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(submission.websiteUrl), billing: publicListingWriteBilling(billing) }, 202);
|
|
24988
|
+
return c.json({ ok: true, message: localSourcebookPublicationMessage(submission), statusCheck: localSourcebookStatusCheck(submission), submission, publicUrls: localSourcebookPublicationLinks(submission), acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(submission.websiteUrl), billing: publicListingWriteBilling(billing) }, 202);
|
|
24962
24989
|
} catch (error) {
|
|
24963
24990
|
if (billing) await refundListingWrite(billing, "Local Sourcebook refresh failed before persistence.").catch(() => null);
|
|
24964
24991
|
return c.json({ error: "moderated_listing", message: error instanceof Error ? error.message : String(error) }, 409);
|
|
@@ -41109,6 +41136,12 @@ function ensureServiceConnectionsSchema() {
|
|
|
41109
41136
|
transport TEXT NOT NULL,
|
|
41110
41137
|
upstream_connection_id TEXT,
|
|
41111
41138
|
label TEXT,
|
|
41139
|
+
user_label TEXT,
|
|
41140
|
+
provider_account_id TEXT,
|
|
41141
|
+
provider_account_email TEXT,
|
|
41142
|
+
provider_account_name TEXT,
|
|
41143
|
+
provider_identity_checked_at TEXT,
|
|
41144
|
+
provider_identity_source_updated_at TEXT,
|
|
41112
41145
|
lifecycle_status TEXT NOT NULL DEFAULT 'pending',
|
|
41113
41146
|
operational_status TEXT NOT NULL DEFAULT 'unknown',
|
|
41114
41147
|
reconnect_required INTEGER NOT NULL DEFAULT 0,
|
|
@@ -41130,6 +41163,30 @@ function ensureServiceConnectionsSchema() {
|
|
|
41130
41163
|
UNIQUE(user_id, provider_config_key, upstream_connection_id)
|
|
41131
41164
|
)
|
|
41132
41165
|
`);
|
|
41166
|
+
try {
|
|
41167
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN user_label TEXT`);
|
|
41168
|
+
} catch {
|
|
41169
|
+
}
|
|
41170
|
+
try {
|
|
41171
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_id TEXT`);
|
|
41172
|
+
} catch {
|
|
41173
|
+
}
|
|
41174
|
+
try {
|
|
41175
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_email TEXT`);
|
|
41176
|
+
} catch {
|
|
41177
|
+
}
|
|
41178
|
+
try {
|
|
41179
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_name TEXT`);
|
|
41180
|
+
} catch {
|
|
41181
|
+
}
|
|
41182
|
+
try {
|
|
41183
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_checked_at TEXT`);
|
|
41184
|
+
} catch {
|
|
41185
|
+
}
|
|
41186
|
+
try {
|
|
41187
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_source_updated_at TEXT`);
|
|
41188
|
+
} catch {
|
|
41189
|
+
}
|
|
41133
41190
|
await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_user_updated ON service_connections(user_id, updated_at DESC)`);
|
|
41134
41191
|
await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_upstream ON service_connections(provider_config_key, upstream_connection_id)`);
|
|
41135
41192
|
await db.execute(`
|
|
@@ -41190,6 +41247,12 @@ function rowToRecord(row) {
|
|
|
41190
41247
|
transport: row.transport === "remote_mcp" ? "remote_mcp" : "nango",
|
|
41191
41248
|
upstreamConnectionId: typeof row.upstream_connection_id === "string" ? row.upstream_connection_id : null,
|
|
41192
41249
|
label: typeof row.label === "string" ? row.label : null,
|
|
41250
|
+
userLabel: typeof row.user_label === "string" ? row.user_label : null,
|
|
41251
|
+
providerAccountId: typeof row.provider_account_id === "string" ? row.provider_account_id : null,
|
|
41252
|
+
providerAccountEmail: typeof row.provider_account_email === "string" ? row.provider_account_email : null,
|
|
41253
|
+
providerAccountName: typeof row.provider_account_name === "string" ? row.provider_account_name : null,
|
|
41254
|
+
providerIdentityCheckedAt: typeof row.provider_identity_checked_at === "string" ? row.provider_identity_checked_at : null,
|
|
41255
|
+
providerIdentitySourceUpdatedAt: typeof row.provider_identity_source_updated_at === "string" ? row.provider_identity_source_updated_at : null,
|
|
41193
41256
|
lifecycleStatus: String(row.lifecycle_status),
|
|
41194
41257
|
operationalStatus: String(row.operational_status),
|
|
41195
41258
|
reconnectRequired: Number(row.reconnect_required) === 1,
|
|
@@ -41275,6 +41338,34 @@ async function updateServiceConnectionTools(connectionId, readTools, actionTools
|
|
|
41275
41338
|
args: [JSON.stringify(readTools), JSON.stringify(actionTools), toolRevision, connectionId]
|
|
41276
41339
|
});
|
|
41277
41340
|
}
|
|
41341
|
+
async function setServiceConnectionProviderIdentity(args) {
|
|
41342
|
+
await ensureServiceConnectionsSchema();
|
|
41343
|
+
await getDb().execute({
|
|
41344
|
+
sql: `
|
|
41345
|
+
UPDATE service_connections SET
|
|
41346
|
+
provider_account_id = ?, provider_account_email = ?, provider_account_name = ?,
|
|
41347
|
+
provider_identity_checked_at = datetime('now'), provider_identity_source_updated_at = ?,
|
|
41348
|
+
updated_at = datetime('now')
|
|
41349
|
+
WHERE id = ?
|
|
41350
|
+
`,
|
|
41351
|
+
args: [
|
|
41352
|
+
args.providerAccountId ?? null,
|
|
41353
|
+
args.providerAccountEmail?.toLowerCase() ?? null,
|
|
41354
|
+
args.providerAccountName ?? null,
|
|
41355
|
+
args.sourceUpdatedAt,
|
|
41356
|
+
args.connectionId
|
|
41357
|
+
]
|
|
41358
|
+
});
|
|
41359
|
+
}
|
|
41360
|
+
async function setServiceConnectionUserLabel(identity, connectionId, userLabel) {
|
|
41361
|
+
const connection = await getOwnedServiceConnection(identity, connectionId);
|
|
41362
|
+
if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
|
|
41363
|
+
await getDb().execute({
|
|
41364
|
+
sql: `UPDATE service_connections SET user_label = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
|
|
41365
|
+
args: [userLabel, connectionId, connection.userId]
|
|
41366
|
+
});
|
|
41367
|
+
return userLabel;
|
|
41368
|
+
}
|
|
41278
41369
|
async function recordServiceConnectionHealth(args) {
|
|
41279
41370
|
await ensureServiceConnectionsSchema();
|
|
41280
41371
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -44114,6 +44205,7 @@ var DEFAULT_NANGO_MCP_URL = "https://api.nango.dev/mcp";
|
|
|
44114
44205
|
var NANGO_TIMEOUT_MS = 2e4;
|
|
44115
44206
|
var NANGO_CONNECTION_PAGE_SIZE = 100;
|
|
44116
44207
|
var NANGO_CONNECTION_MAX_PAGES = 20;
|
|
44208
|
+
var PROVIDER_IDENTITY_RETRY_MS = 6 * 60 * 60 * 1e3;
|
|
44117
44209
|
var MAIN_INTEGRATION_CONTRACT_VERSION = "2026-07-16.1";
|
|
44118
44210
|
var MAIN_INTEGRATION_CONTRACT_HASH = "f037859ec80c2ea2fac6e4a67c1a59f3200dd22d3642c97789b9bb3236723d3e";
|
|
44119
44211
|
var MainNangoTransportError = class extends Error {
|
|
@@ -44217,7 +44309,8 @@ async function nangoRequest(path5, options = {}) {
|
|
|
44217
44309
|
headers: {
|
|
44218
44310
|
authorization: `Bearer ${nangoSecret()}`,
|
|
44219
44311
|
accept: "application/json",
|
|
44220
|
-
...options.body ? { "content-type": "application/json" } : {}
|
|
44312
|
+
...options.body ? { "content-type": "application/json" } : {},
|
|
44313
|
+
...options.headers
|
|
44221
44314
|
},
|
|
44222
44315
|
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
44223
44316
|
signal: AbortSignal.timeout(NANGO_TIMEOUT_MS)
|
|
@@ -44265,22 +44358,142 @@ function discoveredConnection(value) {
|
|
|
44265
44358
|
const upstreamConnectionId = text(row?.connection_id);
|
|
44266
44359
|
const providerConfigKey = text(row?.provider_config_key);
|
|
44267
44360
|
if (!upstreamConnectionId || !providerConfigKey) return null;
|
|
44268
|
-
const tags = asRecord(row?.tags);
|
|
44269
|
-
const endUser = asRecord(row?.end_user);
|
|
44270
44361
|
const errors = Array.isArray(row?.errors) ? row.errors : [];
|
|
44271
44362
|
const authError = errors.some((error) => text(asRecord(error)?.type) === "auth");
|
|
44272
44363
|
return {
|
|
44273
44364
|
upstreamConnectionId,
|
|
44274
44365
|
providerConfigKey,
|
|
44275
44366
|
provider: text(row?.provider) ?? providerConfigKey,
|
|
44276
|
-
|
|
44367
|
+
// Nango's end-user fields identify the MCP Scraper tenant, not the
|
|
44368
|
+
// provider-side principal that authorized this connection.
|
|
44369
|
+
label: null,
|
|
44277
44370
|
lifecycleStatus: authError ? "needs_reauth" : "connected",
|
|
44278
44371
|
reconnectRequired: authError,
|
|
44279
44372
|
createdAt: text(row?.created_at) ?? text(row?.created),
|
|
44280
44373
|
updatedAt: text(row?.updated_at)
|
|
44281
44374
|
};
|
|
44282
44375
|
}
|
|
44283
|
-
|
|
44376
|
+
var IDENTITY_SUPPORTED_PROVIDERS = /* @__PURE__ */ new Set([
|
|
44377
|
+
"facebook",
|
|
44378
|
+
"github-getting-started",
|
|
44379
|
+
"google-analytics",
|
|
44380
|
+
"google-calendar",
|
|
44381
|
+
"google-drive",
|
|
44382
|
+
"google-mail",
|
|
44383
|
+
"google-search-console",
|
|
44384
|
+
"linkedin",
|
|
44385
|
+
"meta-marketing-api",
|
|
44386
|
+
"slack",
|
|
44387
|
+
"twitter-v2",
|
|
44388
|
+
"xero",
|
|
44389
|
+
"youtube",
|
|
44390
|
+
"zoom"
|
|
44391
|
+
]);
|
|
44392
|
+
function scalarId(value) {
|
|
44393
|
+
const valueText = text(value);
|
|
44394
|
+
if (valueText) return valueText;
|
|
44395
|
+
return typeof value === "number" && Number.isFinite(value) ? String(value) : null;
|
|
44396
|
+
}
|
|
44397
|
+
function providerEmail(value) {
|
|
44398
|
+
const email = text(value)?.toLowerCase() ?? null;
|
|
44399
|
+
return email && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) ? email : null;
|
|
44400
|
+
}
|
|
44401
|
+
function compactProviderIdentity(identity) {
|
|
44402
|
+
return identity.id || identity.email || identity.name ? identity : null;
|
|
44403
|
+
}
|
|
44404
|
+
async function providerProxyJson(args) {
|
|
44405
|
+
return nangoRequest(`/proxy${args.path}`, {
|
|
44406
|
+
query: args.query,
|
|
44407
|
+
headers: {
|
|
44408
|
+
"connection-id": args.upstreamConnectionId,
|
|
44409
|
+
"provider-config-key": args.providerConfigKey,
|
|
44410
|
+
...args.baseUrlOverride ? { "base-url-override": args.baseUrlOverride } : {}
|
|
44411
|
+
}
|
|
44412
|
+
});
|
|
44413
|
+
}
|
|
44414
|
+
async function googleProviderAccountIdentity(args) {
|
|
44415
|
+
const request = (path5, query, baseUrlOverride) => providerProxyJson({ ...args, path: path5, query, baseUrlOverride });
|
|
44416
|
+
try {
|
|
44417
|
+
if (args.providerConfigKey === "google-mail") {
|
|
44418
|
+
const payload = asRecord(await request("/gmail/v1/users/me/profile", void 0, "https://gmail.googleapis.com"));
|
|
44419
|
+
const email = providerEmail(payload?.emailAddress);
|
|
44420
|
+
return compactProviderIdentity({ id: email, email, name: null });
|
|
44421
|
+
}
|
|
44422
|
+
if (args.providerConfigKey === "google-drive") {
|
|
44423
|
+
const payload = asRecord(await request("/drive/v3/about", { fields: "user(permissionId,displayName,emailAddress)" }, "https://www.googleapis.com"));
|
|
44424
|
+
const user = asRecord(payload?.user);
|
|
44425
|
+
return compactProviderIdentity({
|
|
44426
|
+
id: text(user?.permissionId),
|
|
44427
|
+
email: providerEmail(user?.emailAddress),
|
|
44428
|
+
name: text(user?.displayName)
|
|
44429
|
+
});
|
|
44430
|
+
}
|
|
44431
|
+
if (args.providerConfigKey === "google-calendar") {
|
|
44432
|
+
const payload = asRecord(await request("/calendar/v3/calendars/primary", void 0, "https://www.googleapis.com"));
|
|
44433
|
+
const id = text(payload?.id);
|
|
44434
|
+
return compactProviderIdentity({ id, email: providerEmail(id), name: text(payload?.summary) });
|
|
44435
|
+
}
|
|
44436
|
+
if (args.providerConfigKey === "youtube") {
|
|
44437
|
+
const payload = asRecord(await request("/youtube/v3/channels", { part: "snippet", mine: "true", maxResults: 1 }, "https://www.googleapis.com"));
|
|
44438
|
+
const first = Array.isArray(payload?.items) ? asRecord(payload.items[0]) : null;
|
|
44439
|
+
return compactProviderIdentity({ id: text(first?.id), email: null, name: text(asRecord(first?.snippet)?.title) });
|
|
44440
|
+
}
|
|
44441
|
+
} catch {
|
|
44442
|
+
}
|
|
44443
|
+
try {
|
|
44444
|
+
const payload = asRecord(await request("/v1/userinfo", void 0, "https://openidconnect.googleapis.com"));
|
|
44445
|
+
return compactProviderIdentity({
|
|
44446
|
+
id: text(payload?.sub),
|
|
44447
|
+
email: payload?.email_verified === false ? null : providerEmail(payload?.email),
|
|
44448
|
+
name: text(payload?.name)
|
|
44449
|
+
});
|
|
44450
|
+
} catch {
|
|
44451
|
+
return null;
|
|
44452
|
+
}
|
|
44453
|
+
}
|
|
44454
|
+
async function getNangoProviderAccountIdentity(args) {
|
|
44455
|
+
if (!IDENTITY_SUPPORTED_PROVIDERS.has(args.providerConfigKey)) return null;
|
|
44456
|
+
if (args.providerConfigKey.startsWith("google-") || args.providerConfigKey === "youtube") {
|
|
44457
|
+
return googleProviderAccountIdentity(args);
|
|
44458
|
+
}
|
|
44459
|
+
try {
|
|
44460
|
+
if (args.providerConfigKey === "github-getting-started") {
|
|
44461
|
+
const payload = asRecord(await providerProxyJson({ ...args, path: "/user" }));
|
|
44462
|
+
return compactProviderIdentity({ id: scalarId(payload?.id), email: providerEmail(payload?.email), name: text(payload?.name) ?? text(payload?.login) });
|
|
44463
|
+
}
|
|
44464
|
+
if (args.providerConfigKey === "slack") {
|
|
44465
|
+
const payload = asRecord(await providerProxyJson({ ...args, path: "/api/auth.test" }));
|
|
44466
|
+
return compactProviderIdentity({ id: text(payload?.user_id), email: null, name: [text(payload?.user), text(payload?.team)].filter(Boolean).join(" \xB7 ") || null });
|
|
44467
|
+
}
|
|
44468
|
+
if (args.providerConfigKey === "zoom") {
|
|
44469
|
+
const payload = asRecord(await providerProxyJson({ ...args, path: "/v2/users/me" }));
|
|
44470
|
+
const fullName = [text(payload?.first_name), text(payload?.last_name)].filter(Boolean).join(" ");
|
|
44471
|
+
return compactProviderIdentity({ id: text(payload?.id), email: providerEmail(payload?.email), name: text(payload?.display_name) ?? (fullName || null) });
|
|
44472
|
+
}
|
|
44473
|
+
if (args.providerConfigKey === "linkedin") {
|
|
44474
|
+
const payload = asRecord(await providerProxyJson({ ...args, path: "/v2/userinfo" }));
|
|
44475
|
+
return compactProviderIdentity({ id: text(payload?.sub), email: payload?.email_verified === false ? null : providerEmail(payload?.email), name: text(payload?.name) });
|
|
44476
|
+
}
|
|
44477
|
+
if (args.providerConfigKey === "meta-marketing-api" || args.providerConfigKey === "facebook") {
|
|
44478
|
+
const payload = asRecord(await providerProxyJson({ ...args, path: "/me", query: { fields: "id,name,email" } }));
|
|
44479
|
+
return compactProviderIdentity({ id: text(payload?.id), email: providerEmail(payload?.email), name: text(payload?.name) });
|
|
44480
|
+
}
|
|
44481
|
+
if (args.providerConfigKey === "twitter-v2") {
|
|
44482
|
+
const payload = asRecord(await providerProxyJson({ ...args, path: "/2/users/me", query: { "user.fields": "id,name,username" } }));
|
|
44483
|
+
const data = asRecord(payload?.data);
|
|
44484
|
+
return compactProviderIdentity({ id: text(data?.id), email: null, name: text(data?.username) ?? text(data?.name) });
|
|
44485
|
+
}
|
|
44486
|
+
if (args.providerConfigKey === "xero") {
|
|
44487
|
+
const payload = await providerProxyJson({ ...args, path: "/connections" });
|
|
44488
|
+
const first = Array.isArray(payload) ? asRecord(payload[0]) : null;
|
|
44489
|
+
return compactProviderIdentity({ id: text(first?.tenantId), email: null, name: text(first?.tenantName) });
|
|
44490
|
+
}
|
|
44491
|
+
} catch {
|
|
44492
|
+
return null;
|
|
44493
|
+
}
|
|
44494
|
+
return null;
|
|
44495
|
+
}
|
|
44496
|
+
async function discoverOwnedNangoConnections(identity, options = {}) {
|
|
44284
44497
|
const rows = [];
|
|
44285
44498
|
for (let page = 0; page < NANGO_CONNECTION_MAX_PAGES; page += 1) {
|
|
44286
44499
|
const payload = await nangoRequest("/connections", {
|
|
@@ -44291,6 +44504,35 @@ async function discoverOwnedNangoConnections(identity) {
|
|
|
44291
44504
|
if (pageRows.length < NANGO_CONNECTION_PAGE_SIZE) break;
|
|
44292
44505
|
}
|
|
44293
44506
|
const discovered = rows.filter((row) => connectionIdentityMatches(row, identity)).map(discoveredConnection).filter((connection) => connection !== null);
|
|
44507
|
+
const reconciled = await reconcileDiscoveredNangoConnections(identity, discovered);
|
|
44508
|
+
if (options.resolveProviderIdentity === false) return reconciled;
|
|
44509
|
+
const remoteByKey = new Map(discovered.map((connection) => [
|
|
44510
|
+
`${connection.providerConfigKey}\0${connection.upstreamConnectionId}`,
|
|
44511
|
+
connection
|
|
44512
|
+
]));
|
|
44513
|
+
await Promise.all(reconciled.map(async (connection) => {
|
|
44514
|
+
if (connection.lifecycleStatus !== "connected" || !connection.upstreamConnectionId) return;
|
|
44515
|
+
const remote = remoteByKey.get(`${connection.providerConfigKey}\0${connection.upstreamConnectionId}`);
|
|
44516
|
+
if (!remote) return;
|
|
44517
|
+
const sourceUpdatedAt = remote.updatedAt ?? remote.createdAt ?? connection.lifecycleStatus;
|
|
44518
|
+
const hasProviderIdentity = Boolean(
|
|
44519
|
+
connection.providerAccountId || connection.providerAccountEmail || connection.providerAccountName
|
|
44520
|
+
);
|
|
44521
|
+
const checkedAt = connection.providerIdentityCheckedAt ? Date.parse(`${connection.providerIdentityCheckedAt.replace(" ", "T")}Z`) : Number.NaN;
|
|
44522
|
+
const unavailableIdentityIsFresh = !hasProviderIdentity && Number.isFinite(checkedAt) && Date.now() - checkedAt < PROVIDER_IDENTITY_RETRY_MS;
|
|
44523
|
+
if (connection.providerIdentitySourceUpdatedAt === sourceUpdatedAt && (hasProviderIdentity || unavailableIdentityIsFresh)) return;
|
|
44524
|
+
const providerIdentity = await getNangoProviderAccountIdentity({
|
|
44525
|
+
upstreamConnectionId: connection.upstreamConnectionId,
|
|
44526
|
+
providerConfigKey: connection.providerConfigKey
|
|
44527
|
+
});
|
|
44528
|
+
await setServiceConnectionProviderIdentity({
|
|
44529
|
+
connectionId: connection.id,
|
|
44530
|
+
providerAccountId: providerIdentity?.id,
|
|
44531
|
+
providerAccountEmail: providerIdentity?.email,
|
|
44532
|
+
providerAccountName: providerIdentity?.name,
|
|
44533
|
+
sourceUpdatedAt
|
|
44534
|
+
});
|
|
44535
|
+
}));
|
|
44294
44536
|
return reconcileDiscoveredNangoConnections(identity, discovered);
|
|
44295
44537
|
}
|
|
44296
44538
|
async function listNangoCatalogDirect() {
|
|
@@ -44965,7 +45207,7 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
44965
45207
|
if (mainOwnsIntegrations()) {
|
|
44966
45208
|
let connections;
|
|
44967
45209
|
try {
|
|
44968
|
-
connections = await discoverOwnedNangoConnections(identity);
|
|
45210
|
+
connections = await discoverOwnedNangoConnections(identity, { resolveProviderIdentity: !options.summaryOnly });
|
|
44969
45211
|
} catch (error) {
|
|
44970
45212
|
try {
|
|
44971
45213
|
connections = await listServiceConnections(identity, "nango");
|
|
@@ -44986,11 +45228,12 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
44986
45228
|
connectionId: connection.id,
|
|
44987
45229
|
providerConfigKey: connection.providerConfigKey,
|
|
44988
45230
|
provider: connection.provider,
|
|
44989
|
-
label: connection.label || connection.providerConfigKey,
|
|
44990
|
-
|
|
44991
|
-
|
|
44992
|
-
|
|
44993
|
-
|
|
45231
|
+
label: connection.userLabel || connection.providerAccountEmail || connection.providerAccountName || (connection.label && connection.label.toLowerCase() !== identity.trim().toLowerCase() ? connection.label : null) || connection.providerConfigKey,
|
|
45232
|
+
userLabel: connection.userLabel,
|
|
45233
|
+
providerAccountId: connection.providerAccountId,
|
|
45234
|
+
providerAccountEmail: connection.providerAccountEmail,
|
|
45235
|
+
providerAccountName: connection.providerAccountName,
|
|
45236
|
+
providerIdentityStatus: connection.providerIdentityCheckedAt ? connection.providerAccountId || connection.providerAccountEmail || connection.providerAccountName ? "verified" : "unavailable" : "pending",
|
|
44994
45237
|
status: connection.reconnectRequired ? "needs_reauth" : "connected",
|
|
44995
45238
|
lifecycleStatus: connection.lifecycleStatus,
|
|
44996
45239
|
operationalStatus: connection.operationalStatus,
|
|
@@ -45091,6 +45334,7 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
45091
45334
|
providerConfigKey,
|
|
45092
45335
|
provider,
|
|
45093
45336
|
label,
|
|
45337
|
+
userLabel: null,
|
|
45094
45338
|
providerAccountId,
|
|
45095
45339
|
providerAccountEmail,
|
|
45096
45340
|
providerAccountName,
|
|
@@ -46932,6 +47176,26 @@ app.get("/schedule-connections", auth4, async (c) => {
|
|
|
46932
47176
|
return scheduleConnectionError(c, err, "Unable to load service connections.");
|
|
46933
47177
|
}
|
|
46934
47178
|
});
|
|
47179
|
+
app.patch("/schedule-connections/:id/label", auth4, async (c) => {
|
|
47180
|
+
const user = c.get("user");
|
|
47181
|
+
const body = await c.req.json().catch(() => ({}));
|
|
47182
|
+
if (body.label !== null && typeof body.label !== "string") {
|
|
47183
|
+
return c.json({ ok: false, error: "label must be a string or null." }, 400);
|
|
47184
|
+
}
|
|
47185
|
+
const label = typeof body.label === "string" ? body.label.normalize("NFKC").trim().replace(/\s+/g, " ") : null;
|
|
47186
|
+
if (label && label.length > 80) {
|
|
47187
|
+
return c.json({ ok: false, error: "label must contain at most 80 characters." }, 400);
|
|
47188
|
+
}
|
|
47189
|
+
try {
|
|
47190
|
+
const userLabel = await setServiceConnectionUserLabel(user.email, c.req.param("id"), label || null);
|
|
47191
|
+
return c.json({ ok: true, userLabel });
|
|
47192
|
+
} catch (err) {
|
|
47193
|
+
if (err instanceof Error && err.message === "service_connection_not_found") {
|
|
47194
|
+
return c.json({ ok: false, error: "Service connection not found." }, 404);
|
|
47195
|
+
}
|
|
47196
|
+
return scheduleConnectionError(c, err, "Unable to rename this service connection.");
|
|
47197
|
+
}
|
|
47198
|
+
});
|
|
46935
47199
|
app.post("/schedule-connections/billing/reconcile", auth4, requireIntegrationsTier, async (c) => {
|
|
46936
47200
|
const user = c.get("user");
|
|
46937
47201
|
try {
|
|
@@ -47518,6 +47782,7 @@ app.post("/integrations/billing/reconcile", (c) => forwardIntegrationAlias(c, "/
|
|
|
47518
47782
|
app.post("/integrations/:id/reconnect-session", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}/reconnect-session`));
|
|
47519
47783
|
app.post("/integrations/:id/enable-actions", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}/enable-actions`));
|
|
47520
47784
|
app.post("/integrations/:id/test", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}/test`));
|
|
47785
|
+
app.patch("/integrations/:id/label", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}/label`));
|
|
47521
47786
|
app.delete("/integrations/:id", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}`));
|
|
47522
47787
|
app.post("/integrations/actions/read", (c) => forwardIntegrationAlias(c, "/schedule-connections/actions/read"));
|
|
47523
47788
|
app.post("/integrations/actions/describe", (c) => forwardIntegrationAlias(c, "/schedule-connections/actions/describe"));
|