@drawbridge/drawbridge-utils 0.0.134 → 0.0.136
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/connections/index.cjs +230 -106
- package/dist/connections/index.d.cts +167 -17
- package/dist/connections/index.d.ts +167 -17
- package/dist/connections/index.js +230 -106
- package/dist/connections/oauth.cjs +8 -2
- package/dist/connections/oauth.d.cts +14 -3
- package/dist/connections/oauth.d.ts +14 -3
- package/dist/connections/oauth.js +8 -2
- package/dist/pricing.cjs +727 -695
- package/dist/pricing.d.cts +53 -5
- package/dist/pricing.d.ts +53 -5
- package/dist/pricing.js +726 -695
- package/dist/providers.cjs +230 -106
- package/dist/providers.d.cts +4 -4
- package/dist/providers.d.ts +4 -4
- package/dist/providers.js +230 -106
- package/dist/twilio.cjs +51 -0
- package/dist/twilio.d.cts +67 -0
- package/dist/twilio.d.ts +67 -0
- package/dist/twilio.js +51 -0
- package/package.json +2 -2
|
@@ -464,8 +464,14 @@ var authToken = async ({
|
|
|
464
464
|
});
|
|
465
465
|
const body = await response.json().catch(() => ({}));
|
|
466
466
|
if (!response.ok) {
|
|
467
|
-
throw
|
|
468
|
-
|
|
467
|
+
throw Object.assign(
|
|
468
|
+
new Error(
|
|
469
|
+
renewing ? "The vendor refused the refresh token (" + response.status + ") \u2014 reconnect the connection" : "The vendor refused the exchange (" + response.status + ")" + ((body == null ? void 0 : body.error) ? ": " + body.error : "")
|
|
470
|
+
),
|
|
471
|
+
{
|
|
472
|
+
status: response.status,
|
|
473
|
+
...renewing && { code: "grant_refused" }
|
|
474
|
+
}
|
|
469
475
|
);
|
|
470
476
|
}
|
|
471
477
|
if (!renewing && !body.access_token) throw new Error("The vendor returned no access token");
|
|
@@ -521,7 +527,10 @@ var accessToken = async ({
|
|
|
521
527
|
}
|
|
522
528
|
if (!force && !isStale(settings, now)) return settings.accessToken;
|
|
523
529
|
if (!settings.refreshToken) {
|
|
524
|
-
throw
|
|
530
|
+
throw Object.assign(
|
|
531
|
+
new Error("This connection has expired and cannot be renewed automatically. Reconnect it."),
|
|
532
|
+
{ code: "grant_refused" }
|
|
533
|
+
);
|
|
525
534
|
}
|
|
526
535
|
const minted = await manifest.hooks.auth.token({
|
|
527
536
|
clientId,
|
|
@@ -1116,6 +1125,74 @@ var drawbridge_default = `<svg width="500" height="500" viewBox="0 0 500 500" fi
|
|
|
1116
1125
|
</defs>
|
|
1117
1126
|
</svg>`;
|
|
1118
1127
|
|
|
1128
|
+
// lib/transactions.js
|
|
1129
|
+
var import_drawbridge_telemetry = require("@drawbridge/drawbridge-telemetry");
|
|
1130
|
+
|
|
1131
|
+
// lib/billing.js
|
|
1132
|
+
var import_drawbridge_telemetry2 = require("@drawbridge/drawbridge-telemetry");
|
|
1133
|
+
var logger = (0, import_drawbridge_telemetry2.createLogger)();
|
|
1134
|
+
var MARKUP = 1.3;
|
|
1135
|
+
var cost = {
|
|
1136
|
+
// gemini-3.5-flash — verified against Google's pricing page 2026-07-09:
|
|
1137
|
+
// $0.15 cached / $1.50 input / $9.00 output per 1M tokens (thinking billed at
|
|
1138
|
+
// output). ~3.6x the retired 2.5-flash output rate.
|
|
1139
|
+
"gemini-3.5-flash": {
|
|
1140
|
+
cached: 15,
|
|
1141
|
+
input: 150,
|
|
1142
|
+
output: 900
|
|
1143
|
+
},
|
|
1144
|
+
// gemini-3.5-flash-lite — verified against Google's pricing page 2026-08-20:
|
|
1145
|
+
// $0.03 cached / $0.30 input / $2.50 output per 1M tokens (thinking billed at
|
|
1146
|
+
// output). A fifth of flash on input, ~a quarter on output. Growth's assistant
|
|
1147
|
+
// ranks the feed on this tier — one call per page — so its rows were the
|
|
1148
|
+
// unpriced ones until now.
|
|
1149
|
+
"gemini-3.5-flash-lite": {
|
|
1150
|
+
cached: 3,
|
|
1151
|
+
input: 30,
|
|
1152
|
+
output: 250
|
|
1153
|
+
},
|
|
1154
|
+
"gemini-2.5-flash": {
|
|
1155
|
+
cached: 3,
|
|
1156
|
+
input: 30,
|
|
1157
|
+
output: 250
|
|
1158
|
+
},
|
|
1159
|
+
"gemini-2.5-flash-image": {
|
|
1160
|
+
cached: 3,
|
|
1161
|
+
input: 30,
|
|
1162
|
+
output: 3e3
|
|
1163
|
+
},
|
|
1164
|
+
// gemini-3-pro-image-preview — verified against Google's pricing page
|
|
1165
|
+
// 2026-08-12: $2.00 input / $12.00 text output per 1M, and image output
|
|
1166
|
+
// tokens at ~$120/1M (a 1K-2K image is 1120 tokens = $0.134, a 4K image
|
|
1167
|
+
// 2000 tokens = $0.24). Encoded the flash-image way: one flat output rate
|
|
1168
|
+
// that reproduces the per-image price from the tokens usageMetadata
|
|
1169
|
+
// reports. Growth's hero generation runs this model today.
|
|
1170
|
+
"gemini-3-pro-image-preview": {
|
|
1171
|
+
cached: 20,
|
|
1172
|
+
input: 200,
|
|
1173
|
+
output: 12e3
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
var toolCost = {
|
|
1177
|
+
search: 3.5
|
|
1178
|
+
};
|
|
1179
|
+
var toolPricing = Object.fromEntries(
|
|
1180
|
+
Object.entries(toolCost).map(([tool, value]) => [
|
|
1181
|
+
tool,
|
|
1182
|
+
Math.ceil(value * MARKUP)
|
|
1183
|
+
])
|
|
1184
|
+
);
|
|
1185
|
+
var pricing = Object.fromEntries(
|
|
1186
|
+
Object.entries(cost).map(([model, rates]) => [
|
|
1187
|
+
model,
|
|
1188
|
+
{
|
|
1189
|
+
cached: Math.round(rates.cached * MARKUP),
|
|
1190
|
+
input: Math.round(rates.input * MARKUP),
|
|
1191
|
+
output: Math.round(rates.output * MARKUP)
|
|
1192
|
+
}
|
|
1193
|
+
])
|
|
1194
|
+
);
|
|
1195
|
+
|
|
1119
1196
|
// lib/features.js
|
|
1120
1197
|
var page = {
|
|
1121
1198
|
qrcode: {
|
|
@@ -1578,74 +1655,6 @@ var conversionRate = (subscription) => {
|
|
|
1578
1655
|
return ((_a = resolvePlan(subscription)) == null ? void 0 : _a.conversion) ?? free.conversion;
|
|
1579
1656
|
};
|
|
1580
1657
|
|
|
1581
|
-
// lib/transactions.js
|
|
1582
|
-
var import_drawbridge_telemetry = require("@drawbridge/drawbridge-telemetry");
|
|
1583
|
-
|
|
1584
|
-
// lib/billing.js
|
|
1585
|
-
var import_drawbridge_telemetry2 = require("@drawbridge/drawbridge-telemetry");
|
|
1586
|
-
var logger = (0, import_drawbridge_telemetry2.createLogger)();
|
|
1587
|
-
var MARKUP = 1.3;
|
|
1588
|
-
var cost = {
|
|
1589
|
-
// gemini-3.5-flash — verified against Google's pricing page 2026-07-09:
|
|
1590
|
-
// $0.15 cached / $1.50 input / $9.00 output per 1M tokens (thinking billed at
|
|
1591
|
-
// output). ~3.6x the retired 2.5-flash output rate.
|
|
1592
|
-
"gemini-3.5-flash": {
|
|
1593
|
-
cached: 15,
|
|
1594
|
-
input: 150,
|
|
1595
|
-
output: 900
|
|
1596
|
-
},
|
|
1597
|
-
// gemini-3.5-flash-lite — verified against Google's pricing page 2026-08-20:
|
|
1598
|
-
// $0.03 cached / $0.30 input / $2.50 output per 1M tokens (thinking billed at
|
|
1599
|
-
// output). A fifth of flash on input, ~a quarter on output. Growth's assistant
|
|
1600
|
-
// ranks the feed on this tier — one call per page — so its rows were the
|
|
1601
|
-
// unpriced ones until now.
|
|
1602
|
-
"gemini-3.5-flash-lite": {
|
|
1603
|
-
cached: 3,
|
|
1604
|
-
input: 30,
|
|
1605
|
-
output: 250
|
|
1606
|
-
},
|
|
1607
|
-
"gemini-2.5-flash": {
|
|
1608
|
-
cached: 3,
|
|
1609
|
-
input: 30,
|
|
1610
|
-
output: 250
|
|
1611
|
-
},
|
|
1612
|
-
"gemini-2.5-flash-image": {
|
|
1613
|
-
cached: 3,
|
|
1614
|
-
input: 30,
|
|
1615
|
-
output: 3e3
|
|
1616
|
-
},
|
|
1617
|
-
// gemini-3-pro-image-preview — verified against Google's pricing page
|
|
1618
|
-
// 2026-08-12: $2.00 input / $12.00 text output per 1M, and image output
|
|
1619
|
-
// tokens at ~$120/1M (a 1K-2K image is 1120 tokens = $0.134, a 4K image
|
|
1620
|
-
// 2000 tokens = $0.24). Encoded the flash-image way: one flat output rate
|
|
1621
|
-
// that reproduces the per-image price from the tokens usageMetadata
|
|
1622
|
-
// reports. Growth's hero generation runs this model today.
|
|
1623
|
-
"gemini-3-pro-image-preview": {
|
|
1624
|
-
cached: 20,
|
|
1625
|
-
input: 200,
|
|
1626
|
-
output: 12e3
|
|
1627
|
-
}
|
|
1628
|
-
};
|
|
1629
|
-
var toolCost = {
|
|
1630
|
-
search: 3.5
|
|
1631
|
-
};
|
|
1632
|
-
var toolPricing = Object.fromEntries(
|
|
1633
|
-
Object.entries(toolCost).map(([tool, value]) => [
|
|
1634
|
-
tool,
|
|
1635
|
-
Math.ceil(value * MARKUP)
|
|
1636
|
-
])
|
|
1637
|
-
);
|
|
1638
|
-
var pricing = Object.fromEntries(
|
|
1639
|
-
Object.entries(cost).map(([model, rates]) => [
|
|
1640
|
-
model,
|
|
1641
|
-
{
|
|
1642
|
-
cached: Math.round(rates.cached * MARKUP),
|
|
1643
|
-
input: Math.round(rates.input * MARKUP),
|
|
1644
|
-
output: Math.round(rates.output * MARKUP)
|
|
1645
|
-
}
|
|
1646
|
-
])
|
|
1647
|
-
);
|
|
1648
|
-
|
|
1649
1658
|
// lib/pricing.js
|
|
1650
1659
|
var emailPlans = {
|
|
1651
1660
|
essentials50k: {
|
|
@@ -1715,6 +1724,33 @@ var sending = {
|
|
|
1715
1724
|
plan: emailPlan,
|
|
1716
1725
|
plans: emailPlans,
|
|
1717
1726
|
title: emailPlans[emailPlan].title
|
|
1727
|
+
},
|
|
1728
|
+
// What one outbound SMS SEGMENT costs us, against what channels.sms below
|
|
1729
|
+
// sells it for. Here for the same reason the email figures are: this file is
|
|
1730
|
+
// where a rate is found without knowing whose module owns it — and its
|
|
1731
|
+
// absence is why "does an SMS action cover its cost" could not be answered
|
|
1732
|
+
// from this repo at all.
|
|
1733
|
+
//
|
|
1734
|
+
// US toll-free outbound, read from Twilio's SMS pricing page 2026-09-03
|
|
1735
|
+
// (https://www.twilio.com/en-us/sms/pricing/us): $0.0083 per segment, plus a
|
|
1736
|
+
// carrier fee that varies by the DESTINATION carrier — $0.0035 AT&T, $0.0045
|
|
1737
|
+
// T-Mobile / Verizon / US Cellular, $0.004 everywhere else. CENTS per
|
|
1738
|
+
// segment, like every other rate in this file.
|
|
1739
|
+
//
|
|
1740
|
+
// WORST CASE, deliberately. A price floor computed from the average is a
|
|
1741
|
+
// floor that half of real traffic sits underneath.
|
|
1742
|
+
//
|
|
1743
|
+
// A segment is 153 GSM-7 characters once a message spans more than one — or
|
|
1744
|
+
// 67 if the body contains a single emoji or curly quote, which flips the
|
|
1745
|
+
// whole message to UCS-2. Twilio reports the count as `num_segments` on the
|
|
1746
|
+
// Message resource, which is what the send bills on.
|
|
1747
|
+
sms: {
|
|
1748
|
+
baseCents: 0.83,
|
|
1749
|
+
// The dearest carrier fee, not the mean: see WORST CASE above.
|
|
1750
|
+
carrierCents: 0.45,
|
|
1751
|
+
// Stated, not summed — 0.83 + 0.45 is 1.2800000000000002 in binary
|
|
1752
|
+
// floating point, and this number is compared against money.
|
|
1753
|
+
segmentCostCents: 1.28
|
|
1718
1754
|
}
|
|
1719
1755
|
};
|
|
1720
1756
|
var channels = {
|
|
@@ -1730,6 +1766,9 @@ var channels = {
|
|
|
1730
1766
|
includedInAllowance: false
|
|
1731
1767
|
}
|
|
1732
1768
|
};
|
|
1769
|
+
var MINIMUM_ACTION_CENTS = Math.round(
|
|
1770
|
+
sending.sms.segmentCostCents / channels.sms.actionsPerSegment * MARKUP * 1e3
|
|
1771
|
+
) / 1e3;
|
|
1733
1772
|
|
|
1734
1773
|
// lib/connections/providers/drawbridge.js
|
|
1735
1774
|
var STOP_KEYWORDS = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"];
|
|
@@ -1754,13 +1793,16 @@ var teamRecipients = async ({ memberIds = [], organization: organization2, read
|
|
|
1754
1793
|
]
|
|
1755
1794
|
}) : [];
|
|
1756
1795
|
const seen = /* @__PURE__ */ new Set();
|
|
1757
|
-
return
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1796
|
+
return {
|
|
1797
|
+
org,
|
|
1798
|
+
recipients: [owner, ...members].filter((member) => {
|
|
1799
|
+
if (!(member == null ? void 0 : member.id) || !(member == null ? void 0 : member.email)) return false;
|
|
1800
|
+
const address = member.email.toLowerCase();
|
|
1801
|
+
if (seen.has(address)) return false;
|
|
1802
|
+
seen.add(address);
|
|
1803
|
+
return true;
|
|
1804
|
+
})
|
|
1805
|
+
};
|
|
1764
1806
|
};
|
|
1765
1807
|
var queueNotification = (data2) => ({ collection: "notification", data: data2, operation: "create" });
|
|
1766
1808
|
var drawbridge_default2 = {
|
|
@@ -1836,7 +1878,7 @@ var drawbridge_default2 = {
|
|
|
1836
1878
|
const count = Number((counted == null ? void 0 : counted.count) || 0);
|
|
1837
1879
|
const request2 = { campaign, count, days };
|
|
1838
1880
|
if (!count) return { message: "No new leads in the period \u2014 digest skipped.", request: request2, response: { skipped: true }, skipped: true };
|
|
1839
|
-
const recipients = await teamRecipients({
|
|
1881
|
+
const { recipients } = await teamRecipients({
|
|
1840
1882
|
memberIds: ((_d = step.settings) == null ? void 0 : _d.members) || [],
|
|
1841
1883
|
organization: workflow.organization,
|
|
1842
1884
|
read
|
|
@@ -1872,7 +1914,7 @@ var drawbridge_default2 = {
|
|
|
1872
1914
|
var _a;
|
|
1873
1915
|
const memberIds = ((_a = step.settings) == null ? void 0 : _a.members) || [];
|
|
1874
1916
|
const request2 = { members: memberIds.length };
|
|
1875
|
-
const recipients = await teamRecipients({ memberIds, organization: workflow.organization, read });
|
|
1917
|
+
const { org, recipients } = await teamRecipients({ memberIds, organization: workflow.organization, read });
|
|
1876
1918
|
if (!recipients.length) {
|
|
1877
1919
|
return {
|
|
1878
1920
|
message: "No owner or accepted member with an email address \u2014 team notification skipped.",
|
|
@@ -1882,32 +1924,37 @@ var drawbridge_default2 = {
|
|
|
1882
1924
|
};
|
|
1883
1925
|
}
|
|
1884
1926
|
const bucket = Math.floor(Date.now() / (15 * 60 * 1e3));
|
|
1927
|
+
const campaign = (context == null ? void 0 : context.campaign) ? await read.get({ collection: "campaign", query: { id: context.campaign } }) : null;
|
|
1928
|
+
const link = (context == null ? void 0 : context.campaign) && (context == null ? void 0 : context.lead) ? {
|
|
1929
|
+
href: "/organizations/" + workflow.organization + "/campaigns/" + context.campaign + "/leads/" + context.lead,
|
|
1930
|
+
text: "View lead"
|
|
1931
|
+
} : null;
|
|
1932
|
+
const body = ((context == null ? void 0 : context.email) || "Someone") + " just entered" + ((campaign == null ? void 0 : campaign.title) ? " " + campaign.title : "") + ((org == null ? void 0 : org.title) ? " at " + org.title : "") + ".";
|
|
1933
|
+
const subject = "New lead" + ((campaign == null ? void 0 : campaign.title) ? " \u2014 " + campaign.title : "");
|
|
1885
1934
|
return {
|
|
1886
1935
|
message: "Team notification queued for " + recipients.length + " recipient(s).",
|
|
1887
1936
|
request: request2,
|
|
1888
1937
|
response: { notified: recipients.length },
|
|
1889
|
-
writes: recipients.map((member) => {
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
};
|
|
1910
|
-
})
|
|
1938
|
+
writes: recipients.map((member) => ({
|
|
1939
|
+
...queueNotification({
|
|
1940
|
+
audience: "member",
|
|
1941
|
+
// Per workflow, recipient AND bucket, so one recipient's damper
|
|
1942
|
+
// can never swallow another's mail and a later bucket is never
|
|
1943
|
+
// mistaken for a duplicate of an earlier one.
|
|
1944
|
+
key: "team.notify." + workflow.id + "." + member.id + "." + bucket,
|
|
1945
|
+
link,
|
|
1946
|
+
message: body,
|
|
1947
|
+
organization: workflow.organization,
|
|
1948
|
+
send: { type: "email", email: member.email },
|
|
1949
|
+
title: subject,
|
|
1950
|
+
workflow: workflow.id
|
|
1951
|
+
}),
|
|
1952
|
+
// E11000 IS THE DAMPER WORKING: this recipient has already been
|
|
1953
|
+
// told within the bucket. Declared per write rather than assumed by
|
|
1954
|
+
// the shell, because on every other write here a duplicate key is a
|
|
1955
|
+
// real failure.
|
|
1956
|
+
ignoreDuplicate: true
|
|
1957
|
+
}))
|
|
1911
1958
|
};
|
|
1912
1959
|
},
|
|
1913
1960
|
// Drawbridge sends lead-facing email itself — no merchant provider gates
|
|
@@ -2313,14 +2360,24 @@ var drawbridge_default2 = {
|
|
|
2313
2360
|
}),
|
|
2314
2361
|
// To organization MEMBERS. Never suppressed — an entrant's opt-out must
|
|
2315
2362
|
// not silence an alert to staff — and not billed.
|
|
2363
|
+
//
|
|
2364
|
+
// ITS COPY IS WRITTEN, NOT ASKED FOR. This says the same thing every
|
|
2365
|
+
// time — a lead arrived, here it is — so the merchant chooses who hears
|
|
2366
|
+
// it, not how it reads, and the hook composes the subject, the body and
|
|
2367
|
+
// the link to the lead.
|
|
2368
|
+
//
|
|
2369
|
+
// `message` and `subject` stay declared, optional and IGNORED: stored
|
|
2370
|
+
// workflows still carry them and the api validates settings with
|
|
2371
|
+
// noUnknown().strict(), so dropping the keys would 400 the next save of
|
|
2372
|
+
// a workflow built before this.
|
|
2316
2373
|
notify: () => ({
|
|
2317
2374
|
hook: "email.notify",
|
|
2318
2375
|
key: "Email \u2014 Notification",
|
|
2319
2376
|
queue: "notification",
|
|
2320
2377
|
settings: {
|
|
2321
2378
|
members: { of: "string", type: "array" },
|
|
2322
|
-
message: {
|
|
2323
|
-
subject: {
|
|
2379
|
+
message: { type: "string" },
|
|
2380
|
+
subject: { type: "string" }
|
|
2324
2381
|
},
|
|
2325
2382
|
triggers: ["lead.insert"],
|
|
2326
2383
|
// Zero is a PRICE, and a deliberate one. Declared rather than omitted
|
|
@@ -2735,8 +2792,62 @@ var klaviyo_default2 = {
|
|
|
2735
2792
|
sms: false,
|
|
2736
2793
|
inbound: false,
|
|
2737
2794
|
// Nothing to set up or tear down at the vendor: the grant is the whole
|
|
2738
|
-
// integration
|
|
2739
|
-
lifecycle
|
|
2795
|
+
// integration. What CAN rot is the grant itself, so health is the one
|
|
2796
|
+
// lifecycle hook Klaviyo carries.
|
|
2797
|
+
lifecycle: {
|
|
2798
|
+
// Nothing to set up, tear down, or re-register at the vendor — the
|
|
2799
|
+
// grant is the whole integration.
|
|
2800
|
+
cleanup: false,
|
|
2801
|
+
register: false,
|
|
2802
|
+
rehydrate: false,
|
|
2803
|
+
// KEEP THE GRANT PROVEN AND WARM. The shell mints a fresh access
|
|
2804
|
+
// token before every hook run — for Klaviyo that spends the refresh
|
|
2805
|
+
// token, which is the only question that reaches Klaviyo (see
|
|
2806
|
+
// auth.probe) — so this body running at all proves the grant still
|
|
2807
|
+
// rotates, and the daily run keeps it inside Klaviyo's 90-day idle
|
|
2808
|
+
// window. A dead grant never gets here: the shell's mint throws
|
|
2809
|
+
// `grant_refused` and the step runner errors the connection itself.
|
|
2810
|
+
//
|
|
2811
|
+
// The one call below proves the minted token is HONOURED — mint and
|
|
2812
|
+
// acceptance are different facts, and /accounts is already the call
|
|
2813
|
+
// the connect flow makes (auth.connect), so it needs no new scope.
|
|
2814
|
+
health: async ({ connection: connection2, token }, { fetcher, read } = {}) => {
|
|
2815
|
+
const request2 = { connectionId: connection2.id };
|
|
2816
|
+
try {
|
|
2817
|
+
await api2("/accounts", { fetcher, token });
|
|
2818
|
+
return {
|
|
2819
|
+
message: "Health check passed \u2014 token minted and accepted.",
|
|
2820
|
+
request: request2,
|
|
2821
|
+
response: { pingedAt: /* @__PURE__ */ new Date() }
|
|
2822
|
+
};
|
|
2823
|
+
} catch (error) {
|
|
2824
|
+
if ([401, 403].includes(error.status) && read) {
|
|
2825
|
+
const current = await read.get({ collection: "connection", query: { id: connection2.id } });
|
|
2826
|
+
if (current) {
|
|
2827
|
+
const others = (current.errors || []).filter((entry) => entry.source !== "oauth");
|
|
2828
|
+
error.writes = [{
|
|
2829
|
+
collection: "connection",
|
|
2830
|
+
data: {
|
|
2831
|
+
$set: {
|
|
2832
|
+
errors: [
|
|
2833
|
+
...others,
|
|
2834
|
+
{
|
|
2835
|
+
message: "Klaviyo no longer accepts this connection. Reconnect Klaviyo to resume syncing.",
|
|
2836
|
+
source: "oauth"
|
|
2837
|
+
}
|
|
2838
|
+
],
|
|
2839
|
+
status: "error"
|
|
2840
|
+
}
|
|
2841
|
+
},
|
|
2842
|
+
operation: "update",
|
|
2843
|
+
query: { id: connection2.id }
|
|
2844
|
+
}];
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
throw error;
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
},
|
|
2740
2851
|
resources: {
|
|
2741
2852
|
// The lists a merchant can sync into, for the picker on their
|
|
2742
2853
|
// connection.
|
|
@@ -2812,6 +2923,19 @@ var klaviyo_default2 = {
|
|
|
2812
2923
|
return ((_a = data2 == null ? void 0 : data2.settings) == null ? void 0 : _a.list) ? data2.status : "pending";
|
|
2813
2924
|
},
|
|
2814
2925
|
steps: {
|
|
2926
|
+
connection: {
|
|
2927
|
+
// The daily grant check — same step type and workflow shape as
|
|
2928
|
+
// Shopify's, provisioned as a system workflow per connection.
|
|
2929
|
+
health: {
|
|
2930
|
+
check: () => ({
|
|
2931
|
+
description: "Keeps the Klaviyo grant working \u2014 spends the refresh token daily so a revoked or idle grant is reported instead of discovered by a failing sync.",
|
|
2932
|
+
hook: "lifecycle.health",
|
|
2933
|
+
key: "Klaviyo Connection Health",
|
|
2934
|
+
queue: "connection",
|
|
2935
|
+
system: true
|
|
2936
|
+
})
|
|
2937
|
+
}
|
|
2938
|
+
},
|
|
2815
2939
|
contacts: {
|
|
2816
2940
|
// A DECLARATION, not the work. It names the hook that does the work, and
|
|
2817
2941
|
// says where that hook's values belong. Nested like the hooks, and the
|