@occluze/tether 0.6.7 → 0.6.8
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/README.md +3 -3
- package/dist/esm/astro.js +2 -2
- package/dist/esm/chunks/account-C3NP6HFB.js +1 -0
- package/dist/esm/chunks/account-pages-WLK374FO.js +1 -0
- package/dist/esm/chunks/analytics-G3SAWMNL.js +1 -0
- package/dist/esm/chunks/chunk-27PJJZGC.js +1 -0
- package/dist/esm/chunks/chunk-2FSXS55U.js +1 -0
- package/dist/esm/chunks/chunk-FCSWQDVV.js +1 -0
- package/dist/esm/chunks/chunk-FOMSMIAM.js +1 -0
- package/dist/esm/chunks/chunk-GZJTCLLW.js +1 -0
- package/dist/esm/chunks/chunk-HEQILZVR.js +1 -0
- package/dist/esm/chunks/chunk-JOOEXYF3.js +4 -0
- package/dist/esm/chunks/chunk-MPTQYLSM.js +1 -0
- package/dist/esm/chunks/chunk-NEHZ37TU.js +3 -0
- package/dist/esm/chunks/chunk-V74KGEJF.js +1 -0
- package/dist/esm/chunks/chunk-VT4CDUJJ.js +1 -0
- package/dist/esm/chunks/chunk-YJLWX2HK.js +1 -0
- package/dist/esm/chunks/diagnose-6VVGZNTY.js +14 -0
- package/dist/esm/chunks/extras-GJVRDFM3.js +1 -0
- package/dist/esm/chunks/markets-ui-UNB4PTA7.js +1 -0
- package/dist/esm/chunks/search-OU75RNQL.js +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/server.js +2 -1
- package/dist/presets/barba.esm.js +56 -36
- package/dist/tether.account-pages.js +773 -442
- package/dist/tether.account-pages.min.js +1 -1
- package/dist/tether.account.js +648 -318
- package/dist/tether.account.min.js +1 -1
- package/dist/tether.analytics.js +318 -191
- package/dist/tether.analytics.min.js +1 -1
- package/dist/tether.diagnose.js +331 -242
- package/dist/tether.diagnose.min.js +2 -2
- package/dist/tether.extras.js +78 -33
- package/dist/tether.extras.min.js +1 -1
- package/dist/tether.js +3176 -2015
- package/dist/tether.markets.js +231 -126
- package/dist/tether.markets.min.js +1 -1
- package/dist/tether.min.js +0 -0
- package/dist/tether.min.js.map +4 -4
- package/dist/tether.search.js +221 -126
- package/dist/tether.search.min.js +1 -1
- package/package.json +23 -4
- package/src/account/addresses-view.js +92 -0
- package/src/account/browser-session.js +359 -0
- package/src/account/markup.js +48 -0
- package/src/account/operations.js +180 -0
- package/src/account/order-lines.js +96 -0
- package/src/account/order-list.js +91 -0
- package/src/account/order-page.js +192 -0
- package/src/account/page-events.js +288 -0
- package/src/account/server-client.js +172 -0
- package/src/account/shared.js +111 -0
- package/src/account/shopify-auth.js +150 -0
- package/src/account/subscriptions-view.js +158 -0
- package/src/account-api.js +118 -0
- package/src/account-links.js +35 -0
- package/src/account-pages.js +69 -298
- package/src/account.js +280 -323
- package/src/amounts.js +36 -0
- package/src/analytics-shopify.js +241 -0
- package/src/analytics.js +304 -184
- package/src/api-version.js +75 -25
- package/src/astro.js +233 -68
- package/src/cart/checkout.js +97 -0
- package/src/cart/drawer.js +138 -0
- package/src/cart/mutations.js +234 -0
- package/src/cart/render.js +294 -0
- package/src/cart/store.js +326 -0
- package/src/cart.js +320 -488
- package/src/client.js +214 -78
- package/src/collection.js +126 -57
- package/src/config.js +323 -59
- package/src/controls/cart-controls.js +155 -0
- package/src/controls/discount.js +96 -0
- package/src/controls/extras.js +63 -0
- package/src/controls/navigation.js +83 -0
- package/src/diagnose-markup.js +333 -0
- package/src/diagnose.js +251 -202
- package/src/dom.js +219 -220
- package/src/extras.js +119 -37
- package/src/fragments.js +115 -22
- package/src/images.js +104 -0
- package/src/index.js +245 -287
- package/src/initial.js +78 -66
- package/src/lazy-iife.js +92 -22
- package/src/lazy.js +25 -4
- package/src/loader.js +263 -124
- package/src/market-codes.js +35 -0
- package/src/markets-ui.js +169 -55
- package/src/markets.js +286 -85
- package/src/markup.js +64 -0
- package/src/mounting.js +214 -91
- package/src/page-market.js +116 -0
- package/src/parts/account-pages.js +4 -1
- package/src/parts/account.js +4 -1
- package/src/parts/analytics.js +4 -1
- package/src/parts/diagnose.js +4 -1
- package/src/parts/extras.js +4 -1
- package/src/parts/markets.js +4 -1
- package/src/parts/search.js +4 -1
- package/src/presets/barba.js +120 -30
- package/src/product-events.js +130 -0
- package/src/product-render.js +257 -0
- package/src/product-state.js +318 -0
- package/src/product.js +233 -459
- package/src/repeaters.js +173 -0
- package/src/search.js +277 -107
- package/src/selling-plans.js +107 -47
- package/src/server.js +168 -205
- package/src/shopify-values.js +89 -0
- package/src/status.js +99 -39
- package/src/store-html.js +0 -0
- package/src/storefront/catalog.js +199 -0
- package/src/storefront/json-ld.js +167 -0
- package/src/variant-render.js +165 -0
- package/types/astro.d.ts +13 -2
- package/types/index.d.ts +178 -27
- package/types/server.d.ts +89 -17
- package/dist/esm/chunks/account-Q6E2TFEV.js +0 -1
- package/dist/esm/chunks/account-pages-UZXR4R5F.js +0 -1
- package/dist/esm/chunks/analytics-IJBKATUP.js +0 -1
- package/dist/esm/chunks/chunk-4QJKLPYR.js +0 -1
- package/dist/esm/chunks/chunk-64TUD3QR.js +0 -1
- package/dist/esm/chunks/chunk-AJLPU2JC.js +0 -4
- package/dist/esm/chunks/chunk-FIOZPPC5.js +0 -1
- package/dist/esm/chunks/chunk-K2YEQAIH.js +0 -1
- package/dist/esm/chunks/chunk-KIDH76B7.js +0 -1
- package/dist/esm/chunks/chunk-PAYT5ZD7.js +0 -1
- package/dist/esm/chunks/chunk-RREHGH4A.js +0 -1
- package/dist/esm/chunks/chunk-SSJT43CJ.js +0 -1
- package/dist/esm/chunks/chunk-VC46IEJQ.js +0 -1
- package/dist/esm/chunks/diagnose-3WVAWNTX.js +0 -14
- package/dist/esm/chunks/extras-7I7WJX35.js +0 -1
- package/dist/esm/chunks/markets-ui-545XLFTE.js +0 -1
- package/dist/esm/chunks/search-NILMGYXG.js +0 -1
package/dist/tether.account.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
(() => {
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __export = (target, all) => {
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
mountAccount: () => mountAccount,
|
|
20
21
|
order: () => order,
|
|
21
22
|
orders: () => orders,
|
|
22
|
-
query: () =>
|
|
23
|
+
query: () => query2,
|
|
23
24
|
render: () => render,
|
|
24
25
|
requestReturn: () => requestReturn,
|
|
25
26
|
saveAddress: () => saveAddress,
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
|
|
31
32
|
// tether-core:config.js
|
|
32
33
|
var m = window.Tether.__core["config.js"];
|
|
33
|
-
var { DEFAULTS, cfg, configure, isConfigured, log, reportError, warn } = m;
|
|
34
|
+
var { DEFAULTS, cfg, configure, isConfigured, log, reportError, resolveConfig, toMetafieldIdentifier, warn } = m;
|
|
34
35
|
|
|
35
36
|
// tether-core:client.js
|
|
36
37
|
var m2 = window.Tether.__core["client.js"];
|
|
@@ -38,422 +39,751 @@
|
|
|
38
39
|
|
|
39
40
|
// tether-core:dom.js
|
|
40
41
|
var m3 = window.Tether.__core["dom.js"];
|
|
41
|
-
var { attr, busy, cdnUrl,
|
|
42
|
+
var { attr, busy, cdnUrl, emit, formatDate, formatMoney, formatPercent, get, has, hasTemplate, imageUrl, notifyLayoutChanged, parseJSONAttr, productUrl, qsa, repeat, repeatKeyed, sameImage, scoped, sel, setImage, setSelected, setStoreHtml, setText, setTextTracked, show, toAttributes, watchImages } = m3;
|
|
42
43
|
|
|
43
44
|
// tether-core:cart.js
|
|
44
45
|
var m4 = window.Tether.__core["cart.js"];
|
|
45
|
-
var { Cart,
|
|
46
|
+
var { Cart, trapFocus } = m4;
|
|
46
47
|
|
|
47
48
|
// tether-core:lazy.js
|
|
48
49
|
var m5 = window.Tether.__core["lazy.js"];
|
|
49
50
|
var { coreModules, loadPart } = m5;
|
|
50
51
|
|
|
51
|
-
// src/account.js
|
|
52
|
-
var
|
|
53
|
-
var
|
|
54
|
-
var
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
// server/src/shared/account-documents.js
|
|
53
|
+
var MONEY = "amount currencyCode";
|
|
54
|
+
var CUSTOMER_QUERY = "query TetherCustomer { customer { id firstName lastName displayName emailAddress { emailAddress } defaultAddress { formatted } } }";
|
|
55
|
+
var ORDERS_QUERY = `query TetherOrders($first:Int!){customer{orders(first:$first,sortKey:PROCESSED_AT,reverse:true){nodes{id name processedAt financialStatus fulfillmentStatus statusPageUrl totalPrice{${MONEY}}lineItems(first:20){nodes{title variantTitle quantity image{url altText}}}}}}}`;
|
|
56
|
+
|
|
57
|
+
// server/src/shared/base64url.js
|
|
58
|
+
function toBase64Url(bytes) {
|
|
59
|
+
return btoa(String.fromCharCode(...new Uint8Array(bytes))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
60
|
+
}
|
|
61
|
+
function fromBase64Url(text) {
|
|
62
|
+
const base64 = text.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(text.length / 4) * 4, "=");
|
|
63
|
+
return Uint8Array.from(atob(base64), (char) => char.charCodeAt(0));
|
|
64
|
+
}
|
|
65
|
+
function randomToken(bytes = 32) {
|
|
66
|
+
return toBase64Url(crypto.getRandomValues(new Uint8Array(bytes)));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/account/shared.js
|
|
70
|
+
var sessionKey = () => `${cfg.prefix}:account:${cfg.domain}`;
|
|
71
|
+
var pendingLoginKey = () => `${cfg.prefix}:account-login:${cfg.domain}`;
|
|
72
|
+
var hintKey = () => `${cfg.prefix}:account-hint:${cfg.domain}`;
|
|
73
|
+
var sessionArea = () => cfg.account?.remember ? "local" : "session";
|
|
74
|
+
var storageOf = (area) => area === "local" ? window.localStorage : window.sessionStorage;
|
|
75
|
+
function readJsonIfAllowed(area, key) {
|
|
60
76
|
try {
|
|
61
|
-
return JSON.parse(
|
|
77
|
+
return JSON.parse(storageOf(area).getItem(key) ?? "null");
|
|
62
78
|
} catch {
|
|
63
79
|
return null;
|
|
64
80
|
}
|
|
65
|
-
}
|
|
66
|
-
|
|
81
|
+
}
|
|
82
|
+
function writeJsonIfAllowed(area, key, value) {
|
|
67
83
|
try {
|
|
68
|
-
if (
|
|
69
|
-
else
|
|
84
|
+
if (value === null) storageOf(area).removeItem(key);
|
|
85
|
+
else storageOf(area).setItem(key, JSON.stringify(value));
|
|
70
86
|
} catch {
|
|
71
87
|
}
|
|
72
|
-
};
|
|
73
|
-
var discovery = null;
|
|
74
|
-
var session = null;
|
|
75
|
-
var refreshing = null;
|
|
76
|
-
var customerCache = null;
|
|
77
|
-
var server = () => cfg.account?.server ? new URL(String(cfg.account.server), window.location.href).href.replace(/\/$/, "") : "";
|
|
78
|
-
var HINT = () => `${cfg.prefix}:account-hint:${cfg.domain}`;
|
|
79
|
-
var serverState = null;
|
|
80
|
-
async function serverFetch(path, { method = "GET", body } = {}) {
|
|
81
|
-
const res = await fetch(`${server()}${path}`, {
|
|
82
|
-
method,
|
|
83
|
-
credentials: "include",
|
|
84
|
-
headers: method === "POST" ? { "Content-Type": "application/json", "X-Tether-Account": "1" } : {},
|
|
85
|
-
body: body ? JSON.stringify(body) : void 0
|
|
86
|
-
});
|
|
87
|
-
const data = await res.json().catch(() => ({}));
|
|
88
|
-
if (!res.ok && res.status !== 401) throw new TetherError(`Customer accounts: ${data.error || res.status}`, { status: res.status, userErrors: data.userErrors });
|
|
89
|
-
return data;
|
|
90
88
|
}
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
function setLoginHint(loggedIn) {
|
|
90
|
+
writeJsonIfAllowed("local", hintKey(), loggedIn ? 1 : null);
|
|
91
|
+
}
|
|
92
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
93
|
+
var textOrNull = (value) => typeof value === "string" ? value : null;
|
|
94
|
+
async function readJsonObjectOrEmpty(response) {
|
|
95
|
+
let json;
|
|
93
96
|
try {
|
|
94
|
-
|
|
95
|
-
else window.localStorage.removeItem(HINT());
|
|
97
|
+
json = await response.json();
|
|
96
98
|
} catch {
|
|
99
|
+
return {};
|
|
97
100
|
}
|
|
98
|
-
return
|
|
101
|
+
return isRecord(json) ? json : {};
|
|
99
102
|
}
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
function navigate(url) {
|
|
104
|
+
const replacement = cfg.account?.navigate;
|
|
105
|
+
if (replacement) replacement(url);
|
|
106
|
+
else window.location.assign(url);
|
|
107
|
+
}
|
|
108
|
+
var loginLanguage = (locale) => locale || cfg.account?.locale || (cfg.locale || "").split("-")[0] || "";
|
|
109
|
+
var loginCountry = () => cfg.country ? cfg.country.toUpperCase() : "";
|
|
110
|
+
|
|
111
|
+
// src/account/shopify-auth.js
|
|
112
|
+
var DEFAULT_TOKEN_SECONDS = 3600;
|
|
113
|
+
var endpoints = null;
|
|
114
|
+
async function wellKnown(document2) {
|
|
102
115
|
const shop = cfg.account?.shopDomain || cfg.domain;
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
const response = await fetch(`https://${shop}/.well-known/${document2}`);
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
throw new TetherError(`Customer accounts: ${document2} answered ${response.status}`, { status: response.status });
|
|
119
|
+
}
|
|
120
|
+
const json = (
|
|
121
|
+
/** @type {unknown} */
|
|
122
|
+
await response.json()
|
|
123
|
+
);
|
|
124
|
+
return isRecord(json) ? json : {};
|
|
110
125
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
function endpointIn(json, name) {
|
|
127
|
+
const url = textOrNull(json[name]);
|
|
128
|
+
if (!url) throw new TetherError(`Customer accounts: Shopify named no ${name}`);
|
|
129
|
+
return url;
|
|
114
130
|
}
|
|
115
|
-
async function
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
131
|
+
async function discover() {
|
|
132
|
+
if (endpoints) return endpoints;
|
|
133
|
+
const [openid, api] = await Promise.all([wellKnown("openid-configuration"), wellKnown("customer-account-api")]);
|
|
134
|
+
endpoints = {
|
|
135
|
+
issuer: endpointIn(openid, "issuer"),
|
|
136
|
+
authorizationEndpoint: endpointIn(openid, "authorization_endpoint"),
|
|
137
|
+
tokenEndpoint: endpointIn(openid, "token_endpoint"),
|
|
138
|
+
endSessionEndpoint: endpointIn(openid, "end_session_endpoint"),
|
|
139
|
+
graphql: endpointIn(api, "graphql_api")
|
|
140
|
+
};
|
|
141
|
+
return endpoints;
|
|
142
|
+
}
|
|
143
|
+
async function requestToken(params) {
|
|
144
|
+
const { tokenEndpoint } = await discover();
|
|
145
|
+
const response = await fetch(cfg.account?.tokenUrl || tokenEndpoint, {
|
|
146
|
+
method: "POST",
|
|
147
|
+
body: new URLSearchParams(params)
|
|
148
|
+
});
|
|
149
|
+
const data = await readJsonObjectOrEmpty(response);
|
|
150
|
+
const access = textOrNull(data["access_token"]);
|
|
151
|
+
if (!response.ok || !access) {
|
|
152
|
+
const ended = response.status === 400 || response.status === 401;
|
|
153
|
+
throw new TetherError(`Customer accounts: token ${response.status} ${textOrNull(data["error"]) ?? ""}`.trim(), {
|
|
154
|
+
status: response.status,
|
|
155
|
+
code: ended ? "SESSION_ENDED" : "TOKEN_FAILED"
|
|
156
|
+
});
|
|
124
157
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const redirect = callbackUrl();
|
|
131
|
-
write(window.sessionStorage, PENDING(), { state, nonce, verifier, redirect, returnTo, at: Date.now() });
|
|
132
|
-
const u = new URL(d.authorization_endpoint);
|
|
133
|
-
const p = {
|
|
134
|
-
client_id: cfg.account.clientId,
|
|
135
|
-
scope: "openid email customer-account-api:full",
|
|
136
|
-
response_type: "code",
|
|
137
|
-
redirect_uri: redirect,
|
|
138
|
-
state,
|
|
139
|
-
nonce,
|
|
140
|
-
code_challenge: await sha256(verifier),
|
|
141
|
-
code_challenge_method: "S256"
|
|
158
|
+
return {
|
|
159
|
+
access,
|
|
160
|
+
refresh: textOrNull(data["refresh_token"]) || null,
|
|
161
|
+
idToken: textOrNull(data["id_token"]) || null,
|
|
162
|
+
expiresInSeconds: Number(data["expires_in"]) || DEFAULT_TOKEN_SECONDS
|
|
142
163
|
};
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
async function tokenRequest(params) {
|
|
150
|
-
const d = await discover();
|
|
151
|
-
const res = await fetch(cfg.account.tokenUrl || d.token_endpoint, { method: "POST", body: new URLSearchParams(params) });
|
|
152
|
-
const data = await res.json().catch(() => ({}));
|
|
153
|
-
if (!res.ok || !data.access_token) throw new TetherError(`Customer accounts: token ${res.status} ${data.error || ""}`.trim(), { status: res.status, code: res.status === 400 || res.status === 401 ? "SESSION_ENDED" : "TOKEN_FAILED" });
|
|
154
|
-
return data;
|
|
155
|
-
}
|
|
156
|
-
function decodeJwt(t) {
|
|
164
|
+
}
|
|
165
|
+
function idTokenClaims(idToken) {
|
|
166
|
+
const payload = idToken.split(".")[1];
|
|
167
|
+
if (!payload) return null;
|
|
168
|
+
let claims;
|
|
157
169
|
try {
|
|
158
|
-
|
|
170
|
+
claims = JSON.parse(new TextDecoder().decode(fromBase64Url(payload)));
|
|
159
171
|
} catch {
|
|
160
172
|
return null;
|
|
161
173
|
}
|
|
174
|
+
return isRecord(claims) ? claims : null;
|
|
175
|
+
}
|
|
176
|
+
function answersLogin(idToken, { nonce, issuer, client }) {
|
|
177
|
+
const claims = idToken ? idTokenClaims(idToken) : null;
|
|
178
|
+
if (!claims) return false;
|
|
179
|
+
const audience = claims["aud"];
|
|
180
|
+
const audiences = Array.isArray(audience) ? audience : [audience];
|
|
181
|
+
const expiresAt = claims["exp"];
|
|
182
|
+
return claims["nonce"] === nonce && claims["iss"] === issuer && audiences.includes(client) && typeof expiresAt === "number" && expiresAt * 1e3 >= Date.now();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/account/browser-session.js
|
|
186
|
+
var REFRESH_MARGIN_SECONDS = 120;
|
|
187
|
+
var LOGIN_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
188
|
+
var session = null;
|
|
189
|
+
var customerProfile = null;
|
|
190
|
+
var refreshing = null;
|
|
191
|
+
function toSession(value) {
|
|
192
|
+
if (!isRecord(value)) return null;
|
|
193
|
+
const access = value["access"];
|
|
194
|
+
const expiresAt = value["expiresAt"];
|
|
195
|
+
if (typeof access !== "string" || typeof expiresAt !== "number") return null;
|
|
196
|
+
return { access, expiresAt, refresh: textOrNull(value["refresh"]), idToken: textOrNull(value["idToken"]) };
|
|
162
197
|
}
|
|
163
|
-
function
|
|
198
|
+
function toPendingLogin(value) {
|
|
199
|
+
if (!isRecord(value)) return null;
|
|
200
|
+
const { state, nonce, verifier, redirect, returnTo, at } = value;
|
|
201
|
+
if (typeof state !== "string" || typeof nonce !== "string" || typeof verifier !== "string") return null;
|
|
202
|
+
if (typeof redirect !== "string" || typeof returnTo !== "string" || typeof at !== "number") return null;
|
|
203
|
+
return { state, nonce, verifier, redirect, returnTo, at };
|
|
204
|
+
}
|
|
205
|
+
var currentSession = () => session ?? toSession(readJsonIfAllowed(sessionArea(), sessionKey()));
|
|
206
|
+
var accessToken = () => session?.access ?? null;
|
|
207
|
+
var hasBrowserLogin = () => Boolean(session && (session.refresh || session.access && session.expiresAt > Date.now()));
|
|
208
|
+
function clientId() {
|
|
209
|
+
const id = cfg.account?.clientId;
|
|
210
|
+
if (!id) throw new TetherError("Customer accounts: set account.clientId", { code: "NOT_CONFIGURED" });
|
|
211
|
+
return id;
|
|
212
|
+
}
|
|
213
|
+
var sha256 = async (text) => toBase64Url(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text)));
|
|
214
|
+
async function browserLoginRedirect({ returnTo, locale }) {
|
|
215
|
+
const client = clientId();
|
|
216
|
+
const endpoints2 = await discover();
|
|
217
|
+
const pending = {
|
|
218
|
+
state: randomToken(),
|
|
219
|
+
nonce: randomToken(),
|
|
220
|
+
verifier: randomToken(),
|
|
221
|
+
redirect: cfg.account?.callbackUrl || `${window.location.origin}${window.location.pathname}`,
|
|
222
|
+
returnTo,
|
|
223
|
+
at: Date.now()
|
|
224
|
+
};
|
|
225
|
+
writeJsonIfAllowed("session", pendingLoginKey(), pending);
|
|
226
|
+
const url = new URL(endpoints2.authorizationEndpoint);
|
|
227
|
+
const params = {
|
|
228
|
+
client_id: client,
|
|
229
|
+
scope: "openid email customer-account-api:full",
|
|
230
|
+
response_type: "code",
|
|
231
|
+
redirect_uri: pending.redirect,
|
|
232
|
+
state: pending.state,
|
|
233
|
+
nonce: pending.nonce,
|
|
234
|
+
code_challenge: await sha256(pending.verifier),
|
|
235
|
+
code_challenge_method: "S256"
|
|
236
|
+
};
|
|
237
|
+
const language = loginLanguage(locale);
|
|
238
|
+
if (language) params["locale"] = language;
|
|
239
|
+
const country = loginCountry();
|
|
240
|
+
if (country) params["region_country"] = country;
|
|
241
|
+
Object.entries(params).forEach(([name, value]) => {
|
|
242
|
+
url.searchParams.set(name, value);
|
|
243
|
+
});
|
|
244
|
+
navigate(url.toString());
|
|
245
|
+
}
|
|
246
|
+
function keep(answer, previous) {
|
|
164
247
|
session = {
|
|
165
|
-
access:
|
|
166
|
-
expiresAt: Date.now() + Math.max(0,
|
|
167
|
-
refresh:
|
|
168
|
-
idToken:
|
|
248
|
+
access: answer.access,
|
|
249
|
+
expiresAt: Date.now() + Math.max(0, answer.expiresInSeconds - REFRESH_MARGIN_SECONDS) * 1e3,
|
|
250
|
+
refresh: answer.refresh ?? previous?.refresh ?? null,
|
|
251
|
+
idToken: answer.idToken ?? previous?.idToken ?? null
|
|
169
252
|
};
|
|
170
|
-
|
|
253
|
+
writeJsonIfAllowed(sessionArea(), sessionKey(), session);
|
|
171
254
|
}
|
|
172
255
|
async function handleCallback() {
|
|
173
|
-
const
|
|
174
|
-
const pending =
|
|
175
|
-
if (!pending || !
|
|
176
|
-
|
|
256
|
+
const params = new URLSearchParams(window.location.search);
|
|
257
|
+
const pending = toPendingLogin(readJsonIfAllowed("session", pendingLoginKey()));
|
|
258
|
+
if (!pending || !params.has("code") && !params.has("error") || params.get("state") !== pending.state) return false;
|
|
259
|
+
writeJsonIfAllowed("session", pendingLoginKey(), null);
|
|
177
260
|
const clean = new URL(window.location.href);
|
|
178
|
-
["code", "state", "iss", "error", "error_description"].forEach((
|
|
261
|
+
["code", "state", "iss", "error", "error_description"].forEach((name) => {
|
|
262
|
+
clean.searchParams.delete(name);
|
|
263
|
+
});
|
|
179
264
|
window.history.replaceState(window.history.state, "", clean);
|
|
180
|
-
|
|
181
|
-
|
|
265
|
+
const error = params.get("error");
|
|
266
|
+
if (error !== null) {
|
|
267
|
+
emit("account-error", { error });
|
|
182
268
|
return false;
|
|
183
269
|
}
|
|
184
|
-
if (Date.now() - pending.at >
|
|
185
|
-
|
|
270
|
+
if (Date.now() - pending.at > LOGIN_TIMEOUT_MS) {
|
|
271
|
+
throw new TetherError("Customer accounts: the login took too long, please try again");
|
|
272
|
+
}
|
|
273
|
+
const client = clientId();
|
|
274
|
+
const answer = await requestToken({
|
|
186
275
|
grant_type: "authorization_code",
|
|
187
|
-
client_id:
|
|
276
|
+
client_id: client,
|
|
188
277
|
redirect_uri: pending.redirect,
|
|
189
|
-
code:
|
|
278
|
+
code: params.get("code") ?? "",
|
|
190
279
|
code_verifier: pending.verifier
|
|
191
280
|
});
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
const aud = [].concat(claims?.aud || []);
|
|
195
|
-
if (!claims || claims.nonce !== pending.nonce || claims.iss !== d.issuer || !aud.includes(cfg.account.clientId) || claims.exp * 1e3 < Date.now()) {
|
|
281
|
+
const { issuer } = await discover();
|
|
282
|
+
if (!answersLogin(answer.idToken, { nonce: pending.nonce, issuer, client })) {
|
|
196
283
|
throw new TetherError("Customer accounts: the login answer did not check out (nonce/issuer/audience)");
|
|
197
284
|
}
|
|
198
|
-
keep(
|
|
285
|
+
keep(answer, null);
|
|
199
286
|
await linkCart();
|
|
200
287
|
emit("account-login", {});
|
|
201
|
-
const back = pending.returnTo
|
|
202
|
-
if (back
|
|
288
|
+
const back = pending.returnTo ? new URL(pending.returnTo, window.location.href) : null;
|
|
289
|
+
if (back?.origin === window.location.origin && back.href !== clean.href) navigate(back.href);
|
|
203
290
|
return true;
|
|
204
291
|
}
|
|
292
|
+
function endSession() {
|
|
293
|
+
session = null;
|
|
294
|
+
customerProfile = null;
|
|
295
|
+
writeJsonIfAllowed(sessionArea(), sessionKey(), null);
|
|
296
|
+
Cart.setCustomer({ accessToken: null });
|
|
297
|
+
}
|
|
298
|
+
async function refresh(expired) {
|
|
299
|
+
try {
|
|
300
|
+
const answer = await requestToken({
|
|
301
|
+
grant_type: "refresh_token",
|
|
302
|
+
client_id: clientId(),
|
|
303
|
+
refresh_token: expired.refresh ?? ""
|
|
304
|
+
});
|
|
305
|
+
keep(answer, expired);
|
|
306
|
+
await linkCart();
|
|
307
|
+
} catch (error) {
|
|
308
|
+
if (error instanceof TetherError && error.code === "SESSION_ENDED") endSession();
|
|
309
|
+
else throw error;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
205
312
|
async function getToken() {
|
|
206
|
-
|
|
207
|
-
if (!session) session = read(store(), KEY());
|
|
313
|
+
session = currentSession();
|
|
208
314
|
if (!session) return null;
|
|
209
315
|
if (session.access && session.expiresAt > Date.now()) return session.access;
|
|
210
316
|
if (!session.refresh) {
|
|
211
317
|
endSession();
|
|
212
318
|
return null;
|
|
213
319
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
} catch (err) {
|
|
219
|
-
if (err.code === "SESSION_ENDED") endSession();
|
|
220
|
-
else throw err;
|
|
221
|
-
} finally {
|
|
222
|
-
refreshing = null;
|
|
223
|
-
}
|
|
224
|
-
})());
|
|
320
|
+
const expired = session;
|
|
321
|
+
refreshing ?? (refreshing = refresh(expired).finally(() => {
|
|
322
|
+
refreshing = null;
|
|
323
|
+
}));
|
|
225
324
|
await refreshing;
|
|
226
|
-
return
|
|
227
|
-
}
|
|
228
|
-
function endSession() {
|
|
229
|
-
session = null;
|
|
230
|
-
customerCache = null;
|
|
231
|
-
write(store(), KEY(), null);
|
|
232
|
-
Cart.customerAccessToken = null;
|
|
325
|
+
return accessToken();
|
|
233
326
|
}
|
|
234
327
|
async function linkCart() {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
328
|
+
const token = accessToken();
|
|
329
|
+
Cart.setCustomer({ accessToken: token });
|
|
330
|
+
if (!Cart.id() || !token) return;
|
|
331
|
+
try {
|
|
332
|
+
await Cart.setBuyerIdentity({ customerAccessToken: token }, { create: false });
|
|
333
|
+
} catch (error) {
|
|
334
|
+
reportError(error, "account cart");
|
|
238
335
|
}
|
|
239
336
|
}
|
|
240
|
-
async function
|
|
241
|
-
if (
|
|
337
|
+
async function unlinkCart() {
|
|
338
|
+
if (!Cart.id()) return;
|
|
339
|
+
try {
|
|
340
|
+
await Cart.setBuyerIdentity({ customerAccessToken: null }, { create: false });
|
|
341
|
+
} catch (error) {
|
|
342
|
+
reportError(error, "account cart unlink");
|
|
343
|
+
writeJsonIfAllowed("local", cfg.storageKey, null);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async function browserLogout(returnTo) {
|
|
347
|
+
const idToken = currentSession()?.idToken ?? null;
|
|
348
|
+
await unlinkCart();
|
|
349
|
+
endSession();
|
|
350
|
+
emit("account-logout", {});
|
|
351
|
+
if (!idToken) return "stayed";
|
|
352
|
+
const url = new URL((await discover()).endSessionEndpoint);
|
|
353
|
+
url.searchParams.set("id_token_hint", idToken);
|
|
354
|
+
url.searchParams.set("post_logout_redirect_uri", returnTo);
|
|
355
|
+
navigate(url.toString());
|
|
356
|
+
return "redirected";
|
|
357
|
+
}
|
|
358
|
+
async function query(document2, variables = {}) {
|
|
242
359
|
const token = await getToken();
|
|
243
360
|
if (!token) throw new TetherError("Not logged in", { code: "NOT_LOGGED_IN" });
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
361
|
+
const { graphql } = await discover();
|
|
362
|
+
const send = (accessToken2) => fetch(graphql, {
|
|
363
|
+
method: "POST",
|
|
364
|
+
headers: { "Content-Type": "application/json", Authorization: accessToken2 },
|
|
365
|
+
body: JSON.stringify({ query: document2, variables })
|
|
366
|
+
});
|
|
367
|
+
let response = await send(token);
|
|
368
|
+
if (response.status === 401 && session?.refresh) {
|
|
248
369
|
session.expiresAt = 0;
|
|
249
|
-
|
|
370
|
+
response = await send(await getToken() ?? "");
|
|
371
|
+
}
|
|
372
|
+
const json = await readJsonObjectOrEmpty(response);
|
|
373
|
+
const errors = Array.isArray(json["errors"]) ? json["errors"] : [];
|
|
374
|
+
if (!response.ok || errors.length) {
|
|
375
|
+
const messages = errors.map((error) => isRecord(error) ? textOrNull(error["message"]) : null).filter(Boolean);
|
|
376
|
+
throw new TetherError(messages.join(" | ") || `Customer accounts: ${response.status}`, {
|
|
377
|
+
status: response.status
|
|
378
|
+
});
|
|
250
379
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
380
|
+
return (
|
|
381
|
+
/** @type {T} */
|
|
382
|
+
json["data"]
|
|
383
|
+
);
|
|
254
384
|
}
|
|
255
|
-
async function
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
|
|
385
|
+
async function browserCustomer() {
|
|
386
|
+
if (customerProfile) return customerProfile;
|
|
387
|
+
const data = await query(CUSTOMER_QUERY);
|
|
388
|
+
customerProfile = data.customer;
|
|
389
|
+
return customerProfile;
|
|
390
|
+
}
|
|
391
|
+
function forgetBrowserCustomer() {
|
|
392
|
+
customerProfile = null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// src/account/server-client.js
|
|
396
|
+
var known = null;
|
|
397
|
+
function serverBase() {
|
|
398
|
+
const server = cfg.account?.server;
|
|
399
|
+
if (!server) return "";
|
|
400
|
+
return new URL(server, window.location.href).href.replace(/\/$/, "");
|
|
401
|
+
}
|
|
402
|
+
async function serverFetch(path, { method = "GET", body } = {}) {
|
|
403
|
+
const init = { method, credentials: "include" };
|
|
404
|
+
if (method === "POST") init.headers = { "Content-Type": "application/json", "X-Tether-Account": "1" };
|
|
405
|
+
if (body) init.body = JSON.stringify(body);
|
|
406
|
+
const response = await fetch(`${serverBase()}${path}`, init);
|
|
407
|
+
if (response.status === 401) return { kind: "logged-out" };
|
|
408
|
+
const data = await readJsonObjectOrEmpty(response);
|
|
409
|
+
if (!response.ok) {
|
|
410
|
+
const reason = textOrNull(data["error"]) ?? String(response.status);
|
|
411
|
+
const userErrors = Array.isArray(data["userErrors"]) ? (
|
|
412
|
+
// The Worker sends Shopify's own `userErrors` list unchanged.
|
|
413
|
+
/** @type {UserError[]} */
|
|
414
|
+
data["userErrors"]
|
|
415
|
+
) : void 0;
|
|
416
|
+
throw new TetherError(`Customer accounts: ${reason}`, {
|
|
417
|
+
status: response.status,
|
|
418
|
+
...userErrors && { userErrors }
|
|
419
|
+
});
|
|
259
420
|
}
|
|
260
|
-
|
|
261
|
-
const data = await query(`query TetherCustomer{customer{id firstName lastName displayName emailAddress{emailAddress}defaultAddress{formatted}}}`);
|
|
262
|
-
customerCache = data.customer;
|
|
263
|
-
return customerCache;
|
|
421
|
+
return { kind: "answer", data };
|
|
264
422
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
return
|
|
423
|
+
function toServerCustomer(value) {
|
|
424
|
+
const customer2 = isRecord(value) ? value : {};
|
|
425
|
+
const address = customer2["address"];
|
|
426
|
+
return {
|
|
427
|
+
firstName: textOrNull(customer2["firstName"]),
|
|
428
|
+
lastName: textOrNull(customer2["lastName"]),
|
|
429
|
+
displayName: textOrNull(customer2["displayName"]),
|
|
430
|
+
email: textOrNull(customer2["email"]),
|
|
431
|
+
address: Array.isArray(address) ? address.filter((line) => typeof line === "string") : null
|
|
432
|
+
};
|
|
269
433
|
}
|
|
270
|
-
async function
|
|
271
|
-
if (
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
try {
|
|
275
|
-
window.localStorage.removeItem(HINT());
|
|
276
|
-
} catch {
|
|
277
|
-
}
|
|
278
|
-
emit("account-logout", {});
|
|
279
|
-
if (r.logoutUrl) go(r.logoutUrl);
|
|
280
|
-
else render(document);
|
|
281
|
-
return;
|
|
434
|
+
async function serverLogin() {
|
|
435
|
+
if (!known) {
|
|
436
|
+
const answer = await serverFetch("/me");
|
|
437
|
+
known = answer.kind === "answer" && answer.data["loggedIn"] === true ? { loggedIn: true, customer: toServerCustomer(answer.data["customer"]) } : { loggedIn: false };
|
|
282
438
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
439
|
+
setLoginHint(known.loggedIn);
|
|
440
|
+
return known;
|
|
441
|
+
}
|
|
442
|
+
var isServerLoggedIn = () => known?.loggedIn === true;
|
|
443
|
+
function forgetServerLogin() {
|
|
444
|
+
known = null;
|
|
445
|
+
}
|
|
446
|
+
function serverLoginRedirect({ returnTo, locale }) {
|
|
447
|
+
const url = new URL(`${serverBase()}/login`);
|
|
448
|
+
url.searchParams.set("returnTo", returnTo);
|
|
449
|
+
const language = loginLanguage(locale);
|
|
450
|
+
if (language) url.searchParams.set("locale", language);
|
|
451
|
+
const country = loginCountry();
|
|
452
|
+
if (country) url.searchParams.set("country", country);
|
|
453
|
+
navigate(url.toString());
|
|
454
|
+
}
|
|
455
|
+
async function serverCheckoutUrl() {
|
|
456
|
+
const cartId = Cart.id();
|
|
457
|
+
if (!(await serverLogin()).loggedIn || !cartId) return null;
|
|
458
|
+
const answer = await serverFetch("/cart", { method: "POST", body: { cartId } });
|
|
459
|
+
if (answer.kind === "logged-out") return null;
|
|
460
|
+
const url = textOrNull(answer.data["checkoutUrl"]);
|
|
461
|
+
if (!url) return null;
|
|
462
|
+
return `${url}${url.includes("?") ? "&" : "?"}logged_in=true`;
|
|
463
|
+
}
|
|
464
|
+
async function serverLogout(returnTo) {
|
|
465
|
+
const answer = await serverFetch("/logout", { method: "POST", body: { returnTo, cartId: Cart.id() } });
|
|
466
|
+
known = null;
|
|
467
|
+
setLoginHint(false);
|
|
287
468
|
emit("account-logout", {});
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const d = await discover();
|
|
293
|
-
const u = new URL(d.end_session_endpoint);
|
|
294
|
-
u.searchParams.set("id_token_hint", idToken);
|
|
295
|
-
u.searchParams.set("post_logout_redirect_uri", returnTo);
|
|
296
|
-
go(u.toString());
|
|
469
|
+
const logoutUrl = answer.kind === "answer" ? textOrNull(answer.data["logoutUrl"]) : null;
|
|
470
|
+
if (!logoutUrl) return "stayed";
|
|
471
|
+
navigate(logoutUrl);
|
|
472
|
+
return "redirected";
|
|
297
473
|
}
|
|
298
|
-
|
|
474
|
+
|
|
475
|
+
// tether-core:shopify-values.js
|
|
476
|
+
var m6 = window.Tether.__core["shopify-values.js"];
|
|
477
|
+
var { STORE_ENV_NAMES, formatMoney: formatMoney2, isHttpsUrl, isSecretShopifyToken, productKey, productUrl: productUrl2 } = m6;
|
|
478
|
+
|
|
479
|
+
// src/account-links.js
|
|
480
|
+
function localDate(iso) {
|
|
299
481
|
try {
|
|
300
|
-
return new
|
|
301
|
-
} catch {
|
|
302
|
-
return
|
|
482
|
+
return new Date(iso).toLocaleDateString(cfg.locale);
|
|
483
|
+
} catch (error) {
|
|
484
|
+
if (error instanceof RangeError) return iso;
|
|
485
|
+
throw error;
|
|
303
486
|
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
return
|
|
487
|
+
}
|
|
488
|
+
function orderUrl(order2) {
|
|
489
|
+
const template = cfg.account?.orderUrl;
|
|
490
|
+
if (template) {
|
|
491
|
+
const numericId = order2.id.split("/").pop()?.split("?")[0] ?? "";
|
|
492
|
+
return template.replace("{id}", encodeURIComponent(numericId));
|
|
310
493
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
qsa(root, sel("account-link")).forEach((a) => {
|
|
320
|
-
const k = get(a, "account-link");
|
|
321
|
-
const u = k === "payment" && (cfg.account?.paymentUrl || cfg.account?.subscriptionsUrl) || k === "subscriptions" && cfg.account?.subscriptionsUrl;
|
|
322
|
-
a.setAttribute("href", u || `https://${cfg.domain}/account`);
|
|
494
|
+
return isHttpsUrl(order2.statusPageUrl) ? order2.statusPageUrl ?? null : null;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/account/markup.js
|
|
498
|
+
function showIn(root, name, conditions) {
|
|
499
|
+
qsa(root, sel(name)).forEach((el) => {
|
|
500
|
+
const key = get(el, name) ?? "";
|
|
501
|
+
if (Object.hasOwn(conditions, key)) show(el, conditions[key] === true);
|
|
323
502
|
});
|
|
324
|
-
if (!logged) return;
|
|
325
|
-
await bindCustomer(root);
|
|
326
|
-
await Promise.all([renderOrderList(root), PAGE_ATTRS.some((n) => qsa(root, sel(n)).length) ? pages().then((m6) => m6.renderPages(root)) : null]);
|
|
327
503
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
504
|
+
var bindsIn = (row, name) => qsa(row, sel(name)).concat(row.matches(sel(name)) ? [row] : []);
|
|
505
|
+
var ownValue = (values, key) => Object.hasOwn(values, key) ? values[key] : void 0;
|
|
506
|
+
|
|
507
|
+
// src/account/order-lines.js
|
|
508
|
+
var lineTexts = (line, returnable) => ({
|
|
509
|
+
title: line.title || line.name,
|
|
510
|
+
"variant-title": line.variantTitle || "",
|
|
511
|
+
quantity: String(line.quantity),
|
|
512
|
+
price: formatMoney(line.price),
|
|
513
|
+
total: formatMoney(line.totalPrice),
|
|
514
|
+
sku: line.sku || "",
|
|
515
|
+
returnable: returnable === null ? void 0 : String(returnable)
|
|
516
|
+
});
|
|
517
|
+
var isInput = (el) => el.tagName === "INPUT";
|
|
518
|
+
function fillReturnControls(row, line, returnable) {
|
|
519
|
+
showIn(row, "order-line-show", { returnable: returnable > 0 });
|
|
520
|
+
qsa(row, sel("return-quantity")).forEach((input) => {
|
|
521
|
+
if (!isInput(input)) return;
|
|
522
|
+
input.max = String(returnable);
|
|
523
|
+
input.min = "0";
|
|
524
|
+
if (!input.value) input.value = "0";
|
|
525
|
+
input.disabled = returnable < 1;
|
|
526
|
+
});
|
|
527
|
+
qsa(row, `select${sel("return-reason")}`).forEach((select) => {
|
|
528
|
+
if (select.dataset["tetherFilled"] === line.id) return;
|
|
529
|
+
select.dataset["tetherFilled"] = line.id;
|
|
530
|
+
(line.suggestedReturnReasonDefinitions?.nodes ?? []).forEach((reason) => {
|
|
531
|
+
select.appendChild(new Option(reason.name, reason.id));
|
|
342
532
|
});
|
|
343
|
-
|
|
344
|
-
lists.forEach((c) => repeat(c, list, (node, o) => {
|
|
345
|
-
const items = o.lineItems?.nodes || [];
|
|
346
|
-
qsa(node, sel("order-bind")).forEach((el) => {
|
|
347
|
-
const k = get(el, "order-bind");
|
|
348
|
-
if (k === "url") {
|
|
349
|
-
const u = orderUrl(o);
|
|
350
|
-
if (u) el.setAttribute("href", u);
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
if (k === "image") {
|
|
354
|
-
const im = items.find((l) => l.image)?.image;
|
|
355
|
-
if (im) setImage(el, im.url, im.altText || items[0]?.title || "");
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
const v = { name: o.name, date: date(o.processedAt), total: formatMoney(o.totalPrice), status: o.financialStatus?.toLowerCase(), fulfillment: o.fulfillmentStatus?.toLowerCase(), items: items.map((l) => `${l.quantity}\xD7 ${l.title}`).join(", ") }[k];
|
|
359
|
-
if (v != null) setText(el, v);
|
|
360
|
-
});
|
|
361
|
-
qsa(node, sel("order-lines")).forEach((box) => fillLines(box, items));
|
|
362
|
-
if (get(node, "order-bind") === "url" && orderUrl(o)) node.setAttribute("href", orderUrl(o));
|
|
363
|
-
}));
|
|
364
|
-
}
|
|
533
|
+
});
|
|
365
534
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (k === "image") {
|
|
377
|
-
if (l.image) setImage(el, l.image.url, l.image.altText || l.title);
|
|
535
|
+
function fillLines(box, lines, returns) {
|
|
536
|
+
repeat(box, lines, (row, line) => {
|
|
537
|
+
const id = line.id;
|
|
538
|
+
const returnable = returns && id ? returns.get(id) ?? 0 : null;
|
|
539
|
+
if (id) row.dataset["tetherLine"] = id;
|
|
540
|
+
const texts = lineTexts(line, returnable);
|
|
541
|
+
bindsIn(row, "order-line").forEach((el) => {
|
|
542
|
+
const key = get(el, "order-line") ?? "";
|
|
543
|
+
if (key === "image") {
|
|
544
|
+
if (line.image) setImage(el, line.image.url, line.image.altText || line.title);
|
|
378
545
|
return;
|
|
379
546
|
}
|
|
380
|
-
const
|
|
381
|
-
if (
|
|
547
|
+
const text = ownValue(texts, key);
|
|
548
|
+
if (text !== void 0) setText(el, text);
|
|
382
549
|
});
|
|
550
|
+
if (id && returnable !== null) fillReturnControls(row, { ...line, id }, returnable);
|
|
383
551
|
});
|
|
384
552
|
}
|
|
385
|
-
|
|
386
|
-
|
|
553
|
+
|
|
554
|
+
// src/account/order-list.js
|
|
555
|
+
var DEFAULT_ORDER_COUNT = 10;
|
|
556
|
+
var orderTexts = (order2) => ({
|
|
557
|
+
name: order2.name,
|
|
558
|
+
date: localDate(order2.processedAt),
|
|
559
|
+
total: formatMoney(order2.totalPrice),
|
|
560
|
+
status: order2.financialStatus?.toLowerCase(),
|
|
561
|
+
fulfillment: order2.fulfillmentStatus?.toLowerCase(),
|
|
562
|
+
items: order2.lineItems.nodes.map((line) => `${line.quantity}\xD7 ${line.title}`).join(", ")
|
|
563
|
+
});
|
|
564
|
+
function fillOrder(node, order2) {
|
|
565
|
+
const lines = order2.lineItems.nodes;
|
|
566
|
+
const url = orderUrl(order2);
|
|
567
|
+
const texts = orderTexts(order2);
|
|
568
|
+
qsa(node, sel("order-bind")).forEach((el) => {
|
|
569
|
+
const key = get(el, "order-bind") ?? "";
|
|
570
|
+
if (key === "url") {
|
|
571
|
+
if (url) el.setAttribute("href", url);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (key === "image") {
|
|
575
|
+
const image = lines.find((line) => line.image)?.image;
|
|
576
|
+
if (image) setImage(el, image.url, image.altText || lines[0]?.title || "");
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const text = ownValue(texts, key);
|
|
580
|
+
if (text !== void 0) setText(el, text);
|
|
581
|
+
});
|
|
582
|
+
qsa(node, sel("order-lines")).forEach((box) => {
|
|
583
|
+
fillLines(box, lines, null);
|
|
584
|
+
});
|
|
585
|
+
if (get(node, "order-bind") === "url" && url) node.setAttribute("href", url);
|
|
586
|
+
}
|
|
587
|
+
async function renderOrderList(root, loadOrders) {
|
|
588
|
+
const lists = qsa(root, sel("orders"));
|
|
589
|
+
const first = lists[0];
|
|
590
|
+
if (!first) return;
|
|
591
|
+
showIn(root, "show", { "orders-loading": true, "orders-empty": false });
|
|
592
|
+
const limit = Number.parseInt(get(first, "limit") || String(DEFAULT_ORDER_COUNT), 10) || DEFAULT_ORDER_COUNT;
|
|
593
|
+
const orders2 = await loadOrders(limit).catch((error) => {
|
|
594
|
+
reportError(error, "orders");
|
|
595
|
+
return null;
|
|
596
|
+
});
|
|
597
|
+
showIn(root, "show", { "orders-loading": false, "orders-empty": orders2?.length === 0 });
|
|
598
|
+
lists.forEach((list) => {
|
|
599
|
+
repeat(list, orders2 ?? [], fillOrder);
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// src/account.js
|
|
604
|
+
var PAGE_ATTRIBUTES = ["order", "addresses", "subscriptions", "profile-form", "address-form"];
|
|
605
|
+
function isLoggedIn() {
|
|
606
|
+
return serverBase() ? isServerLoggedIn() : hasBrowserLogin();
|
|
607
|
+
}
|
|
608
|
+
async function login({ returnTo = window.location.href, locale } = {}) {
|
|
609
|
+
if (serverBase()) serverLoginRedirect({ returnTo, locale });
|
|
610
|
+
else await browserLoginRedirect({ returnTo, locale });
|
|
611
|
+
}
|
|
612
|
+
async function query2(document2, variables = {}) {
|
|
613
|
+
if (serverBase()) {
|
|
614
|
+
throw new TetherError("Customer accounts in server mode only answer customer() and orders()", {
|
|
615
|
+
code: "NOT_ALLOWED"
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
return query(document2, variables);
|
|
619
|
+
}
|
|
620
|
+
async function customer() {
|
|
621
|
+
if (!serverBase()) return browserCustomer();
|
|
622
|
+
const me = await serverLogin();
|
|
623
|
+
if (!me.loggedIn) return null;
|
|
624
|
+
return {
|
|
625
|
+
...me.customer,
|
|
626
|
+
emailAddress: { emailAddress: me.customer.email },
|
|
627
|
+
defaultAddress: { formatted: me.customer.address }
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
async function orders({ first = 10 } = {}) {
|
|
631
|
+
if (serverBase()) {
|
|
632
|
+
const answer = await serverFetch(`/orders?first=${first}`);
|
|
633
|
+
if (answer.kind === "logged-out") return [];
|
|
634
|
+
const list = answer.data["orders"];
|
|
635
|
+
return Array.isArray(list) ? (
|
|
636
|
+
/** @type {AccountOrder[]} */
|
|
637
|
+
list
|
|
638
|
+
) : [];
|
|
639
|
+
}
|
|
640
|
+
const data = await query(ORDERS_QUERY, { first });
|
|
641
|
+
return data.customer?.orders.nodes ?? [];
|
|
642
|
+
}
|
|
643
|
+
async function logout({ returnTo = cfg.account?.logoutUrl || `${window.location.origin}/` } = {}) {
|
|
644
|
+
const outcome = serverBase() ? await serverLogout(returnTo) : await browserLogout(returnTo);
|
|
645
|
+
if (outcome === "stayed") await render(document);
|
|
646
|
+
}
|
|
647
|
+
async function loginState() {
|
|
648
|
+
try {
|
|
649
|
+
if (serverBase()) return (await serverLogin()).loggedIn ? { kind: "logged-in" } : { kind: "logged-out" };
|
|
650
|
+
if (hasBrowserLogin() || await getToken()) return { kind: "logged-in" };
|
|
651
|
+
return { kind: "logged-out" };
|
|
652
|
+
} catch (error) {
|
|
653
|
+
return { kind: "unknown", error };
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
var usesPages = (root) => PAGE_ATTRIBUTES.some((name) => qsa(root, sel(name)).length > 0);
|
|
657
|
+
function linkShopifyPages(root) {
|
|
658
|
+
const account = cfg.account;
|
|
659
|
+
qsa(root, sel("account-link")).forEach((link) => {
|
|
660
|
+
const page = get(link, "account-link");
|
|
661
|
+
const subscriptions2 = account?.subscriptionsUrl;
|
|
662
|
+
const url = page === "payment" && (account?.paymentUrl || subscriptions2) || page === "subscriptions" && subscriptions2;
|
|
663
|
+
link.setAttribute("href", url || `https://${cfg.domain}/account`);
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
async function render(root = document) {
|
|
667
|
+
const state = await loginState();
|
|
668
|
+
if (state.kind === "unknown") reportError(state.error, "account");
|
|
669
|
+
const loggedIn = state.kind === "logged-in";
|
|
670
|
+
qsa(root, sel("show")).forEach((el) => {
|
|
671
|
+
const condition = get(el, "show");
|
|
672
|
+
if (condition === "logged-in") show(el, loggedIn);
|
|
673
|
+
if (condition === "logged-out") show(el, !loggedIn);
|
|
674
|
+
});
|
|
675
|
+
linkShopifyPages(root);
|
|
676
|
+
if (state.kind === "unknown" && usesPages(root)) {
|
|
677
|
+
(await pages()).showUnavailable(root);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
if (!loggedIn) return;
|
|
681
|
+
await bindCustomer(root);
|
|
682
|
+
await Promise.all([
|
|
683
|
+
renderOrderList(root, (first) => orders({ first })),
|
|
684
|
+
usesPages(root) ? pages().then((part) => part.renderPages(root)) : null
|
|
685
|
+
]);
|
|
686
|
+
}
|
|
387
687
|
async function bindCustomer(root) {
|
|
388
688
|
const binds = qsa(root, sel("account-bind"));
|
|
389
689
|
if (!binds.length) return;
|
|
390
|
-
const
|
|
391
|
-
reportError(
|
|
690
|
+
const profile = await customer().catch((error) => {
|
|
691
|
+
reportError(error, "account");
|
|
392
692
|
return null;
|
|
393
693
|
});
|
|
694
|
+
const texts = {
|
|
695
|
+
"first-name": profile?.firstName,
|
|
696
|
+
"last-name": profile?.lastName,
|
|
697
|
+
name: profile?.displayName,
|
|
698
|
+
email: profile?.emailAddress?.emailAddress,
|
|
699
|
+
address: profile?.defaultAddress?.formatted?.join(", ")
|
|
700
|
+
};
|
|
394
701
|
binds.forEach((el) => {
|
|
395
|
-
const
|
|
396
|
-
if (
|
|
702
|
+
const text = ownValue(texts, get(el, "account-bind") ?? "");
|
|
703
|
+
if (text != null) setText(el, text);
|
|
397
704
|
});
|
|
398
705
|
}
|
|
706
|
+
var pagesPart = null;
|
|
399
707
|
function pages() {
|
|
400
|
-
pagesPart
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
708
|
+
pagesPart ?? (pagesPart = loadPart("account-pages").then((part) => {
|
|
709
|
+
part.setup({
|
|
710
|
+
serverBase,
|
|
711
|
+
serverFetch,
|
|
712
|
+
query: query2,
|
|
713
|
+
customer,
|
|
714
|
+
bindCustomer,
|
|
715
|
+
reset: () => {
|
|
716
|
+
forgetBrowserCustomer();
|
|
717
|
+
forgetServerLogin();
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
return part;
|
|
406
721
|
}));
|
|
407
722
|
return pagesPart;
|
|
408
723
|
}
|
|
409
|
-
var
|
|
410
|
-
var
|
|
411
|
-
var
|
|
412
|
-
var
|
|
413
|
-
var
|
|
414
|
-
var
|
|
415
|
-
var
|
|
416
|
-
var
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if (
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if (t.closest?.(sel("account-login"))) {
|
|
425
|
-
e.preventDefault();
|
|
426
|
-
login().catch((err) => reportError(err, "login"));
|
|
427
|
-
} else if (t.closest?.(sel("account-logout"))) {
|
|
428
|
-
e.preventDefault();
|
|
429
|
-
logout().catch((err) => reportError(err, "logout"));
|
|
430
|
-
}
|
|
724
|
+
var order = (id) => pages().then((part) => part.order(id));
|
|
725
|
+
var updateProfile = (fields) => pages().then((part) => part.updateProfile(fields));
|
|
726
|
+
var addresses = () => pages().then((part) => part.addresses());
|
|
727
|
+
var saveAddress = (address, options) => pages().then((part) => part.saveAddress(address, options));
|
|
728
|
+
var deleteAddress = (id) => pages().then((part) => part.deleteAddress(id));
|
|
729
|
+
var subscriptions = () => pages().then((part) => part.subscriptions());
|
|
730
|
+
var subscriptionAction = (id, action) => pages().then((part) => part.subscriptionAction(id, action));
|
|
731
|
+
var requestReturn = (orderId, lines) => pages().then((part) => part.requestReturn(orderId, lines));
|
|
732
|
+
function onClick(event) {
|
|
733
|
+
const target = event.target;
|
|
734
|
+
if (!(target instanceof window.Element)) return;
|
|
735
|
+
if (target.closest(sel("account-login"))) {
|
|
736
|
+
event.preventDefault();
|
|
737
|
+
login().catch((error) => {
|
|
738
|
+
reportError(error, "login");
|
|
431
739
|
});
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
740
|
+
} else if (target.closest(sel("account-logout"))) {
|
|
741
|
+
event.preventDefault();
|
|
742
|
+
logout().catch((error) => {
|
|
743
|
+
reportError(error, "logout");
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
function startServerMode() {
|
|
748
|
+
const url = new URL(window.location.href);
|
|
749
|
+
if (url.searchParams.get("account")) {
|
|
750
|
+
url.searchParams.delete("account");
|
|
751
|
+
window.history.replaceState(window.history.state, "", url);
|
|
752
|
+
emit("account-login", {});
|
|
753
|
+
}
|
|
754
|
+
Cart.setCustomer({ beforeCheckout: serverCheckoutUrl });
|
|
755
|
+
}
|
|
756
|
+
async function startBrowserMode() {
|
|
757
|
+
try {
|
|
758
|
+
await handleCallback();
|
|
759
|
+
} catch (error) {
|
|
760
|
+
reportError(error, "login");
|
|
761
|
+
}
|
|
762
|
+
if (Cart.customerAccessToken) return;
|
|
763
|
+
try {
|
|
764
|
+
const accessToken2 = await getToken();
|
|
765
|
+
if (accessToken2) Cart.setCustomer({ accessToken: accessToken2 });
|
|
766
|
+
} catch (error) {
|
|
767
|
+
reportError(error, "account");
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
var started = false;
|
|
771
|
+
async function mountAccount(root = document) {
|
|
772
|
+
if (!started) {
|
|
773
|
+
started = true;
|
|
774
|
+
document.addEventListener("click", onClick);
|
|
775
|
+
if (serverBase()) startServerMode();
|
|
776
|
+
else await startBrowserMode();
|
|
452
777
|
log("account", isLoggedIn() ? "logged in" : "logged out");
|
|
453
778
|
}
|
|
454
779
|
await render(root);
|
|
455
780
|
}
|
|
456
781
|
|
|
457
782
|
// src/parts/account.js
|
|
458
|
-
|
|
783
|
+
var { Tether } = (
|
|
784
|
+
/** @type {Window & { Tether?: { __parts: Record<string, unknown> } }} */
|
|
785
|
+
window
|
|
786
|
+
);
|
|
787
|
+
if (!Tether) throw new Error("tether.account.js runs after the Tether script, which loads it");
|
|
788
|
+
Tether.__parts["account"] = account_exports;
|
|
459
789
|
})();
|