@amplifyup/sdk 0.1.43 → 0.1.45

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 resource = opts.pageData?.resources?.[0];
479
+ if (resource && resource.type && resource.type !== input.resourceType) {
480
+ log.warn(
481
+ `AmplifyUp: pageContext resourceType "${input.resourceType}" does not match page resource "${resource.type}"`
482
+ );
483
+ return null;
484
+ }
485
+ const fieldDefs = resource?.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 = resource?.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.resource && 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_RESOURCE_MARKER = "__amplifyupPageResource";
566
+ function isPageResourceSentinel(value) {
567
+ return !!value && typeof value === "object" && !Array.isArray(value) && typeof value[PAGE_RESOURCE_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 hydratePageResourceValue(value, fields) {
582
+ if (!isPageResourceSentinel(value)) return value;
583
+ if (!fields) return null;
584
+ if (value.path) return getByDotPath(fields, value.path) ?? null;
585
+ return fields;
586
+ }
587
+ function hydratePageResourceProps(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 (isPageResourceSentinel(val)) {
592
+ out[key] = hydratePageResourceValue(val, fields);
593
+ }
594
+ }
595
+ return out;
596
+ }
597
+
598
+ // src/page-resource-runtime.ts
599
+ var pageResourceFields = null;
600
+ function setPageResourceFields(fields) {
601
+ pageResourceFields = fields && typeof fields === "object" ? fields : null;
602
+ }
603
+ function getPageResourceFields() {
604
+ return pageResourceFields;
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
+ setPageResourceFields(null);
1573
+ return;
1574
+ }
1575
+ this.attachedPageContext = applyPageContext(this.pendingPageContext, {
1576
+ pageData: this.resolvedPageData,
1577
+ routeParams: this.resolvedRouteParams,
1578
+ pageRoute: this.resolvedPageRoute
1579
+ });
1580
+ setPageResourceFields(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;
@@ -3132,7 +3380,10 @@ function ComponentContextProvider({
3132
3380
  layoutNodeId,
3133
3381
  componentId
3134
3382
  }) {
3135
- const liveProps = useLiveComposerProps(layoutNodeId, props || {});
3383
+ const liveProps = useLiveComposerProps(
3384
+ layoutNodeId,
3385
+ hydratePageResourceProps(props || {}, getPageResourceFields())
3386
+ );
3136
3387
  return /* @__PURE__ */ jsx(
3137
3388
  ComponentContext.Provider,
3138
3389
  {
@@ -4335,7 +4586,11 @@ if (typeof window !== "undefined") {
4335
4586
  }
4336
4587
  }
4337
4588
  var AmplifyUpContext = createContext(null);
4338
- function AmplifyUpProvider({ children, config }) {
4589
+ function AmplifyUpProvider({
4590
+ children,
4591
+ config,
4592
+ pageContext
4593
+ }) {
4339
4594
  const [initialized, setInitialized] = useState(false);
4340
4595
  const [pageConfig, setPageConfig] = useState(null);
4341
4596
  const [loading, setLoading] = useState(true);
@@ -4394,7 +4649,10 @@ function AmplifyUpProvider({ children, config }) {
4394
4649
  useEffect(() => {
4395
4650
  let cancelled = false;
4396
4651
  const sdk = getSDK();
4397
- sdk.init(config).then(() => {
4652
+ sdk.init({
4653
+ ...config,
4654
+ ...pageContext ? { pageContext } : {}
4655
+ }).then(() => {
4398
4656
  if (!cancelled) {
4399
4657
  setInitialized(true);
4400
4658
  }
@@ -4407,7 +4665,11 @@ function AmplifyUpProvider({ children, config }) {
4407
4665
  return () => {
4408
4666
  cancelled = true;
4409
4667
  };
4410
- }, [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]);
4411
4673
  useEffect(() => {
4412
4674
  if (!initialized) return;
4413
4675
  let cancelled = false;
@@ -4482,6 +4744,11 @@ function AmplifyUpProvider({ children, config }) {
4482
4744
  });
4483
4745
  } else {
4484
4746
  setPageConfig(result.pageConfig);
4747
+ sdk.applyResolvedPageData(
4748
+ result.pageConfig.pageData || null,
4749
+ result.pageConfig.route,
4750
+ extractRouteParams(result.pageConfig.route, actualRoute)
4751
+ );
4485
4752
  }
4486
4753
  } catch (err) {
4487
4754
  if (cancelled) return;