@amplifyup/sdk 0.1.42 → 0.1.44

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/react.mjs CHANGED
@@ -405,6 +405,205 @@ function isComposerPreview() {
405
405
  return false;
406
406
  }
407
407
 
408
+ // src/page-context.ts
409
+ function extractRouteParams(pattern, url) {
410
+ const patternParts = String(pattern || "").split("/").filter(Boolean);
411
+ const urlParts = String(url || "").split("?")[0].split("#")[0].split("/").filter(Boolean);
412
+ if (patternParts.length !== urlParts.length) return {};
413
+ const params = {};
414
+ for (let i = 0; i < patternParts.length; i++) {
415
+ const part = patternParts[i];
416
+ if (part.startsWith(":")) {
417
+ params[part.slice(1)] = urlParts[i];
418
+ }
419
+ }
420
+ return params;
421
+ }
422
+ var defaultLog = {
423
+ warn: (...args) => {
424
+ if (typeof console !== "undefined") console.warn(...args);
425
+ }
426
+ };
427
+ function coerceField(def, raw, log) {
428
+ if (raw == null) return void 0;
429
+ if (def.multiValue) {
430
+ const list = Array.isArray(raw) ? raw : [raw];
431
+ const out = [];
432
+ for (const item of list) {
433
+ const coerced = coerceScalar(def, item, log);
434
+ if (coerced !== void 0) out.push(coerced);
435
+ }
436
+ return out;
437
+ }
438
+ return coerceScalar(def, raw, log);
439
+ }
440
+ function coerceScalar(def, raw, log) {
441
+ switch (def.dataType) {
442
+ case "string":
443
+ if (typeof raw === "string") return raw;
444
+ if (typeof raw === "number" || typeof raw === "boolean") return String(raw);
445
+ log.warn(`AmplifyUp: dropped field "${def.key}" (expected string)`);
446
+ return void 0;
447
+ case "number": {
448
+ if (typeof raw === "number" && Number.isFinite(raw)) return raw;
449
+ if (typeof raw === "string" && raw.trim() !== "" && Number.isFinite(Number(raw))) {
450
+ return Number(raw);
451
+ }
452
+ log.warn(`AmplifyUp: dropped field "${def.key}" (expected number)`);
453
+ return void 0;
454
+ }
455
+ case "boolean": {
456
+ if (typeof raw === "boolean") return raw;
457
+ if (raw === "true") return true;
458
+ if (raw === "false") return false;
459
+ log.warn(`AmplifyUp: dropped field "${def.key}" (expected boolean)`);
460
+ return void 0;
461
+ }
462
+ case "date": {
463
+ if (typeof raw === "string" && !Number.isNaN(Date.parse(raw))) return raw;
464
+ log.warn(`AmplifyUp: dropped field "${def.key}" (expected date)`);
465
+ return void 0;
466
+ }
467
+ default:
468
+ return void 0;
469
+ }
470
+ }
471
+ function applyPageContext(input, opts) {
472
+ const log = opts.log || defaultLog;
473
+ const key = String(input.key || "").trim();
474
+ if (!key || !input.resourceType) {
475
+ log.warn("AmplifyUp: pageContext needs resourceType and key");
476
+ return null;
477
+ }
478
+ const slot = opts.pageData?.slots?.[0];
479
+ if (slot && slot.type && slot.type !== input.resourceType) {
480
+ log.warn(
481
+ `AmplifyUp: pageContext resourceType "${input.resourceType}" does not match page slot "${slot.type}"`
482
+ );
483
+ return null;
484
+ }
485
+ const fieldDefs = slot?.fields || [];
486
+ const defByKey = new Map(fieldDefs.map((f) => [f.key, f]));
487
+ const rawFields = input.fields && typeof input.fields === "object" ? input.fields : {};
488
+ const fields = {};
489
+ if (fieldDefs.length === 0) {
490
+ Object.assign(fields, rawFields);
491
+ } else {
492
+ for (const [k, v] of Object.entries(rawFields)) {
493
+ const def = defByKey.get(k);
494
+ if (!def) {
495
+ log.warn(`AmplifyUp: unknown resource field "${k}" dropped`);
496
+ continue;
497
+ }
498
+ const coerced = coerceField(def, v, log);
499
+ if (coerced !== void 0) fields[k] = coerced;
500
+ }
501
+ }
502
+ const params = { ...opts.routeParams || {} };
503
+ const mapping = slot?.urlParamMapping;
504
+ let resourceKey = key;
505
+ if (mapping) {
506
+ const fromUrl = params[mapping.param];
507
+ const fromField = fields[mapping.field];
508
+ if (fromUrl) resourceKey = String(fromUrl);
509
+ if (fromUrl && fromField != null && String(fromField) !== String(fromUrl)) {
510
+ log.warn(
511
+ `AmplifyUp: resource key mismatch \u2014 URL :${mapping.param} is "${fromUrl}" but ${mapping.field} is "${String(fromField)}". Using the URL param.`
512
+ );
513
+ }
514
+ }
515
+ const pageFields = {};
516
+ for (const pf of opts.pageData?.pageFields || []) {
517
+ if (pf.source === "static") {
518
+ if (pf.value !== void 0) pageFields[pf.key] = pf.value;
519
+ continue;
520
+ }
521
+ if (pf.source === "mapped" && pf.slot && pf.field) {
522
+ pageFields[pf.key] = fields[pf.field];
523
+ }
524
+ }
525
+ return {
526
+ resource: {
527
+ type: input.resourceType,
528
+ key: resourceKey,
529
+ params,
530
+ fields
531
+ },
532
+ page: { fields: pageFields },
533
+ pageRoute: opts.pageRoute || ""
534
+ };
535
+ }
536
+ function staticPageFieldsFromSnapshot(pageData) {
537
+ const pageFields = {};
538
+ for (const pf of pageData?.pageFields || []) {
539
+ if (pf.source === "static" && pf.value !== void 0) {
540
+ pageFields[pf.key] = pf.value;
541
+ }
542
+ }
543
+ return pageFields;
544
+ }
545
+ function attachStaticPageContext(pageData, pageRoute) {
546
+ if (!pageData) return null;
547
+ const fields = staticPageFieldsFromSnapshot(pageData);
548
+ const hasStatic = Object.keys(fields).length > 0;
549
+ const hasRoute = !!pageRoute;
550
+ if (!hasStatic && !hasRoute) return null;
551
+ return {
552
+ page: { fields },
553
+ pageRoute: pageRoute || ""
554
+ };
555
+ }
556
+ function mergeEventContext(properties, attached) {
557
+ if (!attached) return properties;
558
+ return {
559
+ ...properties,
560
+ ...attached.resource ? { resource: attached.resource } : {},
561
+ page: attached.page,
562
+ ...attached.pageRoute ? { pageRoute: attached.pageRoute } : {}
563
+ };
564
+ }
565
+ var PAGE_SLOT_MARKER = "__amplifyupPageSlot";
566
+ function isPageSlotSentinel(value) {
567
+ return !!value && typeof value === "object" && !Array.isArray(value) && typeof value[PAGE_SLOT_MARKER] === "string";
568
+ }
569
+ function getByDotPath(source, path) {
570
+ if (!path) return source;
571
+ const parts = path.split(".").filter(Boolean);
572
+ let cur = source;
573
+ for (const part of parts) {
574
+ if (cur == null || typeof cur !== "object" || Array.isArray(cur)) {
575
+ return void 0;
576
+ }
577
+ cur = cur[part];
578
+ }
579
+ return cur;
580
+ }
581
+ function hydratePageSlotValue(value, fields) {
582
+ if (!isPageSlotSentinel(value)) return value;
583
+ if (!fields) return null;
584
+ if (value.path) return getByDotPath(fields, value.path) ?? null;
585
+ return fields;
586
+ }
587
+ function hydratePageSlotProps(props, fields) {
588
+ const source = props && typeof props === "object" ? props : {};
589
+ const out = { ...source };
590
+ for (const [key, val] of Object.entries(out)) {
591
+ if (isPageSlotSentinel(val)) {
592
+ out[key] = hydratePageSlotValue(val, fields);
593
+ }
594
+ }
595
+ return out;
596
+ }
597
+
598
+ // src/page-slot-runtime.ts
599
+ var pageSlotFields = null;
600
+ function setPageSlotFields(fields) {
601
+ pageSlotFields = fields && typeof fields === "object" ? fields : null;
602
+ }
603
+ function getPageSlotFields() {
604
+ return pageSlotFields;
605
+ }
606
+
408
607
  // src/edge/normalizeRoute.ts
