PageSourceSearch

https://safety.snipershopping.com/shared/js/account.js

js snipershopping.com collected 2026-10-06 10:32:59 UTC 5,439 bytes, 131 lines download raw bytes

1/* Sniper - the session.
2   One account across both storefronts (7.1): the jacket from Shopping and the
3   harness from Safety sit in one order history.
4
5   This file holds only the session and the rules about it. The sign in and
6   register forms live on login - see shared/js/login-page.js. A page
7   rather than a modal, because it can be linked to, bookmarked, returned to
8   after a password reset, and read by a password manager without a fight.
9
10   The server decides who is signed in (an HttpOnly session cookie). It also
11   sets a readable "sniper_in" cookie, so guests never ask /api/auth/me at all.
12   The last answer is kept in sessionStorage only to paint the header without a
13   flicker on the next page; it is a display cache, never trusted for anything. */
14(function () {
15  "use strict";
16
17  var CACHE = "sniper.me.v1";
18  var L = window.SNIPER_LOYALTY || {};
19  var user = null;
20
21  /* the prototype kept a fake session in localStorage - clear it once */
22  try { localStorage.removeItem("sniper.account.v1"); } catch (e) { /* ignore */ }
23
24  function hinted() { return /(?:^|;\s*)sniper_in=1/.test(document.cookie); }
25
26  try {
27    if (hinted()) user = JSON.parse(sessionStorage.getItem(CACHE) || "null");
28    else sessionStorage.removeItem(CACHE);
29  } catch (e) { user = null; }
30
31  function set(u) {
32    user = u || null;
33    try {
34      if (user) sessionStorage.setItem(CACHE, JSON.stringify(user));
35      else sessionStorage.removeItem(CACHE);
36    } catch (e) { /* ignore */ }
37    paintHeader();
38    document.dispatchEvent(new CustomEvent("sniper:account", { detail: { user: user } }));
39  }
40
41  /* Resolves with the signed-in customer, or null. */
42  var ready = (!hinted() || !window.sniperApi)
43    ? Promise.resolve(null).then(function () { set(null); return null; })
44    : window.sniperApi("/auth/me").then(function (d) { set(d.user); return d.user; },
45        function (err) {
46          /* offline or the server is down: keep the cached name on screen */
47          if (err && (err.network || err.offline)) return user;
48          set(null); return null;
49        });
50
51  /* ------------------------------------------------------------- header */
52  function paintHeader() {
53    var u = user;
54    document.querySelectorAll(".js-who").forEach(function (n) {
55      if (!u) { n.hidden = true; return; }
56      n.hidden = false;
57      n.textContent = String(u.name || u.email || "").trim().charAt(0).toUpperCase();
58    });
59    document.querySelectorAll(".js-account").forEach(function (b) {
60      b.classList.toggle("is-in", !!u);
61      b.setAttribute("title", u ? (u.name || u.email) : "");
62    });
63    /* the label under the icon: "Sign in" for guests, "My account" once signed in */
64    var I = window.sniperI18n;
65    document.querySelectorAll(".js-authlabel").forEach(function (n) {
66      var key = u ? "nav.myAccount" : "auth.signin";
67      n.setAttribute("data-i18n", key);
68      n.textContent = I ? I.t(key, u ? "My account" : "Sign in") : (u ? "My account" : "Sign in");
69    });
70  }
71
72  /* Only same-site paths are followed after sign-in - never another site. */
73  function safeNext(v, fallback) {
74    // browsers drop tabs / newlines and leading spaces from a link ("java\tscript:"),
75    // so the value is read the way the browser would, then kept only if it stays here
76    v = String(v || "").replace(/[\u0000-\u0020\u007f]+/g, "");
77    if (!v || v.indexOf("\\") > -1) return fallback;
78    var u;
79    try { u = new URL(v, location.href); } catch (e) { return fallback; }
80    if (u.origin !== location.origin || !/^https?:$/.test(u.protocol)) return fallback;
81    return (u.pathname + u.search + u.hash).replace(/^\/+/, "") || fallback;
82  }
83
84  /* Where to send someone who clicks the figure in the header. Signed in goes
85     to the account; signed out goes to the form, carrying where they were so
86     they come back to it instead of being dropped on the home page. */
87  function target(mode) {
88    if (user || hinted()) return "account";
89    var here = (location.pathname.split("/").pop() || "").replace(/\.html$/, "");
90    var q = [];
91    if (mode === "register") q.push("mode=register");
92    if (here !== "login" && here !== "index" && here !== "") {
93      q.push("next=" + encodeURIComponent(here + location.search));
94    }
95    return "login" + (q.length ? "?" + q.join("&") : "");
96  }
97
98  function signOut() {
99    var done = function () { set(null); location.href = "./"; };
100    if (window.sniperApi) window.sniperApi("/auth/logout", { method: "POST" }).then(done, done);
101    else done();
102  }
103
104  document.addEventListener("click", function (e) {
105    if (e.target.closest(".js-account") || e.target.closest(".js-signin")) {
106      e.preventDefault(); location.href = target("signin"); return;
107    }
108    if (e.target.closest(".js-register")) {
109      e.preventDefault(); location.href = target("register"); return;
110    }
111    if (e.target.closest(".js-signout")) {
112      e.preventDefault(); signOut();
113    }
114  });
115
116  window.sniperAccount = {
117    user: function () { return user; },
118    ready: ready,
119    set: set,
120    signOut: signOut,
121    target: target,
122    safeNext: safeNext,
123    /* one place asks whether the loyalty card exists at all - see 11.9.2 */
124    loyaltyOn: function () { return L.enabled === true; },
125    loyalty: L
126  };
127
128  document.addEventListener("sniper:lang", paintHeader);
129  if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", paintHeader);
130  else paintHeader();
131})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.