shop-client 3.8.2
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/LICENSE +21 -0
- package/README.md +912 -0
- package/dist/checkout.d.mts +31 -0
- package/dist/checkout.d.ts +31 -0
- package/dist/checkout.js +115 -0
- package/dist/checkout.js.map +1 -0
- package/dist/checkout.mjs +7 -0
- package/dist/checkout.mjs.map +1 -0
- package/dist/chunk-2KBOKOAD.mjs +177 -0
- package/dist/chunk-2KBOKOAD.mjs.map +1 -0
- package/dist/chunk-BWKBRM2Z.mjs +136 -0
- package/dist/chunk-BWKBRM2Z.mjs.map +1 -0
- package/dist/chunk-O4BPIIQ6.mjs +503 -0
- package/dist/chunk-O4BPIIQ6.mjs.map +1 -0
- package/dist/chunk-QCTICSBE.mjs +398 -0
- package/dist/chunk-QCTICSBE.mjs.map +1 -0
- package/dist/chunk-QL5OUZGP.mjs +91 -0
- package/dist/chunk-QL5OUZGP.mjs.map +1 -0
- package/dist/chunk-WTK5HUFI.mjs +1287 -0
- package/dist/chunk-WTK5HUFI.mjs.map +1 -0
- package/dist/collections.d.mts +64 -0
- package/dist/collections.d.ts +64 -0
- package/dist/collections.js +540 -0
- package/dist/collections.js.map +1 -0
- package/dist/collections.mjs +9 -0
- package/dist/collections.mjs.map +1 -0
- package/dist/index.d.mts +233 -0
- package/dist/index.d.ts +233 -0
- package/dist/index.js +3241 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +702 -0
- package/dist/index.mjs.map +1 -0
- package/dist/products.d.mts +63 -0
- package/dist/products.d.ts +63 -0
- package/dist/products.js +1206 -0
- package/dist/products.js.map +1 -0
- package/dist/products.mjs +9 -0
- package/dist/products.mjs.map +1 -0
- package/dist/store-CJVUz2Yb.d.mts +608 -0
- package/dist/store-CJVUz2Yb.d.ts +608 -0
- package/dist/store.d.mts +1 -0
- package/dist/store.d.ts +1 -0
- package/dist/store.js +698 -0
- package/dist/store.js.map +1 -0
- package/dist/store.mjs +9 -0
- package/dist/store.mjs.map +1 -0
- package/dist/utils/rate-limit.d.mts +25 -0
- package/dist/utils/rate-limit.d.ts +25 -0
- package/dist/utils/rate-limit.js +203 -0
- package/dist/utils/rate-limit.js.map +1 -0
- package/dist/utils/rate-limit.mjs +11 -0
- package/dist/utils/rate-limit.mjs.map +1 -0
- package/package.json +116 -0
package/dist/store.js
ADDED
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/store.ts
|
|
21
|
+
var store_exports = {};
|
|
22
|
+
__export(store_exports, {
|
|
23
|
+
createStoreOperations: () => createStoreOperations
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(store_exports);
|
|
26
|
+
|
|
27
|
+
// src/client/get-info.ts
|
|
28
|
+
var import_remeda = require("remeda");
|
|
29
|
+
|
|
30
|
+
// src/utils/detect-country.ts
|
|
31
|
+
var COUNTRY_CODES = {
|
|
32
|
+
"+1": "US",
|
|
33
|
+
// United States (primary) / Canada also uses +1
|
|
34
|
+
"+44": "GB",
|
|
35
|
+
// United Kingdom
|
|
36
|
+
"+61": "AU",
|
|
37
|
+
// Australia
|
|
38
|
+
"+65": "SG",
|
|
39
|
+
// Singapore
|
|
40
|
+
"+91": "IN",
|
|
41
|
+
// India
|
|
42
|
+
"+81": "JP",
|
|
43
|
+
// Japan
|
|
44
|
+
"+49": "DE",
|
|
45
|
+
// Germany
|
|
46
|
+
"+33": "FR",
|
|
47
|
+
// France
|
|
48
|
+
"+971": "AE",
|
|
49
|
+
// United Arab Emirates
|
|
50
|
+
"+39": "IT",
|
|
51
|
+
// Italy
|
|
52
|
+
"+34": "ES",
|
|
53
|
+
// Spain
|
|
54
|
+
"+82": "KR",
|
|
55
|
+
// South Korea
|
|
56
|
+
"+55": "BR",
|
|
57
|
+
// Brazil
|
|
58
|
+
"+62": "ID",
|
|
59
|
+
// Indonesia
|
|
60
|
+
"+92": "PK",
|
|
61
|
+
// Pakistan
|
|
62
|
+
"+7": "RU"
|
|
63
|
+
// Russia
|
|
64
|
+
};
|
|
65
|
+
var CURRENCY_SYMBOLS = {
|
|
66
|
+
Rs: "IN",
|
|
67
|
+
// India
|
|
68
|
+
"\u20B9": "IN",
|
|
69
|
+
// India
|
|
70
|
+
$: "US",
|
|
71
|
+
// United States (primary, though many countries use $)
|
|
72
|
+
CA$: "CA",
|
|
73
|
+
// Canada
|
|
74
|
+
A$: "AU",
|
|
75
|
+
// Australia
|
|
76
|
+
"\xA3": "GB",
|
|
77
|
+
// United Kingdom
|
|
78
|
+
"\u20AC": "EU",
|
|
79
|
+
// European Union (not a country code, but commonly used)
|
|
80
|
+
AED: "AE",
|
|
81
|
+
// United Arab Emirates
|
|
82
|
+
"\u20A9": "KR",
|
|
83
|
+
// South Korea
|
|
84
|
+
"\xA5": "JP"
|
|
85
|
+
// Japan (primary, though China also uses ¥)
|
|
86
|
+
};
|
|
87
|
+
var CURRENCY_SYMBOL_TO_CODE = {
|
|
88
|
+
Rs: "INR",
|
|
89
|
+
"\u20B9": "INR",
|
|
90
|
+
$: "USD",
|
|
91
|
+
CA$: "CAD",
|
|
92
|
+
A$: "AUD",
|
|
93
|
+
"\xA3": "GBP",
|
|
94
|
+
"\u20AC": "EUR",
|
|
95
|
+
AED: "AED",
|
|
96
|
+
"\u20A9": "KRW",
|
|
97
|
+
"\xA5": "JPY"
|
|
98
|
+
};
|
|
99
|
+
var CURRENCY_CODE_TO_COUNTRY = {
|
|
100
|
+
INR: "IN",
|
|
101
|
+
USD: "US",
|
|
102
|
+
CAD: "CA",
|
|
103
|
+
AUD: "AU",
|
|
104
|
+
GBP: "GB",
|
|
105
|
+
EUR: "EU",
|
|
106
|
+
AED: "AE",
|
|
107
|
+
KRW: "KR",
|
|
108
|
+
JPY: "JP"
|
|
109
|
+
};
|
|
110
|
+
function scoreCountry(countryScores, country, weight, reason) {
|
|
111
|
+
if (!country) return;
|
|
112
|
+
if (!countryScores[country])
|
|
113
|
+
countryScores[country] = { score: 0, reasons: [] };
|
|
114
|
+
countryScores[country].score += weight;
|
|
115
|
+
countryScores[country].reasons.push(reason);
|
|
116
|
+
}
|
|
117
|
+
async function detectShopifyCountry(html) {
|
|
118
|
+
var _a, _b;
|
|
119
|
+
const countryScores = {};
|
|
120
|
+
let detectedCurrencyCode;
|
|
121
|
+
const shopifyFeaturesMatch = html.match(
|
|
122
|
+
/<script[^>]+id=["']shopify-features["'][^>]*>([\s\S]*?)<\/script>/
|
|
123
|
+
);
|
|
124
|
+
if (shopifyFeaturesMatch) {
|
|
125
|
+
try {
|
|
126
|
+
const json = shopifyFeaturesMatch[1];
|
|
127
|
+
if (!json) {
|
|
128
|
+
} else {
|
|
129
|
+
const data = JSON.parse(json);
|
|
130
|
+
if (data.country)
|
|
131
|
+
scoreCountry(
|
|
132
|
+
countryScores,
|
|
133
|
+
data.country,
|
|
134
|
+
1,
|
|
135
|
+
"shopify-features.country"
|
|
136
|
+
);
|
|
137
|
+
if ((_a = data.locale) == null ? void 0 : _a.includes("-")) {
|
|
138
|
+
const [, localeCountry] = data.locale.split("-");
|
|
139
|
+
if (localeCountry) {
|
|
140
|
+
scoreCountry(
|
|
141
|
+
countryScores,
|
|
142
|
+
localeCountry.toUpperCase(),
|
|
143
|
+
0.7,
|
|
144
|
+
"shopify-features.locale"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (data.moneyFormat) {
|
|
149
|
+
for (const symbol in CURRENCY_SYMBOLS) {
|
|
150
|
+
if (data.moneyFormat.includes(symbol)) {
|
|
151
|
+
const iso = CURRENCY_SYMBOLS[symbol];
|
|
152
|
+
if (typeof iso === "string") {
|
|
153
|
+
scoreCountry(countryScores, iso, 0.6, "moneyFormat symbol");
|
|
154
|
+
}
|
|
155
|
+
const code = CURRENCY_SYMBOL_TO_CODE[symbol];
|
|
156
|
+
if (!detectedCurrencyCode && typeof code === "string") {
|
|
157
|
+
detectedCurrencyCode = code;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
} catch (_error) {
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const currencyJsonMatch = html.match(/Shopify\.currency\s*=\s*(\{[^}]*\})/);
|
|
167
|
+
if (currencyJsonMatch) {
|
|
168
|
+
try {
|
|
169
|
+
const raw = currencyJsonMatch[1];
|
|
170
|
+
const obj = JSON.parse(raw || "{}");
|
|
171
|
+
const activeCode = typeof (obj == null ? void 0 : obj.active) === "string" ? obj.active.toUpperCase() : void 0;
|
|
172
|
+
const iso = activeCode ? CURRENCY_CODE_TO_COUNTRY[activeCode] : void 0;
|
|
173
|
+
if (activeCode) {
|
|
174
|
+
detectedCurrencyCode = activeCode;
|
|
175
|
+
}
|
|
176
|
+
if (typeof iso === "string") {
|
|
177
|
+
scoreCountry(countryScores, iso, 0.8, "Shopify.currency.active");
|
|
178
|
+
}
|
|
179
|
+
} catch (_error) {
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
const currencyActiveAssignMatch = html.match(
|
|
183
|
+
/Shopify\.currency\.active\s*=\s*['"]([A-Za-z]{3})['"]/i
|
|
184
|
+
);
|
|
185
|
+
if (currencyActiveAssignMatch) {
|
|
186
|
+
const captured = currencyActiveAssignMatch[1];
|
|
187
|
+
const code = typeof captured === "string" ? captured.toUpperCase() : void 0;
|
|
188
|
+
const iso = code ? CURRENCY_CODE_TO_COUNTRY[code] : void 0;
|
|
189
|
+
if (code) {
|
|
190
|
+
detectedCurrencyCode = code;
|
|
191
|
+
}
|
|
192
|
+
if (typeof iso === "string") {
|
|
193
|
+
scoreCountry(countryScores, iso, 0.8, "Shopify.currency.active");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const shopifyCountryMatch = html.match(
|
|
198
|
+
/Shopify\.country\s*=\s*['"]([A-Za-z]{2})['"]/i
|
|
199
|
+
);
|
|
200
|
+
if (shopifyCountryMatch) {
|
|
201
|
+
const captured = shopifyCountryMatch[1];
|
|
202
|
+
const iso = typeof captured === "string" ? captured.toUpperCase() : void 0;
|
|
203
|
+
if (typeof iso === "string") {
|
|
204
|
+
scoreCountry(countryScores, iso, 1, "Shopify.country");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const phones = html.match(/\+\d{1,3}[\s\-()0-9]{5,}/g);
|
|
208
|
+
if (phones) {
|
|
209
|
+
for (const phone of phones) {
|
|
210
|
+
const prefix = (_b = phone.match(/^\+\d{1,3}/)) == null ? void 0 : _b[0];
|
|
211
|
+
if (prefix && COUNTRY_CODES[prefix])
|
|
212
|
+
scoreCountry(
|
|
213
|
+
countryScores,
|
|
214
|
+
COUNTRY_CODES[prefix],
|
|
215
|
+
0.8,
|
|
216
|
+
`phone prefix ${prefix}`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const jsonLdRegex = /<script[^>]+application\/ld\+json[^>]*>(.*?)<\/script>/g;
|
|
221
|
+
let jsonLdMatch = jsonLdRegex.exec(html);
|
|
222
|
+
while (jsonLdMatch !== null) {
|
|
223
|
+
try {
|
|
224
|
+
const json = jsonLdMatch[1];
|
|
225
|
+
if (!json) {
|
|
226
|
+
} else {
|
|
227
|
+
const raw = JSON.parse(json);
|
|
228
|
+
const collectAddressCountries = (node, results = []) => {
|
|
229
|
+
if (Array.isArray(node)) {
|
|
230
|
+
for (const item of node) collectAddressCountries(item, results);
|
|
231
|
+
return results;
|
|
232
|
+
}
|
|
233
|
+
if (node && typeof node === "object") {
|
|
234
|
+
const obj = node;
|
|
235
|
+
const address = obj.address;
|
|
236
|
+
if (address && typeof address === "object") {
|
|
237
|
+
const country = address.addressCountry;
|
|
238
|
+
if (typeof country === "string") results.push(country);
|
|
239
|
+
}
|
|
240
|
+
const graph = obj["@graph"];
|
|
241
|
+
if (graph) collectAddressCountries(graph, results);
|
|
242
|
+
}
|
|
243
|
+
return results;
|
|
244
|
+
};
|
|
245
|
+
const countries = collectAddressCountries(raw);
|
|
246
|
+
for (const country of countries) {
|
|
247
|
+
scoreCountry(countryScores, country, 1, "JSON-LD addressCountry");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} catch (_error) {
|
|
251
|
+
}
|
|
252
|
+
jsonLdMatch = jsonLdRegex.exec(html);
|
|
253
|
+
}
|
|
254
|
+
const footerMatch = html.match(/<footer[^>]*>(.*?)<\/footer>/i);
|
|
255
|
+
if (footerMatch) {
|
|
256
|
+
const footerTextGroup = footerMatch[1];
|
|
257
|
+
const footerText = footerTextGroup ? footerTextGroup.toLowerCase() : "";
|
|
258
|
+
const countryNameToISO = {
|
|
259
|
+
india: "IN",
|
|
260
|
+
"united states": "US",
|
|
261
|
+
canada: "CA",
|
|
262
|
+
australia: "AU",
|
|
263
|
+
"united kingdom": "GB",
|
|
264
|
+
britain: "GB",
|
|
265
|
+
uk: "GB",
|
|
266
|
+
japan: "JP",
|
|
267
|
+
"south korea": "KR",
|
|
268
|
+
korea: "KR",
|
|
269
|
+
germany: "DE",
|
|
270
|
+
france: "FR",
|
|
271
|
+
italy: "IT",
|
|
272
|
+
spain: "ES",
|
|
273
|
+
brazil: "BR",
|
|
274
|
+
russia: "RU",
|
|
275
|
+
singapore: "SG",
|
|
276
|
+
indonesia: "ID",
|
|
277
|
+
pakistan: "PK"
|
|
278
|
+
};
|
|
279
|
+
for (const [countryName, isoCode] of Object.entries(countryNameToISO)) {
|
|
280
|
+
if (footerText.includes(countryName))
|
|
281
|
+
scoreCountry(countryScores, isoCode, 0.4, "footer mention");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const sorted = Object.entries(countryScores).sort(
|
|
285
|
+
(a, b) => b[1].score - a[1].score
|
|
286
|
+
);
|
|
287
|
+
const best = sorted[0];
|
|
288
|
+
return best ? {
|
|
289
|
+
country: best[0],
|
|
290
|
+
confidence: Math.min(1, best[1].score / 2),
|
|
291
|
+
signals: best[1].reasons,
|
|
292
|
+
currencyCode: detectedCurrencyCode
|
|
293
|
+
} : {
|
|
294
|
+
country: "Unknown",
|
|
295
|
+
confidence: 0,
|
|
296
|
+
signals: [],
|
|
297
|
+
currencyCode: detectedCurrencyCode
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/utils/func.ts
|
|
302
|
+
var import_tldts = require("tldts");
|
|
303
|
+
function extractDomainWithoutSuffix(domain) {
|
|
304
|
+
const parsedDomain = (0, import_tldts.parse)(domain);
|
|
305
|
+
return parsedDomain.domainWithoutSuffix;
|
|
306
|
+
}
|
|
307
|
+
function generateStoreSlug(domain) {
|
|
308
|
+
var _a;
|
|
309
|
+
const input = new URL(domain);
|
|
310
|
+
const parsedDomain = (0, import_tldts.parse)(input.href);
|
|
311
|
+
const domainName = (_a = parsedDomain.domainWithoutSuffix) != null ? _a : input.hostname.split(".")[0];
|
|
312
|
+
return (domainName || "").toLowerCase().replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
313
|
+
}
|
|
314
|
+
function sanitizeDomain(input, opts) {
|
|
315
|
+
var _a;
|
|
316
|
+
if (typeof input !== "string") {
|
|
317
|
+
throw new Error("sanitizeDomain: input must be a string");
|
|
318
|
+
}
|
|
319
|
+
let raw = input.trim();
|
|
320
|
+
if (!raw) {
|
|
321
|
+
throw new Error("sanitizeDomain: input cannot be empty");
|
|
322
|
+
}
|
|
323
|
+
const hasProtocol = /^[a-z]+:\/\//i.test(raw);
|
|
324
|
+
if (!hasProtocol && !raw.startsWith("//")) {
|
|
325
|
+
raw = `https://${raw}`;
|
|
326
|
+
}
|
|
327
|
+
const stripWWW = (_a = opts == null ? void 0 : opts.stripWWW) != null ? _a : true;
|
|
328
|
+
try {
|
|
329
|
+
let url;
|
|
330
|
+
if (raw.startsWith("//")) {
|
|
331
|
+
url = new URL(`https:${raw}`);
|
|
332
|
+
} else if (raw.includes("://")) {
|
|
333
|
+
url = new URL(raw);
|
|
334
|
+
} else {
|
|
335
|
+
url = new URL(`https://${raw}`);
|
|
336
|
+
}
|
|
337
|
+
let hostname = url.hostname.toLowerCase();
|
|
338
|
+
const hadWWW = /^www\./i.test(url.hostname);
|
|
339
|
+
if (stripWWW) hostname = hostname.replace(/^www\./, "");
|
|
340
|
+
if (!hostname.includes(".")) {
|
|
341
|
+
throw new Error("sanitizeDomain: invalid domain (missing suffix)");
|
|
342
|
+
}
|
|
343
|
+
const parsed = (0, import_tldts.parse)(hostname);
|
|
344
|
+
if (!parsed.publicSuffix || parsed.isIcann === false) {
|
|
345
|
+
throw new Error("sanitizeDomain: invalid domain (missing suffix)");
|
|
346
|
+
}
|
|
347
|
+
if (!stripWWW && hadWWW) {
|
|
348
|
+
return `www.${parsed.domain || hostname}`;
|
|
349
|
+
}
|
|
350
|
+
return parsed.domain || hostname;
|
|
351
|
+
} catch {
|
|
352
|
+
let hostname = raw.toLowerCase();
|
|
353
|
+
hostname = hostname.replace(/^[a-z]+:\/\//, "");
|
|
354
|
+
hostname = hostname.replace(/^\/\//, "");
|
|
355
|
+
hostname = hostname.replace(/[/:#?].*$/, "");
|
|
356
|
+
const hadWWW = /^www\./i.test(hostname);
|
|
357
|
+
if (stripWWW) hostname = hostname.replace(/^www\./, "");
|
|
358
|
+
if (!hostname.includes(".")) {
|
|
359
|
+
throw new Error("sanitizeDomain: invalid domain (missing suffix)");
|
|
360
|
+
}
|
|
361
|
+
const parsed = (0, import_tldts.parse)(hostname);
|
|
362
|
+
if (!parsed.publicSuffix || parsed.isIcann === false) {
|
|
363
|
+
throw new Error("sanitizeDomain: invalid domain (missing suffix)");
|
|
364
|
+
}
|
|
365
|
+
if (!stripWWW && hadWWW) {
|
|
366
|
+
return `www.${parsed.domain || hostname}`;
|
|
367
|
+
}
|
|
368
|
+
return parsed.domain || hostname;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// src/utils/rate-limit.ts
|
|
373
|
+
var RateLimiter = class {
|
|
374
|
+
constructor(options) {
|
|
375
|
+
this.queue = [];
|
|
376
|
+
this.inFlight = 0;
|
|
377
|
+
this.refillTimer = null;
|
|
378
|
+
this.options = options;
|
|
379
|
+
this.tokens = options.maxRequestsPerInterval;
|
|
380
|
+
}
|
|
381
|
+
startRefill() {
|
|
382
|
+
if (this.refillTimer) return;
|
|
383
|
+
this.refillTimer = setInterval(() => {
|
|
384
|
+
this.tokens = this.options.maxRequestsPerInterval;
|
|
385
|
+
this.tryRun();
|
|
386
|
+
}, this.options.intervalMs);
|
|
387
|
+
if (this.refillTimer && typeof this.refillTimer.unref === "function") {
|
|
388
|
+
this.refillTimer.unref();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
stopRefill() {
|
|
392
|
+
if (this.refillTimer) {
|
|
393
|
+
clearInterval(this.refillTimer);
|
|
394
|
+
this.refillTimer = null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
ensureRefillStarted() {
|
|
398
|
+
if (!this.refillTimer) {
|
|
399
|
+
this.startRefill();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
configure(next) {
|
|
403
|
+
this.options = { ...this.options, ...next };
|
|
404
|
+
this.options.maxRequestsPerInterval = Math.max(
|
|
405
|
+
1,
|
|
406
|
+
this.options.maxRequestsPerInterval
|
|
407
|
+
);
|
|
408
|
+
this.options.intervalMs = Math.max(10, this.options.intervalMs);
|
|
409
|
+
this.options.maxConcurrency = Math.max(1, this.options.maxConcurrency);
|
|
410
|
+
}
|
|
411
|
+
schedule(fn) {
|
|
412
|
+
return new Promise((resolve, reject) => {
|
|
413
|
+
this.ensureRefillStarted();
|
|
414
|
+
this.queue.push({ fn, resolve, reject });
|
|
415
|
+
this.tryRun();
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
tryRun() {
|
|
419
|
+
while (this.queue.length > 0 && this.inFlight < this.options.maxConcurrency && this.tokens > 0) {
|
|
420
|
+
const task = this.queue.shift();
|
|
421
|
+
this.tokens -= 1;
|
|
422
|
+
this.inFlight += 1;
|
|
423
|
+
Promise.resolve().then(task.fn).then((result) => task.resolve(result)).catch((err) => task.reject(err)).finally(() => {
|
|
424
|
+
this.inFlight -= 1;
|
|
425
|
+
setTimeout(() => this.tryRun(), 0);
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
var enabled = false;
|
|
431
|
+
var defaultOptions = {
|
|
432
|
+
maxRequestsPerInterval: 5,
|
|
433
|
+
// 5 requests
|
|
434
|
+
intervalMs: 1e3,
|
|
435
|
+
// per second
|
|
436
|
+
maxConcurrency: 5
|
|
437
|
+
// up to 5 in parallel
|
|
438
|
+
};
|
|
439
|
+
var limiter = new RateLimiter(defaultOptions);
|
|
440
|
+
var hostLimiters = /* @__PURE__ */ new Map();
|
|
441
|
+
var classLimiters = /* @__PURE__ */ new Map();
|
|
442
|
+
function getHost(input) {
|
|
443
|
+
try {
|
|
444
|
+
if (typeof input === "string") {
|
|
445
|
+
return new URL(input).host;
|
|
446
|
+
}
|
|
447
|
+
if (input instanceof URL) {
|
|
448
|
+
return input.host;
|
|
449
|
+
}
|
|
450
|
+
const url = input.url;
|
|
451
|
+
if (url) {
|
|
452
|
+
return new URL(url).host;
|
|
453
|
+
}
|
|
454
|
+
} catch {
|
|
455
|
+
}
|
|
456
|
+
return void 0;
|
|
457
|
+
}
|
|
458
|
+
function getHostLimiter(host) {
|
|
459
|
+
if (!host) return void 0;
|
|
460
|
+
const exact = hostLimiters.get(host);
|
|
461
|
+
if (exact) return exact;
|
|
462
|
+
for (const [key, lim] of hostLimiters.entries()) {
|
|
463
|
+
if (key.startsWith("*.") && host.endsWith(key.slice(2))) {
|
|
464
|
+
return lim;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return void 0;
|
|
468
|
+
}
|
|
469
|
+
async function rateLimitedFetch(input, init) {
|
|
470
|
+
var _a;
|
|
471
|
+
if (!enabled) {
|
|
472
|
+
return fetch(input, init);
|
|
473
|
+
}
|
|
474
|
+
const klass = init == null ? void 0 : init.rateLimitClass;
|
|
475
|
+
const byClass = klass ? classLimiters.get(klass) : void 0;
|
|
476
|
+
const byHost = getHostLimiter(getHost(input));
|
|
477
|
+
const eff = (_a = byClass != null ? byClass : byHost) != null ? _a : limiter;
|
|
478
|
+
return eff.schedule(() => fetch(input, init));
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/client/get-info.ts
|
|
482
|
+
async function getInfoForStore(args) {
|
|
483
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
484
|
+
const {
|
|
485
|
+
baseUrl,
|
|
486
|
+
storeDomain,
|
|
487
|
+
validateProductExists,
|
|
488
|
+
validateCollectionExists,
|
|
489
|
+
validateLinksInBatches
|
|
490
|
+
} = args;
|
|
491
|
+
const response = await rateLimitedFetch(baseUrl);
|
|
492
|
+
if (!response.ok) {
|
|
493
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
494
|
+
}
|
|
495
|
+
const html = await response.text();
|
|
496
|
+
const getMetaTag = (name2) => {
|
|
497
|
+
const regex = new RegExp(
|
|
498
|
+
`<meta[^>]*name=["']${name2}["'][^>]*content=["'](.*?)["']`
|
|
499
|
+
);
|
|
500
|
+
const match = html.match(regex);
|
|
501
|
+
return match ? match[1] : null;
|
|
502
|
+
};
|
|
503
|
+
const getPropertyMetaTag = (property) => {
|
|
504
|
+
const regex = new RegExp(
|
|
505
|
+
`<meta[^>]*property=["']${property}["'][^>]*content=["'](.*?)["']`
|
|
506
|
+
);
|
|
507
|
+
const match = html.match(regex);
|
|
508
|
+
return match ? match[1] : null;
|
|
509
|
+
};
|
|
510
|
+
const name = (_a = getMetaTag("og:site_name")) != null ? _a : extractDomainWithoutSuffix(baseUrl);
|
|
511
|
+
const title = (_b = getMetaTag("og:title")) != null ? _b : getMetaTag("twitter:title");
|
|
512
|
+
const description = getMetaTag("description") || getPropertyMetaTag("og:description");
|
|
513
|
+
const shopifyWalletId = (_c = getMetaTag("shopify-digital-wallet")) == null ? void 0 : _c.split("/")[1];
|
|
514
|
+
const myShopifySubdomainMatch = html.match(/['"](.*?\.myshopify\.com)['"]/);
|
|
515
|
+
const myShopifySubdomain = myShopifySubdomainMatch ? myShopifySubdomainMatch[1] : null;
|
|
516
|
+
let logoUrl = getPropertyMetaTag("og:image") || getPropertyMetaTag("og:image:secure_url");
|
|
517
|
+
if (!logoUrl) {
|
|
518
|
+
const logoMatch = html.match(
|
|
519
|
+
/<img[^>]+src=["']([^"']+\/cdn\/shop\/[^"']+)["']/
|
|
520
|
+
);
|
|
521
|
+
const matchedUrl = logoMatch == null ? void 0 : logoMatch[1];
|
|
522
|
+
logoUrl = matchedUrl ? matchedUrl.replace("http://", "https://") : null;
|
|
523
|
+
} else {
|
|
524
|
+
logoUrl = logoUrl.replace("http://", "https://");
|
|
525
|
+
}
|
|
526
|
+
const socialLinks = {};
|
|
527
|
+
const socialRegex = /<a[^>]+href=["']([^"']*(?:facebook|twitter|instagram|pinterest|youtube|linkedin|tiktok|vimeo)\.com[^"']*)["']/g;
|
|
528
|
+
for (const match of html.matchAll(socialRegex)) {
|
|
529
|
+
const str = match[1];
|
|
530
|
+
if (!str) continue;
|
|
531
|
+
let href = str;
|
|
532
|
+
try {
|
|
533
|
+
if (href.startsWith("//")) {
|
|
534
|
+
href = `https:${href}`;
|
|
535
|
+
} else if (href.startsWith("/")) {
|
|
536
|
+
href = new URL(href, baseUrl).toString();
|
|
537
|
+
}
|
|
538
|
+
const parsed = new URL(href);
|
|
539
|
+
const domain = parsed.hostname.replace("www.", "").split(".")[0];
|
|
540
|
+
if (domain) {
|
|
541
|
+
socialLinks[domain] = parsed.toString();
|
|
542
|
+
}
|
|
543
|
+
} catch {
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const contactLinks = {
|
|
547
|
+
tel: null,
|
|
548
|
+
email: null,
|
|
549
|
+
contactPage: null
|
|
550
|
+
};
|
|
551
|
+
for (const match of html.matchAll(/href=["']tel:([^"']+)["']/g)) {
|
|
552
|
+
contactLinks.tel = ((_d = match == null ? void 0 : match[1]) == null ? void 0 : _d.trim()) || null;
|
|
553
|
+
}
|
|
554
|
+
for (const match of html.matchAll(/href=["']mailto:([^"']+)["']/g)) {
|
|
555
|
+
contactLinks.email = ((_e = match == null ? void 0 : match[1]) == null ? void 0 : _e.trim()) || null;
|
|
556
|
+
}
|
|
557
|
+
for (const match of html.matchAll(
|
|
558
|
+
/href=["']([^"']*(?:\/contact|\/pages\/contact)[^"']*)["']/g
|
|
559
|
+
)) {
|
|
560
|
+
contactLinks.contactPage = (match == null ? void 0 : match[1]) || null;
|
|
561
|
+
}
|
|
562
|
+
const extractedProductLinks = ((_g = (_f = html.match(/href=["']([^"']*\/products\/[^"']+)["']/g)) == null ? void 0 : _f.map(
|
|
563
|
+
(match) => {
|
|
564
|
+
var _a2, _b2;
|
|
565
|
+
return (_b2 = (_a2 = match == null ? void 0 : match.split("href=")[1]) == null ? void 0 : _a2.replace(/["']/g, "")) == null ? void 0 : _b2.split("/").at(-1);
|
|
566
|
+
}
|
|
567
|
+
)) == null ? void 0 : _g.filter(Boolean)) || [];
|
|
568
|
+
const extractedCollectionLinks = ((_i = (_h = html.match(/href=["']([^"']*\/collections\/[^"']+)["']/g)) == null ? void 0 : _h.map(
|
|
569
|
+
(match) => {
|
|
570
|
+
var _a2, _b2;
|
|
571
|
+
return (_b2 = (_a2 = match == null ? void 0 : match.split("href=")[1]) == null ? void 0 : _a2.replace(/["']/g, "")) == null ? void 0 : _b2.split("/").at(-1);
|
|
572
|
+
}
|
|
573
|
+
)) == null ? void 0 : _i.filter(Boolean)) || [];
|
|
574
|
+
const headerLinks = (_k = (_j = html.match(
|
|
575
|
+
/<(header|nav|div|section)\b[^>]*\b(?:id|class)=["'][^"']*(?=.*shopify-section)(?=.*\b(header|navigation|nav|menu)\b)[^"']*["'][^>]*>[\s\S]*?<\/\1>/gi
|
|
576
|
+
)) == null ? void 0 : _j.flatMap((header) => {
|
|
577
|
+
var _a2, _b2;
|
|
578
|
+
const links = (_a2 = header.match(/href=["']([^"']+)["']/g)) == null ? void 0 : _a2.filter(
|
|
579
|
+
(link) => link.includes("/products/") || link.includes("/collections/") || link.includes("/pages/")
|
|
580
|
+
);
|
|
581
|
+
return (_b2 = links == null ? void 0 : links.map((link) => {
|
|
582
|
+
var _a3;
|
|
583
|
+
const href = (_a3 = link.match(/href=["']([^"']+)["']/)) == null ? void 0 : _a3[1];
|
|
584
|
+
if (href && !href.startsWith("#") && !href.startsWith("javascript:")) {
|
|
585
|
+
try {
|
|
586
|
+
const url = new URL(href, storeDomain);
|
|
587
|
+
return url.pathname.replace(/^\/|\/$/g, "");
|
|
588
|
+
} catch {
|
|
589
|
+
return href.replace(/^\/|\/$/g, "");
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return null;
|
|
593
|
+
}).filter((item) => Boolean(item))) != null ? _b2 : [];
|
|
594
|
+
})) != null ? _k : [];
|
|
595
|
+
const slug = generateStoreSlug(baseUrl);
|
|
596
|
+
const countryDetection = await detectShopifyCountry(html);
|
|
597
|
+
const [homePageProductLinks, homePageCollectionLinks] = await Promise.all([
|
|
598
|
+
validateLinksInBatches(
|
|
599
|
+
extractedProductLinks.filter(
|
|
600
|
+
(handle) => Boolean(handle)
|
|
601
|
+
),
|
|
602
|
+
(handle) => validateProductExists(handle)
|
|
603
|
+
),
|
|
604
|
+
validateLinksInBatches(
|
|
605
|
+
extractedCollectionLinks.filter(
|
|
606
|
+
(handle) => Boolean(handle)
|
|
607
|
+
),
|
|
608
|
+
(handle) => validateCollectionExists(handle)
|
|
609
|
+
)
|
|
610
|
+
]);
|
|
611
|
+
const info = {
|
|
612
|
+
name: name || slug,
|
|
613
|
+
domain: sanitizeDomain(baseUrl),
|
|
614
|
+
slug,
|
|
615
|
+
title: title || null,
|
|
616
|
+
description: description || null,
|
|
617
|
+
logoUrl,
|
|
618
|
+
socialLinks,
|
|
619
|
+
contactLinks,
|
|
620
|
+
headerLinks,
|
|
621
|
+
showcase: {
|
|
622
|
+
products: (0, import_remeda.unique)(homePageProductLinks != null ? homePageProductLinks : []),
|
|
623
|
+
collections: (0, import_remeda.unique)(homePageCollectionLinks != null ? homePageCollectionLinks : [])
|
|
624
|
+
},
|
|
625
|
+
jsonLdData: ((_m = (_l = html.match(
|
|
626
|
+
/<script[^>]*type="application\/ld\+json"[^>]*>([^<]+)<\/script>/g
|
|
627
|
+
)) == null ? void 0 : _l.map(
|
|
628
|
+
(match) => {
|
|
629
|
+
var _a2;
|
|
630
|
+
return ((_a2 = match == null ? void 0 : match.split(">")[1]) == null ? void 0 : _a2.replace(/<\/script/g, "")) || null;
|
|
631
|
+
}
|
|
632
|
+
)) == null ? void 0 : _m.map((json) => json ? JSON.parse(json) : null)) || [],
|
|
633
|
+
techProvider: {
|
|
634
|
+
name: "shopify",
|
|
635
|
+
walletId: shopifyWalletId,
|
|
636
|
+
subDomain: myShopifySubdomain != null ? myShopifySubdomain : null
|
|
637
|
+
},
|
|
638
|
+
country: countryDetection.country
|
|
639
|
+
};
|
|
640
|
+
const currencyCode = countryDetection == null ? void 0 : countryDetection.currencyCode;
|
|
641
|
+
return { info, currencyCode };
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// src/store.ts
|
|
645
|
+
function createStoreOperations(context) {
|
|
646
|
+
return {
|
|
647
|
+
/**
|
|
648
|
+
* Fetches comprehensive store information including metadata, social links, and showcase content.
|
|
649
|
+
*
|
|
650
|
+
* @returns {Promise<StoreInfo>} Store information object containing:
|
|
651
|
+
* - `name` - Store name from meta tags or domain
|
|
652
|
+
* - `domain` - Store domain URL
|
|
653
|
+
* - `slug` - Generated store slug
|
|
654
|
+
* - `title` - Store title from meta tags
|
|
655
|
+
* - `description` - Store description from meta tags
|
|
656
|
+
* - `logoUrl` - Store logo URL from Open Graph or CDN
|
|
657
|
+
* - `socialLinks` - Object with social media links (facebook, twitter, instagram, etc.)
|
|
658
|
+
* - `contactLinks` - Object with contact information (tel, email, contactPage)
|
|
659
|
+
* - `headerLinks` - Array of navigation links from header
|
|
660
|
+
* - `showcase` - Object with featured products and collections from homepage
|
|
661
|
+
* - `jsonLdData` - Structured data from JSON-LD scripts
|
|
662
|
+
* - `techProvider` - Shopify-specific information (walletId, subDomain)
|
|
663
|
+
* - `country` - Country detection results with ISO 3166-1 alpha-2 codes (e.g., "US", "GB")
|
|
664
|
+
*
|
|
665
|
+
* @throws {Error} When the store URL is unreachable or returns an error
|
|
666
|
+
*
|
|
667
|
+
* @example
|
|
668
|
+
* ```typescript
|
|
669
|
+
* const shop = new ShopClient('https://exampleshop.com');
|
|
670
|
+
* const storeInfo = await shop.getInfo();
|
|
671
|
+
*
|
|
672
|
+
* console.log(storeInfo.name); // "Example Store"
|
|
673
|
+
* console.log(storeInfo.socialLinks.instagram); // "https://instagram.com/example"
|
|
674
|
+
* console.log(storeInfo.showcase.products); // ["product-handle-1", "product-handle-2"]
|
|
675
|
+
* console.log(storeInfo.country); // "US"
|
|
676
|
+
* ```
|
|
677
|
+
*/
|
|
678
|
+
info: async () => {
|
|
679
|
+
try {
|
|
680
|
+
const { info } = await getInfoForStore({
|
|
681
|
+
baseUrl: context.baseUrl,
|
|
682
|
+
storeDomain: context.storeDomain,
|
|
683
|
+
validateProductExists: context.validateProductExists,
|
|
684
|
+
validateCollectionExists: context.validateCollectionExists,
|
|
685
|
+
validateLinksInBatches: context.validateLinksInBatches
|
|
686
|
+
});
|
|
687
|
+
return info;
|
|
688
|
+
} catch (error) {
|
|
689
|
+
context.handleFetchError(error, "fetching store info", context.baseUrl);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
695
|
+
0 && (module.exports = {
|
|
696
|
+
createStoreOperations
|
|
697
|
+
});
|
|
698
|
+
//# sourceMappingURL=store.js.map
|