@active-reach/web-sdk 1.11.1 → 1.13.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.
@@ -2215,6 +2215,134 @@ const _TraitGovernor = class _TraitGovernor {
2215
2215
  };
2216
2216
  _TraitGovernor.WARN_CAP = 3;
2217
2217
  let TraitGovernor = _TraitGovernor;
2218
+ const VALID_CHANNELS = /* @__PURE__ */ new Set([
2219
+ "email",
2220
+ "sms",
2221
+ "push",
2222
+ "webpush",
2223
+ "whatsapp",
2224
+ "rcs",
2225
+ "inapp"
2226
+ ]);
2227
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2228
+ const PHONE_RE = /^\+[1-9]\d{7,14}$/;
2229
+ const SHA256_HEX_RE = /^[a-f0-9]{64}$/i;
2230
+ const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
2231
+ class UserNamespace {
2232
+ constructor(aegis) {
2233
+ this.pendingTraits = {};
2234
+ this.aegis = aegis;
2235
+ }
2236
+ /**
2237
+ * Authoritative identity write. Flushes any pending pre-login traits
2238
+ * along with the supplied `traits` argument as a single identify call.
2239
+ */
2240
+ login(userId, traits) {
2241
+ if (typeof userId !== "string" || userId.length === 0) {
2242
+ logger.warn("[user.login] userId must be a non-empty string");
2243
+ return;
2244
+ }
2245
+ const merged = { ...this.pendingTraits, ...traits || {} };
2246
+ this.pendingTraits = {};
2247
+ this.aegis.identify(userId, merged);
2248
+ }
2249
+ /**
2250
+ * Drops the current userId and any pending pre-login traits. Does NOT
2251
+ * fire a server-side logout event — that's a separate explicit `track`.
2252
+ */
2253
+ logout() {
2254
+ this.pendingTraits = {};
2255
+ this.aegis.reset();
2256
+ }
2257
+ setAttribute(key, value) {
2258
+ if (typeof key !== "string" || key.length === 0) {
2259
+ logger.warn("[user.setAttribute] key must be a non-empty string");
2260
+ return;
2261
+ }
2262
+ this.writeTraits({ [key]: value });
2263
+ }
2264
+ setAttributes(map) {
2265
+ if (!map || typeof map !== "object") {
2266
+ logger.warn("[user.setAttributes] map must be an object");
2267
+ return;
2268
+ }
2269
+ this.writeTraits(map);
2270
+ }
2271
+ setEmail(email) {
2272
+ if (!EMAIL_RE.test(email)) {
2273
+ logger.warn("[user.setEmail] invalid email format");
2274
+ return;
2275
+ }
2276
+ this.writeTraits({ email: email.toLowerCase() });
2277
+ }
2278
+ setPhone(phone) {
2279
+ if (!PHONE_RE.test(phone)) {
2280
+ logger.warn("[user.setPhone] phone must be E.164 (e.g. +15551234567)");
2281
+ return;
2282
+ }
2283
+ this.writeTraits({ phone });
2284
+ }
2285
+ setHashedEmail(sha256Hex) {
2286
+ if (!SHA256_HEX_RE.test(sha256Hex)) {
2287
+ logger.warn("[user.setHashedEmail] expected 64-char hex SHA-256");
2288
+ return;
2289
+ }
2290
+ this.writeTraits({ email_sha256: sha256Hex.toLowerCase() });
2291
+ }
2292
+ setHashedPhone(sha256Hex) {
2293
+ if (!SHA256_HEX_RE.test(sha256Hex)) {
2294
+ logger.warn("[user.setHashedPhone] expected 64-char hex SHA-256");
2295
+ return;
2296
+ }
2297
+ this.writeTraits({ phone_sha256: sha256Hex.toLowerCase() });
2298
+ }
2299
+ setBirthDate(iso) {
2300
+ if (!ISO_DATE_RE.test(iso)) {
2301
+ logger.warn("[user.setBirthDate] expected YYYY-MM-DD");
2302
+ return;
2303
+ }
2304
+ this.writeTraits({ birth_date: iso });
2305
+ }
2306
+ setOptIn(channel, granted) {
2307
+ if (!VALID_CHANNELS.has(channel)) {
2308
+ logger.warn(`[user.setOptIn] unknown channel: ${channel}`);
2309
+ return;
2310
+ }
2311
+ if (typeof granted !== "boolean") {
2312
+ logger.warn("[user.setOptIn] granted must be a boolean");
2313
+ return;
2314
+ }
2315
+ this.writeTraits({ [`opt_in_${channel}`]: granted });
2316
+ }
2317
+ /**
2318
+ * HMAC-signed identity token. Ingress-side verification not yet wired —
2319
+ * the trait is forwarded as-is and persisted on the contact record
2320
+ * until that lands.
2321
+ */
2322
+ setSecureToken(token) {
2323
+ if (typeof token !== "string" || token.length === 0) {
2324
+ logger.warn("[user.setSecureToken] token must be a non-empty string");
2325
+ return;
2326
+ }
2327
+ this.writeTraits({ _secure_token: token });
2328
+ }
2329
+ /**
2330
+ * Test/inspection hook — returns a copy of the pending traits buffer.
2331
+ * Used by the e2e smoke test to assert pre-login accumulation.
2332
+ */
2333
+ _getPendingTraits() {
2334
+ return { ...this.pendingTraits };
2335
+ }
2336
+ // ---------------------------------------------------------------------------
2337
+ writeTraits(traits) {
2338
+ const userId = this.aegis.getUserId();
2339
+ if (userId) {
2340
+ this.aegis.identify(userId, traits);
2341
+ return;
2342
+ }
2343
+ Object.assign(this.pendingTraits, traits);
2344
+ }
2345
+ }
2218
2346
  class Aegis {
2219
2347
  constructor() {
2220
2348
  this.config = null;
@@ -2225,6 +2353,7 @@ class Aegis {
2225
2353
  this.initPromise = null;
2226
2354
  this.consent = null;
2227
2355
  this._ecommerce = null;
2356
+ this._user = null;
2228
2357
  this.rateLimiter = null;
2229
2358
  this.nameGovernor = new NameGovernor();
2230
2359
  this.traitGovernor = new TraitGovernor();
@@ -2400,6 +2529,20 @@ class Aegis {
2400
2529
  });
2401
2530
  }
2402
2531
  }