409
608
  function normalizeRoute(route) {
410
609
  let path = String(route || "").trim();
@@ -524,7 +723,8 @@ function mapResolvedToPageConfig(payload) {
524
723
  // Stash edge meta for diagnostics (not visitor-facing)
525
724
  description: payload.meta?.degraded ? "degraded" : void 0
526
725
  },
527
- ...payload.eventTracking && payload.eventTracking.length > 0 ? { eventTracking: payload.eventTracking } : {}
726
+ ...payload.eventTracking && payload.eventTracking.length > 0 ? { eventTracking: payload.eventTracking } : {},
727
+ ...payload.pageData ? { pageData: payload.pageData } : {}
528
728
  };
529
729
  }
530
730
  function collectDegraded(payload) {
@@ -814,6 +1014,11 @@ var AmplifyUpSDK = class {
814
1014
  this.pageEngagementActive = false;
815
1015
  /** Path of the last page() / routeChange — avoids duplicate SPA fires. */
816
1016
  this.lastTrackedPath = null;
1017
+ this.pendingPageContext = null;
1018
+ this.attachedPageContext = null;
1019
+ this.resolvedPageData = null;
1020
+ this.resolvedPageRoute = "";
1021
+ this.resolvedRouteParams = {};
817
1022
  this.historyHooksInstalled = false;
818
1023
  this.pageEngagementState = {
819
1024
  reached: /* @__PURE__ */ new Set(),
@@ -976,8 +1181,13 @@ var AmplifyUpSDK = class {
976
1181
  // Enable debug by default
977
1182
  target: this.target,
978
1183
  dev: config.dev,
979
- ...config.siteId ? { siteId: config.siteId } : {}
1184
+ ...config.siteId ? { siteId: config.siteId } : {},
1185
+ ...config.pageContext ? { pageContext: config.pageContext } : {}
980
1186
  };
1187
+ if (config.pageContext) {
1188
+ this.pendingPageContext = config.pageContext;
1189
+ this.recomputeAttachedContext();
1190
+ }
981
1191
  try {
982
1192
  const adapterConfig = {
983
1193
  ...this.config,
@@ -1334,6 +1544,44 @@ var AmplifyUpSDK = class {
1334
1544
  this.componentViewObservers.forEach((obs) => obs.disconnect());
1335
1545
  this.componentViewObservers = [];
1336
1546
  }
1547
+ /**
1548
+ * Set resource context for this page. Prefer passing `pageContext` on
1549
+ * AmplifyUpProvider / init — required for components using From page.
1550
+ * Late calls still attach to subsequent events (not a second page view).
1551
+ */
1552
+ setPageContext(input) {
1553
+ this.pendingPageContext = input;
1554
+ this.recomputeAttachedContext();
1555
+ }
1556
+ /**
1557
+ * Apply published pageData (from /v1/resolve) so field validation and
1558
+ * mapped page fields can run.
1559
+ */
1560
+ applyResolvedPageData(pageData, pageRoute, routeParams) {
1561
+ this.resolvedPageData = pageData || null;
1562
+ if (pageRoute) this.resolvedPageRoute = pageRoute;
1563
+ if (routeParams) this.resolvedRouteParams = routeParams;
1564
+ this.recomputeAttachedContext();
1565
+ }
1566
+ recomputeAttachedContext() {
1567
+ if (!this.pendingPageContext) {
1568
+ this.attachedPageContext = attachStaticPageContext(
1569
+ this.resolvedPageData,
1570
+ this.resolvedPageRoute
1571
+ );
1572
+ setPageSlotFields(null);
1573
+ return;
1574
+ }
1575
+ this.attachedPageContext = applyPageContext(this.pendingPageContext, {
1576
+ pageData: this.resolvedPageData,
1577
+ routeParams: this.resolvedRouteParams,
1578
+ pageRoute: this.resolvedPageRoute
1579
+ });
1580
+ setPageSlotFields(this.attachedPageContext?.resource?.fields || null);
1581
+ }
1582
+ getAttachedPageContext() {
1583
+ return this.attachedPageContext;
1584
+ }
1337
1585
  /**
1338
1586
  * Track a page view
1339
1587
  */
@@ -1355,7 +1603,7 @@ var AmplifyUpSDK = class {
1355
1603
  sessionId,
1356
1604
  trackingId: this.config?.trackingId,
1357
1605
  // Timestamp is included in event payload
1358
- ...properties
1606
+ ...mergeEventContext(properties || {}, this.attachedPageContext)
1359
1607
  };
1360
1608
  this.adapter.page(pageProperties);
1361
1609
  this.lastTrackedPath = url;
@@ -1379,7 +1627,7 @@ var AmplifyUpSDK = class {
1379
1627
  url: typeof window !== "undefined" ? window.location.href : void 0,
1380
1628
  path: properties?.path || (typeof window !== "undefined" ? window.location.pathname : void 0),
1381
1629
  // Timestamp is included in event payload
1382
- ...properties
1630
+ ...mergeEventContext(properties || {}, this.attachedPageContext)
1383
1631
  };
1384
1632
  if (!trackProperties.path && typeof window !== "undefined") {
1385
1633
  trackProperties.path = window.location.pathname;
@@ -1704,8 +1952,15 @@ Available Console Commands:
1704
1952
  });
1705
1953
  if (preview) {
1706
1954
  params.set("preview", "true");
1707
- if (typeof window !== "undefined" && isWorkingContentRequest(new URLSearchParams(window.location.search))) {
1708
- params.set("working", "true");
1955
+ if (typeof window !== "undefined") {
1956
+ const pageSearch = new URLSearchParams(window.location.search);
1957
+ if (isWorkingContentRequest(pageSearch)) {
1958
+ params.set("working", "true");
1959
+ }
1960
+ const zoneId = pageSearch.get("zoneId");
1961
+ const compositionId = pageSearch.get("compositionId");
1962
+ if (zoneId) params.set("zoneId", zoneId);
1963
+ if (compositionId) params.set("compositionId", compositionId);
1709
1964
  }
1710
1965
  }
1711
1966
  if (isOnOrchestratorOrigin) {
@@ -3125,7 +3380,10 @@ function ComponentContextProvider({
3125
3380
  layoutNodeId,
3126
3381
  componentId
3127
3382
  }) {
3128
- const liveProps = useLiveComposerProps(layoutNodeId, props || {});
3383
+ const liveProps = useLiveComposerProps(
3384
+ layoutNodeId,
3385
+ hydratePageSlotProps(props || {}, getPageSlotFields())
3386
+ );
3129
3387
  return /* @__PURE__ */ jsx(
3130
3388
  ComponentContext.Provider,
3131
3389
  {
@@ -4279,6 +4537,17 @@ async function fetchPageConfigServer(route, trackingId, preview = false, options
4279
4537
  });
4280
4538
  if (isPreview) params.set("preview", "true");
4281
4539
  if (workingFlag) params.set("working", "true");
4540
+ if (options?.searchParams) {
4541
+ const sp = options.searchParams instanceof URLSearchParams ? options.searchParams : new URLSearchParams(
4542
+ Object.entries(options.searchParams).flatMap(
4543
+ ([k, v]) => v == null ? [] : Array.isArray(v) ? v.map((item) => [k, item]) : [[k, v]]
4544
+ )
4545
+ );
4546
+ const zoneId = sp.get("zoneId");
4547
+ const compositionId = sp.get("compositionId");
4548
+ if (zoneId) params.set("zoneId", zoneId);
4549
+ if (compositionId) params.set("compositionId", compositionId);
4550
+ }
4282
4551
  const url = `${orchestratorUrl}/api/layout/provider?${params.toString()}`;
4283
4552
  try {
4284
4553
  const res = await fetch(url, {
@@ -4317,7 +4586,11 @@ if (typeof window !== "undefined") {
4317
4586
  }
4318
4587
  }
4319
4588
  var AmplifyUpContext = createContext(null);
4320
- function AmplifyUpProvider({ children, config }) {
4589
+ function AmplifyUpProvider({
4590
+ children,
4591
+ config,
4592
+ pageContext
4593
+ }) {
4321
4594
  const [initialized, setInitialized] = useState(false);
4322
4595
  const [pageConfig, setPageConfig] = useState(null);
4323
4596
  const [loading, setLoading] = useState(true);
@@ -4376,7 +4649,10 @@ function AmplifyUpProvider({ children, config }) {
4376
4649
  useEffect(() => {
4377
4650
  let cancelled = false;
4378
4651
  const sdk = getSDK();
4379
- sdk.init(config).then(() => {
4652
+ sdk.init({
4653
+ ...config,
4654
+ ...pageContext ? { pageContext } : {}
4655
+ }).then(() => {
4380
4656
  if (!cancelled) {
4381
4657
  setInitialized(true);
4382
4658
  }
@@ -4389,7 +4665,11 @@ function AmplifyUpProvider({ children, config }) {
4389
4665
  return () => {
4390
4666
  cancelled = true;
4391
4667
  };
4392
- }, [config.trackingId, config.target, config.debug, config.dev, config.siteId]);
4668
+ }, [config.trackingId, config.target, config.debug, config.dev, config.siteId, pageContext]);
4669
+ useEffect(() => {
4670
+ if (!initialized || !pageContext) return;
4671
+ getSDK().setPageContext(pageContext);
4672
+ }, [initialized, pageContext]);
4393
4673
  useEffect(() => {
4394
4674
  if (!initialized) return;
4395
4675
  let cancelled = false;
@@ -4464,6 +4744,11 @@ function AmplifyUpProvider({ children, config }) {
4464
4744
  });
4465
4745
  } else {
4466
4746
  setPageConfig(result.pageConfig);
4747
+ sdk.applyResolvedPageData(
4748
+ result.pageConfig.pageData || null,
4749
+ result.pageConfig.route,
4750
+ extractRouteParams(result.pageConfig.route, actualRoute)
4751
+ );
4467
4752
  }
4468
4753
  } catch (err) {
4469
4754
  if (cancelled) return;