@drawbridge/drawbridge-utils 0.0.114 → 0.0.116
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 +312 -13
- package/dist/connections/index.d.cts +390 -14
- package/dist/connections/index.d.ts +390 -14
- package/dist/connections/index.js +311 -13
- package/dist/features.cjs +15 -0
- package/dist/features.d.cts +15 -0
- package/dist/features.d.ts +15 -0
- package/dist/features.js +15 -0
- package/dist/fields-validate.js +1 -1
- package/dist/phone.cjs +12 -0
- package/dist/phone.d.cts +30 -2
- package/dist/phone.d.ts +30 -2
- package/dist/phone.js +12 -1
- package/dist/plans.cjs +17 -0
- package/dist/plans.d.cts +2 -0
- package/dist/plans.d.ts +2 -0
- package/dist/plans.js +17 -0
- package/dist/pricing.cjs +17 -0
- package/dist/pricing.js +17 -0
- package/package.json +2 -2
|
@@ -378,6 +378,194 @@ var accessToken = async ({
|
|
|
378
378
|
return next.accessToken;
|
|
379
379
|
};
|
|
380
380
|
|
|
381
|
+
// lib/connections/icons/attentive.js
|
|
382
|
+
var attentive_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
383
|
+
<rect width="500" height="500" fill="#FFD967"/>
|
|
384
|
+
<path d="M239.369 127.404C260.863 122.013 284.964 132.569 295.576 152.039C321.796 201.286 347.928 250.567 374.131 299.825C380.149 311.989 381.244 326.564 376.733 339.41C371.446 355.097 358.039 367.69 342.107 372.089C326.274 376.67 308.351 372.871 295.741 362.255C288.865 356.892 284.161 349.359 280.354 341.629C261.869 306.95 243.478 272.217 225.071 237.501C214.409 218.614 187.221 212.656 169.622 225.315C168.104 227.309 164.159 226.857 164.72 223.796C176.911 202.569 189.361 181.497 201.662 160.331C205.183 154.428 208.214 148.129 212.967 143.091C219.844 135.272 229.263 129.782 239.374 127.404" fill="#1E1C1C"/>
|
|
385
|
+
<path d="M166.04 261.805C180.228 259.107 195.528 261.893 207.581 269.971C218.512 277.079 226.908 288.136 230.604 300.657C234.835 314.103 233.614 329.124 227.485 341.788C220.097 356.875 205.782 368.543 189.317 372.089C173.957 375.652 157.089 372.386 144.304 363.103C132.971 355.295 124.912 342.989 121.891 329.581C118.943 316.636 120.725 302.656 127.002 290.938C134.699 275.951 149.503 264.933 166.046 261.811" fill="#1E1C1C"/>
|
|
386
|
+
</svg>`;
|
|
387
|
+
|
|
388
|
+
// lib/connections/attentive.js
|
|
389
|
+
var attentive_default2 = {
|
|
390
|
+
auth: {
|
|
391
|
+
oauth: {
|
|
392
|
+
// NAMES of the env vars holding OUR app's client — set at registration,
|
|
393
|
+
// never before. No `headers` on the client: Attentive takes credentials
|
|
394
|
+
// as form fields, which is the runner's default.
|
|
395
|
+
client: {
|
|
396
|
+
id: "ATTENTIVE_OAUTH_CLIENT_ID",
|
|
397
|
+
secret: "ATTENTIVE_OAUTH_CLIENT_SECRET"
|
|
398
|
+
},
|
|
399
|
+
urls: {
|
|
400
|
+
authorize: "https://ui.attentivemobile.com/integrations/oauth-install",
|
|
401
|
+
redirect: "/api/connection/attentive/callback",
|
|
402
|
+
token: "https://api.attentivemobile.com/v1/authorization-codes/tokens"
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
type: "oauth"
|
|
406
|
+
},
|
|
407
|
+
// EVERYTHING A MERCHANT READS. `errors` would live in here too — the
|
|
408
|
+
// connection DOCUMENT carries its own `errors` array and is spread OVER the
|
|
409
|
+
// resolved manifest downstream, so a top-level one would never render.
|
|
410
|
+
content: {
|
|
411
|
+
// SAYS WHAT ACTUALLY HAPPENS. This used to promise that disconnecting
|
|
412
|
+
// revokes Drawbridge's access, and it cannot: Attentive documents no
|
|
413
|
+
// revocation endpoint, and their authentication page states an access
|
|
414
|
+
// token "does not expire". So the grant survives a disconnect forever
|
|
415
|
+
// unless the merchant removes the integration at Attentive, and the copy
|
|
416
|
+
// has to say so rather than let them believe otherwise.
|
|
417
|
+
confirm: "Disconnecting removes Drawbridge's stored Attentive token. Attentive does not offer a way for us to revoke it, so remove the Drawbridge integration in Attentive as well if you want its access fully withdrawn. Your subscribers stay in both Attentive and Drawbridge \u2014 neither list is deleted.",
|
|
418
|
+
description: [
|
|
419
|
+
"Attentive is where your SMS marketing lives, and this connection is becoming the way your Drawbridge contacts sync into an Attentive segment.",
|
|
420
|
+
"You authorize Drawbridge from inside Attentive and can revoke that access there at any time. Drawbridge never sees or stores your Attentive password.",
|
|
421
|
+
"Subscriber syncing is not live yet, so connecting today does nothing except choose the segment it will use when it ships."
|
|
422
|
+
],
|
|
423
|
+
excerpt: "Sync your Drawbridge contacts into an Attentive segment.",
|
|
424
|
+
guide: [
|
|
425
|
+
"Press Connect. Drawbridge sends you to Attentive to approve access.",
|
|
426
|
+
"Sign in to Attentive if you are not already, and authorize the permissions listed.",
|
|
427
|
+
"You are returned here \u2014 choose which Attentive segment your contacts should sync into."
|
|
428
|
+
]
|
|
429
|
+
},
|
|
430
|
+
// A contact destination, like Klaviyo and Mailchimp — a merchant could
|
|
431
|
+
// reasonably keep several up to date at once.
|
|
432
|
+
exclusive: false,
|
|
433
|
+
feature: "organization:connection:attentive",
|
|
434
|
+
fields: [
|
|
435
|
+
{
|
|
436
|
+
input: "select",
|
|
437
|
+
key: "segment",
|
|
438
|
+
label: "Attentive segment",
|
|
439
|
+
message: "Contacts your campaigns collect are synced into this segment.",
|
|
440
|
+
hook: "resources.audiences",
|
|
441
|
+
required: true
|
|
442
|
+
// No `search : false` here, and that is a first: /v2/segments takes a
|
|
443
|
+
// `name` filter (partial match, cited above), so this picker searches
|
|
444
|
+
// the ACCOUNT — Klaviyo and Mailchimp can only match the fetched page.
|
|
445
|
+
}
|
|
446
|
+
],
|
|
447
|
+
group: "contacts",
|
|
448
|
+
// A HOOK'S VALUE IS ITS ANSWER. The consent is stored and can be revoked;
|
|
449
|
+
// nothing else is built yet, because subscriber sync has not shipped. Every
|
|
450
|
+
// false here is "not yet" rather than "never" — when the sync lands, probe
|
|
451
|
+
// and contacts.sync are the first to flip.
|
|
452
|
+
hooks: {
|
|
453
|
+
auth: {
|
|
454
|
+
// The exchange already yields the tokens, and Attentive documents no
|
|
455
|
+
// account-identity endpoint to enrich them with — Klaviyo's connect
|
|
456
|
+
// reads the account name back; this has nothing cited to read. The
|
|
457
|
+
// callback stores the tokens and skips enrichment on `unimplemented`.
|
|
458
|
+
// FALSE, NOT {}. `{}` means "supported, implemented in the repo with the
|
|
459
|
+
// dependencies", and nothing anywhere implements either of these —
|
|
460
|
+
// there is nothing for them to do. The exchange already yields the
|
|
461
|
+
// tokens and Attentive documents no account-identity endpoint to
|
|
462
|
+
// enrich them with, so connect has nothing to add; and they document
|
|
463
|
+
// no revocation endpoint at all, so disconnect has nothing to call.
|
|
464
|
+
// Recorded as a decision rather than left as an unkept promise.
|
|
465
|
+
connect: false,
|
|
466
|
+
disconnect: false,
|
|
467
|
+
probe: false,
|
|
468
|
+
scopes: false,
|
|
469
|
+
// THE ONE THING WRAPPED, and it is about the response rather than the
|
|
470
|
+
// request. Attentive's token reply carries expires_in : 900 while their
|
|
471
|
+
// auth overview says access tokens "do not expire" — and no refresh
|
|
472
|
+
// token exists to renew with. Storing that expiry would have
|
|
473
|
+
// accessToken() declaring the credential spent fifteen minutes after
|
|
474
|
+
// consent, with nothing to renew it: every connection would demand
|
|
475
|
+
// reconnecting four times an hour.
|
|
476
|
+
//
|
|
477
|
+
// The overview's answer is modelled — the expiry is dropped, so the
|
|
478
|
+
// token is treated as long-lived. Registration item 1 in the header is
|
|
479
|
+
// the live exchange that proves this right or wrong.
|
|
480
|
+
token: async (args) => {
|
|
481
|
+
const minted = await authToken(args);
|
|
482
|
+
return { ...minted, expiresIn: null };
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
commerce: false,
|
|
486
|
+
contacts: { remove: false, sync: false },
|
|
487
|
+
email: false,
|
|
488
|
+
inbound: false,
|
|
489
|
+
lifecycle: false,
|
|
490
|
+
resources: {
|
|
491
|
+
// The segments a merchant can sync into, for the picker on their
|
|
492
|
+
// connection.
|
|
493
|
+
//
|
|
494
|
+
// GET /v2/segments (cited in the header). `limit` caps at 1000 in their
|
|
495
|
+
// own spec, defaulting to 20 — low enough that leaving it unset would
|
|
496
|
+
// show a picker quietly missing most of a real account. The response's
|
|
497
|
+
// only identifier is `externalId`, so an entry without one cannot be
|
|
498
|
+
// stored and is dropped.
|
|
499
|
+
audiences: async ({ cursor, fetcher = fetch, limit = 100, search, token }) => {
|
|
500
|
+
const query = new URLSearchParams({
|
|
501
|
+
limit: String(Math.min(limit, 1e3)),
|
|
502
|
+
...cursor && { cursor },
|
|
503
|
+
...(search == null ? void 0 : search.value) && { name: String(search.value).trim() }
|
|
504
|
+
});
|
|
505
|
+
const response = await fetcher(
|
|
506
|
+
"https://api.attentivemobile.com/v2/segments?" + query,
|
|
507
|
+
{
|
|
508
|
+
headers: { authorization: "Bearer " + token },
|
|
509
|
+
signal: AbortSignal.timeout(15e3)
|
|
510
|
+
}
|
|
511
|
+
);
|
|
512
|
+
if (!response.ok) {
|
|
513
|
+
throw Object.assign(
|
|
514
|
+
new Error("Attentive refused the request (" + response.status + ")"),
|
|
515
|
+
{ status: response.status }
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
const body = await response.json();
|
|
519
|
+
return {
|
|
520
|
+
items: ((body == null ? void 0 : body.segments) || []).filter((segment) => segment == null ? void 0 : segment.externalId).map((segment) => ({ id: segment.externalId, title: (segment == null ? void 0 : segment.name) || segment.externalId })),
|
|
521
|
+
pageInfo: {
|
|
522
|
+
endCursor: (body == null ? void 0 : body.hasMore) ? (body == null ? void 0 : body.cursor) || null : null,
|
|
523
|
+
hasNextPage: Boolean(body == null ? void 0 : body.hasMore)
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
},
|
|
527
|
+
prices: false,
|
|
528
|
+
products: false,
|
|
529
|
+
promotions: false
|
|
530
|
+
},
|
|
531
|
+
segment: false,
|
|
532
|
+
sms: false,
|
|
533
|
+
webhook: false
|
|
534
|
+
},
|
|
535
|
+
icon: attentive_default,
|
|
536
|
+
requires: [
|
|
537
|
+
"ATTENTIVE_OAUTH_CLIENT_ID",
|
|
538
|
+
"ATTENTIVE_OAUTH_CLIENT_SECRET"
|
|
539
|
+
],
|
|
540
|
+
slug: "attentive",
|
|
541
|
+
// A consent with no segment chosen is authenticated and inert — the sync,
|
|
542
|
+
// when it ships, needs somewhere to put people.
|
|
543
|
+
status: (data2) => {
|
|
544
|
+
var _a;
|
|
545
|
+
return ((_a = data2 == null ? void 0 : data2.settings) == null ? void 0 : _a.segment) ? data2.status : "pending";
|
|
546
|
+
},
|
|
547
|
+
// No steps: subscriber sync has not shipped, so this vendor contributes
|
|
548
|
+
// nothing to a workflow yet. An empty steps object is the honest declaration.
|
|
549
|
+
steps: {},
|
|
550
|
+
tasks: (data2) => {
|
|
551
|
+
var _a;
|
|
552
|
+
return [
|
|
553
|
+
...((_a = data2 == null ? void 0 : data2.settings) == null ? void 0 : _a.segment) ? [] : [
|
|
554
|
+
{
|
|
555
|
+
message: "Choose which Attentive segment your contacts should sync into. Until you do, nothing is being synced.",
|
|
556
|
+
title: "Choose a segment"
|
|
557
|
+
}
|
|
558
|
+
],
|
|
559
|
+
{
|
|
560
|
+
message: "Contact syncing to Attentive segments has not shipped yet. Nothing is being sent to Attentive right now.",
|
|
561
|
+
title: "Subscriber sync not available yet",
|
|
562
|
+
type: "warning"
|
|
563
|
+
}
|
|
564
|
+
];
|
|
565
|
+
},
|
|
566
|
+
title: "Attentive"
|
|
567
|
+
};
|
|
568
|
+
|
|
381
569
|
// lib/connections/icons/drawbridge.js
|
|
382
570
|
var drawbridge_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
383
571
|
<rect width="500" height="500" fill="#BAEC5F"/>
|
|
@@ -460,6 +648,21 @@ var field = {
|
|
|
460
648
|
}
|
|
461
649
|
};
|
|
462
650
|
var connection = {
|
|
651
|
+
attentive: {
|
|
652
|
+
key: "organization:connection:attentive",
|
|
653
|
+
error: "Plan does not include Attentive connection",
|
|
654
|
+
feature: "Attentive connection"
|
|
655
|
+
},
|
|
656
|
+
// Klaviyo shipped without an entry here, which meant no plan GRANTED its
|
|
657
|
+
// key and the feature gate denied every non-admin request — a latent 403
|
|
658
|
+
// found while adding Attentive. getPlanFeature answers granted:false for a
|
|
659
|
+
// key absent from the plan's map, so a manifest feature key that appears in
|
|
660
|
+
// no plan list is a connection only admins can manage.
|
|
661
|
+
klaviyo: {
|
|
662
|
+
key: "organization:connection:klaviyo",
|
|
663
|
+
error: "Plan does not include Klaviyo connection",
|
|
664
|
+
feature: "Klaviyo connection"
|
|
665
|
+
},
|
|
463
666
|
mailchimp: {
|
|
464
667
|
key: "organization:connection:mailchimp",
|
|
465
668
|
error: "Plan does not include Mailchimp connection",
|
|
@@ -637,6 +840,8 @@ var overage = (actionCents) => ({
|
|
|
637
840
|
});
|
|
638
841
|
var all = {
|
|
639
842
|
features: (array = []) => featuresFor([
|
|
843
|
+
connection.attentive.key,
|
|
844
|
+
connection.klaviyo.key,
|
|
640
845
|
connection.mailchimp.key,
|
|
641
846
|
connection.sendgrid.key,
|
|
642
847
|
connection.shopify.key,
|
|
@@ -1928,10 +2133,14 @@ var mailchimp_default2 = {
|
|
|
1928
2133
|
// contacts.sync are the first to flip.
|
|
1929
2134
|
hooks: {
|
|
1930
2135
|
auth: {
|
|
1931
|
-
//
|
|
1932
|
-
//
|
|
1933
|
-
|
|
1934
|
-
|
|
2136
|
+
// FALSE, NOT {}. `{}` promises an implementation living in the repo that
|
|
2137
|
+
// holds the dependencies, and there is no implementation anywhere
|
|
2138
|
+
// because there is nothing to implement: storing and clearing a typed
|
|
2139
|
+
// key needs no vendor call, and the api's own form handler does it.
|
|
2140
|
+
// Declaring `{}` made the coverage check chase a body that does not
|
|
2141
|
+
// exist, and made a caller wait on an answer that never comes.
|
|
2142
|
+
connect: false,
|
|
2143
|
+
disconnect: false,
|
|
1935
2144
|
probe: false,
|
|
1936
2145
|
scopes: false,
|
|
1937
2146
|
// Keys today. When Mailchimp's OAuth lands this becomes a wrapper that
|
|
@@ -2152,10 +2361,29 @@ var shopify_default2 = {
|
|
|
2152
2361
|
// re-registers rather than answering "is this token still good", and
|
|
2153
2362
|
// scope drift is its own hook because a token can be perfectly valid
|
|
2154
2363
|
// while the grant is too narrow.
|
|
2155
|
-
|
|
2156
|
-
disconnect
|
|
2364
|
+
//
|
|
2365
|
+
// connect and disconnect are FALSE rather than `{}`: there is nothing to
|
|
2366
|
+
// call on either side. The install already hands the callback everything
|
|
2367
|
+
// it stores, and a Shopify grant is withdrawn by UNINSTALLING the app in
|
|
2368
|
+
// Shopify admin — which Drawbridge learns about from the app_uninstalled
|
|
2369
|
+
// webhook rather than by asking. `{}` claimed a body implemented
|
|
2370
|
+
// elsewhere; none exists, and none could.
|
|
2371
|
+
connect: false,
|
|
2372
|
+
disconnect: false,
|
|
2157
2373
|
probe: false,
|
|
2158
|
-
|
|
2374
|
+
// WHETHER THE GRANT IS STILL WIDE ENOUGH. A token can be perfectly valid
|
|
2375
|
+
// and still too narrow — a deploy that adds a scope leaves every existing
|
|
2376
|
+
// install short of it, and no webhook fires to say so.
|
|
2377
|
+
//
|
|
2378
|
+
// The comparison is the vendor's, so it belongs here. Reading WHICH
|
|
2379
|
+
// scopes a store granted is not: that lives in the `shop` collection and
|
|
2380
|
+
// needs a controller, which is precisely what a hook in a published
|
|
2381
|
+
// package must not be handed. The caller reads the grant and passes the
|
|
2382
|
+
// string; this answers what is missing from it.
|
|
2383
|
+
//
|
|
2384
|
+
// `shopify` is injected for the same reason it is everywhere else — this
|
|
2385
|
+
// package cannot import @drawbridge/shopify, which depends on it.
|
|
2386
|
+
scopes: ({ scope, shopify }) => scope ? shopify.oauth.missingScopes(scope) : null,
|
|
2159
2387
|
// Shopify's install grant is exchanged inside its own app flow, not
|
|
2160
2388
|
// through the shared OAuth runner.
|
|
2161
2389
|
token: false
|
|
@@ -2209,8 +2437,65 @@ var shopify_default2 = {
|
|
|
2209
2437
|
// variant and arrives with it, so there is nothing for prices to answer
|
|
2210
2438
|
// that products does not already.
|
|
2211
2439
|
prices: false,
|
|
2212
|
-
|
|
2213
|
-
|
|
2440
|
+
// WHAT THE VENDOR ANSWERS, shaped for a picker. Both of these were api
|
|
2441
|
+
// ROUTES — /organization/:organization/shopify/products and
|
|
2442
|
+
// .../connection/:id/shopify/discounts — vendor-named urls in a service
|
|
2443
|
+
// that is supposed to have none, reachable only by knowing the path.
|
|
2444
|
+
// They are the same two questions every other vendor answers through
|
|
2445
|
+
// resources.*, so they answer them the same way now.
|
|
2446
|
+
//
|
|
2447
|
+
// `shopify` is INJECTED: this package cannot import @drawbridge/shopify,
|
|
2448
|
+
// which depends on it. What arrives is the SDK's pure HTTP namespaces
|
|
2449
|
+
// and nothing else — no controller, no collection access. Resolving the
|
|
2450
|
+
// credential is the caller's job because it is Drawbridge's job: the
|
|
2451
|
+
// admin token refreshes and writes itself back, which is service work,
|
|
2452
|
+
// not vendor work.
|
|
2453
|
+
products: async ({ cursor, limit = 100, search, settings, shopify, sort }) => {
|
|
2454
|
+
var _a, _b, _c, _d;
|
|
2455
|
+
const products = await shopify.storefront.getProducts({
|
|
2456
|
+
cursor,
|
|
2457
|
+
domain: settings == null ? void 0 : settings.domain,
|
|
2458
|
+
limit: Number(limit),
|
|
2459
|
+
search: (search == null ? void 0 : search.value) || null,
|
|
2460
|
+
sort,
|
|
2461
|
+
storefrontAccessToken: settings == null ? void 0 : settings.storefrontAccessToken
|
|
2462
|
+
});
|
|
2463
|
+
return {
|
|
2464
|
+
items: ((products == null ? void 0 : products.edges) || []).map((edge) => edge.node),
|
|
2465
|
+
pageInfo: {
|
|
2466
|
+
endCursor: ((_a = products == null ? void 0 : products.pageInfo) == null ? void 0 : _a.endCursor) || null,
|
|
2467
|
+
hasNextPage: Boolean((_b = products == null ? void 0 : products.pageInfo) == null ? void 0 : _b.hasNextPage),
|
|
2468
|
+
hasPreviousPage: Boolean((_c = products == null ? void 0 : products.pageInfo) == null ? void 0 : _c.hasPreviousPage),
|
|
2469
|
+
startCursor: ((_d = products == null ? void 0 : products.pageInfo) == null ? void 0 : _d.startCursor) || null
|
|
2470
|
+
}
|
|
2471
|
+
};
|
|
2472
|
+
},
|
|
2473
|
+
promotions: async ({ cursor, limit = 100, search, settings, shopify }) => {
|
|
2474
|
+
var _a, _b;
|
|
2475
|
+
const discounts = await shopify.admin.getDiscounts({
|
|
2476
|
+
adminAccessToken: settings == null ? void 0 : settings.adminAccessToken,
|
|
2477
|
+
cursor,
|
|
2478
|
+
domain: settings == null ? void 0 : settings.domain,
|
|
2479
|
+
limit: Number(limit),
|
|
2480
|
+
search: (search == null ? void 0 : search.value) || null
|
|
2481
|
+
});
|
|
2482
|
+
return {
|
|
2483
|
+
// The GLOBAL id is what Shopify returns and the bare id is what a
|
|
2484
|
+
// picker stores, which is why the tail is taken here rather than by
|
|
2485
|
+
// each caller that happened to remember.
|
|
2486
|
+
items: ((discounts == null ? void 0 : discounts.edges) || []).map((edge) => {
|
|
2487
|
+
var _a2, _b2, _c;
|
|
2488
|
+
return {
|
|
2489
|
+
id: String(((_a2 = edge == null ? void 0 : edge.node) == null ? void 0 : _a2.id) || "").split("/").pop(),
|
|
2490
|
+
title: (_c = (_b2 = edge == null ? void 0 : edge.node) == null ? void 0 : _b2.codeDiscount) == null ? void 0 : _c.title
|
|
2491
|
+
};
|
|
2492
|
+
}),
|
|
2493
|
+
pageInfo: {
|
|
2494
|
+
endCursor: ((_a = discounts == null ? void 0 : discounts.pageInfo) == null ? void 0 : _a.endCursor) || null,
|
|
2495
|
+
hasNextPage: Boolean((_b = discounts == null ? void 0 : discounts.pageInfo) == null ? void 0 : _b.hasNextPage)
|
|
2496
|
+
}
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2214
2499
|
},
|
|
2215
2500
|
// Drawbridge posts to a merchant's own endpoint, never through a vendor.
|
|
2216
2501
|
webhook: false
|
|
@@ -2561,10 +2846,12 @@ var webhook_default = {
|
|
|
2561
2846
|
// connect generates a secret rather than proving a credential.
|
|
2562
2847
|
hooks: {
|
|
2563
2848
|
auth: {
|
|
2564
|
-
//
|
|
2565
|
-
//
|
|
2566
|
-
|
|
2567
|
-
|
|
2849
|
+
// FALSE, NOT {}. There is no vendor here at all — connecting mints a
|
|
2850
|
+
// secret and disconnecting clears it, both done by the api's own
|
|
2851
|
+
// handler. `{}` would promise a body implemented elsewhere, and none
|
|
2852
|
+
// exists or could.
|
|
2853
|
+
connect: false,
|
|
2854
|
+
disconnect: false,
|
|
2568
2855
|
probe: false,
|
|
2569
2856
|
scopes: false,
|
|
2570
2857
|
// Nothing to mint. Connecting generates a secret; there is no vendor.
|
|
@@ -2807,6 +3094,7 @@ var stepLabels = (catalog = connections) => Object.fromEntries(
|
|
|
2807
3094
|
)
|
|
2808
3095
|
);
|
|
2809
3096
|
var connections = Object.freeze({
|
|
3097
|
+
attentive: build(attentive_default2),
|
|
2810
3098
|
drawbridge: build(drawbridge_default2),
|
|
2811
3099
|
hubspot: build(hubspot_default),
|
|
2812
3100
|
klaviyo: build(klaviyo_default2),
|
|
@@ -2888,6 +3176,15 @@ var runHook = async (slug, name, args = {}) => {
|
|
|
2888
3176
|
var stepQueues = (env = {}) => Object.fromEntries(
|
|
2889
3177
|
connectionSteps(env).map((step) => [step.type, step.queue])
|
|
2890
3178
|
);
|
|
3179
|
+
var stepRoutes = (env = {}) => {
|
|
3180
|
+
const live = stepQueues(env);
|
|
3181
|
+
return Object.freeze({
|
|
3182
|
+
...live,
|
|
3183
|
+
...Object.fromEntries(
|
|
3184
|
+
Object.entries(RETIRED).filter(([, replacement]) => live[replacement]).map(([retired, replacement]) => [retired, live[replacement]])
|
|
3185
|
+
)
|
|
3186
|
+
});
|
|
3187
|
+
};
|
|
2891
3188
|
var scopesMessage = "Shopify permissions are out of date. Open the Drawbridge app in your Shopify admin to approve the updated permissions.";
|
|
2892
3189
|
var mergeSettings = ({ existing, incoming }) => {
|
|
2893
3190
|
if (!existing || typeof existing !== "object") return incoming;
|
|
@@ -2995,5 +3292,6 @@ export {
|
|
|
2995
3292
|
scopesMessage,
|
|
2996
3293
|
stepLabels,
|
|
2997
3294
|
stepQueues,
|
|
3295
|
+
stepRoutes,
|
|
2998
3296
|
tokenSettings
|
|
2999
3297
|
};
|
package/dist/features.cjs
CHANGED
|
@@ -94,6 +94,21 @@ var field = {
|
|
|
94
94
|
}
|
|
95
95
|
};
|
|
96
96
|
var connection = {
|
|
97
|
+
attentive: {
|
|
98
|
+
key: "organization:connection:attentive",
|
|
99
|
+
error: "Plan does not include Attentive connection",
|
|
100
|
+
feature: "Attentive connection"
|
|
101
|
+
},
|
|
102
|
+
// Klaviyo shipped without an entry here, which meant no plan GRANTED its
|
|
103
|
+
// key and the feature gate denied every non-admin request — a latent 403
|
|
104
|
+
// found while adding Attentive. getPlanFeature answers granted:false for a
|
|
105
|
+
// key absent from the plan's map, so a manifest feature key that appears in
|
|
106
|
+
// no plan list is a connection only admins can manage.
|
|
107
|
+
klaviyo: {
|
|
108
|
+
key: "organization:connection:klaviyo",
|
|
109
|
+
error: "Plan does not include Klaviyo connection",
|
|
110
|
+
feature: "Klaviyo connection"
|
|
111
|
+
},
|
|
97
112
|
mailchimp: {
|
|
98
113
|
key: "organization:connection:mailchimp",
|
|
99
114
|
error: "Plan does not include Mailchimp connection",
|
package/dist/features.d.cts
CHANGED
|
@@ -68,6 +68,21 @@ const field = {
|
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
const connection = {
|
|
71
|
+
attentive : {
|
|
72
|
+
key : 'organization:connection:attentive',
|
|
73
|
+
error : 'Plan does not include Attentive connection',
|
|
74
|
+
feature : 'Attentive connection'
|
|
75
|
+
},
|
|
76
|
+
// Klaviyo shipped without an entry here, which meant no plan GRANTED its
|
|
77
|
+
// key and the feature gate denied every non-admin request — a latent 403
|
|
78
|
+
// found while adding Attentive. getPlanFeature answers granted:false for a
|
|
79
|
+
// key absent from the plan's map, so a manifest feature key that appears in
|
|
80
|
+
// no plan list is a connection only admins can manage.
|
|
81
|
+
klaviyo : {
|
|
82
|
+
key : 'organization:connection:klaviyo',
|
|
83
|
+
error : 'Plan does not include Klaviyo connection',
|
|
84
|
+
feature : 'Klaviyo connection'
|
|
85
|
+
},
|
|
71
86
|
mailchimp : {
|
|
72
87
|
key : 'organization:connection:mailchimp',
|
|
73
88
|
error : 'Plan does not include Mailchimp connection',
|
package/dist/features.d.ts
CHANGED
|
@@ -68,6 +68,21 @@ const field = {
|
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
const connection = {
|
|
71
|
+
attentive : {
|
|
72
|
+
key : 'organization:connection:attentive',
|
|
73
|
+
error : 'Plan does not include Attentive connection',
|
|
74
|
+
feature : 'Attentive connection'
|
|
75
|
+
},
|
|
76
|
+
// Klaviyo shipped without an entry here, which meant no plan GRANTED its
|
|
77
|
+
// key and the feature gate denied every non-admin request — a latent 403
|
|
78
|
+
// found while adding Attentive. getPlanFeature answers granted:false for a
|
|
79
|
+
// key absent from the plan's map, so a manifest feature key that appears in
|
|
80
|
+
// no plan list is a connection only admins can manage.
|
|
81
|
+
klaviyo : {
|
|
82
|
+
key : 'organization:connection:klaviyo',
|
|
83
|
+
error : 'Plan does not include Klaviyo connection',
|
|
84
|
+
feature : 'Klaviyo connection'
|
|
85
|
+
},
|
|
71
86
|
mailchimp : {
|
|
72
87
|
key : 'organization:connection:mailchimp',
|
|
73
88
|
error : 'Plan does not include Mailchimp connection',
|
package/dist/features.js
CHANGED
|
@@ -66,6 +66,21 @@ var field = {
|
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
68
|
var connection = {
|
|
69
|
+
attentive: {
|
|
70
|
+
key: "organization:connection:attentive",
|
|
71
|
+
error: "Plan does not include Attentive connection",
|
|
72
|
+
feature: "Attentive connection"
|
|
73
|
+
},
|
|
74
|
+
// Klaviyo shipped without an entry here, which meant no plan GRANTED its
|
|
75
|
+
// key and the feature gate denied every non-admin request — a latent 403
|
|
76
|
+
// found while adding Attentive. getPlanFeature answers granted:false for a
|
|
77
|
+
// key absent from the plan's map, so a manifest feature key that appears in
|
|
78
|
+
// no plan list is a connection only admins can manage.
|
|
79
|
+
klaviyo: {
|
|
80
|
+
key: "organization:connection:klaviyo",
|
|
81
|
+
error: "Plan does not include Klaviyo connection",
|
|
82
|
+
feature: "Klaviyo connection"
|
|
83
|
+
},
|
|
69
84
|
mailchimp: {
|
|
70
85
|
key: "organization:connection:mailchimp",
|
|
71
86
|
error: "Plan does not include Mailchimp connection",
|
package/dist/fields-validate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// lib/phone.js
|
|
2
|
-
import { parsePhoneNumberFromString, isValidPhoneNumber } from "libphonenumber-js";
|
|
2
|
+
import { AsYouType, parsePhoneNumberFromString, isValidPhoneNumber } from "libphonenumber-js";
|
|
3
3
|
var isValid = (value, country) => {
|
|
4
4
|
if (!value) return false;
|
|
5
5
|
try {
|
package/dist/phone.cjs
CHANGED
|
@@ -19,6 +19,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
// lib/phone.js
|
|
20
20
|
var phone_exports = {};
|
|
21
21
|
__export(phone_exports, {
|
|
22
|
+
detectCountry: () => detectCountry,
|
|
22
23
|
isValid: () => isValid,
|
|
23
24
|
toE164: () => toE164
|
|
24
25
|
});
|
|
@@ -33,6 +34,16 @@ var toE164 = (value, country) => {
|
|
|
33
34
|
return null;
|
|
34
35
|
}
|
|
35
36
|
};
|
|
37
|
+
var detectCountry = (value) => {
|
|
38
|
+
if (!value) return null;
|
|
39
|
+
try {
|
|
40
|
+
const parser = new import_libphonenumber_js.AsYouType();
|
|
41
|
+
parser.input(String(value));
|
|
42
|
+
return parser.getCountry() || null;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
36
47
|
var isValid = (value, country) => {
|
|
37
48
|
if (!value) return false;
|
|
38
49
|
try {
|
|
@@ -43,6 +54,7 @@ var isValid = (value, country) => {
|
|
|
43
54
|
};
|
|
44
55
|
// Annotate the CommonJS export names for ESM import in node:
|
|
45
56
|
0 && (module.exports = {
|
|
57
|
+
detectCountry,
|
|
46
58
|
isValid,
|
|
47
59
|
toE164
|
|
48
60
|
});
|
package/dist/phone.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isValidPhoneNumber, parsePhoneNumberFromString } from 'libphonenumber-js';
|
|
1
|
+
import { AsYouType, isValidPhoneNumber, parsePhoneNumberFromString } from 'libphonenumber-js';
|
|
2
2
|
|
|
3
3
|
// Canonicalize a phone value to E.164 (e.g. '+15551234567'), or null if it cannot
|
|
4
4
|
// be parsed. `country` (ISO 3166-1 alpha-2, e.g. 'US') is only needed for
|
|
@@ -24,6 +24,34 @@ const toE164 = ( value, country ) => {
|
|
|
24
24
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
// Resolve which country an entered number belongs to (ISO 3166-1 alpha-2), or null
|
|
28
|
+
// while it is still ambiguous. Backs the country selector on phone fields, which
|
|
29
|
+
// follows the number the visitor types rather than asking them to set it first.
|
|
30
|
+
// libphonenumber only commits to a country once the number is COMPLETE: '+1604…'
|
|
31
|
+
// stays null until the final digit because Canada and the US share the +1 calling
|
|
32
|
+
// code and are told apart by area code. Callers MUST treat null as "leave the
|
|
33
|
+
// current selection alone" — never as "reset it" — or every keystroke short of a
|
|
34
|
+
// finished number would wipe the visitor's country.
|
|
35
|
+
const detectCountry = ( value ) => {
|
|
36
|
+
|
|
37
|
+
if( ! value ) return null;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
|
|
41
|
+
const parser = new AsYouType();
|
|
42
|
+
|
|
43
|
+
parser.input( String( value ) );
|
|
44
|
+
|
|
45
|
+
return parser.getCountry() || null;
|
|
46
|
+
|
|
47
|
+
} catch {
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
};
|
|
54
|
+
|
|
27
55
|
// Strict validity check (wraps libphonenumber-js). Mirrors the validation
|
|
28
56
|
// drawbridge-api's submission route already performs on lead phone input.
|
|
29
57
|
const isValid = ( value, country ) => {
|
|
@@ -42,4 +70,4 @@ const isValid = ( value, country ) => {
|
|
|
42
70
|
|
|
43
71
|
};
|
|
44
72
|
|
|
45
|
-
export { isValid, toE164 };
|
|
73
|
+
export { detectCountry, isValid, toE164 };
|
package/dist/phone.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isValidPhoneNumber, parsePhoneNumberFromString } from 'libphonenumber-js';
|
|
1
|
+
import { AsYouType, isValidPhoneNumber, parsePhoneNumberFromString } from 'libphonenumber-js';
|
|
2
2
|
|
|
3
3
|
// Canonicalize a phone value to E.164 (e.g. '+15551234567'), or null if it cannot
|
|
4
4
|
// be parsed. `country` (ISO 3166-1 alpha-2, e.g. 'US') is only needed for
|
|
@@ -24,6 +24,34 @@ const toE164 = ( value, country ) => {
|
|
|
24
24
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
// Resolve which country an entered number belongs to (ISO 3166-1 alpha-2), or null
|
|
28
|
+
// while it is still ambiguous. Backs the country selector on phone fields, which
|
|
29
|
+
// follows the number the visitor types rather than asking them to set it first.
|
|
30
|
+
// libphonenumber only commits to a country once the number is COMPLETE: '+1604…'
|
|
31
|
+
// stays null until the final digit because Canada and the US share the +1 calling
|
|
32
|
+
// code and are told apart by area code. Callers MUST treat null as "leave the
|
|
33
|
+
// current selection alone" — never as "reset it" — or every keystroke short of a
|
|
34
|
+
// finished number would wipe the visitor's country.
|
|
35
|
+
const detectCountry = ( value ) => {
|
|
36
|
+
|
|
37
|
+
if( ! value ) return null;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
|
|
41
|
+
const parser = new AsYouType();
|
|
42
|
+
|
|
43
|
+
parser.input( String( value ) );
|
|
44
|
+
|
|
45
|
+
return parser.getCountry() || null;
|
|
46
|
+
|
|
47
|
+
} catch {
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
};
|
|
54
|
+
|
|
27
55
|
// Strict validity check (wraps libphonenumber-js). Mirrors the validation
|
|
28
56
|
// drawbridge-api's submission route already performs on lead phone input.
|
|
29
57
|
const isValid = ( value, country ) => {
|
|
@@ -42,4 +70,4 @@ const isValid = ( value, country ) => {
|
|
|
42
70
|
|
|
43
71
|
};
|
|
44
72
|
|
|
45
|
-
export { isValid, toE164 };
|
|
73
|
+
export { detectCountry, isValid, toE164 };
|
package/dist/phone.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// lib/phone.js
|
|
2
|
-
import { parsePhoneNumberFromString, isValidPhoneNumber } from "libphonenumber-js";
|
|
2
|
+
import { AsYouType, parsePhoneNumberFromString, isValidPhoneNumber } from "libphonenumber-js";
|
|
3
3
|
var toE164 = (value, country) => {
|
|
4
4
|
if (!value) return null;
|
|
5
5
|
try {
|
|
@@ -9,6 +9,16 @@ var toE164 = (value, country) => {
|
|
|
9
9
|
return null;
|
|
10
10
|
}
|
|
11
11
|
};
|
|
12
|
+
var detectCountry = (value) => {
|
|
13
|
+
if (!value) return null;
|
|
14
|
+
try {
|
|
15
|
+
const parser = new AsYouType();
|
|
16
|
+
parser.input(String(value));
|
|
17
|
+
return parser.getCountry() || null;
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
12
22
|
var isValid = (value, country) => {
|
|
13
23
|
if (!value) return false;
|
|
14
24
|
try {
|
|
@@ -18,6 +28,7 @@ var isValid = (value, country) => {
|
|
|
18
28
|
}
|
|
19
29
|
};
|
|
20
30
|
export {
|
|
31
|
+
detectCountry,
|
|
21
32
|
isValid,
|
|
22
33
|
toE164
|
|
23
34
|
};
|