@misiki/gocommerce-connector 0.3.0 → 0.3.1
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/dist/{index-BfbB3jQb.d.cts → index-D8lNRhqJ.d.cts} +11 -1
- package/dist/{index-BfbB3jQb.d.ts → index-D8lNRhqJ.d.ts} +11 -1
- package/dist/index.cjs +337 -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 +337 -330
- package/dist/index.js.map +1 -1
- package/dist/services/index.cjs +19 -11
- 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 +19 -12
- package/dist/services/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -138,6 +138,313 @@ 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 engine's base URL from wherever this code runs. In the browser a
|
|
290
|
+
* configured proxy path wins, because the engine answers no CORS preflight.
|
|
291
|
+
*/
|
|
292
|
+
engineBase() {
|
|
293
|
+
const { apiUrl, proxyPath } = this.creds;
|
|
294
|
+
if (proxyPath && typeof window !== "undefined" && window.location) {
|
|
295
|
+
return new URL(proxyPath, window.location.origin).href.replace(/\/+$/, "");
|
|
296
|
+
}
|
|
297
|
+
const url = (apiUrl ?? "").trim();
|
|
298
|
+
if (!url) {
|
|
299
|
+
throw new GoCommerceError(
|
|
300
|
+
"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.",
|
|
301
|
+
0
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
return url.replace(/\/+$/, "");
|
|
305
|
+
}
|
|
306
|
+
/** An absolute engine URL for a path and query, without sending anything. */
|
|
307
|
+
engineURL(path, query) {
|
|
308
|
+
return this.engineBase() + path + qs(query);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* One request to the engine, envelope and all.
|
|
312
|
+
*
|
|
313
|
+
* The body is read as text first, so a proxy's HTML error page is reported
|
|
314
|
+
* as what it is rather than as a JSON parse error three frames away.
|
|
315
|
+
*/
|
|
316
|
+
async engine(path, req = {}) {
|
|
317
|
+
const lang = (req.method ?? "GET") === "GET" ? this.creds.language : void 0;
|
|
318
|
+
const url = this.engineURL(path, lang ? { lang, ...req.query } : req.query);
|
|
319
|
+
const headers = { Accept: "application/json", ...req.headers };
|
|
320
|
+
if (req.body !== void 0) headers["Content-Type"] = "application/json";
|
|
321
|
+
if (this.creds.store) headers["X-Store"] = this.creds.store;
|
|
322
|
+
if (req.auth) {
|
|
323
|
+
const token = sessionToken();
|
|
324
|
+
if (!token) throw new GoCommerceError("Please sign in to continue.", 401, "unauthorized");
|
|
325
|
+
headers.Authorization = `Bearer ${token}`;
|
|
326
|
+
}
|
|
327
|
+
const init = {
|
|
328
|
+
method: req.method ?? "GET",
|
|
329
|
+
headers,
|
|
330
|
+
body: req.body === void 0 ? void 0 : JSON.stringify(req.body)
|
|
331
|
+
};
|
|
332
|
+
let res;
|
|
333
|
+
try {
|
|
334
|
+
res = await this._fetch.call(globalThis, url, init);
|
|
335
|
+
} catch (cause) {
|
|
336
|
+
if (typeof window === "undefined" && /CORS/i.test(String(cause?.message))) {
|
|
337
|
+
try {
|
|
338
|
+
res = await globalThis.fetch(url, init);
|
|
339
|
+
} catch (again) {
|
|
340
|
+
throw unreachable(this.engineBase(), again);
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
throw unreachable(this.engineBase(), cause);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return this.unwrap(res);
|
|
347
|
+
}
|
|
348
|
+
/** The envelope opened: the payload, or the engine's error as a GoCommerceError. */
|
|
349
|
+
async unwrap(res) {
|
|
350
|
+
const text = await res.text();
|
|
351
|
+
let parsed = null;
|
|
352
|
+
if (text) {
|
|
353
|
+
try {
|
|
354
|
+
parsed = JSON.parse(text);
|
|
355
|
+
} catch {
|
|
356
|
+
throw new GoCommerceError(
|
|
357
|
+
`The commerce engine answered ${res.status} with something that is not JSON: ${text.slice(0, 120)}`,
|
|
358
|
+
res.status
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (!res.ok) {
|
|
363
|
+
const err = parsed?.error ?? {};
|
|
364
|
+
throw new GoCommerceError(
|
|
365
|
+
err.message ?? `The commerce engine answered ${res.status}.`,
|
|
366
|
+
res.status,
|
|
367
|
+
err.code,
|
|
368
|
+
err.details
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
return parsed ?? { data: null };
|
|
372
|
+
}
|
|
373
|
+
/** The bare payload, for the many callers that never look at `meta`. */
|
|
374
|
+
async data(path, req = {}) {
|
|
375
|
+
return (await this.engine(path, req)).data;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Prices a cart at the signed-in shopper's customer-group prices. The
|
|
379
|
+
* engine does this only for an account whose email is confirmed (403
|
|
380
|
+
* otherwise), and the cart stays the shopper's either way — so a refusal
|
|
381
|
+
* is not an error, and neither is a build without ext/identity.
|
|
382
|
+
*/
|
|
383
|
+
async claimCartForAccount(cartId) {
|
|
384
|
+
try {
|
|
385
|
+
await this.engine("/x/identity/me/carts", { method: "POST", body: { cart_id: cartId }, auth: true });
|
|
386
|
+
} catch {
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
// ------------------------------------------------------------ Litekart verbs
|
|
390
|
+
/**
|
|
391
|
+
* The family's path-addressed verbs. An absolute URL is fetched as-is; a
|
|
392
|
+
* relative one is a Litekart REST path, answered from the storefront's own
|
|
393
|
+
* data where it has some and refused otherwise (rest-guard.ts).
|
|
394
|
+
*/
|
|
395
|
+
get(path) {
|
|
396
|
+
return this.raw("get", path);
|
|
397
|
+
}
|
|
398
|
+
post(path, data) {
|
|
399
|
+
return this.raw("post", path, data);
|
|
400
|
+
}
|
|
401
|
+
put(path, data) {
|
|
402
|
+
return this.raw("put", path, data);
|
|
403
|
+
}
|
|
404
|
+
patch(path, data) {
|
|
405
|
+
return this.raw("patch", path, data);
|
|
406
|
+
}
|
|
407
|
+
delete(path) {
|
|
408
|
+
return this.raw("delete", path);
|
|
409
|
+
}
|
|
410
|
+
async raw(method, path, data) {
|
|
411
|
+
if (!/^https?:\/\//i.test(path)) return await resolveRestLocally(method, path);
|
|
412
|
+
const res = await this._fetch.call(globalThis, path, {
|
|
413
|
+
method: method.toUpperCase(),
|
|
414
|
+
headers: { Accept: "application/json", ...data === void 0 ? {} : { "Content-Type": "application/json" } },
|
|
415
|
+
body: data === void 0 ? void 0 : JSON.stringify(data)
|
|
416
|
+
});
|
|
417
|
+
const body = await res.json().catch(() => ({}));
|
|
418
|
+
if (!res.ok)
|
|
419
|
+
throw new GoCommerceError(body?.error?.message ?? `Request failed with status ${res.status}`, res.status);
|
|
420
|
+
return body;
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
var unreachable = (base, cause) => new GoCommerceError(`Could not reach the commerce engine at ${base}: ${cause?.message ?? cause}`, 0);
|
|
424
|
+
function qs(query) {
|
|
425
|
+
if (!query) return "";
|
|
426
|
+
const sp = new URLSearchParams();
|
|
427
|
+
for (const [key, value] of Object.entries(query)) {
|
|
428
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
429
|
+
if (Array.isArray(value)) for (const v of value) sp.append(key, String(v));
|
|
430
|
+
else sp.append(key, String(value));
|
|
431
|
+
}
|
|
432
|
+
const s = sp.toString();
|
|
433
|
+
return s ? `?${s}` : "";
|
|
434
|
+
}
|
|
435
|
+
function toPage(env, map) {
|
|
436
|
+
const rows = env.data ?? [];
|
|
437
|
+
const meta = env.meta;
|
|
438
|
+
return {
|
|
439
|
+
data: rows.map(map),
|
|
440
|
+
count: meta?.total ?? rows.length,
|
|
441
|
+
pageSize: meta?.limit ?? rows.length,
|
|
442
|
+
noOfPage: meta?.total_pages ?? 1,
|
|
443
|
+
page: meta?.page ?? 1
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
var emptyPage = () => ({ data: [], count: 0, pageSize: 0, noOfPage: 0, page: 1 });
|
|
447
|
+
|
|
141
448
|
// src/map.ts
|
|
142
449
|
var str = (v) => v === null || v === void 0 ? "" : String(v);
|
|
143
450
|
var arr = (v) => v ?? [];
|
|
@@ -145,7 +452,8 @@ var liveVariants = (p) => arr(p.variants).filter((v) => v.active);
|
|
|
145
452
|
function variantImages(v) {
|
|
146
453
|
const out = [];
|
|
147
454
|
for (const img of [v.image, ...arr(v.images)]) {
|
|
148
|
-
|
|
455
|
+
const url = mediaUrl(img?.url);
|
|
456
|
+
if (url && (img?.kind ?? "image") === "image" && !out.includes(url)) out.push(url);
|
|
149
457
|
}
|
|
150
458
|
return out;
|
|
151
459
|
}
|
|
@@ -154,7 +462,7 @@ function productImages(p) {
|
|
|
154
462
|
const push = (url) => {
|
|
155
463
|
if (url && !out.includes(url)) out.push(url);
|
|
156
464
|
};
|
|
157
|
-
push(p.image_url);
|
|
465
|
+
push(mediaUrl(p.image_url));
|
|
158
466
|
for (const v of arr(p.variants)) for (const url of variantImages(v)) push(url);
|
|
159
467
|
return out;
|
|
160
468
|
}
|
|
@@ -284,9 +592,9 @@ function fromSearchDoc(d) {
|
|
|
284
592
|
mrp: price,
|
|
285
593
|
minPrice: price,
|
|
286
594
|
maxPrice: minorToMajor(d.price_max_minor, d.currency),
|
|
287
|
-
thumbnail: d.image
|
|
288
|
-
image_url: d.image
|
|
289
|
-
images: d.image ?? "",
|
|
595
|
+
thumbnail: mediaUrl(d.image),
|
|
596
|
+
image_url: mediaUrl(d.image),
|
|
597
|
+
images: mediaUrl(d.image) ?? "",
|
|
290
598
|
stock: d.in_stock ? 1 : 0,
|
|
291
599
|
hasStock: d.in_stock,
|
|
292
600
|
// The index cannot say how much is left, only whether any is; a card
|
|
@@ -315,7 +623,7 @@ function toCategory(c) {
|
|
|
315
623
|
level: c.depth,
|
|
316
624
|
rank: c.position,
|
|
317
625
|
childCount: c.child_count,
|
|
318
|
-
thumbnail: c.metadata?.image
|
|
626
|
+
thumbnail: mediaUrl(c.metadata?.image),
|
|
319
627
|
description: c.metadata?.description ?? null,
|
|
320
628
|
isActive: true,
|
|
321
629
|
isFeatured: false,
|
|
@@ -335,7 +643,7 @@ function toCollection(c) {
|
|
|
335
643
|
description: c.description || null,
|
|
336
644
|
subTitle: null,
|
|
337
645
|
rank: str(c.position),
|
|
338
|
-
thumbnail: c.metadata?.image
|
|
646
|
+
thumbnail: mediaUrl(c.metadata?.image),
|
|
339
647
|
isActive: true,
|
|
340
648
|
metaTitle: c.title,
|
|
341
649
|
metaDescription: c.description || null,
|
|
@@ -418,7 +726,7 @@ function toCartLine(l, extra = {}) {
|
|
|
418
726
|
title: l.title,
|
|
419
727
|
name: l.title,
|
|
420
728
|
slug: extra.slug ?? "",
|
|
421
|
-
thumbnail: extra.thumbnail
|
|
729
|
+
thumbnail: mediaUrl(extra.thumbnail),
|
|
422
730
|
variantTitle: l.variant_label ?? "",
|
|
423
731
|
qty: l.quantity,
|
|
424
732
|
price: toMajor(l.unit_price),
|
|
@@ -526,8 +834,8 @@ function toOrderLine(l, currency) {
|
|
|
526
834
|
title: l.title,
|
|
527
835
|
name: l.title,
|
|
528
836
|
slug: "",
|
|
529
|
-
thumbnail: l.image_url
|
|
530
|
-
img: l.image_url
|
|
837
|
+
thumbnail: mediaUrl(l.image_url),
|
|
838
|
+
img: mediaUrl(l.image_url),
|
|
531
839
|
variantTitle: l.variant_label ?? "",
|
|
532
840
|
qty: l.quantity,
|
|
533
841
|
shippedQty: l.shipped_quantity,
|
|
@@ -627,335 +935,34 @@ function toVendor(v) {
|
|
|
627
935
|
id: str(v.id),
|
|
628
936
|
slug: v.slug,
|
|
629
937
|
name: v.name,
|
|
630
|
-
businessName: v.name,
|
|
631
|
-
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;
|
|
938
|
+
businessName: v.name,
|
|
939
|
+
about: v.about,
|
|
940
|
+
description: v.about,
|
|
941
|
+
website: v.website,
|
|
942
|
+
address: v.address,
|
|
943
|
+
banners: [],
|
|
944
|
+
rating: null
|
|
945
|
+
};
|
|
709
946
|
}
|
|
710
|
-
|
|
711
|
-
var sessionToken = () => readLocal(TOKEN);
|
|
712
|
-
function toMe(c) {
|
|
947
|
+
function toUser(c) {
|
|
713
948
|
const [firstName, ...rest] = (c.name ?? "").trim().split(/\s+/);
|
|
714
949
|
return {
|
|
715
|
-
id:
|
|
716
|
-
userId:
|
|
950
|
+
id: str(c.id),
|
|
951
|
+
userId: str(c.id),
|
|
717
952
|
email: c.email,
|
|
953
|
+
emailVerified: c.email_verified,
|
|
718
954
|
phone: c.phone || null,
|
|
719
955
|
firstName: firstName || "",
|
|
720
956
|
lastName: rest.join(" "),
|
|
721
|
-
name: c.name
|
|
722
|
-
fullName: c.name
|
|
957
|
+
name: c.name,
|
|
958
|
+
fullName: c.name,
|
|
723
959
|
avatar: null,
|
|
724
960
|
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
|
|
961
|
+
createdAt: c.created_at,
|
|
962
|
+
updatedAt: c.updated_at
|
|
956
963
|
};
|
|
957
964
|
}
|
|
958
|
-
var
|
|
965
|
+
var priceOf = (m) => m ? toMajor(m) : 0;
|
|
959
966
|
|
|
960
967
|
// src/services/facts.ts
|
|
961
968
|
var StoreFacts = class extends BaseService {
|
|
@@ -1973,7 +1980,7 @@ var OrderService = class extends BaseService {
|
|
|
1973
1980
|
productId: l.product_id,
|
|
1974
1981
|
variantId: l.variant_id,
|
|
1975
1982
|
title: l.title,
|
|
1976
|
-
img: l.image_url
|
|
1983
|
+
img: mediaUrl(l.image_url),
|
|
1977
1984
|
price: toMajor(l.unit_price),
|
|
1978
1985
|
qty: l.quantity
|
|
1979
1986
|
});
|
|
@@ -3159,6 +3166,7 @@ exports.galleryService = galleryService;
|
|
|
3159
3166
|
exports.hasStaticStore = hasStaticStore;
|
|
3160
3167
|
exports.homeService = homeService;
|
|
3161
3168
|
exports.initService = initService;
|
|
3169
|
+
exports.mediaUrl = mediaUrl;
|
|
3162
3170
|
exports.meilisearchService = meilisearchService;
|
|
3163
3171
|
exports.menuService = menuService;
|
|
3164
3172
|
exports.minorToMajor = minorToMajor;
|