@blamejs/blamejs-shop 0.5.19 → 0.5.21

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.
Files changed (194) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -0
  3. package/SECURITY.md +17 -0
  4. package/lib/addresses.js +2 -2
  5. package/lib/admin.js +49 -45
  6. package/lib/affiliates.js +7 -23
  7. package/lib/announcement-bar.js +5 -22
  8. package/lib/api-keys.js +4 -13
  9. package/lib/asset-manifest.json +1 -1
  10. package/lib/banner-ab-tests.js +5 -9
  11. package/lib/blog-articles.js +8 -26
  12. package/lib/captcha-gate.js +7 -17
  13. package/lib/carrier-accounts.js +3 -10
  14. package/lib/catalog-drafts.js +5 -12
  15. package/lib/category-navigation.js +4 -20
  16. package/lib/cms-blocks.js +4 -7
  17. package/lib/compliance-export.js +4 -11
  18. package/lib/consent-ledger.js +2 -4
  19. package/lib/cost-layers.js +56 -21
  20. package/lib/customer-import.js +2 -2
  21. package/lib/customer-notes.js +7 -23
  22. package/lib/customer-roles.js +3 -5
  23. package/lib/customer-segments.js +17 -14
  24. package/lib/customer-surveys.js +7 -14
  25. package/lib/customers.js +2 -4
  26. package/lib/dispute-resolution.js +5 -13
  27. package/lib/dropship-forwarding.js +3 -5
  28. package/lib/dunning.js +2 -7
  29. package/lib/email-ab-tests.js +4 -8
  30. package/lib/email-campaigns.js +25 -14
  31. package/lib/email-warmup.js +2 -7
  32. package/lib/experiments.js +6 -10
  33. package/lib/externaldb-d1.js +78 -44
  34. package/lib/geolocation.js +18 -28
  35. package/lib/gift-options.js +5 -23
  36. package/lib/knowledge-base.js +7 -22
  37. package/lib/line-gift-wrap.js +3 -17
  38. package/lib/live-chat.js +5 -18
  39. package/lib/metered-usage.js +3 -8
  40. package/lib/notifications.js +3 -7
  41. package/lib/operator-accounts.js +3 -5
  42. package/lib/operator-approvals.js +6 -8
  43. package/lib/operator-audit-log.js +2 -7
  44. package/lib/operator-help-center.js +5 -21
  45. package/lib/operator-inbox.js +4 -11
  46. package/lib/operator-roles.js +5 -7
  47. package/lib/order-escalation.js +2 -2
  48. package/lib/order-export.js +24 -20
  49. package/lib/order-notes.js +4 -14
  50. package/lib/order-ratings.js +4 -10
  51. package/lib/payment-methods.js +5 -13
  52. package/lib/payment-retries.js +2 -7
  53. package/lib/payment.js +103 -58
  54. package/lib/pixel-events.js +8 -23
  55. package/lib/plan-changes.js +2 -7
  56. package/lib/product-qa.js +4 -8
  57. package/lib/promo-banners.js +7 -15
  58. package/lib/purchase-orders.js +3 -6
  59. package/lib/push-notifications.js +7 -32
  60. package/lib/pwa-manifest.js +6 -11
  61. package/lib/quotes.js +5 -9
  62. package/lib/referrals.js +10 -6
  63. package/lib/reviews.js +4 -10
  64. package/lib/robots-config.js +6 -14
  65. package/lib/sales-tax-filings.js +2 -2
  66. package/lib/search-facets.js +5 -5
  67. package/lib/search-ranking.js +4 -7
  68. package/lib/search-synonyms.js +7 -21
  69. package/lib/security-middleware.js +125 -70
  70. package/lib/seller-signup.js +10 -26
  71. package/lib/sidebar-widgets.js +6 -15
  72. package/lib/site-redirects.js +49 -137
  73. package/lib/sitemap-generator.js +3 -7
  74. package/lib/sms-dispatcher.js +7 -23
  75. package/lib/stock-receipts.js +4 -10
  76. package/lib/storefront-dashboards.js +2 -10
  77. package/lib/storefront-forms.js +9 -22
  78. package/lib/storefront-pages.js +6 -20
  79. package/lib/storefront.js +21 -23
  80. package/lib/subscription-billing.js +3 -8
  81. package/lib/subscription-controls.js +2 -7
  82. package/lib/suggestion-box.js +9 -26
  83. package/lib/support-tickets.js +6 -24
  84. package/lib/tax-remittance.js +2 -2
  85. package/lib/tenants.js +2 -7
  86. package/lib/text-guard.js +137 -35
  87. package/lib/theme-assets.js +4 -16
  88. package/lib/translations.js +3 -6
  89. package/lib/trust-badges.js +4 -7
  90. package/lib/vendor/MANIFEST.json +107 -97
  91. package/lib/vendor/blamejs/CHANGELOG.md +180 -0
  92. package/lib/vendor/blamejs/NOTICE +14 -10
  93. package/lib/vendor/blamejs/README.md +17 -5
  94. package/lib/vendor/blamejs/index.js +2 -0
  95. package/lib/vendor/blamejs/lib/acme.js +9 -9
  96. package/lib/vendor/blamejs/lib/api-snapshot.js +35 -16
  97. package/lib/vendor/blamejs/lib/app-shutdown.js +1 -1
  98. package/lib/vendor/blamejs/lib/archive-adapters.js +1 -1
  99. package/lib/vendor/blamejs/lib/archive.js +13 -24
  100. package/lib/vendor/blamejs/lib/auth/access-lock.js +2 -1
  101. package/lib/vendor/blamejs/lib/auth/bot-challenge.js +3 -6
  102. package/lib/vendor/blamejs/lib/auth/ciba.js +32 -3
  103. package/lib/vendor/blamejs/lib/auth/oauth.js +65 -8
  104. package/lib/vendor/blamejs/lib/auth/password.js +1 -1
  105. package/lib/vendor/blamejs/lib/auth/saml.js +21 -3
  106. package/lib/vendor/blamejs/lib/auth-bot-challenge.js +3 -3
  107. package/lib/vendor/blamejs/lib/backup/index.js +5 -5
  108. package/lib/vendor/blamejs/lib/case-fold-classes.js +491 -0
  109. package/lib/vendor/blamejs/lib/codepoint-class.js +131 -0
  110. package/lib/vendor/blamejs/lib/constants.js +79 -0
  111. package/lib/vendor/blamejs/lib/db-declare-view.js +8 -2
  112. package/lib/vendor/blamejs/lib/error-page.js +20 -1
  113. package/lib/vendor/blamejs/lib/external-db.js +5 -1
  114. package/lib/vendor/blamejs/lib/flag-evaluation-context.js +24 -6
  115. package/lib/vendor/blamejs/lib/flag.js +28 -2
  116. package/lib/vendor/blamejs/lib/framework-schema.js +14 -3
  117. package/lib/vendor/blamejs/lib/guard-regex.js +2345 -89
  118. package/lib/vendor/blamejs/lib/http-client.js +217 -40
  119. package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
  120. package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +3 -2
  121. package/lib/vendor/blamejs/lib/mail-dav.js +26 -26
  122. package/lib/vendor/blamejs/lib/mail-deploy.js +10 -10
  123. package/lib/vendor/blamejs/lib/mail-scan.js +3 -0
  124. package/lib/vendor/blamejs/lib/mail-store.js +7 -1
  125. package/lib/vendor/blamejs/lib/mail.js +5 -1
  126. package/lib/vendor/blamejs/lib/middleware/age-gate.js +3 -2
  127. package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +1 -1
  128. package/lib/vendor/blamejs/lib/middleware/assetlinks.js +2 -1
  129. package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +3 -2
  130. package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +2 -1
  131. package/lib/vendor/blamejs/lib/middleware/compression.js +1 -1
  132. package/lib/vendor/blamejs/lib/middleware/cors.js +1 -1
  133. package/lib/vendor/blamejs/lib/middleware/csp-report.js +5 -5
  134. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +2 -1
  135. package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +2 -2
  136. package/lib/vendor/blamejs/lib/middleware/deny-response.js +2 -1
  137. package/lib/vendor/blamejs/lib/middleware/dpop.js +2 -2
  138. package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +2 -1
  139. package/lib/vendor/blamejs/lib/middleware/flag-context.js +17 -0
  140. package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +10 -8
  141. package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +3 -2
  142. package/lib/vendor/blamejs/lib/middleware/require-aal.js +3 -2
  143. package/lib/vendor/blamejs/lib/middleware/require-auth.js +4 -3
  144. package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +4 -3
  145. package/lib/vendor/blamejs/lib/middleware/require-content-type.js +3 -2
  146. package/lib/vendor/blamejs/lib/middleware/require-methods.js +3 -2
  147. package/lib/vendor/blamejs/lib/middleware/require-mtls.js +3 -2
  148. package/lib/vendor/blamejs/lib/middleware/security-txt.js +2 -1
  149. package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +2 -1
  150. package/lib/vendor/blamejs/lib/network-dns-resolver.js +1 -1
  151. package/lib/vendor/blamejs/lib/network-dns.js +3 -3
  152. package/lib/vendor/blamejs/lib/network-nts.js +35 -14
  153. package/lib/vendor/blamejs/lib/network-proxy.js +42 -4
  154. package/lib/vendor/blamejs/lib/network-smtp-policy.js +1 -1
  155. package/lib/vendor/blamejs/lib/network-tls.js +341 -11
  156. package/lib/vendor/blamejs/lib/nist-crosswalk.js +2 -2
  157. package/lib/vendor/blamejs/lib/ntp-check.js +30 -19
  158. package/lib/vendor/blamejs/lib/object-store/azure-blob.js +1 -1
  159. package/lib/vendor/blamejs/lib/object-store/gcs.js +1 -1
  160. package/lib/vendor/blamejs/lib/object-store/http-put.js +2 -1
  161. package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -5
  162. package/lib/vendor/blamejs/lib/object-store/sigv4.js +2 -2
  163. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +4 -2
  164. package/lib/vendor/blamejs/lib/openapi.js +1 -1
  165. package/lib/vendor/blamejs/lib/pqc-agent.js +4 -7
  166. package/lib/vendor/blamejs/lib/problem-details.js +2 -1
  167. package/lib/vendor/blamejs/lib/redis-client.js +12 -1
  168. package/lib/vendor/blamejs/lib/regex-linear.js +1349 -0
  169. package/lib/vendor/blamejs/lib/render.js +531 -7
  170. package/lib/vendor/blamejs/lib/request-helpers.js +181 -5
  171. package/lib/vendor/blamejs/lib/router.js +37 -13
  172. package/lib/vendor/blamejs/lib/safe-async.js +142 -0
  173. package/lib/vendor/blamejs/lib/safe-icap.js +3 -0
  174. package/lib/vendor/blamejs/lib/safe-sql.js +15 -3
  175. package/lib/vendor/blamejs/lib/self-update.js +2 -2
  176. package/lib/vendor/blamejs/lib/testing.js +54 -7
  177. package/lib/vendor/blamejs/lib/validate-opts.js +56 -0
  178. package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +62 -25
  179. package/lib/vendor/blamejs/lib/vendor/blamejs-pki.cjs +2004 -142
  180. package/lib/vendor/blamejs/lib/vendor/browser/noble-ciphers.mjs +727 -0
  181. package/lib/vendor/blamejs/lib/vendor/browser/noble-hashes.mjs +1070 -0
  182. package/lib/vendor/blamejs/lib/vendor/browser/noble-post-quantum.mjs +1090 -0
  183. package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +510 -227
  184. package/lib/vendor/blamejs/lib/webhook-hmac.js +19 -2
  185. package/lib/vendor/blamejs/lib/webhook.js +1 -1
  186. package/lib/vendor/blamejs/lib/ws-client.js +24 -2
  187. package/lib/vendor/blamejs/package.json +1 -1
  188. package/lib/vendor/blamejs/sbom.cdx.json +6 -6
  189. package/lib/vendor-invoices.js +3 -6
  190. package/lib/vendors.js +5 -19
  191. package/lib/webhook-receiver.js +46 -83
  192. package/lib/webhook-subscriptions.js +3 -13
  193. package/lib/wishlist-sharing.js +5 -10
  194. package/package.json +1 -1
