@amplifyup/sdk 0.1.21 → 0.1.23

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.
@@ -151,17 +151,42 @@ var AWSAdapter = class {
151
151
  };
152
152
  this.sendEvent("/v1/track", eventData);
153
153
  }
154
- identify(userId, traits) {
154
+ identify(userIdOrIdentifiers, traits = {}) {
155
155
  if (!this.initialized) {
156
156
  console.warn("AmplifyUp SDK: Not initialized. Call init() first.");
157
157
  return;
158
158
  }
159
+ const isMap = userIdOrIdentifiers !== null && typeof userIdOrIdentifiers === "object" && !Array.isArray(userIdOrIdentifiers);
160
+ const identifiers = isMap ? userIdOrIdentifiers : traits.identifiers;
161
+ const userId = isMap ? void 0 : String(userIdOrIdentifiers);
159
162
  const eventData = {
160
163
  type: "identify",
161
- userId,
162
164
  anonymousId: traits.instanceId,
163
165
  traits: {
164
- ...traits
166
+ ...traits,
167
+ ...identifiers ? { identifiers } : {}
168
+ },
169
+ ...identifiers ? { identifiers } : {},
170
+ ...userId ? { userId } : {},
171
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
172
+ };
173
+ this.sendEvent("/v1/track", eventData);
174
+ }
175
+ setProfileFields(fields, context = {}) {
176
+ if (!this.initialized) {
177
+ console.warn("AmplifyUp SDK: Not initialized. Call init() first.");
178
+ return;
179
+ }
180
+ const eventData = {
181
+ type: "set_fields",
182
+ anonymousId: context.instanceId,
183
+ fields,
184
+ traits: {
185
+ instanceId: context.instanceId,
186
+ trackingId: context.trackingId
187
+ },
188
+ properties: {
189
+ trackingId: context.trackingId
165
190
  },
166
191
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
167
192
  };
@@ -223,6 +248,7 @@ function clearIds() {
223
248
  var AWS_DEV_API = "https://api-dev.amplifyup.ai";
224
249
  var AWS_PROD_API = "https://api.amplifyup.ai";
225
250
  var PROD_ORCHESTRATOR = "https://amplifyup.ai";
251
+ var PROD_EDGE = "https://edge.amplifyup.ai";
226
252
  function isAmplifyUpTarget(value) {
227
253
  return value === "local" || value === "dev" || value === "production";
228
254
  }
@@ -258,6 +284,10 @@ function localAwsApiUrl() {
258
284
  const fromEnv = typeof process !== "undefined" ? process.env.NEXT_PUBLIC_AMPLIFYUP_API_URL : void 0;
259
285
  return (fromEnv || AWS_DEV_API).replace(/\/$/, "");
260
286
  }
287
+ function localEdgeUrl() {
288
+ const fromEnv = typeof process !== "undefined" ? process.env.NEXT_PUBLIC_EDGE_URL : void 0;
289
+ return (fromEnv || "http://localhost:8787").replace(/\/$/, "");
290
+ }
261
291
  function resolveAmplifyUpUrls(target) {
262
292
  if (target === "local") {
263
293
  const orchestratorUrl2 = localOrchestratorUrl();
@@ -268,18 +298,22 @@ function resolveAmplifyUpUrls(target) {
268
298
  orchestratorUrl: orchestratorUrl2,
269
299
  // Events + personalize → AWS development stack
270
300
  apiGatewayUrl,
271
- decisionApiUrl: `${apiGatewayUrl}/v1/personalize`
301
+ decisionApiUrl: `${apiGatewayUrl}/v1/personalize`,
302
+ edgeUrl: localEdgeUrl()
272
303
  };
273
304
  }
274
305
  const resolvedApi = typeof process !== "undefined" && process.env.NEXT_PUBLIC_AMPLIFYUP_API_URL?.replace(/\/$/, "") || AWS_PROD_API;
275
306
  const orchestratorFromEnv = typeof process !== "undefined" ? process.env.NEXT_PUBLIC_ORCHESTRATOR_URL : void 0;
276
307
  const isLocalOrchestrator = !!orchestratorFromEnv && (orchestratorFromEnv.includes("localhost") || orchestratorFromEnv.includes("127.0.0.1"));
277
308
  const orchestratorUrl = isLocalOrchestrator || !orchestratorFromEnv ? PROD_ORCHESTRATOR : orchestratorFromEnv.replace(/\/$/, "");
309
+ const edgeFromEnv = typeof process !== "undefined" ? process.env.NEXT_PUBLIC_EDGE_URL?.replace(/\/$/, "") : void 0;
310
+ const edgeUrl = edgeFromEnv && !edgeFromEnv.includes("localhost") && !edgeFromEnv.includes("127.0.0.1") ? edgeFromEnv : PROD_EDGE;
278
311
  return {
279
312
  target: "production",
280
313
  apiGatewayUrl: resolvedApi,
281
314
  orchestratorUrl,
282
- decisionApiUrl: `${resolvedApi}/v1/personalize`
315
+ decisionApiUrl: `${resolvedApi}/v1/personalize`,
316
+ edgeUrl
283
317
  };
284
318
  }
285
319
 
@@ -310,6 +344,7 @@ function isComposerPreview() {
310
344
  }
311
345
 
312
346
  // src/index.ts
347
+ var SESSION_IDLE_GAP_MS = 30 * 60 * 1e3;
313
348
  function readPreviewQueryOverrides() {
314
349
  if (typeof window === "undefined") return {};
315
350
  const params = new URLSearchParams(window.location.search);
@@ -337,6 +372,8 @@ var AmplifyUpSDK = class {
337
372
  this.apiGatewayUrl = "";
338
373
  this.orchestratorUrl = "";
339
374
  this.decisionApiUrl = "";
375
+ this.edgeUrl = "";
376
+ this.siteId = "";
340
377
  this.target = "local";
341
378
  this.initialized = false;
342
379
  /** Prevents overlapping init() from React Strict Mode double-mount */
@@ -376,7 +413,9 @@ var AmplifyUpSDK = class {
376
413
  reached: /* @__PURE__ */ new Set(),
377
414
  visibleMs: 0,
378
415
  visibleSince: null,
379
- lastDwellSecondsSent: 0
416
+ lastDwellSecondsSent: 0,
417
+ /** When the tab last became hidden — used for idle-resume re-page. */
418
+ hiddenAt: null
380
419
  };
381
420
  this.adapter = adapter || new AWSAdapter();
382
421
  if (typeof window !== "undefined") {
@@ -491,7 +530,8 @@ var AmplifyUpSDK = class {
491
530
  isInitialized: () => this.isInitialized(),
492
531
  page: (url, properties) => this.page(url, properties),
493
532
  track: (eventName, properties) => this.track(eventName, properties),
494
- identify: (userId, traits) => this.identify(userId, traits)
533
+ identify: (userIdOrIdentifiers, traits) => this.identify(userIdOrIdentifiers, traits),
534
+ setProfileFields: (fields) => this.setProfileFields(fields)
495
535
  };
496
536
  }
497
537
  async performInit(config) {
@@ -506,6 +546,8 @@ var AmplifyUpSDK = class {
506
546
  this.apiGatewayUrl = urls.apiGatewayUrl;
507
547
  this.orchestratorUrl = urls.orchestratorUrl;
508
548
  this.decisionApiUrl = urls.decisionApiUrl;
549
+ this.edgeUrl = urls.edgeUrl;
550
+ this.siteId = config.siteId || "";
509
551
  const previewOverrides = readPreviewQueryOverrides();
510
552
  if (previewOverrides.orchestratorUrl) {
511
553
  this.orchestratorUrl = previewOverrides.orchestratorUrl;
@@ -514,6 +556,7 @@ var AmplifyUpSDK = class {
514
556
  this.target = "production";
515
557
  this.apiGatewayUrl = prodUrls.apiGatewayUrl;
516
558
  this.decisionApiUrl = prodUrls.decisionApiUrl;
559
+ this.edgeUrl = prodUrls.edgeUrl;
517
560
  }
518
561
  }
519
562
  this.config = {
@@ -521,7 +564,8 @@ var AmplifyUpSDK = class {
521
564
  debug: config.debug ?? true,
522
565
  // Enable debug by default
523
566
  target: this.target,
524
- dev: config.dev
567
+ dev: config.dev,
568
+ ...config.siteId ? { siteId: config.siteId } : {}
525
569
  };
526
570
  try {
527
571
  const adapterConfig = {
@@ -607,6 +651,7 @@ var AmplifyUpSDK = class {
607
651
  state.visibleMs = 0;
608
652
  state.visibleSince = document.visibilityState === "visible" ? Date.now() : null;
609
653
  state.lastDwellSecondsSent = 0;
654
+ state.hiddenAt = document.visibilityState === "hidden" ? Date.now() : null;
610
655
  let scrollRaf = null;
611
656
  const path = () => this.currentPath();
612
657
  const measureScrollPercent = () => {
@@ -649,12 +694,12 @@ var AmplifyUpSDK = class {
649
694
  state.visibleMs += Date.now() - state.visibleSince;
650
695
  state.visibleSince = Date.now();
651
696
  };
652
- const emitDwell = (force = false) => {
697
+ const emitDwell = (_force = false) => {
653
698
  if (this.isPreviewMode()) return;
654
699
  accumulateVisible();
655
700
  const seconds = Math.floor(state.visibleMs / 1e3);
656
701
  if (seconds <= 0) return;
657
- if (!force && seconds <= state.lastDwellSecondsSent) return;
702
+ if (seconds <= state.lastDwellSecondsSent) return;
658
703
  state.lastDwellSecondsSent = seconds;
659
704
  this.track("Time on Page", {
660
705
  seconds,
@@ -664,14 +709,34 @@ var AmplifyUpSDK = class {
664
709
  this._emitPageDwell = emitDwell;
665
710
  const onVisibility = () => {
666
711
  if (document.visibilityState === "visible") {
712
+ const idleMs = state.hiddenAt != null ? Date.now() - state.hiddenAt : 0;
713
+ state.hiddenAt = null;
714
+ if (idleMs >= SESSION_IDLE_GAP_MS) {
715
+ this.resetEngagementQuiet();
716
+ const resumePath = path();
717
+ this.page(resumePath, { resumed: true });
718
+ this.lastTrackedPath = resumePath;
719
+ if (this.config?.debug) {
720
+ console.log(
721
+ "[AmplifyUp SDK] Idle resume page view",
722
+ resumePath,
723
+ `(hidden ${Math.round(idleMs / 6e4)}m)`
724
+ );
725
+ }
726
+ }
667
727
  state.visibleSince = Date.now();
668
728
  } else {
669
729
  emitDwell(true);
730
+ state.hiddenAt = Date.now();
670
731
  state.visibleSince = null;
671
732
  }
672
733
  };
673
734
  const onPageHide = () => {
674
735
  emitDwell(true);
736
+ if (state.hiddenAt == null) {
737
+ state.hiddenAt = Date.now();
738
+ }
739
+ state.visibleSince = null;
675
740
  };
676
741
  window.addEventListener("scroll", onScroll, { passive: true });
677
742
  document.addEventListener("visibilitychange", onVisibility);
@@ -700,13 +765,19 @@ var AmplifyUpSDK = class {
700
765
  console.log("[AmplifyUp SDK] Page engagement tracking enabled");
701
766
  }
702
767
  }
768
+ /** Flush dwell then reset — used on SPA route changes. */
703
769
  resetEngagementForNewPage() {
704
770
  const emit = this._emitPageDwell;
705
771
  emit?.(true);
772
+ this.resetEngagementQuiet();
773
+ }
774
+ /** Reset dwell/scroll state without emitting (idle resume already flushed on hide). */
775
+ resetEngagementQuiet() {
706
776
  const state = this.pageEngagementState;
707
777
  state.reached = /* @__PURE__ */ new Set();
708
778
  state.visibleMs = 0;
709
779
  state.lastDwellSecondsSent = 0;
780
+ state.hiddenAt = null;
710
781
  state.visibleSince = typeof document !== "undefined" && document.visibilityState === "visible" ? Date.now() : null;
711
782
  this.componentViewSeen.clear();
712
783
  }
@@ -892,12 +963,17 @@ var AmplifyUpSDK = class {
892
963
  });
893
964
  }
894
965
  /**
895
- * Identify user (when they sign up or log in)
966
+ * Identify a visitor.
967
+ * - identify('user_123') — bare string; server maps via org default type
968
+ * - identify({ email: '...', crm_id: '...' }) — explicit types
969
+ * - identify('user_123', traits) — legacy form
896
970
  */
897
- identify(userId, traits) {
971
+ identify(userIdOrIdentifiers, traits) {
898
972
  if (this.isPreviewMode()) {
899
973
  if (this.config?.debug) {
900
- console.log("AmplifyUp SDK: Identify tracking disabled in preview mode", { userId });
974
+ console.log("AmplifyUp SDK: Identify tracking disabled in preview mode", {
975
+ userIdOrIdentifiers
976
+ });
901
977
  }
902
978
  return;
903
979
  }
@@ -913,10 +989,45 @@ var AmplifyUpSDK = class {
913
989
  trackingId: this.config?.trackingId,
914
990
  ...traits
915
991
  };
916
- this.adapter.identify(userId, identifyTraits);
917
- setUserId(userId);
992
+ this.adapter.identify(userIdOrIdentifiers, identifyTraits);
993
+ if (typeof userIdOrIdentifiers === "string") {
994
+ setUserId(userIdOrIdentifiers);
995
+ }
918
996
  if (this.config?.debug) {
919
- console.log("AmplifyUp SDK: User identified", { userId, instanceId });
997
+ console.log("AmplifyUp SDK: User identified", {
998
+ userIdOrIdentifiers,
999
+ instanceId
1000
+ });
1001
+ }
1002
+ }
1003
+ /**
1004
+ * Set profile fields (partial update). Omitted keys are left alone;
1005
+ * an explicit empty value clears the field.
1006
+ */
1007
+ setProfileFields(fields) {
1008
+ if (this.isPreviewMode()) {
1009
+ if (this.config?.debug) {
1010
+ console.log("AmplifyUp SDK: setProfileFields disabled in preview mode", {
1011
+ fields
1012
+ });
1013
+ }
1014
+ return;
1015
+ }
1016
+ if (!this.isInitialized()) {
1017
+ if (this.config?.debug) {
1018
+ console.warn("AmplifyUp SDK: Not initialized. setProfileFields not called.");
1019
+ }
1020
+ return;
1021
+ }
1022
+ const instanceId = getInstanceId();
1023
+ if (typeof this.adapter.setProfileFields === "function") {
1024
+ this.adapter.setProfileFields(fields, {
1025
+ instanceId: instanceId || void 0,
1026
+ trackingId: this.config?.trackingId
1027
+ });
1028
+ }
1029
+ if (this.config?.debug) {
1030
+ console.log("AmplifyUp SDK: Profile fields set", { fields, instanceId });
920
1031
  }
921
1032
  }
922
1033
  /**
@@ -1091,6 +1202,18 @@ Available Console Commands:
1091
1202
  getDecisionApiUrl() {
1092
1203
  return this.decisionApiUrl || void 0;
1093
1204
  }
1205
+ /**
1206
+ * Get Edge Worker URL (production content resolve)
1207
+ */
1208
+ getEdgeUrl() {
1209
+ return this.edgeUrl || void 0;
1210
+ }
1211
+ /**
1212
+ * Optional site id from config (Edge can resolve via trackingId alone).
1213
+ */
1214
+ getSiteId() {
1215
+ return this.siteId || this.config?.siteId || void 0;
1216
+ }
1094
1217
  /**
1095
1218
  * Build API URL for fetching page config from the Layout Provider
1096
1219
  */
@@ -2396,6 +2519,7 @@ function AmplifyRenderer({
2396
2519
  const pageConfig = serverPageConfig || clientHook.pageConfig || null;
2397
2520
  const loading = serverPageConfig ? false : clientHook.loading;
2398
2521
  const error = serverPageConfig ? null : clientHook.error;
2522
+ const contentSource = clientHook.source;
2399
2523
  const [variantCache, setVariantCache] = useState({});
2400
2524
  useEffect(() => {
2401
2525
  if (typeof window !== "undefined") {
@@ -2537,6 +2661,9 @@ function AmplifyRenderer({
2537
2661
  }, [pageConfig]);
2538
2662
  useEffect(() => {
2539
2663
  if (!pageConfig?.layoutTree || !pageConfig.layoutId) return;
2664
+ const roots = pageConfig.layoutTree;
2665
+ const fromEdge = contentSource === "edge" || roots.some((n) => n._contentSource === "edge");
2666
+ if (fromEdge) return;
2540
2667
  const sdk = getSDK();
2541
2668
  const instanceId = sdk.getInstanceId();
2542
2669
  const decisionApiUrl = sdk.getDecisionApiUrl();
@@ -2631,7 +2758,7 @@ function AmplifyRenderer({
2631
2758
  setVariantCache(newCache);
2632
2759
  };
2633
2760
  fetchVariants();
2634
- }, [pageConfig?.layoutTree, pageConfig?.layoutId, pageConfig?.route]);
2761
+ }, [pageConfig?.layoutTree, pageConfig?.layoutId, pageConfig?.route, contentSource]);
2635
2762
  if (loading) {
2636
2763
  return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent || /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50", children: /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4 py-16", children: /* @__PURE__ */ jsxs("div", { className: "animate-pulse", children: [
2637
2764
  /* @__PURE__ */ jsx("div", { className: "h-8 bg-gray-200 rounded w-1/4 mb-4" }),