@misiki/gocommerce-connector 0.3.0 → 0.4.0
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 +28 -0
- package/dist/{index-BfbB3jQb.d.cts → index-iPG0fxzc.d.cts} +41 -1
- package/dist/{index-BfbB3jQb.d.ts → index-iPG0fxzc.d.ts} +41 -1
- package/dist/index.cjs +372 -329
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +370 -330
- package/dist/index.js.map +1 -1
- package/dist/services/index.cjs +57 -14
- package/dist/services/index.cjs.map +1 -1
- package/dist/services/index.d.cts +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.js +55 -15
- package/dist/services/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -138,6 +138,337 @@ function forgetAll() {
|
|
|
138
138
|
entries.clear();
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
// src/browser.ts
|
|
142
|
+
var inBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
|
|
143
|
+
function readLocal(key) {
|
|
144
|
+
if (!inBrowser()) return null;
|
|
145
|
+
try {
|
|
146
|
+
return window.localStorage.getItem(key);
|
|
147
|
+
} catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function writeLocal(key, value) {
|
|
152
|
+
if (!inBrowser()) return;
|
|
153
|
+
try {
|
|
154
|
+
window.localStorage.setItem(key, value);
|
|
155
|
+
} catch {
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function removeLocal(key) {
|
|
159
|
+
if (!inBrowser()) return;
|
|
160
|
+
try {
|
|
161
|
+
window.localStorage.removeItem(key);
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function readJSON(key) {
|
|
166
|
+
const raw = readLocal(key);
|
|
167
|
+
if (!raw) return null;
|
|
168
|
+
try {
|
|
169
|
+
return JSON.parse(raw);
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
var YEAR = 60 * 60 * 24 * 365;
|
|
175
|
+
function setCookie(name, value, maxAge = YEAR) {
|
|
176
|
+
if (!inBrowser()) return;
|
|
177
|
+
document.cookie = `${name}=${value}; path=/; max-age=${maxAge}; samesite=lax`;
|
|
178
|
+
}
|
|
179
|
+
function clearCookie(name) {
|
|
180
|
+
if (!inBrowser()) return;
|
|
181
|
+
document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
|
|
182
|
+
}
|
|
183
|
+
function readCookie(name) {
|
|
184
|
+
if (!inBrowser()) return null;
|
|
185
|
+
for (const pair of document.cookie.split(";")) {
|
|
186
|
+
const at = pair.indexOf("=");
|
|
187
|
+
if (at < 0) continue;
|
|
188
|
+
if (pair.slice(0, at).trim() === name) return pair.slice(at + 1).trim();
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
var TOKEN = "gocommerce_token";
|
|
193
|
+
var sessionToken = () => readLocal(TOKEN);
|
|
194
|
+
function toMe(c) {
|
|
195
|
+
const [firstName, ...rest] = (c.name ?? "").trim().split(/\s+/);
|
|
196
|
+
return {
|
|
197
|
+
id: String(c.id),
|
|
198
|
+
userId: String(c.id),
|
|
199
|
+
email: c.email,
|
|
200
|
+
phone: c.phone || null,
|
|
201
|
+
firstName: firstName || "",
|
|
202
|
+
lastName: rest.join(" "),
|
|
203
|
+
name: c.name ?? "",
|
|
204
|
+
fullName: c.name ?? "",
|
|
205
|
+
avatar: null,
|
|
206
|
+
role: "USER",
|
|
207
|
+
emailVerified: c.email_verified,
|
|
208
|
+
storeId: null
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function startSession(auth) {
|
|
212
|
+
writeLocal(TOKEN, auth.token);
|
|
213
|
+
const me = toMe(auth.record);
|
|
214
|
+
const expires = Math.max(60, Math.floor((Date.parse(auth.expires_at) - Date.now()) / 1e3)) || YEAR;
|
|
215
|
+
setCookie("me", encodeURIComponent(JSON.stringify(me)), expires);
|
|
216
|
+
setCookie("connect.sid", "gocommerce", expires);
|
|
217
|
+
return me;
|
|
218
|
+
}
|
|
219
|
+
function updateMe(c) {
|
|
220
|
+
const me = toMe(c);
|
|
221
|
+
setCookie("me", encodeURIComponent(JSON.stringify(me)));
|
|
222
|
+
return me;
|
|
223
|
+
}
|
|
224
|
+
function endSession() {
|
|
225
|
+
removeLocal(TOKEN);
|
|
226
|
+
clearCookie("me");
|
|
227
|
+
clearCookie("connect.sid");
|
|
228
|
+
}
|
|
229
|
+
function currentMe() {
|
|
230
|
+
const raw = readCookie("me");
|
|
231
|
+
if (!raw) return null;
|
|
232
|
+
try {
|
|
233
|
+
return JSON.parse(decodeURIComponent(raw));
|
|
234
|
+
} catch {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function storedCartId() {
|
|
239
|
+
const v = readLocal("cart_id");
|
|
240
|
+
if (!v || v === "undefined" || v === "null") return null;
|
|
241
|
+
return v;
|
|
242
|
+
}
|
|
243
|
+
var rememberCartId = (id2) => writeLocal("cart_id", id2);
|
|
244
|
+
var extrasKey = (cartId) => `gocommerce_cart_${cartId}`;
|
|
245
|
+
var cartExtras = (cartId) => readJSON(extrasKey(cartId)) ?? {};
|
|
246
|
+
function saveCartExtras(cartId, patch) {
|
|
247
|
+
const next = { ...cartExtras(cartId), ...patch };
|
|
248
|
+
writeLocal(extrasKey(cartId), JSON.stringify(next));
|
|
249
|
+
return next;
|
|
250
|
+
}
|
|
251
|
+
var forgetCartExtras = (cartId) => removeLocal(extrasKey(cartId));
|
|
252
|
+
var ORDERS = "gocommerce_orders";
|
|
253
|
+
var placedOrders = () => readJSON(ORDERS) ?? [];
|
|
254
|
+
function rememberOrder(o) {
|
|
255
|
+
const kept = placedOrders().filter((p) => p.number !== o.number);
|
|
256
|
+
writeLocal(ORDERS, JSON.stringify([o, ...kept].slice(0, 50)));
|
|
257
|
+
}
|
|
258
|
+
var placedOrder = (number) => placedOrders().find((p) => p.number === number);
|
|
259
|
+
var placedOrderForCart = (cartId) => placedOrders().find((p) => p.cartId === cartId);
|
|
260
|
+
var placedOrderByReference = (reference) => placedOrders().find((p) => p.reference === reference);
|
|
261
|
+
var WISHLIST = "gocommerce_wishlist";
|
|
262
|
+
var wishlistToken = () => readLocal(WISHLIST);
|
|
263
|
+
var rememberWishlistToken = (token) => writeLocal(WISHLIST, token);
|
|
264
|
+
var forgetWishlistToken = () => removeLocal(WISHLIST);
|
|
265
|
+
|
|
266
|
+
// src/services/base.service.ts
|
|
267
|
+
function mediaUrl(url) {
|
|
268
|
+
if (!url) return null;
|
|
269
|
+
if (!url.startsWith("/") || url.startsWith("//")) return url;
|
|
270
|
+
const { apiUrl, proxyPath } = BaseService.getCredentials();
|
|
271
|
+
return (proxyPath || apiUrl || "").trim().replace(/\/+$/, "") + url;
|
|
272
|
+
}
|
|
273
|
+
var BaseService = class _BaseService {
|
|
274
|
+
static _credentials = { apiUrl: "" };
|
|
275
|
+
_fetch;
|
|
276
|
+
constructor(fetchFn) {
|
|
277
|
+
this._fetch = fetchFn ?? ((input, init) => globalThis.fetch(input, init));
|
|
278
|
+
}
|
|
279
|
+
static setCredentials(creds) {
|
|
280
|
+
_BaseService._credentials = { ..._BaseService._credentials, ...creds };
|
|
281
|
+
}
|
|
282
|
+
static getCredentials() {
|
|
283
|
+
return _BaseService._credentials;
|
|
284
|
+
}
|
|
285
|
+
get creds() {
|
|
286
|
+
return _BaseService._credentials;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* The store this call is for: the request host's subdomain under
|
|
290
|
+
* `baseDomain`, else the static `store`. Read per call, never stored on the
|
|
291
|
+
* instance, because the server shares one process (and one credentials
|
|
292
|
+
* object) across every concurrent request and each may be for a different host.
|
|
293
|
+
*/
|
|
294
|
+
get store() {
|
|
295
|
+
const { baseDomain, store } = this.creds;
|
|
296
|
+
const host = typeof window !== "undefined" ? window.location?.hostname : requestHost?.();
|
|
297
|
+
return storeForHost(host, baseDomain) ?? store;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* The engine's base URL from wherever this code runs. In the browser a
|
|
301
|
+
* configured proxy path wins, because the engine answers no CORS preflight.
|
|
302
|
+
*/
|
|
303
|
+
engineBase() {
|
|
304
|
+
const { apiUrl, proxyPath } = this.creds;
|
|
305
|
+
if (proxyPath && typeof window !== "undefined" && window.location) {
|
|
306
|
+
return new URL(proxyPath, window.location.origin).href.replace(/\/+$/, "");
|
|
307
|
+
}
|
|
308
|
+
const url = (apiUrl ?? "").trim();
|
|
309
|
+
if (!url) {
|
|
310
|
+
throw new GoCommerceError(
|
|
311
|
+
"PUBLIC_GOCOMMERCE_API_URL is not set, so this storefront does not know where its commerce engine is. Point it at a running GoCommerce, e.g. http://127.0.0.1:8080.",
|
|
312
|
+
0
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
return url.replace(/\/+$/, "");
|
|
316
|
+
}
|
|
317
|
+
/** An absolute engine URL for a path and query, without sending anything. */
|
|
318
|
+
engineURL(path, query) {
|
|
319
|
+
return this.engineBase() + path + qs(query);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* One request to the engine, envelope and all.
|
|
323
|
+
*
|
|
324
|
+
* The body is read as text first, so a proxy's HTML error page is reported
|
|
325
|
+
* as what it is rather than as a JSON parse error three frames away.
|
|
326
|
+
*/
|
|
327
|
+
async engine(path, req = {}) {
|
|
328
|
+
const lang = (req.method ?? "GET") === "GET" ? this.creds.language : void 0;
|
|
329
|
+
const url = this.engineURL(path, lang ? { lang, ...req.query } : req.query);
|
|
330
|
+
const headers = { Accept: "application/json", ...req.headers };
|
|
331
|
+
if (req.body !== void 0) headers["Content-Type"] = "application/json";
|
|
332
|
+
const store = this.store;
|
|
333
|
+
if (store) headers["X-Store"] = store;
|
|
334
|
+
if (req.auth) {
|
|
335
|
+
const token = sessionToken();
|
|
336
|
+
if (!token) throw new GoCommerceError("Please sign in to continue.", 401, "unauthorized");
|
|
337
|
+
headers.Authorization = `Bearer ${token}`;
|
|
338
|
+
}
|
|
339
|
+
const init = {
|
|
340
|
+
method: req.method ?? "GET",
|
|
341
|
+
headers,
|
|
342
|
+
body: req.body === void 0 ? void 0 : JSON.stringify(req.body)
|
|
343
|
+
};
|
|
344
|
+
let res;
|
|
345
|
+
try {
|
|
346
|
+
res = await this._fetch.call(globalThis, url, init);
|
|
347
|
+
} catch (cause) {
|
|
348
|
+
if (typeof window === "undefined" && /CORS/i.test(String(cause?.message))) {
|
|
349
|
+
try {
|
|
350
|
+
res = await globalThis.fetch(url, init);
|
|
351
|
+
} catch (again) {
|
|
352
|
+
throw unreachable(this.engineBase(), again);
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
throw unreachable(this.engineBase(), cause);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return this.unwrap(res);
|
|
359
|
+
}
|
|
360
|
+
/** The envelope opened: the payload, or the engine's error as a GoCommerceError. */
|
|
361
|
+
async unwrap(res) {
|
|
362
|
+
const text = await res.text();
|
|
363
|
+
let parsed = null;
|
|
364
|
+
if (text) {
|
|
365
|
+
try {
|
|
366
|
+
parsed = JSON.parse(text);
|
|
367
|
+
} catch {
|
|
368
|
+
throw new GoCommerceError(
|
|
369
|
+
`The commerce engine answered ${res.status} with something that is not JSON: ${text.slice(0, 120)}`,
|
|
370
|
+
res.status
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (!res.ok) {
|
|
375
|
+
const err = parsed?.error ?? {};
|
|
376
|
+
throw new GoCommerceError(
|
|
377
|
+
err.message ?? `The commerce engine answered ${res.status}.`,
|
|
378
|
+
res.status,
|
|
379
|
+
err.code,
|
|
380
|
+
err.details
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
return parsed ?? { data: null };
|
|
384
|
+
}
|
|
385
|
+
/** The bare payload, for the many callers that never look at `meta`. */
|
|
386
|
+
async data(path, req = {}) {
|
|
387
|
+
return (await this.engine(path, req)).data;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Prices a cart at the signed-in shopper's customer-group prices. The
|
|
391
|
+
* engine does this only for an account whose email is confirmed (403
|
|
392
|
+
* otherwise), and the cart stays the shopper's either way — so a refusal
|
|
393
|
+
* is not an error, and neither is a build without ext/identity.
|
|
394
|
+
*/
|
|
395
|
+
async claimCartForAccount(cartId) {
|
|
396
|
+
try {
|
|
397
|
+
await this.engine("/x/identity/me/carts", { method: "POST", body: { cart_id: cartId }, auth: true });
|
|
398
|
+
} catch {
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// ------------------------------------------------------------ Litekart verbs
|
|
402
|
+
/**
|
|
403
|
+
* The family's path-addressed verbs. An absolute URL is fetched as-is; a
|
|
404
|
+
* relative one is a Litekart REST path, answered from the storefront's own
|
|
405
|
+
* data where it has some and refused otherwise (rest-guard.ts).
|
|
406
|
+
*/
|
|
407
|
+
get(path) {
|
|
408
|
+
return this.raw("get", path);
|
|
409
|
+
}
|
|
410
|
+
post(path, data) {
|
|
411
|
+
return this.raw("post", path, data);
|
|
412
|
+
}
|
|
413
|
+
put(path, data) {
|
|
414
|
+
return this.raw("put", path, data);
|
|
415
|
+
}
|
|
416
|
+
patch(path, data) {
|
|
417
|
+
return this.raw("patch", path, data);
|
|
418
|
+
}
|
|
419
|
+
delete(path) {
|
|
420
|
+
return this.raw("delete", path);
|
|
421
|
+
}
|
|
422
|
+
async raw(method, path, data) {
|
|
423
|
+
if (!/^https?:\/\//i.test(path)) return await resolveRestLocally(method, path);
|
|
424
|
+
const res = await this._fetch.call(globalThis, path, {
|
|
425
|
+
method: method.toUpperCase(),
|
|
426
|
+
headers: { Accept: "application/json", ...data === void 0 ? {} : { "Content-Type": "application/json" } },
|
|
427
|
+
body: data === void 0 ? void 0 : JSON.stringify(data)
|
|
428
|
+
});
|
|
429
|
+
const body = await res.json().catch(() => ({}));
|
|
430
|
+
if (!res.ok)
|
|
431
|
+
throw new GoCommerceError(body?.error?.message ?? `Request failed with status ${res.status}`, res.status);
|
|
432
|
+
return body;
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
var requestHost;
|
|
436
|
+
function setRequestHost(fn) {
|
|
437
|
+
requestHost = fn;
|
|
438
|
+
}
|
|
439
|
+
function storeForHost(host, baseDomain) {
|
|
440
|
+
if (!host || !baseDomain) return void 0;
|
|
441
|
+
const h = host.toLowerCase().replace(/\.$/, "").replace(/:\d+$/, "");
|
|
442
|
+
const suffix = "." + baseDomain.toLowerCase().replace(/^\.+/, "").replace(/\.$/, "");
|
|
443
|
+
if (!h.endsWith(suffix)) return void 0;
|
|
444
|
+
const slug = h.slice(0, -suffix.length);
|
|
445
|
+
return slug && !slug.includes(".") ? slug : void 0;
|
|
446
|
+
}
|
|
447
|
+
var unreachable = (base, cause) => new GoCommerceError(`Could not reach the commerce engine at ${base}: ${cause?.message ?? cause}`, 0);
|
|
448
|
+
function qs(query) {
|
|
449
|
+
if (!query) return "";
|
|
450
|
+
const sp = new URLSearchParams();
|
|
451
|
+
for (const [key, value] of Object.entries(query)) {
|
|
452
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
453
|
+
if (Array.isArray(value)) for (const v of value) sp.append(key, String(v));
|
|
454
|
+
else sp.append(key, String(value));
|
|
455
|
+
}
|
|
456
|
+
const s = sp.toString();
|
|
457
|
+
return s ? `?${s}` : "";
|
|
458
|
+
}
|
|
459
|
+
function toPage(env, map) {
|
|
460
|
+
const rows = env.data ?? [];
|
|
461
|
+
const meta = env.meta;
|
|
462
|
+
return {
|
|
463
|
+
data: rows.map(map),
|
|
464
|
+
count: meta?.total ?? rows.length,
|
|
465
|
+
pageSize: meta?.limit ?? rows.length,
|
|
466
|
+
noOfPage: meta?.total_pages ?? 1,
|
|
467
|
+
page: meta?.page ?? 1
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
var emptyPage = () => ({ data: [], count: 0, pageSize: 0, noOfPage: 0, page: 1 });
|
|
471
|
+
|
|
141
472
|
// src/map.ts
|
|
142
473
|
var str = (v) => v === null || v === void 0 ? "" : String(v);
|
|
143
474
|
var arr = (v) => v ?? [];
|
|
@@ -145,7 +476,8 @@ var liveVariants = (p) => arr(p.variants).filter((v) => v.active);
|
|
|
145
476
|
function variantImages(v) {
|
|
146
477
|
const out = [];
|
|
147
478
|
for (const img of [v.image, ...arr(v.images)]) {
|
|
148
|
-
|
|
479
|
+
const url = mediaUrl(img?.url);
|
|
480
|
+
if (url && (img?.kind ?? "image") === "image" && !out.includes(url)) out.push(url);
|
|
149
481
|
}
|
|
150
482
|
return out;
|
|
151
483
|
}
|
|
@@ -154,7 +486,7 @@ function productImages(p) {
|
|
|
154
486
|
const push = (url) => {
|
|
155
487
|
if (url && !out.includes(url)) out.push(url);
|
|
156
488
|
};
|
|
157
|
-
push(p.image_url);
|
|
489
|
+
push(mediaUrl(p.image_url));
|
|
158
490
|
for (const v of arr(p.variants)) for (const url of variantImages(v)) push(url);
|
|
159
491
|
return out;
|
|
160
492
|
}
|
|
@@ -284,9 +616,9 @@ function fromSearchDoc(d) {
|
|
|
284
616
|
mrp: price,
|
|
285
617
|
minPrice: price,
|
|
286
618
|
maxPrice: minorToMajor(d.price_max_minor, d.currency),
|
|
287
|
-
thumbnail: d.image
|
|
288
|
-
image_url: d.image
|
|
289
|
-
images: d.image ?? "",
|
|
619
|
+
thumbnail: mediaUrl(d.image),
|
|
620
|
+
image_url: mediaUrl(d.image),
|
|
621
|
+
images: mediaUrl(d.image) ?? "",
|
|
290
622
|
stock: d.in_stock ? 1 : 0,
|
|
291
623
|
hasStock: d.in_stock,
|
|
292
624
|
// The index cannot say how much is left, only whether any is; a card
|
|
@@ -315,7 +647,7 @@ function toCategory(c) {
|
|
|
315
647
|
level: c.depth,
|
|
316
648
|
rank: c.position,
|
|
317
649
|
childCount: c.child_count,
|
|
318
|
-
thumbnail: c.metadata?.image
|
|
650
|
+
thumbnail: mediaUrl(c.metadata?.image),
|
|
319
651
|
description: c.metadata?.description ?? null,
|
|
320
652
|
isActive: true,
|
|
321
653
|
isFeatured: false,
|
|
@@ -335,7 +667,7 @@ function toCollection(c) {
|
|
|
335
667
|
description: c.description || null,
|
|
336
668
|
subTitle: null,
|
|
337
669
|
rank: str(c.position),
|
|
338
|
-
thumbnail: c.metadata?.image
|
|
670
|
+
thumbnail: mediaUrl(c.metadata?.image),
|
|
339
671
|
isActive: true,
|
|
340
672
|
metaTitle: c.title,
|
|
341
673
|
metaDescription: c.description || null,
|
|
@@ -418,7 +750,7 @@ function toCartLine(l, extra = {}) {
|
|
|
418
750
|
title: l.title,
|
|
419
751
|
name: l.title,
|
|
420
752
|
slug: extra.slug ?? "",
|
|
421
|
-
thumbnail: extra.thumbnail
|
|
753
|
+
thumbnail: mediaUrl(extra.thumbnail),
|
|
422
754
|
variantTitle: l.variant_label ?? "",
|
|
423
755
|
qty: l.quantity,
|
|
424
756
|
price: toMajor(l.unit_price),
|
|
@@ -526,8 +858,8 @@ function toOrderLine(l, currency) {
|
|
|
526
858
|
title: l.title,
|
|
527
859
|
name: l.title,
|
|
528
860
|
slug: "",
|
|
529
|
-
thumbnail: l.image_url
|
|
530
|
-
img: l.image_url
|
|
861
|
+
thumbnail: mediaUrl(l.image_url),
|
|
862
|
+
img: mediaUrl(l.image_url),
|
|
531
863
|
variantTitle: l.variant_label ?? "",
|
|
532
864
|
qty: l.quantity,
|
|
533
865
|
shippedQty: l.shipped_quantity,
|
|
@@ -629,338 +961,37 @@ function toVendor(v) {
|
|
|
629
961
|
name: v.name,
|
|
630
962
|
businessName: v.name,
|
|
631
963
|
about: v.about,
|
|
632
|
-
description: v.about,
|
|
633
|
-
website: v.website,
|
|
634
|
-
address: v.address,
|
|
635
|
-
banners: [],
|
|
636
|
-
rating: null
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
function toUser(c) {
|
|
640
|
-
const [firstName, ...rest] = (c.name ?? "").trim().split(/\s+/);
|
|
641
|
-
return {
|
|
642
|
-
id: str(c.id),
|
|
643
|
-
userId: str(c.id),
|
|
644
|
-
email: c.email,
|
|
645
|
-
emailVerified: c.email_verified,
|
|
646
|
-
phone: c.phone || null,
|
|
647
|
-
firstName: firstName || "",
|
|
648
|
-
lastName: rest.join(" "),
|
|
649
|
-
name: c.name,
|
|
650
|
-
fullName: c.name,
|
|
651
|
-
avatar: null,
|
|
652
|
-
role: "USER",
|
|
653
|
-
createdAt: c.created_at,
|
|
654
|
-
updatedAt: c.updated_at
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
var priceOf = (m) => m ? toMajor(m) : 0;
|
|
658
|
-
|
|
659
|
-
// src/browser.ts
|
|
660
|
-
var inBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
|
|
661
|
-
function readLocal(key) {
|
|
662
|
-
if (!inBrowser()) return null;
|
|
663
|
-
try {
|
|
664
|
-
return window.localStorage.getItem(key);
|
|
665
|
-
} catch {
|
|
666
|
-
return null;
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
function writeLocal(key, value) {
|
|
670
|
-
if (!inBrowser()) return;
|
|
671
|
-
try {
|
|
672
|
-
window.localStorage.setItem(key, value);
|
|
673
|
-
} catch {
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
function removeLocal(key) {
|
|
677
|
-
if (!inBrowser()) return;
|
|
678
|
-
try {
|
|
679
|
-
window.localStorage.removeItem(key);
|
|
680
|
-
} catch {
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
function readJSON(key) {
|
|
684
|
-
const raw = readLocal(key);
|
|
685
|
-
if (!raw) return null;
|
|
686
|
-
try {
|
|
687
|
-
return JSON.parse(raw);
|
|
688
|
-
} catch {
|
|
689
|
-
return null;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
var YEAR = 60 * 60 * 24 * 365;
|
|
693
|
-
function setCookie(name, value, maxAge = YEAR) {
|
|
694
|
-
if (!inBrowser()) return;
|
|
695
|
-
document.cookie = `${name}=${value}; path=/; max-age=${maxAge}; samesite=lax`;
|
|
696
|
-
}
|
|
697
|
-
function clearCookie(name) {
|
|
698
|
-
if (!inBrowser()) return;
|
|
699
|
-
document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
|
|
700
|
-
}
|
|
701
|
-
function readCookie(name) {
|
|
702
|
-
if (!inBrowser()) return null;
|
|
703
|
-
for (const pair of document.cookie.split(";")) {
|
|
704
|
-
const at = pair.indexOf("=");
|
|
705
|
-
if (at < 0) continue;
|
|
706
|
-
if (pair.slice(0, at).trim() === name) return pair.slice(at + 1).trim();
|
|
707
|
-
}
|
|
708
|
-
return null;
|
|
964
|
+
description: v.about,
|
|
965
|
+
website: v.website,
|
|
966
|
+
address: v.address,
|
|
967
|
+
banners: [],
|
|
968
|
+
rating: null
|
|
969
|
+
};
|
|
709
970
|
}
|
|
710
|
-
|
|
711
|
-
var sessionToken = () => readLocal(TOKEN);
|
|
712
|
-
function toMe(c) {
|
|
971
|
+
function toUser(c) {
|
|
713
972
|
const [firstName, ...rest] = (c.name ?? "").trim().split(/\s+/);
|
|
714
973
|
return {
|
|
715
|
-
id:
|
|
716
|
-
userId:
|
|
974
|
+
id: str(c.id),
|
|
975
|
+
userId: str(c.id),
|
|
717
976
|
email: c.email,
|
|
977
|
+
emailVerified: c.email_verified,
|
|
718
978
|
phone: c.phone || null,
|
|
719
979
|
firstName: firstName || "",
|
|
720
980
|
lastName: rest.join(" "),
|
|
721
|
-
name: c.name
|
|
722
|
-
fullName: c.name
|
|
981
|
+
name: c.name,
|
|
982
|
+
fullName: c.name,
|
|
723
983
|
avatar: null,
|
|
724
984
|
role: "USER",
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
function startSession(auth) {
|
|
730
|
-
writeLocal(TOKEN, auth.token);
|
|
731
|
-
const me = toMe(auth.record);
|
|
732
|
-
const expires = Math.max(60, Math.floor((Date.parse(auth.expires_at) - Date.now()) / 1e3)) || YEAR;
|
|
733
|
-
setCookie("me", encodeURIComponent(JSON.stringify(me)), expires);
|
|
734
|
-
setCookie("connect.sid", "gocommerce", expires);
|
|
735
|
-
return me;
|
|
736
|
-
}
|
|
737
|
-
function updateMe(c) {
|
|
738
|
-
const me = toMe(c);
|
|
739
|
-
setCookie("me", encodeURIComponent(JSON.stringify(me)));
|
|
740
|
-
return me;
|
|
741
|
-
}
|
|
742
|
-
function endSession() {
|
|
743
|
-
removeLocal(TOKEN);
|
|
744
|
-
clearCookie("me");
|
|
745
|
-
clearCookie("connect.sid");
|
|
746
|
-
}
|
|
747
|
-
function currentMe() {
|
|
748
|
-
const raw = readCookie("me");
|
|
749
|
-
if (!raw) return null;
|
|
750
|
-
try {
|
|
751
|
-
return JSON.parse(decodeURIComponent(raw));
|
|
752
|
-
} catch {
|
|
753
|
-
return null;
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
function storedCartId() {
|
|
757
|
-
const v = readLocal("cart_id");
|
|
758
|
-
if (!v || v === "undefined" || v === "null") return null;
|
|
759
|
-
return v;
|
|
760
|
-
}
|
|
761
|
-
var rememberCartId = (id2) => writeLocal("cart_id", id2);
|
|
762
|
-
var extrasKey = (cartId) => `gocommerce_cart_${cartId}`;
|
|
763
|
-
var cartExtras = (cartId) => readJSON(extrasKey(cartId)) ?? {};
|
|
764
|
-
function saveCartExtras(cartId, patch) {
|
|
765
|
-
const next = { ...cartExtras(cartId), ...patch };
|
|
766
|
-
writeLocal(extrasKey(cartId), JSON.stringify(next));
|
|
767
|
-
return next;
|
|
768
|
-
}
|
|
769
|
-
var forgetCartExtras = (cartId) => removeLocal(extrasKey(cartId));
|
|
770
|
-
var ORDERS = "gocommerce_orders";
|
|
771
|
-
var placedOrders = () => readJSON(ORDERS) ?? [];
|
|
772
|
-
function rememberOrder(o) {
|
|
773
|
-
const kept = placedOrders().filter((p) => p.number !== o.number);
|
|
774
|
-
writeLocal(ORDERS, JSON.stringify([o, ...kept].slice(0, 50)));
|
|
775
|
-
}
|
|
776
|
-
var placedOrder = (number) => placedOrders().find((p) => p.number === number);
|
|
777
|
-
var placedOrderForCart = (cartId) => placedOrders().find((p) => p.cartId === cartId);
|
|
778
|
-
var placedOrderByReference = (reference) => placedOrders().find((p) => p.reference === reference);
|
|
779
|
-
var WISHLIST = "gocommerce_wishlist";
|
|
780
|
-
var wishlistToken = () => readLocal(WISHLIST);
|
|
781
|
-
var rememberWishlistToken = (token) => writeLocal(WISHLIST, token);
|
|
782
|
-
var forgetWishlistToken = () => removeLocal(WISHLIST);
|
|
783
|
-
|
|
784
|
-
// src/services/base.service.ts
|
|
785
|
-
var BaseService = class _BaseService {
|
|
786
|
-
static _credentials = { apiUrl: "" };
|
|
787
|
-
_fetch;
|
|
788
|
-
constructor(fetchFn) {
|
|
789
|
-
this._fetch = fetchFn ?? ((input, init) => globalThis.fetch(input, init));
|
|
790
|
-
}
|
|
791
|
-
static setCredentials(creds) {
|
|
792
|
-
_BaseService._credentials = { ..._BaseService._credentials, ...creds };
|
|
793
|
-
}
|
|
794
|
-
static getCredentials() {
|
|
795
|
-
return _BaseService._credentials;
|
|
796
|
-
}
|
|
797
|
-
get creds() {
|
|
798
|
-
return _BaseService._credentials;
|
|
799
|
-
}
|
|
800
|
-
/**
|
|
801
|
-
* The engine's base URL from wherever this code runs. In the browser a
|
|
802
|
-
* configured proxy path wins, because the engine answers no CORS preflight.
|
|
803
|
-
*/
|
|
804
|
-
engineBase() {
|
|
805
|
-
const { apiUrl, proxyPath } = this.creds;
|
|
806
|
-
if (proxyPath && typeof window !== "undefined" && window.location) {
|
|
807
|
-
return new URL(proxyPath, window.location.origin).href.replace(/\/+$/, "");
|
|
808
|
-
}
|
|
809
|
-
const url = (apiUrl ?? "").trim();
|
|
810
|
-
if (!url) {
|
|
811
|
-
throw new GoCommerceError(
|
|
812
|
-
"PUBLIC_GOCOMMERCE_API_URL is not set, so this storefront does not know where its commerce engine is. Point it at a running GoCommerce, e.g. http://127.0.0.1:8080.",
|
|
813
|
-
0
|
|
814
|
-
);
|
|
815
|
-
}
|
|
816
|
-
return url.replace(/\/+$/, "");
|
|
817
|
-
}
|
|
818
|
-
/** An absolute engine URL for a path and query, without sending anything. */
|
|
819
|
-
engineURL(path, query) {
|
|
820
|
-
return this.engineBase() + path + qs(query);
|
|
821
|
-
}
|
|
822
|
-
/**
|
|
823
|
-
* One request to the engine, envelope and all.
|
|
824
|
-
*
|
|
825
|
-
* The body is read as text first, so a proxy's HTML error page is reported
|
|
826
|
-
* as what it is rather than as a JSON parse error three frames away.
|
|
827
|
-
*/
|
|
828
|
-
async engine(path, req = {}) {
|
|
829
|
-
const lang = (req.method ?? "GET") === "GET" ? this.creds.language : void 0;
|
|
830
|
-
const url = this.engineURL(path, lang ? { lang, ...req.query } : req.query);
|
|
831
|
-
const headers = { Accept: "application/json", ...req.headers };
|
|
832
|
-
if (req.body !== void 0) headers["Content-Type"] = "application/json";
|
|
833
|
-
if (this.creds.store) headers["X-Store"] = this.creds.store;
|
|
834
|
-
if (req.auth) {
|
|
835
|
-
const token = sessionToken();
|
|
836
|
-
if (!token) throw new GoCommerceError("Please sign in to continue.", 401, "unauthorized");
|
|
837
|
-
headers.Authorization = `Bearer ${token}`;
|
|
838
|
-
}
|
|
839
|
-
const init = {
|
|
840
|
-
method: req.method ?? "GET",
|
|
841
|
-
headers,
|
|
842
|
-
body: req.body === void 0 ? void 0 : JSON.stringify(req.body)
|
|
843
|
-
};
|
|
844
|
-
let res;
|
|
845
|
-
try {
|
|
846
|
-
res = await this._fetch.call(globalThis, url, init);
|
|
847
|
-
} catch (cause) {
|
|
848
|
-
if (typeof window === "undefined" && /CORS/i.test(String(cause?.message))) {
|
|
849
|
-
try {
|
|
850
|
-
res = await globalThis.fetch(url, init);
|
|
851
|
-
} catch (again) {
|
|
852
|
-
throw unreachable(this.engineBase(), again);
|
|
853
|
-
}
|
|
854
|
-
} else {
|
|
855
|
-
throw unreachable(this.engineBase(), cause);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
return this.unwrap(res);
|
|
859
|
-
}
|
|
860
|
-
/** The envelope opened: the payload, or the engine's error as a GoCommerceError. */
|
|
861
|
-
async unwrap(res) {
|
|
862
|
-
const text = await res.text();
|
|
863
|
-
let parsed = null;
|
|
864
|
-
if (text) {
|
|
865
|
-
try {
|
|
866
|
-
parsed = JSON.parse(text);
|
|
867
|
-
} catch {
|
|
868
|
-
throw new GoCommerceError(
|
|
869
|
-
`The commerce engine answered ${res.status} with something that is not JSON: ${text.slice(0, 120)}`,
|
|
870
|
-
res.status
|
|
871
|
-
);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
if (!res.ok) {
|
|
875
|
-
const err = parsed?.error ?? {};
|
|
876
|
-
throw new GoCommerceError(
|
|
877
|
-
err.message ?? `The commerce engine answered ${res.status}.`,
|
|
878
|
-
res.status,
|
|
879
|
-
err.code,
|
|
880
|
-
err.details
|
|
881
|
-
);
|
|
882
|
-
}
|
|
883
|
-
return parsed ?? { data: null };
|
|
884
|
-
}
|
|
885
|
-
/** The bare payload, for the many callers that never look at `meta`. */
|
|
886
|
-
async data(path, req = {}) {
|
|
887
|
-
return (await this.engine(path, req)).data;
|
|
888
|
-
}
|
|
889
|
-
/**
|
|
890
|
-
* Prices a cart at the signed-in shopper's customer-group prices. The
|
|
891
|
-
* engine does this only for an account whose email is confirmed (403
|
|
892
|
-
* otherwise), and the cart stays the shopper's either way — so a refusal
|
|
893
|
-
* is not an error, and neither is a build without ext/identity.
|
|
894
|
-
*/
|
|
895
|
-
async claimCartForAccount(cartId) {
|
|
896
|
-
try {
|
|
897
|
-
await this.engine("/x/identity/me/carts", { method: "POST", body: { cart_id: cartId }, auth: true });
|
|
898
|
-
} catch {
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
// ------------------------------------------------------------ Litekart verbs
|
|
902
|
-
/**
|
|
903
|
-
* The family's path-addressed verbs. An absolute URL is fetched as-is; a
|
|
904
|
-
* relative one is a Litekart REST path, answered from the storefront's own
|
|
905
|
-
* data where it has some and refused otherwise (rest-guard.ts).
|
|
906
|
-
*/
|
|
907
|
-
get(path) {
|
|
908
|
-
return this.raw("get", path);
|
|
909
|
-
}
|
|
910
|
-
post(path, data) {
|
|
911
|
-
return this.raw("post", path, data);
|
|
912
|
-
}
|
|
913
|
-
put(path, data) {
|
|
914
|
-
return this.raw("put", path, data);
|
|
915
|
-
}
|
|
916
|
-
patch(path, data) {
|
|
917
|
-
return this.raw("patch", path, data);
|
|
918
|
-
}
|
|
919
|
-
delete(path) {
|
|
920
|
-
return this.raw("delete", path);
|
|
921
|
-
}
|
|
922
|
-
async raw(method, path, data) {
|
|
923
|
-
if (!/^https?:\/\//i.test(path)) return await resolveRestLocally(method, path);
|
|
924
|
-
const res = await this._fetch.call(globalThis, path, {
|
|
925
|
-
method: method.toUpperCase(),
|
|
926
|
-
headers: { Accept: "application/json", ...data === void 0 ? {} : { "Content-Type": "application/json" } },
|
|
927
|
-
body: data === void 0 ? void 0 : JSON.stringify(data)
|
|
928
|
-
});
|
|
929
|
-
const body = await res.json().catch(() => ({}));
|
|
930
|
-
if (!res.ok)
|
|
931
|
-
throw new GoCommerceError(body?.error?.message ?? `Request failed with status ${res.status}`, res.status);
|
|
932
|
-
return body;
|
|
933
|
-
}
|
|
934
|
-
};
|
|
935
|
-
var unreachable = (base, cause) => new GoCommerceError(`Could not reach the commerce engine at ${base}: ${cause?.message ?? cause}`, 0);
|
|
936
|
-
function qs(query) {
|
|
937
|
-
if (!query) return "";
|
|
938
|
-
const sp = new URLSearchParams();
|
|
939
|
-
for (const [key, value] of Object.entries(query)) {
|
|
940
|
-
if (value === void 0 || value === null || value === "") continue;
|
|
941
|
-
if (Array.isArray(value)) for (const v of value) sp.append(key, String(v));
|
|
942
|
-
else sp.append(key, String(value));
|
|
943
|
-
}
|
|
944
|
-
const s = sp.toString();
|
|
945
|
-
return s ? `?${s}` : "";
|
|
946
|
-
}
|
|
947
|
-
function toPage(env, map) {
|
|
948
|
-
const rows = env.data ?? [];
|
|
949
|
-
const meta = env.meta;
|
|
950
|
-
return {
|
|
951
|
-
data: rows.map(map),
|
|
952
|
-
count: meta?.total ?? rows.length,
|
|
953
|
-
pageSize: meta?.limit ?? rows.length,
|
|
954
|
-
noOfPage: meta?.total_pages ?? 1,
|
|
955
|
-
page: meta?.page ?? 1
|
|
985
|
+
createdAt: c.created_at,
|
|
986
|
+
updatedAt: c.updated_at
|
|
956
987
|
};
|
|
957
988
|
}
|
|
958
|
-
var
|
|
989
|
+
var priceOf = (m) => m ? toMajor(m) : 0;
|
|
959
990
|
|
|
960
991
|
// src/services/facts.ts
|
|
961
992
|
var StoreFacts = class extends BaseService {
|
|
962
993
|
key(name) {
|
|
963
|
-
return `${this.engineBase()}|${this.
|
|
994
|
+
return `${this.engineBase()}|${this.store ?? ""}|${name}`;
|
|
964
995
|
}
|
|
965
996
|
/** Enabled plugins by key. A module that is not installed has no key here. */
|
|
966
997
|
plugins() {
|
|
@@ -1973,7 +2004,7 @@ var OrderService = class extends BaseService {
|
|
|
1973
2004
|
productId: l.product_id,
|
|
1974
2005
|
variantId: l.variant_id,
|
|
1975
2006
|
title: l.title,
|
|
1976
|
-
img: l.image_url
|
|
2007
|
+
img: mediaUrl(l.image_url),
|
|
1977
2008
|
price: toMajor(l.unit_price),
|
|
1978
2009
|
qty: l.quantity
|
|
1979
2010
|
});
|
|
@@ -2390,7 +2421,7 @@ var MenuService = class extends BaseService {
|
|
|
2390
2421
|
/** The header and footer menus, or `null` when the module is not there. Cached: a menu is store-level. */
|
|
2391
2422
|
async engineMenus() {
|
|
2392
2423
|
if (!await this.facts.pluginOn("navigation")) return null;
|
|
2393
|
-
return remember(`${this.engineBase()}|${this.
|
|
2424
|
+
return remember(`${this.engineBase()}|${this.store ?? ""}|menus`, async () => {
|
|
2394
2425
|
const out = [];
|
|
2395
2426
|
for (const handle of ["header", "footer"]) {
|
|
2396
2427
|
try {
|
|
@@ -2424,6 +2455,7 @@ var NewsletterService = class extends BaseService {
|
|
|
2424
2455
|
var newsletterService = new NewsletterService();
|
|
2425
2456
|
|
|
2426
2457
|
// src/services/store-service.ts
|
|
2458
|
+
var isStoreMissing = (err) => err instanceof GoCommerceError && err.status === 404 && (err.code === "store_not_found" || /store not found|no such store/i.test(err.message));
|
|
2427
2459
|
function symbolFor(code) {
|
|
2428
2460
|
try {
|
|
2429
2461
|
const part = new Intl.NumberFormat("en", { style: "currency", currency: code }).formatToParts(0).find((p) => p.type === "currency");
|
|
@@ -2436,6 +2468,14 @@ var StoreService = class extends BaseService {
|
|
|
2436
2468
|
facts = new StoreFacts(this._fetch);
|
|
2437
2469
|
async getStoreByIdOrDomain(_args = {}) {
|
|
2438
2470
|
const base = await readStaticStore();
|
|
2471
|
+
const store = this.store;
|
|
2472
|
+
if (store) {
|
|
2473
|
+
await this.facts.plugins().catch((err) => {
|
|
2474
|
+
if (isStoreMissing(err)) {
|
|
2475
|
+
throw new GoCommerceError(`store not found: no store "${store}" on this engine`, 404, "store_not_found");
|
|
2476
|
+
}
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2439
2479
|
const live = await this.live().catch(() => ({}));
|
|
2440
2480
|
return { ...base, ...live, id: base.id || "gocommerce" };
|
|
2441
2481
|
}
|
|
@@ -3159,6 +3199,7 @@ exports.galleryService = galleryService;
|
|
|
3159
3199
|
exports.hasStaticStore = hasStaticStore;
|
|
3160
3200
|
exports.homeService = homeService;
|
|
3161
3201
|
exports.initService = initService;
|
|
3202
|
+
exports.mediaUrl = mediaUrl;
|
|
3162
3203
|
exports.meilisearchService = meilisearchService;
|
|
3163
3204
|
exports.menuService = menuService;
|
|
3164
3205
|
exports.minorToMajor = minorToMajor;
|
|
@@ -3181,11 +3222,13 @@ exports.regionService = regionService;
|
|
|
3181
3222
|
exports.reviewService = reviewService;
|
|
3182
3223
|
exports.searchService = searchService;
|
|
3183
3224
|
exports.serveRestLocally = serveRestLocally;
|
|
3225
|
+
exports.setRequestHost = setRequestHost;
|
|
3184
3226
|
exports.setStaticStore = setStaticStore;
|
|
3185
3227
|
exports.settingService = settingService;
|
|
3186
3228
|
exports.settingsService = settingsService;
|
|
3187
3229
|
exports.stateService = stateService;
|
|
3188
3230
|
exports.staticStore = staticStore;
|
|
3231
|
+
exports.storeForHost = storeForHost;
|
|
3189
3232
|
exports.storeService = storeService;
|
|
3190
3233
|
exports.toCart = toCart;
|
|
3191
3234
|
exports.toCartLine = toCartLine;
|