@blamejs/blamejs-shop 0.3.52 → 0.3.53

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.3.52",
2
+ "version": "0.3.53",
3
3
  "assets": {
4
4
  "css/admin.css": {
5
5
  "integrity": "sha384-/NizdENQkTc96romD0kmnl8220NAvW32QI0aSFuGiPbWE5lYbRxLXU5shsjlx5Ek",
package/lib/email.js CHANGED
@@ -220,6 +220,49 @@ var REVIEW_REQUEST_TEXT_LINE =
220
220
  var REVIEW_REQUEST_TEXT_AFTER_LINES =
221
221
  "\nEach link takes about a minute. Thank you.\n";
222
222
 
223
+ // Back-in-stock — double-opt-in confirmation + the fired notification.
224
+ // Both auto-escape every field through the strict `{{var}}` renderer
225
+ // (product_title / sku are catalog data; escape anyway).
226
+
227
+ var STOCK_ALERT_CONFIRM_HTML =
228
+ "<!DOCTYPE html>\n" +
229
+ "<html lang=\"en\"><head><meta charset=\"utf-8\"><title>Confirm your back-in-stock alert</title></head>" +
230
+ "<body style=\"margin:0;background:#ffffff;color:#0d0d0d;font-family:system-ui,sans-serif;\">\n" +
231
+ "<div style=\"max-width:560px;margin:0 auto;padding:24px;\">\n" +
232
+ " <h1 style=\"color:#0d0d0d;margin:0 0 12px;\">Confirm your back-in-stock alert</h1>\n" +
233
+ " <p style=\"margin:0 0 16px;\">You asked to be emailed when <strong>{{product_title}}</strong> (SKU {{sku}}) is back in stock. Confirm below and we'll email you once, the moment it returns.</p>\n" +
234
+ " <p style=\"margin:24px 0;\"><a href=\"{{confirm_url}}\" style=\"background:#fa4f09;color:#ffffff;padding:12px 20px;text-decoration:none;display:inline-block;font-weight:bold;\">Confirm my alert</a></p>\n" +
235
+ " <p style=\"margin:0;color:#0d0d0d;font-size:13px;\">If you didn't request this, ignore this email — no alert is set until you confirm. Link: {{confirm_url}}</p>\n" +
236
+ "</div>\n" +
237
+ "</body></html>\n";
238
+
239
+ var STOCK_ALERT_CONFIRM_TEXT =
240
+ "Confirm your back-in-stock alert\n\n" +
241
+ "You asked to be emailed when {{product_title}} (SKU {{sku}}) is back in stock.\n" +
242
+ "Confirm to set the alert — we'll email you once, the moment it returns.\n\n" +
243
+ "Confirm: {{confirm_url}}\n\n" +
244
+ "If you didn't request this, ignore this email — no alert is set until you confirm.\n";
245
+
246
+ var BACK_IN_STOCK_HTML =
247
+ "<!DOCTYPE html>\n" +
248
+ "<html lang=\"en\"><head><meta charset=\"utf-8\"><title>Back in stock: {{product_title}}</title></head>" +
249
+ "<body style=\"margin:0;background:#ffffff;color:#0d0d0d;font-family:system-ui,sans-serif;\">\n" +
250
+ "<div style=\"max-width:560px;margin:0 auto;padding:24px;\">\n" +
251
+ " <h1 style=\"color:#0d0d0d;margin:0 0 12px;\">It's back in stock</h1>\n" +
252
+ " <p style=\"margin:0 0 16px;\"><strong>{{product_title}}</strong> (SKU {{sku}}) is available again. Stock can move fast — grab it while it's here.</p>\n" +
253
+ " <p style=\"margin:24px 0;\"><a href=\"{{product_url}}\" style=\"background:#fa4f09;color:#ffffff;padding:12px 20px;text-decoration:none;display:inline-block;font-weight:bold;\">View the product</a></p>\n" +
254
+ " <p style=\"margin:0;color:#0d0d0d;font-size:13px;\">Link: {{product_url}}</p>\n" +
255
+ " <p style=\"margin:16px 0 0;color:#0d0d0d;font-size:13px;\">Don't want these alerts? Unsubscribe: {{unsubscribe_url}}</p>\n" +
256
+ "</div>\n" +
257
+ "</body></html>\n";
258
+
259
+ var BACK_IN_STOCK_TEXT =
260
+ "It's back in stock\n\n" +
261
+ "{{product_title}} (SKU {{sku}}) is available again.\n" +
262
+ "Stock can move fast — grab it while it's here.\n\n" +
263
+ "View the product: {{product_url}}\n\n" +
264
+ "Don't want these alerts? Unsubscribe: {{unsubscribe_url}}\n";
265
+
223
266
  // ---- factory ------------------------------------------------------------
224
267
 
225
268
  function _email(s) {
@@ -460,6 +503,53 @@ function create(opts) {
460
503
  html, text, input.replyTo
461
504
  );
462
505
  },
506
+
507
+ // Back-in-stock double-opt-in confirmation. `confirm_url` is built by
508
+ // the route from SHOP_ORIGIN + the plaintext token returned once by
509
+ // stockAlerts.subscribe. product_title / sku are catalog data; the
510
+ // strict renderer escapes every field anyway (escape-by-default).
511
+ sendStockAlertConfirmation: async function (input) {
512
+ if (!input) throw new TypeError("email.sendStockAlertConfirmation: input object required");
513
+ if (typeof input.to !== "string" || !input.to) {
514
+ throw new TypeError("email.sendStockAlertConfirmation: to required");
515
+ }
516
+ if (typeof input.confirm_url !== "string" || !input.confirm_url) {
517
+ throw new TypeError("email.sendStockAlertConfirmation: confirm_url required");
518
+ }
519
+ var vars = {
520
+ product_title: input.product_title == null ? input.sku || "this item" : String(input.product_title),
521
+ sku: input.sku == null ? "—" : String(input.sku),
522
+ confirm_url: input.confirm_url,
523
+ };
524
+ var html = _render(STOCK_ALERT_CONFIRM_HTML, vars);
525
+ var text = _render(STOCK_ALERT_CONFIRM_TEXT, vars);
526
+ return await _send(input.to, "Confirm your back-in-stock alert", html, text, input.replyTo);
527
+ },
528
+
529
+ // Back-in-stock fired notification. Subject is built from the same
530
+ // already-escaped var path as the body (the subject string itself is
531
+ // never HTML — it carries the raw title; the mailer transport escapes
532
+ // header-injection, and the body is rendered through the strict
533
+ // escaping renderer).
534
+ sendBackInStock: async function (input) {
535
+ if (!input) throw new TypeError("email.sendBackInStock: input object required");
536
+ if (typeof input.to !== "string" || !input.to) {
537
+ throw new TypeError("email.sendBackInStock: to required");
538
+ }
539
+ if (typeof input.product_url !== "string" || !input.product_url) {
540
+ throw new TypeError("email.sendBackInStock: product_url required");
541
+ }
542
+ var title = input.product_title == null ? (input.sku || "this item") : String(input.product_title);
543
+ var vars = {
544
+ product_title: title,
545
+ sku: input.sku == null ? "—" : String(input.sku),
546
+ product_url: input.product_url,
547
+ unsubscribe_url: input.unsubscribe_url == null ? "" : String(input.unsubscribe_url),
548
+ };
549
+ var html = _render(BACK_IN_STOCK_HTML, vars);
550
+ var text = _render(BACK_IN_STOCK_TEXT, vars);
551
+ return await _send(input.to, "Back in stock: " + title, html, text, input.replyTo);
552
+ },
463
553
  };
464
554
  }
