@drawbridge/drawbridge-utils 0.0.143 → 0.0.145
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 +99 -10
- package/dist/connections/index.d.cts +118 -10
- package/dist/connections/index.d.ts +118 -10
- package/dist/connections/index.js +101 -12
- package/dist/providers.cjs +120 -50
- package/dist/providers.d.cts +14 -3
- package/dist/providers.d.ts +14 -3
- package/dist/providers.js +122 -52
- package/package.json +1 -1
package/dist/providers.js
CHANGED
|
@@ -3228,6 +3228,45 @@ var toCanonicalEmail = (value) => {
|
|
|
3228
3228
|
return local + "@" + domain;
|
|
3229
3229
|
};
|
|
3230
3230
|
|
|
3231
|
+
// lib/encrypt.js
|
|
3232
|
+
import crypto2 from "crypto";
|
|
3233
|
+
|
|
3234
|
+
// lib/token.js
|
|
3235
|
+
import crypto from "crypto";
|
|
3236
|
+
var generate = (bytes = 32, encoding = "base64url") => {
|
|
3237
|
+
const buf = crypto.randomBytes(bytes);
|
|
3238
|
+
return encoding ? buf.toString(encoding) : buf;
|
|
3239
|
+
};
|
|
3240
|
+
|
|
3241
|
+
// lib/encrypt.js
|
|
3242
|
+
var ALGORITHM = "aes-256-gcm";
|
|
3243
|
+
var getKey = () => Buffer.from(process.env.ENCRYPT_CONNECTION_SECRET, "hex");
|
|
3244
|
+
var encrypt = (value) => {
|
|
3245
|
+
const iv = generate(12, null);
|
|
3246
|
+
const cipher = crypto2.createCipheriv(ALGORITHM, getKey(), iv);
|
|
3247
|
+
const data2 = Buffer.concat([
|
|
3248
|
+
cipher.update(JSON.stringify(value), "utf8"),
|
|
3249
|
+
cipher.final()
|
|
3250
|
+
]);
|
|
3251
|
+
const tag = cipher.getAuthTag();
|
|
3252
|
+
return [iv, tag, data2].map((b) => b.toString("hex")).join(":");
|
|
3253
|
+
};
|
|
3254
|
+
var decrypt = (value) => {
|
|
3255
|
+
if (typeof value !== "string") return value;
|
|
3256
|
+
const [ivHex, tagHex, dataHex] = value.split(":");
|
|
3257
|
+
const decipher = crypto2.createDecipheriv(
|
|
3258
|
+
ALGORITHM,
|
|
3259
|
+
getKey(),
|
|
3260
|
+
Buffer.from(ivHex, "hex")
|
|
3261
|
+
);
|
|
3262
|
+
decipher.setAuthTag(Buffer.from(tagHex, "hex"));
|
|
3263
|
+
const result = Buffer.concat([
|
|
3264
|
+
decipher.update(Buffer.from(dataHex, "hex")),
|
|
3265
|
+
decipher.final()
|
|
3266
|
+
]);
|
|
3267
|
+
return JSON.parse(result.toString("utf8"));
|
|
3268
|
+
};
|
|
3269
|
+
|
|
3231
3270
|
// lib/slugify.js
|
|
3232
3271
|
import slug from "slugify";
|
|
3233
3272
|
var slugify = (value, nochars = false) => {
|
|
@@ -3282,6 +3321,8 @@ var generateDiscountCode = customAlphabet2("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
|
3282
3321
|
var ORDER_EVENT_HANDLE = slugify("drawbridge-orders");
|
|
3283
3322
|
var REFRESH_TOKEN_FRESHNESS_BUFFER_MS = 5 * 24 * 60 * 60 * 1e3;
|
|
3284
3323
|
var OAUTH_ERROR_SOURCE = "oauth";
|
|
3324
|
+
var BILLING_ERROR_SOURCE = "billing";
|
|
3325
|
+
var BILLING_ERROR_MESSAGE = "Order billing isn't set up for this store \u2014 reselect and approve the plan from the Shopify connection page.";
|
|
3285
3326
|
var OAUTH_GRANT_REVOKED_CODES = ["application_cannot_be_found", "invalid_grant"];
|
|
3286
3327
|
var inbound = {
|
|
3287
3328
|
headers: {
|
|
@@ -3802,6 +3843,9 @@ var shopify_default2 = {
|
|
|
3802
3843
|
}
|
|
3803
3844
|
}
|
|
3804
3845
|
const billable = (org == null ? void 0 : org.billingProvider) === "shopify" && fee > 0 && !backfill;
|
|
3846
|
+
const providerRow = billable ? await read.get({ collection: "provider", query: { slug: "shopify" } }) : null;
|
|
3847
|
+
const { orderEventHandle } = (providerRow == null ? void 0 : providerRow.settings) ? decrypt(providerRow.settings) : {};
|
|
3848
|
+
const handle = typeof orderEventHandle === "string" && slugify(orderEventHandle) || ORDER_EVENT_HANDLE;
|
|
3805
3849
|
if (billable && !((_f = connection2 == null ? void 0 : connection2.source) == null ? void 0 : _f.id)) {
|
|
3806
3850
|
(_g = logger2 == null ? void 0 : logger2.error) == null ? void 0 : _g.call(logger2, new Error(
|
|
3807
3851
|
"shopify.usage.billing.skipped on order " + orderId + ": " + Math.round(fee * 100) + " cents not billed \u2014 connection " + (connection2 == null ? void 0 : connection2.id) + " has no source.id"
|
|
@@ -3820,7 +3864,7 @@ var shopify_default2 = {
|
|
|
3820
3864
|
// verify it against the transaction prefix and refuse a
|
|
3821
3865
|
// drifted pair — idempotency keys are permanent, so a
|
|
3822
3866
|
// mistraceable event can never be resent under its own key.
|
|
3823
|
-
handle
|
|
3867
|
+
handle,
|
|
3824
3868
|
idempotencyKey: String(orderId),
|
|
3825
3869
|
orderDocId,
|
|
3826
3870
|
orderId: String(orderId),
|
|
@@ -3830,7 +3874,7 @@ var shopify_default2 = {
|
|
|
3830
3874
|
// here — the event handle plus the order id — and sent as the
|
|
3831
3875
|
// event's `reference`. queue/usage.js stamps the same id onto
|
|
3832
3876
|
// the order as billed.transaction.
|
|
3833
|
-
transaction:
|
|
3877
|
+
transaction: handle + "." + orderId,
|
|
3834
3878
|
value: Math.round(fee * 100)
|
|
3835
3879
|
},
|
|
3836
3880
|
name: "billing",
|
|
@@ -4035,6 +4079,46 @@ var shopify_default2 = {
|
|
|
4035
4079
|
query: { id: connection2.id }
|
|
4036
4080
|
});
|
|
4037
4081
|
}
|
|
4082
|
+
const hasBillingIssue = (connection2.errors || []).some((entry) => entry.source === BILLING_ERROR_SOURCE);
|
|
4083
|
+
if (metered === null && !hasBillingIssue) {
|
|
4084
|
+
writes.push({
|
|
4085
|
+
collection: "connection",
|
|
4086
|
+
data: {
|
|
4087
|
+
$push: {
|
|
4088
|
+
errors: {
|
|
4089
|
+
message: BILLING_ERROR_MESSAGE,
|
|
4090
|
+
source: BILLING_ERROR_SOURCE
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
},
|
|
4094
|
+
operation: "update",
|
|
4095
|
+
query: { id: connection2.id }
|
|
4096
|
+
});
|
|
4097
|
+
}
|
|
4098
|
+
if (typeof metered === "string" && hasBillingIssue) {
|
|
4099
|
+
writes.push({
|
|
4100
|
+
collection: "connection",
|
|
4101
|
+
data: {
|
|
4102
|
+
$pull: {
|
|
4103
|
+
errors: { source: BILLING_ERROR_SOURCE }
|
|
4104
|
+
}
|
|
4105
|
+
},
|
|
4106
|
+
operation: "update",
|
|
4107
|
+
query: { id: connection2.id }
|
|
4108
|
+
});
|
|
4109
|
+
writes.push({
|
|
4110
|
+
collection: "connection",
|
|
4111
|
+
data: {
|
|
4112
|
+
$set: { status: "active" }
|
|
4113
|
+
},
|
|
4114
|
+
operation: "update",
|
|
4115
|
+
query: {
|
|
4116
|
+
id: connection2.id,
|
|
4117
|
+
status: "error",
|
|
4118
|
+
"errors.0": { $exists: false }
|
|
4119
|
+
}
|
|
4120
|
+
});
|
|
4121
|
+
}
|
|
4038
4122
|
return {
|
|
4039
4123
|
writes,
|
|
4040
4124
|
enqueues: [{
|
|
@@ -4199,7 +4283,15 @@ var shopify_default2 = {
|
|
|
4199
4283
|
{ input: "text", key: "apiKey", credential: "SHOPIFY_API_KEY", label: "API key", required: true },
|
|
4200
4284
|
{ input: "password", key: "apiSecret", credential: "SHOPIFY_API_SECRET", label: "API secret", redact: true, required: true },
|
|
4201
4285
|
{ input: "text", key: "appHandle", credential: "SHOPIFY_APP_HANDLE", label: "App handle", required: true },
|
|
4202
|
-
{ input: "text", key: "listingUrl", credential: "SHOPIFY_APP_LISTING_URL", label: "App listing URL", required: true }
|
|
4286
|
+
{ input: "text", key: "listingUrl", credential: "SHOPIFY_APP_LISTING_URL", label: "App listing URL", required: true },
|
|
4287
|
+
// The usage meter's event handle, when the plan config's meter ever
|
|
4288
|
+
// changes: stored value overrides the built-in default
|
|
4289
|
+
// (events.order.handle) at the mint point, and `format : 'slug'`
|
|
4290
|
+
// normalizes it on save — a handle can carry no spaces or capitals,
|
|
4291
|
+
// and classification against it is case-sensitive. Optional: empty
|
|
4292
|
+
// means the default, and the sender still refuses any handle that
|
|
4293
|
+
// disagrees with the transaction it minted.
|
|
4294
|
+
{ format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", required: false, setting: true }
|
|
4203
4295
|
]
|
|
4204
4296
|
},
|
|
4205
4297
|
// A pre-launch integration: it only surfaces once the App Store listing
|
|
@@ -4217,9 +4309,18 @@ var shopify_default2 = {
|
|
|
4217
4309
|
// there is nothing further to choose. `shop` absent means the install did not
|
|
4218
4310
|
// finish, which is a credential problem rather than a setup one, so the
|
|
4219
4311
|
// stored status already says so.
|
|
4220
|
-
//
|
|
4221
|
-
//
|
|
4222
|
-
|
|
4312
|
+
//
|
|
4313
|
+
// ONE presentation downgrade: an ACTIVE store whose approval carries no
|
|
4314
|
+
// usage meter (source.metered explicitly null) PRESENTS as error, so the
|
|
4315
|
+
// connections list shows the red state and the merchant knows to click in
|
|
4316
|
+
// — where the Fix-order-billing card and the billing Issue explain the
|
|
4317
|
+
// click. The STORED status stays active on purpose: serving and tracking
|
|
4318
|
+
// gate on it, and the orders recorded while the merchant fixes the plan
|
|
4319
|
+
// are the ones billed afterward.
|
|
4320
|
+
status: (data2) => {
|
|
4321
|
+
var _a;
|
|
4322
|
+
return (data2 == null ? void 0 : data2.status) === "active" && ((_a = data2 == null ? void 0 : data2.source) == null ? void 0 : _a.metered) === null ? "error" : data2 == null ? void 0 : data2.status;
|
|
4323
|
+
},
|
|
4223
4324
|
// Step types name the CAPABILITY, not this vendor. A second store platform
|
|
4224
4325
|
// implements the same four commerce steps, and the connection on the step
|
|
4225
4326
|
// says which store it runs against — so a merchant sees one "Create
|
|
@@ -4341,8 +4442,9 @@ var shopify_default2 = {
|
|
|
4341
4442
|
...(data2 == null ? void 0 : data2.status) === "pending" ? [
|
|
4342
4443
|
{
|
|
4343
4444
|
action: "billing",
|
|
4344
|
-
message: "Approve the Drawbridge plan to activate this connection. You'll
|
|
4345
|
-
title: "Approve your plan"
|
|
4445
|
+
message: "Approve the Drawbridge plan to activate this connection. You'll be asked to select a plan on Shopify, then everything else happens here.",
|
|
4446
|
+
title: "Approve your plan",
|
|
4447
|
+
type: "warning"
|
|
4346
4448
|
}
|
|
4347
4449
|
] : [],
|
|
4348
4450
|
// The store's ACTIVE approval carries no usage component — stamped by
|
|
@@ -4355,8 +4457,9 @@ var shopify_default2 = {
|
|
|
4355
4457
|
...(data2 == null ? void 0 : data2.status) === "active" && ((_a = data2 == null ? void 0 : data2.source) == null ? void 0 : _a.metered) === null ? [
|
|
4356
4458
|
{
|
|
4357
4459
|
action: "billing",
|
|
4358
|
-
message: "Order billing isn't set up for this store, so campaign-attributed orders aren't being charged.
|
|
4359
|
-
title: "Fix order billing"
|
|
4460
|
+
message: "Order billing isn't set up for this store, so campaign-attributed orders aren't being charged. You'll need to reselect your plan on Shopify and approve it to fix this.",
|
|
4461
|
+
title: "Fix order billing",
|
|
4462
|
+
type: "error"
|
|
4360
4463
|
}
|
|
4361
4464
|
] : []
|
|
4362
4465
|
];
|
|
@@ -4365,7 +4468,7 @@ var shopify_default2 = {
|
|
|
4365
4468
|
};
|
|
4366
4469
|
|
|
4367
4470
|
// lib/connections/providers/webhook.js
|
|
4368
|
-
import
|
|
4471
|
+
import crypto3 from "crypto";
|
|
4369
4472
|
|
|
4370
4473
|
// lib/safe-http.js
|
|
4371
4474
|
import dns2 from "dns";
|
|
@@ -4600,7 +4703,7 @@ var webhook_default = {
|
|
|
4600
4703
|
request2.body = body;
|
|
4601
4704
|
const outgoing = { ...headers };
|
|
4602
4705
|
if (settings == null ? void 0 : settings.secret) {
|
|
4603
|
-
outgoing["X-Drawbridge-Signature"] = "sha256=" +
|
|
4706
|
+
outgoing["X-Drawbridge-Signature"] = "sha256=" + crypto3.createHmac("sha256", settings.secret).update(JSON.stringify(body)).digest("hex");
|
|
4604
4707
|
}
|
|
4605
4708
|
const response = await send2({ body, headers: outgoing, method, url });
|
|
4606
4709
|
return { message: "Webhook POSTed to " + url + ".", request: request2, response: response || { delivered: true } };
|
|
@@ -4879,6 +4982,11 @@ var publicConnectionKeys = Object.freeze([
|
|
|
4879
4982
|
"settings",
|
|
4880
4983
|
"shop",
|
|
4881
4984
|
"slug",
|
|
4985
|
+
// Vendor identity plus the metered billing stamp — domain, id, label,
|
|
4986
|
+
// metered — no credentials live here (those are the encrypted settings
|
|
4987
|
+
// blob). The status and tasks hooks pivot on source.metered, so stripping
|
|
4988
|
+
// it here left the presented status blind to the one state it must show.
|
|
4989
|
+
"source",
|
|
4882
4990
|
"status",
|
|
4883
4991
|
"tasks",
|
|
4884
4992
|
"title",
|
|
@@ -4886,45 +4994,6 @@ var publicConnectionKeys = Object.freeze([
|
|
|
4886
4994
|
"warnings"
|
|
4887
4995
|
]);
|
|
4888
4996
|
|
|
4889
|
-
// lib/encrypt.js
|
|
4890
|
-
import crypto3 from "crypto";
|
|
4891
|
-
|
|
4892
|
-
// lib/token.js
|
|
4893
|
-
import crypto2 from "crypto";
|
|
4894
|
-
var generate = (bytes = 32, encoding = "base64url") => {
|
|
4895
|
-
const buf = crypto2.randomBytes(bytes);
|
|
4896
|
-
return encoding ? buf.toString(encoding) : buf;
|
|
4897
|
-
};
|
|
4898
|
-
|
|
4899
|
-
// lib/encrypt.js
|
|
4900
|
-
var ALGORITHM = "aes-256-gcm";
|
|
4901
|
-
var getKey = () => Buffer.from(process.env.ENCRYPT_CONNECTION_SECRET, "hex");
|
|
4902
|
-
var encrypt = (value) => {
|
|
4903
|
-
const iv = generate(12, null);
|
|
4904
|
-
const cipher = crypto3.createCipheriv(ALGORITHM, getKey(), iv);
|
|
4905
|
-
const data2 = Buffer.concat([
|
|
4906
|
-
cipher.update(JSON.stringify(value), "utf8"),
|
|
4907
|
-
cipher.final()
|
|
4908
|
-
]);
|
|
4909
|
-
const tag = cipher.getAuthTag();
|
|
4910
|
-
return [iv, tag, data2].map((b) => b.toString("hex")).join(":");
|
|
4911
|
-
};
|
|
4912
|
-
var decrypt = (value) => {
|
|
4913
|
-
if (typeof value !== "string") return value;
|
|
4914
|
-
const [ivHex, tagHex, dataHex] = value.split(":");
|
|
4915
|
-
const decipher = crypto3.createDecipheriv(
|
|
4916
|
-
ALGORITHM,
|
|
4917
|
-
getKey(),
|
|
4918
|
-
Buffer.from(ivHex, "hex")
|
|
4919
|
-
);
|
|
4920
|
-
decipher.setAuthTag(Buffer.from(tagHex, "hex"));
|
|
4921
|
-
const result = Buffer.concat([
|
|
4922
|
-
decipher.update(Buffer.from(dataHex, "hex")),
|
|
4923
|
-
decipher.final()
|
|
4924
|
-
]);
|
|
4925
|
-
return JSON.parse(result.toString("utf8"));
|
|
4926
|
-
};
|
|
4927
|
-
|
|
4928
4997
|
// lib/providers.js
|
|
4929
4998
|
var providerFields = (slug2) => {
|
|
4930
4999
|
var _a;
|
|
@@ -4964,9 +5033,10 @@ var saveProviderSettings = async ({ authenticated, clear, controller, settings,
|
|
|
4964
5033
|
});
|
|
4965
5034
|
const stored = (existing == null ? void 0 : existing.settings) ? decrypt(existing.settings) : {};
|
|
4966
5035
|
const pasted = (value) => typeof value === "string" ? value.trim().replace(/^['"]+|['"]+$/g, "") : value;
|
|
5036
|
+
const normalized = (field2, value) => field2.format === "slug" && typeof value === "string" && value ? slugify(value) : value;
|
|
4967
5037
|
const merged = mergeSettings({
|
|
4968
5038
|
existing: stored,
|
|
4969
|
-
incoming: Object.fromEntries(fields2.map((field2) => [field2.key, pasted(settings == null ? void 0 : settings[field2.key])]))
|
|
5039
|
+
incoming: Object.fromEntries(fields2.map((field2) => [field2.key, normalized(field2, pasted(settings == null ? void 0 : settings[field2.key]))]))
|
|
4970
5040
|
});
|
|
4971
5041
|
for (const key of Array.isArray(clear) ? clear : []) {
|
|
4972
5042
|
if (fields2.some((field2) => field2.key === key)) delete merged[key];
|
package/package.json
CHANGED