brainerce 1.53.1 → 1.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6255 -6180
- package/dist/bot/bootstrap.global.js +12 -12
- package/dist/bot/index.js +10 -2
- package/dist/bot/index.mjs +10 -2
- package/dist/index.d.mts +223 -5
- package/dist/index.d.ts +223 -5
- package/dist/index.js +286 -6
- package/dist/index.mjs +286 -6
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -12,7 +12,7 @@ var CART_GUARDS = [
|
|
|
12
12
|
var CART_ITEM_GUARDS = [
|
|
13
13
|
{
|
|
14
14
|
property: "name",
|
|
15
|
-
message: 'CartItem has no "name" field. Use item.product.name
|
|
15
|
+
message: 'CartItem has no "name" field. Use getCartItemName(item) for the full\n"Product - Variant" label, or item.product.name for the product name alone.\nImport: import { getCartItemName } from "brainerce";'
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
property: "price",
|
|
@@ -115,11 +115,29 @@ function isDevGuardsEnabled() {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
// src/version.ts
|
|
118
|
-
var SDK_VERSION = "1.
|
|
118
|
+
var SDK_VERSION = "1.54.0";
|
|
119
119
|
|
|
120
120
|
// src/client.ts
|
|
121
121
|
var DEFAULT_BASE_URL = "https://api.brainerce.com";
|
|
122
122
|
var DEFAULT_TIMEOUT = 3e4;
|
|
123
|
+
var META_EVENT_NAMES = {
|
|
124
|
+
view_item: "ViewContent",
|
|
125
|
+
add_to_cart: "AddToCart",
|
|
126
|
+
begin_checkout: "InitiateCheckout",
|
|
127
|
+
add_payment_info: "AddPaymentInfo",
|
|
128
|
+
purchase: "Purchase",
|
|
129
|
+
search: "Search",
|
|
130
|
+
sign_up: "CompleteRegistration"
|
|
131
|
+
};
|
|
132
|
+
var TIKTOK_EVENT_NAMES = {
|
|
133
|
+
view_item: "ViewContent",
|
|
134
|
+
add_to_cart: "AddToCart",
|
|
135
|
+
begin_checkout: "InitiateCheckout",
|
|
136
|
+
add_payment_info: "AddPaymentInfo",
|
|
137
|
+
purchase: "CompletePayment",
|
|
138
|
+
search: "Search",
|
|
139
|
+
sign_up: "CompleteRegistration"
|
|
140
|
+
};
|
|
123
141
|
var RTL_LOCALES = /* @__PURE__ */ new Set(["ar", "he", "fa", "ur", "yi"]);
|
|
124
142
|
function getDirectionForLocale(locale) {
|
|
125
143
|
if (!locale) return "ltr";
|
|
@@ -174,6 +192,12 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
174
192
|
// GA4 stitch state (see `loadGoogleAnalytics()` in the Analytics section).
|
|
175
193
|
this._ga4MeasurementId = null;
|
|
176
194
|
this._ga4StitchPromise = null;
|
|
195
|
+
// Marketing tags booted by `initTracking()`. Each holds the id already
|
|
196
|
+
// loaded, which is what makes a repeat call a no-op instead of a second
|
|
197
|
+
// pixel install (and a doubled PageView).
|
|
198
|
+
this._gtmContainerId = null;
|
|
199
|
+
this._metaPixelId = null;
|
|
200
|
+
this._tiktokPixelId = null;
|
|
177
201
|
/** One warning per client, not per keystroke-driven address submit. */
|
|
178
202
|
this._warnedResolvedOnlyAddressFields = false;
|
|
179
203
|
/** localStorage key for session cart reference (sessionToken + cartId) */
|
|
@@ -1195,6 +1219,225 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
1195
1219
|
}
|
|
1196
1220
|
});
|
|
1197
1221
|
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Boot every marketing tag the merchant has configured — GA4, Google Tag
|
|
1224
|
+
* Manager, the Meta pixel, the TikTok pixel — in one call.
|
|
1225
|
+
*
|
|
1226
|
+
* Pass `storeInfo.tracking` straight through. The ids in it are resolved
|
|
1227
|
+
* server-side from the marketplace apps the merchant already connected, so
|
|
1228
|
+
* for the common case nobody types an id anywhere and nobody redeploys the
|
|
1229
|
+
* storefront: connect the Google app in the dashboard and this call starts
|
|
1230
|
+
* loading GA4 on the next page render.
|
|
1231
|
+
*
|
|
1232
|
+
* Call it once, as early as possible (root layout / app entry). It is
|
|
1233
|
+
* idempotent, a no-op during SSR, and never throws — a blocked or missing
|
|
1234
|
+
* tag must never take a storefront down with it.
|
|
1235
|
+
*
|
|
1236
|
+
* GA4 goes through {@link loadGoogleAnalytics}, so the `client_id` /
|
|
1237
|
+
* `session_id` stitch ids keep flowing onto cart and checkout calls and the
|
|
1238
|
+
* server-side purchase conversion still lands in the right session.
|
|
1239
|
+
*
|
|
1240
|
+
* @example
|
|
1241
|
+
* ```typescript
|
|
1242
|
+
* const storeInfo = await client.getStoreInfo();
|
|
1243
|
+
* client.initTracking(storeInfo.tracking);
|
|
1244
|
+
* // …later, on the order confirmation page:
|
|
1245
|
+
* client.trackMarketingEvent('purchase', {
|
|
1246
|
+
* transactionId: order.id,
|
|
1247
|
+
* currency: order.currency,
|
|
1248
|
+
* value: order.totalAmount,
|
|
1249
|
+
* items: order.items.map((i) => ({ itemId: i.sku, itemName: i.name, price: i.price, quantity: i.quantity })),
|
|
1250
|
+
* });
|
|
1251
|
+
* ```
|
|
1252
|
+
*/
|
|
1253
|
+
initTracking(tracking) {
|
|
1254
|
+
if (typeof window === "undefined" || !tracking) return;
|
|
1255
|
+
if (tracking.ga4MeasurementId) {
|
|
1256
|
+
this.loadGoogleAnalytics(tracking.ga4MeasurementId);
|
|
1257
|
+
}
|
|
1258
|
+
if (tracking.gtmContainerId) this.loadGtm(tracking.gtmContainerId);
|
|
1259
|
+
if (tracking.metaPixelId) this.loadMetaPixel(tracking.metaPixelId);
|
|
1260
|
+
if (tracking.tiktokPixelId) this.loadTikTokPixel(tracking.tiktokPixelId);
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Report one e-commerce event to every marketing tag that
|
|
1264
|
+
* {@link initTracking} loaded (GA4/GTM, Meta, TikTok).
|
|
1265
|
+
*
|
|
1266
|
+
* Distinct from {@link trackEvent}, which posts a cookieless pageview/beacon
|
|
1267
|
+
* to Brainerce's own storefront analytics. This one is about ad platforms —
|
|
1268
|
+
* call both; they answer different questions.
|
|
1269
|
+
*
|
|
1270
|
+
* You describe what happened once, in GA4's vocabulary, and the SDK
|
|
1271
|
+
* translates: a `dataLayer` push for GA4/GTM, the matching Meta standard
|
|
1272
|
+
* event via `fbq`, and the matching TikTok event via `ttq`. Tags that aren't
|
|
1273
|
+
* loaded are skipped silently, so the same call is correct whether the
|
|
1274
|
+
* merchant has connected none, one, or all of them.
|
|
1275
|
+
*
|
|
1276
|
+
* Why this matters for ad spend: a GTM container with no `dataLayer` events
|
|
1277
|
+
* is an empty container, and Meta cannot optimize a campaign it never sees a
|
|
1278
|
+
* `Purchase` for. The value/currency/item-id triple in {@link TrackingEventPayload}
|
|
1279
|
+
* is the whole input to that optimization.
|
|
1280
|
+
*
|
|
1281
|
+
* `purchase` is de-duplicated by the vendors on `transactionId` (GA4
|
|
1282
|
+
* `transaction_id`, Meta `eventID`, TikTok `event_id`), so a shopper
|
|
1283
|
+
* refreshing the confirmation page cannot double-count the order — pass the
|
|
1284
|
+
* order id and the safety is automatic.
|
|
1285
|
+
*
|
|
1286
|
+
* SSR-safe and never throws.
|
|
1287
|
+
*/
|
|
1288
|
+
trackMarketingEvent(name, payload = {}) {
|
|
1289
|
+
if (typeof window === "undefined") return;
|
|
1290
|
+
try {
|
|
1291
|
+
const items = payload.items ?? [];
|
|
1292
|
+
window.dataLayer = window.dataLayer || [];
|
|
1293
|
+
window.dataLayer.push({ ecommerce: null });
|
|
1294
|
+
window.dataLayer.push({
|
|
1295
|
+
event: name,
|
|
1296
|
+
ecommerce: {
|
|
1297
|
+
...payload.currency ? { currency: payload.currency } : {},
|
|
1298
|
+
...payload.value !== void 0 ? { value: payload.value } : {},
|
|
1299
|
+
...payload.transactionId ? { transaction_id: payload.transactionId } : {},
|
|
1300
|
+
...payload.shipping !== void 0 ? { shipping: payload.shipping } : {},
|
|
1301
|
+
...payload.tax !== void 0 ? { tax: payload.tax } : {},
|
|
1302
|
+
...payload.coupon ? { coupon: payload.coupon } : {},
|
|
1303
|
+
items: items.map((item) => ({
|
|
1304
|
+
item_id: item.itemId,
|
|
1305
|
+
...item.itemName ? { item_name: item.itemName } : {},
|
|
1306
|
+
...item.price !== void 0 ? { price: item.price } : {},
|
|
1307
|
+
...item.quantity !== void 0 ? { quantity: item.quantity } : {},
|
|
1308
|
+
...item.itemVariant ? { item_variant: item.itemVariant } : {},
|
|
1309
|
+
...item.itemCategory ? { item_category: item.itemCategory } : {}
|
|
1310
|
+
}))
|
|
1311
|
+
}
|
|
1312
|
+
});
|
|
1313
|
+
if (window.gtag && this._ga4MeasurementId) {
|
|
1314
|
+
window.gtag("event", name, {
|
|
1315
|
+
...payload.currency ? { currency: payload.currency } : {},
|
|
1316
|
+
...payload.value !== void 0 ? { value: payload.value } : {},
|
|
1317
|
+
...payload.transactionId ? { transaction_id: payload.transactionId } : {},
|
|
1318
|
+
...payload.shipping !== void 0 ? { shipping: payload.shipping } : {},
|
|
1319
|
+
...payload.tax !== void 0 ? { tax: payload.tax } : {},
|
|
1320
|
+
...payload.coupon ? { coupon: payload.coupon } : {},
|
|
1321
|
+
items: items.map((item) => ({
|
|
1322
|
+
item_id: item.itemId,
|
|
1323
|
+
item_name: item.itemName,
|
|
1324
|
+
price: item.price,
|
|
1325
|
+
quantity: item.quantity
|
|
1326
|
+
}))
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
const metaEvent = META_EVENT_NAMES[name];
|
|
1330
|
+
if (window.fbq && metaEvent) {
|
|
1331
|
+
const contents = items.map((item) => ({
|
|
1332
|
+
id: item.itemId,
|
|
1333
|
+
quantity: item.quantity ?? 1,
|
|
1334
|
+
...item.price !== void 0 ? { item_price: item.price } : {}
|
|
1335
|
+
}));
|
|
1336
|
+
window.fbq(
|
|
1337
|
+
"track",
|
|
1338
|
+
metaEvent,
|
|
1339
|
+
{
|
|
1340
|
+
...payload.currency ? { currency: payload.currency } : {},
|
|
1341
|
+
...payload.value !== void 0 ? { value: payload.value } : {},
|
|
1342
|
+
...contents.length ? { contents, content_ids: contents.map((c) => c.id), content_type: "product" } : {}
|
|
1343
|
+
},
|
|
1344
|
+
// Meta de-dupes a browser event against a server (CAPI) event of the
|
|
1345
|
+
// same eventID, and against a repeat send of the same page.
|
|
1346
|
+
payload.transactionId ? { eventID: payload.transactionId } : void 0
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
const tiktokEvent = TIKTOK_EVENT_NAMES[name];
|
|
1350
|
+
if (window.ttq?.track && tiktokEvent) {
|
|
1351
|
+
window.ttq.track(
|
|
1352
|
+
tiktokEvent,
|
|
1353
|
+
{
|
|
1354
|
+
...payload.currency ? { currency: payload.currency } : {},
|
|
1355
|
+
...payload.value !== void 0 ? { value: payload.value } : {},
|
|
1356
|
+
contents: items.map((item) => ({
|
|
1357
|
+
content_id: item.itemId,
|
|
1358
|
+
content_name: item.itemName,
|
|
1359
|
+
quantity: item.quantity ?? 1,
|
|
1360
|
+
price: item.price
|
|
1361
|
+
}))
|
|
1362
|
+
},
|
|
1363
|
+
payload.transactionId ? { event_id: payload.transactionId } : void 0
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
} catch {
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
/** Install the GTM container loader. Idempotent; no-op if already present. */
|
|
1370
|
+
loadGtm(containerId) {
|
|
1371
|
+
if (this._gtmContainerId === containerId) return;
|
|
1372
|
+
this._gtmContainerId = containerId;
|
|
1373
|
+
try {
|
|
1374
|
+
window.dataLayer = window.dataLayer || [];
|
|
1375
|
+
window.dataLayer.push({ "gtm.start": Date.now(), event: "gtm.js" });
|
|
1376
|
+
const script = document.createElement("script");
|
|
1377
|
+
script.async = true;
|
|
1378
|
+
script.src = `https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(containerId)}`;
|
|
1379
|
+
document.head.appendChild(script);
|
|
1380
|
+
} catch {
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
/** Install the Meta pixel and fire its initial PageView. Idempotent. */
|
|
1384
|
+
loadMetaPixel(pixelId) {
|
|
1385
|
+
if (this._metaPixelId === pixelId) return;
|
|
1386
|
+
this._metaPixelId = pixelId;
|
|
1387
|
+
try {
|
|
1388
|
+
if (!window.fbq) {
|
|
1389
|
+
const queue = [];
|
|
1390
|
+
const fbq = ((...args) => {
|
|
1391
|
+
if (fbq.callMethod) fbq.callMethod(...args);
|
|
1392
|
+
else queue.push(args);
|
|
1393
|
+
});
|
|
1394
|
+
fbq.queue = queue;
|
|
1395
|
+
fbq.loaded = true;
|
|
1396
|
+
fbq.version = "2.0";
|
|
1397
|
+
window.fbq = fbq;
|
|
1398
|
+
window._fbq = fbq;
|
|
1399
|
+
const script = document.createElement("script");
|
|
1400
|
+
script.async = true;
|
|
1401
|
+
script.src = "https://connect.facebook.net/en_US/fbevents.js";
|
|
1402
|
+
document.head.appendChild(script);
|
|
1403
|
+
}
|
|
1404
|
+
window.fbq("init", pixelId);
|
|
1405
|
+
window.fbq("track", "PageView");
|
|
1406
|
+
} catch {
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
/** Install the TikTok pixel and fire its initial page view. Idempotent. */
|
|
1410
|
+
loadTikTokPixel(pixelId) {
|
|
1411
|
+
if (this._tiktokPixelId === pixelId) return;
|
|
1412
|
+
this._tiktokPixelId = pixelId;
|
|
1413
|
+
try {
|
|
1414
|
+
const ttq = window.ttq ?? {};
|
|
1415
|
+
ttq._i = ttq._i ?? {};
|
|
1416
|
+
ttq._i[pixelId] = ttq._i[pixelId] ?? [];
|
|
1417
|
+
ttq._t = ttq._t ?? {};
|
|
1418
|
+
ttq._t[pixelId] = Date.now();
|
|
1419
|
+
ttq._o = ttq._o ?? {};
|
|
1420
|
+
ttq._o[pixelId] = {};
|
|
1421
|
+
const methods = ["page", "track", "identify", "instances", "ready"];
|
|
1422
|
+
ttq.methods = methods;
|
|
1423
|
+
for (const method of methods) {
|
|
1424
|
+
if (typeof ttq[method] !== "function") {
|
|
1425
|
+
ttq[method] = (...args) => {
|
|
1426
|
+
ttq._i?.[pixelId]?.push([method, ...args]);
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
window.ttq = ttq;
|
|
1431
|
+
const script = document.createElement("script");
|
|
1432
|
+
script.async = true;
|
|
1433
|
+
script.src = `https://analytics.tiktok.com/i18n/pixel/events.js?sdkid=${encodeURIComponent(
|
|
1434
|
+
pixelId
|
|
1435
|
+
)}&lib=ttq`;
|
|
1436
|
+
document.head.appendChild(script);
|
|
1437
|
+
ttq.page?.();
|
|
1438
|
+
} catch {
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1198
1441
|
/**
|
|
1199
1442
|
* Merge the resolved GA4 stitch ids onto a request body — only for fields
|
|
1200
1443
|
* the caller didn't already set explicitly (explicit values always win).
|
|
@@ -5115,9 +5358,7 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
5115
5358
|
* ```
|
|
5116
5359
|
*/
|
|
5117
5360
|
async setShippingAddress(checkoutId, address) {
|
|
5118
|
-
const body = await this.withAnalyticsStitchIds(
|
|
5119
|
-
this.stripResolvedOnlyAddressFields(address)
|
|
5120
|
-
);
|
|
5361
|
+
const body = await this.withAnalyticsStitchIds(this.stripResolvedOnlyAddressFields(address));
|
|
5121
5362
|
if (this.isVibeCodedMode()) {
|
|
5122
5363
|
return this.vibeCodedRequest(
|
|
5123
5364
|
"PATCH",
|
|
@@ -8174,6 +8415,42 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
8174
8415
|
`/api/v1/regions/${encodePathSegment(regionId)}/compatible-providers`
|
|
8175
8416
|
);
|
|
8176
8417
|
}
|
|
8418
|
+
/**
|
|
8419
|
+
* List a region's manual price overrides (regional pricing, admin). Prices
|
|
8420
|
+
* are in the region's currency; `variantId: null` rows are product-level.
|
|
8421
|
+
*/
|
|
8422
|
+
async getRegionPrices(regionId, params = {}) {
|
|
8423
|
+
return this.adminRequest(
|
|
8424
|
+
"GET",
|
|
8425
|
+
`/api/v1/regions/${encodePathSegment(regionId)}/prices`,
|
|
8426
|
+
void 0,
|
|
8427
|
+
{
|
|
8428
|
+
productId: params.productId,
|
|
8429
|
+
page: params.page,
|
|
8430
|
+
limit: params.limit
|
|
8431
|
+
}
|
|
8432
|
+
);
|
|
8433
|
+
}
|
|
8434
|
+
/**
|
|
8435
|
+
* Bulk upsert/remove manual price overrides for a region (admin). Each
|
|
8436
|
+
* entry: `price` = regular, `salePrice` = sale (must be lower), in the
|
|
8437
|
+
* REGION's currency; `remove: true` deletes. A product/variant with no
|
|
8438
|
+
* entry keeps automatic FX conversion.
|
|
8439
|
+
*/
|
|
8440
|
+
async upsertRegionPrices(regionId, entries) {
|
|
8441
|
+
return this.adminRequest(
|
|
8442
|
+
"PUT",
|
|
8443
|
+
`/api/v1/regions/${encodePathSegment(regionId)}/prices`,
|
|
8444
|
+
{ entries }
|
|
8445
|
+
);
|
|
8446
|
+
}
|
|
8447
|
+
/** Delete one manual price override by id (admin). */
|
|
8448
|
+
async deleteRegionPrice(regionId, priceId) {
|
|
8449
|
+
await this.adminRequest(
|
|
8450
|
+
"DELETE",
|
|
8451
|
+
`/api/v1/regions/${encodePathSegment(regionId)}/prices/${encodePathSegment(priceId)}`
|
|
8452
|
+
);
|
|
8453
|
+
}
|
|
8177
8454
|
// -------------------- Regions (Storefront + vibe-coded, public — no apiKey) --------------------
|
|
8178
8455
|
// storeId- or connectionId-based, no auth. Call these from a storefront to
|
|
8179
8456
|
// detect the buyer's region (story S1), then pair with detectRegion(). Then
|
|
@@ -10123,7 +10400,10 @@ function getCartTotals(cart, shippingPrice) {
|
|
|
10123
10400
|
return { subtotal, discount, shipping, total };
|
|
10124
10401
|
}
|
|
10125
10402
|
function getCartItemName(item) {
|
|
10126
|
-
|
|
10403
|
+
const productName = item.product.name;
|
|
10404
|
+
const variantName = item.variant?.name?.trim();
|
|
10405
|
+
if (!variantName || variantName === productName) return productName;
|
|
10406
|
+
return `${productName} - ${variantName}`;
|
|
10127
10407
|
}
|
|
10128
10408
|
function getCartItemImage(item) {
|
|
10129
10409
|
if (item.variant?.image) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainerce",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.55.0",
|
|
4
4
|
"description": "Official SDK for building e-commerce storefronts with Brainerce Platform. Perfect for vibe-coded sites, AI-built stores (Cursor, Lovable, v0), and custom storefronts.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"lint": "eslint \"src/**/*.ts\"",
|
|
29
29
|
"test": "vitest run",
|
|
30
30
|
"test:watch": "vitest",
|
|
31
|
+
"release": "pnpm build && node ../../scripts/publish-workspace-package.js .",
|
|
31
32
|
"prepublishOnly": "pnpm build"
|
|
32
33
|
},
|
|
33
34
|
"keywords": [
|