@amplifyup/sdk 0.1.22 → 0.1.24

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/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @amplifyup/sdk
2
2
 
3
- Official JavaScript SDK for [AmplifyUp](https://amplifyup.ai) — event tracking, page layouts, personalization, and Composer preview on your site.
3
+ Official JavaScript SDK for [AmplifyUp](https://amplifyup.ai) — event tracking, Edge-resolved page layouts, personalization, and Composer preview on your site.
4
+
5
+ **Full guide:** [docs/sdk-site-setup.md](../../docs/sdk-site-setup.md)
4
6
 
5
7
  ## Install
6
8
 
@@ -8,11 +10,11 @@ Official JavaScript SDK for [AmplifyUp](https://amplifyup.ai) — event tracking
8
10
  npm install @amplifyup/sdk
9
11
  ```
10
12
 
11
- ## Quick start
13
+ ## Quick start (production)
12
14
 
13
- ### React / Next.js
15
+ Production is the default. You only need a **tracking ID** from the AmplifyUp dashboard.
14
16
 
15
- Wrap your app with the provider and pass your site **tracking ID** from the AmplifyUp dashboard:
17
+ ### 1. Provider
16
18
 
17
19
  ```tsx
18
20
  import { AmplifyUpProvider } from '@amplifyup/sdk/react';
@@ -26,61 +28,86 @@ export default function RootLayout({ children }) {
26
28
  }
27
29
  ```
28
30
 
29
- ### Track events
31
+ ### 2. Page content
30
32
 
31
- ```typescript
32
- import { init, page, track, identify } from '@amplifyup/sdk';
33
+ ```tsx
34
+ import { AmplifyPageContent, ComponentContextProvider } from '@amplifyup/sdk/react';
35
+ import { Hero } from '@/components/Hero';
33
36
 
34
- await init({ trackingId: 'your-tracking-id' });
37
+ const registry = { Hero };
35
38
 
36
- page('/home');
39
+ function renderComponent(componentId, props, slots, context) {
40
+ const Component = registry[componentId];
41
+ if (!Component) return null;
42
+ return (
43
+ <ComponentContextProvider
44
+ props={props}
45
+ slots={slots}
46
+ layoutNodeId={context?.layoutNodeId}
47
+ componentId={componentId}
48
+ >
49
+ <Component {...props} />
50
+ </ComponentContextProvider>
51
+ );
52
+ }
37
53
 
38
- track('Product Viewed', { productId: '123' });
54
+ export default function Page() {
55
+ return (
56
+ <AmplifyPageContent
57
+ renderComponent={renderComponent}
58
+ fallback={<YourStaticPage />}
59
+ />
60
+ );
61
+ }
62
+ ```
39
63
 
40
- identify('user-123', { email: 'user@example.com' });
64
+ ### 3. Components use Field / Slot
65
+
66
+ ```tsx
67
+ import { Field, Slot } from '@amplifyup/sdk/react';
68
+
69
+ export function Hero() {
70
+ return (
71
+ <section>
72
+ <h1>
73
+ <Field name="heading" fallback="Welcome" />
74
+ </h1>
75
+ </section>
76
+ );
77
+ }
41
78
  ```
42
79
 
43
- ## Composer and hybrid pages
80
+ Field names must match the component registry in AmplifyUp. Connect CMS documents in Composer, then **Deploy** so Edge can serve the page.
44
81
 
45
- If a page has its own native content **and** can be edited in Composer, use `AmplifyPageContent`:
82
+ ## Content sources
46
83
 
47
- - **Visitors** see your AmplifyUp layout, or your native content when no layout is configured yet.
48
- - **Composer preview** shows only the layout canvas — native fallback content is hidden.
84
+ | Mode | Source | When |
85
+ |------|--------|------|
86
+ | Production visitors | Edge `/v1/resolve` | Default |
87
+ | Composer preview | Orchestrator layout provider | `?preview=true` / Composer iframe |
49
88
 
50
- ```tsx
51
- import { isPreviewRequest } from '@amplifyup/sdk';
52
- import { AmplifyPageContent } from '@amplifyup/sdk/react';
89
+ The SDK maps Edge’s resolved tree into `layoutTree` and renders it. Edge already applies personalization and CMS projection — the site does not call CMS or Decision APIs for layout.
53
90
 
54
- export default async function Page({ searchParams }) {
55
- const params = await searchParams;
91
+ ## Track events
56
92
 
57
- if (isPreviewRequest(params)) {
58
- return (
59
- <Layout>
60
- <AmplifyPageContent renderComponent={renderAmplifyComponent} />
61
- </Layout>
62
- );
63
- }
93
+ ```typescript
94
+ import { init, page, track, identify } from '@amplifyup/sdk';
64
95
 
65
- return (
66
- <Layout>
67
- <AmplifyPageContent
68
- fallback={<YourNativePage />}
69
- renderComponent={renderAmplifyComponent}
70
- />
71
- </Layout>
72
- );
73
- }
96
+ await init({ trackingId: 'your-tracking-id' });
97
+
98
+ page('/home');
99
+ track('Product Viewed', { productId: '123' });
100
+ identify('user-123', { email: 'user@example.com' });
74
101
  ```
75
102
 
76
103
  ## Debugging
77
104
 
78
- In the browser console (on your site):
79
-
80
105
  ```js
81
106
  AmplifyUp.getStatus()
82
107
  ```
83
108
 
109
+ React context: `useAmplifyUp()` → `{ pageConfig, loading, error, source, meta }`.
110
+
84
111
  ## License
85
112
 
86
113
  MIT
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { P as PageConfig } from './types-Cy5aWQ5c.mjs';
3
+ import { P as PageConfig } from './types-BxpG9Gsy.mjs';
4
4
 
5
5
  interface AmplifyRendererProps {
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { P as PageConfig } from './types-Cy5aWQ5c.js';
3
+ import { P as PageConfig } from './types-BxpG9Gsy.js';
4
4
 
5
5
  interface AmplifyRendererProps {
6
6
  /**
@@ -3,6 +3,7 @@
3
3
 
4
4
  var react = require('react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
+ require('marked');
6
7
 
7
8
  /* @amplifyup/sdk - https://amplifyup.ai */
8
9
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
@@ -153,17 +154,42 @@ var AWSAdapter = class {
153
154
  };
154
155
  this.sendEvent("/v1/track", eventData);
155
156
  }
156
- identify(userId, traits) {
157
+ identify(userIdOrIdentifiers, traits = {}) {
157
158
  if (!this.initialized) {
158
159
  console.warn("AmplifyUp SDK: Not initialized. Call init() first.");
159
160
  return;
160
161
  }
162
+ const isMap = userIdOrIdentifiers !== null && typeof userIdOrIdentifiers === "object" && !Array.isArray(userIdOrIdentifiers);
163
+ const identifiers = isMap ? userIdOrIdentifiers : traits.identifiers;
164
+ const userId = isMap ? void 0 : String(userIdOrIdentifiers);
161
165
  const eventData = {
162
166
  type: "identify",
163
- userId,
164
167
  anonymousId: traits.instanceId,
165
168
  traits: {
166
- ...traits
169
+ ...traits,
170
+ ...identifiers ? { identifiers } : {}
171
+ },
172
+ ...identifiers ? { identifiers } : {},
173
+ ...userId ? { userId } : {},
174
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
175
+ };
176
+ this.sendEvent("/v1/track", eventData);
177
+ }
178
+ setProfileFields(fields, context = {}) {
179
+ if (!this.initialized) {
180
+ console.warn("AmplifyUp SDK: Not initialized. Call init() first.");
181
+ return;
182
+ }
183
+ const eventData = {
184
+ type: "set_fields",
185
+ anonymousId: context.instanceId,
186
+ fields,
187
+ traits: {
188
+ instanceId: context.instanceId,
189
+ trackingId: context.trackingId
190
+ },
191
+ properties: {
192
+ trackingId: context.trackingId
167
193
  },
168
194
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
169
195
  };
@@ -225,6 +251,7 @@ function clearIds() {
225
251
  var AWS_DEV_API = "https://api-dev.amplifyup.ai";
226
252
  var AWS_PROD_API = "https://api.amplifyup.ai";
227
253
  var PROD_ORCHESTRATOR = "https://amplifyup.ai";
254
+ var PROD_EDGE = "https://edge.amplifyup.ai";
228
255
  function isAmplifyUpTarget(value) {
229
256
  return value === "local" || value === "dev" || value === "production";
230
257
  }
@@ -260,6 +287,10 @@ function localAwsApiUrl() {
260
287
  const fromEnv = typeof process !== "undefined" ? process.env.NEXT_PUBLIC_AMPLIFYUP_API_URL : void 0;
261
288
  return (fromEnv || AWS_DEV_API).replace(/\/$/, "");
262
289
  }
290
+ function localEdgeUrl() {
291
+ const fromEnv = typeof process !== "undefined" ? process.env.NEXT_PUBLIC_EDGE_URL : void 0;
292
+ return (fromEnv || "http://localhost:8787").replace(/\/$/, "");
293
+ }
263
294
  function resolveAmplifyUpUrls(target) {
264
295
  if (target === "local") {
265
296
  const orchestratorUrl2 = localOrchestratorUrl();
@@ -270,18 +301,22 @@ function resolveAmplifyUpUrls(target) {
270
301
  orchestratorUrl: orchestratorUrl2,
271
302
  // Events + personalize → AWS development stack
272
303
  apiGatewayUrl,
273
- decisionApiUrl: `${apiGatewayUrl}/v1/personalize`
304
+ decisionApiUrl: `${apiGatewayUrl}/v1/personalize`,
305
+ edgeUrl: localEdgeUrl()
274
306
  };
275
307
  }
276
308
  const resolvedApi = typeof process !== "undefined" && process.env.NEXT_PUBLIC_AMPLIFYUP_API_URL?.replace(/\/$/, "") || AWS_PROD_API;
277
309
  const orchestratorFromEnv = typeof process !== "undefined" ? process.env.NEXT_PUBLIC_ORCHESTRATOR_URL : void 0;
278
310
  const isLocalOrchestrator = !!orchestratorFromEnv && (orchestratorFromEnv.includes("localhost") || orchestratorFromEnv.includes("127.0.0.1"));
279
311
  const orchestratorUrl = isLocalOrchestrator || !orchestratorFromEnv ? PROD_ORCHESTRATOR : orchestratorFromEnv.replace(/\/$/, "");
312
+ const edgeFromEnv = typeof process !== "undefined" ? process.env.NEXT_PUBLIC_EDGE_URL?.replace(/\/$/, "") : void 0;
313
+ const edgeUrl = edgeFromEnv && !edgeFromEnv.includes("localhost") && !edgeFromEnv.includes("127.0.0.1") ? edgeFromEnv : PROD_EDGE;
280
314
  return {
281
315
  target: "production",
282
316
  apiGatewayUrl: resolvedApi,
283
317
  orchestratorUrl,
284
- decisionApiUrl: `${resolvedApi}/v1/personalize`
318
+ decisionApiUrl: `${resolvedApi}/v1/personalize`,
319
+ edgeUrl
285
320
  };
286
321
  }
287
322
 
@@ -340,6 +375,8 @@ var AmplifyUpSDK = class {
340
375
  this.apiGatewayUrl = "";
341
376
  this.orchestratorUrl = "";
342
377
  this.decisionApiUrl = "";
378
+ this.edgeUrl = "";
379
+ this.siteId = "";
343
380
  this.target = "local";
344
381
  this.initialized = false;
345
382
  /** Prevents overlapping init() from React Strict Mode double-mount */
@@ -496,7 +533,8 @@ var AmplifyUpSDK = class {
496
533
  isInitialized: () => this.isInitialized(),
497
534
  page: (url, properties) => this.page(url, properties),
498
535
  track: (eventName, properties) => this.track(eventName, properties),
499
- identify: (userId, traits) => this.identify(userId, traits)
536
+ identify: (userIdOrIdentifiers, traits) => this.identify(userIdOrIdentifiers, traits),
537
+ setProfileFields: (fields) => this.setProfileFields(fields)
500
538
  };
501
539
  }
502
540
  async performInit(config) {
@@ -511,6 +549,8 @@ var AmplifyUpSDK = class {
511
549
  this.apiGatewayUrl = urls.apiGatewayUrl;
512
550
  this.orchestratorUrl = urls.orchestratorUrl;
513
551
  this.decisionApiUrl = urls.decisionApiUrl;
552
+ this.edgeUrl = urls.edgeUrl;
553
+ this.siteId = config.siteId || "";
514
554
  const previewOverrides = readPreviewQueryOverrides();
515
555
  if (previewOverrides.orchestratorUrl) {
516
556
  this.orchestratorUrl = previewOverrides.orchestratorUrl;
@@ -519,6 +559,7 @@ var AmplifyUpSDK = class {
519
559
  this.target = "production";
520
560
  this.apiGatewayUrl = prodUrls.apiGatewayUrl;
521
561
  this.decisionApiUrl = prodUrls.decisionApiUrl;
562
+ this.edgeUrl = prodUrls.edgeUrl;
522
563
  }
523
564
  }
524
565
  this.config = {
@@ -526,7 +567,8 @@ var AmplifyUpSDK = class {
526
567
  debug: config.debug ?? true,
527
568
  // Enable debug by default
528
569
  target: this.target,
529
- dev: config.dev
570
+ dev: config.dev,
571
+ ...config.siteId ? { siteId: config.siteId } : {}
530
572
  };
531
573
  try {
532
574
  const adapterConfig = {
@@ -924,12 +966,17 @@ var AmplifyUpSDK = class {
924
966
  });
925
967
  }
926
968
  /**
927
- * Identify user (when they sign up or log in)
969
+ * Identify a visitor.
970
+ * - identify('user_123') — bare string; server maps via org default type
971
+ * - identify({ email: '...', crm_id: '...' }) — explicit types
972
+ * - identify('user_123', traits) — legacy form
928
973
  */
929
- identify(userId, traits) {
974
+ identify(userIdOrIdentifiers, traits) {
930
975
  if (this.isPreviewMode()) {
931
976
  if (this.config?.debug) {
932
- console.log("AmplifyUp SDK: Identify tracking disabled in preview mode", { userId });
977
+ console.log("AmplifyUp SDK: Identify tracking disabled in preview mode", {
978
+ userIdOrIdentifiers
979
+ });
933
980
  }
934
981
  return;
935
982
  }
@@ -945,10 +992,45 @@ var AmplifyUpSDK = class {
945
992
  trackingId: this.config?.trackingId,
946
993
  ...traits
947
994
  };
948
- this.adapter.identify(userId, identifyTraits);
949
- setUserId(userId);
995
+ this.adapter.identify(userIdOrIdentifiers, identifyTraits);
996
+ if (typeof userIdOrIdentifiers === "string") {
997
+ setUserId(userIdOrIdentifiers);
998
+ }
999
+ if (this.config?.debug) {
1000
+ console.log("AmplifyUp SDK: User identified", {
1001
+ userIdOrIdentifiers,
1002
+ instanceId
1003
+ });
1004
+ }
1005
+ }
1006
+ /**
1007
+ * Set profile fields (partial update). Omitted keys are left alone;
1008
+ * an explicit empty value clears the field.
1009
+ */
1010
+ setProfileFields(fields) {
1011
+ if (this.isPreviewMode()) {
1012
+ if (this.config?.debug) {
1013
+ console.log("AmplifyUp SDK: setProfileFields disabled in preview mode", {
1014
+ fields
1015
+ });
1016
+ }
1017
+ return;
1018
+ }
1019
+ if (!this.isInitialized()) {
1020
+ if (this.config?.debug) {
1021
+ console.warn("AmplifyUp SDK: Not initialized. setProfileFields not called.");
1022
+ }
1023
+ return;
1024
+ }
1025
+ const instanceId = getInstanceId();
1026
+ if (typeof this.adapter.setProfileFields === "function") {
1027
+ this.adapter.setProfileFields(fields, {
1028
+ instanceId: instanceId || void 0,
1029
+ trackingId: this.config?.trackingId
1030
+ });
1031
+ }
950
1032
  if (this.config?.debug) {
951
- console.log("AmplifyUp SDK: User identified", { userId, instanceId });
1033
+ console.log("AmplifyUp SDK: Profile fields set", { fields, instanceId });
952
1034
  }
953
1035
  }
954
1036
  /**
@@ -1123,6 +1205,18 @@ Available Console Commands:
1123
1205
  getDecisionApiUrl() {
1124
1206
  return this.decisionApiUrl || void 0;
1125
1207
  }
1208
+ /**
1209
+ * Get Edge Worker URL (production content resolve)
1210
+ */
1211
+ getEdgeUrl() {
1212
+ return this.edgeUrl || void 0;
1213
+ }
1214
+ /**
1215
+ * Optional site id from config (Edge can resolve via trackingId alone).
1216
+ */
1217
+ getSiteId() {
1218
+ return this.siteId || this.config?.siteId || void 0;
1219
+ }
1126
1220
  /**
1127
1221
  * Build API URL for fetching page config from the Layout Provider
1128
1222
  */
@@ -2416,6 +2510,53 @@ function useAmplifyUp() {
2416
2510
  }
2417
2511
  return context;
2418
2512
  }
2513
+ var ComponentRenderBoundary = class extends react.Component {
2514
+ constructor() {
2515
+ super(...arguments);
2516
+ this.state = { error: null };
2517
+ }
2518
+ static getDerivedStateFromError(error) {
2519
+ return { error };
2520
+ }
2521
+ componentDidCatch(error) {
2522
+ console.error(
2523
+ `[AmplifyRenderer] "${this.props.componentId}" (${this.props.nodeId}) threw while rendering:`,
2524
+ error
2525
+ );
2526
+ }
2527
+ render() {
2528
+ if (this.state.error) {
2529
+ if (process.env.NODE_ENV !== "production") {
2530
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2531
+ "div",
2532
+ {
2533
+ "data-amplifyup-render-error": "",
2534
+ "data-component-id": this.props.componentId,
2535
+ "data-node-id": this.props.nodeId,
2536
+ style: {
2537
+ margin: "1rem 0",
2538
+ padding: "0.75rem 1rem",
2539
+ border: "1px solid #fca5a5",
2540
+ borderRadius: "0.5rem",
2541
+ background: "#fef2f2",
2542
+ color: "#991b1b",
2543
+ fontSize: "0.875rem"
2544
+ },
2545
+ children: [
2546
+ 'Component "',
2547
+ this.props.componentId,
2548
+ '" failed to render:',
2549
+ " ",
2550
+ this.state.error.message
2551
+ ]
2552
+ }
2553
+ );
2554
+ }
2555
+ return null;
2556
+ }
2557
+ return this.props.children;
2558
+ }
2559
+ };
2419
2560
  function AmplifyRenderer({
2420
2561
  renderComponent,
2421
2562
  pageConfig: serverPageConfig,
@@ -2428,6 +2569,7 @@ function AmplifyRenderer({
2428
2569
  const pageConfig = serverPageConfig || clientHook.pageConfig || null;
2429
2570
  const loading = serverPageConfig ? false : clientHook.loading;
2430
2571
  const error = serverPageConfig ? null : clientHook.error;
2572
+ const contentSource = clientHook.source;
2431
2573
  const [variantCache, setVariantCache] = react.useState({});
2432
2574
  react.useEffect(() => {
2433
2575
  if (typeof window !== "undefined") {
@@ -2569,6 +2711,9 @@ function AmplifyRenderer({
2569
2711
  }, [pageConfig]);
2570
2712
  react.useEffect(() => {
2571
2713
  if (!pageConfig?.layoutTree || !pageConfig.layoutId) return;
2714
+ const roots = pageConfig.layoutTree;
2715
+ const fromEdge = contentSource === "edge" || roots.some((n) => n._contentSource === "edge");
2716
+ if (fromEdge) return;
2572
2717
  const sdk = getSDK();
2573
2718
  const instanceId = sdk.getInstanceId();
2574
2719
  const decisionApiUrl = sdk.getDecisionApiUrl();
@@ -2663,7 +2808,7 @@ function AmplifyRenderer({
2663
2808
  setVariantCache(newCache);
2664
2809
  };
2665
2810
  fetchVariants();
2666
- }, [pageConfig?.layoutTree, pageConfig?.layoutId, pageConfig?.route]);
2811
+ }, [pageConfig?.layoutTree, pageConfig?.layoutId, pageConfig?.route, contentSource]);
2667
2812
  if (loading) {
2668
2813
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingComponent || /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-screen bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "container mx-auto px-4 py-16", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "animate-pulse", children: [
2669
2814
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 bg-gray-200 rounded w-1/4 mb-4" }),
@@ -2768,11 +2913,20 @@ function AmplifyRenderer({
2768
2913
  const slotsToPass = Object.keys(slotChildren).length > 0 ? slotChildren : void 0;
2769
2914
  console.log(`[AmplifyRenderer] Calling renderComponent for ${node.componentId}`, {
2770
2915
  hasSlots: !!slotsToPass,
2771
- slotKeys: slotsToPass ? Object.keys(slotsToPass) : []
2916
+ slotKeys: slotsToPass ? Object.keys(slotsToPass) : [],
2917
+ propKeys: Object.keys(node.props || {}),
2918
+ props: node.props || {}
2772
2919
  });
2773
- return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-node-id": node.id, "data-amplifyup-node": "", children: renderComponent(node.componentId, node.props || {}, slotsToPass, {
2774
- layoutNodeId: node.id
2775
- }) });
2920
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-node-id": node.id, "data-amplifyup-node": "", children: /* @__PURE__ */ jsxRuntime.jsx(
2921
+ ComponentRenderBoundary,
2922
+ {
2923
+ componentId: node.componentId,
2924
+ nodeId: node.id,
2925
+ children: renderComponent(node.componentId, node.props || {}, slotsToPass, {
2926
+ layoutNodeId: node.id
2927
+ })
2928
+ }
2929
+ ) });
2776
2930
  };
2777
2931
  const rawTree = pageConfig.layoutTree;
2778
2932
  const layoutTree = Array.isArray(rawTree) ? rawTree : rawTree && typeof rawTree === "object" && rawTree.componentId ? [rawTree] : [];