@drawbridge/drawbridge-utils 0.0.171 → 0.0.173
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 +57 -37
- package/dist/connections/index.d.cts +167 -51
- package/dist/connections/index.d.ts +167 -51
- package/dist/connections/index.js +57 -37
- package/dist/providers.cjs +57 -37
- package/dist/providers.js +57 -37
- package/package.json +2 -2
package/dist/providers.cjs
CHANGED
|
@@ -527,6 +527,7 @@ var row = ({ connection: connection2, data: data2, manifest, row: described }) =
|
|
|
527
527
|
};
|
|
528
528
|
};
|
|
529
529
|
var segmentRowWrites = ({ connection: connection2, data: data2, manifest, row: described, segment }) => {
|
|
530
|
+
if ((described == null ? void 0 : described.id) === void 0 || (described == null ? void 0 : described.id) === null) return [];
|
|
530
531
|
const built = row({ connection: connection2, data: data2, manifest, row: described });
|
|
531
532
|
return [
|
|
532
533
|
// PUSH IF ABSENT. The $ne guard is what makes a second concurrent register
|
|
@@ -2076,16 +2077,16 @@ var deliveryWrite = ({ at, code: code2, notification, permanent, provider, reaso
|
|
|
2076
2077
|
};
|
|
2077
2078
|
};
|
|
2078
2079
|
var summariseEvents = (events) => {
|
|
2079
|
-
const
|
|
2080
|
-
if (!
|
|
2080
|
+
const list2 = Array.isArray(events) ? events : [];
|
|
2081
|
+
if (!list2.length) return { message: "SendGrid delivered an empty event batch.", skipped: true };
|
|
2081
2082
|
const counts = {};
|
|
2082
|
-
for (const { event } of
|
|
2083
|
+
for (const { event } of list2) {
|
|
2083
2084
|
const name = event || "unknown";
|
|
2084
2085
|
counts[name] = (counts[name] || 0) + 1;
|
|
2085
2086
|
}
|
|
2086
2087
|
const tally = Object.entries(counts).map(([name, count]) => count + " " + name).join(", ");
|
|
2087
|
-
const refused =
|
|
2088
|
-
const writes =
|
|
2088
|
+
const refused = list2.filter(({ event }) => REFUSALS.has(event)).slice(0, 5).map(({ email, reason, response, status }) => email + " \u2014 " + (reason || response || status || "no reason given"));
|
|
2089
|
+
const writes = list2.map((event) => {
|
|
2089
2090
|
var _a;
|
|
2090
2091
|
return deliveryWrite({
|
|
2091
2092
|
at: event.timestamp ? new Date(event.timestamp * 1e3) : void 0,
|
|
@@ -2105,7 +2106,7 @@ var summariseEvents = (events) => {
|
|
|
2105
2106
|
});
|
|
2106
2107
|
}).filter(Boolean);
|
|
2107
2108
|
return {
|
|
2108
|
-
message: [
|
|
2109
|
+
message: [list2.length + " SendGrid event(s): " + tally, ...refused].join(" | "),
|
|
2109
2110
|
...writes.length ? { writes } : { skipped: true }
|
|
2110
2111
|
};
|
|
2111
2112
|
};
|
|
@@ -2972,6 +2973,14 @@ var klaviyo_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill=
|
|
|
2972
2973
|
<path d="M365.047 327.038H134.954V172.964H365.047L316.856 250.001L365.047 327.038Z" fill="#232121"/>
|
|
2973
2974
|
</svg>`;
|
|
2974
2975
|
|
|
2976
|
+
// lib/connections/scopes.js
|
|
2977
|
+
var list = (value) => (Array.isArray(value) ? value.flatMap((entry) => String(entry).split(/\s+/)) : String(value ?? "").split(/\s+/)).map((entry) => entry.trim()).filter(Boolean);
|
|
2978
|
+
var missingScopes = ({ granted, required }) => {
|
|
2979
|
+
if (granted === null || granted === void 0 || granted === "") return null;
|
|
2980
|
+
const held = new Set(list(granted));
|
|
2981
|
+
return list(required).filter((scope) => !held.has(scope));
|
|
2982
|
+
};
|
|
2983
|
+
|
|
2975
2984
|
// lib/connections/providers/klaviyo.js
|
|
2976
2985
|
var api2 = async (path, { fetcher = fetch, method = "GET", payload, token }) => {
|
|
2977
2986
|
const response = await fetcher("https://a.klaviyo.com/api" + path, {
|
|
@@ -2999,8 +3008,10 @@ var api2 = async (path, { fetcher = fetch, method = "GET", payload, token }) =>
|
|
|
2999
3008
|
}
|
|
3000
3009
|
return response.status === 204 ? null : response.json();
|
|
3001
3010
|
};
|
|
3002
|
-
var segmentName = (title) => "Drawbridge: " + title;
|
|
3003
|
-
var
|
|
3011
|
+
var segmentName = (title, id) => "Drawbridge: " + title + " (" + String(id).slice(-6) + ")";
|
|
3012
|
+
var SCOPES = "accounts:read lists:read lists:write profiles:read profiles:write segments:read segments:write";
|
|
3013
|
+
var missing = (settings) => missingScopes({ granted: settings == null ? void 0 : settings.scope, required: SCOPES });
|
|
3014
|
+
var canManageSegments = (settings) => !(missing(settings) || []).includes("segments:write");
|
|
3004
3015
|
var klaviyo_default2 = {
|
|
3005
3016
|
// OAuth 2.1, and PKCE is REQUIRED rather than recommended: Klaviyo refuses an
|
|
3006
3017
|
// exchange without a code_verifier matching the challenge the consent
|
|
@@ -3044,7 +3055,7 @@ var klaviyo_default2 = {
|
|
|
3044
3055
|
// Update and Delete Segment each list `segments:write`
|
|
3045
3056
|
// (raw.githubusercontent.com/klaviyo/openapi/main/openapi/stable.json,
|
|
3046
3057
|
// revision 2026-07-15, fetched 2026-09-11).
|
|
3047
|
-
scopes:
|
|
3058
|
+
scopes: SCOPES,
|
|
3048
3059
|
// EVERY VENDOR URL, in one place. `revoke` used to be a literal inside
|
|
3049
3060
|
// the disconnect hook — three vendor addresses, two of them declared,
|
|
3050
3061
|
// which is exactly the kind of split that goes unnoticed.
|
|
@@ -3225,8 +3236,14 @@ var klaviyo_default2 = {
|
|
|
3225
3236
|
});
|
|
3226
3237
|
return { ok: Boolean(token) };
|
|
3227
3238
|
},
|
|
3228
|
-
//
|
|
3229
|
-
scopes
|
|
3239
|
+
// THEY DO DRIFT, and the comment here used to say they could not. Klaviyo
|
|
3240
|
+
// scopes are fixed on the APP, so adding one re-consents every NEW grant
|
|
3241
|
+
// and leaves every EXISTING one exactly as narrow as it was — with no
|
|
3242
|
+
// error, no webhook, and nothing that notices. That is what left grants
|
|
3243
|
+
// authenticating perfectly while silently unable to manage a segment.
|
|
3244
|
+
//
|
|
3245
|
+
// The same slot Shopify answers, so one caller can ask any vendor.
|
|
3246
|
+
scopes: ({ scope }) => missingScopes({ granted: scope, required: SCOPES }),
|
|
3230
3247
|
// KLAVIYO REQUIRES HTTP BASIC on the token endpoint and rejects the same
|
|
3231
3248
|
// client_id/client_secret pair as body fields. Everything else about the
|
|
3232
3249
|
// request is standard, so this is the shared implementation told the one
|
|
@@ -3247,9 +3264,9 @@ var klaviyo_default2 = {
|
|
|
3247
3264
|
remove: false,
|
|
3248
3265
|
sync: async ({ context, lead, segments, settings, suppressed, token }, { fetcher } = {}) => {
|
|
3249
3266
|
var _a, _b, _c, _d, _e;
|
|
3250
|
-
const
|
|
3267
|
+
const list2 = settings == null ? void 0 : settings.list;
|
|
3251
3268
|
const request2 = { email: ((_b = (_a = lead == null ? void 0 : lead.canonical) == null ? void 0 : _a.email) == null ? void 0 : _b.value) || (lead == null ? void 0 : lead.email) || null, list: (settings == null ? void 0 : settings.list) || null };
|
|
3252
|
-
if (!
|
|
3269
|
+
if (!list2) return { message: "No Klaviyo list is chosen for this connection.", request: request2, skipped: true };
|
|
3253
3270
|
const email = ((_d = (_c = lead == null ? void 0 : lead.canonical) == null ? void 0 : _c.email) == null ? void 0 : _d.value) || (lead == null ? void 0 : lead.email);
|
|
3254
3271
|
if (!email) return { message: "That lead has no email address to sync.", request: request2, skipped: true };
|
|
3255
3272
|
const person = (context == null ? void 0 : context.contact) || null;
|
|
@@ -3328,7 +3345,7 @@ var klaviyo_default2 = {
|
|
|
3328
3345
|
}]
|
|
3329
3346
|
}
|
|
3330
3347
|
},
|
|
3331
|
-
relationships: { list: { data: { id:
|
|
3348
|
+
relationships: { list: { data: { id: list2, type: "list" } } },
|
|
3332
3349
|
type: "profile-subscription-bulk-create-job"
|
|
3333
3350
|
}
|
|
3334
3351
|
},
|
|
@@ -3374,7 +3391,7 @@ var klaviyo_default2 = {
|
|
|
3374
3391
|
skipped: true
|
|
3375
3392
|
};
|
|
3376
3393
|
}
|
|
3377
|
-
const name = segmentName(segment.title);
|
|
3394
|
+
const name = segmentName(segment.title, segment.id);
|
|
3378
3395
|
const existing = segmentRowFor({ connection: connection2, segment });
|
|
3379
3396
|
let id = null;
|
|
3380
3397
|
if (existing == null ? void 0 : existing.id) {
|
|
@@ -3501,7 +3518,7 @@ var klaviyo_default2 = {
|
|
|
3501
3518
|
// The one call below proves the minted token is HONOURED — mint and
|
|
3502
3519
|
// acceptance are different facts, and /accounts is already the call
|
|
3503
3520
|
// the connect flow makes (auth.connect), so it needs no new scope.
|
|
3504
|
-
health: async ({ connection: connection2, token }, { fetcher, read } = {}) => {
|
|
3521
|
+
health: async ({ connection: connection2, settings, token }, { fetcher, read } = {}) => {
|
|
3505
3522
|
const request2 = { connectionId: connection2.id };
|
|
3506
3523
|
try {
|
|
3507
3524
|
await api2("/accounts", { fetcher, token });
|
|
@@ -3553,8 +3570,8 @@ var klaviyo_default2 = {
|
|
|
3553
3570
|
let pages = 0;
|
|
3554
3571
|
while (next && audiences.length < limit && pages < 20) {
|
|
3555
3572
|
const body = await api2(next, { fetcher, token });
|
|
3556
|
-
for (const
|
|
3557
|
-
audiences.push({ id:
|
|
3573
|
+
for (const list2 of (body == null ? void 0 : body.data) || []) {
|
|
3574
|
+
audiences.push({ id: list2.id, title: ((_a = list2 == null ? void 0 : list2.attributes) == null ? void 0 : _a.name) || list2.id });
|
|
3558
3575
|
}
|
|
3559
3576
|
const link = (_b = body == null ? void 0 : body.links) == null ? void 0 : _b.next;
|
|
3560
3577
|
next = link ? String(link).replace(/^https:\/\/a\.klaviyo\.com\/api/, "") : null;
|
|
@@ -3720,13 +3737,14 @@ var klaviyo_default2 = {
|
|
|
3720
3737
|
var _a;
|
|
3721
3738
|
if (!["active", "pending"].includes(data2 == null ? void 0 : data2.status)) return [];
|
|
3722
3739
|
return [
|
|
3723
|
-
//
|
|
3724
|
-
//
|
|
3725
|
-
//
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3740
|
+
// THE MISSING SEGMENT SCOPE IS NOT HERE ANY MORE. It is an ERROR entry,
|
|
3741
|
+
// written by the health check, because a task is quiet: it renders only in
|
|
3742
|
+
// the body of this connection's own page, so a merchant who never opens it
|
|
3743
|
+
// never learns that their segments stopped being published. An error entry
|
|
3744
|
+
// reaches the card and the organization checklist too.
|
|
3745
|
+
//
|
|
3746
|
+
// Deliberately not a status change — see the health hook for why that
|
|
3747
|
+
// would stop the contact sync that still works.
|
|
3730
3748
|
...((_a = data2 == null ? void 0 : data2.settings) == null ? void 0 : _a.list) ? [] : [{
|
|
3731
3749
|
message: "Choose which Klaviyo list your contacts should sync into. Until you do, nothing is being synced.",
|
|
3732
3750
|
title: "Choose a list"
|
|
@@ -3781,7 +3799,10 @@ var api3 = async (path, { dc, fetcher = fetch, method = "GET", payload, token })
|
|
|
3781
3799
|
};
|
|
3782
3800
|
var subscriberHash = (email) => (0, import_node_crypto5.createHash)("md5").update(String(email).trim().toLowerCase()).digest("hex");
|
|
3783
3801
|
var TAG_NAME_LIMIT = 100;
|
|
3784
|
-
var tagName = (title) =>
|
|
3802
|
+
var tagName = (title, id) => {
|
|
3803
|
+
const suffix = " (" + String(id).slice(-6) + ")";
|
|
3804
|
+
return ("Drawbridge: " + title).slice(0, TAG_NAME_LIMIT - suffix.length) + suffix;
|
|
3805
|
+
};
|
|
3785
3806
|
var mailchimp_default2 = {
|
|
3786
3807
|
// OAUTH 2, authorization code. Every url below is quoted from
|
|
3787
3808
|
// mailchimp.com/developer/marketing/guides/access-user-data-oauth-2/ rather
|
|
@@ -3962,14 +3983,17 @@ var mailchimp_default2 = {
|
|
|
3962
3983
|
}
|
|
3963
3984
|
},
|
|
3964
3985
|
{
|
|
3965
|
-
|
|
3986
|
+
// THE ID AS WELL AS THE TITLE, because the tag name carries it —
|
|
3987
|
+
// see tagName. Without it every name here would end in the
|
|
3988
|
+
// string 'undefined' and match nothing register wrote.
|
|
3989
|
+
$project: { _id: 0, id: 1, title: 1 }
|
|
3966
3990
|
}
|
|
3967
3991
|
]
|
|
3968
3992
|
});
|
|
3969
|
-
const joined = new Set(segments.map((entry) => entry.
|
|
3970
|
-
const tags = (owned || []).
|
|
3971
|
-
name: tagName(title),
|
|
3972
|
-
status: joined.has(
|
|
3993
|
+
const joined = new Set(segments.map((entry) => entry.id));
|
|
3994
|
+
const tags = (owned || []).filter((entry) => entry.id && entry.title).map((entry) => ({
|
|
3995
|
+
name: tagName(entry.title, entry.id),
|
|
3996
|
+
status: joined.has(entry.id) ? "active" : "inactive"
|
|
3973
3997
|
}));
|
|
3974
3998
|
if (tags.length > 0) {
|
|
3975
3999
|
await api3("/lists/" + audience + "/members/" + hash + "/tags", {
|
|
@@ -4021,7 +4045,7 @@ var mailchimp_default2 = {
|
|
|
4021
4045
|
if (!audience) return { message: "No Mailchimp audience is chosen for this connection.", skipped: true };
|
|
4022
4046
|
const segment = await currentSegment({ read, segment: context == null ? void 0 : context.segment });
|
|
4023
4047
|
if (!(segment == null ? void 0 : segment.id) || segment.system) return { message: "That segment is not one this connection publishes.", skipped: true };
|
|
4024
|
-
const name = tagName(segment.title);
|
|
4048
|
+
const name = tagName(segment.title, segment.id);
|
|
4025
4049
|
const existing = segmentRowFor({ connection: connection2, segment });
|
|
4026
4050
|
let id = null;
|
|
4027
4051
|
if (existing == null ? void 0 : existing.id) {
|
|
@@ -4127,7 +4151,7 @@ var mailchimp_default2 = {
|
|
|
4127
4151
|
"/lists?count=" + count + "&offset=" + offset + "&fields=lists.id,lists.name,total_items",
|
|
4128
4152
|
{ dc: settings == null ? void 0 : settings.dc, fetcher, token }
|
|
4129
4153
|
);
|
|
4130
|
-
const audiences = ((body == null ? void 0 : body.lists) || []).map((
|
|
4154
|
+
const audiences = ((body == null ? void 0 : body.lists) || []).map((list2) => ({ id: list2.id, title: (list2 == null ? void 0 : list2.name) || list2.id }));
|
|
4131
4155
|
const term = String((search == null ? void 0 : search.value) || "").trim().toLowerCase();
|
|
4132
4156
|
const items = term ? audiences.filter((entry) => entry.title.toLowerCase().includes(term)) : audiences;
|
|
4133
4157
|
const nextOffset = offset + count;
|
|
@@ -4593,10 +4617,6 @@ var shopify_default2 = {
|
|
|
4593
4617
|
if (!(context == null ? void 0 : context.lead)) return { message: "Lead id is missing.", request: request2, response: { skipped: true }, skipped: true };
|
|
4594
4618
|
if (!(discount == null ? void 0 : discount.id)) return { message: "Discount is not configured on this step.", request: request2, response: { skipped: true }, skipped: true };
|
|
4595
4619
|
const adminAccessToken = await adminToken();
|
|
4596
|
-
if (!context.shopifyCustomerId) {
|
|
4597
|
-
const customer = await shopify.admin.getOrCreateCustomer({ adminAccessToken, domain: connection2.shop, email: context.email });
|
|
4598
|
-
if (!(customer == null ? void 0 : customer.id)) return { message: "Shopify did not return a customer id \u2014 create/lookup failed.", request: request2, response: { skipped: true }, skipped: true };
|
|
4599
|
-
}
|
|
4600
4620
|
const discountCode = await shopify.admin.createDiscountCode({
|
|
4601
4621
|
adminAccessToken,
|
|
4602
4622
|
code: "DB-" + generateDiscountCode(),
|
package/dist/providers.js
CHANGED
|
@@ -478,6 +478,7 @@ var row = ({ connection: connection2, data: data2, manifest, row: described }) =
|
|
|
478
478
|
};
|
|
479
479
|
};
|
|
480
480
|
var segmentRowWrites = ({ connection: connection2, data: data2, manifest, row: described, segment }) => {
|
|
481
|
+
if ((described == null ? void 0 : described.id) === void 0 || (described == null ? void 0 : described.id) === null) return [];
|
|
481
482
|
const built = row({ connection: connection2, data: data2, manifest, row: described });
|
|
482
483
|
return [
|
|
483
484
|
// PUSH IF ABSENT. The $ne guard is what makes a second concurrent register
|
|
@@ -2027,16 +2028,16 @@ var deliveryWrite = ({ at, code: code2, notification, permanent, provider, reaso
|
|
|
2027
2028
|
};
|
|
2028
2029
|
};
|
|
2029
2030
|
var summariseEvents = (events) => {
|
|
2030
|
-
const
|
|
2031
|
-
if (!
|
|
2031
|
+
const list2 = Array.isArray(events) ? events : [];
|
|
2032
|
+
if (!list2.length) return { message: "SendGrid delivered an empty event batch.", skipped: true };
|
|
2032
2033
|
const counts = {};
|
|
2033
|
-
for (const { event } of
|
|
2034
|
+
for (const { event } of list2) {
|
|
2034
2035
|
const name = event || "unknown";
|
|
2035
2036
|
counts[name] = (counts[name] || 0) + 1;
|
|
2036
2037
|
}
|
|
2037
2038
|
const tally = Object.entries(counts).map(([name, count]) => count + " " + name).join(", ");
|
|
2038
|
-
const refused =
|
|
2039
|
-
const writes =
|
|
2039
|
+
const refused = list2.filter(({ event }) => REFUSALS.has(event)).slice(0, 5).map(({ email, reason, response, status }) => email + " \u2014 " + (reason || response || status || "no reason given"));
|
|
2040
|
+
const writes = list2.map((event) => {
|
|
2040
2041
|
var _a;
|
|
2041
2042
|
return deliveryWrite({
|
|
2042
2043
|
at: event.timestamp ? new Date(event.timestamp * 1e3) : void 0,
|
|
@@ -2056,7 +2057,7 @@ var summariseEvents = (events) => {
|
|
|
2056
2057
|
});
|
|
2057
2058
|
}).filter(Boolean);
|
|
2058
2059
|
return {
|
|
2059
|
-
message: [
|
|
2060
|
+
message: [list2.length + " SendGrid event(s): " + tally, ...refused].join(" | "),
|
|
2060
2061
|
...writes.length ? { writes } : { skipped: true }
|
|
2061
2062
|
};
|
|
2062
2063
|
};
|
|
@@ -2923,6 +2924,14 @@ var klaviyo_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill=
|
|
|
2923
2924
|
<path d="M365.047 327.038H134.954V172.964H365.047L316.856 250.001L365.047 327.038Z" fill="#232121"/>
|
|
2924
2925
|
</svg>`;
|
|
2925
2926
|
|
|
2927
|
+
// lib/connections/scopes.js
|
|
2928
|
+
var list = (value) => (Array.isArray(value) ? value.flatMap((entry) => String(entry).split(/\s+/)) : String(value ?? "").split(/\s+/)).map((entry) => entry.trim()).filter(Boolean);
|
|
2929
|
+
var missingScopes = ({ granted, required }) => {
|
|
2930
|
+
if (granted === null || granted === void 0 || granted === "") return null;
|
|
2931
|
+
const held = new Set(list(granted));
|
|
2932
|
+
return list(required).filter((scope) => !held.has(scope));
|
|
2933
|
+
};
|
|
2934
|
+
|
|
2926
2935
|
// lib/connections/providers/klaviyo.js
|
|
2927
2936
|
var api2 = async (path, { fetcher = fetch, method = "GET", payload, token }) => {
|
|
2928
2937
|
const response = await fetcher("https://a.klaviyo.com/api" + path, {
|
|
@@ -2950,8 +2959,10 @@ var api2 = async (path, { fetcher = fetch, method = "GET", payload, token }) =>
|
|
|
2950
2959
|
}
|
|
2951
2960
|
return response.status === 204 ? null : response.json();
|
|
2952
2961
|
};
|
|
2953
|
-
var segmentName = (title) => "Drawbridge: " + title;
|
|
2954
|
-
var
|
|
2962
|
+
var segmentName = (title, id) => "Drawbridge: " + title + " (" + String(id).slice(-6) + ")";
|
|
2963
|
+
var SCOPES = "accounts:read lists:read lists:write profiles:read profiles:write segments:read segments:write";
|
|
2964
|
+
var missing = (settings) => missingScopes({ granted: settings == null ? void 0 : settings.scope, required: SCOPES });
|
|
2965
|
+
var canManageSegments = (settings) => !(missing(settings) || []).includes("segments:write");
|
|
2955
2966
|
var klaviyo_default2 = {
|
|
2956
2967
|
// OAuth 2.1, and PKCE is REQUIRED rather than recommended: Klaviyo refuses an
|
|
2957
2968
|
// exchange without a code_verifier matching the challenge the consent
|
|
@@ -2995,7 +3006,7 @@ var klaviyo_default2 = {
|
|
|
2995
3006
|
// Update and Delete Segment each list `segments:write`
|
|
2996
3007
|
// (raw.githubusercontent.com/klaviyo/openapi/main/openapi/stable.json,
|
|
2997
3008
|
// revision 2026-07-15, fetched 2026-09-11).
|
|
2998
|
-
scopes:
|
|
3009
|
+
scopes: SCOPES,
|
|
2999
3010
|
// EVERY VENDOR URL, in one place. `revoke` used to be a literal inside
|
|
3000
3011
|
// the disconnect hook — three vendor addresses, two of them declared,
|
|
3001
3012
|
// which is exactly the kind of split that goes unnoticed.
|
|
@@ -3176,8 +3187,14 @@ var klaviyo_default2 = {
|
|
|
3176
3187
|
});
|
|
3177
3188
|
return { ok: Boolean(token) };
|
|
3178
3189
|
},
|
|
3179
|
-
//
|
|
3180
|
-
scopes
|
|
3190
|
+
// THEY DO DRIFT, and the comment here used to say they could not. Klaviyo
|
|
3191
|
+
// scopes are fixed on the APP, so adding one re-consents every NEW grant
|
|
3192
|
+
// and leaves every EXISTING one exactly as narrow as it was — with no
|
|
3193
|
+
// error, no webhook, and nothing that notices. That is what left grants
|
|
3194
|
+
// authenticating perfectly while silently unable to manage a segment.
|
|
3195
|
+
//
|
|
3196
|
+
// The same slot Shopify answers, so one caller can ask any vendor.
|
|
3197
|
+
scopes: ({ scope }) => missingScopes({ granted: scope, required: SCOPES }),
|
|
3181
3198
|
// KLAVIYO REQUIRES HTTP BASIC on the token endpoint and rejects the same
|
|
3182
3199
|
// client_id/client_secret pair as body fields. Everything else about the
|
|
3183
3200
|
// request is standard, so this is the shared implementation told the one
|
|
@@ -3198,9 +3215,9 @@ var klaviyo_default2 = {
|
|
|
3198
3215
|
remove: false,
|
|
3199
3216
|
sync: async ({ context, lead, segments, settings, suppressed, token }, { fetcher } = {}) => {
|
|
3200
3217
|
var _a, _b, _c, _d, _e;
|
|
3201
|
-
const
|
|
3218
|
+
const list2 = settings == null ? void 0 : settings.list;
|
|
3202
3219
|
const request2 = { email: ((_b = (_a = lead == null ? void 0 : lead.canonical) == null ? void 0 : _a.email) == null ? void 0 : _b.value) || (lead == null ? void 0 : lead.email) || null, list: (settings == null ? void 0 : settings.list) || null };
|
|
3203
|
-
if (!
|
|
3220
|
+
if (!list2) return { message: "No Klaviyo list is chosen for this connection.", request: request2, skipped: true };
|
|
3204
3221
|
const email = ((_d = (_c = lead == null ? void 0 : lead.canonical) == null ? void 0 : _c.email) == null ? void 0 : _d.value) || (lead == null ? void 0 : lead.email);
|
|
3205
3222
|
if (!email) return { message: "That lead has no email address to sync.", request: request2, skipped: true };
|
|
3206
3223
|
const person = (context == null ? void 0 : context.contact) || null;
|
|
@@ -3279,7 +3296,7 @@ var klaviyo_default2 = {
|
|
|
3279
3296
|
}]
|
|
3280
3297
|
}
|
|
3281
3298
|
},
|
|
3282
|
-
relationships: { list: { data: { id:
|
|
3299
|
+
relationships: { list: { data: { id: list2, type: "list" } } },
|
|
3283
3300
|
type: "profile-subscription-bulk-create-job"
|
|
3284
3301
|
}
|
|
3285
3302
|
},
|
|
@@ -3325,7 +3342,7 @@ var klaviyo_default2 = {
|
|
|
3325
3342
|
skipped: true
|
|
3326
3343
|
};
|
|
3327
3344
|
}
|
|
3328
|
-
const name = segmentName(segment.title);
|
|
3345
|
+
const name = segmentName(segment.title, segment.id);
|
|
3329
3346
|
const existing = segmentRowFor({ connection: connection2, segment });
|
|
3330
3347
|
let id = null;
|
|
3331
3348
|
if (existing == null ? void 0 : existing.id) {
|
|
@@ -3452,7 +3469,7 @@ var klaviyo_default2 = {
|
|
|
3452
3469
|
// The one call below proves the minted token is HONOURED — mint and
|
|
3453
3470
|
// acceptance are different facts, and /accounts is already the call
|
|
3454
3471
|
// the connect flow makes (auth.connect), so it needs no new scope.
|
|
3455
|
-
health: async ({ connection: connection2, token }, { fetcher, read } = {}) => {
|
|
3472
|
+
health: async ({ connection: connection2, settings, token }, { fetcher, read } = {}) => {
|
|
3456
3473
|
const request2 = { connectionId: connection2.id };
|
|
3457
3474
|
try {
|
|
3458
3475
|
await api2("/accounts", { fetcher, token });
|
|
@@ -3504,8 +3521,8 @@ var klaviyo_default2 = {
|
|
|
3504
3521
|
let pages = 0;
|
|
3505
3522
|
while (next && audiences.length < limit && pages < 20) {
|
|
3506
3523
|
const body = await api2(next, { fetcher, token });
|
|
3507
|
-
for (const
|
|
3508
|
-
audiences.push({ id:
|
|
3524
|
+
for (const list2 of (body == null ? void 0 : body.data) || []) {
|
|
3525
|
+
audiences.push({ id: list2.id, title: ((_a = list2 == null ? void 0 : list2.attributes) == null ? void 0 : _a.name) || list2.id });
|
|
3509
3526
|
}
|
|
3510
3527
|
const link = (_b = body == null ? void 0 : body.links) == null ? void 0 : _b.next;
|
|
3511
3528
|
next = link ? String(link).replace(/^https:\/\/a\.klaviyo\.com\/api/, "") : null;
|
|
@@ -3671,13 +3688,14 @@ var klaviyo_default2 = {
|
|
|
3671
3688
|
var _a;
|
|
3672
3689
|
if (!["active", "pending"].includes(data2 == null ? void 0 : data2.status)) return [];
|
|
3673
3690
|
return [
|
|
3674
|
-
//
|
|
3675
|
-
//
|
|
3676
|
-
//
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3691
|
+
// THE MISSING SEGMENT SCOPE IS NOT HERE ANY MORE. It is an ERROR entry,
|
|
3692
|
+
// written by the health check, because a task is quiet: it renders only in
|
|
3693
|
+
// the body of this connection's own page, so a merchant who never opens it
|
|
3694
|
+
// never learns that their segments stopped being published. An error entry
|
|
3695
|
+
// reaches the card and the organization checklist too.
|
|
3696
|
+
//
|
|
3697
|
+
// Deliberately not a status change — see the health hook for why that
|
|
3698
|
+
// would stop the contact sync that still works.
|
|
3681
3699
|
...((_a = data2 == null ? void 0 : data2.settings) == null ? void 0 : _a.list) ? [] : [{
|
|
3682
3700
|
message: "Choose which Klaviyo list your contacts should sync into. Until you do, nothing is being synced.",
|
|
3683
3701
|
title: "Choose a list"
|
|
@@ -3732,7 +3750,10 @@ var api3 = async (path, { dc, fetcher = fetch, method = "GET", payload, token })
|
|
|
3732
3750
|
};
|
|
3733
3751
|
var subscriberHash = (email) => createHash2("md5").update(String(email).trim().toLowerCase()).digest("hex");
|
|
3734
3752
|
var TAG_NAME_LIMIT = 100;
|
|
3735
|
-
var tagName = (title) =>
|
|
3753
|
+
var tagName = (title, id) => {
|
|
3754
|
+
const suffix = " (" + String(id).slice(-6) + ")";
|
|
3755
|
+
return ("Drawbridge: " + title).slice(0, TAG_NAME_LIMIT - suffix.length) + suffix;
|
|
3756
|
+
};
|
|
3736
3757
|
var mailchimp_default2 = {
|
|
3737
3758
|
// OAUTH 2, authorization code. Every url below is quoted from
|
|
3738
3759
|
// mailchimp.com/developer/marketing/guides/access-user-data-oauth-2/ rather
|
|
@@ -3913,14 +3934,17 @@ var mailchimp_default2 = {
|
|
|
3913
3934
|
}
|
|
3914
3935
|
},
|
|
3915
3936
|
{
|
|
3916
|
-
|
|
3937
|
+
// THE ID AS WELL AS THE TITLE, because the tag name carries it —
|
|
3938
|
+
// see tagName. Without it every name here would end in the
|
|
3939
|
+
// string 'undefined' and match nothing register wrote.
|
|
3940
|
+
$project: { _id: 0, id: 1, title: 1 }
|
|
3917
3941
|
}
|
|
3918
3942
|
]
|
|
3919
3943
|
});
|
|
3920
|
-
const joined = new Set(segments.map((entry) => entry.
|
|
3921
|
-
const tags = (owned || []).
|
|
3922
|
-
name: tagName(title),
|
|
3923
|
-
status: joined.has(
|
|
3944
|
+
const joined = new Set(segments.map((entry) => entry.id));
|
|
3945
|
+
const tags = (owned || []).filter((entry) => entry.id && entry.title).map((entry) => ({
|
|
3946
|
+
name: tagName(entry.title, entry.id),
|
|
3947
|
+
status: joined.has(entry.id) ? "active" : "inactive"
|
|
3924
3948
|
}));
|
|
3925
3949
|
if (tags.length > 0) {
|
|
3926
3950
|
await api3("/lists/" + audience + "/members/" + hash + "/tags", {
|
|
@@ -3972,7 +3996,7 @@ var mailchimp_default2 = {
|
|
|
3972
3996
|
if (!audience) return { message: "No Mailchimp audience is chosen for this connection.", skipped: true };
|
|
3973
3997
|
const segment = await currentSegment({ read, segment: context == null ? void 0 : context.segment });
|
|
3974
3998
|
if (!(segment == null ? void 0 : segment.id) || segment.system) return { message: "That segment is not one this connection publishes.", skipped: true };
|
|
3975
|
-
const name = tagName(segment.title);
|
|
3999
|
+
const name = tagName(segment.title, segment.id);
|
|
3976
4000
|
const existing = segmentRowFor({ connection: connection2, segment });
|
|
3977
4001
|
let id = null;
|
|
3978
4002
|
if (existing == null ? void 0 : existing.id) {
|
|
@@ -4078,7 +4102,7 @@ var mailchimp_default2 = {
|
|
|
4078
4102
|
"/lists?count=" + count + "&offset=" + offset + "&fields=lists.id,lists.name,total_items",
|
|
4079
4103
|
{ dc: settings == null ? void 0 : settings.dc, fetcher, token }
|
|
4080
4104
|
);
|
|
4081
|
-
const audiences = ((body == null ? void 0 : body.lists) || []).map((
|
|
4105
|
+
const audiences = ((body == null ? void 0 : body.lists) || []).map((list2) => ({ id: list2.id, title: (list2 == null ? void 0 : list2.name) || list2.id }));
|
|
4082
4106
|
const term = String((search == null ? void 0 : search.value) || "").trim().toLowerCase();
|
|
4083
4107
|
const items = term ? audiences.filter((entry) => entry.title.toLowerCase().includes(term)) : audiences;
|
|
4084
4108
|
const nextOffset = offset + count;
|
|
@@ -4544,10 +4568,6 @@ var shopify_default2 = {
|
|
|
4544
4568
|
if (!(context == null ? void 0 : context.lead)) return { message: "Lead id is missing.", request: request2, response: { skipped: true }, skipped: true };
|
|
4545
4569
|
if (!(discount == null ? void 0 : discount.id)) return { message: "Discount is not configured on this step.", request: request2, response: { skipped: true }, skipped: true };
|
|
4546
4570
|
const adminAccessToken = await adminToken();
|
|
4547
|
-
if (!context.shopifyCustomerId) {
|
|
4548
|
-
const customer = await shopify.admin.getOrCreateCustomer({ adminAccessToken, domain: connection2.shop, email: context.email });
|
|
4549
|
-
if (!(customer == null ? void 0 : customer.id)) return { message: "Shopify did not return a customer id \u2014 create/lookup failed.", request: request2, response: { skipped: true }, skipped: true };
|
|
4550
|
-
}
|
|
4551
4571
|
const discountCode = await shopify.admin.createDiscountCode({
|
|
4552
4572
|
adminAccessToken,
|
|
4553
4573
|
code: "DB-" + generateDiscountCode(),
|
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"tinycolor2": "1.6.0"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@drawbridge/drawbridge-agents": "0.1.
|
|
16
|
+
"@drawbridge/drawbridge-agents": "0.1.59",
|
|
17
17
|
"@node-oauth/oauth2-server": "5.3.0",
|
|
18
18
|
"tsup": "8.5.1",
|
|
19
19
|
"typescript": "5.9.3",
|
|
@@ -226,5 +226,5 @@
|
|
|
226
226
|
"prepublishOnly": ". \"$HOME/.nvm/nvm.sh\" && nvm use && tsup && node --test"
|
|
227
227
|
},
|
|
228
228
|
"types": "dist/index.d.ts",
|
|
229
|
-
"version": "0.0.
|
|
229
|
+
"version": "0.0.173"
|
|
230
230
|
}
|