@drawbridge/drawbridge-utils 0.0.157 → 0.0.159
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 +248 -37
- package/dist/connections/index.d.cts +435 -76
- package/dist/connections/index.d.ts +435 -76
- package/dist/connections/index.js +247 -36
- package/dist/providers.cjs +248 -37
- package/dist/providers.js +247 -36
- package/dist/sendgrid.cjs +18 -1
- package/dist/sendgrid.d.cts +24 -1
- package/dist/sendgrid.d.ts +24 -1
- package/dist/sendgrid.js +18 -1
- package/package.json +1 -1
package/dist/providers.cjs
CHANGED
|
@@ -837,7 +837,53 @@ var attentive_default2 = {
|
|
|
837
837
|
};
|
|
838
838
|
|
|
839
839
|
// lib/connections/providers/drawbridge.js
|
|
840
|
+
var import_node_crypto3 = require("crypto");
|
|
841
|
+
|
|
842
|
+
// lib/connections/inbound.js
|
|
840
843
|
var import_node_crypto2 = require("crypto");
|
|
844
|
+
var verifySignature = ({ body, descriptor, headers, secret }) => {
|
|
845
|
+
if (!secret) {
|
|
846
|
+
throw Object.assign(new Error("Missing webhook secret: " + descriptor.signature.secret), { status: 500 });
|
|
847
|
+
}
|
|
848
|
+
const provided = headers[descriptor.headers.signature];
|
|
849
|
+
if (!provided) {
|
|
850
|
+
throw Object.assign(new Error("Missing webhook signature"), { status: 401 });
|
|
851
|
+
}
|
|
852
|
+
const digest = (0, import_node_crypto2.createHmac)(descriptor.signature.algorithm, secret).update(body).digest(descriptor.signature.encoding);
|
|
853
|
+
const digestBuffer = Buffer.from(digest, descriptor.signature.encoding);
|
|
854
|
+
const providedBuffer = Buffer.from(provided, descriptor.signature.encoding);
|
|
855
|
+
if (digestBuffer.length !== providedBuffer.length || !(0, import_node_crypto2.timingSafeEqual)(digestBuffer, providedBuffer)) {
|
|
856
|
+
throw Object.assign(new Error("Invalid webhook signature"), { status: 401 });
|
|
857
|
+
}
|
|
858
|
+
return JSON.parse(body.toString());
|
|
859
|
+
};
|
|
860
|
+
var REPLAY_TOLERANCE_SECONDS = 5 * 60;
|
|
861
|
+
var asPem = (key) => [
|
|
862
|
+
"-----BEGIN PUBLIC KEY-----",
|
|
863
|
+
...key.match(/.{1,64}/g) || [],
|
|
864
|
+
"-----END PUBLIC KEY-----"
|
|
865
|
+
].join("\n");
|
|
866
|
+
var verifyEcdsa = ({ body, descriptor, headers, secret }) => {
|
|
867
|
+
if (!secret) {
|
|
868
|
+
throw Object.assign(new Error("Missing webhook key: " + descriptor.signature.secret), { status: 500 });
|
|
869
|
+
}
|
|
870
|
+
const provided = headers[descriptor.headers.signature];
|
|
871
|
+
const timestamp = headers[descriptor.headers.timestamp];
|
|
872
|
+
if (!provided || !timestamp) {
|
|
873
|
+
throw Object.assign(new Error("Missing webhook signature"), { status: 401 });
|
|
874
|
+
}
|
|
875
|
+
const age = Math.abs(Math.floor(Date.now() / 1e3) - Number(timestamp));
|
|
876
|
+
if (!(age <= REPLAY_TOLERANCE_SECONDS)) {
|
|
877
|
+
throw Object.assign(new Error("Stale webhook signature"), { status: 401 });
|
|
878
|
+
}
|
|
879
|
+
const payload = Buffer.concat([Buffer.from(String(timestamp), "utf8"), body]);
|
|
880
|
+
const verified = (0, import_node_crypto2.createVerify)("sha256").update(payload).verify(asPem(secret), Buffer.from(provided, "base64"));
|
|
881
|
+
if (!verified) {
|
|
882
|
+
throw Object.assign(new Error("Invalid webhook signature"), { status: 401 });
|
|
883
|
+
}
|
|
884
|
+
return JSON.parse(body.toString());
|
|
885
|
+
};
|
|
886
|
+
var readEventHeader = ({ descriptor, headers }) => headers[descriptor.headers.event];
|
|
841
887
|
|
|
842
888
|
// lib/http.js
|
|
843
889
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
@@ -1728,6 +1774,144 @@ var MINIMUM_ACTION_CENTS = Math.round(
|
|
|
1728
1774
|
// lib/connections/providers/drawbridge.js
|
|
1729
1775
|
var STOP_KEYWORDS = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"];
|
|
1730
1776
|
var START_KEYWORDS = ["START", "UNSTOP", "YES"];
|
|
1777
|
+
var sendgridInbound = {
|
|
1778
|
+
headers: {
|
|
1779
|
+
signature: "x-twilio-email-event-webhook-signature",
|
|
1780
|
+
timestamp: "x-twilio-email-event-webhook-timestamp"
|
|
1781
|
+
},
|
|
1782
|
+
signature: {
|
|
1783
|
+
secret: "SENDGRID_EVENT_WEBHOOK_KEY"
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
var REFUSALS = /* @__PURE__ */ new Set(["blocked", "bounce", "deferred", "dropped", "spamreport"]);
|
|
1787
|
+
var DELIVERY = {
|
|
1788
|
+
queued: 10,
|
|
1789
|
+
sent: 20,
|
|
1790
|
+
deferred: 30,
|
|
1791
|
+
blocked: 40,
|
|
1792
|
+
delivered: 50,
|
|
1793
|
+
bounced: 60,
|
|
1794
|
+
complained: 70
|
|
1795
|
+
};
|
|
1796
|
+
var fromSendgrid = (event) => {
|
|
1797
|
+
if ((event == null ? void 0 : event.event) === "bounce") return event.type === "blocked" ? "blocked" : "bounced";
|
|
1798
|
+
return {
|
|
1799
|
+
blocked: "blocked",
|
|
1800
|
+
deferred: "deferred",
|
|
1801
|
+
delivered: "delivered",
|
|
1802
|
+
dropped: "bounced",
|
|
1803
|
+
processed: "queued",
|
|
1804
|
+
spamreport: "complained"
|
|
1805
|
+
}[event == null ? void 0 : event.event] || null;
|
|
1806
|
+
};
|
|
1807
|
+
var fromTwilio = (status) => ({
|
|
1808
|
+
delivered: "delivered",
|
|
1809
|
+
failed: "bounced",
|
|
1810
|
+
queued: "queued",
|
|
1811
|
+
sending: "sent",
|
|
1812
|
+
sent: "sent",
|
|
1813
|
+
undelivered: "bounced"
|
|
1814
|
+
})[String(status || "").toLowerCase()] || null;
|
|
1815
|
+
var deliveryWrite = ({ at, code: code2, notification, permanent, provider, reason, status }) => {
|
|
1816
|
+
const rank = DELIVERY[status];
|
|
1817
|
+
if (!notification || !rank) return null;
|
|
1818
|
+
return {
|
|
1819
|
+
collection: "notification",
|
|
1820
|
+
data: {
|
|
1821
|
+
$set: {
|
|
1822
|
+
"meta.delivery": {
|
|
1823
|
+
at: at || /* @__PURE__ */ new Date(),
|
|
1824
|
+
...code2 !== void 0 && code2 !== null && { code: String(code2) },
|
|
1825
|
+
...permanent !== void 0 && { permanent },
|
|
1826
|
+
provider,
|
|
1827
|
+
rank,
|
|
1828
|
+
...reason && { reason: String(reason) },
|
|
1829
|
+
status
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
},
|
|
1833
|
+
operation: "update",
|
|
1834
|
+
query: {
|
|
1835
|
+
id: notification,
|
|
1836
|
+
// NEVER an upsert. A callback naming a notification we do not have is a
|
|
1837
|
+
// vendor talking about someone else's message, not a document to create.
|
|
1838
|
+
$or: [
|
|
1839
|
+
{ "meta.delivery.rank": { $exists: false } },
|
|
1840
|
+
{ "meta.delivery.rank": { $lt: rank } }
|
|
1841
|
+
]
|
|
1842
|
+
}
|
|
1843
|
+
};
|
|
1844
|
+
};
|
|
1845
|
+
var summariseEvents = (events) => {
|
|
1846
|
+
const list = Array.isArray(events) ? events : [];
|
|
1847
|
+
if (!list.length) return { message: "SendGrid delivered an empty event batch.", skipped: true };
|
|
1848
|
+
const counts = {};
|
|
1849
|
+
for (const { event } of list) {
|
|
1850
|
+
const name = event || "unknown";
|
|
1851
|
+
counts[name] = (counts[name] || 0) + 1;
|
|
1852
|
+
}
|
|
1853
|
+
const tally = Object.entries(counts).map(([name, count]) => count + " " + name).join(", ");
|
|
1854
|
+
const refused = list.filter(({ event }) => REFUSALS.has(event)).slice(0, 5).map(({ email, reason, response, status }) => email + " \u2014 " + (reason || response || status || "no reason given"));
|
|
1855
|
+
const writes = list.map((event) => {
|
|
1856
|
+
var _a;
|
|
1857
|
+
return deliveryWrite({
|
|
1858
|
+
at: event.timestamp ? new Date(event.timestamp * 1e3) : void 0,
|
|
1859
|
+
code: event.status,
|
|
1860
|
+
// Read BOTH shapes. SendGrid echoes custom_args as siblings of `email`
|
|
1861
|
+
// and `event`, which is why it warns about colliding with reserved
|
|
1862
|
+
// names — but this is the one thing in the design that cannot be proved
|
|
1863
|
+
// until a real event arrives, and reading the nested form too costs a
|
|
1864
|
+
// token and removes the only silent failure left here.
|
|
1865
|
+
notification: event.notification || ((_a = event.custom_args) == null ? void 0 : _a.notification),
|
|
1866
|
+
// Only a bounce carries the distinction, and only a real one is
|
|
1867
|
+
// permanent — `blocked` is this attempt refused, not this address dead.
|
|
1868
|
+
...event.event === "bounce" && { permanent: event.type !== "blocked" },
|
|
1869
|
+
provider: { slug: "sendgrid", id: event.sg_message_id || null },
|
|
1870
|
+
reason: event.reason || event.response,
|
|
1871
|
+
status: fromSendgrid(event)
|
|
1872
|
+
});
|
|
1873
|
+
}).filter(Boolean);
|
|
1874
|
+
return {
|
|
1875
|
+
message: [list.length + " SendGrid event(s): " + tally, ...refused].join(" | "),
|
|
1876
|
+
...writes.length ? { writes } : { skipped: true }
|
|
1877
|
+
};
|
|
1878
|
+
};
|
|
1879
|
+
var statusCallback = (data2) => {
|
|
1880
|
+
const status = fromTwilio((data2 == null ? void 0 : data2.MessageStatus) || (data2 == null ? void 0 : data2.SmsStatus));
|
|
1881
|
+
if (!status) return { message: "Twilio sent a status this does not map: " + ((data2 == null ? void 0 : data2.MessageStatus) || "none"), skipped: true };
|
|
1882
|
+
const rank = DELIVERY[status];
|
|
1883
|
+
const write = {
|
|
1884
|
+
collection: "notification",
|
|
1885
|
+
data: {
|
|
1886
|
+
$set: {
|
|
1887
|
+
"meta.delivery": {
|
|
1888
|
+
at: /* @__PURE__ */ new Date(),
|
|
1889
|
+
...(data2 == null ? void 0 : data2.ErrorCode) && { code: String(data2.ErrorCode) },
|
|
1890
|
+
// `failed` never left Twilio and `undelivered` was refused by the
|
|
1891
|
+
// carrier. Both are dead ends for this handset, so neither is
|
|
1892
|
+
// reported as a temporary condition.
|
|
1893
|
+
...status === "bounced" && { permanent: true },
|
|
1894
|
+
provider: { slug: "twilio", id: (data2 == null ? void 0 : data2.MessageSid) || null },
|
|
1895
|
+
rank,
|
|
1896
|
+
...(data2 == null ? void 0 : data2.ErrorMessage) && { reason: String(data2.ErrorMessage) },
|
|
1897
|
+
status
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
},
|
|
1901
|
+
operation: "update",
|
|
1902
|
+
query: {
|
|
1903
|
+
"meta.delivery.provider.id": data2 == null ? void 0 : data2.MessageSid,
|
|
1904
|
+
$or: [
|
|
1905
|
+
{ "meta.delivery.rank": { $exists: false } },
|
|
1906
|
+
{ "meta.delivery.rank": { $lt: rank } }
|
|
1907
|
+
]
|
|
1908
|
+
}
|
|
1909
|
+
};
|
|
1910
|
+
return {
|
|
1911
|
+
message: "Twilio " + status + " for " + ((data2 == null ? void 0 : data2.MessageSid) || "an unnamed message") + ((data2 == null ? void 0 : data2.ErrorCode) ? " (" + data2.ErrorCode + ")" : ""),
|
|
1912
|
+
...(data2 == null ? void 0 : data2.MessageSid) ? { writes: [write] } : { skipped: true }
|
|
1913
|
+
};
|
|
1914
|
+
};
|
|
1731
1915
|
var interpolate = (template, data2) => {
|
|
1732
1916
|
if (!template) return template;
|
|
1733
1917
|
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => (data2 == null ? void 0 : data2[key]) != null ? String(data2[key]) : "{{" + key + "}}");
|
|
@@ -1971,18 +2155,27 @@ var drawbridge_default2 = {
|
|
|
1971
2155
|
// other vendor, and the bespoke webhooks route + the handler that lived in
|
|
1972
2156
|
// sync's buffer table are gone.
|
|
1973
2157
|
inbound: {
|
|
1974
|
-
//
|
|
1975
|
-
//
|
|
1976
|
-
|
|
2158
|
+
// THE REGISTERED URL IS THE TOPIC. Neither vendor sends a topic header,
|
|
2159
|
+
// so the channel it arrived on names the event — `sms` is one inbound
|
|
2160
|
+
// message, `email` is a batch of SendGrid delivery events.
|
|
2161
|
+
//
|
|
2162
|
+
// Defaulted because the drain calls `event()` with nothing when it only
|
|
2163
|
+
// wants the SMS name.
|
|
2164
|
+
event: ({ channel } = {}) => ({
|
|
2165
|
+
email: "email.events",
|
|
2166
|
+
"sms-status": "sms.status"
|
|
2167
|
+
})[channel] || "message.inbound",
|
|
1977
2168
|
// STOP AND START, AS DESCRIBED WRITES. A withdrawn number is withdrawn
|
|
1978
2169
|
// for everyone — organization : null is the platform floor canSend()
|
|
1979
2170
|
// reads on every send path. $setOnInsert + upsert so webhook replays
|
|
1980
2171
|
// and repeat STOPs collapse onto the unique { channel, address,
|
|
1981
2172
|
// organization } index instead of erroring.
|
|
1982
2173
|
process: ({ context }) => {
|
|
1983
|
-
var _a, _b;
|
|
1984
|
-
|
|
1985
|
-
|
|
2174
|
+
var _a, _b, _c;
|
|
2175
|
+
if ((context == null ? void 0 : context.topic) === "email.events") return summariseEvents((_a = context == null ? void 0 : context.data) == null ? void 0 : _a.events);
|
|
2176
|
+
if ((context == null ? void 0 : context.topic) === "sms.status") return statusCallback(context == null ? void 0 : context.data);
|
|
2177
|
+
const keyword = String(((_b = context == null ? void 0 : context.data) == null ? void 0 : _b.Body) || "").trim().toUpperCase();
|
|
2178
|
+
const address = toE164((_c = context == null ? void 0 : context.data) == null ? void 0 : _c.From);
|
|
1986
2179
|
if (!address) return { message: "Inbound SMS carried no usable sender number.", skipped: true };
|
|
1987
2180
|
if (STOP_KEYWORDS.includes(keyword)) {
|
|
1988
2181
|
return {
|
|
@@ -2026,9 +2219,24 @@ var drawbridge_default2 = {
|
|
|
2026
2219
|
}
|
|
2027
2220
|
return { message: "A real reply, not a keyword \u2014 nothing to record.", skipped: true };
|
|
2028
2221
|
},
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2222
|
+
// A SendGrid batch carries many sg_message_ids and no single identity, so
|
|
2223
|
+
// it names no provider id. That means no dedupe key for a redelivery —
|
|
2224
|
+
// acceptable because SendGrid only retries a non-2xx, and the cost of a
|
|
2225
|
+
// duplicate here is a second buffer row rather than a repeated side
|
|
2226
|
+
// effect. Revisit if this ever writes suppressions.
|
|
2227
|
+
//
|
|
2228
|
+
// THE BATCH IS WRAPPED, and it has to be. SendGrid posts a JSON ARRAY
|
|
2229
|
+
// where every other vendor posts an object, and drawbridge-api's
|
|
2230
|
+
// schema/buffer.js declares `data : { bsonType : 'object' }` — which in
|
|
2231
|
+
// JSON Schema does NOT match an array. Writing the array raw fails
|
|
2232
|
+
// validation, answers 500, and puts SendGrid into a retry loop for a
|
|
2233
|
+
// webhook we would never accept.
|
|
2234
|
+
//
|
|
2235
|
+
// Wrapped here rather than widening the schema: `data` stays one shape
|
|
2236
|
+
// for every consumer, and the vendor that is odd carries the oddity.
|
|
2237
|
+
receive: ({ channel, payload }) => ({
|
|
2238
|
+
data: channel === "email" ? { events: payload } : payload,
|
|
2239
|
+
provider: { id: channel === "email" ? null : (payload == null ? void 0 : payload.MessageSid) || null }
|
|
2032
2240
|
}),
|
|
2033
2241
|
// TWILIO'S SCHEME, from docs.twilio.com/usage/security: take the full
|
|
2034
2242
|
// registered url, sort the POST parameters alphabetically (Unix-style,
|
|
@@ -2037,13 +2245,14 @@ var drawbridge_default2 = {
|
|
|
2037
2245
|
// X-Twilio-Signature. It signs the URL rather than the raw body, which
|
|
2038
2246
|
// is exactly why the shared body-HMAC verifier cannot cover it and this
|
|
2039
2247
|
// hook exists.
|
|
2040
|
-
verify: ({ body, headers, secret, url }) => {
|
|
2248
|
+
verify: ({ body, channel, headers, secret, url }) => {
|
|
2249
|
+
if (channel === "email") return verifyEcdsa({ body, descriptor: sendgridInbound, headers, secret });
|
|
2041
2250
|
if (!secret) throw Object.assign(new Error("Missing webhook secret: TWILIO_AUTH_TOKEN"), { status: 500 });
|
|
2042
2251
|
const params = new URLSearchParams(String(body || ""));
|
|
2043
2252
|
const signed = url + [...params.keys()].sort().map((key) => key + params.get(key)).join("");
|
|
2044
|
-
const expected = (0,
|
|
2253
|
+
const expected = (0, import_node_crypto3.createHmac)("sha1", secret).update(signed).digest("base64");
|
|
2045
2254
|
const provided = String((headers == null ? void 0 : headers["x-twilio-signature"]) || "");
|
|
2046
|
-
const matches = expected.length === provided.length && (0,
|
|
2255
|
+
const matches = expected.length === provided.length && (0, import_node_crypto3.timingSafeEqual)(Buffer.from(expected), Buffer.from(provided));
|
|
2047
2256
|
if (!matches) throw Object.assign(new Error("Invalid Twilio signature"), { status: 401 });
|
|
2048
2257
|
return Object.fromEntries(params);
|
|
2049
2258
|
}
|
|
@@ -2227,15 +2436,30 @@ var drawbridge_default2 = {
|
|
|
2227
2436
|
},
|
|
2228
2437
|
icon: drawbridge_default,
|
|
2229
2438
|
// WHERE TWILIO PUTS THINGS on an inbound request, and WHICH credential
|
|
2230
|
-
// verifies
|
|
2231
|
-
//
|
|
2439
|
+
// verifies each channel. The route resolves the NAME to the stored value and
|
|
2440
|
+
// hands it to verify; this manifest never reads a store.
|
|
2441
|
+
//
|
|
2442
|
+
// `signature.channels` because this is the one manifest that receives from two
|
|
2443
|
+
// different vendors — Twilio on `sms`, SendGrid on `email` — which need
|
|
2444
|
+
// different credentials and share no scheme. A manifest whose channels all
|
|
2445
|
+
// verify the same way keeps declaring `signature` flat, and the route falls
|
|
2446
|
+
// back to it; Shopify's two channels do exactly that.
|
|
2232
2447
|
inbound: {
|
|
2233
2448
|
headers: {
|
|
2234
2449
|
id: "i-twilio-idempotency-token",
|
|
2235
2450
|
signature: "x-twilio-signature"
|
|
2236
2451
|
},
|
|
2237
2452
|
signature: {
|
|
2238
|
-
|
|
2453
|
+
channels: {
|
|
2454
|
+
email: sendgridInbound.signature,
|
|
2455
|
+
sms: { secret: "TWILIO_AUTH_TOKEN" },
|
|
2456
|
+
// Outbound delivery receipts, which Twilio signs exactly as it signs
|
|
2457
|
+
// an inbound message — same scheme, same credential, different url.
|
|
2458
|
+
// Separate channel because the payload is a MessageStatus rather than
|
|
2459
|
+
// a message, and conflating them would put a STOP keyword check on a
|
|
2460
|
+
// delivery receipt.
|
|
2461
|
+
"sms-status": { secret: "TWILIO_AUTH_TOKEN" }
|
|
2462
|
+
}
|
|
2239
2463
|
}
|
|
2240
2464
|
},
|
|
2241
2465
|
// PRIVATE: never in the catalog, always available to the builder.
|
|
@@ -2267,6 +2491,13 @@ var drawbridge_default2 = {
|
|
|
2267
2491
|
// Optional: SENDGRID_SEND_FROM_ADDRESS is not boot-required in sync
|
|
2268
2492
|
// either. Unset, it degrades to the account sender rather than
|
|
2269
2493
|
// refusing to start.
|
|
2494
|
+
// NOT redacted, because it is a PUBLIC key. Marking it secret would
|
|
2495
|
+
// be theatre, and it would stop an operator reading back the value
|
|
2496
|
+
// they need to compare against the SendGrid console.
|
|
2497
|
+
//
|
|
2498
|
+
// Optional: absent, the event webhook answers 500 on verify rather
|
|
2499
|
+
// than accepting unverified deliveries, and nothing else notices.
|
|
2500
|
+
{ input: "text", key: "eventKey", credential: "SENDGRID_EVENT_WEBHOOK_KEY", label: "Event webhook key", message: "SendGrid, Settings, Mail Settings, Event Webhook \u2014 the verification key shown once signature verification is enabled. Public, not a secret." },
|
|
2270
2501
|
{ input: "email", key: "leadSender", credential: "SENDGRID_SEND_FROM_ADDRESS", label: "Lead sender", message: "The default for lead-facing mail when a merchant has not verified their own domain." }
|
|
2271
2502
|
],
|
|
2272
2503
|
icon: sendgrid_default,
|
|
@@ -3013,7 +3244,7 @@ var klaviyo_default2 = {
|
|
|
3013
3244
|
};
|
|
3014
3245
|
|
|
3015
3246
|
// lib/connections/providers/mailchimp.js
|
|
3016
|
-
var
|
|
3247
|
+
var import_node_crypto4 = require("crypto");
|
|
3017
3248
|
|
|
3018
3249
|
// lib/connections/icons/mailchimp.js
|
|
3019
3250
|
var mailchimp_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
@@ -3045,7 +3276,7 @@ var api3 = async (path, { dc, fetcher = fetch, method = "GET", payload, token })
|
|
|
3045
3276
|
}
|
|
3046
3277
|
return response.status === 204 ? null : response.json();
|
|
3047
3278
|
};
|
|
3048
|
-
var subscriberHash = (email) => (0,
|
|
3279
|
+
var subscriberHash = (email) => (0, import_node_crypto4.createHash)("md5").update(String(email).trim().toLowerCase()).digest("hex");
|
|
3049
3280
|
var mailchimp_default2 = {
|
|
3050
3281
|
// OAUTH 2, authorization code. Every url below is quoted from
|
|
3051
3282
|
// mailchimp.com/developer/marketing/guides/access-user-data-oauth-2/ rather
|
|
@@ -3381,26 +3612,6 @@ var shopify_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill=
|
|
|
3381
3612
|
<path d="M300.325 382.771L368 365.96C368 365.96 338.904 169.185 338.689 167.892C338.473 166.599 337.396 165.737 336.318 165.737C335.24 165.737 316.274 165.306 316.274 165.306C316.274 165.306 304.636 154.099 300.325 149.788V382.771Z" fill="white"/>
|
|
3382
3613
|
</svg>`;
|
|
3383
3614
|
|
|
3384
|
-
// lib/connections/inbound.js
|
|
3385
|
-
var import_node_crypto4 = require("crypto");
|
|
3386
|
-
var verifySignature = ({ body, descriptor, headers, secret }) => {
|
|
3387
|
-
if (!secret) {
|
|
3388
|
-
throw Object.assign(new Error("Missing webhook secret: " + descriptor.signature.secret), { status: 500 });
|
|
3389
|
-
}
|
|
3390
|
-
const provided = headers[descriptor.headers.signature];
|
|
3391
|
-
if (!provided) {
|
|
3392
|
-
throw Object.assign(new Error("Missing webhook signature"), { status: 401 });
|
|
3393
|
-
}
|
|
3394
|
-
const digest = (0, import_node_crypto4.createHmac)(descriptor.signature.algorithm, secret).update(body).digest(descriptor.signature.encoding);
|
|
3395
|
-
const digestBuffer = Buffer.from(digest, descriptor.signature.encoding);
|
|
3396
|
-
const providedBuffer = Buffer.from(provided, descriptor.signature.encoding);
|
|
3397
|
-
if (digestBuffer.length !== providedBuffer.length || !(0, import_node_crypto4.timingSafeEqual)(digestBuffer, providedBuffer)) {
|
|
3398
|
-
throw Object.assign(new Error("Invalid webhook signature"), { status: 401 });
|
|
3399
|
-
}
|
|
3400
|
-
return JSON.parse(body.toString());
|
|
3401
|
-
};
|
|
3402
|
-
var readEventHeader = ({ descriptor, headers }) => headers[descriptor.headers.event];
|
|
3403
|
-
|
|
3404
3615
|
// lib/email.js
|
|
3405
3616
|
var GMAIL_DOMAINS = /* @__PURE__ */ new Set(["gmail.com", "googlemail.com"]);
|
|
3406
3617
|
var toCanonicalEmail = (value) => {
|