@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.js CHANGED
@@ -411,6 +411,205 @@ function isComposerPreview() {
411
411
  return false;
412
412
  }
413
413
 
414
+ // src/page-context.ts
415
+ function extractRouteParams(pattern, url) {
416
+ const patternParts = String(pattern || "").split("/").filter(Boolean);
417
+ const urlParts = String(url || "").split("?")[0].split("#")[0].split("/").filter(Boolean);
418
+ if (patternParts.length !== urlParts.length) return {};
419
+ const params = {};
420
+ for (let i = 0; i < patternParts.length; i++) {
421
+ const part = patternParts[i];
422
+ if (part.startsWith(":")) {
423
+ params[part.slice(1)] = urlParts[i];
424
+ }
425
+ }
426
+ return params;
427
+ }
428
+ var defaultLog = {
429
+ warn: (...args) => {
430
+ if (typeof console !== "undefined") console.warn(...args);
431
+ }
432
+ };
433
+ function coerceField(def, raw, log) {
434
+ if (raw == null) return void 0;
435
+ if (def.multiValue) {
436
+ const list = Array.isArray(raw) ? raw : [raw];
437
+ const out = [];
438
+ for (const item of list) {
439
+ const coerced = coerceScalar(def, item, log);
440
+ if (coerced !== void 0) out.push(coerced);
441
+ }
442
+ return out;
443
+ }
444
+ return coerceScalar(def, raw, log);
445
+ }
446
+ function coerceScalar(def, raw, log) {
447
+ switch (def.dataType) {
448
+ case "string":
449
+ if (typeof raw === "string") return raw;
450
+ if (typeof raw === "number" || typeof raw === "boolean") return String(raw);
451
+ log.warn(`AmplifyUp: dropped field "${def.key}" (expected string)`);
452
+ return void 0;
453
+ case "number": {
454
+ if (typeof raw === "number" && Number.isFinite(raw)) return raw;
455
+ if (typeof raw === "string" && raw.trim() !== "" && Number.isFinite(Number(raw))) {
456
+ return Number(raw);
457
+ }
458
+ log.warn(`AmplifyUp: dropped field "${def.key}" (expected number)`);
459
+ return void 0;
460
+ }
461
+ case "boolean": {
462
+ if (typeof raw === "boolean") return raw;
463
+ if (raw === "true") return true;
464
+ if (raw === "false") return false;
465
+ log.warn(`AmplifyUp: dropped field "${def.key}" (expected boolean)`);
466
+ return void 0;
467
+ }
468
+ case "date": {
469
+ if (typeof raw === "string" && !Number.isNaN(Date.parse(raw))) return raw;
470
+ log.warn(`AmplifyUp: dropped field "${def.key}" (expected date)`);
471
+ return void 0;
472
+ }
473
+ default:
474
+ return void 0;
475
+ }
476
+ }
477
+ function applyPageContext(input, opts) {
478
+ const log = opts.log || defaultLog;
479
+ const key = String(input.key || "").trim();
480
+ if (!key || !input.resourceType) {
481
+ log.warn("AmplifyUp: pageContext needs resourceType and key");
482
+ return null;
483
+ }
484
+ const slot = opts.pageData?.slots?.[0];
485
+ if (slot && slot.type && slot.type !== input.resourceType) {
486
+ log.warn(
487
+ `AmplifyUp: pageContext resourceType "${input.resourceType}" does not match page slot "${slot.type}"`
488
+ );
489
+ return null;
490
+ }
491
+ const fieldDefs = slot?.fields || [];
492
+ const defByKey = new Map(fieldDefs.map((f) => [f.key, f]));
493
+ const rawFields = input.fields && typeof input.fields === "object" ? input.fields : {};
494
+ const fields = {};
495
+ if (fieldDefs.length === 0) {
496
+ Object.assign(fields, rawFields);
497
+ } else {
498
+ for (const [k, v] of Object.entries(rawFields)) {
499
+ const def = defByKey.get(k);
500
+ if (!def) {
501
+ log.warn(`AmplifyUp: unknown resource field "${k}" dropped`);
502
+ continue;
503
+ }
504
+ const coerced = coerceField(def, v, log);
505
+ if (coerced !== void 0) fields[k] = coerced;
506
+ }
507
+ }
508
+ const params = { ...opts.routeParams || {} };
509
+ const mapping = slot?.urlParamMapping;
510
+ let resourceKey = key;
511
+ if (mapping) {
512
+ const fromUrl = params[mapping.param];
513
+ const fromField = fields[mapping.field];
514
+ if (fromUrl) resourceKey = String(fromUrl);
515
+ if (fromUrl && fromField != null && String(fromField) !== String(fromUrl)) {
516
+ log.warn(
517
+ `AmplifyUp: resource key mismatch \u2014 URL :${mapping.param} is "${fromUrl}" but ${mapping.field} is "${String(fromField)}". Using the URL param.`
518
+ );
519
+ }
520
+ }
521
+ const pageFields = {};
522
+ for (const pf of opts.pageData?.pageFields || []) {
523
+ if (pf.source === "static") {
524
+ if (pf.value !== void 0) pageFields[pf.key] = pf.value;
525
+ continue;
526
+ }
527
+ if (pf.source === "mapped" && pf.slot && pf.field) {
528
+ pageFields[pf.key] = fields[pf.field];
529
+ }
530
+ }
531
+ return {
532
+ resource: {
533
+ type: input.resourceType,
534
+ key: resourceKey,
535
+ params,
536
+ fields
537
+ },
538
+ page: { fields: pageFields },
539
+ pageRoute: opts.pageRoute || ""
540
+ };
541
+ }
542
+ function staticPageFieldsFromSnapshot(pageData) {
543
+ const pageFields = {};
544
+ for (const pf of pageData?.pageFields || []) {
545
+ if (pf.source === "static" && pf.value !== void 0) {
546
+ pageFields[pf.key] = pf.value;
547
+ }
548
+ }
549
+ return pageFields;
550
+ }
551
+ function attachStaticPageContext(pageData, pageRoute) {
552
+ if (!pageData) return null;
553
+ const fields = staticPageFieldsFromSnapshot(pageData);
554
+ const hasStatic = Object.keys(fields).length > 0;
555
+ const hasRoute = !!pageRoute;
556
+ if (!hasStatic && !hasRoute) return null;
557
+ return {
558
+ page: { fields },
559
+ pageRoute: pageRoute || ""
560
+ };
561
+ }
562
+ function mergeEventContext(properties, attached) {
563
+ if (!attached) return properties;
564
+ return {
565
+ ...properties,
566
+ ...attached.resource ? { resource: attached.resource } : {},
567
+ page: attached.page,
568
+ ...attached.pageRoute ? { pageRoute: attached.pageRoute } : {}
569
+ };
570
+ }
571
+ var PAGE_SLOT_MARKER = "__amplifyupPageSlot";
572
+ function isPageSlotSentinel(value) {
573
+ return !!value && typeof value === "object" && !Array.isArray(value) && typeof value[PAGE_SLOT_MARKER] === "string";
574
+ }
575
+ function getByDotPath(source, path) {
576
+ if (!path) return source;
577
+ const parts = path.split(".").filter(Boolean);
578
+ let cur = source;
579
+ for (const part of parts) {
580
+ if (cur == null || typeof cur !== "object" || Array.isArray(cur)) {
581
+ return void 0;
582
+ }
583
+ cur = cur[part];
584
+ }
585
+ return cur;
586
+ }
587
+ function hydratePageSlotValue(value, fields) {
588
+ if (!isPageSlotSentinel(value)) return value;
589
+ if (!fields) return null;
590
+ if (value.path) return getByDotPath(fields, value.path) ?? null;
591
+ return fields;
592
+ }
593
+ function hydratePageSlotProps(props, fields) {
594
+ const source = props && typeof props === "object" ? props : {};
595
+ const out = { ...source };
596
+ for (const [key, val] of Object.entries(out)) {
597
+ if (isPageSlotSentinel(val)) {
598
+ out[key] = hydratePageSlotValue(val, fields);
599
+ }
600
+ }
601
+ return out;
602
+ }
603
+
604
+ // src/page-slot-runtime.ts
605
+ var pageSlotFields = null;
606
+ function setPageSlotFields(fields) {
607
+ pageSlotFields = fields && typeof fields === "object" ? fields : null;
608
+ }
609
+ function getPageSlotFields() {
610
+ return pageSlotFields;
611
+ }
612
+
414
613
  // src/edge/normalizeRoute.ts
