mcp-scraper 0.32.2 → 0.33.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/README.md +1 -1
- package/dist/bin/api-server.cjs +432 -260
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +5 -5
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +2 -2
- 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 +47 -6
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/bin/paa-harvest.js +1 -1
- package/dist/{chunk-MKZIL4EA.js → chunk-AT2PFWHW.js} +5 -5
- package/dist/chunk-AT2PFWHW.js.map +1 -0
- package/dist/{chunk-AEMABLEL.js → chunk-ERORC7AP.js} +1 -3
- package/dist/chunk-IASKABFL.js +7 -0
- package/dist/chunk-IASKABFL.js.map +1 -0
- package/dist/{chunk-XEED325T.js → chunk-JURXY3DP.js} +48 -7
- package/dist/chunk-JURXY3DP.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/{server-LTZDJHFG.js → server-RKAKXRAR.js} +378 -247
- package/dist/server-RKAKXRAR.js.map +1 -0
- package/dist/{worker-ZZCZNY3R.js → worker-JU5FX3NE.js} +2 -2
- package/docs/adr/0003-waive-unrecoverable-scheduled-model-cost.md +22 -0
- package/docs/adr/README.md +1 -0
- package/docs/mcp-tool-manifest.generated.json +170 -13
- package/package.json +1 -1
- package/dist/chunk-MKZIL4EA.js.map +0 -1
- package/dist/chunk-XEED325T.js.map +0 -1
- package/dist/chunk-XLO5CZ6T.js +0 -7
- package/dist/chunk-XLO5CZ6T.js.map +0 -1
- package/dist/server-LTZDJHFG.js.map +0 -1
- /package/dist/{chunk-AEMABLEL.js.map → chunk-ERORC7AP.js.map} +0 -0
- /package/dist/{worker-ZZCZNY3R.js.map → worker-JU5FX3NE.js.map} +0 -0
|
@@ -12,12 +12,10 @@ import {
|
|
|
12
12
|
MapsSelectors,
|
|
13
13
|
buildYouTubeChannelVideosUrl,
|
|
14
14
|
deleteKernelProxyId,
|
|
15
|
-
encodeUule,
|
|
16
15
|
harvest,
|
|
17
|
-
normalizeLocation,
|
|
18
16
|
resolveKernelProxyId,
|
|
19
17
|
serpArrivalUrl
|
|
20
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-ERORC7AP.js";
|
|
21
19
|
import {
|
|
22
20
|
HttpMcpToolExecutor,
|
|
23
21
|
assessTranscriptSignal,
|
|
@@ -37,7 +35,7 @@ import {
|
|
|
37
35
|
sanitizeAttempts,
|
|
38
36
|
sanitizeHarvestResult,
|
|
39
37
|
transcribeMediaUrl
|
|
40
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-JURXY3DP.js";
|
|
41
39
|
import {
|
|
42
40
|
auditImages,
|
|
43
41
|
buildLinkReport,
|
|
@@ -66,7 +64,7 @@ import {
|
|
|
66
64
|
workflowOutputBaseDir,
|
|
67
65
|
workflowStepCount,
|
|
68
66
|
workflowSupportsSteps
|
|
69
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-AT2PFWHW.js";
|
|
70
68
|
import "./chunk-K443GQY5.js";
|
|
71
69
|
import {
|
|
72
70
|
DEFAULT_MAPS_PROXY_MODE,
|
|
@@ -78,7 +76,7 @@ import {
|
|
|
78
76
|
RawMapsOverviewSchema,
|
|
79
77
|
RawMapsReviewStatsSchema
|
|
80
78
|
} from "./chunk-V73MPRU6.js";
|
|
81
|
-
import "./chunk-
|
|
79
|
+
import "./chunk-IASKABFL.js";
|
|
82
80
|
import {
|
|
83
81
|
completeExtractJob,
|
|
84
82
|
countSuccessfulPages,
|
|
@@ -10029,7 +10027,7 @@ var HarvestBodySchema = z13.object({
|
|
|
10029
10027
|
gl: z13.string().optional(),
|
|
10030
10028
|
hl: z13.string().optional(),
|
|
10031
10029
|
device: z13.enum(["desktop", "mobile"]).optional(),
|
|
10032
|
-
proxyMode: z13.enum(["
|
|
10030
|
+
proxyMode: z13.enum(["configured", "none"]).optional(),
|
|
10033
10031
|
proxyZip: z13.string().regex(/^\d{5}$/).optional(),
|
|
10034
10032
|
debug: z13.boolean().optional(),
|
|
10035
10033
|
serpOnly: z13.boolean().optional(),
|
|
@@ -13088,15 +13086,29 @@ var MAPS_REDIRECT_BASE = "https://serp-redirector.vercel.app";
|
|
|
13088
13086
|
function buildGoogleLocalResultsUrl(input) {
|
|
13089
13087
|
const query = [input.query, input.location].filter(Boolean).join(" ");
|
|
13090
13088
|
const params = new URLSearchParams({ q: query, udm: "1", gl: input.gl, hl: input.hl, pws: "0" });
|
|
13091
|
-
if (input.location) params.set("uule", encodeUule(normalizeLocation(input.location)));
|
|
13092
13089
|
return `https://www.google.com/search?${params.toString()}`;
|
|
13093
13090
|
}
|
|
13094
13091
|
function buildGoogleOrganicSearchUrl(input) {
|
|
13095
13092
|
const query = [input.query, input.location].filter(Boolean).join(" ");
|
|
13096
13093
|
const params = new URLSearchParams({ q: query, gl: input.gl, hl: input.hl, pws: "0" });
|
|
13097
|
-
if (input.location) params.set("uule", encodeUule(normalizeLocation(input.location)));
|
|
13098
13094
|
return `https://www.google.com/search?${params.toString()}`;
|
|
13099
13095
|
}
|
|
13096
|
+
function mapsIdentifiersFromUrl(url) {
|
|
13097
|
+
const fid = url.match(/!1s(0x[0-9a-f]+):(0x[0-9a-f]+)/i) ?? url.match(/(0x[0-9a-f]+):(0x[0-9a-f]+)/i);
|
|
13098
|
+
if (fid) {
|
|
13099
|
+
try {
|
|
13100
|
+
return { cid: `${fid[1]}:${fid[2]}`, cidDecimal: BigInt(fid[2]).toString() };
|
|
13101
|
+
} catch {
|
|
13102
|
+
return { cid: `${fid[1]}:${fid[2]}`, cidDecimal: null };
|
|
13103
|
+
}
|
|
13104
|
+
}
|
|
13105
|
+
try {
|
|
13106
|
+
const decimal = new URL(url).searchParams.get("cid");
|
|
13107
|
+
return { cid: null, cidDecimal: decimal && /^\d+$/.test(decimal) ? decimal : null };
|
|
13108
|
+
} catch {
|
|
13109
|
+
return { cid: null, cidDecimal: null };
|
|
13110
|
+
}
|
|
13111
|
+
}
|
|
13100
13112
|
var MapsSearchExtractor = class {
|
|
13101
13113
|
constructor(driver) {
|
|
13102
13114
|
this.driver = driver;
|
|
@@ -13172,13 +13184,15 @@ var MapsSearchExtractor = class {
|
|
|
13172
13184
|
for (const card of cards) {
|
|
13173
13185
|
if (results.length >= options.maxResults || seen.has(card.cardKey)) continue;
|
|
13174
13186
|
seen.add(card.cardKey);
|
|
13187
|
+
const cardIds = mapsIdentifiersFromUrl(card.directionsUrl ?? "");
|
|
13188
|
+
const cardPlaceUrl = cardIds.cidDecimal ? `https://www.google.com/maps?cid=${cardIds.cidDecimal}` : card.placeUrl;
|
|
13175
13189
|
const details = await this.openCardAndExtractDialog(page, card, options.includeServices);
|
|
13176
13190
|
results.push({
|
|
13177
13191
|
position: results.length + 1,
|
|
13178
13192
|
name: card.name,
|
|
13179
|
-
placeUrl: details?.placeUrl ??
|
|
13180
|
-
cid: details?.cid ??
|
|
13181
|
-
cidDecimal: details?.cidDecimal ??
|
|
13193
|
+
placeUrl: details?.placeUrl ?? cardPlaceUrl,
|
|
13194
|
+
cid: details?.cid ?? cardIds.cid,
|
|
13195
|
+
cidDecimal: details?.cidDecimal ?? cardIds.cidDecimal,
|
|
13182
13196
|
rating: details?.rating ?? card.rating,
|
|
13183
13197
|
reviewCount: details?.reviewCount ?? card.reviewCount,
|
|
13184
13198
|
category: details?.category ?? card.category,
|
|
@@ -13235,8 +13249,10 @@ var MapsSearchExtractor = class {
|
|
|
13235
13249
|
const phone = lines.map((line) => line.match(phonePattern)?.[0]).find((value) => Boolean(value)) ?? null;
|
|
13236
13250
|
const hoursStatus = normalize((lines.find((line) => /\b(?:open|closed|opens|closes)\b/i.test(line)) ?? "").split("\xB7")[0] ?? "");
|
|
13237
13251
|
const address = lines.find((line) => addressPattern.test(line)) ?? null;
|
|
13238
|
-
const
|
|
13252
|
+
const anchorHrefs = Array.from(card.querySelectorAll("a[href]")).map((anchor) => anchor.href);
|
|
13253
|
+
const websiteUrl = anchorHrefs.find((href) => /^https?:/i.test(href) && !/google\.|gstatic|googleusercontent/.test(href)) ?? null;
|
|
13239
13254
|
if (!rating && !(phone && websiteUrl)) continue;
|
|
13255
|
+
const fidHref = anchorHrefs.find((href) => /(0x[0-9a-f]+):(0x[0-9a-f]+)/i.test(href)) ?? null;
|
|
13240
13256
|
const key = `${name.toLowerCase()}|${lines.join(" ").toLowerCase()}`;
|
|
13241
13257
|
if (seen.has(key)) continue;
|
|
13242
13258
|
seen.add(key);
|
|
@@ -13256,7 +13272,7 @@ var MapsSearchExtractor = class {
|
|
|
13256
13272
|
phone,
|
|
13257
13273
|
hoursStatus,
|
|
13258
13274
|
websiteUrl,
|
|
13259
|
-
directionsUrl: `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent([name, address].filter(Boolean).join(", "))}`,
|
|
13275
|
+
directionsUrl: fidHref ?? `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent([name, address].filter(Boolean).join(", "))}`,
|
|
13260
13276
|
metadata: lines.slice(0, 20)
|
|
13261
13277
|
});
|
|
13262
13278
|
}
|
|
@@ -13955,6 +13971,7 @@ async function cleanupDisposableProxy(kernelApiKey, proxyId, eventName) {
|
|
|
13955
13971
|
}
|
|
13956
13972
|
}
|
|
13957
13973
|
async function runMapsSearchWithRotation(options) {
|
|
13974
|
+
if (options.proxyMode === "location") options = { ...options, proxyMode: "none" };
|
|
13958
13975
|
const attempts = [];
|
|
13959
13976
|
const maxAttempts = mapsSearchMaxAttemptsForProxyMode(options.proxyMode) + 1;
|
|
13960
13977
|
const started = Date.now();
|
|
@@ -14746,7 +14763,7 @@ var DirectoryWorkflowOptionsSchema = z23.object({
|
|
|
14746
14763
|
saveCsv: z23.boolean().default(true),
|
|
14747
14764
|
gl: z23.string().length(2).default("us"),
|
|
14748
14765
|
hl: z23.string().length(2).default("en"),
|
|
14749
|
-
proxyMode: z23.enum(["
|
|
14766
|
+
proxyMode: z23.enum(["configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
|
|
14750
14767
|
proxyZip: z23.string().regex(/^\d{5}$/).optional(),
|
|
14751
14768
|
debug: z23.boolean().default(false),
|
|
14752
14769
|
headless: z23.boolean().default(true),
|
|
@@ -19554,9 +19571,222 @@ if (state.key) { refreshSessions(); if (state.current) selectSession(state.curre
|
|
|
19554
19571
|
}
|
|
19555
19572
|
|
|
19556
19573
|
// src/api/stripe-routes.ts
|
|
19557
|
-
import
|
|
19574
|
+
import Stripe2 from "stripe";
|
|
19558
19575
|
import { Hono as Hono19 } from "hono";
|
|
19559
|
-
|
|
19576
|
+
|
|
19577
|
+
// src/api/connected-account-billing.ts
|
|
19578
|
+
import Stripe from "stripe";
|
|
19579
|
+
var DEFAULT_CONNECTED_ACCOUNT_PRICE_ID = "price_1TtYPAS8aAcsk3TGHOgrDZiQ";
|
|
19580
|
+
var STRIPE_API_VERSION = "2026-02-25.clover";
|
|
19581
|
+
async function ensureConnectedAccountBillingSchema() {
|
|
19582
|
+
await getDb().execute(`CREATE TABLE IF NOT EXISTS connected_account_billing (
|
|
19583
|
+
user_id INTEGER PRIMARY KEY,
|
|
19584
|
+
stripe_subscription_id TEXT,
|
|
19585
|
+
stripe_subscription_item_id TEXT,
|
|
19586
|
+
price_id TEXT,
|
|
19587
|
+
quantity INTEGER NOT NULL DEFAULT 0,
|
|
19588
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
19589
|
+
last_error_code TEXT,
|
|
19590
|
+
synced_at TEXT,
|
|
19591
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
19592
|
+
)`);
|
|
19593
|
+
}
|
|
19594
|
+
async function getConnectedAccountBillingState(userId) {
|
|
19595
|
+
await ensureConnectedAccountBillingSchema();
|
|
19596
|
+
const result = await getDb().execute({
|
|
19597
|
+
sql: "SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",
|
|
19598
|
+
args: [Number(userId)]
|
|
19599
|
+
});
|
|
19600
|
+
return result.rows[0] ? result.rows[0] : null;
|
|
19601
|
+
}
|
|
19602
|
+
async function setConnectedAccountBillingState(input) {
|
|
19603
|
+
await ensureConnectedAccountBillingSchema();
|
|
19604
|
+
await getDb().execute({
|
|
19605
|
+
sql: `INSERT INTO connected_account_billing
|
|
19606
|
+
(user_id, stripe_subscription_id, stripe_subscription_item_id, price_id, quantity, status, last_error_code, synced_at, updated_at)
|
|
19607
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, CASE WHEN ? THEN datetime('now') ELSE NULL END, datetime('now'))
|
|
19608
|
+
ON CONFLICT(user_id) DO UPDATE SET
|
|
19609
|
+
stripe_subscription_id = excluded.stripe_subscription_id,
|
|
19610
|
+
stripe_subscription_item_id = excluded.stripe_subscription_item_id,
|
|
19611
|
+
price_id = excluded.price_id,
|
|
19612
|
+
quantity = excluded.quantity,
|
|
19613
|
+
status = excluded.status,
|
|
19614
|
+
last_error_code = excluded.last_error_code,
|
|
19615
|
+
synced_at = CASE WHEN ? THEN datetime('now') ELSE connected_account_billing.synced_at END,
|
|
19616
|
+
updated_at = datetime('now')`,
|
|
19617
|
+
args: [
|
|
19618
|
+
Number(input.userId),
|
|
19619
|
+
input.stripeSubscriptionId,
|
|
19620
|
+
input.stripeSubscriptionItemId,
|
|
19621
|
+
input.priceId,
|
|
19622
|
+
input.quantity,
|
|
19623
|
+
input.status,
|
|
19624
|
+
input.lastErrorCode,
|
|
19625
|
+
input.synced ? 1 : 0,
|
|
19626
|
+
input.synced ? 1 : 0
|
|
19627
|
+
]
|
|
19628
|
+
});
|
|
19629
|
+
const state = await getConnectedAccountBillingState(input.userId);
|
|
19630
|
+
if (!state) throw new Error("connected account billing state was not persisted");
|
|
19631
|
+
return state;
|
|
19632
|
+
}
|
|
19633
|
+
var ConnectedAccountBillingError = class extends Error {
|
|
19634
|
+
constructor(message, code, status = 409) {
|
|
19635
|
+
super(message);
|
|
19636
|
+
this.code = code;
|
|
19637
|
+
this.status = status;
|
|
19638
|
+
this.name = "ConnectedAccountBillingError";
|
|
19639
|
+
}
|
|
19640
|
+
code;
|
|
19641
|
+
status;
|
|
19642
|
+
};
|
|
19643
|
+
function connectedAccountPriceId() {
|
|
19644
|
+
const configured = process.env.CONNECTED_ACCOUNT_PRICE_ID?.trim();
|
|
19645
|
+
if (configured) return configured;
|
|
19646
|
+
return DEFAULT_CONNECTED_ACCOUNT_PRICE_ID || null;
|
|
19647
|
+
}
|
|
19648
|
+
function connectedAccountBillingView(actualQuantity, state) {
|
|
19649
|
+
const quantity = Math.max(0, Math.round(actualQuantity));
|
|
19650
|
+
return {
|
|
19651
|
+
provider: "nango",
|
|
19652
|
+
billingMode: "flat_recurring_usd_plus_credits",
|
|
19653
|
+
priceId: connectedAccountPriceId(),
|
|
19654
|
+
unitAmountUsd: CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
|
|
19655
|
+
interval: "month",
|
|
19656
|
+
quantity,
|
|
19657
|
+
projectedMonthlyUsd: quantity * CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
|
|
19658
|
+
status: state?.status ?? "pending",
|
|
19659
|
+
lastErrorCode: state?.last_error_code ?? null,
|
|
19660
|
+
syncedAt: state?.synced_at ?? null,
|
|
19661
|
+
usage: CONNECTED_USAGE_RATE_POLICY.usage
|
|
19662
|
+
};
|
|
19663
|
+
}
|
|
19664
|
+
function findBasePlanItem(subscription) {
|
|
19665
|
+
return subscription.items.data.find((item) => !!item.price?.id && item.price.id in SUBSCRIPTION_TIERS);
|
|
19666
|
+
}
|
|
19667
|
+
function findConnectedAccountItem(subscription, priceId = connectedAccountPriceId()) {
|
|
19668
|
+
return priceId ? subscription.items.data.find((item) => item.price?.id === priceId) : void 0;
|
|
19669
|
+
}
|
|
19670
|
+
function liveSubscription(subscription) {
|
|
19671
|
+
return subscription.status === "active" || subscription.status === "trialing";
|
|
19672
|
+
}
|
|
19673
|
+
function safeStripeErrorCode(error) {
|
|
19674
|
+
if (error instanceof ConnectedAccountBillingError) return error.code;
|
|
19675
|
+
return "stripe_connection_billing_failed";
|
|
19676
|
+
}
|
|
19677
|
+
async function persistFailure(user, actualQuantity, error, subscriptionItemId = null) {
|
|
19678
|
+
await setConnectedAccountBillingState({
|
|
19679
|
+
userId: user.id,
|
|
19680
|
+
stripeSubscriptionId: user.subscription_id,
|
|
19681
|
+
stripeSubscriptionItemId: subscriptionItemId,
|
|
19682
|
+
priceId: connectedAccountPriceId(),
|
|
19683
|
+
quantity: actualQuantity,
|
|
19684
|
+
status: error instanceof ConnectedAccountBillingError && error.code !== "stripe_connection_billing_failed" ? "blocked" : "error",
|
|
19685
|
+
lastErrorCode: safeStripeErrorCode(error),
|
|
19686
|
+
synced: false
|
|
19687
|
+
});
|
|
19688
|
+
}
|
|
19689
|
+
async function reconcileConnectedAccountBilling(user, actualQuantity, stripeClient) {
|
|
19690
|
+
if (!Number.isSafeInteger(actualQuantity) || actualQuantity < 0) {
|
|
19691
|
+
throw new Error("actualQuantity must be a non-negative safe integer");
|
|
19692
|
+
}
|
|
19693
|
+
const priceId = connectedAccountPriceId();
|
|
19694
|
+
if (!priceId) {
|
|
19695
|
+
const error = new ConnectedAccountBillingError(
|
|
19696
|
+
"Connected-account billing is not configured.",
|
|
19697
|
+
"connected_account_price_not_configured",
|
|
19698
|
+
503
|
|
19699
|
+
);
|
|
19700
|
+
await persistFailure(user, actualQuantity, error);
|
|
19701
|
+
throw error;
|
|
19702
|
+
}
|
|
19703
|
+
if (!user.subscription_id) {
|
|
19704
|
+
if (actualQuantity === 0) {
|
|
19705
|
+
const state = await setConnectedAccountBillingState({
|
|
19706
|
+
userId: user.id,
|
|
19707
|
+
stripeSubscriptionId: null,
|
|
19708
|
+
stripeSubscriptionItemId: null,
|
|
19709
|
+
priceId,
|
|
19710
|
+
quantity: 0,
|
|
19711
|
+
status: "synced",
|
|
19712
|
+
lastErrorCode: null,
|
|
19713
|
+
synced: true
|
|
19714
|
+
});
|
|
19715
|
+
return connectedAccountBillingView(0, state);
|
|
19716
|
+
}
|
|
19717
|
+
const error = new ConnectedAccountBillingError(
|
|
19718
|
+
"An active paid plan is required before connected accounts can be billed.",
|
|
19719
|
+
"paid_plan_required",
|
|
19720
|
+
403
|
|
19721
|
+
);
|
|
19722
|
+
await persistFailure(user, actualQuantity, error);
|
|
19723
|
+
throw error;
|
|
19724
|
+
}
|
|
19725
|
+
const client2 = stripeClient ?? (() => {
|
|
19726
|
+
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
19727
|
+
if (!secret2) {
|
|
19728
|
+
throw new ConnectedAccountBillingError(
|
|
19729
|
+
"Stripe is not configured.",
|
|
19730
|
+
"connected_account_price_not_configured",
|
|
19731
|
+
503
|
|
19732
|
+
);
|
|
19733
|
+
}
|
|
19734
|
+
return new Stripe(secret2, { apiVersion: STRIPE_API_VERSION });
|
|
19735
|
+
})();
|
|
19736
|
+
let currentItem;
|
|
19737
|
+
try {
|
|
19738
|
+
const subscription = await client2.subscriptions.retrieve(user.subscription_id);
|
|
19739
|
+
currentItem = findConnectedAccountItem(subscription, priceId);
|
|
19740
|
+
if (!liveSubscription(subscription)) {
|
|
19741
|
+
throw new ConnectedAccountBillingError(
|
|
19742
|
+
"The plan subscription is not active.",
|
|
19743
|
+
"subscription_not_active",
|
|
19744
|
+
402
|
|
19745
|
+
);
|
|
19746
|
+
}
|
|
19747
|
+
if (!findBasePlanItem(subscription)) {
|
|
19748
|
+
throw new ConnectedAccountBillingError(
|
|
19749
|
+
"The base plan item could not be found on this subscription.",
|
|
19750
|
+
"subscription_plan_item_missing",
|
|
19751
|
+
409
|
|
19752
|
+
);
|
|
19753
|
+
}
|
|
19754
|
+
const currentQuantity = Math.max(0, currentItem?.quantity ?? 0);
|
|
19755
|
+
let resultingItem = currentItem;
|
|
19756
|
+
if (actualQuantity !== currentQuantity) {
|
|
19757
|
+
const updated = await client2.subscriptions.update(subscription.id, {
|
|
19758
|
+
items: actualQuantity === 0 ? currentItem ? [{ id: currentItem.id, deleted: true }] : [] : currentItem ? [{ id: currentItem.id, quantity: actualQuantity }] : [{ price: priceId, quantity: actualQuantity }],
|
|
19759
|
+
proration_behavior: "create_prorations"
|
|
19760
|
+
});
|
|
19761
|
+
resultingItem = findConnectedAccountItem(updated, priceId);
|
|
19762
|
+
}
|
|
19763
|
+
const state = await setConnectedAccountBillingState({
|
|
19764
|
+
userId: user.id,
|
|
19765
|
+
stripeSubscriptionId: subscription.id,
|
|
19766
|
+
stripeSubscriptionItemId: resultingItem?.id ?? null,
|
|
19767
|
+
priceId,
|
|
19768
|
+
quantity: actualQuantity,
|
|
19769
|
+
status: "synced",
|
|
19770
|
+
lastErrorCode: null,
|
|
19771
|
+
synced: true
|
|
19772
|
+
});
|
|
19773
|
+
return connectedAccountBillingView(actualQuantity, state);
|
|
19774
|
+
} catch (error) {
|
|
19775
|
+
await persistFailure(user, actualQuantity, error, currentItem?.id ?? null);
|
|
19776
|
+
if (error instanceof ConnectedAccountBillingError) throw error;
|
|
19777
|
+
throw new ConnectedAccountBillingError(
|
|
19778
|
+
"Unable to update connected-account billing in Stripe.",
|
|
19779
|
+
"stripe_connection_billing_failed",
|
|
19780
|
+
503
|
|
19781
|
+
);
|
|
19782
|
+
}
|
|
19783
|
+
}
|
|
19784
|
+
async function currentConnectedAccountBillingView(userId, actualQuantity) {
|
|
19785
|
+
return connectedAccountBillingView(actualQuantity, await getConnectedAccountBillingState(userId));
|
|
19786
|
+
}
|
|
19787
|
+
|
|
19788
|
+
// src/api/stripe-routes.ts
|
|
19789
|
+
var stripe = new Stripe2(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
|
|
19560
19790
|
var stripeApp = new Hono19();
|
|
19561
19791
|
stripeApp.post("/webhooks", async (c) => {
|
|
19562
19792
|
const sig = c.req.header("stripe-signature");
|
|
@@ -19573,15 +19803,23 @@ stripeApp.post("/webhooks", async (c) => {
|
|
|
19573
19803
|
const invoice = event.data.object;
|
|
19574
19804
|
const memLineId = invoice.lines.data.map(linePriceId).find((id) => id && id in MEMORY_PLANS);
|
|
19575
19805
|
if (memLineId) return c.json({ received: true });
|
|
19576
|
-
const
|
|
19577
|
-
|
|
19578
|
-
|
|
19579
|
-
|
|
19580
|
-
|
|
19581
|
-
|
|
19582
|
-
|
|
19583
|
-
|
|
19584
|
-
|
|
19806
|
+
const invoiceHasTierLine = invoice.lines.data.some((l) => {
|
|
19807
|
+
const id = linePriceId(l);
|
|
19808
|
+
return id && id in SUBSCRIPTION_TIERS;
|
|
19809
|
+
});
|
|
19810
|
+
if (invoiceHasTierLine) {
|
|
19811
|
+
const subId = invoice.subscription;
|
|
19812
|
+
const liveBasePlanPriceId = subId ? findBasePlanItem(await stripe.subscriptions.retrieve(subId))?.price?.id : void 0;
|
|
19813
|
+
const tierPriceId = liveBasePlanPriceId ?? invoice.lines.data.map(linePriceId).find((id) => id && id in SUBSCRIPTION_TIERS);
|
|
19814
|
+
const tier = tierPriceId ? SUBSCRIPTION_TIERS[tierPriceId] : void 0;
|
|
19815
|
+
if (tier) {
|
|
19816
|
+
const user = await resolveUser2(invoice.customer, invoice.customer_email ?? void 0);
|
|
19817
|
+
if (user && invoice.id && !await ledgerExistsForStripePI(invoice.id)) {
|
|
19818
|
+
await creditMc(user.id, tier.credits_mc, LedgerOperation.SUBSCRIPTION, `${tier.label} subscription credits`, invoice.id);
|
|
19819
|
+
await setSubscriptionTier(user.id, tier.tier, tier.concurrency, subId ?? user.subscription_id);
|
|
19820
|
+
const credentials = await syncScheduledActionCredentials(user);
|
|
19821
|
+
if (!credentials.ok) console.warn("[stripe] scheduled-action credential sync failed:", credentials.error ?? "unknown error");
|
|
19822
|
+
}
|
|
19585
19823
|
}
|
|
19586
19824
|
}
|
|
19587
19825
|
}
|
|
@@ -22832,8 +23070,8 @@ async function reconcileDiscoveredNangoConnections(identity, discovered) {
|
|
|
22832
23070
|
sql: `
|
|
22833
23071
|
INSERT INTO service_connections (
|
|
22834
23072
|
id, user_id, provider_config_key, provider, transport, upstream_connection_id,
|
|
22835
|
-
label, lifecycle_status, reconnect_required, legacy_owner_identity, created_at, updated_at
|
|
22836
|
-
) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
|
|
23073
|
+
label, lifecycle_status, reconnect_required, actions_enabled, legacy_owner_identity, created_at, updated_at
|
|
23074
|
+
) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, 1, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
|
|
22837
23075
|
ON CONFLICT(user_id, provider_config_key, upstream_connection_id) DO UPDATE SET
|
|
22838
23076
|
provider = excluded.provider,
|
|
22839
23077
|
label = COALESCE(excluded.label, service_connections.label),
|
|
@@ -24902,217 +25140,6 @@ async function verifySchedulerIntegrationRequest(request, rawBody) {
|
|
|
24902
25140
|
return { requestId };
|
|
24903
25141
|
}
|
|
24904
25142
|
|
|
24905
|
-
// src/api/connected-account-billing.ts
|
|
24906
|
-
import Stripe2 from "stripe";
|
|
24907
|
-
var DEFAULT_CONNECTED_ACCOUNT_PRICE_ID = "price_1TtYPAS8aAcsk3TGHOgrDZiQ";
|
|
24908
|
-
var STRIPE_API_VERSION = "2026-02-25.clover";
|
|
24909
|
-
async function ensureConnectedAccountBillingSchema() {
|
|
24910
|
-
await getDb().execute(`CREATE TABLE IF NOT EXISTS connected_account_billing (
|
|
24911
|
-
user_id INTEGER PRIMARY KEY,
|
|
24912
|
-
stripe_subscription_id TEXT,
|
|
24913
|
-
stripe_subscription_item_id TEXT,
|
|
24914
|
-
price_id TEXT,
|
|
24915
|
-
quantity INTEGER NOT NULL DEFAULT 0,
|
|
24916
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
24917
|
-
last_error_code TEXT,
|
|
24918
|
-
synced_at TEXT,
|
|
24919
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
24920
|
-
)`);
|
|
24921
|
-
}
|
|
24922
|
-
async function getConnectedAccountBillingState(userId) {
|
|
24923
|
-
await ensureConnectedAccountBillingSchema();
|
|
24924
|
-
const result = await getDb().execute({
|
|
24925
|
-
sql: "SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",
|
|
24926
|
-
args: [Number(userId)]
|
|
24927
|
-
});
|
|
24928
|
-
return result.rows[0] ? result.rows[0] : null;
|
|
24929
|
-
}
|
|
24930
|
-
async function setConnectedAccountBillingState(input) {
|
|
24931
|
-
await ensureConnectedAccountBillingSchema();
|
|
24932
|
-
await getDb().execute({
|
|
24933
|
-
sql: `INSERT INTO connected_account_billing
|
|
24934
|
-
(user_id, stripe_subscription_id, stripe_subscription_item_id, price_id, quantity, status, last_error_code, synced_at, updated_at)
|
|
24935
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, CASE WHEN ? THEN datetime('now') ELSE NULL END, datetime('now'))
|
|
24936
|
-
ON CONFLICT(user_id) DO UPDATE SET
|
|
24937
|
-
stripe_subscription_id = excluded.stripe_subscription_id,
|
|
24938
|
-
stripe_subscription_item_id = excluded.stripe_subscription_item_id,
|
|
24939
|
-
price_id = excluded.price_id,
|
|
24940
|
-
quantity = excluded.quantity,
|
|
24941
|
-
status = excluded.status,
|
|
24942
|
-
last_error_code = excluded.last_error_code,
|
|
24943
|
-
synced_at = CASE WHEN ? THEN datetime('now') ELSE connected_account_billing.synced_at END,
|
|
24944
|
-
updated_at = datetime('now')`,
|
|
24945
|
-
args: [
|
|
24946
|
-
Number(input.userId),
|
|
24947
|
-
input.stripeSubscriptionId,
|
|
24948
|
-
input.stripeSubscriptionItemId,
|
|
24949
|
-
input.priceId,
|
|
24950
|
-
input.quantity,
|
|
24951
|
-
input.status,
|
|
24952
|
-
input.lastErrorCode,
|
|
24953
|
-
input.synced ? 1 : 0,
|
|
24954
|
-
input.synced ? 1 : 0
|
|
24955
|
-
]
|
|
24956
|
-
});
|
|
24957
|
-
const state = await getConnectedAccountBillingState(input.userId);
|
|
24958
|
-
if (!state) throw new Error("connected account billing state was not persisted");
|
|
24959
|
-
return state;
|
|
24960
|
-
}
|
|
24961
|
-
var ConnectedAccountBillingError = class extends Error {
|
|
24962
|
-
constructor(message, code, status = 409) {
|
|
24963
|
-
super(message);
|
|
24964
|
-
this.code = code;
|
|
24965
|
-
this.status = status;
|
|
24966
|
-
this.name = "ConnectedAccountBillingError";
|
|
24967
|
-
}
|
|
24968
|
-
code;
|
|
24969
|
-
status;
|
|
24970
|
-
};
|
|
24971
|
-
function connectedAccountPriceId() {
|
|
24972
|
-
const configured = process.env.CONNECTED_ACCOUNT_PRICE_ID?.trim();
|
|
24973
|
-
if (configured) return configured;
|
|
24974
|
-
return DEFAULT_CONNECTED_ACCOUNT_PRICE_ID || null;
|
|
24975
|
-
}
|
|
24976
|
-
function connectedAccountBillingView(actualQuantity, state) {
|
|
24977
|
-
const quantity = Math.max(0, Math.round(actualQuantity));
|
|
24978
|
-
return {
|
|
24979
|
-
provider: "nango",
|
|
24980
|
-
billingMode: "flat_recurring_usd_plus_credits",
|
|
24981
|
-
priceId: connectedAccountPriceId(),
|
|
24982
|
-
unitAmountUsd: CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
|
|
24983
|
-
interval: "month",
|
|
24984
|
-
quantity,
|
|
24985
|
-
projectedMonthlyUsd: quantity * CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
|
|
24986
|
-
status: state?.status ?? "pending",
|
|
24987
|
-
lastErrorCode: state?.last_error_code ?? null,
|
|
24988
|
-
syncedAt: state?.synced_at ?? null,
|
|
24989
|
-
usage: CONNECTED_USAGE_RATE_POLICY.usage
|
|
24990
|
-
};
|
|
24991
|
-
}
|
|
24992
|
-
function findBasePlanItem(subscription) {
|
|
24993
|
-
return subscription.items.data.find((item) => !!item.price?.id && item.price.id in SUBSCRIPTION_TIERS);
|
|
24994
|
-
}
|
|
24995
|
-
function findConnectedAccountItem(subscription, priceId = connectedAccountPriceId()) {
|
|
24996
|
-
return priceId ? subscription.items.data.find((item) => item.price?.id === priceId) : void 0;
|
|
24997
|
-
}
|
|
24998
|
-
function liveSubscription(subscription) {
|
|
24999
|
-
return subscription.status === "active" || subscription.status === "trialing";
|
|
25000
|
-
}
|
|
25001
|
-
function safeStripeErrorCode(error) {
|
|
25002
|
-
if (error instanceof ConnectedAccountBillingError) return error.code;
|
|
25003
|
-
return "stripe_connection_billing_failed";
|
|
25004
|
-
}
|
|
25005
|
-
async function persistFailure(user, actualQuantity, error, subscriptionItemId = null) {
|
|
25006
|
-
await setConnectedAccountBillingState({
|
|
25007
|
-
userId: user.id,
|
|
25008
|
-
stripeSubscriptionId: user.subscription_id,
|
|
25009
|
-
stripeSubscriptionItemId: subscriptionItemId,
|
|
25010
|
-
priceId: connectedAccountPriceId(),
|
|
25011
|
-
quantity: actualQuantity,
|
|
25012
|
-
status: error instanceof ConnectedAccountBillingError && error.code !== "stripe_connection_billing_failed" ? "blocked" : "error",
|
|
25013
|
-
lastErrorCode: safeStripeErrorCode(error),
|
|
25014
|
-
synced: false
|
|
25015
|
-
});
|
|
25016
|
-
}
|
|
25017
|
-
async function reconcileConnectedAccountBilling(user, actualQuantity, stripeClient) {
|
|
25018
|
-
if (!Number.isSafeInteger(actualQuantity) || actualQuantity < 0) {
|
|
25019
|
-
throw new Error("actualQuantity must be a non-negative safe integer");
|
|
25020
|
-
}
|
|
25021
|
-
const priceId = connectedAccountPriceId();
|
|
25022
|
-
if (!priceId) {
|
|
25023
|
-
const error = new ConnectedAccountBillingError(
|
|
25024
|
-
"Connected-account billing is not configured.",
|
|
25025
|
-
"connected_account_price_not_configured",
|
|
25026
|
-
503
|
|
25027
|
-
);
|
|
25028
|
-
await persistFailure(user, actualQuantity, error);
|
|
25029
|
-
throw error;
|
|
25030
|
-
}
|
|
25031
|
-
if (!user.subscription_id) {
|
|
25032
|
-
if (actualQuantity === 0) {
|
|
25033
|
-
const state = await setConnectedAccountBillingState({
|
|
25034
|
-
userId: user.id,
|
|
25035
|
-
stripeSubscriptionId: null,
|
|
25036
|
-
stripeSubscriptionItemId: null,
|
|
25037
|
-
priceId,
|
|
25038
|
-
quantity: 0,
|
|
25039
|
-
status: "synced",
|
|
25040
|
-
lastErrorCode: null,
|
|
25041
|
-
synced: true
|
|
25042
|
-
});
|
|
25043
|
-
return connectedAccountBillingView(0, state);
|
|
25044
|
-
}
|
|
25045
|
-
const error = new ConnectedAccountBillingError(
|
|
25046
|
-
"An active paid plan is required before connected accounts can be billed.",
|
|
25047
|
-
"paid_plan_required",
|
|
25048
|
-
403
|
|
25049
|
-
);
|
|
25050
|
-
await persistFailure(user, actualQuantity, error);
|
|
25051
|
-
throw error;
|
|
25052
|
-
}
|
|
25053
|
-
const client2 = stripeClient ?? (() => {
|
|
25054
|
-
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
25055
|
-
if (!secret2) {
|
|
25056
|
-
throw new ConnectedAccountBillingError(
|
|
25057
|
-
"Stripe is not configured.",
|
|
25058
|
-
"connected_account_price_not_configured",
|
|
25059
|
-
503
|
|
25060
|
-
);
|
|
25061
|
-
}
|
|
25062
|
-
return new Stripe2(secret2, { apiVersion: STRIPE_API_VERSION });
|
|
25063
|
-
})();
|
|
25064
|
-
let currentItem;
|
|
25065
|
-
try {
|
|
25066
|
-
const subscription = await client2.subscriptions.retrieve(user.subscription_id);
|
|
25067
|
-
currentItem = findConnectedAccountItem(subscription, priceId);
|
|
25068
|
-
if (!liveSubscription(subscription)) {
|
|
25069
|
-
throw new ConnectedAccountBillingError(
|
|
25070
|
-
"The plan subscription is not active.",
|
|
25071
|
-
"subscription_not_active",
|
|
25072
|
-
402
|
|
25073
|
-
);
|
|
25074
|
-
}
|
|
25075
|
-
if (!findBasePlanItem(subscription)) {
|
|
25076
|
-
throw new ConnectedAccountBillingError(
|
|
25077
|
-
"The base plan item could not be found on this subscription.",
|
|
25078
|
-
"subscription_plan_item_missing",
|
|
25079
|
-
409
|
|
25080
|
-
);
|
|
25081
|
-
}
|
|
25082
|
-
const currentQuantity = Math.max(0, currentItem?.quantity ?? 0);
|
|
25083
|
-
let resultingItem = currentItem;
|
|
25084
|
-
if (actualQuantity !== currentQuantity) {
|
|
25085
|
-
const updated = await client2.subscriptions.update(subscription.id, {
|
|
25086
|
-
items: actualQuantity === 0 ? currentItem ? [{ id: currentItem.id, deleted: true }] : [] : currentItem ? [{ id: currentItem.id, quantity: actualQuantity }] : [{ price: priceId, quantity: actualQuantity }],
|
|
25087
|
-
proration_behavior: "create_prorations"
|
|
25088
|
-
});
|
|
25089
|
-
resultingItem = findConnectedAccountItem(updated, priceId);
|
|
25090
|
-
}
|
|
25091
|
-
const state = await setConnectedAccountBillingState({
|
|
25092
|
-
userId: user.id,
|
|
25093
|
-
stripeSubscriptionId: subscription.id,
|
|
25094
|
-
stripeSubscriptionItemId: resultingItem?.id ?? null,
|
|
25095
|
-
priceId,
|
|
25096
|
-
quantity: actualQuantity,
|
|
25097
|
-
status: "synced",
|
|
25098
|
-
lastErrorCode: null,
|
|
25099
|
-
synced: true
|
|
25100
|
-
});
|
|
25101
|
-
return connectedAccountBillingView(actualQuantity, state);
|
|
25102
|
-
} catch (error) {
|
|
25103
|
-
await persistFailure(user, actualQuantity, error, currentItem?.id ?? null);
|
|
25104
|
-
if (error instanceof ConnectedAccountBillingError) throw error;
|
|
25105
|
-
throw new ConnectedAccountBillingError(
|
|
25106
|
-
"Unable to update connected-account billing in Stripe.",
|
|
25107
|
-
"stripe_connection_billing_failed",
|
|
25108
|
-
503
|
|
25109
|
-
);
|
|
25110
|
-
}
|
|
25111
|
-
}
|
|
25112
|
-
async function currentConnectedAccountBillingView(userId, actualQuantity) {
|
|
25113
|
-
return connectedAccountBillingView(actualQuantity, await getConnectedAccountBillingState(userId));
|
|
25114
|
-
}
|
|
25115
|
-
|
|
25116
25143
|
// src/api/server.ts
|
|
25117
25144
|
var secureCookies2 = process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
|
|
25118
25145
|
var isProduction2 = secureCookies2;
|
|
@@ -26097,7 +26124,7 @@ app.post("/schedule-connections/actions/slack/send-message", auth2, requireInteg
|
|
|
26097
26124
|
user.email,
|
|
26098
26125
|
connectionId,
|
|
26099
26126
|
{ channel: body.channel, text: body.text },
|
|
26100
|
-
|
|
26127
|
+
"send-message",
|
|
26101
26128
|
connectedActionIdempotencyKey(c)
|
|
26102
26129
|
);
|
|
26103
26130
|
return c.json({ ok: true, result });
|
|
@@ -26117,7 +26144,7 @@ app.post("/schedule-connections/actions/gmail/send-message", auth2, requireInteg
|
|
|
26117
26144
|
user.email,
|
|
26118
26145
|
connectionId,
|
|
26119
26146
|
{ to: body.to, subject: body.subject, body: body.body },
|
|
26120
|
-
|
|
26147
|
+
"send-message",
|
|
26121
26148
|
connectedActionIdempotencyKey(c)
|
|
26122
26149
|
);
|
|
26123
26150
|
return c.json({ ok: true, result });
|
|
@@ -26125,6 +26152,76 @@ app.post("/schedule-connections/actions/gmail/send-message", auth2, requireInteg
|
|
|
26125
26152
|
return scheduleConnectionError(c, err, "Unable to send the email.");
|
|
26126
26153
|
}
|
|
26127
26154
|
});
|
|
26155
|
+
function toolResultText(result) {
|
|
26156
|
+
if (!result || typeof result !== "object") return null;
|
|
26157
|
+
const content = result.content;
|
|
26158
|
+
if (!Array.isArray(content)) return null;
|
|
26159
|
+
const textItem = content.find((item) => item && typeof item === "object" && item.type === "text");
|
|
26160
|
+
const text2 = textItem?.text;
|
|
26161
|
+
return typeof text2 === "string" ? text2 : null;
|
|
26162
|
+
}
|
|
26163
|
+
function parseEmailAddress(headerValue) {
|
|
26164
|
+
const value = headerValue?.trim();
|
|
26165
|
+
if (!value) return null;
|
|
26166
|
+
const angled = value.match(/^"?([^"<]*?)"?\s*<([^>]+)>$/);
|
|
26167
|
+
if (angled) return { email: angled[2].trim().toLowerCase(), name: angled[1].trim() || null };
|
|
26168
|
+
const bare = value.match(/^([^\s<>]+@[^\s<>]+)$/);
|
|
26169
|
+
return bare ? { email: bare[1].trim().toLowerCase(), name: null } : null;
|
|
26170
|
+
}
|
|
26171
|
+
app.post("/schedule-connections/actions/gmail/search-contacts", auth2, requireIntegrationsTier, async (c) => {
|
|
26172
|
+
const user = c.get("user");
|
|
26173
|
+
const body = await c.req.json().catch(() => ({}));
|
|
26174
|
+
const connectionId = providerConfigKeyFrom(body.connectionId);
|
|
26175
|
+
const query = typeof body.query === "string" ? body.query.trim() : "";
|
|
26176
|
+
const maxMessages = Math.min(150, Math.max(1, Number.isFinite(body.maxMessages) ? Number(body.maxMessages) : 50));
|
|
26177
|
+
const pageToken = typeof body.pageToken === "string" ? body.pageToken : void 0;
|
|
26178
|
+
if (!connectionId || !query) return c.json({ ok: false, error: "connectionId and query are required." }, 400);
|
|
26179
|
+
try {
|
|
26180
|
+
const listRaw = await callScheduleConnectionRead(user.email, connectionId, "list-messages", { q: query, maxResults: maxMessages, pageToken });
|
|
26181
|
+
const listText = toolResultText(listRaw);
|
|
26182
|
+
const list = listText ? JSON.parse(listText) : {};
|
|
26183
|
+
const ids = (list.messages ?? []).map((m) => m.id);
|
|
26184
|
+
const messages = [];
|
|
26185
|
+
const contacts = /* @__PURE__ */ new Map();
|
|
26186
|
+
for (const id of ids) {
|
|
26187
|
+
const msgRaw = await callScheduleConnectionRead(user.email, connectionId, "get-message", { id, format: "metadata" });
|
|
26188
|
+
const msgText = toolResultText(msgRaw);
|
|
26189
|
+
if (!msgText) continue;
|
|
26190
|
+
const msg = JSON.parse(msgText);
|
|
26191
|
+
const headers = new Map((msg.payload?.headers ?? []).map((h) => [h.name.toLowerCase(), h.value]));
|
|
26192
|
+
const date = headers.get("date") ?? null;
|
|
26193
|
+
const fromRaw = headers.get("from");
|
|
26194
|
+
const from = parseEmailAddress(fromRaw);
|
|
26195
|
+
const subject = headers.get("subject") ?? null;
|
|
26196
|
+
messages.push({ id, date, from: fromRaw ?? null, to: headers.get("to") ?? null, subject, snippet: msg.snippet ?? null });
|
|
26197
|
+
if (from) {
|
|
26198
|
+
const domain = from.email.split("@")[1] ?? null;
|
|
26199
|
+
const existing = contacts.get(from.email);
|
|
26200
|
+
if (existing) {
|
|
26201
|
+
existing.messageCount += 1;
|
|
26202
|
+
existing.name = existing.name ?? from.name;
|
|
26203
|
+
if (subject && existing.sampleSubjects.length < 5 && !existing.sampleSubjects.includes(subject)) existing.sampleSubjects.push(subject);
|
|
26204
|
+
if (date && (!existing.lastSeen || date > existing.lastSeen)) existing.lastSeen = date;
|
|
26205
|
+
if (date && (!existing.firstSeen || date < existing.firstSeen)) existing.firstSeen = date;
|
|
26206
|
+
} else {
|
|
26207
|
+
contacts.set(from.email, { email: from.email, name: from.name, domain, messageCount: 1, firstSeen: date, lastSeen: date, sampleSubjects: subject ? [subject] : [] });
|
|
26208
|
+
}
|
|
26209
|
+
}
|
|
26210
|
+
}
|
|
26211
|
+
const totalMatches = list.resultSizeEstimate;
|
|
26212
|
+
return c.json({
|
|
26213
|
+
ok: true,
|
|
26214
|
+
totalMatches,
|
|
26215
|
+
messagesFetched: messages.length,
|
|
26216
|
+
truncated: typeof totalMatches === "number" ? totalMatches > messages.length : Boolean(list.nextPageToken),
|
|
26217
|
+
nextPageToken: list.nextPageToken ?? null,
|
|
26218
|
+
messages,
|
|
26219
|
+
contacts: Array.from(contacts.values())
|
|
26220
|
+
});
|
|
26221
|
+
} catch (err) {
|
|
26222
|
+
return scheduleConnectionError(c, err, "Unable to search Gmail contacts.");
|
|
26223
|
+
}
|
|
26224
|
+
});
|
|
26128
26225
|
app.post("/schedule-connections/actions/google-calendar/create-event", auth2, requireIntegrationsTier, async (c) => {
|
|
26129
26226
|
const user = c.get("user");
|
|
26130
26227
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -26143,7 +26240,7 @@ app.post("/schedule-connections/actions/google-calendar/create-event", auth2, re
|
|
|
26143
26240
|
start: { dateTime: body.startDateTime, timeZone },
|
|
26144
26241
|
end: { dateTime: body.endDateTime, timeZone },
|
|
26145
26242
|
attendees
|
|
26146
|
-
},
|
|
26243
|
+
}, "create-event", connectedActionIdempotencyKey(c));
|
|
26147
26244
|
return c.json({ ok: true, result });
|
|
26148
26245
|
} catch (err) {
|
|
26149
26246
|
return scheduleConnectionError(c, err, "Unable to create the calendar event.");
|
|
@@ -26163,7 +26260,7 @@ app.post("/schedule-connections/actions/zoom/create-meeting", auth2, requireInte
|
|
|
26163
26260
|
durationMinutes: typeof body.durationMinutes === "number" ? body.durationMinutes : 30,
|
|
26164
26261
|
timezone: typeof body.timezone === "string" ? body.timezone : void 0,
|
|
26165
26262
|
agenda: body.agenda
|
|
26166
|
-
},
|
|
26263
|
+
}, "create-meeting", connectedActionIdempotencyKey(c));
|
|
26167
26264
|
return c.json({ ok: true, result });
|
|
26168
26265
|
} catch (err) {
|
|
26169
26266
|
return scheduleConnectionError(c, err, "Unable to create the Zoom meeting.");
|
|
@@ -27191,6 +27288,22 @@ app.post("/billing/concurrency/checkout", requireAllowedOrigin, sessionAuth, asy
|
|
|
27191
27288
|
return c.json({ error: message }, 500);
|
|
27192
27289
|
}
|
|
27193
27290
|
});
|
|
27291
|
+
async function chargeTierChangeNow(stripeClient, subscriptionId, customerId) {
|
|
27292
|
+
try {
|
|
27293
|
+
const draft = await stripeClient.invoices.create({
|
|
27294
|
+
customer: customerId,
|
|
27295
|
+
subscription: subscriptionId,
|
|
27296
|
+
auto_advance: true,
|
|
27297
|
+
collection_method: "charge_automatically"
|
|
27298
|
+
});
|
|
27299
|
+
const finalized = await stripeClient.invoices.finalizeInvoice(draft.id);
|
|
27300
|
+
if (finalized.amount_due <= 0 || finalized.status === "paid") return { ok: true, amountDue: finalized.amount_due };
|
|
27301
|
+
const paid = await stripeClient.invoices.pay(finalized.id);
|
|
27302
|
+
return { ok: paid.status === "paid", amountDue: paid.amount_due };
|
|
27303
|
+
} catch (err) {
|
|
27304
|
+
return { ok: false, amountDue: 0, error: err instanceof Error ? err.message : "Unable to charge the plan change immediately." };
|
|
27305
|
+
}
|
|
27306
|
+
}
|
|
27194
27307
|
app.post("/billing/subscribe", requireAllowedOrigin, sessionAuth, async (c) => {
|
|
27195
27308
|
try {
|
|
27196
27309
|
const user = c.get("sessionUser");
|
|
@@ -27214,7 +27327,16 @@ app.post("/billing/subscribe", requireAllowedOrigin, sessionAuth, async (c) => {
|
|
|
27214
27327
|
items: [{ id: itemId, price: tier.price_id }],
|
|
27215
27328
|
proration_behavior: "create_prorations"
|
|
27216
27329
|
});
|
|
27217
|
-
|
|
27330
|
+
const billed = await chargeTierChangeNow(stripeClient, user.subscription_id, customerId);
|
|
27331
|
+
if (!billed.ok) {
|
|
27332
|
+
return c.json({
|
|
27333
|
+
updated: true,
|
|
27334
|
+
tier: tier.tier,
|
|
27335
|
+
billed: false,
|
|
27336
|
+
error: billed.error ?? "Plan changed, but the prorated charge could not be collected. Update your payment method and it will retry automatically."
|
|
27337
|
+
});
|
|
27338
|
+
}
|
|
27339
|
+
return c.json({ updated: true, tier: tier.tier, billed: true });
|
|
27218
27340
|
}
|
|
27219
27341
|
}
|
|
27220
27342
|
const session = await stripeClient.checkout.sessions.create({
|
|
@@ -27331,7 +27453,16 @@ app.post("/billing/subscribe/terminal-checkout", auth2, async (c) => {
|
|
|
27331
27453
|
items: [{ id: itemId, price: tier.price_id }],
|
|
27332
27454
|
proration_behavior: "create_prorations"
|
|
27333
27455
|
});
|
|
27334
|
-
|
|
27456
|
+
const billed = await chargeTierChangeNow(stripeClient, user.subscription_id, customerId);
|
|
27457
|
+
if (!billed.ok) {
|
|
27458
|
+
return c.json({
|
|
27459
|
+
updated: true,
|
|
27460
|
+
tier: tier.tier,
|
|
27461
|
+
billed: false,
|
|
27462
|
+
message: `Switched to ${tier.label} (prorated), but the prorated charge could not be collected: ${billed.error ?? "unknown error"}. Update your payment method and it will retry automatically.`
|
|
27463
|
+
});
|
|
27464
|
+
}
|
|
27465
|
+
return c.json({ updated: true, tier: tier.tier, billed: true, message: `Switched to ${tier.label} and charged the prorated difference immediately \u2014 credits and concurrency are live now.` });
|
|
27335
27466
|
}
|
|
27336
27467
|
}
|
|
27337
27468
|
const session = await stripeClient.checkout.sessions.create({
|
|
@@ -27430,7 +27561,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
27430
27561
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
27431
27562
|
return c.json({ error: "Unauthorized" }, 401);
|
|
27432
27563
|
}
|
|
27433
|
-
const { drainQueue } = await import("./worker-
|
|
27564
|
+
const { drainQueue } = await import("./worker-JU5FX3NE.js");
|
|
27434
27565
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
27435
27566
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
27436
27567
|
const [results, sweepResult, reapResult, expiredResult, blobCleanup, connectedDataArtifactCleanup] = await Promise.all([
|
|
@@ -27633,4 +27764,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
27633
27764
|
export {
|
|
27634
27765
|
app
|
|
27635
27766
|
};
|
|
27636
|
-
//# sourceMappingURL=server-
|
|
27767
|
+
//# sourceMappingURL=server-RKAKXRAR.js.map
|