465
555
 
@@ -491,5 +581,9 @@ module.exports = {
491
581
  REVIEW_REQUEST_TEXT_BEFORE_LINES: REVIEW_REQUEST_TEXT_BEFORE_LINES,
492
582
  REVIEW_REQUEST_TEXT_LINE: REVIEW_REQUEST_TEXT_LINE,
493
583
  REVIEW_REQUEST_TEXT_AFTER_LINES: REVIEW_REQUEST_TEXT_AFTER_LINES,
584
+ STOCK_ALERT_CONFIRM_HTML: STOCK_ALERT_CONFIRM_HTML,
585
+ STOCK_ALERT_CONFIRM_TEXT: STOCK_ALERT_CONFIRM_TEXT,
586
+ BACK_IN_STOCK_HTML: BACK_IN_STOCK_HTML,
587
+ BACK_IN_STOCK_TEXT: BACK_IN_STOCK_TEXT,
494
588
  },
495
589
  };
@@ -126,6 +126,15 @@ var EDGE_POST_PATHS = [
126
126
  // layer). Container-only — there's no edge copy to keep parity with.
127
127
  "/unsubscribe",
128
128
  "/announcements/",
129
+ // Back-in-stock "Notify me" — the subscribe form lives on the edge-cached,
130
+ // cookie-less PDP buy box (dual-rendered byte-identical with the worker
131
+ // twin), so it cannot carry a per-session `_csrf` token. A DISTINCT
132
+ // top-level action keeps `/products/` (which DOES require the token on its
133
+ // review + question POSTs) un-exempted. Keeps SameSite + fetch-metadata
134
+ // defense; the confirm/unsubscribe GETs need no CSRF. Kept in lockstep with
135
+ // the `edge-form-csrf-exempt` detector lookahead in
136
+ // test/layer-0-primitives/codebase-patterns.test.js.
137
+ "/stock-alert/subscribe",
129
138
  ];
130
139
 
131
140
  // The TLS-terminated public origin(s) the storefront is served on. Behind