@drawbridge/drawbridge-utils 0.0.154 → 0.0.156
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 +78 -16
- package/dist/connections/index.d.cts +156 -22
- package/dist/connections/index.d.ts +156 -22
- package/dist/connections/index.js +78 -16
- package/dist/email.cjs +14 -0
- package/dist/email.d.cts +53 -1
- package/dist/email.d.ts +53 -1
- package/dist/email.js +13 -0
- package/dist/providers.cjs +78 -16
- package/dist/providers.js +78 -16
- package/package.json +1 -1
package/dist/providers.cjs
CHANGED
|
@@ -245,6 +245,7 @@ var HOOK_PROPS = Object.freeze([
|
|
|
245
245
|
"scope",
|
|
246
246
|
"search",
|
|
247
247
|
"secret",
|
|
248
|
+
"segments",
|
|
248
249
|
"settings",
|
|
249
250
|
"sort",
|
|
250
251
|
"step",
|
|
@@ -2574,10 +2575,15 @@ var klaviyo_default2 = {
|
|
|
2574
2575
|
excerpt: "Sync the contacts your campaigns collect into a Klaviyo list.",
|
|
2575
2576
|
// HOW TO CONNECT, in the merchant's words. Was `setup`, which nothing
|
|
2576
2577
|
// rendered — four useful instructions no component displayed.
|
|
2578
|
+
// NAMES THE LIST STEP, because `status` below gates on it: a grant with no
|
|
2579
|
+
// list chosen sits at Pending, and this told the merchant it would show
|
|
2580
|
+
// Active. The one required action was the one action the guide omitted, so
|
|
2581
|
+
// anybody following it landed on a connection that looked broken.
|
|
2577
2582
|
guide: [
|
|
2578
2583
|
"Press Connect. Drawbridge sends you to Klaviyo to approve access.",
|
|
2579
2584
|
"Sign in to Klaviyo if you are not already, and choose the account to connect.",
|
|
2580
|
-
"Approve the permissions Klaviyo lists. You
|
|
2585
|
+
"Approve the permissions Klaviyo lists. You come back here to pick the list your contacts should sync into.",
|
|
2586
|
+
"The connection shows Pending until you pick a list, then Active.",
|
|
2581
2587
|
"You can revoke access at any time from Klaviyo, under Integrations."
|
|
2582
2588
|
]
|
|
2583
2589
|
},
|
|
@@ -2699,14 +2705,15 @@ var klaviyo_default2 = {
|
|
|
2699
2705
|
// Not yet. Suppression syncs an opt-out as unsubscribed, which is a
|
|
2700
2706
|
// different thing from deleting the profile.
|
|
2701
2707
|
remove: false,
|
|
2702
|
-
sync: async ({
|
|
2708
|
+
sync: async ({ context, lead, segments, settings, suppressed, token }, { fetcher } = {}) => {
|
|
2703
2709
|
var _a, _b, _c;
|
|
2704
2710
|
const list = settings == null ? void 0 : settings.list;
|
|
2705
2711
|
if (!list) return { message: "No Klaviyo list is chosen for this connection.", skipped: true };
|
|
2706
2712
|
const 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);
|
|
2707
2713
|
if (!email) return { message: "That lead has no email address to sync.", skipped: true };
|
|
2708
|
-
const
|
|
2709
|
-
const
|
|
2714
|
+
const person = (context == null ? void 0 : context.contact) || null;
|
|
2715
|
+
const totals = (person == null ? void 0 : person.totals) || {};
|
|
2716
|
+
const profile = await api2("/profile-import", {
|
|
2710
2717
|
fetcher,
|
|
2711
2718
|
method: "POST",
|
|
2712
2719
|
payload: {
|
|
@@ -2715,16 +2722,36 @@ var klaviyo_default2 = {
|
|
|
2715
2722
|
email,
|
|
2716
2723
|
...(lead == null ? void 0 : lead.name) && { first_name: String(lead.name).trim().split(/\s+/)[0] },
|
|
2717
2724
|
properties: {
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2725
|
+
...person && {
|
|
2726
|
+
drawbridge_campaigns: (person.campaigns || []).length,
|
|
2727
|
+
drawbridge_draws: totals.draws || 0,
|
|
2728
|
+
drawbridge_entries: totals.entries || 0,
|
|
2729
|
+
drawbridge_orders: totals.orders || 0,
|
|
2730
|
+
// Campaign-attributed, NOT lifetime. A merchant running
|
|
2731
|
+
// Shopify already has lifetime revenue in Klaviyo through
|
|
2732
|
+
// Klaviyo's own integration; what only we can say is how
|
|
2733
|
+
// much a campaign drove. Named so the two cannot be
|
|
2734
|
+
// mistaken for one another in a segment builder.
|
|
2735
|
+
drawbridge_revenue: totals.gross || 0
|
|
2736
|
+
},
|
|
2737
|
+
// THE DRAWBRIDGE SEGMENTS THEY ARE IN, as a list property the
|
|
2738
|
+
// merchant builds Klaviyo segments on top of. Klaviyo owns no
|
|
2739
|
+
// writable membership — its segments are computed from rules,
|
|
2740
|
+
// so a profile cannot be put in one — and this is the nearest
|
|
2741
|
+
// honest equivalent.
|
|
2742
|
+
//
|
|
2743
|
+
// WHOLESALE, WHICH IS WHAT MAKES IT SELF-HEALING. Drawbridge
|
|
2744
|
+
// segments are dynamic and nothing dispatches a leave event —
|
|
2745
|
+
// only segment.contact.add exists — so a property that was
|
|
2746
|
+
// only ever appended to would outlive the membership that
|
|
2747
|
+
// earned it. Rewriting the whole array each run means every
|
|
2748
|
+
// sync corrects whatever the last one got wrong, with no
|
|
2749
|
+
// removal path to build.
|
|
2750
|
+
//
|
|
2751
|
+
// `segments` is null when the run carried no contact document,
|
|
2752
|
+
// meaning nobody looked — different from [], which means they
|
|
2753
|
+
// are in none. Null omits the key and merge leaves it alone.
|
|
2754
|
+
...segments && { drawbridge_segments: segments.map((entry) => entry.title).filter(Boolean) }
|
|
2728
2755
|
}
|
|
2729
2756
|
},
|
|
2730
2757
|
type: "profile"
|
|
@@ -3016,7 +3043,7 @@ var api3 = async (path, { dc, fetcher = fetch, method = "GET", payload, token })
|
|
|
3016
3043
|
{ status: response.status }
|
|
3017
3044
|
);
|
|
3018
3045
|
}
|
|
3019
|
-
return response.json();
|
|
3046
|
+
return response.status === 204 ? null : response.json();
|
|
3020
3047
|
};
|
|
3021
3048
|
var subscriberHash = (email) => (0, import_node_crypto3.createHash)("md5").update(String(email).trim().toLowerCase()).digest("hex");
|
|
3022
3049
|
var mailchimp_default2 = {
|
|
@@ -3150,7 +3177,7 @@ var mailchimp_default2 = {
|
|
|
3150
3177
|
// PUT /lists/{list_id}/members/{subscriber_hash} — an UPSERT, which is
|
|
3151
3178
|
// why there is no create-or-update branch here. Quoted from Mailchimp's
|
|
3152
3179
|
// Marketing API reference for the list-members resource.
|
|
3153
|
-
sync: async ({ lead, settings, suppressed, token }, { fetcher } = {}) => {
|
|
3180
|
+
sync: async ({ connection: connection2, lead, segments, settings, suppressed, token }, { fetcher, read } = {}) => {
|
|
3154
3181
|
var _a, _b;
|
|
3155
3182
|
const audience = settings == null ? void 0 : settings.audience;
|
|
3156
3183
|
if (!audience) return { message: "No Mailchimp audience is chosen for this connection.", skipped: true };
|
|
@@ -3176,6 +3203,36 @@ var mailchimp_default2 = {
|
|
|
3176
3203
|
},
|
|
3177
3204
|
token
|
|
3178
3205
|
});
|
|
3206
|
+
if (segments && read) {
|
|
3207
|
+
const owned = await read.aggregate({
|
|
3208
|
+
collection: "segment",
|
|
3209
|
+
pipeline: [
|
|
3210
|
+
{
|
|
3211
|
+
$match: {
|
|
3212
|
+
organization: connection2 == null ? void 0 : connection2.organization,
|
|
3213
|
+
system: { $ne: true }
|
|
3214
|
+
}
|
|
3215
|
+
},
|
|
3216
|
+
{
|
|
3217
|
+
$project: { _id: 0, title: 1 }
|
|
3218
|
+
}
|
|
3219
|
+
]
|
|
3220
|
+
});
|
|
3221
|
+
const joined = new Set(segments.map((entry) => entry.title));
|
|
3222
|
+
const tags = (owned || []).map((entry) => entry.title).filter(Boolean).map((title) => ({
|
|
3223
|
+
name: "Drawbridge: " + title,
|
|
3224
|
+
status: joined.has(title) ? "active" : "inactive"
|
|
3225
|
+
}));
|
|
3226
|
+
if (tags.length > 0) {
|
|
3227
|
+
await api3("/lists/" + audience + "/members/" + hash + "/tags", {
|
|
3228
|
+
dc: settings == null ? void 0 : settings.dc,
|
|
3229
|
+
fetcher,
|
|
3230
|
+
method: "POST",
|
|
3231
|
+
payload: { tags },
|
|
3232
|
+
token
|
|
3233
|
+
});
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3179
3236
|
return {
|
|
3180
3237
|
// Merged into `context` for later steps in this run.
|
|
3181
3238
|
context: { mailchimpMemberId: (member == null ? void 0 : member.id) || hash },
|
|
@@ -4863,9 +4920,14 @@ var webhook_default = {
|
|
|
4863
4920
|
"Generate a signing secret and Drawbridge signs every request with it. Your endpoint recomputes the signature to confirm each payload genuinely came from Drawbridge before acting on it."
|
|
4864
4921
|
],
|
|
4865
4922
|
excerpt: "Sign outgoing webhook payloads with an HMAC secret to verify authenticity.",
|
|
4923
|
+
// SAYS WHERE THE URL GOES. Connecting only mints a secret, so a merchant
|
|
4924
|
+
// following this had nowhere to put the endpoint they came to configure —
|
|
4925
|
+
// the destination lives on the step (see steps.webhook.send.settings), and
|
|
4926
|
+
// the guide never said so.
|
|
4866
4927
|
guide: [
|
|
4867
4928
|
"Press Connect. Drawbridge generates a signing secret and shows it here.",
|
|
4868
4929
|
"Copy the secret into your own endpoint.",
|
|
4930
|
+
"Add a Send webhook step to a workflow and put your endpoint URL on it. The URL belongs to the step rather than the connection, so one connection can serve several endpoints.",
|
|
4869
4931
|
"On each request, compute HMAC-SHA256 of the raw body using the secret and compare it against the X-Drawbridge-Signature header before acting on the payload."
|
|
4870
4932
|
]
|
|
4871
4933
|
},
|
package/dist/providers.js
CHANGED
|
@@ -199,6 +199,7 @@ var HOOK_PROPS = Object.freeze([
|
|
|
199
199
|
"scope",
|
|
200
200
|
"search",
|
|
201
201
|
"secret",
|
|
202
|
+
"segments",
|
|
202
203
|
"settings",
|
|
203
204
|
"sort",
|
|
204
205
|
"step",
|
|
@@ -2528,10 +2529,15 @@ var klaviyo_default2 = {
|
|
|
2528
2529
|
excerpt: "Sync the contacts your campaigns collect into a Klaviyo list.",
|
|
2529
2530
|
// HOW TO CONNECT, in the merchant's words. Was `setup`, which nothing
|
|
2530
2531
|
// rendered — four useful instructions no component displayed.
|
|
2532
|
+
// NAMES THE LIST STEP, because `status` below gates on it: a grant with no
|
|
2533
|
+
// list chosen sits at Pending, and this told the merchant it would show
|
|
2534
|
+
// Active. The one required action was the one action the guide omitted, so
|
|
2535
|
+
// anybody following it landed on a connection that looked broken.
|
|
2531
2536
|
guide: [
|
|
2532
2537
|
"Press Connect. Drawbridge sends you to Klaviyo to approve access.",
|
|
2533
2538
|
"Sign in to Klaviyo if you are not already, and choose the account to connect.",
|
|
2534
|
-
"Approve the permissions Klaviyo lists. You
|
|
2539
|
+
"Approve the permissions Klaviyo lists. You come back here to pick the list your contacts should sync into.",
|
|
2540
|
+
"The connection shows Pending until you pick a list, then Active.",
|
|
2535
2541
|
"You can revoke access at any time from Klaviyo, under Integrations."
|
|
2536
2542
|
]
|
|
2537
2543
|
},
|
|
@@ -2653,14 +2659,15 @@ var klaviyo_default2 = {
|
|
|
2653
2659
|
// Not yet. Suppression syncs an opt-out as unsubscribed, which is a
|
|
2654
2660
|
// different thing from deleting the profile.
|
|
2655
2661
|
remove: false,
|
|
2656
|
-
sync: async ({
|
|
2662
|
+
sync: async ({ context, lead, segments, settings, suppressed, token }, { fetcher } = {}) => {
|
|
2657
2663
|
var _a, _b, _c;
|
|
2658
2664
|
const list = settings == null ? void 0 : settings.list;
|
|
2659
2665
|
if (!list) return { message: "No Klaviyo list is chosen for this connection.", skipped: true };
|
|
2660
2666
|
const 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);
|
|
2661
2667
|
if (!email) return { message: "That lead has no email address to sync.", skipped: true };
|
|
2662
|
-
const
|
|
2663
|
-
const
|
|
2668
|
+
const person = (context == null ? void 0 : context.contact) || null;
|
|
2669
|
+
const totals = (person == null ? void 0 : person.totals) || {};
|
|
2670
|
+
const profile = await api2("/profile-import", {
|
|
2664
2671
|
fetcher,
|
|
2665
2672
|
method: "POST",
|
|
2666
2673
|
payload: {
|
|
@@ -2669,16 +2676,36 @@ var klaviyo_default2 = {
|
|
|
2669
2676
|
email,
|
|
2670
2677
|
...(lead == null ? void 0 : lead.name) && { first_name: String(lead.name).trim().split(/\s+/)[0] },
|
|
2671
2678
|
properties: {
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2679
|
+
...person && {
|
|
2680
|
+
drawbridge_campaigns: (person.campaigns || []).length,
|
|
2681
|
+
drawbridge_draws: totals.draws || 0,
|
|
2682
|
+
drawbridge_entries: totals.entries || 0,
|
|
2683
|
+
drawbridge_orders: totals.orders || 0,
|
|
2684
|
+
// Campaign-attributed, NOT lifetime. A merchant running
|
|
2685
|
+
// Shopify already has lifetime revenue in Klaviyo through
|
|
2686
|
+
// Klaviyo's own integration; what only we can say is how
|
|
2687
|
+
// much a campaign drove. Named so the two cannot be
|
|
2688
|
+
// mistaken for one another in a segment builder.
|
|
2689
|
+
drawbridge_revenue: totals.gross || 0
|
|
2690
|
+
},
|
|
2691
|
+
// THE DRAWBRIDGE SEGMENTS THEY ARE IN, as a list property the
|
|
2692
|
+
// merchant builds Klaviyo segments on top of. Klaviyo owns no
|
|
2693
|
+
// writable membership — its segments are computed from rules,
|
|
2694
|
+
// so a profile cannot be put in one — and this is the nearest
|
|
2695
|
+
// honest equivalent.
|
|
2696
|
+
//
|
|
2697
|
+
// WHOLESALE, WHICH IS WHAT MAKES IT SELF-HEALING. Drawbridge
|
|
2698
|
+
// segments are dynamic and nothing dispatches a leave event —
|
|
2699
|
+
// only segment.contact.add exists — so a property that was
|
|
2700
|
+
// only ever appended to would outlive the membership that
|
|
2701
|
+
// earned it. Rewriting the whole array each run means every
|
|
2702
|
+
// sync corrects whatever the last one got wrong, with no
|
|
2703
|
+
// removal path to build.
|
|
2704
|
+
//
|
|
2705
|
+
// `segments` is null when the run carried no contact document,
|
|
2706
|
+
// meaning nobody looked — different from [], which means they
|
|
2707
|
+
// are in none. Null omits the key and merge leaves it alone.
|
|
2708
|
+
...segments && { drawbridge_segments: segments.map((entry) => entry.title).filter(Boolean) }
|
|
2682
2709
|
}
|
|
2683
2710
|
},
|
|
2684
2711
|
type: "profile"
|
|
@@ -2970,7 +2997,7 @@ var api3 = async (path, { dc, fetcher = fetch, method = "GET", payload, token })
|
|
|
2970
2997
|
{ status: response.status }
|
|
2971
2998
|
);
|
|
2972
2999
|
}
|
|
2973
|
-
return response.json();
|
|
3000
|
+
return response.status === 204 ? null : response.json();
|
|
2974
3001
|
};
|
|
2975
3002
|
var subscriberHash = (email) => createHash2("md5").update(String(email).trim().toLowerCase()).digest("hex");
|
|
2976
3003
|
var mailchimp_default2 = {
|
|
@@ -3104,7 +3131,7 @@ var mailchimp_default2 = {
|
|
|
3104
3131
|
// PUT /lists/{list_id}/members/{subscriber_hash} — an UPSERT, which is
|
|
3105
3132
|
// why there is no create-or-update branch here. Quoted from Mailchimp's
|
|
3106
3133
|
// Marketing API reference for the list-members resource.
|
|
3107
|
-
sync: async ({ lead, settings, suppressed, token }, { fetcher } = {}) => {
|
|
3134
|
+
sync: async ({ connection: connection2, lead, segments, settings, suppressed, token }, { fetcher, read } = {}) => {
|
|
3108
3135
|
var _a, _b;
|
|
3109
3136
|
const audience = settings == null ? void 0 : settings.audience;
|
|
3110
3137
|
if (!audience) return { message: "No Mailchimp audience is chosen for this connection.", skipped: true };
|
|
@@ -3130,6 +3157,36 @@ var mailchimp_default2 = {
|
|
|
3130
3157
|
},
|
|
3131
3158
|
token
|
|
3132
3159
|
});
|
|
3160
|
+
if (segments && read) {
|
|
3161
|
+
const owned = await read.aggregate({
|
|
3162
|
+
collection: "segment",
|
|
3163
|
+
pipeline: [
|
|
3164
|
+
{
|
|
3165
|
+
$match: {
|
|
3166
|
+
organization: connection2 == null ? void 0 : connection2.organization,
|
|
3167
|
+
system: { $ne: true }
|
|
3168
|
+
}
|
|
3169
|
+
},
|
|
3170
|
+
{
|
|
3171
|
+
$project: { _id: 0, title: 1 }
|
|
3172
|
+
}
|
|
3173
|
+
]
|
|
3174
|
+
});
|
|
3175
|
+
const joined = new Set(segments.map((entry) => entry.title));
|
|
3176
|
+
const tags = (owned || []).map((entry) => entry.title).filter(Boolean).map((title) => ({
|
|
3177
|
+
name: "Drawbridge: " + title,
|
|
3178
|
+
status: joined.has(title) ? "active" : "inactive"
|
|
3179
|
+
}));
|
|
3180
|
+
if (tags.length > 0) {
|
|
3181
|
+
await api3("/lists/" + audience + "/members/" + hash + "/tags", {
|
|
3182
|
+
dc: settings == null ? void 0 : settings.dc,
|
|
3183
|
+
fetcher,
|
|
3184
|
+
method: "POST",
|
|
3185
|
+
payload: { tags },
|
|
3186
|
+
token
|
|
3187
|
+
});
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3133
3190
|
return {
|
|
3134
3191
|
// Merged into `context` for later steps in this run.
|
|
3135
3192
|
context: { mailchimpMemberId: (member == null ? void 0 : member.id) || hash },
|
|
@@ -4817,9 +4874,14 @@ var webhook_default = {
|
|
|
4817
4874
|
"Generate a signing secret and Drawbridge signs every request with it. Your endpoint recomputes the signature to confirm each payload genuinely came from Drawbridge before acting on it."
|
|
4818
4875
|
],
|
|
4819
4876
|
excerpt: "Sign outgoing webhook payloads with an HMAC secret to verify authenticity.",
|
|
4877
|
+
// SAYS WHERE THE URL GOES. Connecting only mints a secret, so a merchant
|
|
4878
|
+
// following this had nowhere to put the endpoint they came to configure —
|
|
4879
|
+
// the destination lives on the step (see steps.webhook.send.settings), and
|
|
4880
|
+
// the guide never said so.
|
|
4820
4881
|
guide: [
|
|
4821
4882
|
"Press Connect. Drawbridge generates a signing secret and shows it here.",
|
|
4822
4883
|
"Copy the secret into your own endpoint.",
|
|
4884
|
+
"Add a Send webhook step to a workflow and put your endpoint URL on it. The URL belongs to the step rather than the connection, so one connection can serve several endpoints.",
|
|
4823
4885
|
"On each request, compute HMAC-SHA256 of the raw body using the secret and compare it against the X-Drawbridge-Signature header before acting on the payload."
|
|
4824
4886
|
]
|
|
4825
4887
|
},
|
package/package.json
CHANGED