@drawbridge/drawbridge-utils 0.0.111 → 0.0.112
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 +63 -33
- package/dist/connections/index.d.cts +87 -40
- package/dist/connections/index.d.ts +87 -40
- package/dist/connections/index.js +63 -33
- package/package.json +1 -1
|
@@ -126,6 +126,25 @@ var HOOKS = Object.freeze({
|
|
|
126
126
|
// Vendor data a campaign draws on. Named for what every store platform has,
|
|
127
127
|
// not for what Shopify calls it: Shopify says discounts, Stripe says coupons
|
|
128
128
|
// and promotion codes, BigCommerce says coupons and promotions.
|
|
129
|
+
//
|
|
130
|
+
// ONE SHAPE FOR ALL OF THEM — searchable and cursor-paged:
|
|
131
|
+
//
|
|
132
|
+
// ({ connection, cursor, limit, search, settings, token })
|
|
133
|
+
// -> { items : [ { id, title, ... } ], pageInfo : { endCursor, hasNextPage } }
|
|
134
|
+
//
|
|
135
|
+
// Lifted from what the Shopify product picker already does, rather than
|
|
136
|
+
// invented: that endpoint takes cursor/limit/search and returns items plus a
|
|
137
|
+
// pageInfo, and ListResource consumes exactly that. It was a good contract
|
|
138
|
+
// written once for one vendor; this makes it the contract.
|
|
139
|
+
//
|
|
140
|
+
// `title` is not arbitrary — ListResource searches `[ 'title' ]` by default,
|
|
141
|
+
// so normalising to { id, title } is what lets a picker work with no
|
|
142
|
+
// per-vendor configuration.
|
|
143
|
+
//
|
|
144
|
+
// HOW a vendor searches is its own business, which is the point of a hook.
|
|
145
|
+
// Shopify pushes the term into its GraphQL query, Klaviyo has a filter
|
|
146
|
+
// parameter, and Mailchimp's /lists has no name filter at all so its hook
|
|
147
|
+
// matches against what it fetched. The caller never learns which.
|
|
129
148
|
catalog: Object.freeze([
|
|
130
149
|
// The named groups a contact can be synced INTO. Klaviyo calls them lists,
|
|
131
150
|
// Mailchimp calls them audiences; `audiences` is the industry-generic term
|
|
@@ -451,21 +470,29 @@ var klaviyo_default2 = {
|
|
|
451
470
|
// more would show a picker missing the one they wanted — with nothing to
|
|
452
471
|
// indicate anything was cut. Follows links.next, bounded so a runaway
|
|
453
472
|
// cursor cannot spin forever.
|
|
454
|
-
"catalog.audiences": async ({ fetcher, token }) => {
|
|
473
|
+
"catalog.audiences": async ({ cursor, fetcher, limit = 100, search, token }) => {
|
|
455
474
|
var _a, _b;
|
|
456
475
|
const audiences = [];
|
|
457
|
-
let
|
|
476
|
+
let next = cursor ? "/lists?page%5Bsize%5D=10&page%5Bcursor%5D=" + encodeURIComponent(cursor) : "/lists?page%5Bsize%5D=10";
|
|
458
477
|
let pages = 0;
|
|
459
|
-
while (
|
|
460
|
-
const body = await api(
|
|
478
|
+
while (next && audiences.length < limit && pages < 20) {
|
|
479
|
+
const body = await api(next, { fetcher, token });
|
|
461
480
|
for (const list of (body == null ? void 0 : body.data) || []) {
|
|
462
|
-
audiences.push({
|
|
481
|
+
audiences.push({ id: list.id, title: ((_a = list == null ? void 0 : list.attributes) == null ? void 0 : _a.name) || list.id });
|
|
463
482
|
}
|
|
464
|
-
const
|
|
465
|
-
|
|
483
|
+
const link = (_b = body == null ? void 0 : body.links) == null ? void 0 : _b.next;
|
|
484
|
+
next = link ? String(link).replace(/^https:\/\/a\.klaviyo\.com\/api/, "") : null;
|
|
466
485
|
pages = pages + 1;
|
|
467
486
|
}
|
|
468
|
-
|
|
487
|
+
const term = String((search == null ? void 0 : search.value) || "").trim().toLowerCase();
|
|
488
|
+
const items = term ? audiences.filter((entry) => entry.title.toLowerCase().includes(term)) : audiences;
|
|
489
|
+
return {
|
|
490
|
+
items,
|
|
491
|
+
pageInfo: {
|
|
492
|
+
endCursor: next,
|
|
493
|
+
hasNextPage: Boolean(next)
|
|
494
|
+
}
|
|
495
|
+
};
|
|
469
496
|
},
|
|
470
497
|
"auth.disconnect": async ({ clientId, clientSecret, fetcher = fetch, settings }) => {
|
|
471
498
|
const token = (settings == null ? void 0 : settings.refreshToken) || (settings == null ? void 0 : settings.accessToken);
|
|
@@ -632,34 +659,37 @@ var mailchimp_default2 = {
|
|
|
632
659
|
// the same silent truncation Klaviyo has, at a different number. Paged
|
|
633
660
|
// against total_items so an account past a thousand still resolves.
|
|
634
661
|
hooks: {
|
|
635
|
-
"catalog.audiences": async ({ fetcher = fetch, settings }) => {
|
|
662
|
+
"catalog.audiences": async ({ cursor, fetcher = fetch, limit = 100, search, settings }) => {
|
|
636
663
|
const key = settings == null ? void 0 : settings.apiKey;
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const response = await fetcher(
|
|
646
|
-
base(key) + "/lists?count=1000&offset=" + offset + "&fields=lists.id,lists.name,total_items",
|
|
647
|
-
{ headers, signal: AbortSignal.timeout(15e3) }
|
|
648
|
-
);
|
|
649
|
-
if (!response.ok) {
|
|
650
|
-
throw Object.assign(
|
|
651
|
-
new Error("Mailchimp refused the request (" + response.status + ")"),
|
|
652
|
-
{ status: response.status }
|
|
653
|
-
);
|
|
664
|
+
const count = Math.min(limit, 1e3);
|
|
665
|
+
const offset = Number(cursor || 0);
|
|
666
|
+
const response = await fetcher(
|
|
667
|
+
base(key) + "/lists?count=" + count + "&offset=" + offset + "&fields=lists.id,lists.name,total_items",
|
|
668
|
+
{
|
|
669
|
+
// Basic with any username — Mailchimp reads only the password half.
|
|
670
|
+
headers: { authorization: "Basic " + Buffer.from("drawbridge:" + key).toString("base64") },
|
|
671
|
+
signal: AbortSignal.timeout(15e3)
|
|
654
672
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
673
|
+
);
|
|
674
|
+
if (!response.ok) {
|
|
675
|
+
throw Object.assign(
|
|
676
|
+
new Error("Mailchimp refused the request (" + response.status + ")"),
|
|
677
|
+
{ status: response.status }
|
|
678
|
+
);
|
|
661
679
|
}
|
|
662
|
-
|
|
680
|
+
const body = await response.json();
|
|
681
|
+
const audiences = ((body == null ? void 0 : body.lists) || []).map((list) => ({ id: list.id, title: (list == null ? void 0 : list.name) || list.id }));
|
|
682
|
+
const term = String((search == null ? void 0 : search.value) || "").trim().toLowerCase();
|
|
683
|
+
const items = term ? audiences.filter((entry) => entry.title.toLowerCase().includes(term)) : audiences;
|
|
684
|
+
const nextOffset = offset + count;
|
|
685
|
+
const more = nextOffset < Number((body == null ? void 0 : body.total_items) || 0);
|
|
686
|
+
return {
|
|
687
|
+
items,
|
|
688
|
+
pageInfo: {
|
|
689
|
+
endCursor: more ? String(nextOffset) : null,
|
|
690
|
+
hasNextPage: more
|
|
691
|
+
}
|
|
692
|
+
};
|
|
663
693
|
}
|
|
664
694
|
},
|
|
665
695
|
icon: mailchimp_default,
|
|
@@ -97,6 +97,25 @@ const HOOKS = Object.freeze({
|
|
|
97
97
|
// Vendor data a campaign draws on. Named for what every store platform has,
|
|
98
98
|
// not for what Shopify calls it: Shopify says discounts, Stripe says coupons
|
|
99
99
|
// and promotion codes, BigCommerce says coupons and promotions.
|
|
100
|
+
//
|
|
101
|
+
// ONE SHAPE FOR ALL OF THEM — searchable and cursor-paged:
|
|
102
|
+
//
|
|
103
|
+
// ({ connection, cursor, limit, search, settings, token })
|
|
104
|
+
// -> { items : [ { id, title, ... } ], pageInfo : { endCursor, hasNextPage } }
|
|
105
|
+
//
|
|
106
|
+
// Lifted from what the Shopify product picker already does, rather than
|
|
107
|
+
// invented: that endpoint takes cursor/limit/search and returns items plus a
|
|
108
|
+
// pageInfo, and ListResource consumes exactly that. It was a good contract
|
|
109
|
+
// written once for one vendor; this makes it the contract.
|
|
110
|
+
//
|
|
111
|
+
// `title` is not arbitrary — ListResource searches `[ 'title' ]` by default,
|
|
112
|
+
// so normalising to { id, title } is what lets a picker work with no
|
|
113
|
+
// per-vendor configuration.
|
|
114
|
+
//
|
|
115
|
+
// HOW a vendor searches is its own business, which is the point of a hook.
|
|
116
|
+
// Shopify pushes the term into its GraphQL query, Klaviyo has a filter
|
|
117
|
+
// parameter, and Mailchimp's /lists has no name filter at all so its hook
|
|
118
|
+
// matches against what it fetched. The caller never learns which.
|
|
100
119
|
catalog : Object.freeze([
|
|
101
120
|
// The named groups a contact can be synced INTO. Klaviyo calls them lists,
|
|
102
121
|
// Mailchimp calls them audiences; `audiences` is the industry-generic term
|
|
@@ -473,34 +492,56 @@ var klaviyo = {
|
|
|
473
492
|
// more would show a picker missing the one they wanted — with nothing to
|
|
474
493
|
// indicate anything was cut. Follows links.next, bounded so a runaway
|
|
475
494
|
// cursor cannot spin forever.
|
|
476
|
-
'catalog.audiences' : async ({ fetcher, token }) => {
|
|
495
|
+
'catalog.audiences' : async ({ cursor, fetcher, limit = 100, search, token }) => {
|
|
477
496
|
|
|
497
|
+
// Klaviyo pages by cursor and caps page[size] at 10, so `limit` is what
|
|
498
|
+
// the CALLER wants back rather than what one request can carry — the
|
|
499
|
+
// loop keeps pulling until it has that many or the vendor runs out.
|
|
478
500
|
const audiences = [];
|
|
479
501
|
|
|
480
|
-
let
|
|
502
|
+
let next = cursor
|
|
503
|
+
? '/lists?page%5Bsize%5D=10&page%5Bcursor%5D=' + encodeURIComponent( cursor )
|
|
504
|
+
: '/lists?page%5Bsize%5D=10';
|
|
505
|
+
|
|
481
506
|
let pages = 0;
|
|
482
507
|
|
|
483
|
-
while(
|
|
508
|
+
while( next && audiences.length < limit && pages < 20 ){
|
|
484
509
|
|
|
485
|
-
const body = await api(
|
|
510
|
+
const body = await api( next, { fetcher, token });
|
|
486
511
|
|
|
487
512
|
for( const list of ( body?.data || [] ) ){
|
|
488
513
|
|
|
489
|
-
audiences.push({
|
|
514
|
+
audiences.push({ id : list.id, title : list?.attributes?.name || list.id });
|
|
490
515
|
|
|
491
516
|
}
|
|
492
517
|
|
|
493
|
-
const
|
|
518
|
+
const link = body?.links?.next;
|
|
494
519
|
|
|
495
|
-
// Klaviyo returns an absolute url; the
|
|
496
|
-
//
|
|
497
|
-
|
|
520
|
+
// Klaviyo returns an absolute url; only the part after /api travels on,
|
|
521
|
+
// because the shared caller prefixes its own base.
|
|
522
|
+
next = link ? String( link ).replace( /^https:\/\/a\.klaviyo\.com\/api/, '' ) : null;
|
|
498
523
|
|
|
499
524
|
pages = pages + 1;
|
|
500
525
|
|
|
501
526
|
}
|
|
502
527
|
|
|
503
|
-
|
|
528
|
+
// Klaviyo's list endpoint has no name filter, so a search term is applied
|
|
529
|
+
// to what came back. Honest about its own limits: with more lists than
|
|
530
|
+
// `limit`, a term matching only later ones finds nothing — which is why
|
|
531
|
+
// a genuinely large catalog wants server-side filtering, not this.
|
|
532
|
+
const term = String( search?.value || '' ).trim().toLowerCase();
|
|
533
|
+
|
|
534
|
+
const items = term
|
|
535
|
+
? audiences.filter( ( entry ) => entry.title.toLowerCase().includes( term ) )
|
|
536
|
+
: audiences;
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
items,
|
|
540
|
+
pageInfo : {
|
|
541
|
+
endCursor : next,
|
|
542
|
+
hasNextPage : Boolean( next )
|
|
543
|
+
}
|
|
544
|
+
};
|
|
504
545
|
|
|
505
546
|
},
|
|
506
547
|
'auth.disconnect' : async ({ clientId, clientSecret, fetcher = fetch, settings }) => {
|
|
@@ -698,50 +739,56 @@ var mailchimp = {
|
|
|
698
739
|
// the same silent truncation Klaviyo has, at a different number. Paged
|
|
699
740
|
// against total_items so an account past a thousand still resolves.
|
|
700
741
|
hooks : {
|
|
701
|
-
'catalog.audiences' : async ({ fetcher = fetch, settings }) => {
|
|
742
|
+
'catalog.audiences' : async ({ cursor, fetcher = fetch, limit = 100, search, settings }) => {
|
|
702
743
|
|
|
703
744
|
const key = settings?.apiKey;
|
|
704
745
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
746
|
+
// count DEFAULTS TO 10 and maxes at 1000 in Mailchimp's own spec, so
|
|
747
|
+
// leaving it unset returns ten audiences and looks entirely successful.
|
|
748
|
+
const count = Math.min( limit, 1000 );
|
|
749
|
+
const offset = Number( cursor || 0 );
|
|
750
|
+
|
|
751
|
+
const response = await fetcher(
|
|
752
|
+
base( key ) + '/lists?count=' + count + '&offset=' + offset + '&fields=lists.id,lists.name,total_items',
|
|
753
|
+
{
|
|
754
|
+
// Basic with any username — Mailchimp reads only the password half.
|
|
755
|
+
headers : { authorization : 'Basic ' + Buffer.from( 'drawbridge:' + key ).toString( 'base64' ) },
|
|
756
|
+
signal : AbortSignal.timeout( 15000 )
|
|
757
|
+
}
|
|
758
|
+
);
|
|
714
759
|
|
|
715
|
-
|
|
760
|
+
if( ! response.ok ){
|
|
716
761
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
{
|
|
762
|
+
throw Object.assign(
|
|
763
|
+
new Error( 'Mailchimp refused the request (' + response.status + ')' ),
|
|
764
|
+
{ status : response.status }
|
|
720
765
|
);
|
|
721
766
|
|
|
722
|
-
|
|
767
|
+
}
|
|
723
768
|
|
|
724
|
-
|
|
725
|
-
new Error( 'Mailchimp refused the request (' + response.status + ')' ),
|
|
726
|
-
{ status : response.status }
|
|
727
|
-
);
|
|
769
|
+
const body = await response.json();
|
|
728
770
|
|
|
729
|
-
|
|
771
|
+
const audiences = ( body?.lists || [] ).map( ( list ) => ({ id : list.id, title : list?.name || list.id }) );
|
|
730
772
|
|
|
731
|
-
|
|
773
|
+
// Mailchimp's /lists takes no name filter, so a term is matched against
|
|
774
|
+
// this page rather than the account. Stated plainly because it is a real
|
|
775
|
+
// limit: a term matching only an audience on a later page finds nothing.
|
|
776
|
+
const term = String( search?.value || '' ).trim().toLowerCase();
|
|
732
777
|
|
|
733
|
-
|
|
778
|
+
const items = term
|
|
779
|
+
? audiences.filter( ( entry ) => entry.title.toLowerCase().includes( term ) )
|
|
780
|
+
: audiences;
|
|
734
781
|
|
|
735
|
-
|
|
782
|
+
const nextOffset = offset + count;
|
|
783
|
+
const more = nextOffset < Number( body?.total_items || 0 );
|
|
736
784
|
|
|
785
|
+
return {
|
|
786
|
+
items,
|
|
787
|
+
pageInfo : {
|
|
788
|
+
endCursor : more ? String( nextOffset ) : null,
|
|
789
|
+
hasNextPage : more
|
|
737
790
|
}
|
|
738
|
-
|
|
739
|
-
total = body?.total_items ?? audiences.length;
|
|
740
|
-
offset = offset + 1000;
|
|
741
|
-
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
return audiences;
|
|
791
|
+
};
|
|
745
792
|
|
|
746
793
|
}
|
|
747
794
|
},
|
|
@@ -97,6 +97,25 @@ const HOOKS = Object.freeze({
|
|
|
97
97
|
// Vendor data a campaign draws on. Named for what every store platform has,
|
|
98
98
|
// not for what Shopify calls it: Shopify says discounts, Stripe says coupons
|
|
99
99
|
// and promotion codes, BigCommerce says coupons and promotions.
|
|
100
|
+
//
|
|
101
|
+
// ONE SHAPE FOR ALL OF THEM — searchable and cursor-paged:
|
|
102
|
+
//
|
|
103
|
+
// ({ connection, cursor, limit, search, settings, token })
|
|
104
|
+
// -> { items : [ { id, title, ... } ], pageInfo : { endCursor, hasNextPage } }
|
|
105
|
+
//
|
|
106
|
+
// Lifted from what the Shopify product picker already does, rather than
|
|
107
|
+
// invented: that endpoint takes cursor/limit/search and returns items plus a
|
|
108
|
+
// pageInfo, and ListResource consumes exactly that. It was a good contract
|
|
109
|
+
// written once for one vendor; this makes it the contract.
|
|
110
|
+
//
|
|
111
|
+
// `title` is not arbitrary — ListResource searches `[ 'title' ]` by default,
|
|
112
|
+
// so normalising to { id, title } is what lets a picker work with no
|
|
113
|
+
// per-vendor configuration.
|
|
114
|
+
//
|
|
115
|
+
// HOW a vendor searches is its own business, which is the point of a hook.
|
|
116
|
+
// Shopify pushes the term into its GraphQL query, Klaviyo has a filter
|
|
117
|
+
// parameter, and Mailchimp's /lists has no name filter at all so its hook
|
|
118
|
+
// matches against what it fetched. The caller never learns which.
|
|
100
119
|
catalog : Object.freeze([
|
|
101
120
|
// The named groups a contact can be synced INTO. Klaviyo calls them lists,
|
|
102
121
|
// Mailchimp calls them audiences; `audiences` is the industry-generic term
|
|
@@ -473,34 +492,56 @@ var klaviyo = {
|
|
|
473
492
|
// more would show a picker missing the one they wanted — with nothing to
|
|
474
493
|
// indicate anything was cut. Follows links.next, bounded so a runaway
|
|
475
494
|
// cursor cannot spin forever.
|
|
476
|
-
'catalog.audiences' : async ({ fetcher, token }) => {
|
|
495
|
+
'catalog.audiences' : async ({ cursor, fetcher, limit = 100, search, token }) => {
|
|
477
496
|
|
|
497
|
+
// Klaviyo pages by cursor and caps page[size] at 10, so `limit` is what
|
|
498
|
+
// the CALLER wants back rather than what one request can carry — the
|
|
499
|
+
// loop keeps pulling until it has that many or the vendor runs out.
|
|
478
500
|
const audiences = [];
|
|
479
501
|
|
|
480
|
-
let
|
|
502
|
+
let next = cursor
|
|
503
|
+
? '/lists?page%5Bsize%5D=10&page%5Bcursor%5D=' + encodeURIComponent( cursor )
|
|
504
|
+
: '/lists?page%5Bsize%5D=10';
|
|
505
|
+
|
|
481
506
|
let pages = 0;
|
|
482
507
|
|
|
483
|
-
while(
|
|
508
|
+
while( next && audiences.length < limit && pages < 20 ){
|
|
484
509
|
|
|
485
|
-
const body = await api(
|
|
510
|
+
const body = await api( next, { fetcher, token });
|
|
486
511
|
|
|
487
512
|
for( const list of ( body?.data || [] ) ){
|
|
488
513
|
|
|
489
|
-
audiences.push({
|
|
514
|
+
audiences.push({ id : list.id, title : list?.attributes?.name || list.id });
|
|
490
515
|
|
|
491
516
|
}
|
|
492
517
|
|
|
493
|
-
const
|
|
518
|
+
const link = body?.links?.next;
|
|
494
519
|
|
|
495
|
-
// Klaviyo returns an absolute url; the
|
|
496
|
-
//
|
|
497
|
-
|
|
520
|
+
// Klaviyo returns an absolute url; only the part after /api travels on,
|
|
521
|
+
// because the shared caller prefixes its own base.
|
|
522
|
+
next = link ? String( link ).replace( /^https:\/\/a\.klaviyo\.com\/api/, '' ) : null;
|
|
498
523
|
|
|
499
524
|
pages = pages + 1;
|
|
500
525
|
|
|
501
526
|
}
|
|
502
527
|
|
|
503
|
-
|
|
528
|
+
// Klaviyo's list endpoint has no name filter, so a search term is applied
|
|
529
|
+
// to what came back. Honest about its own limits: with more lists than
|
|
530
|
+
// `limit`, a term matching only later ones finds nothing — which is why
|
|
531
|
+
// a genuinely large catalog wants server-side filtering, not this.
|
|
532
|
+
const term = String( search?.value || '' ).trim().toLowerCase();
|
|
533
|
+
|
|
534
|
+
const items = term
|
|
535
|
+
? audiences.filter( ( entry ) => entry.title.toLowerCase().includes( term ) )
|
|
536
|
+
: audiences;
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
items,
|
|
540
|
+
pageInfo : {
|
|
541
|
+
endCursor : next,
|
|
542
|
+
hasNextPage : Boolean( next )
|
|
543
|
+
}
|
|
544
|
+
};
|
|
504
545
|
|
|
505
546
|
},
|
|
506
547
|
'auth.disconnect' : async ({ clientId, clientSecret, fetcher = fetch, settings }) => {
|
|
@@ -698,50 +739,56 @@ var mailchimp = {
|
|
|
698
739
|
// the same silent truncation Klaviyo has, at a different number. Paged
|
|
699
740
|
// against total_items so an account past a thousand still resolves.
|
|
700
741
|
hooks : {
|
|
701
|
-
'catalog.audiences' : async ({ fetcher = fetch, settings }) => {
|
|
742
|
+
'catalog.audiences' : async ({ cursor, fetcher = fetch, limit = 100, search, settings }) => {
|
|
702
743
|
|
|
703
744
|
const key = settings?.apiKey;
|
|
704
745
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
746
|
+
// count DEFAULTS TO 10 and maxes at 1000 in Mailchimp's own spec, so
|
|
747
|
+
// leaving it unset returns ten audiences and looks entirely successful.
|
|
748
|
+
const count = Math.min( limit, 1000 );
|
|
749
|
+
const offset = Number( cursor || 0 );
|
|
750
|
+
|
|
751
|
+
const response = await fetcher(
|
|
752
|
+
base( key ) + '/lists?count=' + count + '&offset=' + offset + '&fields=lists.id,lists.name,total_items',
|
|
753
|
+
{
|
|
754
|
+
// Basic with any username — Mailchimp reads only the password half.
|
|
755
|
+
headers : { authorization : 'Basic ' + Buffer.from( 'drawbridge:' + key ).toString( 'base64' ) },
|
|
756
|
+
signal : AbortSignal.timeout( 15000 )
|
|
757
|
+
}
|
|
758
|
+
);
|
|
714
759
|
|
|
715
|
-
|
|
760
|
+
if( ! response.ok ){
|
|
716
761
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
{
|
|
762
|
+
throw Object.assign(
|
|
763
|
+
new Error( 'Mailchimp refused the request (' + response.status + ')' ),
|
|
764
|
+
{ status : response.status }
|
|
720
765
|
);
|
|
721
766
|
|
|
722
|
-
|
|
767
|
+
}
|
|
723
768
|
|
|
724
|
-
|
|
725
|
-
new Error( 'Mailchimp refused the request (' + response.status + ')' ),
|
|
726
|
-
{ status : response.status }
|
|
727
|
-
);
|
|
769
|
+
const body = await response.json();
|
|
728
770
|
|
|
729
|
-
|
|
771
|
+
const audiences = ( body?.lists || [] ).map( ( list ) => ({ id : list.id, title : list?.name || list.id }) );
|
|
730
772
|
|
|
731
|
-
|
|
773
|
+
// Mailchimp's /lists takes no name filter, so a term is matched against
|
|
774
|
+
// this page rather than the account. Stated plainly because it is a real
|
|
775
|
+
// limit: a term matching only an audience on a later page finds nothing.
|
|
776
|
+
const term = String( search?.value || '' ).trim().toLowerCase();
|
|
732
777
|
|
|
733
|
-
|
|
778
|
+
const items = term
|
|
779
|
+
? audiences.filter( ( entry ) => entry.title.toLowerCase().includes( term ) )
|
|
780
|
+
: audiences;
|
|
734
781
|
|
|
735
|
-
|
|
782
|
+
const nextOffset = offset + count;
|
|
783
|
+
const more = nextOffset < Number( body?.total_items || 0 );
|
|
736
784
|
|
|
785
|
+
return {
|
|
786
|
+
items,
|
|
787
|
+
pageInfo : {
|
|
788
|
+
endCursor : more ? String( nextOffset ) : null,
|
|
789
|
+
hasNextPage : more
|
|
737
790
|
}
|
|
738
|
-
|
|
739
|
-
total = body?.total_items ?? audiences.length;
|
|
740
|
-
offset = offset + 1000;
|
|
741
|
-
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
return audiences;
|
|
791
|
+
};
|
|
745
792
|
|
|
746
793
|
}
|
|
747
794
|
},
|
|
@@ -73,6 +73,25 @@ var HOOKS = Object.freeze({
|
|
|
73
73
|
// Vendor data a campaign draws on. Named for what every store platform has,
|
|
74
74
|
// not for what Shopify calls it: Shopify says discounts, Stripe says coupons
|
|
75
75
|
// and promotion codes, BigCommerce says coupons and promotions.
|
|
76
|
+
//
|
|
77
|
+
// ONE SHAPE FOR ALL OF THEM — searchable and cursor-paged:
|
|
78
|
+
//
|
|
79
|
+
// ({ connection, cursor, limit, search, settings, token })
|
|
80
|
+
// -> { items : [ { id, title, ... } ], pageInfo : { endCursor, hasNextPage } }
|
|
81
|
+
//
|
|
82
|
+
// Lifted from what the Shopify product picker already does, rather than
|
|
83
|
+
// invented: that endpoint takes cursor/limit/search and returns items plus a
|
|
84
|
+
// pageInfo, and ListResource consumes exactly that. It was a good contract
|
|
85
|
+
// written once for one vendor; this makes it the contract.
|
|
86
|
+
//
|
|
87
|
+
// `title` is not arbitrary — ListResource searches `[ 'title' ]` by default,
|
|
88
|
+
// so normalising to { id, title } is what lets a picker work with no
|
|
89
|
+
// per-vendor configuration.
|
|
90
|
+
//
|
|
91
|
+
// HOW a vendor searches is its own business, which is the point of a hook.
|
|
92
|
+
// Shopify pushes the term into its GraphQL query, Klaviyo has a filter
|
|
93
|
+
// parameter, and Mailchimp's /lists has no name filter at all so its hook
|
|
94
|
+
// matches against what it fetched. The caller never learns which.
|
|
76
95
|
catalog: Object.freeze([
|
|
77
96
|
// The named groups a contact can be synced INTO. Klaviyo calls them lists,
|
|
78
97
|
// Mailchimp calls them audiences; `audiences` is the industry-generic term
|
|
@@ -398,21 +417,29 @@ var klaviyo_default2 = {
|
|
|
398
417
|
// more would show a picker missing the one they wanted — with nothing to
|
|
399
418
|
// indicate anything was cut. Follows links.next, bounded so a runaway
|
|
400
419
|
// cursor cannot spin forever.
|
|
401
|
-
"catalog.audiences": async ({ fetcher, token }) => {
|
|
420
|
+
"catalog.audiences": async ({ cursor, fetcher, limit = 100, search, token }) => {
|
|
402
421
|
var _a, _b;
|
|
403
422
|
const audiences = [];
|
|
404
|
-
let
|
|
423
|
+
let next = cursor ? "/lists?page%5Bsize%5D=10&page%5Bcursor%5D=" + encodeURIComponent(cursor) : "/lists?page%5Bsize%5D=10";
|
|
405
424
|
let pages = 0;
|
|
406
|
-
while (
|
|
407
|
-
const body = await api(
|
|
425
|
+
while (next && audiences.length < limit && pages < 20) {
|
|
426
|
+
const body = await api(next, { fetcher, token });
|
|
408
427
|
for (const list of (body == null ? void 0 : body.data) || []) {
|
|
409
|
-
audiences.push({
|
|
428
|
+
audiences.push({ id: list.id, title: ((_a = list == null ? void 0 : list.attributes) == null ? void 0 : _a.name) || list.id });
|
|
410
429
|
}
|
|
411
|
-
const
|
|
412
|
-
|
|
430
|
+
const link = (_b = body == null ? void 0 : body.links) == null ? void 0 : _b.next;
|
|
431
|
+
next = link ? String(link).replace(/^https:\/\/a\.klaviyo\.com\/api/, "") : null;
|
|
413
432
|
pages = pages + 1;
|
|
414
433
|
}
|
|
415
|
-
|
|
434
|
+
const term = String((search == null ? void 0 : search.value) || "").trim().toLowerCase();
|
|
435
|
+
const items = term ? audiences.filter((entry) => entry.title.toLowerCase().includes(term)) : audiences;
|
|
436
|
+
return {
|
|
437
|
+
items,
|
|
438
|
+
pageInfo: {
|
|
439
|
+
endCursor: next,
|
|
440
|
+
hasNextPage: Boolean(next)
|
|
441
|
+
}
|
|
442
|
+
};
|
|
416
443
|
},
|
|
417
444
|
"auth.disconnect": async ({ clientId, clientSecret, fetcher = fetch, settings }) => {
|
|
418
445
|
const token = (settings == null ? void 0 : settings.refreshToken) || (settings == null ? void 0 : settings.accessToken);
|
|
@@ -579,34 +606,37 @@ var mailchimp_default2 = {
|
|
|
579
606
|
// the same silent truncation Klaviyo has, at a different number. Paged
|
|
580
607
|
// against total_items so an account past a thousand still resolves.
|
|
581
608
|
hooks: {
|
|
582
|
-
"catalog.audiences": async ({ fetcher = fetch, settings }) => {
|
|
609
|
+
"catalog.audiences": async ({ cursor, fetcher = fetch, limit = 100, search, settings }) => {
|
|
583
610
|
const key = settings == null ? void 0 : settings.apiKey;
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
const response = await fetcher(
|
|
593
|
-
base(key) + "/lists?count=1000&offset=" + offset + "&fields=lists.id,lists.name,total_items",
|
|
594
|
-
{ headers, signal: AbortSignal.timeout(15e3) }
|
|
595
|
-
);
|
|
596
|
-
if (!response.ok) {
|
|
597
|
-
throw Object.assign(
|
|
598
|
-
new Error("Mailchimp refused the request (" + response.status + ")"),
|
|
599
|
-
{ status: response.status }
|
|
600
|
-
);
|
|
611
|
+
const count = Math.min(limit, 1e3);
|
|
612
|
+
const offset = Number(cursor || 0);
|
|
613
|
+
const response = await fetcher(
|
|
614
|
+
base(key) + "/lists?count=" + count + "&offset=" + offset + "&fields=lists.id,lists.name,total_items",
|
|
615
|
+
{
|
|
616
|
+
// Basic with any username — Mailchimp reads only the password half.
|
|
617
|
+
headers: { authorization: "Basic " + Buffer.from("drawbridge:" + key).toString("base64") },
|
|
618
|
+
signal: AbortSignal.timeout(15e3)
|
|
601
619
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
620
|
+
);
|
|
621
|
+
if (!response.ok) {
|
|
622
|
+
throw Object.assign(
|
|
623
|
+
new Error("Mailchimp refused the request (" + response.status + ")"),
|
|
624
|
+
{ status: response.status }
|
|
625
|
+
);
|
|
608
626
|
}
|
|
609
|
-
|
|
627
|
+
const body = await response.json();
|
|
628
|
+
const audiences = ((body == null ? void 0 : body.lists) || []).map((list) => ({ id: list.id, title: (list == null ? void 0 : list.name) || list.id }));
|
|
629
|
+
const term = String((search == null ? void 0 : search.value) || "").trim().toLowerCase();
|
|
630
|
+
const items = term ? audiences.filter((entry) => entry.title.toLowerCase().includes(term)) : audiences;
|
|
631
|
+
const nextOffset = offset + count;
|
|
632
|
+
const more = nextOffset < Number((body == null ? void 0 : body.total_items) || 0);
|
|
633
|
+
return {
|
|
634
|
+
items,
|
|
635
|
+
pageInfo: {
|
|
636
|
+
endCursor: more ? String(nextOffset) : null,
|
|
637
|
+
hasNextPage: more
|
|
638
|
+
}
|
|
639
|
+
};
|
|
610
640
|
}
|
|
611
641
|
},
|
|
612
642
|
icon: mailchimp_default,
|
package/package.json
CHANGED