415
614
  function normalizeRoute(route) {
416
615
  let path = String(route || "").trim();
@@ -530,7 +729,8 @@ function mapResolvedToPageConfig(payload) {
530
729
  // Stash edge meta for diagnostics (not visitor-facing)
531
730
  description: payload.meta?.degraded ? "degraded" : void 0
532
731
  },
533
- ...payload.eventTracking && payload.eventTracking.length > 0 ? { eventTracking: payload.eventTracking } : {}
732
+ ...payload.eventTracking && payload.eventTracking.length > 0 ? { eventTracking: payload.eventTracking } : {},
733
+ ...payload.pageData ? { pageData: payload.pageData } : {}
534
734
  };
535
735
  }
536
736
  function collectDegraded(payload) {
@@ -820,6 +1020,11 @@ var AmplifyUpSDK = class {
820
1020
  this.pageEngagementActive = false;
821
1021
  /** Path of the last page() / routeChange — avoids duplicate SPA fires. */
822
1022
  this.lastTrackedPath = null;
1023
+ this.pendingPageContext = null;
1024
+ this.attachedPageContext = null;
1025
+ this.resolvedPageData = null;
1026
+ this.resolvedPageRoute = "";
1027
+ this.resolvedRouteParams = {};
823
1028
  this.historyHooksInstalled = false;
824
1029
  this.pageEngagementState = {
825
1030
  reached: /* @__PURE__ */ new Set(),
@@ -982,8 +1187,13 @@ var AmplifyUpSDK = class {
982
1187
  // Enable debug by default
983
1188
  target: this.target,
984
1189
  dev: config.dev,
985
- ...config.siteId ? { siteId: config.siteId } : {}
1190
+ ...config.siteId ? { siteId: config.siteId } : {},
1191
+ ...config.pageContext ? { pageContext: config.pageContext } : {}
986
1192
  };
1193
+ if (config.pageContext) {
1194
+ this.pendingPageContext = config.pageContext;
1195
+ this.recomputeAttachedContext();
1196
+ }
987
1197
  try {
988
1198
  const adapterConfig = {
989
1199
  ...this.config,
@@ -1340,6 +1550,44 @@ var AmplifyUpSDK = class {
1340
1550
  this.componentViewObservers.forEach((obs) => obs.disconnect());
1341
1551
  this.componentViewObservers = [];
1342
1552
  }
1553
+ /**
1554
+ * Set resource context for this page. Prefer passing `pageContext` on
1555
+ * AmplifyUpProvider / init — required for components using From page.
1556
+ * Late calls still attach to subsequent events (not a second page view).
1557
+ */
1558
+ setPageContext(input) {
1559
+ this.pendingPageContext = input;
1560
+ this.recomputeAttachedContext();
1561
+ }
1562
+ /**
1563
+ * Apply published pageData (from /v1/resolve) so field validation and
1564
+ * mapped page fields can run.
1565
+ */
1566
+ applyResolvedPageData(pageData, pageRoute, routeParams) {
1567
+ this.resolvedPageData = pageData || null;
1568
+ if (pageRoute) this.resolvedPageRoute = pageRoute;
1569
+ if (routeParams) this.resolvedRouteParams = routeParams;
1570
+ this.recomputeAttachedContext();
1571
+ }
1572
+ recomputeAttachedContext() {
1573
+ if (!this.pendingPageContext) {
1574
+ this.attachedPageContext = attachStaticPageContext(
1575
+ this.resolvedPageData,
1576
+ this.resolvedPageRoute
1577
+ );
1578
+ setPageSlotFields(null);
1579
+ return;
1580
+ }
1581
+ this.attachedPageContext = applyPageContext(this.pendingPageContext, {
1582
+ pageData: this.resolvedPageData,
1583
+ routeParams: this.resolvedRouteParams,
1584
+ pageRoute: this.resolvedPageRoute
1585
+ });
1586
+ setPageSlotFields(this.attachedPageContext?.resource?.fields || null);
1587
+ }
1588
+ getAttachedPageContext() {
1589
+ return this.attachedPageContext;
1590
+ }
1343
1591
  /**
1344
1592
  * Track a page view
1345
1593
  */
@@ -1361,7 +1609,7 @@ var AmplifyUpSDK = class {
1361
1609
  sessionId,
1362
1610
  trackingId: this.config?.trackingId,
1363
1611
  // Timestamp is included in event payload
1364
- ...properties
1612
+ ...mergeEventContext(properties || {}, this.attachedPageContext)
1365
1613
  };
1366
1614
  this.adapter.page(pageProperties);
1367
1615
  this.lastTrackedPath = url;
@@ -1385,7 +1633,7 @@ var AmplifyUpSDK = class {
1385
1633
  url: typeof window !== "undefined" ? window.location.href : void 0,
1386
1634
  path: properties?.path || (typeof window !== "undefined" ? window.location.pathname : void 0),
1387
1635
  // Timestamp is included in event payload
1388
- ...properties
1636
+ ...mergeEventContext(properties || {}, this.attachedPageContext)
1389
1637
  };
1390
1638
  if (!trackProperties.path && typeof window !== "undefined") {
1391
1639
  trackProperties.path = window.location.pathname;
@@ -1710,8 +1958,15 @@ Available Console Commands:
1710
1958
  });
1711
1959
  if (preview) {
1712
1960
  params.set("preview", "true");
1713
- if (typeof window !== "undefined" && isWorkingContentRequest(new URLSearchParams(window.location.search))) {
1714
- params.set("working", "true");
1961
+ if (typeof window !== "undefined") {
1962
+ const pageSearch = new URLSearchParams(window.location.search);
1963
+ if (isWorkingContentRequest(pageSearch)) {
1964
+ params.set("working", "true");
1965
+ }
1966
+ const zoneId = pageSearch.get("zoneId");
1967
+ const compositionId = pageSearch.get("compositionId");
1968
+ if (zoneId) params.set("zoneId", zoneId);
1969
+ if (compositionId) params.set("compositionId", compositionId);
1715
1970
  }
1716
1971
  }
1717
1972
  if (isOnOrchestratorOrigin) {
@@ -3131,7 +3386,10 @@ function ComponentContextProvider({
3131
3386
  layoutNodeId,
3132
3387
  componentId
3133
3388
  }) {
3134
- const liveProps = useLiveComposerProps(layoutNodeId, props || {});
3389
+ const liveProps = useLiveComposerProps(
3390
+ layoutNodeId,
3391
+ hydratePageSlotProps(props || {}, getPageSlotFields())
3392
+ );
3135
3393
  return /* @__PURE__ */ jsxRuntime.jsx(
3136
3394
  ComponentContext.Provider,
3137
3395
  {
@@ -4285,6 +4543,17 @@ async function fetchPageConfigServer(route, trackingId, preview = false, options
4285
4543
  });
4286
4544
  if (isPreview) params.set("preview", "true");
4287
4545
  if (workingFlag) params.set("working", "true");
4546
+ if (options?.searchParams) {
4547
+ const sp = options.searchParams instanceof URLSearchParams ? options.searchParams : new URLSearchParams(
4548
+ Object.entries(options.searchParams).flatMap(
4549
+ ([k, v]) => v == null ? [] : Array.isArray(v) ? v.map((item) => [k, item]) : [[k, v]]
4550
+ )
4551
+ );
4552
+ const zoneId = sp.get("zoneId");
4553
+ const compositionId = sp.get("compositionId");
4554
+ if (zoneId) params.set("zoneId", zoneId);
4555
+ if (compositionId) params.set("compositionId", compositionId);
4556
+ }
4288
4557
  const url = `${orchestratorUrl}/api/layout/provider?${params.toString()}`;
4289
4558
  try {
4290
4559
  const res = await fetch(url, {
@@ -4323,7 +4592,11 @@ if (typeof window !== "undefined") {
4323
4592
  }
4324
4593
  }
4325
4594
  var AmplifyUpContext = React.createContext(null);
4326
- function AmplifyUpProvider({ children, config }) {
4595
+ function AmplifyUpProvider({
4596
+ children,
4597
+ config,
4598
+ pageContext
4599
+ }) {
4327
4600
  const [initialized, setInitialized] = React.useState(false);
4328
4601
  const [pageConfig, setPageConfig] = React.useState(null);
4329
4602
  const [loading, setLoading] = React.useState(true);
@@ -4382,7 +4655,10 @@ function AmplifyUpProvider({ children, config }) {
4382
4655
  React.useEffect(() => {
4383
4656
  let cancelled = false;
4384
4657
  const sdk = getSDK();
4385
- sdk.init(config).then(() => {
4658
+ sdk.init({
4659
+ ...config,
4660
+ ...pageContext ? { pageContext } : {}
4661
+ }).then(() => {
4386
4662
  if (!cancelled) {
4387
4663
  setInitialized(true);
4388
4664
  }
@@ -4395,7 +4671,11 @@ function AmplifyUpProvider({ children, config }) {
4395
4671
  return () => {
4396
4672
  cancelled = true;
4397
4673
  };
4398
- }, [config.trackingId, config.target, config.debug, config.dev, config.siteId]);
4674
+ }, [config.trackingId, config.target, config.debug, config.dev, config.siteId, pageContext]);
4675
+ React.useEffect(() => {
4676
+ if (!initialized || !pageContext) return;
4677
+ getSDK().setPageContext(pageContext);
4678
+ }, [initialized, pageContext]);
4399
4679
  React.useEffect(() => {
4400
4680
  if (!initialized) return;
4401
4681
  let cancelled = false;
@@ -4470,6 +4750,11 @@ function AmplifyUpProvider({ children, config }) {
4470
4750
  });
4471
4751
  } else {
4472
4752
  setPageConfig(result.pageConfig);
4753
+ sdk.applyResolvedPageData(
4754
+ result.pageConfig.pageData || null,
4755
+ result.pageConfig.route,
4756
+ extractRouteParams(result.pageConfig.route, actualRoute)
4757
+ );
4473
4758
  }
4474
4759
  } catch (err) {
4475
4760
  if (cancelled) return;