@drawbridge/drawbridge-utils 0.0.142 → 0.0.144
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 +98 -20
- package/dist/connections/index.d.cts +137 -30
- package/dist/connections/index.d.ts +137 -30
- package/dist/connections/index.js +100 -22
- package/dist/providers.cjs +119 -60
- package/dist/providers.d.cts +14 -3
- package/dist/providers.d.ts +14 -3
- package/dist/providers.js +121 -62
- package/package.json +1 -1
package/dist/providers.cjs
CHANGED
|
@@ -3272,6 +3272,45 @@ var toCanonicalEmail = (value) => {
|
|
|
3272
3272
|
return local + "@" + domain;
|
|
3273
3273
|
};
|
|
3274
3274
|
|
|
3275
|
+
// lib/encrypt.js
|
|
3276
|
+
var import_crypto2 = __toESM(require("crypto"), 1);
|
|
3277
|
+
|
|
3278
|
+
// lib/token.js
|
|
3279
|
+
var import_crypto = __toESM(require("crypto"), 1);
|
|
3280
|
+
var generate = (bytes = 32, encoding = "base64url") => {
|
|
3281
|
+
const buf = import_crypto.default.randomBytes(bytes);
|
|
3282
|
+
return encoding ? buf.toString(encoding) : buf;
|
|
3283
|
+
};
|
|
3284
|
+
|
|
3285
|
+
// lib/encrypt.js
|
|
3286
|
+
var ALGORITHM = "aes-256-gcm";
|
|
3287
|
+
var getKey = () => Buffer.from(process.env.ENCRYPT_CONNECTION_SECRET, "hex");
|
|
3288
|
+
var encrypt = (value) => {
|
|
3289
|
+
const iv = generate(12, null);
|
|
3290
|
+
const cipher = import_crypto2.default.createCipheriv(ALGORITHM, getKey(), iv);
|
|
3291
|
+
const data2 = Buffer.concat([
|
|
3292
|
+
cipher.update(JSON.stringify(value), "utf8"),
|
|
3293
|
+
cipher.final()
|
|
3294
|
+
]);
|
|
3295
|
+
const tag = cipher.getAuthTag();
|
|
3296
|
+
return [iv, tag, data2].map((b) => b.toString("hex")).join(":");
|
|
3297
|
+
};
|
|
3298
|
+
var decrypt = (value) => {
|
|
3299
|
+
if (typeof value !== "string") return value;
|
|
3300
|
+
const [ivHex, tagHex, dataHex] = value.split(":");
|
|
3301
|
+
const decipher = import_crypto2.default.createDecipheriv(
|
|
3302
|
+
ALGORITHM,
|
|
3303
|
+
getKey(),
|
|
3304
|
+
Buffer.from(ivHex, "hex")
|
|
3305
|
+
);
|
|
3306
|
+
decipher.setAuthTag(Buffer.from(tagHex, "hex"));
|
|
3307
|
+
const result = Buffer.concat([
|
|
3308
|
+
decipher.update(Buffer.from(dataHex, "hex")),
|
|
3309
|
+
decipher.final()
|
|
3310
|
+
]);
|
|
3311
|
+
return JSON.parse(result.toString("utf8"));
|
|
3312
|
+
};
|
|
3313
|
+
|
|
3275
3314
|
// lib/slugify.js
|
|
3276
3315
|
var import_slugify = __toESM(require("slugify"), 1);
|
|
3277
3316
|
var slugify = (value, nochars = false) => {
|
|
@@ -3326,6 +3365,8 @@ var generateDiscountCode = (0, import_nanoid2.customAlphabet)("0123456789ABCDEFG
|
|
|
3326
3365
|
var ORDER_EVENT_HANDLE = slugify("drawbridge-orders");
|
|
3327
3366
|
var REFRESH_TOKEN_FRESHNESS_BUFFER_MS = 5 * 24 * 60 * 60 * 1e3;
|
|
3328
3367
|
var OAUTH_ERROR_SOURCE = "oauth";
|
|
3368
|
+
var BILLING_ERROR_SOURCE = "billing";
|
|
3369
|
+
var BILLING_ERROR_MESSAGE = "Order billing isn't set up for this store \u2014 reselect and approve the plan from the Shopify connection page.";
|
|
3329
3370
|
var OAUTH_GRANT_REVOKED_CODES = ["application_cannot_be_found", "invalid_grant"];
|
|
3330
3371
|
var inbound = {
|
|
3331
3372
|
headers: {
|
|
@@ -3846,6 +3887,9 @@ var shopify_default2 = {
|
|
|
3846
3887
|
}
|
|
3847
3888
|
}
|
|
3848
3889
|
const billable = (org == null ? void 0 : org.billingProvider) === "shopify" && fee > 0 && !backfill;
|
|
3890
|
+
const providerRow = billable ? await read.get({ collection: "provider", query: { slug: "shopify" } }) : null;
|
|
3891
|
+
const { orderEventHandle } = (providerRow == null ? void 0 : providerRow.settings) ? decrypt(providerRow.settings) : {};
|
|
3892
|
+
const handle = typeof orderEventHandle === "string" && slugify(orderEventHandle) || ORDER_EVENT_HANDLE;
|
|
3849
3893
|
if (billable && !((_f = connection2 == null ? void 0 : connection2.source) == null ? void 0 : _f.id)) {
|
|
3850
3894
|
(_g = logger2 == null ? void 0 : logger2.error) == null ? void 0 : _g.call(logger2, new Error(
|
|
3851
3895
|
"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"
|
|
@@ -3864,7 +3908,7 @@ var shopify_default2 = {
|
|
|
3864
3908
|
// verify it against the transaction prefix and refuse a
|
|
3865
3909
|
// drifted pair — idempotency keys are permanent, so a
|
|
3866
3910
|
// mistraceable event can never be resent under its own key.
|
|
3867
|
-
handle
|
|
3911
|
+
handle,
|
|
3868
3912
|
idempotencyKey: String(orderId),
|
|
3869
3913
|
orderDocId,
|
|
3870
3914
|
orderId: String(orderId),
|
|
@@ -3874,7 +3918,7 @@ var shopify_default2 = {
|
|
|
3874
3918
|
// here — the event handle plus the order id — and sent as the
|
|
3875
3919
|
// event's `reference`. queue/usage.js stamps the same id onto
|
|
3876
3920
|
// the order as billed.transaction.
|
|
3877
|
-
transaction:
|
|
3921
|
+
transaction: handle + "." + orderId,
|
|
3878
3922
|
value: Math.round(fee * 100)
|
|
3879
3923
|
},
|
|
3880
3924
|
name: "billing",
|
|
@@ -4018,7 +4062,7 @@ var shopify_default2 = {
|
|
|
4018
4062
|
// against the ones the app now needs, and queues a webhook
|
|
4019
4063
|
// reconciliation.
|
|
4020
4064
|
health: async ({ connection: connection2, workflow }, { adminToken, logger: logger2, read, reconcileScopes, resolveSettings, rotateToken, shopify } = {}) => {
|
|
4021
|
-
var _a, _b;
|
|
4065
|
+
var _a, _b, _c;
|
|
4022
4066
|
const request2 = {
|
|
4023
4067
|
connectionId: workflow.connection,
|
|
4024
4068
|
organizationId: workflow.organization,
|
|
@@ -4040,7 +4084,7 @@ var shopify_default2 = {
|
|
|
4040
4084
|
const writes = [];
|
|
4041
4085
|
let source = connection2.source || null;
|
|
4042
4086
|
let sourceRepaired = false;
|
|
4043
|
-
let metered
|
|
4087
|
+
let metered;
|
|
4044
4088
|
let shopCurrency = null;
|
|
4045
4089
|
let billingProbeFailed = false;
|
|
4046
4090
|
try {
|
|
@@ -4059,26 +4103,66 @@ var shopify_default2 = {
|
|
|
4059
4103
|
domain: connection2.shop
|
|
4060
4104
|
});
|
|
4061
4105
|
if (subscriptions.length && subscriptions.every((subscription) => typeof subscription.metered === "boolean")) {
|
|
4062
|
-
metered = subscriptions.
|
|
4106
|
+
metered = ((_a = subscriptions.find((subscription) => subscription.usageLineItemId)) == null ? void 0 : _a.usageLineItemId) || null;
|
|
4063
4107
|
}
|
|
4064
4108
|
} catch (probeError) {
|
|
4065
4109
|
billingProbeFailed = true;
|
|
4066
|
-
(
|
|
4110
|
+
(_b = logger2 == null ? void 0 : logger2.warn) == null ? void 0 : _b.call(logger2, "shopify.health.billing.probe.failed", {
|
|
4067
4111
|
connectionId: connection2.id,
|
|
4068
4112
|
message: probeError == null ? void 0 : probeError.message,
|
|
4069
4113
|
shop: connection2.shop
|
|
4070
4114
|
});
|
|
4071
4115
|
}
|
|
4072
|
-
if (sourceRepaired || metered !==
|
|
4116
|
+
if (sourceRepaired || metered !== void 0 && metered !== (((_c = connection2.source) == null ? void 0 : _c.metered) ?? void 0)) {
|
|
4073
4117
|
writes.push({
|
|
4074
4118
|
collection: "connection",
|
|
4075
4119
|
data: {
|
|
4076
|
-
$set: sourceRepaired ? { source: { ...source, ...metered !==
|
|
4120
|
+
$set: sourceRepaired ? { source: { ...source, ...metered !== void 0 && { metered } } } : { "source.metered": metered }
|
|
4077
4121
|
},
|
|
4078
4122
|
operation: "update",
|
|
4079
4123
|
query: { id: connection2.id }
|
|
4080
4124
|
});
|
|
4081
4125
|
}
|
|
4126
|
+
const hasBillingIssue = (connection2.errors || []).some((entry) => entry.source === BILLING_ERROR_SOURCE);
|
|
4127
|
+
if (metered === null && !hasBillingIssue) {
|
|
4128
|
+
writes.push({
|
|
4129
|
+
collection: "connection",
|
|
4130
|
+
data: {
|
|
4131
|
+
$push: {
|
|
4132
|
+
errors: {
|
|
4133
|
+
message: BILLING_ERROR_MESSAGE,
|
|
4134
|
+
source: BILLING_ERROR_SOURCE
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
},
|
|
4138
|
+
operation: "update",
|
|
4139
|
+
query: { id: connection2.id }
|
|
4140
|
+
});
|
|
4141
|
+
}
|
|
4142
|
+
if (typeof metered === "string" && hasBillingIssue) {
|
|
4143
|
+
writes.push({
|
|
4144
|
+
collection: "connection",
|
|
4145
|
+
data: {
|
|
4146
|
+
$pull: {
|
|
4147
|
+
errors: { source: BILLING_ERROR_SOURCE }
|
|
4148
|
+
}
|
|
4149
|
+
},
|
|
4150
|
+
operation: "update",
|
|
4151
|
+
query: { id: connection2.id }
|
|
4152
|
+
});
|
|
4153
|
+
writes.push({
|
|
4154
|
+
collection: "connection",
|
|
4155
|
+
data: {
|
|
4156
|
+
$set: { status: "active" }
|
|
4157
|
+
},
|
|
4158
|
+
operation: "update",
|
|
4159
|
+
query: {
|
|
4160
|
+
id: connection2.id,
|
|
4161
|
+
status: "error",
|
|
4162
|
+
"errors.0": { $exists: false }
|
|
4163
|
+
}
|
|
4164
|
+
});
|
|
4165
|
+
}
|
|
4082
4166
|
return {
|
|
4083
4167
|
writes,
|
|
4084
4168
|
enqueues: [{
|
|
@@ -4093,11 +4177,12 @@ var shopify_default2 = {
|
|
|
4093
4177
|
options: { jobId: "connection.update.register." + workflow.connection + "." + (0, import_node_crypto5.randomUUID)() },
|
|
4094
4178
|
queue: "connection"
|
|
4095
4179
|
}],
|
|
4096
|
-
message: ((scopesMissing == null ? void 0 : scopesMissing.length) ? "Health check: ping ok, webhooks reconciled \u2014 connection errored, granted scopes are missing: " + scopesMissing.join(", ") + "." : refreshTokenRotated ? "Health check passed \u2014 refresh token rotated, ping ok, webhooks reconciled." : "Health check passed \u2014 ping ok, webhooks reconciled.") + (sourceRepaired ? " Billing source repaired from the live shop." : "") + (metered ===
|
|
4180
|
+
message: ((scopesMissing == null ? void 0 : scopesMissing.length) ? "Health check: ping ok, webhooks reconciled \u2014 connection errored, granted scopes are missing: " + scopesMissing.join(", ") + "." : refreshTokenRotated ? "Health check passed \u2014 refresh token rotated, ping ok, webhooks reconciled." : "Health check passed \u2014 ping ok, webhooks reconciled.") + (sourceRepaired ? " Billing source repaired from the live shop." : "") + (metered === null ? " No usage line on the store's approved plan \u2014 order billing needs the merchant to approve the updated plan." : ""),
|
|
4097
4181
|
request: request2,
|
|
4098
4182
|
response: {
|
|
4099
4183
|
billingProbeFailed,
|
|
4100
|
-
metered,
|
|
4184
|
+
metered: metered ?? null,
|
|
4185
|
+
meterProbed: metered !== void 0,
|
|
4101
4186
|
pingedAt: /* @__PURE__ */ new Date(),
|
|
4102
4187
|
refreshTokenExpiresAt: refreshTokenExpiresAt || null,
|
|
4103
4188
|
refreshTokenRotated,
|
|
@@ -4242,7 +4327,15 @@ var shopify_default2 = {
|
|
|
4242
4327
|
{ input: "text", key: "apiKey", credential: "SHOPIFY_API_KEY", label: "API key", required: true },
|
|
4243
4328
|
{ input: "password", key: "apiSecret", credential: "SHOPIFY_API_SECRET", label: "API secret", redact: true, required: true },
|
|
4244
4329
|
{ input: "text", key: "appHandle", credential: "SHOPIFY_APP_HANDLE", label: "App handle", required: true },
|
|
4245
|
-
{ input: "text", key: "listingUrl", credential: "SHOPIFY_APP_LISTING_URL", label: "App listing URL", required: true }
|
|
4330
|
+
{ input: "text", key: "listingUrl", credential: "SHOPIFY_APP_LISTING_URL", label: "App listing URL", required: true },
|
|
4331
|
+
// The usage meter's event handle, when the plan config's meter ever
|
|
4332
|
+
// changes: stored value overrides the built-in default
|
|
4333
|
+
// (events.order.handle) at the mint point, and `format : 'slug'`
|
|
4334
|
+
// normalizes it on save — a handle can carry no spaces or capitals,
|
|
4335
|
+
// and classification against it is case-sensitive. Optional: empty
|
|
4336
|
+
// means the default, and the sender still refuses any handle that
|
|
4337
|
+
// disagrees with the transaction it minted.
|
|
4338
|
+
{ format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", required: false, setting: true }
|
|
4246
4339
|
]
|
|
4247
4340
|
},
|
|
4248
4341
|
// A pre-launch integration: it only surfaces once the App Store listing
|
|
@@ -4384,20 +4477,24 @@ var shopify_default2 = {
|
|
|
4384
4477
|
...(data2 == null ? void 0 : data2.status) === "pending" ? [
|
|
4385
4478
|
{
|
|
4386
4479
|
action: "billing",
|
|
4387
|
-
message: "Approve the Drawbridge plan to activate this connection. You'll
|
|
4388
|
-
title: "Approve your plan"
|
|
4480
|
+
message: "Approve the Drawbridge plan to activate this connection. You'll be asked to select a plan on Shopify, then everything else happens here.",
|
|
4481
|
+
title: "Approve your plan",
|
|
4482
|
+
type: "warning"
|
|
4389
4483
|
}
|
|
4390
4484
|
] : [],
|
|
4391
4485
|
// The store's ACTIVE approval carries no usage component — stamped by
|
|
4392
|
-
// the daily health check and the approval webhook (`source.metered
|
|
4393
|
-
//
|
|
4394
|
-
//
|
|
4395
|
-
//
|
|
4396
|
-
|
|
4486
|
+
// the daily health check and the approval webhook (`source.metered`:
|
|
4487
|
+
// the usage line's id when present, null when probed-and-absent).
|
|
4488
|
+
// Approved pricing is never retroactive, so the fix is a fresh
|
|
4489
|
+
// approval. Gated on an EXPLICIT null — a connection the probe hasn't
|
|
4490
|
+
// reached (or couldn't read) has no key and shows nothing rather than
|
|
4491
|
+
// nagging on silence.
|
|
4492
|
+
...(data2 == null ? void 0 : data2.status) === "active" && ((_a = data2 == null ? void 0 : data2.source) == null ? void 0 : _a.metered) === null ? [
|
|
4397
4493
|
{
|
|
4398
4494
|
action: "billing",
|
|
4399
|
-
message: "Order billing isn't set up for this store, so campaign-attributed orders aren't being charged.
|
|
4400
|
-
title: "Fix order billing"
|
|
4495
|
+
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.",
|
|
4496
|
+
title: "Fix order billing",
|
|
4497
|
+
type: "error"
|
|
4401
4498
|
}
|
|
4402
4499
|
] : []
|
|
4403
4500
|
];
|
|
@@ -4927,45 +5024,6 @@ var publicConnectionKeys = Object.freeze([
|
|
|
4927
5024
|
"warnings"
|
|
4928
5025
|
]);
|
|
4929
5026
|
|
|
4930
|
-
// lib/encrypt.js
|
|
4931
|
-
var import_crypto2 = __toESM(require("crypto"), 1);
|
|
4932
|
-
|
|
4933
|
-
// lib/token.js
|
|
4934
|
-
var import_crypto = __toESM(require("crypto"), 1);
|
|
4935
|
-
var generate = (bytes = 32, encoding = "base64url") => {
|
|
4936
|
-
const buf = import_crypto.default.randomBytes(bytes);
|
|
4937
|
-
return encoding ? buf.toString(encoding) : buf;
|
|
4938
|
-
};
|
|
4939
|
-
|
|
4940
|
-
// lib/encrypt.js
|
|
4941
|
-
var ALGORITHM = "aes-256-gcm";
|
|
4942
|
-
var getKey = () => Buffer.from(process.env.ENCRYPT_CONNECTION_SECRET, "hex");
|
|
4943
|
-
var encrypt = (value) => {
|
|
4944
|
-
const iv = generate(12, null);
|
|
4945
|
-
const cipher = import_crypto2.default.createCipheriv(ALGORITHM, getKey(), iv);
|
|
4946
|
-
const data2 = Buffer.concat([
|
|
4947
|
-
cipher.update(JSON.stringify(value), "utf8"),
|
|
4948
|
-
cipher.final()
|
|
4949
|
-
]);
|
|
4950
|
-
const tag = cipher.getAuthTag();
|
|
4951
|
-
return [iv, tag, data2].map((b) => b.toString("hex")).join(":");
|
|
4952
|
-
};
|
|
4953
|
-
var decrypt = (value) => {
|
|
4954
|
-
if (typeof value !== "string") return value;
|
|
4955
|
-
const [ivHex, tagHex, dataHex] = value.split(":");
|
|
4956
|
-
const decipher = import_crypto2.default.createDecipheriv(
|
|
4957
|
-
ALGORITHM,
|
|
4958
|
-
getKey(),
|
|
4959
|
-
Buffer.from(ivHex, "hex")
|
|
4960
|
-
);
|
|
4961
|
-
decipher.setAuthTag(Buffer.from(tagHex, "hex"));
|
|
4962
|
-
const result = Buffer.concat([
|
|
4963
|
-
decipher.update(Buffer.from(dataHex, "hex")),
|
|
4964
|
-
decipher.final()
|
|
4965
|
-
]);
|
|
4966
|
-
return JSON.parse(result.toString("utf8"));
|
|
4967
|
-
};
|
|
4968
|
-
|
|
4969
5027
|
// lib/providers.js
|
|
4970
5028
|
var providerFields = (slug2) => {
|
|
4971
5029
|
var _a;
|
|
@@ -5005,9 +5063,10 @@ var saveProviderSettings = async ({ authenticated, clear, controller, settings,
|
|
|
5005
5063
|
});
|
|
5006
5064
|
const stored = (existing == null ? void 0 : existing.settings) ? decrypt(existing.settings) : {};
|
|
5007
5065
|
const pasted = (value) => typeof value === "string" ? value.trim().replace(/^['"]+|['"]+$/g, "") : value;
|
|
5066
|
+
const normalized = (field2, value) => field2.format === "slug" && typeof value === "string" && value ? slugify(value) : value;
|
|
5008
5067
|
const merged = mergeSettings({
|
|
5009
5068
|
existing: stored,
|
|
5010
|
-
incoming: Object.fromEntries(fields2.map((field2) => [field2.key, pasted(settings == null ? void 0 : settings[field2.key])]))
|
|
5069
|
+
incoming: Object.fromEntries(fields2.map((field2) => [field2.key, normalized(field2, pasted(settings == null ? void 0 : settings[field2.key]))]))
|
|
5011
5070
|
});
|
|
5012
5071
|
for (const key of Array.isArray(clear) ? clear : []) {
|
|
5013
5072
|
if (fields2.some((field2) => field2.key === key)) delete merged[key];
|
package/dist/providers.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { connections, mergeSettings } from './connections/index.cjs';
|
|
2
2
|
import { decrypt, encrypt } from './encrypt.cjs';
|
|
3
|
+
import { slugify } from './slugify.cjs';
|
|
3
4
|
import './connections/oauth.cjs';
|
|
4
5
|
import 'node:crypto';
|
|
5
6
|
import './phone.cjs';
|
|
@@ -18,8 +19,6 @@ import 'nanoid';
|
|
|
18
19
|
import './color.cjs';
|
|
19
20
|
import 'tinycolor2';
|
|
20
21
|
import './email.cjs';
|
|
21
|
-
import './slugify.cjs';
|
|
22
|
-
import 'slugify';
|
|
23
22
|
import './safe-http.cjs';
|
|
24
23
|
import 'dns';
|
|
25
24
|
import 'node:http';
|
|
@@ -29,6 +28,7 @@ import 'axios';
|
|
|
29
28
|
import 'net';
|
|
30
29
|
import 'crypto';
|
|
31
30
|
import './token.cjs';
|
|
31
|
+
import 'slugify';
|
|
32
32
|
|
|
33
33
|
// DRAWBRIDGE'S OWN CREDENTIALS for a vendor, as opposed to a merchant's.
|
|
34
34
|
//
|
|
@@ -202,6 +202,17 @@ const saveProviderSettings = async ({ authenticated, clear, controller, settings
|
|
|
202
202
|
? value.trim().replace( /^['"]+|['"]+$/g, '' )
|
|
203
203
|
: value;
|
|
204
204
|
|
|
205
|
+
// A field declared `format : 'slug'` is normalized on the way IN — a value
|
|
206
|
+
// like Shopify's meter event handle can legally hold no spaces or capitals,
|
|
207
|
+
// and classification against it is case-sensitive, so the stored copy is
|
|
208
|
+
// forced into the only shape the vendor accepts rather than trusting the
|
|
209
|
+
// admin's typing.
|
|
210
|
+
const normalized = ( field, value ) => (
|
|
211
|
+
field.format === 'slug' && typeof value === 'string' && value
|
|
212
|
+
? slugify( value )
|
|
213
|
+
: value
|
|
214
|
+
);
|
|
215
|
+
|
|
205
216
|
// mergeSettings is the blank-keeps rule, already used for merchant
|
|
206
217
|
// connection settings in route/organization-connection.js — same behaviour,
|
|
207
218
|
// one implementation. The declared-field allowlist is the part that is ours:
|
|
@@ -209,7 +220,7 @@ const saveProviderSettings = async ({ authenticated, clear, controller, settings
|
|
|
209
220
|
// not declare reaching the document.
|
|
210
221
|
const merged = mergeSettings({
|
|
211
222
|
existing : stored,
|
|
212
|
-
incoming : Object.fromEntries( fields.map( ( field ) => [ field.key, pasted( settings?.[ field.key ] ) ] ) )
|
|
223
|
+
incoming : Object.fromEntries( fields.map( ( field ) => [ field.key, normalized( field, pasted( settings?.[ field.key ] ) ) ] ) )
|
|
213
224
|
});
|
|
214
225
|
|
|
215
226
|
// CLEARING IS EXPLICIT, because blank already means keep and the two cannot
|
package/dist/providers.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { connections, mergeSettings } from './connections/index.js';
|
|
2
2
|
import { decrypt, encrypt } from './encrypt.js';
|
|
3
|
+
import { slugify } from './slugify.js';
|
|
3
4
|
import './connections/oauth.js';
|
|
4
5
|
import 'node:crypto';
|
|
5
6
|
import './phone.js';
|
|
@@ -18,8 +19,6 @@ import 'nanoid';
|
|
|
18
19
|
import './color.js';
|
|
19
20
|
import 'tinycolor2';
|
|
20
21
|
import './email.js';
|
|
21
|
-
import './slugify.js';
|
|
22
|
-
import 'slugify';
|
|
23
22
|
import './safe-http.js';
|
|
24
23
|
import 'dns';
|
|
25
24
|
import 'node:http';
|
|
@@ -29,6 +28,7 @@ import 'axios';
|
|
|
29
28
|
import 'net';
|
|
30
29
|
import 'crypto';
|
|
31
30
|
import './token.js';
|
|
31
|
+
import 'slugify';
|
|
32
32
|
|
|
33
33
|
// DRAWBRIDGE'S OWN CREDENTIALS for a vendor, as opposed to a merchant's.
|
|
34
34
|
//
|
|
@@ -202,6 +202,17 @@ const saveProviderSettings = async ({ authenticated, clear, controller, settings
|
|
|
202
202
|
? value.trim().replace( /^['"]+|['"]+$/g, '' )
|
|
203
203
|
: value;
|
|
204
204
|
|
|
205
|
+
// A field declared `format : 'slug'` is normalized on the way IN — a value
|
|
206
|
+
// like Shopify's meter event handle can legally hold no spaces or capitals,
|
|
207
|
+
// and classification against it is case-sensitive, so the stored copy is
|
|
208
|
+
// forced into the only shape the vendor accepts rather than trusting the
|
|
209
|
+
// admin's typing.
|
|
210
|
+
const normalized = ( field, value ) => (
|
|
211
|
+
field.format === 'slug' && typeof value === 'string' && value
|
|
212
|
+
? slugify( value )
|
|
213
|
+
: value
|
|
214
|
+
);
|
|
215
|
+
|
|
205
216
|
// mergeSettings is the blank-keeps rule, already used for merchant
|
|
206
217
|
// connection settings in route/organization-connection.js — same behaviour,
|
|
207
218
|
// one implementation. The declared-field allowlist is the part that is ours:
|
|
@@ -209,7 +220,7 @@ const saveProviderSettings = async ({ authenticated, clear, controller, settings
|
|
|
209
220
|
// not declare reaching the document.
|
|
210
221
|
const merged = mergeSettings({
|
|
211
222
|
existing : stored,
|
|
212
|
-
incoming : Object.fromEntries( fields.map( ( field ) => [ field.key, pasted( settings?.[ field.key ] ) ] ) )
|
|
223
|
+
incoming : Object.fromEntries( fields.map( ( field ) => [ field.key, normalized( field, pasted( settings?.[ field.key ] ) ) ] ) )
|
|
213
224
|
});
|
|
214
225
|
|
|
215
226
|
// CLEARING IS EXPLICIT, because blank already means keep and the two cannot
|