@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
package/dist/providers.js
CHANGED
|
@@ -326,8 +326,14 @@ var authToken = async ({
|
|
|
326
326
|
});
|
|
327
327
|
const body = await response.json().catch(() => ({}));
|
|
328
328
|
if (!response.ok) {
|
|
329
|
-
throw
|
|
330
|
-
|
|
329
|
+
throw Object.assign(
|
|
330
|
+
new Error(
|
|
331
|
+
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 : "")
|
|
332
|
+
),
|
|
333
|
+
{
|
|
334
|
+
status: response.status,
|
|
335
|
+
...renewing && { code: "grant_refused" }
|
|
336
|
+
}
|
|
331
337
|
);
|
|
332
338
|
}
|
|
333
339
|
if (!renewing && !body.access_token) throw new Error("The vendor returned no access token");
|
|
@@ -383,7 +389,10 @@ var accessToken = async ({
|
|
|
383
389
|
}
|
|
384
390
|
if (!force && !isStale(settings, now)) return settings.accessToken;
|
|
385
391
|
if (!settings.refreshToken) {
|
|
386
|
-
throw
|
|
392
|
+
throw Object.assign(
|
|
393
|
+
new Error("This connection has expired and cannot be renewed automatically. Reconnect it."),
|
|
394
|
+
{ code: "grant_refused" }
|
|
395
|
+
);
|
|
387
396
|
}
|
|
388
397
|
const minted = await manifest.hooks.auth.token({
|
|
389
398
|
clientId,
|
|
@@ -978,6 +987,74 @@ var drawbridge_default = `<svg width="500" height="500" viewBox="0 0 500 500" fi
|
|
|
978
987
|
</defs>
|
|
979
988
|
</svg>`;
|
|
980
989
|
|
|
990
|
+
// lib/transactions.js
|
|
991
|
+
import { currentTraceId } from "@drawbridge/drawbridge-telemetry";
|
|
992
|
+
|
|
993
|
+
// lib/billing.js
|
|
994
|
+
import { createLogger } from "@drawbridge/drawbridge-telemetry";
|
|
995
|
+
var logger = createLogger();
|
|
996
|
+
var MARKUP = 1.3;
|
|
997
|
+
var cost = {
|
|
998
|
+
// gemini-3.5-flash — verified against Google's pricing page 2026-07-09:
|
|
999
|
+
// $0.15 cached / $1.50 input / $9.00 output per 1M tokens (thinking billed at
|
|
1000
|
+
// output). ~3.6x the retired 2.5-flash output rate.
|
|
1001
|
+
"gemini-3.5-flash": {
|
|
1002
|
+
cached: 15,
|
|
1003
|
+
input: 150,
|
|
1004
|
+
output: 900
|
|
1005
|
+
},
|
|
1006
|
+
// gemini-3.5-flash-lite — verified against Google's pricing page 2026-08-20:
|
|
1007
|
+
// $0.03 cached / $0.30 input / $2.50 output per 1M tokens (thinking billed at
|
|
1008
|
+
// output). A fifth of flash on input, ~a quarter on output. Growth's assistant
|
|
1009
|
+
// ranks the feed on this tier — one call per page — so its rows were the
|
|
1010
|
+
// unpriced ones until now.
|
|
1011
|
+
"gemini-3.5-flash-lite": {
|
|
1012
|
+
cached: 3,
|
|
1013
|
+
input: 30,
|
|
1014
|
+
output: 250
|
|
1015
|
+
},
|
|
1016
|
+
"gemini-2.5-flash": {
|
|
1017
|
+
cached: 3,
|
|
1018
|
+
input: 30,
|
|
1019
|
+
output: 250
|
|
1020
|
+
},
|
|
1021
|
+
"gemini-2.5-flash-image": {
|
|
1022
|
+
cached: 3,
|
|
1023
|
+
input: 30,
|
|
1024
|
+
output: 3e3
|
|
1025
|
+
},
|
|
1026
|
+
// gemini-3-pro-image-preview — verified against Google's pricing page
|
|
1027
|
+
// 2026-08-12: $2.00 input / $12.00 text output per 1M, and image output
|
|
1028
|
+
// tokens at ~$120/1M (a 1K-2K image is 1120 tokens = $0.134, a 4K image
|
|
1029
|
+
// 2000 tokens = $0.24). Encoded the flash-image way: one flat output rate
|
|
1030
|
+
// that reproduces the per-image price from the tokens usageMetadata
|
|
1031
|
+
// reports. Growth's hero generation runs this model today.
|
|
1032
|
+
"gemini-3-pro-image-preview": {
|
|
1033
|
+
cached: 20,
|
|
1034
|
+
input: 200,
|
|
1035
|
+
output: 12e3
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
var toolCost = {
|
|
1039
|
+
search: 3.5
|
|
1040
|
+
};
|
|
1041
|
+
var toolPricing = Object.fromEntries(
|
|
1042
|
+
Object.entries(toolCost).map(([tool, value]) => [
|
|
1043
|
+
tool,
|
|
1044
|
+
Math.ceil(value * MARKUP)
|
|
1045
|
+
])
|
|
1046
|
+
);
|
|
1047
|
+
var pricing = Object.fromEntries(
|
|
1048
|
+
Object.entries(cost).map(([model, rates]) => [
|
|
1049
|
+
model,
|
|
1050
|
+
{
|
|
1051
|
+
cached: Math.round(rates.cached * MARKUP),
|
|
1052
|
+
input: Math.round(rates.input * MARKUP),
|
|
1053
|
+
output: Math.round(rates.output * MARKUP)
|
|
1054
|
+
}
|
|
1055
|
+
])
|
|
1056
|
+
);
|
|
1057
|
+
|
|
981
1058
|
// lib/features.js
|
|
982
1059
|
var page = {
|
|
983
1060
|
qrcode: {
|
|
@@ -1440,74 +1517,6 @@ var conversionRate = (subscription) => {
|
|
|
1440
1517
|
return ((_a = resolvePlan(subscription)) == null ? void 0 : _a.conversion) ?? free.conversion;
|
|
1441
1518
|
};
|
|
1442
1519
|
|
|
1443
|
-
// lib/transactions.js
|
|
1444
|
-
import { currentTraceId } from "@drawbridge/drawbridge-telemetry";
|
|
1445
|
-
|
|
1446
|
-
// lib/billing.js
|
|
1447
|
-
import { createLogger } from "@drawbridge/drawbridge-telemetry";
|
|
1448
|
-
var logger = createLogger();
|
|
1449
|
-
var MARKUP = 1.3;
|
|
1450
|
-
var cost = {
|
|
1451
|
-
// gemini-3.5-flash — verified against Google's pricing page 2026-07-09:
|
|
1452
|
-
// $0.15 cached / $1.50 input / $9.00 output per 1M tokens (thinking billed at
|
|
1453
|
-
// output). ~3.6x the retired 2.5-flash output rate.
|
|
1454
|
-
"gemini-3.5-flash": {
|
|
1455
|
-
cached: 15,
|
|
1456
|
-
input: 150,
|
|
1457
|
-
output: 900
|
|
1458
|
-
},
|
|
1459
|
-
// gemini-3.5-flash-lite — verified against Google's pricing page 2026-08-20:
|
|
1460
|
-
// $0.03 cached / $0.30 input / $2.50 output per 1M tokens (thinking billed at
|
|
1461
|
-
// output). A fifth of flash on input, ~a quarter on output. Growth's assistant
|
|
1462
|
-
// ranks the feed on this tier — one call per page — so its rows were the
|
|
1463
|
-
// unpriced ones until now.
|
|
1464
|
-
"gemini-3.5-flash-lite": {
|
|
1465
|
-
cached: 3,
|
|
1466
|
-
input: 30,
|
|
1467
|
-
output: 250
|
|
1468
|
-
},
|
|
1469
|
-
"gemini-2.5-flash": {
|
|
1470
|
-
cached: 3,
|
|
1471
|
-
input: 30,
|
|
1472
|
-
output: 250
|
|
1473
|
-
},
|
|
1474
|
-
"gemini-2.5-flash-image": {
|
|
1475
|
-
cached: 3,
|
|
1476
|
-
input: 30,
|
|
1477
|
-
output: 3e3
|
|
1478
|
-
},
|
|
1479
|
-
// gemini-3-pro-image-preview — verified against Google's pricing page
|
|
1480
|
-
// 2026-08-12: $2.00 input / $12.00 text output per 1M, and image output
|
|
1481
|
-
// tokens at ~$120/1M (a 1K-2K image is 1120 tokens = $0.134, a 4K image
|
|
1482
|
-
// 2000 tokens = $0.24). Encoded the flash-image way: one flat output rate
|
|
1483
|
-
// that reproduces the per-image price from the tokens usageMetadata
|
|
1484
|
-
// reports. Growth's hero generation runs this model today.
|
|
1485
|
-
"gemini-3-pro-image-preview": {
|
|
1486
|
-
cached: 20,
|
|
1487
|
-
input: 200,
|
|
1488
|
-
output: 12e3
|
|
1489
|
-
}
|
|
1490
|
-
};
|
|
1491
|
-
var toolCost = {
|
|
1492
|
-
search: 3.5
|
|
1493
|
-
};
|
|
1494
|
-
var toolPricing = Object.fromEntries(
|
|
1495
|
-
Object.entries(toolCost).map(([tool, value]) => [
|
|
1496
|
-
tool,
|
|
1497
|
-
Math.ceil(value * MARKUP)
|
|
1498
|
-
])
|
|
1499
|
-
);
|
|
1500
|
-
var pricing = Object.fromEntries(
|
|
1501
|
-
Object.entries(cost).map(([model, rates]) => [
|
|
1502
|
-
model,
|
|
1503
|
-
{
|
|
1504
|
-
cached: Math.round(rates.cached * MARKUP),
|
|
1505
|
-
input: Math.round(rates.input * MARKUP),
|
|
1506
|
-
output: Math.round(rates.output * MARKUP)
|
|
1507
|
-
}
|
|
1508
|
-
])
|
|
1509
|
-
);
|
|
1510
|
-
|
|
1511
1520
|
// lib/pricing.js
|
|
1512
1521
|
var emailPlans = {
|
|
1513
1522
|
essentials50k: {
|
|
@@ -1577,6 +1586,33 @@ var sending = {
|
|
|
1577
1586
|
plan: emailPlan,
|
|
1578
1587
|
plans: emailPlans,
|
|
1579
1588
|
title: emailPlans[emailPlan].title
|
|
1589
|
+
},
|
|
1590
|
+
// What one outbound SMS SEGMENT costs us, against what channels.sms below
|
|
1591
|
+
// sells it for. Here for the same reason the email figures are: this file is
|
|
1592
|
+
// where a rate is found without knowing whose module owns it — and its
|
|
1593
|
+
// absence is why "does an SMS action cover its cost" could not be answered
|
|
1594
|
+
// from this repo at all.
|
|
1595
|
+
//
|
|
1596
|
+
// US toll-free outbound, read from Twilio's SMS pricing page 2026-09-03
|
|
1597
|
+
// (https://www.twilio.com/en-us/sms/pricing/us): $0.0083 per segment, plus a
|
|
1598
|
+
// carrier fee that varies by the DESTINATION carrier — $0.0035 AT&T, $0.0045
|
|
1599
|
+
// T-Mobile / Verizon / US Cellular, $0.004 everywhere else. CENTS per
|
|
1600
|
+
// segment, like every other rate in this file.
|
|
1601
|
+
//
|
|
1602
|
+
// WORST CASE, deliberately. A price floor computed from the average is a
|
|
1603
|
+
// floor that half of real traffic sits underneath.
|
|
1604
|
+
//
|
|
1605
|
+
// A segment is 153 GSM-7 characters once a message spans more than one — or
|
|
1606
|
+
// 67 if the body contains a single emoji or curly quote, which flips the
|
|
1607
|
+
// whole message to UCS-2. Twilio reports the count as `num_segments` on the
|
|
1608
|
+
// Message resource, which is what the send bills on.
|
|
1609
|
+
sms: {
|
|
1610
|
+
baseCents: 0.83,
|
|
1611
|
+
// The dearest carrier fee, not the mean: see WORST CASE above.
|
|
1612
|
+
carrierCents: 0.45,
|
|
1613
|
+
// Stated, not summed — 0.83 + 0.45 is 1.2800000000000002 in binary
|
|
1614
|
+
// floating point, and this number is compared against money.
|
|
1615
|
+
segmentCostCents: 1.28
|
|
1580
1616
|
}
|
|
1581
1617
|
};
|
|
1582
1618
|
var channels = {
|
|
@@ -1592,6 +1628,9 @@ var channels = {
|
|
|
1592
1628
|
includedInAllowance: false
|
|
1593
1629
|
}
|
|
1594
1630
|
};
|
|
1631
|
+
var MINIMUM_ACTION_CENTS = Math.round(
|
|
1632
|
+
sending.sms.segmentCostCents / channels.sms.actionsPerSegment * MARKUP * 1e3
|
|
1633
|
+
) / 1e3;
|
|
1595
1634
|
|
|
1596
1635
|
// lib/connections/providers/drawbridge.js
|
|
1597
1636
|
var STOP_KEYWORDS = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"];
|
|
@@ -1616,13 +1655,16 @@ var teamRecipients = async ({ memberIds = [], organization: organization2, read
|
|
|
1616
1655
|
]
|
|
1617
1656
|
}) : [];
|
|
1618
1657
|
const seen = /* @__PURE__ */ new Set();
|
|
1619
|
-
return
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1658
|
+
return {
|
|
1659
|
+
org,
|
|
1660
|
+
recipients: [owner, ...members].filter((member) => {
|
|
1661
|
+
if (!(member == null ? void 0 : member.id) || !(member == null ? void 0 : member.email)) return false;
|
|
1662
|
+
const address = member.email.toLowerCase();
|
|
1663
|
+
if (seen.has(address)) return false;
|
|
1664
|
+
seen.add(address);
|
|
1665
|
+
return true;
|
|
1666
|
+
})
|
|
1667
|
+
};
|
|
1626
1668
|
};
|
|
1627
1669
|
var queueNotification = (data2) => ({ collection: "notification", data: data2, operation: "create" });
|
|
1628
1670
|
var drawbridge_default2 = {
|
|
@@ -1698,7 +1740,7 @@ var drawbridge_default2 = {
|
|
|
1698
1740
|
const count = Number((counted == null ? void 0 : counted.count) || 0);
|
|
1699
1741
|
const request2 = { campaign, count, days };
|
|
1700
1742
|
if (!count) return { message: "No new leads in the period \u2014 digest skipped.", request: request2, response: { skipped: true }, skipped: true };
|
|
1701
|
-
const recipients = await teamRecipients({
|
|
1743
|
+
const { recipients } = await teamRecipients({
|
|
1702
1744
|
memberIds: ((_d = step.settings) == null ? void 0 : _d.members) || [],
|
|
1703
1745
|
organization: workflow.organization,
|
|
1704
1746
|
read
|
|
@@ -1734,7 +1776,7 @@ var drawbridge_default2 = {
|
|
|
1734
1776
|
var _a;
|
|
1735
1777
|
const memberIds = ((_a = step.settings) == null ? void 0 : _a.members) || [];
|
|
1736
1778
|
const request2 = { members: memberIds.length };
|
|
1737
|
-
const recipients = await teamRecipients({ memberIds, organization: workflow.organization, read });
|
|
1779
|
+
const { org, recipients } = await teamRecipients({ memberIds, organization: workflow.organization, read });
|
|
1738
1780
|
if (!recipients.length) {
|
|
1739
1781
|
return {
|
|
1740
1782
|
message: "No owner or accepted member with an email address \u2014 team notification skipped.",
|
|
@@ -1744,32 +1786,37 @@ var drawbridge_default2 = {
|
|
|
1744
1786
|
};
|
|
1745
1787
|
}
|
|
1746
1788
|
const bucket = Math.floor(Date.now() / (15 * 60 * 1e3));
|
|
1789
|
+
const campaign = (context == null ? void 0 : context.campaign) ? await read.get({ collection: "campaign", query: { id: context.campaign } }) : null;
|
|
1790
|
+
const link = (context == null ? void 0 : context.campaign) && (context == null ? void 0 : context.lead) ? {
|
|
1791
|
+
href: "/organizations/" + workflow.organization + "/campaigns/" + context.campaign + "/leads/" + context.lead,
|
|
1792
|
+
text: "View lead"
|
|
1793
|
+
} : null;
|
|
1794
|
+
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 : "") + ".";
|
|
1795
|
+
const subject = "New lead" + ((campaign == null ? void 0 : campaign.title) ? " \u2014 " + campaign.title : "");
|
|
1747
1796
|
return {
|
|
1748
1797
|
message: "Team notification queued for " + recipients.length + " recipient(s).",
|
|
1749
1798
|
request: request2,
|
|
1750
1799
|
response: { notified: recipients.length },
|
|
1751
|
-
writes: recipients.map((member) => {
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
};
|
|
1772
|
-
})
|
|
1800
|
+
writes: recipients.map((member) => ({
|
|
1801
|
+
...queueNotification({
|
|
1802
|
+
audience: "member",
|
|
1803
|
+
// Per workflow, recipient AND bucket, so one recipient's damper
|
|
1804
|
+
// can never swallow another's mail and a later bucket is never
|
|
1805
|
+
// mistaken for a duplicate of an earlier one.
|
|
1806
|
+
key: "team.notify." + workflow.id + "." + member.id + "." + bucket,
|
|
1807
|
+
link,
|
|
1808
|
+
message: body,
|
|
1809
|
+
organization: workflow.organization,
|
|
1810
|
+
send: { type: "email", email: member.email },
|
|
1811
|
+
title: subject,
|
|
1812
|
+
workflow: workflow.id
|
|
1813
|
+
}),
|
|
1814
|
+
// E11000 IS THE DAMPER WORKING: this recipient has already been
|
|
1815
|
+
// told within the bucket. Declared per write rather than assumed by
|
|
1816
|
+
// the shell, because on every other write here a duplicate key is a
|
|
1817
|
+
// real failure.
|
|
1818
|
+
ignoreDuplicate: true
|
|
1819
|
+
}))
|
|
1773
1820
|
};
|
|
1774
1821
|
},
|
|
1775
1822
|
// Drawbridge sends lead-facing email itself — no merchant provider gates
|
|
@@ -2175,14 +2222,24 @@ var drawbridge_default2 = {
|
|
|
2175
2222
|
}),
|
|
2176
2223
|
// To organization MEMBERS. Never suppressed — an entrant's opt-out must
|
|
2177
2224
|
// not silence an alert to staff — and not billed.
|
|
2225
|
+
//
|
|
2226
|
+
// ITS COPY IS WRITTEN, NOT ASKED FOR. This says the same thing every
|
|
2227
|
+
// time — a lead arrived, here it is — so the merchant chooses who hears
|
|
2228
|
+
// it, not how it reads, and the hook composes the subject, the body and
|
|
2229
|
+
// the link to the lead.
|
|
2230
|
+
//
|
|
2231
|
+
// `message` and `subject` stay declared, optional and IGNORED: stored
|
|
2232
|
+
// workflows still carry them and the api validates settings with
|
|
2233
|
+
// noUnknown().strict(), so dropping the keys would 400 the next save of
|
|
2234
|
+
// a workflow built before this.
|
|
2178
2235
|
notify: () => ({
|
|
2179
2236
|
hook: "email.notify",
|
|
2180
2237
|
key: "Email \u2014 Notification",
|
|
2181
2238
|
queue: "notification",
|
|
2182
2239
|
settings: {
|
|
2183
2240
|
members: { of: "string", type: "array" },
|
|
2184
|
-
message: {
|
|
2185
|
-
subject: {
|
|
2241
|
+
message: { type: "string" },
|
|
2242
|
+
subject: { type: "string" }
|
|
2186
2243
|
},
|
|
2187
2244
|
triggers: ["lead.insert"],
|
|
2188
2245
|
// Zero is a PRICE, and a deliberate one. Declared rather than omitted
|
|
@@ -2597,8 +2654,62 @@ var klaviyo_default2 = {
|
|
|
2597
2654
|
sms: false,
|
|
2598
2655
|
inbound: false,
|
|
2599
2656
|
// Nothing to set up or tear down at the vendor: the grant is the whole
|
|
2600
|
-
// integration
|
|
2601
|
-
lifecycle
|
|
2657
|
+
// integration. What CAN rot is the grant itself, so health is the one
|
|
2658
|
+
// lifecycle hook Klaviyo carries.
|
|
2659
|
+
lifecycle: {
|
|
2660
|
+
// Nothing to set up, tear down, or re-register at the vendor — the
|
|
2661
|
+
// grant is the whole integration.
|
|
2662
|
+
cleanup: false,
|
|
2663
|
+
register: false,
|
|
2664
|
+
rehydrate: false,
|
|
2665
|
+
// KEEP THE GRANT PROVEN AND WARM. The shell mints a fresh access
|
|
2666
|
+
// token before every hook run — for Klaviyo that spends the refresh
|
|
2667
|
+
// token, which is the only question that reaches Klaviyo (see
|
|
2668
|
+
// auth.probe) — so this body running at all proves the grant still
|
|
2669
|
+
// rotates, and the daily run keeps it inside Klaviyo's 90-day idle
|
|
2670
|
+
// window. A dead grant never gets here: the shell's mint throws
|
|
2671
|
+
// `grant_refused` and the step runner errors the connection itself.
|
|
2672
|
+
//
|
|
2673
|
+
// The one call below proves the minted token is HONOURED — mint and
|
|
2674
|
+
// acceptance are different facts, and /accounts is already the call
|
|
2675
|
+
// the connect flow makes (auth.connect), so it needs no new scope.
|
|
2676
|
+
health: async ({ connection: connection2, token }, { fetcher, read } = {}) => {
|
|
2677
|
+
const request2 = { connectionId: connection2.id };
|
|
2678
|
+
try {
|
|
2679
|
+
await api2("/accounts", { fetcher, token });
|
|
2680
|
+
return {
|
|
2681
|
+
message: "Health check passed \u2014 token minted and accepted.",
|
|
2682
|
+
request: request2,
|
|
2683
|
+
response: { pingedAt: /* @__PURE__ */ new Date() }
|
|
2684
|
+
};
|
|
2685
|
+
} catch (error) {
|
|
2686
|
+
if ([401, 403].includes(error.status) && read) {
|
|
2687
|
+
const current = await read.get({ collection: "connection", query: { id: connection2.id } });
|
|
2688
|
+
if (current) {
|
|
2689
|
+
const others = (current.errors || []).filter((entry) => entry.source !== "oauth");
|
|
2690
|
+
error.writes = [{
|
|
2691
|
+
collection: "connection",
|
|
2692
|
+
data: {
|
|
2693
|
+
$set: {
|
|
2694
|
+
errors: [
|
|
2695
|
+
...others,
|
|
2696
|
+
{
|
|
2697
|
+
message: "Klaviyo no longer accepts this connection. Reconnect Klaviyo to resume syncing.",
|
|
2698
|
+
source: "oauth"
|
|
2699
|
+
}
|
|
2700
|
+
],
|
|
2701
|
+
status: "error"
|
|
2702
|
+
}
|
|
2703
|
+
},
|
|
2704
|
+
operation: "update",
|
|
2705
|
+
query: { id: connection2.id }
|
|
2706
|
+
}];
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
throw error;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
},
|
|
2602
2713
|
resources: {
|
|
2603
2714
|
// The lists a merchant can sync into, for the picker on their
|
|
2604
2715
|
// connection.
|
|
@@ -2674,6 +2785,19 @@ var klaviyo_default2 = {
|
|
|
2674
2785
|
return ((_a = data2 == null ? void 0 : data2.settings) == null ? void 0 : _a.list) ? data2.status : "pending";
|
|
2675
2786
|
},
|
|
2676
2787
|
steps: {
|
|
2788
|
+
connection: {
|
|
2789
|
+
// The daily grant check — same step type and workflow shape as
|
|
2790
|
+
// Shopify's, provisioned as a system workflow per connection.
|
|
2791
|
+
health: {
|
|
2792
|
+
check: () => ({
|
|
2793
|
+
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.",
|
|
2794
|
+
hook: "lifecycle.health",
|
|
2795
|
+
key: "Klaviyo Connection Health",
|
|
2796
|
+
queue: "connection",
|
|
2797
|
+
system: true
|
|
2798
|
+
})
|
|
2799
|
+
}
|
|
2800
|
+
},
|
|
2677
2801
|
contacts: {
|
|
2678
2802
|
// A DECLARATION, not the work. It names the hook that does the work, and
|
|
2679
2803
|
// says where that hook's values belong. Nested like the hooks, and the
|
package/dist/twilio.cjs
CHANGED
|
@@ -174,6 +174,57 @@ var twilio = {
|
|
|
174
174
|
sid: result == null ? void 0 : result.sid
|
|
175
175
|
};
|
|
176
176
|
},
|
|
177
|
+
// THE NUMBER AS TWILIO HOLDS IT NOW. Same IncomingPhoneNumber resource the
|
|
178
|
+
// purchase and release above use (api.twilio.com 2010-04-01, GET
|
|
179
|
+
// /IncomingPhoneNumbers/{sid}.json) — a 404 means the number is no longer
|
|
180
|
+
// on the account, which the health sweep must distinguish from a transient,
|
|
181
|
+
// so it is answered as { gone : true } rather than thrown.
|
|
182
|
+
numberConfig: async ({ accountSid, authToken, request: request2 = request, sid }) => {
|
|
183
|
+
if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
|
|
184
|
+
if (!sid) throw new Error("Twilio config read needs the IncomingPhoneNumber sid");
|
|
185
|
+
let result;
|
|
186
|
+
try {
|
|
187
|
+
result = await request2({
|
|
188
|
+
method: "GET",
|
|
189
|
+
url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/IncomingPhoneNumbers/" + sid + ".json",
|
|
190
|
+
headers: {
|
|
191
|
+
"Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if ((error == null ? void 0 : error.status) === 404) return { gone: true };
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
gone: false,
|
|
200
|
+
number: result == null ? void 0 : result.phone_number,
|
|
201
|
+
smsMethod: result == null ? void 0 : result.sms_method,
|
|
202
|
+
smsUrl: result == null ? void 0 : result.sms_url,
|
|
203
|
+
status: result == null ? void 0 : result.status
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
// RE-POINT AN OWNED NUMBER'S INBOUND WEBHOOK — POST on the same resource,
|
|
207
|
+
// the exact fields purchase sets. The health sweep's repair arm.
|
|
208
|
+
updateNumber: async ({ accountSid, authToken, request: request2 = request, sid, smsUrl }) => {
|
|
209
|
+
if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
|
|
210
|
+
if (!sid) throw new Error("Twilio update needs the IncomingPhoneNumber sid");
|
|
211
|
+
if (!smsUrl) throw new Error("Twilio update needs the smsUrl to point the number at");
|
|
212
|
+
const result = await request2({
|
|
213
|
+
method: "POST",
|
|
214
|
+
type: "form",
|
|
215
|
+
url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/IncomingPhoneNumbers/" + sid + ".json",
|
|
216
|
+
headers: {
|
|
217
|
+
"Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
|
|
218
|
+
},
|
|
219
|
+
body: {
|
|
220
|
+
SmsUrl: smsUrl,
|
|
221
|
+
SmsMethod: "POST"
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
smsUrl: result == null ? void 0 : result.sms_url
|
|
226
|
+
};
|
|
227
|
+
},
|
|
177
228
|
releaseNumber: async ({ accountSid, authToken, request: request2 = request, sid }) => {
|
|
178
229
|
if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
|
|
179
230
|
if (!sid) throw new Error("Twilio release needs the IncomingPhoneNumber sid");
|
package/dist/twilio.d.cts
CHANGED
|
@@ -159,6 +159,73 @@ const twilio = {
|
|
|
159
159
|
|
|
160
160
|
},
|
|
161
161
|
|
|
162
|
+
// THE NUMBER AS TWILIO HOLDS IT NOW. Same IncomingPhoneNumber resource the
|
|
163
|
+
// purchase and release above use (api.twilio.com 2010-04-01, GET
|
|
164
|
+
// /IncomingPhoneNumbers/{sid}.json) — a 404 means the number is no longer
|
|
165
|
+
// on the account, which the health sweep must distinguish from a transient,
|
|
166
|
+
// so it is answered as { gone : true } rather than thrown.
|
|
167
|
+
numberConfig : async ({ accountSid, authToken, request: request$1 = request, sid }) => {
|
|
168
|
+
|
|
169
|
+
if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
|
|
170
|
+
if( ! sid ) throw new Error( 'Twilio config read needs the IncomingPhoneNumber sid' );
|
|
171
|
+
|
|
172
|
+
let result;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
|
|
176
|
+
result = await request$1({
|
|
177
|
+
method : 'GET',
|
|
178
|
+
url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/IncomingPhoneNumbers/' + sid + '.json',
|
|
179
|
+
headers : {
|
|
180
|
+
'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
} catch ( error ) {
|
|
185
|
+
|
|
186
|
+
if( error?.status === 404 ) return { gone : true };
|
|
187
|
+
|
|
188
|
+
throw error;
|
|
189
|
+
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
gone : false,
|
|
194
|
+
number : result?.phone_number,
|
|
195
|
+
smsMethod : result?.sms_method,
|
|
196
|
+
smsUrl : result?.sms_url,
|
|
197
|
+
status : result?.status
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
// RE-POINT AN OWNED NUMBER'S INBOUND WEBHOOK — POST on the same resource,
|
|
203
|
+
// the exact fields purchase sets. The health sweep's repair arm.
|
|
204
|
+
updateNumber : async ({ accountSid, authToken, request: request$1 = request, sid, smsUrl }) => {
|
|
205
|
+
|
|
206
|
+
if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
|
|
207
|
+
if( ! sid ) throw new Error( 'Twilio update needs the IncomingPhoneNumber sid' );
|
|
208
|
+
if( ! smsUrl ) throw new Error( 'Twilio update needs the smsUrl to point the number at' );
|
|
209
|
+
|
|
210
|
+
const result = await request$1({
|
|
211
|
+
method : 'POST',
|
|
212
|
+
type : 'form',
|
|
213
|
+
url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/IncomingPhoneNumbers/' + sid + '.json',
|
|
214
|
+
headers : {
|
|
215
|
+
'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
|
|
216
|
+
},
|
|
217
|
+
body : {
|
|
218
|
+
SmsUrl : smsUrl,
|
|
219
|
+
SmsMethod : 'POST'
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
smsUrl : result?.sms_url
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
},
|
|
228
|
+
|
|
162
229
|
releaseNumber : async ({ accountSid, authToken, request: request$1 = request, sid }) => {
|
|
163
230
|
|
|
164
231
|
if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
|
package/dist/twilio.d.ts
CHANGED
|
@@ -159,6 +159,73 @@ const twilio = {
|
|
|
159
159
|
|
|
160
160
|
},
|
|
161
161
|
|
|
162
|
+
// THE NUMBER AS TWILIO HOLDS IT NOW. Same IncomingPhoneNumber resource the
|
|
163
|
+
// purchase and release above use (api.twilio.com 2010-04-01, GET
|
|
164
|
+
// /IncomingPhoneNumbers/{sid}.json) — a 404 means the number is no longer
|
|
165
|
+
// on the account, which the health sweep must distinguish from a transient,
|
|
166
|
+
// so it is answered as { gone : true } rather than thrown.
|
|
167
|
+
numberConfig : async ({ accountSid, authToken, request: request$1 = request, sid }) => {
|
|
168
|
+
|
|
169
|
+
if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
|
|
170
|
+
if( ! sid ) throw new Error( 'Twilio config read needs the IncomingPhoneNumber sid' );
|
|
171
|
+
|
|
172
|
+
let result;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
|
|
176
|
+
result = await request$1({
|
|
177
|
+
method : 'GET',
|
|
178
|
+
url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/IncomingPhoneNumbers/' + sid + '.json',
|
|
179
|
+
headers : {
|
|
180
|
+
'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
} catch ( error ) {
|
|
185
|
+
|
|
186
|
+
if( error?.status === 404 ) return { gone : true };
|
|
187
|
+
|
|
188
|
+
throw error;
|
|
189
|
+
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
gone : false,
|
|
194
|
+
number : result?.phone_number,
|
|
195
|
+
smsMethod : result?.sms_method,
|
|
196
|
+
smsUrl : result?.sms_url,
|
|
197
|
+
status : result?.status
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
// RE-POINT AN OWNED NUMBER'S INBOUND WEBHOOK — POST on the same resource,
|
|
203
|
+
// the exact fields purchase sets. The health sweep's repair arm.
|
|
204
|
+
updateNumber : async ({ accountSid, authToken, request: request$1 = request, sid, smsUrl }) => {
|
|
205
|
+
|
|
206
|
+
if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
|
|
207
|
+
if( ! sid ) throw new Error( 'Twilio update needs the IncomingPhoneNumber sid' );
|
|
208
|
+
if( ! smsUrl ) throw new Error( 'Twilio update needs the smsUrl to point the number at' );
|
|
209
|
+
|
|
210
|
+
const result = await request$1({
|
|
211
|
+
method : 'POST',
|
|
212
|
+
type : 'form',
|
|
213
|
+
url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/IncomingPhoneNumbers/' + sid + '.json',
|
|
214
|
+
headers : {
|
|
215
|
+
'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
|
|
216
|
+
},
|
|
217
|
+
body : {
|
|
218
|
+
SmsUrl : smsUrl,
|
|
219
|
+
SmsMethod : 'POST'
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
smsUrl : result?.sms_url
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
},
|
|
228
|
+
|
|
162
229
|
releaseNumber : async ({ accountSid, authToken, request: request$1 = request, sid }) => {
|
|
163
230
|
|
|
164
231
|
if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
|