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.