2532
+ /**
2533
+ * Symmetric counterpart to `use(plugin)`. Calls the plugin's `destroy()`
2534
+ * hook (if defined) and removes it from the registry so its hooks no
2535
+ * longer fire on subsequent events. Safe to call with a name that
2536
+ * isn't registered — logs a warning and returns.
2537
+ *
2538
+ * Primary use case: React components that register a plugin in
2539
+ * `useEffect` and need to unregister in the cleanup function so HMR /
2540
+ * provider remount doesn't leak listeners. See cashier-portal's
2541
+ * MetaPixelInjector for an example.
2542
+ */
2543
+ removePlugin(name) {
2544
+ this.plugins.unregister(name);
2545
+ }
2403
2546
  track(eventName, properties) {
2404
2547
  if (!this.assertInitialized()) return;
2405
2548
  const messageId = generateMessageId();
@@ -2484,6 +2627,31 @@ class Aegis {
2484
2627
  };
2485
2628
  this.captureEvent(event);
2486
2629
  }
2630
+ /**
2631
+ * SPA-logical screen view. Distinct from `page()` (URL-bound) — fires
2632
+ * when the user enters a logical surface like cart / checkout / drawer
2633
+ * without a URL change. Gateway maps `screen` → `engagement.screen_view`.
2634
+ */
2635
+ screen(name, properties) {
2636
+ if (!this.assertInitialized()) return;
2637
+ if (typeof name !== "string" || name.length === 0) {
2638
+ logger.warn("aegis.screen: name must be a non-empty string");
2639
+ return;
2640
+ }
2641
+ const event = {
2642
+ type: "screen",
2643
+ name,
2644
+ properties: properties || {},
2645
+ messageId: generateMessageId(),
2646
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2647
+ anonymousId: this.identity.getAnonymousId(),
2648
+ userId: this.identity.getUserId() || void 0,
2649
+ sessionId: this.session.getSessionId(),
2650
+ workspace_id: this.config.workspace_id || void 0,
2651
+ context: buildContext(this.config, this.session)
2652
+ };
2653
+ this.captureEvent(event);
2654
+ }
2487
2655
  async captureEvent(event) {
2488
2656
  var _a;
2489
2657
  if (((_a = this.config) == null ? void 0 : _a.enable_consent_mode) && this.consent) {
@@ -2570,6 +2738,7 @@ class Aegis {
2570
2738
  reset() {
2571
2739
  if (!this.assertInitialized()) return;
2572
2740
  this.identity.reset();
2741
+ this._user = null;
2573
2742
  logger.info("User identity reset");
2574
2743
  }
2575
2744
  /**
@@ -2754,6 +2923,12 @@ class Aegis {
2754
2923
  }
2755
2924
  return this._ecommerce;
2756
2925
  }
2926
+ get user() {
2927
+ if (!this._user) {
2928
+ this._user = new UserNamespace(this);
2929
+ }
2930
+ return this._user;
2931
+ }
2757
2932
  destroy() {
2758
2933
  this.stopSPATracking();
2759
2934
  if (this.queue) {
@@ -2780,7 +2955,8 @@ export {
2780
2955
  NameGovernor as N,
2781
2956
  RateLimiter as R,
2782
2957
  Storage as S,
2958
+ UserNamespace as U,
2783
2959
  logger as l,
2784
2960
  murmurhash3_x86_32 as m
2785
2961
  };
2786
- //# sourceMappingURL=analytics-6PR9ERDS.mjs.map
2962
+ //# sourceMappingURL=analytics-Mh4H4ekQ.mjs.map