brainerce 1.39.0 → 1.42.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/dist/index.mjs CHANGED
@@ -115,7 +115,7 @@ function isDevGuardsEnabled() {
115
115
  }
116
116
 
117
117
  // src/version.ts
118
- var SDK_VERSION = "1.36.1";
118
+ var SDK_VERSION = "1.42.0";
119
119
 
120
120
  // src/client.ts
121
121
  var DEFAULT_BASE_URL = "https://api.brainerce.com";
@@ -583,6 +583,7 @@ var BrainerceClient = class {
583
583
  }
584
584
  }
585
585
  this.proxyMode = options.proxyMode || false;
586
+ this.analyticsBaseUrl = this.resolveAnalyticsBaseUrl(options.analyticsBaseUrl, resolvedBase);
586
587
  this.onAuthError = options.onAuthError;
587
588
  this.hydrateSessionCart();
588
589
  this.detectRecoverCartFromUrl();
@@ -1036,12 +1037,46 @@ var BrainerceClient = class {
1036
1037
  * ```
1037
1038
  */
1038
1039
  async trackEvent(payload = {}) {
1040
+ if (this.isVibeCodedMode() && this.connectionId) {
1041
+ this.sendAnalyticsBeacon(`/api/vc/${encodePathSegment(this.connectionId)}/events`, payload);
1042
+ } else if (this.isStorefrontMode() && this.storeId) {
1043
+ this.sendAnalyticsBeacon(`/api/stores/${encodePathSegment(this.storeId)}/events`, payload);
1044
+ }
1045
+ }
1046
+ /**
1047
+ * Resolve the base URL for analytics beacons. Analytics must go DIRECTLY to the
1048
+ * Brainerce API (never through a same-origin BFF proxy) so the visitor's real IP —
1049
+ * hence country — reaches the backend. Explicit `analyticsBaseUrl` wins; otherwise
1050
+ * use the direct API when `baseUrl` is a same-origin proxy, else the (already-direct)
1051
+ * `baseUrl` (which may be a custom / self-hosted Brainerce API).
1052
+ */
1053
+ resolveAnalyticsBaseUrl(explicit, resolvedBase) {
1054
+ if (explicit) return explicit.replace(/\/$/, "");
1055
+ const sameOriginProxy = this.proxyMode || typeof window !== "undefined" && !!window.location?.origin && resolvedBase.startsWith(window.location.origin);
1056
+ return sameOriginProxy ? DEFAULT_BASE_URL : resolvedBase;
1057
+ }
1058
+ /**
1059
+ * Fire-and-forget analytics beacon sent directly to `analyticsBaseUrl`. Uses
1060
+ * `keepalive` so it survives page unload (engagement events fired on pagehide),
1061
+ * omits credentials (cookieless), and never throws.
1062
+ */
1063
+ sendAnalyticsBeacon(eventsPath, payload) {
1039
1064
  try {
1040
- if (this.isVibeCodedMode()) {
1041
- await this.vibeCodedRequest("POST", "/events", payload);
1042
- } else if (this.isStorefrontMode()) {
1043
- await this.storefrontRequest("POST", "/events", payload);
1044
- }
1065
+ const headers = {
1066
+ "Content-Type": "application/json",
1067
+ "X-SDK-Version": SDK_VERSION,
1068
+ "ngrok-skip-browser-warning": "true"
1069
+ };
1070
+ if (this.origin) headers["Origin"] = this.origin;
1071
+ void fetch(`${this.analyticsBaseUrl}${eventsPath}`, {
1072
+ method: "POST",
1073
+ headers,
1074
+ body: JSON.stringify(payload),
1075
+ keepalive: true,
1076
+ credentials: "omit",
1077
+ mode: "cors"
1078
+ }).catch(() => {
1079
+ });
1045
1080
  } catch {
1046
1081
  }
1047
1082
  }
@@ -5159,6 +5194,14 @@ var BrainerceClient = class {
5159
5194
  *
5160
5195
  * @returns Payment providers config with hasPayments flag and provider list
5161
5196
  *
5197
+ * Each provider carries a `methodType` (Shopify-parity taxonomy) and a derived
5198
+ * `isAdditive` / `presentation`. There is at most ONE primary card processor
5199
+ * (`methodType: 'CREDIT_CARD'`, the `defaultProvider`) that settles the order;
5200
+ * additive methods (`isAdditive: true`, e.g. PayPal as a `'WALLET'`) are shown
5201
+ * *alongside* it as accelerated-checkout express buttons — they never replace
5202
+ * the card form. A wallet-only store is the one exception: with no card
5203
+ * processor installed, the wallet becomes the `defaultProvider` and stands alone.
5204
+ *
5162
5205
  * @example
5163
5206
  * ```typescript
5164
5207
  * const { providers, hasPayments, defaultProvider } = await client.getPaymentProviders();
@@ -5168,27 +5211,21 @@ var BrainerceClient = class {
5168
5211
  * return;
5169
5212
  * }
5170
5213
  *
5171
- * // Find specific providers
5172
- * const stripeProvider = providers.find(p => p.provider === 'stripe');
5173
- * const paypalProvider = providers.find(p => p.provider === 'paypal');
5174
- *
5175
- * // Initialize Stripe if available (with Connect account support)
5176
- * if (stripeProvider) {
5177
- * const stripe = await loadStripe(stripeProvider.publicKey, {
5178
- * stripeAccount: stripeProvider.stripeAccountId, // For Stripe Connect
5179
- * });
5180
- * // Show Stripe payment form
5181
- * }
5182
- *
5183
- * // Initialize PayPal if available
5184
- * if (paypalProvider) {
5185
- * // Load PayPal SDK with paypalProvider.publicKey as client-id
5186
- * // Show PayPal buttons
5214
+ * // Render additive methods (wallets) as express buttons ABOVE the card form.
5215
+ * const expressMethods = providers.filter(p => p.isAdditive); // e.g. PayPal
5216
+ * for (const m of expressMethods) {
5217
+ * // m.presentation === 'express_button' — render an express/accelerated button
5218
+ * // and call createPaymentIntent({ providerId: m.id }) when the buyer taps it.
5187
5219
  * }
5188
5220
  *
5189
- * // Or use the default provider for a simpler single-provider flow
5190
- * if (defaultProvider) {
5191
- * console.log(`Using default: ${defaultProvider.name} (${defaultProvider.provider})`);
5221
+ * // Render the primary card processor (the settling default) as the main form.
5222
+ * if (defaultProvider && !defaultProvider.isAdditive) {
5223
+ * if (defaultProvider.provider === 'stripe') {
5224
+ * const stripe = await loadStripe(defaultProvider.publicKey, {
5225
+ * stripeAccount: defaultProvider.stripeAccountId, // For Stripe Connect
5226
+ * });
5227
+ * // Show the Stripe card form
5228
+ * }
5192
5229
  * }
5193
5230
  * ```
5194
5231
  */
@@ -6208,6 +6245,102 @@ var BrainerceClient = class {
6208
6245
  400
6209
6246
  );
6210
6247
  }
6248
+ // -------------------- Loyalty --------------------
6249
+ // Storefront-mode only (storeId + customerToken). The backend exposes loyalty
6250
+ // solely under /api/stores/:storeId/loyalty — there is no vibe-coded route, so
6251
+ // these do NOT branch on isVibeCodedMode() (that would 404 silently).
6252
+ /**
6253
+ * Get the logged-in customer's loyalty status: enrollment, points balance,
6254
+ * lifetime earned, and the program's display config (requires customerToken).
6255
+ * Only available in storefront mode. `program` is null when the store has no
6256
+ * loyalty program.
6257
+ *
6258
+ * @example
6259
+ * ```typescript
6260
+ * client.setCustomerToken(auth.token);
6261
+ * const status = await client.getLoyaltyStatus();
6262
+ * if (status.enrolled) console.log(`${status.pointsBalance} ${status.program?.pointsName}`);
6263
+ * ```
6264
+ */
6265
+ async getLoyaltyStatus() {
6266
+ if (!this.customerToken && !this.proxyMode) {
6267
+ throw new BrainerceError(
6268
+ "Customer token required. Call setCustomerToken() after login.",
6269
+ 401
6270
+ );
6271
+ }
6272
+ if (this.storeId && !this.apiKey) {
6273
+ return this.storefrontRequest("GET", "/loyalty/me");
6274
+ }
6275
+ throw new BrainerceError("getLoyaltyStatus is only available in storefront mode", 400);
6276
+ }
6277
+ /**
6278
+ * Enroll the logged-in customer in the store's loyalty program (requires
6279
+ * customerToken). Idempotent — returns the same status shape as
6280
+ * getLoyaltyStatus(). Only available in storefront mode.
6281
+ *
6282
+ * @example
6283
+ * ```typescript
6284
+ * const status = await client.enrollInLoyalty();
6285
+ * ```
6286
+ */
6287
+ async enrollInLoyalty() {
6288
+ if (!this.customerToken && !this.proxyMode) {
6289
+ throw new BrainerceError(
6290
+ "Customer token required. Call setCustomerToken() after login.",
6291
+ 401
6292
+ );
6293
+ }
6294
+ if (this.storeId && !this.apiKey) {
6295
+ return this.storefrontRequest("POST", "/loyalty/enroll");
6296
+ }
6297
+ throw new BrainerceError("enrollInLoyalty is only available in storefront mode", 400);
6298
+ }
6299
+ /**
6300
+ * List the rewards the customer can redeem points for (active rewards, cheapest
6301
+ * first). Requires customerToken. Only available in storefront mode.
6302
+ *
6303
+ * @example
6304
+ * ```typescript
6305
+ * client.setCustomerToken(auth.token);
6306
+ * const rewards = await client.getAvailableRewards();
6307
+ * ```
6308
+ */
6309
+ async getAvailableRewards() {
6310
+ if (!this.customerToken && !this.proxyMode) {
6311
+ throw new BrainerceError(
6312
+ "Customer token required. Call setCustomerToken() after login.",
6313
+ 401
6314
+ );
6315
+ }
6316
+ if (this.storeId && !this.apiKey) {
6317
+ return this.storefrontRequest("GET", "/loyalty/rewards/available");
6318
+ }
6319
+ throw new BrainerceError("getAvailableRewards is only available in storefront mode", 400);
6320
+ }
6321
+ /**
6322
+ * Redeem a reward: spends the customer's points and mints a one-time coupon
6323
+ * that applies at checkout (requires customerToken). Only available in
6324
+ * storefront mode.
6325
+ *
6326
+ * @example
6327
+ * ```typescript
6328
+ * const { couponCode, pointsBalance } = await client.redeemLoyaltyReward('rw_123');
6329
+ * // apply couponCode to the cart at checkout
6330
+ * ```
6331
+ */
6332
+ async redeemLoyaltyReward(rewardId) {
6333
+ if (!this.customerToken && !this.proxyMode) {
6334
+ throw new BrainerceError(
6335
+ "Customer token required. Call setCustomerToken() after login.",
6336
+ 401
6337
+ );
6338
+ }
6339
+ if (this.storeId && !this.apiKey) {
6340
+ return this.storefrontRequest("POST", "/loyalty/redeem", { rewardId });
6341
+ }
6342
+ throw new BrainerceError("redeemLoyaltyReward is only available in storefront mode", 400);
6343
+ }
6211
6344
  /**
6212
6345
  * Get the current customer's orders (requires customerToken)
6213
6346
  * Works in vibe-coded and storefront modes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainerce",
3
- "version": "1.39.0",
3
+ "version": "1.42.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",