@@ -98,51 +98,85 @@ function _isWriteSql(sql) {
98
98
  return verb === "INSERT" || verb === "UPDATE" || verb === "DELETE" || verb === "CREATE" || verb === "DROP" || verb === "ALTER" || verb === "REPLACE";
99
99
  }
100
100
 
101
- function _withTimeout(promise, ms, label) {
102
- var controller = new AbortController();
103
- var timer = setTimeout(function () { controller.abort(); }, ms);
104
- return {
105
- signal: controller.signal,
106
- finish: function () {
107
- clearTimeout(timer);
108
- return promise;
109
- },
110
- timeoutErr: function () {
111
- var e = new Error("externaldbD1: " + label + " timed out after " + ms + "ms");
112
- e.code = "D1_TIMEOUT";
113
- return e;
114
- },
115
- };
101
+ // Per-query deadline. b.safeAsync.withTimeoutSignal returns an AbortSignal
102
+ // that fires after `ms`, which is what fetch already takes.
103
+ //
104
+ // This replaced a hand-rolled AbortController whose setTimeout was only
105
+ // cleared by a `finish()` nobody ever called: every query left a pending
106
+ // timer behind, and a pending timer is ref'd, so it kept the event loop
107
+ // awake until it fired. On a query path this busy that is a standing
108
+ // population of live timers and a shutdown that cannot settle until the last
109
+ // one expires. The signal the primitive returns does not hold the loop open.
110
+ function _timeoutSignal(ms) {
111
+ return b.safeAsync.withTimeoutSignal(null, ms);
112
+ }
113
+
114
+ // A deadline reached through AbortSignal.timeout rejects with a DOMException
115
+ // named TimeoutError, NOT AbortError — so a handler that recognises only the
116
+ // latter stops producing D1_TIMEOUT and reports a transport failure instead.
117
+ // Both names map here; the only abort wired to these dials is the deadline.
118
+ function _isTimeoutAbort(e) {
119
+ return !!e && (e.name === "TimeoutError" || e.name === "AbortError");
120
+ }
121
+
122
+ function _timeoutErr(ms, label) {
123
+ var e = new Error("externaldbD1: " + label + " timed out after " + ms + "ms");
124
+ e.code = "D1_TIMEOUT";
125
+ return e;
116
126
  }
117
127
 
128
+ // The attempt loop is b.retry.withRetry — exponential backoff, jitter, and
129
+ // the final rethrow — with two things this adapter has to supply.
130
+ //
131
+ // The classifier is ours. The framework's default reads `err.code` and
132
+ // `err.statusCode` off the error itself, and undici does not put them there:
133
+ // a transport failure arrives as TypeError("fetch failed") carrying the real
134
+ // code on `.cause`. Its retryable set also omits EAI_AGAIN and UND_ERR_SOCKET,
135
+ // which are precisely the DNS and socket failures this adapter exists to ride
136
+ // out. Handing it the default classifier would leave the loop in place and
137
+ // silently stop retrying anything that matters.
138
+ //
139
+ // The HTTP arm has to throw to be seen at all. b.httpClient and fetch both
140
+ // resolve a 5xx as an ordinary response, so a retryable status is raised
141
+ // inside the attempt and the response handed back at the boundary once the
142
+ // retries are spent — the caller still inspects `res.status` itself.
118
143
  async function _retryingFetch(fetchImpl, url, init, attempts, label) {
119
- var lastErr;
120
- for (var i = 0; i <= attempts; i += 1) {
121
- try {
122
- var res = await fetchImpl(url, init);
123
- // Retry only on 5xx / 429; 4xx is a permanent client error.
124
- if (res.status >= 500 || res.status === 429) {
125
- lastErr = _httpErr(res.status, label);
126
- if (i < attempts) {
127
- await _backoff(i);
128
- continue;
129
- }
130
- }
131
- return res;
132
- } catch (e) {
133
- lastErr = e;
134
- if (i < attempts && _isTransient(e)) {
135
- await _backoff(i);
136
- continue;
137
- }
138
- throw e;
139
- }
140
- }
141
- throw lastErr;
144
+ var received = null;
145
+ return b.retry.withRetry(async function () {
146
+ var res = await fetchImpl(url, init);
147
+ received = res;
148
+ // Retry only on 5xx / 429; a 4xx is the request's own fault.
149
+ if (res.status >= 500 || res.status === 429) throw _httpErr(res.status, label);
150
+ return res;
151
+ }, {
152
+ maxAttempts: attempts + 1, // `attempts` counts RETRIES, not tries
153
+ baseDelayMs: RETRY_BACKOFF_MS,
154
+ isRetryable: _isTransient,
155
+ }).catch(function (e) {
156
+ // Retries exhausted on a status we did receive: give the caller the
157
+ // response so it reports the upstream's own error, exactly as the previous
158
+ // loop did. A transport failure has no response and propagates.
159
+ if (received && e && e.status === received.status) return received;
160
+ throw e;
161
+ });
142
162
  }
143
163
 
164
+ // Which failures are worth another attempt. Both arms matter and neither is
165
+ // what the framework's default classifier looks at.
166
+ //
167
+ // Transport: undici reports a network failure as TypeError("fetch failed")
168
+ // with the real code nested on `.cause`, so the code is read from either
169
+ // place. EAI_AGAIN (transient DNS) and UND_ERR_SOCKET (socket torn down
170
+ // mid-request) are the two this adapter most needs and the two the framework's
171
+ // set leaves out.
172
+ //
173
+ // HTTP: a 5xx or 429 is raised as an error inside the attempt so the loop can
174
+ // see it at all — fetch resolves those as ordinary responses. `_httpErr`
175
+ // records the status on `.status`, so that is what is read here. A 4xx is the
176
+ // request's own fault and is never retried.
144
177
  function _isTransient(err) {
145
178
  if (!err) return false;
179
+ if (typeof err.status === "number") return err.status >= 500 || err.status === 429;
146
180
  var c = err.code || (err.cause && err.cause.code);
147
181
  return c === "ECONNRESET" || c === "ETIMEDOUT" || c === "ENOTFOUND" || c === "EAI_AGAIN" || c === "UND_ERR_SOCKET";
148
182
  }
@@ -179,7 +213,7 @@ function _serviceBindingQuery(opts) {
179
213
  return async function (_client, sql, params) {
180
214
  var mode = _isWriteSql(sql) ? "run" : "all";
181
215
  var body = JSON.stringify({ sql: sql, params: params || [], mode: mode });
182
- var tcap = _withTimeout(null, timeoutMs, "service-binding query");
216
+ var tsig = _timeoutSignal(timeoutMs);
183
217
  var res;
184
218
  try {
185
219
  res = await _retryingFetch(fetchImpl, url, {
@@ -190,10 +224,10 @@ function _serviceBindingQuery(opts) {
190
224
  "accept": "application/json",
191
225
  },
192
226
  body: body,
193
- signal: tcap.signal,
227
+ signal: tsig,
194
228
  }, MAX_RETRY, "service-binding query");
195
229
  } catch (e) {
196
- if (e && e.name === "AbortError") throw tcap.timeoutErr();
230
+ if (_isTimeoutAbort(e)) throw _timeoutErr(timeoutMs, "service-binding query");
197
231
  throw e;
198
232
  }
199
233
  if (!res.ok) throw _httpErr(res.status, "service-binding query");
@@ -224,7 +258,7 @@ function _restApiQuery(opts) {
224
258
  }
225
259
  return async function (_client, sql, params) {
226
260
  var body = JSON.stringify({ sql: sql, params: params || [] });
227
- var tcap = _withTimeout(null, timeoutMs, "rest-api query");
261
+ var tsig = _timeoutSignal(timeoutMs);
228
262
  var res;
229
263
  try {
230
264
  res = await _retryingFetch(fetchImpl, url, {
@@ -235,10 +269,10 @@ function _restApiQuery(opts) {
235
269
  "accept": "application/json",
236
270
  },
237
271
  body: body,
238
- signal: tcap.signal,
272
+ signal: tsig,
239
273
  }, MAX_RETRY, "rest-api query");
240
274
  } catch (e) {
241
- if (e && e.name === "AbortError") throw tcap.timeoutErr();
275
+ if (_isTimeoutAbort(e)) throw _timeoutErr(timeoutMs, "rest-api query");
242
276
  throw e;
243
277
  }
244
278
  if (!res.ok) throw _httpErr(res.status, "rest-api query");
@@ -254,34 +254,24 @@ function _now() { return Date.now(); }
254
254
  // through to the country's default_locale.
255
255
  function _parseAcceptLanguage(raw) {
256
256
  if (typeof raw !== "string" || !raw.length) return [];
257
- var entries = raw.split(",");
258
- var parsed = [];
259
- for (var i = 0; i < entries.length; i += 1) {
260
- var part = entries[i].trim();
261
- if (!part) continue;
262
- var semi = part.indexOf(";");
263
- var tag = (semi === -1 ? part : part.slice(0, semi)).trim();
264
- var q = 1.0;
265
- if (semi !== -1) {
266
- var attrs = part.slice(semi + 1).split(";");
267
- for (var j = 0; j < attrs.length; j += 1) {
268
- var a = attrs[j].trim();
269
- if (a.slice(0, 2).toLowerCase() === "q=") {
270
- var n = parseFloat(a.slice(2));
271
- if (isFinite(n) && n >= 0 && n <= 1) q = n;
272
- }
273
- }
274
- }
275
- if (tag === "*") continue;
276
- if (!LOCALE_RE.test(tag)) continue;
277
- parsed.push({ tag: tag, q: q, order: i });
278
- }
279
- // Stable q-desc sort (preserve original order on ties).
280
- parsed.sort(function (a, b) {
281
- if (a.q !== b.q) return b.q - a.q;
282
- return a.order - b.order;
283
- });
284
- return parsed;
257
+ // b.requestHelpers.parseQualityList is the RFC 9110 §12.5 parser, and the
258
+ // edge bypass check reads the same header through it — which is what keeps
259
+ // the two agreeing on which tag wins. They had drifted: a header spelling
260
+ // the parameter `Q=0.9` was read correctly here and ignored at the edge, so
261
+ // a visitor sending it got one locale decided at the edge and another here.
262
+ //
263
+ // `caseSensitive` because these are BCP 47 tags and the chosen one is
264
+ // returned to the caller — a browser asking for `de-AT` should get `de-AT`
265
+ // back, not `de-at`. Matching lowercases separately in _primarySubtag.
266
+ //
267
+ // A tag carrying `q=0` is dropped rather than ranked last: q=0 means "not
268
+ // acceptable", and keeping it meant a locale the visitor had explicitly
269
+ // ruled out could still be selected when nothing else matched.
270
+ return b.requestHelpers.parseQualityList(raw, { caseSensitive: true })
271
+ .filter(function (e) {
272
+ return e.value !== "*" && e.q > 0 && LOCALE_RE.test(e.value);
273
+ })
274
+ .map(function (e) { return { tag: e.value, q: e.q }; });
285
275
  }
286
276
 
287
277
  function _primarySubtag(tag) {
@@ -69,8 +69,6 @@ var SKU_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
69
69
  // in adjacent ecosystems. Newlines are allowed in gift_message
70
70
  // (people write multi-line messages); the recipient name is a
71
71
  // single line and refuses LF / CR too.
72
- var CONTROL_BYTE_MSG_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
73
- var CONTROL_BYTE_NAME_RE = /[\x00-\x1f\x7f]/;
74
72
 
75
73
  // Zero-width / direction-override family — mirrors the order-notes
76
74
  // primitive's catalogue: ZWSP/ZWNJ/ZWJ (U+200B-200D), LRM/RLM
@@ -79,15 +77,13 @@ var CONTROL_BYTE_NAME_RE = /[\x00-\x1f\x7f]/;
79
77
  // (U+2066-U+2069), the BOM (U+FEFF), and the Arabic letter mark
80
78
  // (U+061C). Spelled with \u-escapes so ESLint's
81
79
  // no-irregular-whitespace stays happy.
82
- var ZERO_WIDTH_RE = new RegExp(
83
- "[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
84
- );
85
80
 
86
81
  var ALLOWED_WRAP_COLUMNS = Object.freeze([
87
82
  "title", "fee_minor", "image_url", "max_per_order", "active",
88
83
  ]);
89
84
 
90
85
  var b = require("./vendor/blamejs");
86
+ var textGuard = require("./text-guard");
91
87
 
92
88
  // ---- validators ---------------------------------------------------------
93
89
 
@@ -107,9 +103,7 @@ function _title(s) {
107
103
  if (typeof s !== "string" || !s.length || s.length > MAX_TITLE_LEN) {
108
104
  throw new TypeError("giftOptions: title must be a non-empty string ≤ " + MAX_TITLE_LEN + " chars");
109
105
  }
110
- if (CONTROL_BYTE_NAME_RE.test(s) || ZERO_WIDTH_RE.test(s)) {
111
- throw new TypeError("giftOptions: title contains control / zero-width bytes");
112
- }
106
+ textGuard.freeText(s, "giftOptions: title", { singleLine: "reject", zeroWidth: "reject" });
113
107
  return s;
114
108
  }
115
109
 
@@ -133,9 +127,7 @@ function _imageUrl(s) {
133
127
  if (!(/^https:\/\//.test(s) || /^\/\//.test(s) || /^\//.test(s))) {
134
128
  throw new TypeError("giftOptions: image_url must be https://, // (protocol-relative), or / (absolute path)");
135
129
  }
136
- if (CONTROL_BYTE_NAME_RE.test(s) || ZERO_WIDTH_RE.test(s)) {
137
- throw new TypeError("giftOptions: image_url contains control / zero-width bytes");
138
- }
130
+ textGuard.freeText(s, "giftOptions: image_url", { singleLine: "reject", zeroWidth: "reject" });
139
131
  return s;
140
132
  }
141
133
 
@@ -162,12 +154,7 @@ function _giftMessage(s) {
162
154
  if (s.length > MAX_MESSAGE_LEN) {
163
155
  throw new TypeError("giftOptions: gift_message must be ≤ " + MAX_MESSAGE_LEN + " chars");
164
156
  }
165
- if (CONTROL_BYTE_MSG_RE.test(s)) {
166
- throw new TypeError("giftOptions: gift_message contains control bytes");
167
- }
168
- if (ZERO_WIDTH_RE.test(s)) {
169
- throw new TypeError("giftOptions: gift_message contains zero-width / direction-override characters");
170
- }
157
+ textGuard.freeText(s, "giftOptions: gift_message", { zeroWidth: "reject" });
171
158
  return s;
172
159
  }
173
160
 
@@ -179,12 +166,7 @@ function _recipientName(s) {
179
166
  if (s.length > MAX_RECIPIENT_LEN) {
180
167
  throw new TypeError("giftOptions: recipient_name must be ≤ " + MAX_RECIPIENT_LEN + " chars");
181
168
  }
182
- if (CONTROL_BYTE_NAME_RE.test(s)) {
183
- throw new TypeError("giftOptions: recipient_name contains control bytes (incl. CR/LF)");
184
- }
185
- if (ZERO_WIDTH_RE.test(s)) {
186
- throw new TypeError("giftOptions: recipient_name contains zero-width / direction-override characters");
187
- }
169
+ textGuard.freeText(s, "giftOptions: recipient_name", { singleLine: "reject", zeroWidth: "reject" });
188
170
  return s;
189
171
  }
190
172
 
@@ -108,11 +108,6 @@ var LOCALE_RE = /^[a-z]{2,3}(?:-[a-z0-9]{2,8})*$/;
108
108
  var CATEGORY_RE = /^[a-z][a-z0-9_-]*$/;
109
109
  var TAG_RE = /^[a-z][a-z0-9_-]*$/;
110
110
 
111
- var CONTROL_BYTE_BODY_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
112
- var CONTROL_BYTE_STRICT_RE = /[\x00-\x1f\x7f]/;
113
- var ZERO_WIDTH_RE = new RegExp(
114
- "[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
115
- );
116
111
 
117
112
  var SEARCH_WEIGHTS = Object.freeze({
118
113
  title: 3,
@@ -121,6 +116,7 @@ var SEARCH_WEIGHTS = Object.freeze({
121
116
  });
122
117
 
123
118
  var b = require("./vendor/blamejs");
119
+ var textGuard = require("./text-guard");
124
120
 
125
121
  // ---- monotonic clock ---------------------------------------------------
126
122
  //
@@ -163,12 +159,7 @@ function _title(s) {
163
159
  if (s.length > MAX_TITLE_LEN) {
164
160
  throw new TypeError("knowledgeBase: title must be <= " + MAX_TITLE_LEN + " characters");
165
161
  }
166
- if (CONTROL_BYTE_STRICT_RE.test(s)) {
167
- throw new TypeError("knowledgeBase: title contains control bytes");
168
- }
169
- if (ZERO_WIDTH_RE.test(s)) {
170
- throw new TypeError("knowledgeBase: title contains zero-width / direction-override characters");
171
- }
162
+ textGuard.freeText(s, "knowledgeBase: title", { singleLine: "reject", zeroWidth: "reject" });
172
163
  return s;
173
164
  }
174
165
 
@@ -183,12 +174,10 @@ function _body(s) {
183
174
  if (s.length > MAX_BODY_LEN) {
184
175
  throw new TypeError("knowledgeBase: body must be <= " + MAX_BODY_LEN + " characters");
185
176
  }
186
- if (CONTROL_BYTE_BODY_RE.test(s)) {
177
+ if (textGuard.hasCodepointThreat(s)) {
187
178
  throw new TypeError("knowledgeBase: body contains control bytes");
188
179
  }
189
- if (ZERO_WIDTH_RE.test(s)) {
190
- throw new TypeError("knowledgeBase: body contains zero-width / direction-override characters");
191
- }
180
+ textGuard.freeText(s, "knowledgeBase: body", { zeroWidth: "reject" });
192
181
  return s;
193
182
  }
194
183
 
@@ -282,9 +271,7 @@ function _queryStr(s) {
282
271
  if (s.length > MAX_QUERY_LEN) {
283
272
  throw new TypeError("knowledgeBase: query must be <= " + MAX_QUERY_LEN + " characters");
284
273
  }
285
- if (CONTROL_BYTE_BODY_RE.test(s) || ZERO_WIDTH_RE.test(s)) {
286
- throw new TypeError("knowledgeBase: query contains control / zero-width bytes");
287
- }
274
+ textGuard.freeText(s, "knowledgeBase: query", { zeroWidth: "reject" });
288
275
  return s;
289
276
  }
290
277
 
@@ -303,9 +290,7 @@ function _sessionIdRaw(s) {
303
290
  if (s.length > 256) {
304
291
  throw new TypeError("knowledgeBase: session_id must be <= 256 characters");
305
292
  }
306
- if (CONTROL_BYTE_STRICT_RE.test(s) || ZERO_WIDTH_RE.test(s)) {
307
- throw new TypeError("knowledgeBase: session_id contains control / zero-width bytes");
308
- }
293
+ textGuard.freeText(s, "knowledgeBase: session_id", { singleLine: "reject", zeroWidth: "reject" });
309
294
  return s;
310
295
  }
311
296
 
@@ -347,7 +332,7 @@ function _esc(s) {
347
332
 
348
333
  function _safeLinkUrl(url) {
349
334
  if (typeof url !== "string" || !url.length || url.length > 2048) return null;
350
- if (CONTROL_BYTE_BODY_RE.test(url) || ZERO_WIDTH_RE.test(url)) return null;
335
+ if (textGuard.hasCodepointThreat(url, { zeroWidth: "reject" })) return null;
351
336
  if (url.charCodeAt(0) === 47 /* "/" */) {
352
337
  if (url.length > 1 && url.charCodeAt(1) === 47) return null;
353
338
  if (url.indexOf("..") !== -1) return null;
@@ -69,6 +69,7 @@
69
69
  // ---- constants ----------------------------------------------------------
70
70
 
71
71
  var b = require("./vendor/blamejs");
72
+ var textGuard = require("./text-guard");
72
73
 
73
74
  var C = b.constants;
74
75
 
@@ -89,8 +90,6 @@ var SKU_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
89
90
  // adjacent ecosystems. Newlines are allowed in gift_message (people
90
91
  // write multi-line messages); the recipient name is a single line
91
92
  // and refuses LF / CR too.
92
- var CONTROL_BYTE_MSG_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
93
- var CONTROL_BYTE_NAME_RE = /[\x00-\x1f\x7f]/;
94
93
 
95
94
  // Zero-width / direction-override family — mirrors the gift-options
96
95
  // primitive's catalogue: ZWSP/ZWNJ/ZWJ (U+200B-200D), LRM/RLM
@@ -99,9 +98,6 @@ var CONTROL_BYTE_NAME_RE = /[\x00-\x1f\x7f]/;
99
98
  // (U+2066-U+2069), the BOM (U+FEFF), and the Arabic letter mark
100
99
  // (U+061C). Spelled with \u-escapes so ESLint's
101
100
  // no-irregular-whitespace stays happy.
102
- var ZERO_WIDTH_RE = new RegExp(
103
- "[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
104
- );
105
101
 
106
102
  var BCP47_RE = /^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/;
107
103
 
@@ -155,12 +151,7 @@ function _giftMessage(s) {
155
151
  if (s.length > MAX_MESSAGE_LEN) {
156
152
  throw new TypeError("lineGiftWrap: gift_message must be ≤ " + MAX_MESSAGE_LEN + " chars");
157
153
  }
158
- if (CONTROL_BYTE_MSG_RE.test(s)) {
159
- throw new TypeError("lineGiftWrap: gift_message contains control bytes");
160
- }
161
- if (ZERO_WIDTH_RE.test(s)) {
162
- throw new TypeError("lineGiftWrap: gift_message contains zero-width / direction-override characters");
163
- }
154
+ textGuard.freeText(s, "lineGiftWrap: gift_message", { zeroWidth: "reject" });
164
155
  return s;
165
156
  }
166
157
 
@@ -172,12 +163,7 @@ function _recipientName(s) {
172
163
  if (s.length > MAX_RECIPIENT_LEN) {
173
164
  throw new TypeError("lineGiftWrap: recipient_name must be ≤ " + MAX_RECIPIENT_LEN + " chars");
174
165
  }
175
- if (CONTROL_BYTE_NAME_RE.test(s)) {
176
- throw new TypeError("lineGiftWrap: recipient_name contains control bytes (incl. CR/LF)");
177
- }
178
- if (ZERO_WIDTH_RE.test(s)) {
179
- throw new TypeError("lineGiftWrap: recipient_name contains zero-width / direction-override characters");
180
- }
166
+ textGuard.freeText(s, "lineGiftWrap: recipient_name", { singleLine: "reject", zeroWidth: "reject" });
181
167
  return s;
182
168
  }
183
169
 
package/lib/live-chat.js CHANGED
@@ -98,15 +98,11 @@ var TRANSITIONS = {
98
98
  abandoned: {},
99
99
  };
100
100
 
101
- var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
102
- var CONTROL_BYTE_STRICT_RE = /[\x00-\x1f\x7f]/;
103
101
  // Zero-width + direction-override characters. Spelled via \u escapes
104
102
  // so the file stays ESLint no-irregular-whitespace clean.
105
- var ZERO_WIDTH_RE = new RegExp(
106
- "[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
107
- );
108
103
 
109
104
  var b = require("./vendor/blamejs");
105
+ var textGuard = require("./text-guard");
110
106
  var C = b.constants;
111
107
 
112
108
  // ---- validators ---------------------------------------------------------
@@ -134,12 +130,7 @@ function _body(s) {
134
130
  if (s.length > MAX_BODY_LEN) {
135
131
  throw new TypeError("live-chat: body must be <= " + MAX_BODY_LEN + " characters");
136
132
  }
137
- if (CONTROL_BYTE_RE.test(s)) {
138
- throw new TypeError("live-chat: body contains control bytes");
139
- }
140
- if (ZERO_WIDTH_RE.test(s)) {
141
- throw new TypeError("live-chat: body contains zero-width / direction-override characters");
142
- }
133
+ textGuard.freeText(s, "live-chat: body", { zeroWidth: "reject" });
143
134
  return s;
144
135
  }
145
136
 
@@ -152,7 +143,7 @@ function _reason(r, label) {
152
143
  if (r.length > MAX_REASON_LEN) {
153
144
  throw new TypeError("live-chat: " + (label || "reason") + " must be <= " + MAX_REASON_LEN + " characters");
154
145
  }
155
- if (CONTROL_BYTE_STRICT_RE.test(r) || ZERO_WIDTH_RE.test(r)) {
146
+ if (textGuard.hasCodepointThreat(r, { singleLine: "reject", zeroWidth: "reject" })) {
156
147
  throw new TypeError("live-chat: " + (label || "reason") + " contains control / zero-width bytes");
157
148
  }
158
149
  return r;
@@ -165,9 +156,7 @@ function _sourcePage(s) {
165
156
  if (s.length > MAX_SOURCE_PAGE) {
166
157
  throw new TypeError("live-chat: source_page must be <= " + MAX_SOURCE_PAGE + " characters");
167
158
  }
168
- if (CONTROL_BYTE_STRICT_RE.test(s) || ZERO_WIDTH_RE.test(s)) {
169
- throw new TypeError("live-chat: source_page contains control / zero-width bytes");
170
- }
159
+ textGuard.freeText(s, "live-chat: source_page", { singleLine: "reject", zeroWidth: "reject" });
171
160
  return s;
172
161
  }
173
162
 
@@ -178,9 +167,7 @@ function _visitorSessionId(s) {
178
167
  if (s.length > MAX_SESSION_ID_LEN) {
179
168
  throw new TypeError("live-chat: session_id must be <= " + MAX_SESSION_ID_LEN + " characters");
180
169
  }
181
- if (CONTROL_BYTE_STRICT_RE.test(s) || ZERO_WIDTH_RE.test(s)) {
182
- throw new TypeError("live-chat: session_id contains control / zero-width bytes");
183
- }
170
+ textGuard.freeText(s, "live-chat: session_id", { singleLine: "reject", zeroWidth: "reject" });
184
171
  return s;
185
172
  }
186
173
 
@@ -90,6 +90,7 @@
90
90
  */
91
91
 
92
92
  var b = require("./vendor/blamejs");
93
+ var textGuard = require("./text-guard");
93
94
 
94
95
  // ---- constants ----------------------------------------------------------
95
96
 
@@ -111,10 +112,6 @@ var SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
111
112
  // primitives — operator-authored copy (title, unit) ends up on
112
113
  // receipt emails + the operator dashboard, so the same "no direction-
113
114
  // override / no invisible glyph" floor applies.
114
- var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
115
- var ZERO_WIDTH_RE = new RegExp(
116
- "[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
117
- );
118
115
 
119
116
  var ALLOWED_PATCH_COLUMNS = Object.freeze([
120
117
  "title",
@@ -146,7 +143,7 @@ function _shortText(s, label, max) {
146
143
  if (s.length > max) {
147
144
  throw new TypeError("meteredUsage: " + label + " must be <= " + max + " characters");
148
145
  }
149
- if (CONTROL_BYTE_RE.test(s) || ZERO_WIDTH_RE.test(s)) {
146
+ if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
150
147
  throw new TypeError("meteredUsage: " + label + " contains control / zero-width bytes");
151
148
  }
152
149
  return s;
@@ -253,9 +250,7 @@ function _idempotencyKey(s) {
253
250
  if (s.length > MAX_IDEM_KEY_LEN) {
254
251
  throw new TypeError("meteredUsage: idempotency_key must be <= " + MAX_IDEM_KEY_LEN + " characters");
255
252
  }
256
- if (CONTROL_BYTE_RE.test(s)) {
257
- throw new TypeError("meteredUsage: idempotency_key contains control bytes");
258
- }
253
+ textGuard.freeText(s, "meteredUsage: idempotency_key");
259
254
  return s;
260
255
  }
261
256
 
@@ -39,6 +39,7 @@
39
39
  */
40
40
 
41
41
  var b = require("./vendor/blamejs");
42
+ var textGuard = require("./text-guard");
42
43
 
43
44
  var RECIPIENT_NAMESPACE = "notification-recipient";
44
45
 
@@ -54,7 +55,6 @@ var MAX_PAYLOAD_BYTES = 65536;
54
55
  var MAX_ERROR_LEN = 1024;
55
56
 
56
57
  var EVENT_TYPE_RE = /^[a-z](?:[a-z0-9._-]*[a-z0-9])?$/;
57
- var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
58
58
  var DEFAULT_PAGE_SIZE = 50;
59
59
  var MAX_PAGE_SIZE = 500;
60
60
 
@@ -67,9 +67,7 @@ function _recipientId(s) {
67
67
  if (s.length > MAX_RECIPIENT_LEN) {
68
68
  throw new TypeError("notifications: recipient_id must be <= " + MAX_RECIPIENT_LEN + " characters");
69
69
  }
70
- if (CONTROL_BYTE_RE.test(s)) {
71
- throw new TypeError("notifications: recipient_id contains control bytes");
72
- }
70
+ textGuard.freeText(s, "notifications: recipient_id");
73
71
  return s;
74
72
  }
75
73
 
@@ -101,9 +99,7 @@ function _title(s) {
101
99
  if (s.length > MAX_TITLE_LEN) {
102
100
  throw new TypeError("notifications: title must be <= " + MAX_TITLE_LEN + " characters");
103
101
  }
104
- if (CONTROL_BYTE_RE.test(s)) {
105
- throw new TypeError("notifications: title contains control bytes");
106
- }
102
+ textGuard.freeText(s, "notifications: title");
107
103
  return s;
108
104
  }
109
105
 
@@ -156,9 +156,9 @@ var MAX_LIST_LIMIT = 200;
156
156
  var ROLES = Object.freeze(["owner", "manager", "viewer"]);
157
157
  var STATUS = Object.freeze(["active", "disabled"]);
158
158
 
159
- var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
160
159
 
161
160
  var b = require("./vendor/blamejs");
161
+ var textGuard = require("./text-guard");
162
162
 
163
163
  // ---- validators ---------------------------------------------------------
164
164
 
@@ -174,7 +174,7 @@ function _actorId(s, label) {
174
174
  if (typeof s !== "string" || !s.length || s.length > 128) {
175
175
  throw new TypeError("operatorAccounts: " + (label || "actor_id") + " must be a non-empty string (<= 128 chars)");
176
176
  }
177
- if (CONTROL_BYTE_RE.test(s)) {
177
+ if (textGuard.hasCodepointThreat(s, { singleLine: "reject" })) {
178
178
  throw new TypeError("operatorAccounts: " + (label || "actor_id") + " must not contain control bytes");
179
179
  }
180
180
  return s;
@@ -212,9 +212,7 @@ function _displayName(s) {
212
212
  if (typeof s !== "string" || !s.length || s.length > MAX_DISPLAY_NAME_LEN) {
213
213
  throw new TypeError("operatorAccounts: display_name must be a non-empty string (<= " + MAX_DISPLAY_NAME_LEN + " chars)");
214
214
  }
215
- if (CONTROL_BYTE_RE.test(s)) {
216
- throw new TypeError("operatorAccounts: display_name must not contain control bytes");
217
- }
215
+ textGuard.freeText(s, "operatorAccounts: display_name", { singleLine: "reject" });
218
216
  return s;
219
217
  }
220
218