@drawbridge/drawbridge-utils 0.0.116 → 0.0.118
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 +293 -294
- package/dist/connections/index.d.cts +344 -316
- package/dist/connections/index.d.ts +344 -316
- package/dist/connections/index.js +293 -294
- package/dist/providers.cjs +3336 -0
- package/dist/providers.d.cts +317 -0
- package/dist/providers.d.ts +317 -0
- package/dist/providers.js +3291 -0
- package/package.json +6 -1
|
@@ -635,6 +635,197 @@ var attentive_default2 = {
|
|
|
635
635
|
title: "Attentive"
|
|
636
636
|
};
|
|
637
637
|
|
|
638
|
+
// lib/http.js
|
|
639
|
+
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
640
|
+
var request = async ({
|
|
641
|
+
body,
|
|
642
|
+
headers = {},
|
|
643
|
+
method = "GET",
|
|
644
|
+
query,
|
|
645
|
+
timeout = DEFAULT_TIMEOUT_MS,
|
|
646
|
+
type = "json",
|
|
647
|
+
url
|
|
648
|
+
}) => {
|
|
649
|
+
const fullUrl = new URL(url);
|
|
650
|
+
if (query) {
|
|
651
|
+
Object.entries(query).forEach(([k, v]) => fullUrl.searchParams.set(k, v));
|
|
652
|
+
}
|
|
653
|
+
;
|
|
654
|
+
const isForm = type === "form";
|
|
655
|
+
const response = await fetch(fullUrl.toString(), {
|
|
656
|
+
method,
|
|
657
|
+
headers: {
|
|
658
|
+
"Content-Type": isForm ? "application/x-www-form-urlencoded" : "application/json",
|
|
659
|
+
...headers
|
|
660
|
+
},
|
|
661
|
+
signal: AbortSignal.timeout(timeout),
|
|
662
|
+
...body !== void 0 && {
|
|
663
|
+
body: isForm ? new URLSearchParams(body).toString() : JSON.stringify(body)
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
if (!response.ok) {
|
|
667
|
+
const text2 = await response.text().catch(() => "");
|
|
668
|
+
const error = new Error(text2 || response.statusText);
|
|
669
|
+
error.status = response.status;
|
|
670
|
+
throw error;
|
|
671
|
+
}
|
|
672
|
+
;
|
|
673
|
+
const text = await response.text();
|
|
674
|
+
try {
|
|
675
|
+
return text ? JSON.parse(text) : null;
|
|
676
|
+
} catch {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// lib/hubspot.js
|
|
682
|
+
var HUBSPOT_BASE = "https://api.hubapi.com";
|
|
683
|
+
var hubspotRequest = ({ body, fetcher, method, path, query, token }) => {
|
|
684
|
+
return (fetcher || request)({
|
|
685
|
+
body,
|
|
686
|
+
headers: {
|
|
687
|
+
"Authorization": "Bearer " + (token || process.env.HUBSPOT_ACCESS_TOKEN)
|
|
688
|
+
},
|
|
689
|
+
method,
|
|
690
|
+
query,
|
|
691
|
+
url: HUBSPOT_BASE + path
|
|
692
|
+
});
|
|
693
|
+
};
|
|
694
|
+
var UTM_PROPERTIES = {
|
|
695
|
+
campaign: "utm_campaign",
|
|
696
|
+
content: "utm_content",
|
|
697
|
+
id: "utm_id",
|
|
698
|
+
medium: "utm_medium",
|
|
699
|
+
source: "utm_source",
|
|
700
|
+
term: "utm_term"
|
|
701
|
+
};
|
|
702
|
+
var CLICK_PROPERTIES = {
|
|
703
|
+
fbclid: "hs_facebook_click_id",
|
|
704
|
+
gclid: "hs_google_click_id",
|
|
705
|
+
liFatId: "hs_linkedin_click_id",
|
|
706
|
+
msclkid: "hs_bing_click_id",
|
|
707
|
+
ttclid: "hs_tiktok_click_id"
|
|
708
|
+
};
|
|
709
|
+
var DROPPABLE = new Set(Object.values(UTM_PROPERTIES));
|
|
710
|
+
var isUtmProperty = (key) => DROPPABLE.has(key);
|
|
711
|
+
var toProperties = ({ email, firstName, lastName, utm }) => {
|
|
712
|
+
var _a;
|
|
713
|
+
const properties = {};
|
|
714
|
+
if (email !== void 0) properties.email = email;
|
|
715
|
+
if (firstName !== void 0) properties.firstname = firstName;
|
|
716
|
+
if (lastName !== void 0) properties.lastname = lastName;
|
|
717
|
+
if (utm) {
|
|
718
|
+
for (const [key, property] of Object.entries(UTM_PROPERTIES)) {
|
|
719
|
+
if (utm[key]) properties[property] = utm[key];
|
|
720
|
+
}
|
|
721
|
+
;
|
|
722
|
+
for (const [key, property] of Object.entries(CLICK_PROPERTIES)) {
|
|
723
|
+
if ((_a = utm.click) == null ? void 0 : _a[key]) properties[property] = utm.click[key];
|
|
724
|
+
}
|
|
725
|
+
;
|
|
726
|
+
}
|
|
727
|
+
;
|
|
728
|
+
return properties;
|
|
729
|
+
};
|
|
730
|
+
var send = async ({ doc, fetcher, method, path, token }) => {
|
|
731
|
+
const properties = toProperties(doc);
|
|
732
|
+
try {
|
|
733
|
+
return await hubspotRequest({
|
|
734
|
+
body: { properties },
|
|
735
|
+
fetcher,
|
|
736
|
+
method,
|
|
737
|
+
path,
|
|
738
|
+
token
|
|
739
|
+
});
|
|
740
|
+
} catch (error) {
|
|
741
|
+
const enriched = Object.keys(properties).some(isUtmProperty);
|
|
742
|
+
if ((error == null ? void 0 : error.status) !== 400 || !enriched) throw error;
|
|
743
|
+
return await hubspotRequest({
|
|
744
|
+
body: {
|
|
745
|
+
properties: Object.fromEntries(
|
|
746
|
+
Object.entries(properties).filter(([key]) => !isUtmProperty(key))
|
|
747
|
+
)
|
|
748
|
+
},
|
|
749
|
+
fetcher,
|
|
750
|
+
method,
|
|
751
|
+
path,
|
|
752
|
+
token
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
var lookup = async ({ email, fetcher, token }) => {
|
|
757
|
+
var _a, _b;
|
|
758
|
+
if (!token || !email) return;
|
|
759
|
+
try {
|
|
760
|
+
const body = await hubspotRequest({
|
|
761
|
+
body: {
|
|
762
|
+
filterGroups: [
|
|
763
|
+
{
|
|
764
|
+
filters: [
|
|
765
|
+
{
|
|
766
|
+
operator: "EQ",
|
|
767
|
+
propertyName: "email",
|
|
768
|
+
value: email
|
|
769
|
+
}
|
|
770
|
+
]
|
|
771
|
+
}
|
|
772
|
+
],
|
|
773
|
+
limit: 1,
|
|
774
|
+
properties: ["email"]
|
|
775
|
+
},
|
|
776
|
+
fetcher,
|
|
777
|
+
method: "POST",
|
|
778
|
+
path: "/crm/v3/objects/contacts/search",
|
|
779
|
+
token
|
|
780
|
+
});
|
|
781
|
+
return (_b = (_a = body == null ? void 0 : body.results) == null ? void 0 : _a[0]) == null ? void 0 : _b.id;
|
|
782
|
+
} catch (error) {
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
var contacts = {
|
|
786
|
+
// FORGET A CONTACT, by id or by email. Account deletion — the caller had
|
|
787
|
+
// to search then remove, which is one round trip it should not have to
|
|
788
|
+
// know about.
|
|
789
|
+
remove: async ({ email, fetcher, id, token }) => {
|
|
790
|
+
const key = token || process.env.HUBSPOT_ACCESS_TOKEN;
|
|
791
|
+
if (!key) return;
|
|
792
|
+
const contact = id || await lookup({ email, fetcher, token: key });
|
|
793
|
+
if (!contact) return;
|
|
794
|
+
return hubspotRequest({
|
|
795
|
+
fetcher,
|
|
796
|
+
method: "DELETE",
|
|
797
|
+
path: "/crm/v3/objects/contacts/" + contact,
|
|
798
|
+
token: key
|
|
799
|
+
});
|
|
800
|
+
},
|
|
801
|
+
// Connect an account to its contact by email, creating it if absent, and
|
|
802
|
+
// return the contact id. Unlike SendGrid, HubSpot renames a contact's
|
|
803
|
+
// email in place, so an email change is a plain PATCH on the cached id —
|
|
804
|
+
// no delete-old-then-create-new.
|
|
805
|
+
//
|
|
806
|
+
// Prefer the cached hubspotId; fall back to a search; create last.
|
|
807
|
+
sync: async ({ doc, fetcher, token }) => {
|
|
808
|
+
var _a, _b;
|
|
809
|
+
const key = token || process.env.HUBSPOT_ACCESS_TOKEN;
|
|
810
|
+
if (!key) return;
|
|
811
|
+
if (doc == null ? void 0 : doc.hubspotId) {
|
|
812
|
+
try {
|
|
813
|
+
return (_a = await send({ doc, fetcher, method: "PATCH", path: "/crm/v3/objects/contacts/" + doc.hubspotId, token: key })) == null ? void 0 : _a.id;
|
|
814
|
+
} catch (error) {
|
|
815
|
+
if ((error == null ? void 0 : error.status) !== 404) throw error;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
const existing = await lookup({ email: doc == null ? void 0 : doc.email, fetcher, token: key });
|
|
819
|
+
return (_b = await send({
|
|
820
|
+
doc,
|
|
821
|
+
fetcher,
|
|
822
|
+
method: existing ? "PATCH" : "POST",
|
|
823
|
+
path: existing ? "/crm/v3/objects/contacts/" + existing : "/crm/v3/objects/contacts",
|
|
824
|
+
token: key
|
|
825
|
+
})) == null ? void 0 : _b.id;
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
|
|
638
829
|
// lib/connections/icons/drawbridge.js
|
|
639
830
|
var drawbridge_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
640
831
|
<rect width="500" height="500" fill="#BAEC5F"/>
|
|
@@ -1272,7 +1463,15 @@ var drawbridge_default2 = {
|
|
|
1272
1463
|
token: false
|
|
1273
1464
|
},
|
|
1274
1465
|
commerce: false,
|
|
1275
|
-
|
|
1466
|
+
// DRAWBRIDGE'S OWN CRM. Not a merchant's — this keeps our HubSpot portal in
|
|
1467
|
+
// step with account signups, and drawbridge-sync's user stream calls it.
|
|
1468
|
+
//
|
|
1469
|
+
// A real implementation here rather than `{}` because the bodies are pure
|
|
1470
|
+
// HTTP against a token: no controller, no queue, nothing that would have to
|
|
1471
|
+
// live in the service. lib/hubspot.js holds them, beside lib/sendgrid.js
|
|
1472
|
+
// and lib/twilio.js, which are the same kind of thing — vendor clients for
|
|
1473
|
+
// accounts DRAWBRIDGE holds rather than ones a merchant connects.
|
|
1474
|
+
contacts,
|
|
1276
1475
|
email: {
|
|
1277
1476
|
digest: {},
|
|
1278
1477
|
// To organization members. NEVER suppressed and never billed: an
|
|
@@ -1297,6 +1496,17 @@ var drawbridge_default2 = {
|
|
|
1297
1496
|
icon: drawbridge_default,
|
|
1298
1497
|
// PRIVATE: never in the catalog, always available to the builder.
|
|
1299
1498
|
private: true,
|
|
1499
|
+
// NOTHING, and HUBSPOT_ACCESS_TOKEN in particular must not be here.
|
|
1500
|
+
//
|
|
1501
|
+
// `requires` gates AVAILABILITY: a name in it that is unset removes the whole
|
|
1502
|
+
// connection. This one is private and contributes every base workflow step —
|
|
1503
|
+
// email.send, sms.send, segment.sync — so gating it on a CRM token would take
|
|
1504
|
+
// all of them away from any deployment without a HubSpot portal, to protect a
|
|
1505
|
+
// sync that is best-effort and already no-ops without a token.
|
|
1506
|
+
//
|
|
1507
|
+
// The test 'a vendor is only available when its environment is configured'
|
|
1508
|
+
// caught exactly that: availableConnections({}) went from [ 'drawbridge' ] to
|
|
1509
|
+
// empty the moment this was added.
|
|
1300
1510
|
requires: [],
|
|
1301
1511
|
slug: "drawbridge",
|
|
1302
1512
|
// Always on. There is no credential that could go bad and no configuration a
|
|
@@ -1420,251 +1630,6 @@ var drawbridge_default2 = {
|
|
|
1420
1630
|
title: "Drawbridge"
|
|
1421
1631
|
};
|
|
1422
1632
|
|
|
1423
|
-
// lib/http.js
|
|
1424
|
-
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
1425
|
-
var request = async ({
|
|
1426
|
-
body,
|
|
1427
|
-
headers = {},
|
|
1428
|
-
method = "GET",
|
|
1429
|
-
query,
|
|
1430
|
-
timeout = DEFAULT_TIMEOUT_MS,
|
|
1431
|
-
type = "json",
|
|
1432
|
-
url
|
|
1433
|
-
}) => {
|
|
1434
|
-
const fullUrl = new URL(url);
|
|
1435
|
-
if (query) {
|
|
1436
|
-
Object.entries(query).forEach(([k, v]) => fullUrl.searchParams.set(k, v));
|
|
1437
|
-
}
|
|
1438
|
-
;
|
|
1439
|
-
const isForm = type === "form";
|
|
1440
|
-
const response = await fetch(fullUrl.toString(), {
|
|
1441
|
-
method,
|
|
1442
|
-
headers: {
|
|
1443
|
-
"Content-Type": isForm ? "application/x-www-form-urlencoded" : "application/json",
|
|
1444
|
-
...headers
|
|
1445
|
-
},
|
|
1446
|
-
signal: AbortSignal.timeout(timeout),
|
|
1447
|
-
...body !== void 0 && {
|
|
1448
|
-
body: isForm ? new URLSearchParams(body).toString() : JSON.stringify(body)
|
|
1449
|
-
}
|
|
1450
|
-
});
|
|
1451
|
-
if (!response.ok) {
|
|
1452
|
-
const text2 = await response.text().catch(() => "");
|
|
1453
|
-
const error = new Error(text2 || response.statusText);
|
|
1454
|
-
error.status = response.status;
|
|
1455
|
-
throw error;
|
|
1456
|
-
}
|
|
1457
|
-
;
|
|
1458
|
-
const text = await response.text();
|
|
1459
|
-
try {
|
|
1460
|
-
return text ? JSON.parse(text) : null;
|
|
1461
|
-
} catch {
|
|
1462
|
-
return null;
|
|
1463
|
-
}
|
|
1464
|
-
};
|
|
1465
|
-
|
|
1466
|
-
// lib/connections/hubspot.js
|
|
1467
|
-
var HUBSPOT_BASE = "https://api.hubapi.com";
|
|
1468
|
-
var hubspotRequest = ({ body, fetcher, method, path, query, token }) => {
|
|
1469
|
-
return (fetcher || request)({
|
|
1470
|
-
body,
|
|
1471
|
-
headers: {
|
|
1472
|
-
"Authorization": "Bearer " + (token || process.env.HUBSPOT_ACCESS_TOKEN)
|
|
1473
|
-
},
|
|
1474
|
-
method,
|
|
1475
|
-
query,
|
|
1476
|
-
url: HUBSPOT_BASE + path
|
|
1477
|
-
});
|
|
1478
|
-
};
|
|
1479
|
-
var UTM_PROPERTIES = {
|
|
1480
|
-
campaign: "utm_campaign",
|
|
1481
|
-
content: "utm_content",
|
|
1482
|
-
id: "utm_id",
|
|
1483
|
-
medium: "utm_medium",
|
|
1484
|
-
source: "utm_source",
|
|
1485
|
-
term: "utm_term"
|
|
1486
|
-
};
|
|
1487
|
-
var CLICK_PROPERTIES = {
|
|
1488
|
-
fbclid: "hs_facebook_click_id",
|
|
1489
|
-
gclid: "hs_google_click_id",
|
|
1490
|
-
liFatId: "hs_linkedin_click_id",
|
|
1491
|
-
msclkid: "hs_bing_click_id",
|
|
1492
|
-
ttclid: "hs_tiktok_click_id"
|
|
1493
|
-
};
|
|
1494
|
-
var DROPPABLE = new Set(Object.values(UTM_PROPERTIES));
|
|
1495
|
-
var isUtmProperty = (key) => DROPPABLE.has(key);
|
|
1496
|
-
var toProperties = ({ email, firstName, lastName, utm }) => {
|
|
1497
|
-
var _a;
|
|
1498
|
-
const properties = {};
|
|
1499
|
-
if (email !== void 0) properties.email = email;
|
|
1500
|
-
if (firstName !== void 0) properties.firstname = firstName;
|
|
1501
|
-
if (lastName !== void 0) properties.lastname = lastName;
|
|
1502
|
-
if (utm) {
|
|
1503
|
-
for (const [key, property] of Object.entries(UTM_PROPERTIES)) {
|
|
1504
|
-
if (utm[key]) properties[property] = utm[key];
|
|
1505
|
-
}
|
|
1506
|
-
;
|
|
1507
|
-
for (const [key, property] of Object.entries(CLICK_PROPERTIES)) {
|
|
1508
|
-
if ((_a = utm.click) == null ? void 0 : _a[key]) properties[property] = utm.click[key];
|
|
1509
|
-
}
|
|
1510
|
-
;
|
|
1511
|
-
}
|
|
1512
|
-
;
|
|
1513
|
-
return properties;
|
|
1514
|
-
};
|
|
1515
|
-
var send = async ({ doc, fetcher, method, path, token }) => {
|
|
1516
|
-
const properties = toProperties(doc);
|
|
1517
|
-
try {
|
|
1518
|
-
return await hubspotRequest({
|
|
1519
|
-
body: { properties },
|
|
1520
|
-
fetcher,
|
|
1521
|
-
method,
|
|
1522
|
-
path,
|
|
1523
|
-
token
|
|
1524
|
-
});
|
|
1525
|
-
} catch (error) {
|
|
1526
|
-
const enriched = Object.keys(properties).some(isUtmProperty);
|
|
1527
|
-
if ((error == null ? void 0 : error.status) !== 400 || !enriched) throw error;
|
|
1528
|
-
return await hubspotRequest({
|
|
1529
|
-
body: {
|
|
1530
|
-
properties: Object.fromEntries(
|
|
1531
|
-
Object.entries(properties).filter(([key]) => !isUtmProperty(key))
|
|
1532
|
-
)
|
|
1533
|
-
},
|
|
1534
|
-
fetcher,
|
|
1535
|
-
method,
|
|
1536
|
-
path,
|
|
1537
|
-
token
|
|
1538
|
-
});
|
|
1539
|
-
}
|
|
1540
|
-
};
|
|
1541
|
-
var lookup = async ({ email, fetcher, token }) => {
|
|
1542
|
-
var _a, _b;
|
|
1543
|
-
if (!token || !email) return;
|
|
1544
|
-
try {
|
|
1545
|
-
const body = await hubspotRequest({
|
|
1546
|
-
body: {
|
|
1547
|
-
filterGroups: [
|
|
1548
|
-
{
|
|
1549
|
-
filters: [
|
|
1550
|
-
{
|
|
1551
|
-
operator: "EQ",
|
|
1552
|
-
propertyName: "email",
|
|
1553
|
-
value: email
|
|
1554
|
-
}
|
|
1555
|
-
]
|
|
1556
|
-
}
|
|
1557
|
-
],
|
|
1558
|
-
limit: 1,
|
|
1559
|
-
properties: ["email"]
|
|
1560
|
-
},
|
|
1561
|
-
fetcher,
|
|
1562
|
-
method: "POST",
|
|
1563
|
-
path: "/crm/v3/objects/contacts/search",
|
|
1564
|
-
token
|
|
1565
|
-
});
|
|
1566
|
-
return (_b = (_a = body == null ? void 0 : body.results) == null ? void 0 : _a[0]) == null ? void 0 : _b.id;
|
|
1567
|
-
} catch (error) {
|
|
1568
|
-
}
|
|
1569
|
-
};
|
|
1570
|
-
var hubspot_default = {
|
|
1571
|
-
auth: {
|
|
1572
|
-
// A Private App token from our own portal. Nothing to connect, nothing to
|
|
1573
|
-
// consent to, and no merchant involved.
|
|
1574
|
-
type: "none"
|
|
1575
|
-
},
|
|
1576
|
-
content: {
|
|
1577
|
-
confirm: "This connection is part of Drawbridge and cannot be disconnected.",
|
|
1578
|
-
description: [
|
|
1579
|
-
"Drawbridge keeps its own HubSpot portal in step with account signups, so the campaign a customer arrived on is on their contact record."
|
|
1580
|
-
],
|
|
1581
|
-
excerpt: "Drawbridge's own CRM sync.",
|
|
1582
|
-
guide: [
|
|
1583
|
-
"Nothing to do. This is internal to Drawbridge."
|
|
1584
|
-
]
|
|
1585
|
-
},
|
|
1586
|
-
exclusive: false,
|
|
1587
|
-
fields: [],
|
|
1588
|
-
group: "contacts",
|
|
1589
|
-
hooks: {
|
|
1590
|
-
auth: {
|
|
1591
|
-
connect: false,
|
|
1592
|
-
disconnect: false,
|
|
1593
|
-
probe: false,
|
|
1594
|
-
scopes: false,
|
|
1595
|
-
token: false
|
|
1596
|
-
},
|
|
1597
|
-
commerce: false,
|
|
1598
|
-
contacts: {
|
|
1599
|
-
// FORGET A CONTACT, by id or by email. Account deletion — the caller had
|
|
1600
|
-
// to search then remove, which is one round trip it should not have to
|
|
1601
|
-
// know about.
|
|
1602
|
-
remove: async ({ email, fetcher, id, token }) => {
|
|
1603
|
-
const key = token || process.env.HUBSPOT_ACCESS_TOKEN;
|
|
1604
|
-
if (!key) return;
|
|
1605
|
-
const contact = id || await lookup({ email, fetcher, token: key });
|
|
1606
|
-
if (!contact) return;
|
|
1607
|
-
return hubspotRequest({
|
|
1608
|
-
fetcher,
|
|
1609
|
-
method: "DELETE",
|
|
1610
|
-
path: "/crm/v3/objects/contacts/" + contact,
|
|
1611
|
-
token: key
|
|
1612
|
-
});
|
|
1613
|
-
},
|
|
1614
|
-
// Connect an account to its contact by email, creating it if absent, and
|
|
1615
|
-
// return the contact id. Unlike SendGrid, HubSpot renames a contact's
|
|
1616
|
-
// email in place, so an email change is a plain PATCH on the cached id —
|
|
1617
|
-
// no delete-old-then-create-new.
|
|
1618
|
-
//
|
|
1619
|
-
// Prefer the cached hubspotId; fall back to a search; create last.
|
|
1620
|
-
sync: async ({ doc, fetcher, token }) => {
|
|
1621
|
-
var _a, _b;
|
|
1622
|
-
const key = token || process.env.HUBSPOT_ACCESS_TOKEN;
|
|
1623
|
-
if (!key) return;
|
|
1624
|
-
if (doc == null ? void 0 : doc.hubspotId) {
|
|
1625
|
-
try {
|
|
1626
|
-
return (_a = await send({ doc, fetcher, method: "PATCH", path: "/crm/v3/objects/contacts/" + doc.hubspotId, token: key })) == null ? void 0 : _a.id;
|
|
1627
|
-
} catch (error) {
|
|
1628
|
-
if ((error == null ? void 0 : error.status) !== 404) throw error;
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
const existing = await lookup({ email: doc == null ? void 0 : doc.email, fetcher, token: key });
|
|
1632
|
-
return (_b = await send({
|
|
1633
|
-
doc,
|
|
1634
|
-
fetcher,
|
|
1635
|
-
method: existing ? "PATCH" : "POST",
|
|
1636
|
-
path: existing ? "/crm/v3/objects/contacts/" + existing : "/crm/v3/objects/contacts",
|
|
1637
|
-
token: key
|
|
1638
|
-
})) == null ? void 0 : _b.id;
|
|
1639
|
-
}
|
|
1640
|
-
},
|
|
1641
|
-
email: false,
|
|
1642
|
-
inbound: false,
|
|
1643
|
-
lifecycle: false,
|
|
1644
|
-
resources: {
|
|
1645
|
-
audiences: false,
|
|
1646
|
-
prices: false,
|
|
1647
|
-
products: false,
|
|
1648
|
-
promotions: false
|
|
1649
|
-
},
|
|
1650
|
-
segment: false,
|
|
1651
|
-
sms: false,
|
|
1652
|
-
webhook: false
|
|
1653
|
-
},
|
|
1654
|
-
icon: drawbridge_default,
|
|
1655
|
-
// Borrowed: the Drawbridge mark, because this is ours and never rendered.
|
|
1656
|
-
private: true,
|
|
1657
|
-
// Absent the token the hooks no-op, so a deployment without a portal simply
|
|
1658
|
-
// contributes nothing rather than failing.
|
|
1659
|
-
requires: ["HUBSPOT_ACCESS_TOKEN"],
|
|
1660
|
-
slug: "hubspot",
|
|
1661
|
-
status: () => "active",
|
|
1662
|
-
// No workflow steps. The hooks are called by the user stream, not the builder.
|
|
1663
|
-
steps: {},
|
|
1664
|
-
tasks: () => [],
|
|
1665
|
-
title: "HubSpot"
|
|
1666
|
-
};
|
|
1667
|
-
|
|
1668
1633
|
// lib/connections/icons/klaviyo.js
|
|
1669
1634
|
var klaviyo_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
1670
1635
|
<rect width="500" height="500" fill="white"/>
|
|
@@ -2126,46 +2091,59 @@ var mailchimp_default = `<svg width="500" height="500" viewBox="0 0 500 500" fil
|
|
|
2126
2091
|
</svg>`;
|
|
2127
2092
|
|
|
2128
2093
|
// lib/connections/mailchimp.js
|
|
2129
|
-
var base = (
|
|
2130
|
-
|
|
2131
|
-
if (!dc || dc === apiKey) throw new Error("That Mailchimp key carries no data centre suffix, so there is no host to call");
|
|
2094
|
+
var base = (dc) => {
|
|
2095
|
+
if (!dc) throw new Error("This Mailchimp connection has no data centre stored, so there is no host to call");
|
|
2132
2096
|
return "https://" + dc + ".api.mailchimp.com/3.0";
|
|
2133
2097
|
};
|
|
2134
2098
|
var mailchimp_default2 = {
|
|
2135
|
-
//
|
|
2136
|
-
//
|
|
2137
|
-
//
|
|
2099
|
+
// OAUTH 2, authorization code. Every url below is quoted from
|
|
2100
|
+
// mailchimp.com/developer/marketing/guides/access-user-data-oauth-2/ rather
|
|
2101
|
+
// than remembered.
|
|
2138
2102
|
//
|
|
2139
|
-
//
|
|
2140
|
-
//
|
|
2141
|
-
//
|
|
2103
|
+
// THE METADATA CALL IS MAILCHIMP'S QUIRK and cannot be skipped: an access
|
|
2104
|
+
// token alone cannot call the Marketing API, because every account lives
|
|
2105
|
+
// behind a data-centre prefix (us1, us19...) that only GET
|
|
2106
|
+
// login.mailchimp.com/oauth2/metadata returns — and every subsequent request
|
|
2107
|
+
// needs it in the HOST. That is why auth.connect below is a real function:
|
|
2108
|
+
// the standard exchange does not know where to send anything afterwards.
|
|
2142
2109
|
//
|
|
2143
|
-
//
|
|
2144
|
-
//
|
|
2145
|
-
//
|
|
2146
|
-
//
|
|
2110
|
+
// No PKCE. No scopes — the docs describe none. And no refresh token: "Mailchimp
|
|
2111
|
+
// Marketing access tokens do not expire, so you don't need to use a
|
|
2112
|
+
// refresh_token", so tokenSettings stores no expiry and isStale reads that as
|
|
2113
|
+
// nothing to refresh toward.
|
|
2147
2114
|
//
|
|
2148
|
-
//
|
|
2149
|
-
//
|
|
2150
|
-
//
|
|
2115
|
+
// auth.token stays false: the exchange is POST form-encoded with grant_type,
|
|
2116
|
+
// client_id, client_secret, redirect_uri and code, which is exactly the
|
|
2117
|
+
// runner's default — nothing to wrap.
|
|
2151
2118
|
auth: {
|
|
2152
|
-
|
|
2119
|
+
oauth: {
|
|
2120
|
+
client: {
|
|
2121
|
+
id: "MAILCHIMP_OAUTH_CLIENT_ID",
|
|
2122
|
+
secret: "MAILCHIMP_OAUTH_CLIENT_SECRET"
|
|
2123
|
+
},
|
|
2124
|
+
urls: {
|
|
2125
|
+
authorize: "https://login.mailchimp.com/oauth2/authorize",
|
|
2126
|
+
redirect: "/api/connection/mailchimp/callback",
|
|
2127
|
+
token: "https://login.mailchimp.com/oauth2/token"
|
|
2128
|
+
}
|
|
2129
|
+
},
|
|
2130
|
+
type: "oauth"
|
|
2153
2131
|
},
|
|
2154
2132
|
// EVERYTHING A MERCHANT READS. `errors` belongs in here rather than at the
|
|
2155
2133
|
// top level because the connection DOCUMENT carries its own `errors` array
|
|
2156
2134
|
// and the document is spread OVER the resolved manifest downstream — a
|
|
2157
2135
|
// top-level one would be replaced by that array and never render.
|
|
2158
2136
|
content: {
|
|
2159
|
-
confirm: "Disconnecting removes
|
|
2137
|
+
confirm: "Disconnecting removes Drawbridge's stored Mailchimp access. You can also remove Drawbridge from the Authorized Apps page in your Mailchimp account. Your contacts stay in both Drawbridge and Mailchimp \u2014 neither list is deleted.",
|
|
2160
2138
|
description: [
|
|
2161
2139
|
"Drawbridge no longer sends email through Mailchimp. Notification email now sends from Drawbridge itself, and verifying a domain under Networking in your organization settings puts your own brand in the from line.",
|
|
2162
|
-
"This connection is becoming the way your Drawbridge contacts sync into a Mailchimp audience. Audience syncing is not live yet, so
|
|
2140
|
+
"This connection is becoming the way your Drawbridge contacts sync into a Mailchimp audience. Audience syncing is not live yet, so connecting today does nothing except choose the audience it will use when it ships."
|
|
2163
2141
|
],
|
|
2164
2142
|
excerpt: "Sync your Drawbridge contacts into a Mailchimp audience.",
|
|
2165
2143
|
guide: [
|
|
2166
|
-
"
|
|
2167
|
-
"
|
|
2168
|
-
"
|
|
2144
|
+
"Press Connect. Drawbridge sends you to Mailchimp to approve access.",
|
|
2145
|
+
"Sign in to Mailchimp if you are not already, and choose the account to connect.",
|
|
2146
|
+
"You come back here to pick the audience your contacts should sync into."
|
|
2169
2147
|
]
|
|
2170
2148
|
},
|
|
2171
2149
|
// Mailchimp and SendGrid shared a group while they were SENDERS, where an org
|
|
@@ -2175,15 +2153,6 @@ var mailchimp_default2 = {
|
|
|
2175
2153
|
exclusive: false,
|
|
2176
2154
|
feature: "organization:connection:mailchimp",
|
|
2177
2155
|
fields: [
|
|
2178
|
-
{
|
|
2179
|
-
input: "password",
|
|
2180
|
-
key: "apiKey",
|
|
2181
|
-
label: "Mailchimp API key",
|
|
2182
|
-
message: "Your Mailchimp API key",
|
|
2183
|
-
placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022-us1",
|
|
2184
|
-
redact: true,
|
|
2185
|
-
required: true
|
|
2186
|
-
},
|
|
2187
2156
|
{
|
|
2188
2157
|
input: "select",
|
|
2189
2158
|
key: "audience",
|
|
@@ -2202,20 +2171,45 @@ var mailchimp_default2 = {
|
|
|
2202
2171
|
// contacts.sync are the first to flip.
|
|
2203
2172
|
hooks: {
|
|
2204
2173
|
auth: {
|
|
2205
|
-
//
|
|
2206
|
-
//
|
|
2207
|
-
//
|
|
2208
|
-
//
|
|
2209
|
-
//
|
|
2210
|
-
//
|
|
2211
|
-
|
|
2174
|
+
// WHERE THE ACCOUNT LIVES. Not enrichment — without this the connection
|
|
2175
|
+
// is unusable, because the Marketing API host is per-account and only
|
|
2176
|
+
// this call knows it. The callback merges what this returns into the
|
|
2177
|
+
// stored settings, which is how `dc` reaches every later request.
|
|
2178
|
+
//
|
|
2179
|
+
// The header here is `OAuth <token>`, not Bearer — that is specific to
|
|
2180
|
+
// the metadata endpoint. Marketing API calls take Bearer; see the
|
|
2181
|
+
// audiences hook.
|
|
2182
|
+
connect: async ({ fetcher = fetch, tokens }) => {
|
|
2183
|
+
const response = await fetcher("https://login.mailchimp.com/oauth2/metadata", {
|
|
2184
|
+
headers: {
|
|
2185
|
+
authorization: "OAuth " + (tokens == null ? void 0 : tokens.accessToken)
|
|
2186
|
+
},
|
|
2187
|
+
signal: AbortSignal.timeout(15e3)
|
|
2188
|
+
});
|
|
2189
|
+
if (!response.ok) {
|
|
2190
|
+
throw Object.assign(
|
|
2191
|
+
new Error("Mailchimp would not say which data centre this account is on (" + response.status + ")"),
|
|
2192
|
+
{ status: response.status }
|
|
2193
|
+
);
|
|
2194
|
+
}
|
|
2195
|
+
const body = await response.json();
|
|
2196
|
+
if (!(body == null ? void 0 : body.dc)) throw new Error("Mailchimp returned no data centre for this account");
|
|
2197
|
+
return { dc: body.dc };
|
|
2198
|
+
},
|
|
2199
|
+
// Nothing to call. A merchant revokes Drawbridge from Mailchimp's own
|
|
2200
|
+
// Authorized Apps page; the docs describe no revocation endpoint for us
|
|
2201
|
+
// to call on their behalf.
|
|
2212
2202
|
disconnect: false,
|
|
2213
2203
|
probe: false,
|
|
2214
2204
|
scopes: false,
|
|
2215
|
-
//
|
|
2216
|
-
//
|
|
2217
|
-
//
|
|
2218
|
-
|
|
2205
|
+
// The plain exchange. Mailchimp takes the client as FORM FIELDS
|
|
2206
|
+
// (grant_type, client_id, client_secret, redirect_uri, code), which is
|
|
2207
|
+
// the runner's default — so no `basic : true` as Klaviyo needs.
|
|
2208
|
+
//
|
|
2209
|
+
// build() requires an oauth manifest to name this explicitly rather than
|
|
2210
|
+
// letting it default, which caught this file declaring `false` on the
|
|
2211
|
+
// first import after the conversion.
|
|
2212
|
+
token: authToken
|
|
2219
2213
|
},
|
|
2220
2214
|
commerce: false,
|
|
2221
2215
|
contacts: { remove: false, sync: false },
|
|
@@ -2237,15 +2231,14 @@ var mailchimp_default2 = {
|
|
|
2237
2231
|
// successful — the same silent truncation Klaviyo has, at a different
|
|
2238
2232
|
// number. Paged against total_items so an account past a thousand still
|
|
2239
2233
|
// resolves.
|
|
2240
|
-
audiences: async ({ cursor, fetcher = fetch, limit = 100, search, settings }) => {
|
|
2241
|
-
const
|
|
2234
|
+
audiences: async ({ cursor, fetcher = fetch, limit = 100, search, settings, token }) => {
|
|
2235
|
+
const dc = settings == null ? void 0 : settings.dc;
|
|
2242
2236
|
const count = Math.min(limit, 1e3);
|
|
2243
2237
|
const offset = Number(cursor || 0);
|
|
2244
2238
|
const response = await fetcher(
|
|
2245
|
-
base(
|
|
2239
|
+
base(dc) + "/lists?count=" + count + "&offset=" + offset + "&fields=lists.id,lists.name,total_items",
|
|
2246
2240
|
{
|
|
2247
|
-
|
|
2248
|
-
headers: { authorization: "Basic " + Buffer.from("drawbridge:" + key).toString("base64") },
|
|
2241
|
+
headers: { authorization: "Bearer " + token },
|
|
2249
2242
|
signal: AbortSignal.timeout(15e3)
|
|
2250
2243
|
}
|
|
2251
2244
|
);
|
|
@@ -2277,6 +2270,13 @@ var mailchimp_default2 = {
|
|
|
2277
2270
|
webhook: false
|
|
2278
2271
|
},
|
|
2279
2272
|
icon: mailchimp_default,
|
|
2273
|
+
// The OAuth client this deployment registered. Without both, the vendor drops
|
|
2274
|
+
// out of availableConnections rather than offering a Connect button that
|
|
2275
|
+
// cannot complete.
|
|
2276
|
+
requires: [
|
|
2277
|
+
"MAILCHIMP_OAUTH_CLIENT_ID",
|
|
2278
|
+
"MAILCHIMP_OAUTH_CLIENT_SECRET"
|
|
2279
|
+
],
|
|
2280
2280
|
slug: "mailchimp",
|
|
2281
2281
|
// A key with no audience chosen is authenticated and inert. Mailchimp also
|
|
2282
2282
|
// needs its merge fields created on that audience before any Drawbridge total
|
|
@@ -3165,7 +3165,6 @@ var stepLabels = (catalog = connections) => Object.fromEntries(
|
|
|
3165
3165
|
var connections = Object.freeze({
|
|
3166
3166
|
attentive: build(attentive_default2),
|
|
3167
3167
|
drawbridge: build(drawbridge_default2),
|
|
3168
|
-
hubspot: build(hubspot_default),
|
|
3169
3168
|
klaviyo: build(klaviyo_default2),
|
|
3170
3169
|
mailchimp: build(mailchimp_default2),
|
|
3171
3170
|
shopify: build(shopify_default2),
|