brainerce 1.46.2 → 1.47.3
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 +215 -34
- package/dist/bot/bootstrap.global.js +6 -6
- package/dist/bot/index.d.mts +26 -0
- package/dist/bot/index.d.ts +26 -0
- package/dist/bot/index.js +32 -1
- package/dist/bot/index.mjs +32 -1
- package/dist/index.d.mts +432 -16
- package/dist/index.d.ts +432 -16
- package/dist/index.js +562 -15
- package/dist/index.mjs +553 -15
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -585,6 +585,7 @@ var BrainerceClient = class {
|
|
|
585
585
|
this.proxyMode = options.proxyMode || false;
|
|
586
586
|
this.analyticsBaseUrl = this.resolveAnalyticsBaseUrl(options.analyticsBaseUrl, resolvedBase);
|
|
587
587
|
this.onAuthError = options.onAuthError;
|
|
588
|
+
this.onCartReset = options.onCartReset;
|
|
588
589
|
this.hydrateSessionCart();
|
|
589
590
|
this.detectRecoverCartFromUrl();
|
|
590
591
|
}
|
|
@@ -1244,6 +1245,34 @@ var BrainerceClient = class {
|
|
|
1244
1245
|
}
|
|
1245
1246
|
throw new BrainerceError("getCategories is only available in vibe-coded mode", 400);
|
|
1246
1247
|
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Get one category by slug — the payload for a storefront category
|
|
1250
|
+
* (collection) landing page: name, description HTML, meta, breadcrumb and
|
|
1251
|
+
* product count. Fetch the products themselves with
|
|
1252
|
+
* `getProducts({ categories: [category.id] })`. Vibe-coded mode only, like
|
|
1253
|
+
* {@link getCategories}.
|
|
1254
|
+
*
|
|
1255
|
+
* @example
|
|
1256
|
+
* ```typescript
|
|
1257
|
+
* const category = await client.getCategoryBySlug('running-shoes').catch(() => null);
|
|
1258
|
+
* if (!category) notFound();
|
|
1259
|
+
* const { data: products } = await client.getProducts({ categories: [category.id] });
|
|
1260
|
+
* ```
|
|
1261
|
+
*/
|
|
1262
|
+
async getCategoryBySlug(slug, options) {
|
|
1263
|
+
const headerOverrides = options?.locale ? { "Accept-Language": options.locale } : void 0;
|
|
1264
|
+
const encodedSlug = encodePathSegment(slug);
|
|
1265
|
+
if (this.isVibeCodedMode()) {
|
|
1266
|
+
return this.vibeCodedRequest(
|
|
1267
|
+
"GET",
|
|
1268
|
+
`/categories/slug/${encodedSlug}`,
|
|
1269
|
+
void 0,
|
|
1270
|
+
void 0,
|
|
1271
|
+
headerOverrides
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
throw new BrainerceError("getCategoryBySlug is only available in vibe-coded mode", 400);
|
|
1275
|
+
}
|
|
1247
1276
|
/**
|
|
1248
1277
|
* Get available brands for filtering products
|
|
1249
1278
|
* Works in vibe-coded mode
|
|
@@ -4155,14 +4184,17 @@ var BrainerceClient = class {
|
|
|
4155
4184
|
const migrated = await this.migrateLocalCartToSession();
|
|
4156
4185
|
if (migrated) return migrated;
|
|
4157
4186
|
if (this.sessionCartId && this.sessionToken) {
|
|
4187
|
+
const previousCartId = this.sessionCartId;
|
|
4158
4188
|
try {
|
|
4159
4189
|
const cart2 = await this.getCart(this.sessionCartId);
|
|
4160
4190
|
if (cart2.status === "ACTIVE") {
|
|
4161
4191
|
return cart2;
|
|
4162
4192
|
}
|
|
4163
4193
|
this.clearSessionCart();
|
|
4194
|
+
this.onCartReset?.({ previousCartId, reason: "not_active" });
|
|
4164
4195
|
} catch {
|
|
4165
4196
|
this.clearSessionCart();
|
|
4197
|
+
this.onCartReset?.({ previousCartId, reason: "not_found" });
|
|
4166
4198
|
}
|
|
4167
4199
|
}
|
|
4168
4200
|
if (this.sessionToken) {
|
|
@@ -6391,20 +6423,23 @@ var BrainerceClient = class {
|
|
|
6391
6423
|
);
|
|
6392
6424
|
}
|
|
6393
6425
|
// -------------------- Loyalty --------------------
|
|
6394
|
-
//
|
|
6395
|
-
//
|
|
6396
|
-
//
|
|
6426
|
+
// Available in both storefront mode (storeId + customerToken) and
|
|
6427
|
+
// vibe-coded mode (salesChannelId + customerToken) — Phase 5 added the
|
|
6428
|
+
// /api/vc/:connectionId/loyalty/* routes mirroring /api/stores/:storeId/loyalty.
|
|
6397
6429
|
/**
|
|
6398
6430
|
* Get the logged-in customer's loyalty status: enrollment, points balance,
|
|
6399
|
-
* lifetime earned,
|
|
6400
|
-
*
|
|
6401
|
-
*
|
|
6431
|
+
* lifetime earned, the program's display config, earned milestone `badges`,
|
|
6432
|
+
* and the `paidMembership` subscription state (null for free members).
|
|
6433
|
+
* Requires customerToken. Only available in storefront mode. `program` is
|
|
6434
|
+
* null when the store has no loyalty program.
|
|
6402
6435
|
*
|
|
6403
6436
|
* @example
|
|
6404
6437
|
* ```typescript
|
|
6405
6438
|
* client.setCustomerToken(auth.token);
|
|
6406
6439
|
* const status = await client.getLoyaltyStatus();
|
|
6407
6440
|
* if (status.enrolled) console.log(`${status.pointsBalance} ${status.program?.pointsName}`);
|
|
6441
|
+
* status.badges?.forEach((b) => console.log(`🏅 ${b.name}`));
|
|
6442
|
+
* if (status.paidMembership?.status === 'ACTIVE') showPremiumPerks(status.paidMembership.plan);
|
|
6408
6443
|
* ```
|
|
6409
6444
|
*/
|
|
6410
6445
|
async getLoyaltyStatus() {
|
|
@@ -6414,10 +6449,16 @@ var BrainerceClient = class {
|
|
|
6414
6449
|
401
|
|
6415
6450
|
);
|
|
6416
6451
|
}
|
|
6452
|
+
if (this.isVibeCodedMode()) {
|
|
6453
|
+
return this.vibeCodedRequest("GET", "/loyalty/me");
|
|
6454
|
+
}
|
|
6417
6455
|
if (this.storeId && !this.apiKey) {
|
|
6418
6456
|
return this.storefrontRequest("GET", "/loyalty/me");
|
|
6419
6457
|
}
|
|
6420
|
-
throw new BrainerceError(
|
|
6458
|
+
throw new BrainerceError(
|
|
6459
|
+
"getLoyaltyStatus is only available in vibe-coded or storefront mode",
|
|
6460
|
+
400
|
|
6461
|
+
);
|
|
6421
6462
|
}
|
|
6422
6463
|
/**
|
|
6423
6464
|
* Enroll the logged-in customer in the store's loyalty program (requires
|
|
@@ -6436,10 +6477,16 @@ var BrainerceClient = class {
|
|
|
6436
6477
|
401
|
|
6437
6478
|
);
|
|
6438
6479
|
}
|
|
6480
|
+
if (this.isVibeCodedMode()) {
|
|
6481
|
+
return this.vibeCodedRequest("POST", "/loyalty/enroll");
|
|
6482
|
+
}
|
|
6439
6483
|
if (this.storeId && !this.apiKey) {
|
|
6440
6484
|
return this.storefrontRequest("POST", "/loyalty/enroll");
|
|
6441
6485
|
}
|
|
6442
|
-
throw new BrainerceError(
|
|
6486
|
+
throw new BrainerceError(
|
|
6487
|
+
"enrollInLoyalty is only available in vibe-coded or storefront mode",
|
|
6488
|
+
400
|
|
6489
|
+
);
|
|
6443
6490
|
}
|
|
6444
6491
|
/**
|
|
6445
6492
|
* List the rewards the customer can redeem points for (active rewards, cheapest
|
|
@@ -6458,10 +6505,16 @@ var BrainerceClient = class {
|
|
|
6458
6505
|
401
|
|
6459
6506
|
);
|
|
6460
6507
|
}
|
|
6508
|
+
if (this.isVibeCodedMode()) {
|
|
6509
|
+
return this.vibeCodedRequest("GET", "/loyalty/rewards/available");
|
|
6510
|
+
}
|
|
6461
6511
|
if (this.storeId && !this.apiKey) {
|
|
6462
6512
|
return this.storefrontRequest("GET", "/loyalty/rewards/available");
|
|
6463
6513
|
}
|
|
6464
|
-
throw new BrainerceError(
|
|
6514
|
+
throw new BrainerceError(
|
|
6515
|
+
"getAvailableRewards is only available in vibe-coded or storefront mode",
|
|
6516
|
+
400
|
|
6517
|
+
);
|
|
6465
6518
|
}
|
|
6466
6519
|
/**
|
|
6467
6520
|
* Redeem a reward: spends the customer's points and mints a one-time coupon
|
|
@@ -6481,10 +6534,16 @@ var BrainerceClient = class {
|
|
|
6481
6534
|
401
|
|
6482
6535
|
);
|
|
6483
6536
|
}
|
|
6537
|
+
if (this.isVibeCodedMode()) {
|
|
6538
|
+
return this.vibeCodedRequest("POST", "/loyalty/redeem", { rewardId });
|
|
6539
|
+
}
|
|
6484
6540
|
if (this.storeId && !this.apiKey) {
|
|
6485
6541
|
return this.storefrontRequest("POST", "/loyalty/redeem", { rewardId });
|
|
6486
6542
|
}
|
|
6487
|
-
throw new BrainerceError(
|
|
6543
|
+
throw new BrainerceError(
|
|
6544
|
+
"redeemLoyaltyReward is only available in vibe-coded or storefront mode",
|
|
6545
|
+
400
|
|
6546
|
+
);
|
|
6488
6547
|
}
|
|
6489
6548
|
/**
|
|
6490
6549
|
* Report a social share, granting the SOCIAL_SHARE earning-rule bonus if the
|
|
@@ -6504,6 +6563,13 @@ var BrainerceClient = class {
|
|
|
6504
6563
|
401
|
|
6505
6564
|
);
|
|
6506
6565
|
}
|
|
6566
|
+
if (this.isVibeCodedMode()) {
|
|
6567
|
+
return this.vibeCodedRequest(
|
|
6568
|
+
"POST",
|
|
6569
|
+
"/loyalty/social-share",
|
|
6570
|
+
platform ? { platform } : {}
|
|
6571
|
+
);
|
|
6572
|
+
}
|
|
6507
6573
|
if (this.storeId && !this.apiKey) {
|
|
6508
6574
|
return this.storefrontRequest(
|
|
6509
6575
|
"POST",
|
|
@@ -6511,7 +6577,10 @@ var BrainerceClient = class {
|
|
|
6511
6577
|
platform ? { platform } : {}
|
|
6512
6578
|
);
|
|
6513
6579
|
}
|
|
6514
|
-
throw new BrainerceError(
|
|
6580
|
+
throw new BrainerceError(
|
|
6581
|
+
"reportSocialShare is only available in vibe-coded or storefront mode",
|
|
6582
|
+
400
|
|
6583
|
+
);
|
|
6515
6584
|
}
|
|
6516
6585
|
/**
|
|
6517
6586
|
* Public referral-link lookup: who referred the visitor and what welcome
|
|
@@ -6530,12 +6599,231 @@ var BrainerceClient = class {
|
|
|
6530
6599
|
* ```
|
|
6531
6600
|
*/
|
|
6532
6601
|
async getReferralInfo(code) {
|
|
6602
|
+
if (this.isVibeCodedMode()) {
|
|
6603
|
+
return this.vibeCodedRequest("GET", "/loyalty/referral-info", void 0, {
|
|
6604
|
+
code
|
|
6605
|
+
});
|
|
6606
|
+
}
|
|
6533
6607
|
if (this.storeId && !this.apiKey) {
|
|
6534
6608
|
return this.storefrontRequest("GET", "/loyalty/referral-info", void 0, {
|
|
6535
6609
|
code
|
|
6536
6610
|
});
|
|
6537
6611
|
}
|
|
6538
|
-
throw new BrainerceError(
|
|
6612
|
+
throw new BrainerceError(
|
|
6613
|
+
"getReferralInfo is only available in vibe-coded or storefront mode",
|
|
6614
|
+
400
|
|
6615
|
+
);
|
|
6616
|
+
}
|
|
6617
|
+
/**
|
|
6618
|
+
* AI-recommended reward for the logged-in customer — "recommended for you"
|
|
6619
|
+
* at the top of the rewards list (requires customerToken). The result is
|
|
6620
|
+
* ALWAYS a real reward from the store's catalog (the AI only ranks; a
|
|
6621
|
+
* hallucinated pick falls back to a deterministic choice). Returns
|
|
6622
|
+
* `{ reward: null }` when the catalog is empty. Rate-limited (5/min per
|
|
6623
|
+
* customer — it spends the merchant's AI credits). Only available in
|
|
6624
|
+
* storefront mode.
|
|
6625
|
+
*
|
|
6626
|
+
* @example
|
|
6627
|
+
* ```typescript
|
|
6628
|
+
* const { reward, reason } = await client.getRecommendedReward();
|
|
6629
|
+
* if (reward) showRecommendation(reward, reason);
|
|
6630
|
+
* ```
|
|
6631
|
+
*/
|
|
6632
|
+
async getRecommendedReward() {
|
|
6633
|
+
if (!this.customerToken && !this.proxyMode) {
|
|
6634
|
+
throw new BrainerceError(
|
|
6635
|
+
"Customer token required. Call setCustomerToken() after login.",
|
|
6636
|
+
401
|
|
6637
|
+
);
|
|
6638
|
+
}
|
|
6639
|
+
if (this.isVibeCodedMode()) {
|
|
6640
|
+
return this.vibeCodedRequest(
|
|
6641
|
+
"GET",
|
|
6642
|
+
"/loyalty/rewards/recommended"
|
|
6643
|
+
);
|
|
6644
|
+
}
|
|
6645
|
+
if (this.storeId && !this.apiKey) {
|
|
6646
|
+
return this.storefrontRequest(
|
|
6647
|
+
"GET",
|
|
6648
|
+
"/loyalty/rewards/recommended"
|
|
6649
|
+
);
|
|
6650
|
+
}
|
|
6651
|
+
throw new BrainerceError(
|
|
6652
|
+
"getRecommendedReward is only available in vibe-coded or storefront mode",
|
|
6653
|
+
400
|
|
6654
|
+
);
|
|
6655
|
+
}
|
|
6656
|
+
/**
|
|
6657
|
+
* List the paid membership plans the customer can subscribe to (requires
|
|
6658
|
+
* customerToken). Empty when the store offers none or the program is not
|
|
6659
|
+
* active. Only available in storefront mode.
|
|
6660
|
+
*
|
|
6661
|
+
* @example
|
|
6662
|
+
* ```typescript
|
|
6663
|
+
* const plans = await client.getMembershipPlans();
|
|
6664
|
+
* ```
|
|
6665
|
+
*/
|
|
6666
|
+
async getMembershipPlans() {
|
|
6667
|
+
if (!this.customerToken && !this.proxyMode) {
|
|
6668
|
+
throw new BrainerceError(
|
|
6669
|
+
"Customer token required. Call setCustomerToken() after login.",
|
|
6670
|
+
401
|
|
6671
|
+
);
|
|
6672
|
+
}
|
|
6673
|
+
if (this.isVibeCodedMode()) {
|
|
6674
|
+
return this.vibeCodedRequest("GET", "/loyalty/membership/plans");
|
|
6675
|
+
}
|
|
6676
|
+
if (this.storeId && !this.apiKey) {
|
|
6677
|
+
return this.storefrontRequest("GET", "/loyalty/membership/plans");
|
|
6678
|
+
}
|
|
6679
|
+
throw new BrainerceError(
|
|
6680
|
+
"getMembershipPlans is only available in vibe-coded or storefront mode",
|
|
6681
|
+
400
|
|
6682
|
+
);
|
|
6683
|
+
}
|
|
6684
|
+
/**
|
|
6685
|
+
* List the customer's saved payment methods (display fields only — brand /
|
|
6686
|
+
* last4 / expiry, never card data) for the membership subscribe flow.
|
|
6687
|
+
* Requires customerToken. Cards are vaulted by checking out with
|
|
6688
|
+
* `saveCard: true`. Only available in storefront mode.
|
|
6689
|
+
*
|
|
6690
|
+
* @example
|
|
6691
|
+
* ```typescript
|
|
6692
|
+
* const methods = await client.getMySavedPaymentMethods();
|
|
6693
|
+
* ```
|
|
6694
|
+
*/
|
|
6695
|
+
async getMySavedPaymentMethods() {
|
|
6696
|
+
if (!this.customerToken && !this.proxyMode) {
|
|
6697
|
+
throw new BrainerceError(
|
|
6698
|
+
"Customer token required. Call setCustomerToken() after login.",
|
|
6699
|
+
401
|
|
6700
|
+
);
|
|
6701
|
+
}
|
|
6702
|
+
if (this.isVibeCodedMode()) {
|
|
6703
|
+
return this.vibeCodedRequest(
|
|
6704
|
+
"GET",
|
|
6705
|
+
"/loyalty/membership/payment-methods"
|
|
6706
|
+
);
|
|
6707
|
+
}
|
|
6708
|
+
if (this.storeId && !this.apiKey) {
|
|
6709
|
+
return this.storefrontRequest(
|
|
6710
|
+
"GET",
|
|
6711
|
+
"/loyalty/membership/payment-methods"
|
|
6712
|
+
);
|
|
6713
|
+
}
|
|
6714
|
+
throw new BrainerceError(
|
|
6715
|
+
"getMySavedPaymentMethods is only available in vibe-coded or storefront mode",
|
|
6716
|
+
400
|
|
6717
|
+
);
|
|
6718
|
+
}
|
|
6719
|
+
/**
|
|
6720
|
+
* Subscribe the customer to a paid membership plan — charges the saved card
|
|
6721
|
+
* IMMEDIATELY and starts the recurring cycle (requires customerToken).
|
|
6722
|
+
* Throws a 409 with a `code` ('card_declined' | 'requires_action' | ...)
|
|
6723
|
+
* when the charge fails; 3D-Secure challenges are not supported off-session
|
|
6724
|
+
* and surface as `requires_action`. Only available in storefront mode.
|
|
6725
|
+
*
|
|
6726
|
+
* @example
|
|
6727
|
+
* ```typescript
|
|
6728
|
+
* const membership = await client.subscribeToMembership({
|
|
6729
|
+
* planId: plan.id,
|
|
6730
|
+
* savedPaymentTokenId: method.id,
|
|
6731
|
+
* });
|
|
6732
|
+
* // membership.status === 'ACTIVE'
|
|
6733
|
+
* ```
|
|
6734
|
+
*/
|
|
6735
|
+
async subscribeToMembership(params) {
|
|
6736
|
+
if (!this.customerToken && !this.proxyMode) {
|
|
6737
|
+
throw new BrainerceError(
|
|
6738
|
+
"Customer token required. Call setCustomerToken() after login.",
|
|
6739
|
+
401
|
|
6740
|
+
);
|
|
6741
|
+
}
|
|
6742
|
+
if (this.isVibeCodedMode()) {
|
|
6743
|
+
return this.vibeCodedRequest(
|
|
6744
|
+
"POST",
|
|
6745
|
+
"/loyalty/membership/subscribe",
|
|
6746
|
+
params
|
|
6747
|
+
);
|
|
6748
|
+
}
|
|
6749
|
+
if (this.storeId && !this.apiKey) {
|
|
6750
|
+
return this.storefrontRequest(
|
|
6751
|
+
"POST",
|
|
6752
|
+
"/loyalty/membership/subscribe",
|
|
6753
|
+
params
|
|
6754
|
+
);
|
|
6755
|
+
}
|
|
6756
|
+
throw new BrainerceError(
|
|
6757
|
+
"subscribeToMembership is only available in vibe-coded or storefront mode",
|
|
6758
|
+
400
|
|
6759
|
+
);
|
|
6760
|
+
}
|
|
6761
|
+
/**
|
|
6762
|
+
* Cancel the customer's paid membership (requires customerToken).
|
|
6763
|
+
* End-of-period semantics: perks continue until `nextBillingAt`, then the
|
|
6764
|
+
* subscription ends without another charge (a PAST_DUE membership cancels
|
|
6765
|
+
* immediately). Re-subscribing to the same plan before period end simply
|
|
6766
|
+
* un-cancels. Only available in storefront mode.
|
|
6767
|
+
*
|
|
6768
|
+
* @example
|
|
6769
|
+
* ```typescript
|
|
6770
|
+
* const membership = await client.cancelMembership();
|
|
6771
|
+
* // membership.cancelAtPeriodEnd === true
|
|
6772
|
+
* ```
|
|
6773
|
+
*/
|
|
6774
|
+
async cancelMembership() {
|
|
6775
|
+
if (!this.customerToken && !this.proxyMode) {
|
|
6776
|
+
throw new BrainerceError(
|
|
6777
|
+
"Customer token required. Call setCustomerToken() after login.",
|
|
6778
|
+
401
|
|
6779
|
+
);
|
|
6780
|
+
}
|
|
6781
|
+
if (this.isVibeCodedMode()) {
|
|
6782
|
+
return this.vibeCodedRequest("POST", "/loyalty/membership/cancel");
|
|
6783
|
+
}
|
|
6784
|
+
if (this.storeId && !this.apiKey) {
|
|
6785
|
+
return this.storefrontRequest("POST", "/loyalty/membership/cancel");
|
|
6786
|
+
}
|
|
6787
|
+
throw new BrainerceError(
|
|
6788
|
+
"cancelMembership is only available in vibe-coded or storefront mode",
|
|
6789
|
+
400
|
|
6790
|
+
);
|
|
6791
|
+
}
|
|
6792
|
+
/**
|
|
6793
|
+
* Mint a short-lived session for the embeddable loyalty widget (Phase 5) and
|
|
6794
|
+
* return the ready-to-use iframe URL. Requires customerToken. The returned
|
|
6795
|
+
* `embedUrl` is safe to drop straight into an `<iframe src>` — it carries a
|
|
6796
|
+
* scoped ~15-minute session token, never the real customerToken. Re-call this
|
|
6797
|
+
* before the iframe reloads (e.g. on page navigation) to refresh it.
|
|
6798
|
+
*
|
|
6799
|
+
* @example
|
|
6800
|
+
* ```typescript
|
|
6801
|
+
* const { embedUrl } = await client.getLoyaltyWidgetSession();
|
|
6802
|
+
* // <iframe src={embedUrl} width="360" height="420" />
|
|
6803
|
+
* ```
|
|
6804
|
+
*/
|
|
6805
|
+
async getLoyaltyWidgetSession() {
|
|
6806
|
+
if (!this.customerToken && !this.proxyMode) {
|
|
6807
|
+
throw new BrainerceError(
|
|
6808
|
+
"Customer token required. Call setCustomerToken() after login.",
|
|
6809
|
+
401
|
|
6810
|
+
);
|
|
6811
|
+
}
|
|
6812
|
+
let result;
|
|
6813
|
+
if (this.isVibeCodedMode()) {
|
|
6814
|
+
result = await this.vibeCodedRequest("POST", "/loyalty/widget-session");
|
|
6815
|
+
} else if (this.storeId && !this.apiKey) {
|
|
6816
|
+
result = await this.storefrontRequest("POST", "/loyalty/widget-session");
|
|
6817
|
+
} else {
|
|
6818
|
+
throw new BrainerceError(
|
|
6819
|
+
"getLoyaltyWidgetSession is only available in vibe-coded or storefront mode",
|
|
6820
|
+
400
|
|
6821
|
+
);
|
|
6822
|
+
}
|
|
6823
|
+
return {
|
|
6824
|
+
...result,
|
|
6825
|
+
embedUrl: `${this.baseUrl}/api/loyalty/embed/${encodePathSegment(result.storeId)}/${encodePathSegment(result.sessionId)}`
|
|
6826
|
+
};
|
|
6539
6827
|
}
|
|
6540
6828
|
/**
|
|
6541
6829
|
* Get the current customer's orders (requires customerToken)
|
|
@@ -8822,6 +9110,247 @@ function formatAmount(amount, currency, locale) {
|
|
|
8822
9110
|
}
|
|
8823
9111
|
}
|
|
8824
9112
|
|
|
9113
|
+
// src/jsonld.ts
|
|
9114
|
+
function absoluteUrl(siteUrl, path) {
|
|
9115
|
+
if (!path) return void 0;
|
|
9116
|
+
const base = siteUrl.replace(/\/+$/, "");
|
|
9117
|
+
return path.startsWith("http") ? path : `${base}${path.startsWith("/") ? path : `/${path}`}`;
|
|
9118
|
+
}
|
|
9119
|
+
function stripHtml(html) {
|
|
9120
|
+
return (html ?? "").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
9121
|
+
}
|
|
9122
|
+
function buildArticleJsonLd(post, opts) {
|
|
9123
|
+
const url = absoluteUrl(opts.siteUrl, opts.path ?? `/blog/${post.slug}`);
|
|
9124
|
+
const result = {
|
|
9125
|
+
"@context": "https://schema.org",
|
|
9126
|
+
"@type": "Article",
|
|
9127
|
+
headline: post.seoTitle ?? post.title,
|
|
9128
|
+
name: post.title,
|
|
9129
|
+
...post.excerpt || post.seoDescription ? { description: post.seoDescription ?? post.excerpt } : {},
|
|
9130
|
+
...url ? { url, mainEntityOfPage: { "@type": "WebPage", "@id": url } } : {},
|
|
9131
|
+
...post.coverImageUrl ? { image: [post.coverImageUrl] } : {},
|
|
9132
|
+
...post.publishedAt ? { datePublished: post.publishedAt } : {},
|
|
9133
|
+
dateModified: post.updatedAt,
|
|
9134
|
+
...post.author ? { author: { "@type": "Person", name: post.author } } : opts.organizationName ? { author: { "@type": "Organization", name: opts.organizationName } } : {},
|
|
9135
|
+
...opts.organizationName ? { publisher: { "@type": "Organization", name: opts.organizationName } } : {},
|
|
9136
|
+
...post.tags.length > 0 ? { keywords: post.tags.join(", ") } : {}
|
|
9137
|
+
};
|
|
9138
|
+
return result;
|
|
9139
|
+
}
|
|
9140
|
+
function buildProductJsonLd(product, opts) {
|
|
9141
|
+
const url = absoluteUrl(
|
|
9142
|
+
opts.siteUrl,
|
|
9143
|
+
opts.path ?? (product.slug ? `/products/${product.slug}` : void 0)
|
|
9144
|
+
);
|
|
9145
|
+
const images = (product.images ?? []).map((img) => img.url).filter(Boolean);
|
|
9146
|
+
const brand = opts.brandName ?? product.brands?.[0]?.name;
|
|
9147
|
+
const description = stripHtml(product.description).slice(0, 5e3);
|
|
9148
|
+
const effectivePrice = product.salePrice ?? product.basePrice;
|
|
9149
|
+
const inStock = product.inventory ? (product.inventory.available ?? 0) > 0 : true;
|
|
9150
|
+
const isVariable = product.type === "VARIABLE" && product.priceMin && product.priceMax;
|
|
9151
|
+
const itemCondition = "https://schema.org/NewCondition";
|
|
9152
|
+
const shippingDetails = (opts.shipping ?? []).filter((z) => z.amount !== null).map((z) => ({
|
|
9153
|
+
"@type": "OfferShippingDetails",
|
|
9154
|
+
shippingRate: { "@type": "MonetaryAmount", value: z.amount, currency: opts.currency },
|
|
9155
|
+
shippingDestination: { "@type": "DefinedRegion", addressCountry: z.countries },
|
|
9156
|
+
...z.handlingTime != null || z.minDeliveryDays != null || z.maxDeliveryDays != null ? {
|
|
9157
|
+
deliveryTime: {
|
|
9158
|
+
"@type": "ShippingDeliveryTime",
|
|
9159
|
+
...z.handlingTime != null ? {
|
|
9160
|
+
handlingTime: {
|
|
9161
|
+
"@type": "QuantitativeValue",
|
|
9162
|
+
minValue: 0,
|
|
9163
|
+
maxValue: z.handlingTime
|
|
9164
|
+
}
|
|
9165
|
+
} : {},
|
|
9166
|
+
...z.minDeliveryDays != null || z.maxDeliveryDays != null ? {
|
|
9167
|
+
transitTime: {
|
|
9168
|
+
"@type": "QuantitativeValue",
|
|
9169
|
+
minValue: z.minDeliveryDays ?? z.maxDeliveryDays,
|
|
9170
|
+
maxValue: z.maxDeliveryDays ?? z.minDeliveryDays
|
|
9171
|
+
}
|
|
9172
|
+
} : {}
|
|
9173
|
+
}
|
|
9174
|
+
} : {}
|
|
9175
|
+
}));
|
|
9176
|
+
const offer = isVariable ? {
|
|
9177
|
+
"@type": "AggregateOffer",
|
|
9178
|
+
lowPrice: product.priceMin,
|
|
9179
|
+
highPrice: product.priceMax,
|
|
9180
|
+
priceCurrency: opts.currency,
|
|
9181
|
+
availability: inStock ? "https://schema.org/InStock" : "https://schema.org/OutOfStock",
|
|
9182
|
+
itemCondition,
|
|
9183
|
+
...shippingDetails.length > 0 ? { shippingDetails } : {},
|
|
9184
|
+
...url ? { url } : {}
|
|
9185
|
+
} : {
|
|
9186
|
+
"@type": "Offer",
|
|
9187
|
+
price: effectivePrice,
|
|
9188
|
+
priceCurrency: opts.currency,
|
|
9189
|
+
availability: inStock ? "https://schema.org/InStock" : "https://schema.org/OutOfStock",
|
|
9190
|
+
itemCondition,
|
|
9191
|
+
// Only meaningful for an active sale price with a known end date —
|
|
9192
|
+
// a regular (non-sale) price has no expiry to declare.
|
|
9193
|
+
...product.salePrice && product.salePriceEndsAt ? { priceValidUntil: product.salePriceEndsAt } : {},
|
|
9194
|
+
...shippingDetails.length > 0 ? { shippingDetails } : {},
|
|
9195
|
+
...url ? { url } : {}
|
|
9196
|
+
};
|
|
9197
|
+
return {
|
|
9198
|
+
"@context": "https://schema.org",
|
|
9199
|
+
"@type": "Product",
|
|
9200
|
+
name: product.name,
|
|
9201
|
+
...description ? { description } : {},
|
|
9202
|
+
...images.length > 0 ? { image: images } : {},
|
|
9203
|
+
...url ? { url } : {},
|
|
9204
|
+
sku: product.sku,
|
|
9205
|
+
// Identifiers Google uses to match a product to its catalog — key for
|
|
9206
|
+
// free merchant-listing eligibility without a Merchant Center feed.
|
|
9207
|
+
...product.gtin ? { gtin: product.gtin } : {},
|
|
9208
|
+
...product.mpn ? { mpn: product.mpn } : {},
|
|
9209
|
+
...brand ? { brand: { "@type": "Brand", name: brand } } : {},
|
|
9210
|
+
offers: offer,
|
|
9211
|
+
// Google policy: never emit an empty/zero rating block.
|
|
9212
|
+
...product.reviewCount && product.reviewCount > 0 && product.avgRating ? {
|
|
9213
|
+
aggregateRating: {
|
|
9214
|
+
"@type": "AggregateRating",
|
|
9215
|
+
ratingValue: product.avgRating,
|
|
9216
|
+
reviewCount: product.reviewCount
|
|
9217
|
+
}
|
|
9218
|
+
} : {}
|
|
9219
|
+
};
|
|
9220
|
+
}
|
|
9221
|
+
function buildOrganizationJsonLd(store, opts) {
|
|
9222
|
+
const sameAs = Object.values(store.socialLinks ?? {}).filter(Boolean);
|
|
9223
|
+
return {
|
|
9224
|
+
"@context": "https://schema.org",
|
|
9225
|
+
"@type": "Organization",
|
|
9226
|
+
name: store.name,
|
|
9227
|
+
url: opts.siteUrl,
|
|
9228
|
+
...store.logo ? { logo: store.logo } : {},
|
|
9229
|
+
...store.metaDescription ? { description: store.metaDescription } : {},
|
|
9230
|
+
...store.contactEmail ? { email: store.contactEmail } : {},
|
|
9231
|
+
...store.contactPhone ? { telephone: store.contactPhone } : {},
|
|
9232
|
+
...sameAs.length > 0 ? { sameAs } : {}
|
|
9233
|
+
};
|
|
9234
|
+
}
|
|
9235
|
+
function buildWebsiteJsonLd(store, opts) {
|
|
9236
|
+
const base = opts.siteUrl.replace(/\/+$/, "");
|
|
9237
|
+
const result = {
|
|
9238
|
+
"@context": "https://schema.org",
|
|
9239
|
+
"@type": "WebSite",
|
|
9240
|
+
name: store.name,
|
|
9241
|
+
url: opts.siteUrl
|
|
9242
|
+
};
|
|
9243
|
+
if (opts.searchUrlTemplate?.includes("{search_term_string}")) {
|
|
9244
|
+
const tmpl = opts.searchUrlTemplate.startsWith("http") ? opts.searchUrlTemplate : `${base}${opts.searchUrlTemplate.startsWith("/") ? "" : "/"}${opts.searchUrlTemplate}`;
|
|
9245
|
+
result.potentialAction = {
|
|
9246
|
+
"@type": "SearchAction",
|
|
9247
|
+
target: { "@type": "EntryPoint", urlTemplate: tmpl },
|
|
9248
|
+
"query-input": "required name=search_term_string"
|
|
9249
|
+
};
|
|
9250
|
+
}
|
|
9251
|
+
return result;
|
|
9252
|
+
}
|
|
9253
|
+
function buildCollectionPageJsonLd(category, opts) {
|
|
9254
|
+
const url = absoluteUrl(opts.siteUrl, opts.path);
|
|
9255
|
+
const description = category.metaDescription ?? (stripHtml(category.description) || void 0);
|
|
9256
|
+
return {
|
|
9257
|
+
"@context": "https://schema.org",
|
|
9258
|
+
"@type": "CollectionPage",
|
|
9259
|
+
name: category.name,
|
|
9260
|
+
...description ? { description } : {},
|
|
9261
|
+
...url ? { url, mainEntityOfPage: { "@type": "WebPage", "@id": url } } : {},
|
|
9262
|
+
...category.image ? { image: [category.image] } : {}
|
|
9263
|
+
};
|
|
9264
|
+
}
|
|
9265
|
+
function buildBreadcrumbJsonLd(items) {
|
|
9266
|
+
return {
|
|
9267
|
+
"@context": "https://schema.org",
|
|
9268
|
+
"@type": "BreadcrumbList",
|
|
9269
|
+
itemListElement: items.map((item, index) => ({
|
|
9270
|
+
"@type": "ListItem",
|
|
9271
|
+
position: index + 1,
|
|
9272
|
+
name: item.name,
|
|
9273
|
+
item: item.url
|
|
9274
|
+
}))
|
|
9275
|
+
};
|
|
9276
|
+
}
|
|
9277
|
+
function jsonLdScriptProps(data) {
|
|
9278
|
+
return {
|
|
9279
|
+
type: "application/ld+json",
|
|
9280
|
+
dangerouslySetInnerHTML: { __html: JSON.stringify(data).replace(/</g, "\\u003c") }
|
|
9281
|
+
};
|
|
9282
|
+
}
|
|
9283
|
+
|
|
9284
|
+
// src/sitemap.ts
|
|
9285
|
+
async function getBlogSitemapEntries(client, opts) {
|
|
9286
|
+
const base = opts.siteUrl.replace(/\/+$/, "");
|
|
9287
|
+
const basePath = opts.basePath ?? "/blog";
|
|
9288
|
+
const pageSize = Math.min(opts.pageSize ?? 100, 100);
|
|
9289
|
+
const maxEntries = opts.maxEntries ?? 5e3;
|
|
9290
|
+
const posts = [];
|
|
9291
|
+
let page = 1;
|
|
9292
|
+
for (; ; ) {
|
|
9293
|
+
const res = await client.blog.getPosts({ page, limit: pageSize });
|
|
9294
|
+
posts.push(...res.data.map((p) => ({ slug: p.slug, updatedAt: p.updatedAt })));
|
|
9295
|
+
if (page >= res.meta.totalPages || posts.length >= maxEntries) break;
|
|
9296
|
+
page += 1;
|
|
9297
|
+
}
|
|
9298
|
+
const nonDefaultLocales = opts.locales?.filter((locale) => locale !== opts.defaultLocale) ?? [];
|
|
9299
|
+
const entries = [];
|
|
9300
|
+
entries.push({ url: `${base}${basePath}`, changeFrequency: "daily", priority: 0.7 });
|
|
9301
|
+
for (const locale of nonDefaultLocales) {
|
|
9302
|
+
entries.push({
|
|
9303
|
+
url: `${base}/${locale}${basePath}`,
|
|
9304
|
+
changeFrequency: "daily",
|
|
9305
|
+
priority: 0.7
|
|
9306
|
+
});
|
|
9307
|
+
}
|
|
9308
|
+
for (const post of posts.slice(0, maxEntries)) {
|
|
9309
|
+
const lastModified = new Date(post.updatedAt);
|
|
9310
|
+
entries.push({
|
|
9311
|
+
url: `${base}${basePath}/${post.slug}`,
|
|
9312
|
+
lastModified,
|
|
9313
|
+
changeFrequency: "weekly",
|
|
9314
|
+
priority: 0.6
|
|
9315
|
+
});
|
|
9316
|
+
for (const locale of nonDefaultLocales) {
|
|
9317
|
+
entries.push({
|
|
9318
|
+
url: `${base}/${locale}${basePath}/${post.slug}`,
|
|
9319
|
+
lastModified,
|
|
9320
|
+
changeFrequency: "weekly",
|
|
9321
|
+
priority: 0.5
|
|
9322
|
+
});
|
|
9323
|
+
}
|
|
9324
|
+
}
|
|
9325
|
+
return entries;
|
|
9326
|
+
}
|
|
9327
|
+
async function getCategorySitemapEntries(client, opts) {
|
|
9328
|
+
const base = opts.siteUrl.replace(/\/+$/, "");
|
|
9329
|
+
const basePath = opts.basePath ?? "/category";
|
|
9330
|
+
const nonDefaultLocales = opts.locales?.filter((l) => l !== opts.defaultLocale) ?? [];
|
|
9331
|
+
const { categories } = await client.getCategories();
|
|
9332
|
+
const slugs = [];
|
|
9333
|
+
const walk = (nodes) => {
|
|
9334
|
+
for (const node of nodes) {
|
|
9335
|
+
if (node.slug) slugs.push(node.slug);
|
|
9336
|
+
if (node.children?.length) walk(node.children);
|
|
9337
|
+
}
|
|
9338
|
+
};
|
|
9339
|
+
walk(categories);
|
|
9340
|
+
const entries = [];
|
|
9341
|
+
for (const slug of slugs) {
|
|
9342
|
+
entries.push({ url: `${base}${basePath}/${slug}`, changeFrequency: "daily", priority: 0.8 });
|
|
9343
|
+
for (const locale of nonDefaultLocales) {
|
|
9344
|
+
entries.push({
|
|
9345
|
+
url: `${base}/${locale}${basePath}/${slug}`,
|
|
9346
|
+
changeFrequency: "daily",
|
|
9347
|
+
priority: 0.7
|
|
9348
|
+
});
|
|
9349
|
+
}
|
|
9350
|
+
}
|
|
9351
|
+
return entries;
|
|
9352
|
+
}
|
|
9353
|
+
|
|
8825
9354
|
// src/types.ts
|
|
8826
9355
|
function isHtmlDescription(product) {
|
|
8827
9356
|
if (product?.descriptionFormat === "html") return true;
|
|
@@ -8837,7 +9366,7 @@ function getDescriptionContent(product) {
|
|
|
8837
9366
|
}
|
|
8838
9367
|
return { text: product.description };
|
|
8839
9368
|
}
|
|
8840
|
-
function
|
|
9369
|
+
function stripHtml2(html) {
|
|
8841
9370
|
if (!html) return "";
|
|
8842
9371
|
return html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/ /gi, " ").replace(/&/gi, "&").replace(/</gi, "<").replace(/>/gi, ">").replace(/"/gi, '"').replace(/'/gi, "'").replace(/'/gi, "'").replace(///gi, "/").replace(/\s+/g, " ").trim();
|
|
8843
9372
|
}
|
|
@@ -8846,7 +9375,7 @@ function deriveSeoDescription(product, options) {
|
|
|
8846
9375
|
const maxLength = options?.maxLength ?? 160;
|
|
8847
9376
|
const authored = (product.seoDescription ?? product.metaDescription ?? "").trim();
|
|
8848
9377
|
if (authored) return authored;
|
|
8849
|
-
const plain =
|
|
9378
|
+
const plain = stripHtml2(product.description);
|
|
8850
9379
|
if (plain) {
|
|
8851
9380
|
if (plain.length <= maxLength) return plain;
|
|
8852
9381
|
const sliceLength = maxLength - 1;
|
|
@@ -9067,6 +9596,12 @@ export {
|
|
|
9067
9596
|
BrainerceError,
|
|
9068
9597
|
RTL_LOCALES,
|
|
9069
9598
|
SDK_VERSION,
|
|
9599
|
+
buildArticleJsonLd,
|
|
9600
|
+
buildBreadcrumbJsonLd,
|
|
9601
|
+
buildCollectionPageJsonLd,
|
|
9602
|
+
buildOrganizationJsonLd,
|
|
9603
|
+
buildProductJsonLd,
|
|
9604
|
+
buildWebsiteJsonLd,
|
|
9070
9605
|
createWebhookHandler,
|
|
9071
9606
|
deriveSeoDescription,
|
|
9072
9607
|
enableDevGuards,
|
|
@@ -9074,9 +9609,11 @@ export {
|
|
|
9074
9609
|
formatPrice,
|
|
9075
9610
|
formatProductPrice,
|
|
9076
9611
|
formatVariantPrice,
|
|
9612
|
+
getBlogSitemapEntries,
|
|
9077
9613
|
getCartItemImage,
|
|
9078
9614
|
getCartItemName,
|
|
9079
9615
|
getCartTotals,
|
|
9616
|
+
getCategorySitemapEntries,
|
|
9080
9617
|
getDescriptionContent,
|
|
9081
9618
|
getDirectionForLocale,
|
|
9082
9619
|
formatPrice as getPriceDisplay,
|
|
@@ -9094,8 +9631,9 @@ export {
|
|
|
9094
9631
|
isCouponApplicableToProduct,
|
|
9095
9632
|
isHtmlDescription,
|
|
9096
9633
|
isWebhookEventType,
|
|
9634
|
+
jsonLdScriptProps,
|
|
9097
9635
|
parseWebhookEvent,
|
|
9098
9636
|
safePaymentRedirect,
|
|
9099
|
-
stripHtml,
|
|
9637
|
+
stripHtml2 as stripHtml,
|
|
9100
9638
|
verifyWebhook
|
|
9101
9639
|
};
|