personalize-connect-sdk 1.2.0 → 1.2.1

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/index.d.mts CHANGED
@@ -63,6 +63,8 @@ interface PersonalizeConnectProviderProps {
63
63
  resolveDatasource?: (datasourceId: string) => Promise<ComponentFields>;
64
64
  /** Override editing mode detection. When true the HOC renders a visual indicator. */
65
65
  isEditing?: boolean;
66
+ /** Enable debug logging to the browser console. */
67
+ debug?: boolean;
66
68
  children: ReactNode;
67
69
  }
68
70
  /** Resolved datasource fields passed as component props */
@@ -88,7 +90,7 @@ interface PersonalizeContextValue {
88
90
  siteName: string;
89
91
  }
90
92
 
91
- declare function PersonalizeProvider({ children, sitecoreEdgeContextId, sitecoreEdgeUrl, siteName, clientKey, pointOfSale, edgeUrl, apiKey, channel, language, currencyCode, timeout, resolveDatasource, isEditing: isEditingProp, }: PersonalizeConnectProviderProps): react_jsx_runtime.JSX.Element;
93
+ declare function PersonalizeProvider({ children, sitecoreEdgeContextId, sitecoreEdgeUrl, siteName, clientKey, pointOfSale, edgeUrl, apiKey, channel, language, currencyCode, timeout, resolveDatasource, isEditing: isEditingProp, debug, }: PersonalizeConnectProviderProps): react_jsx_runtime.JSX.Element;
92
94
  declare function usePersonalizeContext(): PersonalizeContextValue | null;
93
95
 
94
96
  /**
@@ -131,13 +133,13 @@ declare function resolveContent(options: ResolveContentOptions): Promise<Resolve
131
133
 
132
134
  type GetConfigFromProps<P> = (props: P) => PersonalizeConnectConfig | undefined;
133
135
  /**
134
- * HOC that wraps any JSS/Content SDK component.
136
+ * HOC that wraps any JSS component.
135
137
  * If the rendering has a personalizeConnect config, it renders with defaultKey first,
136
138
  * then asynchronously fetches the personalized content and re-renders.
137
139
  * If no config, passes through unchanged.
138
140
  *
139
- * In editing mode (Page Builder / Experience Editor), renders a visual
140
- * indicator (border + badge) on components that have personalization configured.
141
+ * In Page Builder, renders a visual indicator (border + badge) on
142
+ * components that have personalization configured.
141
143
  */
142
144
  declare function withPersonalizeConnect<P extends object>(WrappedComponent: ComponentType<P>, getConfig?: GetConfigFromProps<P>): ComponentType<P>;
143
145
 
@@ -171,17 +173,26 @@ declare function createEdgeResolver(edgeUrl: string, apiKey: string, language?:
171
173
  declare function createEdgeProxyResolver(edgeBaseUrl: string, contextId: string, language?: string): (datasourceId: string) => Promise<ComponentFields>;
172
174
 
173
175
  /**
174
- * Lightweight Sitecore editing mode detection.
175
- * Works in Experience Editor and XM Cloud Pages without importing JSS.
176
+ * XM Cloud Pages (Page Builder) editing mode detection.
177
+ * Uses the same Sitecore context shape that JSS for XMC writes to __NEXT_DATA__.
176
178
  */
177
179
  /**
178
- * Detects whether the page is currently rendered inside a Sitecore editor
179
- * (Experience Editor or XM Cloud Pages). Result is cached after the first call.
180
+ * Detects whether the page is rendered inside XM Cloud Pages (Page Builder).
181
+ * Checks the JSS Sitecore context for pageEditing / pageState, and whether
182
+ * the rendering host is loaded in an iframe (Pages always iframes the site).
183
+ * Result is cached after the first call.
180
184
  */
181
185
  declare function isEditingMode(): boolean;
182
186
  /** Reset the cached result (useful for testing). */
183
187
  declare function resetEditingDetectionCache(): void;
184
188
 
189
+ /**
190
+ * SDK debug logger. All output is prefixed with [PersonalizeConnect].
191
+ * Enable by passing debug={true} on PersonalizeProvider.
192
+ */
193
+ declare function setDebug(on: boolean): void;
194
+ declare function isDebugEnabled(): boolean;
195
+
185
196
  /**
186
197
  * Manages the Sitecore CDP browser ID.
187
198
  *
@@ -205,4 +216,4 @@ declare function getEdgeBrowserId(edgeUrl: string, contextId: string, siteName:
205
216
  /** Reset the cached init promise (for testing). */
206
217
  declare function resetEdgeInitCache(): void;
207
218
 
208
- export { type CallFlowsRequest, type CallPersonalizeOptions, type ComponentFields, type EdgeInitResponse, type GetConfigFromProps, type PersonalizeConnectConfig, type PersonalizeConnectProviderProps, type PersonalizeConnectResponse, type PersonalizeContextValue, PersonalizeProvider, type ResolveContentOptions, type ResolvedContent, type UsePersonalizeExperienceResult, callPersonalize, createEdgeProxyResolver, createEdgeResolver, getBrowserId, getEdgeBrowserId, isEditingMode, resetEdgeInitCache, resetEditingDetectionCache, resolveContent, usePersonalizeContext, usePersonalizeExperience, withPersonalizeConnect };
219
+ export { type CallFlowsRequest, type CallPersonalizeOptions, type ComponentFields, type EdgeInitResponse, type GetConfigFromProps, type PersonalizeConnectConfig, type PersonalizeConnectProviderProps, type PersonalizeConnectResponse, type PersonalizeContextValue, PersonalizeProvider, type ResolveContentOptions, type ResolvedContent, type UsePersonalizeExperienceResult, callPersonalize, createEdgeProxyResolver, createEdgeResolver, getBrowserId, getEdgeBrowserId, isDebugEnabled, isEditingMode, resetEdgeInitCache, resetEditingDetectionCache, resolveContent, setDebug, usePersonalizeContext, usePersonalizeExperience, withPersonalizeConnect };
package/dist/index.d.ts CHANGED
@@ -63,6 +63,8 @@ interface PersonalizeConnectProviderProps {
63
63
  resolveDatasource?: (datasourceId: string) => Promise<ComponentFields>;
64
64
  /** Override editing mode detection. When true the HOC renders a visual indicator. */
65
65
  isEditing?: boolean;
66
+ /** Enable debug logging to the browser console. */
67
+ debug?: boolean;
66
68
  children: ReactNode;
67
69
  }
68
70
  /** Resolved datasource fields passed as component props */
@@ -88,7 +90,7 @@ interface PersonalizeContextValue {
88
90
  siteName: string;
89
91
  }
90
92
 
91
- declare function PersonalizeProvider({ children, sitecoreEdgeContextId, sitecoreEdgeUrl, siteName, clientKey, pointOfSale, edgeUrl, apiKey, channel, language, currencyCode, timeout, resolveDatasource, isEditing: isEditingProp, }: PersonalizeConnectProviderProps): react_jsx_runtime.JSX.Element;
93
+ declare function PersonalizeProvider({ children, sitecoreEdgeContextId, sitecoreEdgeUrl, siteName, clientKey, pointOfSale, edgeUrl, apiKey, channel, language, currencyCode, timeout, resolveDatasource, isEditing: isEditingProp, debug, }: PersonalizeConnectProviderProps): react_jsx_runtime.JSX.Element;
92
94
  declare function usePersonalizeContext(): PersonalizeContextValue | null;
93
95
 
94
96
  /**
@@ -131,13 +133,13 @@ declare function resolveContent(options: ResolveContentOptions): Promise<Resolve
131
133
 
132
134
  type GetConfigFromProps<P> = (props: P) => PersonalizeConnectConfig | undefined;
133
135
  /**
134
- * HOC that wraps any JSS/Content SDK component.
136
+ * HOC that wraps any JSS component.
135
137
  * If the rendering has a personalizeConnect config, it renders with defaultKey first,
136
138
  * then asynchronously fetches the personalized content and re-renders.
137
139
  * If no config, passes through unchanged.
138
140
  *
139
- * In editing mode (Page Builder / Experience Editor), renders a visual
140
- * indicator (border + badge) on components that have personalization configured.
141
+ * In Page Builder, renders a visual indicator (border + badge) on
142
+ * components that have personalization configured.
141
143
  */
142
144
  declare function withPersonalizeConnect<P extends object>(WrappedComponent: ComponentType<P>, getConfig?: GetConfigFromProps<P>): ComponentType<P>;
143
145
 
@@ -171,17 +173,26 @@ declare function createEdgeResolver(edgeUrl: string, apiKey: string, language?:
171
173
  declare function createEdgeProxyResolver(edgeBaseUrl: string, contextId: string, language?: string): (datasourceId: string) => Promise<ComponentFields>;
172
174
 
173
175
  /**
174
- * Lightweight Sitecore editing mode detection.
175
- * Works in Experience Editor and XM Cloud Pages without importing JSS.
176
+ * XM Cloud Pages (Page Builder) editing mode detection.
177
+ * Uses the same Sitecore context shape that JSS for XMC writes to __NEXT_DATA__.
176
178
  */
177
179
  /**
178
- * Detects whether the page is currently rendered inside a Sitecore editor
179
- * (Experience Editor or XM Cloud Pages). Result is cached after the first call.
180
+ * Detects whether the page is rendered inside XM Cloud Pages (Page Builder).
181
+ * Checks the JSS Sitecore context for pageEditing / pageState, and whether
182
+ * the rendering host is loaded in an iframe (Pages always iframes the site).
183
+ * Result is cached after the first call.
180
184
  */
181
185
  declare function isEditingMode(): boolean;
182
186
  /** Reset the cached result (useful for testing). */
183
187
  declare function resetEditingDetectionCache(): void;
184
188
 
189
+ /**
190
+ * SDK debug logger. All output is prefixed with [PersonalizeConnect].
191
+ * Enable by passing debug={true} on PersonalizeProvider.
192
+ */
193
+ declare function setDebug(on: boolean): void;
194
+ declare function isDebugEnabled(): boolean;
195
+
185
196
  /**
186
197
  * Manages the Sitecore CDP browser ID.
187
198
  *
@@ -205,4 +216,4 @@ declare function getEdgeBrowserId(edgeUrl: string, contextId: string, siteName:
205
216
  /** Reset the cached init promise (for testing). */
206
217
  declare function resetEdgeInitCache(): void;
207
218
 
208
- export { type CallFlowsRequest, type CallPersonalizeOptions, type ComponentFields, type EdgeInitResponse, type GetConfigFromProps, type PersonalizeConnectConfig, type PersonalizeConnectProviderProps, type PersonalizeConnectResponse, type PersonalizeContextValue, PersonalizeProvider, type ResolveContentOptions, type ResolvedContent, type UsePersonalizeExperienceResult, callPersonalize, createEdgeProxyResolver, createEdgeResolver, getBrowserId, getEdgeBrowserId, isEditingMode, resetEdgeInitCache, resetEditingDetectionCache, resolveContent, usePersonalizeContext, usePersonalizeExperience, withPersonalizeConnect };
219
+ export { type CallFlowsRequest, type CallPersonalizeOptions, type ComponentFields, type EdgeInitResponse, type GetConfigFromProps, type PersonalizeConnectConfig, type PersonalizeConnectProviderProps, type PersonalizeConnectResponse, type PersonalizeContextValue, PersonalizeProvider, type ResolveContentOptions, type ResolvedContent, type UsePersonalizeExperienceResult, callPersonalize, createEdgeProxyResolver, createEdgeResolver, getBrowserId, getEdgeBrowserId, isDebugEnabled, isEditingMode, resetEdgeInitCache, resetEditingDetectionCache, resolveContent, setDebug, usePersonalizeContext, usePersonalizeExperience, withPersonalizeConnect };
package/dist/index.js CHANGED
@@ -26,10 +26,12 @@ __export(index_exports, {
26
26
  createEdgeResolver: () => createEdgeResolver,
27
27
  getBrowserId: () => getBrowserId,
28
28
  getEdgeBrowserId: () => getEdgeBrowserId,
29
+ isDebugEnabled: () => isDebugEnabled,
29
30
  isEditingMode: () => isEditingMode,
30
31
  resetEdgeInitCache: () => resetEdgeInitCache,
31
32
  resetEditingDetectionCache: () => resetEditingDetectionCache,
32
33
  resolveContent: () => resolveContent,
34
+ setDebug: () => setDebug,
33
35
  usePersonalizeContext: () => usePersonalizeContext,
34
36
  usePersonalizeExperience: () => usePersonalizeExperience,
35
37
  withPersonalizeConnect: () => withPersonalizeConnect
@@ -39,6 +41,31 @@ module.exports = __toCommonJS(index_exports);
39
41
  // src/PersonalizeProvider.tsx
40
42
  var import_react = require("react");
41
43
 
44
+ // src/logger.ts
45
+ var PREFIX = "[PersonalizeConnect]";
46
+ var enabled = false;
47
+ function setDebug(on) {
48
+ enabled = on;
49
+ }
50
+ function isDebugEnabled() {
51
+ return enabled;
52
+ }
53
+ function log(...args) {
54
+ if (enabled) console.log(PREFIX, ...args);
55
+ }
56
+ function warn(...args) {
57
+ if (enabled) console.warn(PREFIX, ...args);
58
+ }
59
+ function error(...args) {
60
+ if (enabled) console.error(PREFIX, ...args);
61
+ }
62
+ function group(label) {
63
+ if (enabled) console.groupCollapsed(`${PREFIX} ${label}`);
64
+ }
65
+ function groupEnd() {
66
+ if (enabled) console.groupEnd();
67
+ }
68
+
42
69
  // src/browserId.ts
43
70
  var LEGACY_COOKIE_PREFIX = "bid_";
44
71
  var CONTEXT_COOKIE_PREFIX = "sc_";
@@ -71,6 +98,9 @@ function getBrowserId(clientKey) {
71
98
  if (!bid || typeof bid !== "string" || bid.length < 10) {
72
99
  bid = generateBrowserId();
73
100
  setCookie(cookieName, bid);
101
+ log("BrowserId (legacy): generated new", bid, `cookie=${cookieName}`);
102
+ } else {
103
+ log("BrowserId (legacy): from cookie", bid, `cookie=${cookieName}`);
74
104
  }
75
105
  return bid;
76
106
  }
@@ -78,15 +108,21 @@ var edgeInitPromise = null;
78
108
  async function getEdgeBrowserId(edgeUrl, contextId, siteName) {
79
109
  const cookieName = CONTEXT_COOKIE_PREFIX + contextId + CONTEXT_COOKIE_SUFFIX;
80
110
  const cached = getCookie(cookieName);
81
- if (cached && cached.length >= 10) return cached;
111
+ if (cached && cached.length >= 10) {
112
+ log("BrowserId (edge): from cookie", cached, `cookie=${cookieName}`);
113
+ return cached;
114
+ }
115
+ log("BrowserId (edge): cookie miss, calling /v1/init", { edgeUrl, contextId, siteName });
82
116
  if (!edgeInitPromise) {
83
117
  edgeInitPromise = fetchEdgeInit(edgeUrl, contextId, siteName);
84
118
  }
85
119
  try {
86
120
  const result = await edgeInitPromise;
121
+ log("BrowserId (edge): init returned", result);
87
122
  setCookie(cookieName, result.browserId);
88
123
  return result.browserId;
89
- } catch {
124
+ } catch (e) {
125
+ warn("BrowserId (edge): init failed, generating fallback", e);
90
126
  const fallback = generateBrowserId();
91
127
  setCookie(cookieName, fallback);
92
128
  return fallback;
@@ -97,12 +133,17 @@ async function getEdgeBrowserId(edgeUrl, contextId, siteName) {
97
133
  async function fetchEdgeInit(edgeUrl, contextId, siteName) {
98
134
  const base = edgeUrl.replace(/\/$/, "");
99
135
  const url = `${base}/v1/init?sitecoreContextId=${encodeURIComponent(contextId)}&siteName=${encodeURIComponent(siteName)}`;
136
+ log("BrowserId (edge): GET", url);
100
137
  const res = await fetch(url, { method: "GET" });
138
+ log("BrowserId (edge): init response status:", res.status);
101
139
  if (!res.ok) {
140
+ const text = await res.text().catch(() => "");
141
+ error("BrowserId (edge): init non-OK:", res.status, text);
102
142
  throw new Error(`Edge init failed: ${res.status}`);
103
143
  }
104
144
  const data = await res.json();
105
145
  if (!data.browserId || typeof data.browserId !== "string") {
146
+ error("BrowserId (edge): init response missing browserId", data);
106
147
  throw new Error("Edge init response missing browserId");
107
148
  }
108
149
  return data;
@@ -132,6 +173,7 @@ function mapFields(fields) {
132
173
  return result;
133
174
  }
134
175
  async function queryEdge(url, headers, datasourceId, language) {
176
+ log("Edge GraphQL request:", { url, datasourceId, language });
135
177
  const res = await fetch(url, {
136
178
  method: "POST",
137
179
  headers: { "Content-Type": "application/json", ...headers },
@@ -140,16 +182,26 @@ async function queryEdge(url, headers, datasourceId, language) {
140
182
  variables: { itemId: datasourceId, language }
141
183
  })
142
184
  });
185
+ log("Edge GraphQL response status:", res.status);
143
186
  if (!res.ok) {
187
+ const text = await res.text().catch(() => "");
188
+ error("Edge GraphQL non-OK response:", res.status, text);
144
189
  throw new Error(`Experience Edge request failed: ${res.status}`);
145
190
  }
146
191
  const json = await res.json();
147
192
  if (json.errors?.length) {
148
- throw new Error(json.errors.map((e) => e.message ?? String(e)).join("; "));
193
+ const msg = json.errors.map((e) => e.message ?? String(e)).join("; ");
194
+ error("Edge GraphQL errors:", msg);
195
+ throw new Error(msg);
149
196
  }
150
197
  const fields = json.data?.item?.fields;
151
- if (!fields || !Array.isArray(fields)) return {};
152
- return mapFields(fields);
198
+ if (!fields || !Array.isArray(fields)) {
199
+ warn("Edge GraphQL: item not found or has no fields for", datasourceId);
200
+ return {};
201
+ }
202
+ const mapped = mapFields(fields);
203
+ log("Edge GraphQL resolved fields:", { datasourceId, fieldCount: Object.keys(mapped).length, fieldNames: Object.keys(mapped) });
204
+ return mapped;
153
205
  }
154
206
  function createEdgeResolver(edgeUrl, apiKey, language = "en") {
155
207
  return (datasourceId) => queryEdge(edgeUrl, { sc_apikey: apiKey }, datasourceId, language);
@@ -162,23 +214,46 @@ function createEdgeProxyResolver(edgeBaseUrl, contextId, language = "en") {
162
214
 
163
215
  // src/editingDetection.ts
164
216
  var cachedResult = null;
217
+ function getSitecoreContext() {
218
+ if (typeof window === "undefined") return void 0;
219
+ try {
220
+ const nd = window.__NEXT_DATA__;
221
+ return nd?.props?.pageProps?.layoutData?.sitecore?.context;
222
+ } catch {
223
+ return void 0;
224
+ }
225
+ }
165
226
  function isEditingMode() {
166
- if (typeof window === "undefined") return false;
167
- if (cachedResult !== null) return cachedResult;
168
- const params = new URLSearchParams(window.location.search);
169
- const scMode = params.get("sc_mode");
170
- if (scMode && scMode !== "normal") {
171
- cachedResult = true;
172
- return true;
227
+ if (typeof window === "undefined") {
228
+ log("Editing detection: SSR \u2014 returning false");
229
+ return false;
173
230
  }
174
- if (params.has("sc_layoutKind") || params.has("sc_itemid")) {
231
+ if (cachedResult !== null) {
232
+ log("Editing detection: returning cached result", cachedResult);
233
+ return cachedResult;
234
+ }
235
+ log("Editing detection: running checks...");
236
+ const sc = getSitecoreContext();
237
+ log("Editing detection: __NEXT_DATA__ sitecore context:", sc ?? "(not found)");
238
+ if (sc?.pageEditing === true || sc?.pageState && sc.pageState !== "normal") {
239
+ log("Editing detection: MATCH \u2014 JSS context pageEditing/pageState", { pageEditing: sc.pageEditing, pageState: sc.pageState });
175
240
  cachedResult = true;
176
241
  return true;
177
242
  }
178
- if (document.getElementById("scWebEditRibbon") || document.querySelector("[chrometype]")) {
243
+ try {
244
+ const inIframe = window.self !== window.top;
245
+ log("Editing detection: iframe check \u2014 window.self !== window.top:", inIframe);
246
+ if (inIframe) {
247
+ log("Editing detection: MATCH \u2014 running inside iframe (Page Builder)");
248
+ cachedResult = true;
249
+ return true;
250
+ }
251
+ } catch {
252
+ log("Editing detection: MATCH \u2014 cross-origin iframe access threw (Page Builder)");
179
253
  cachedResult = true;
180
254
  return true;
181
255
  }
256
+ log("Editing detection: no match \u2014 not in editing mode");
182
257
  cachedResult = false;
183
258
  return false;
184
259
  }
@@ -197,43 +272,87 @@ var DEFAULT_EDGE_URL = "https://edge-platform.sitecorecloud.io";
197
272
  var noopResolver = async () => ({});
198
273
  function PersonalizeProvider({
199
274
  children,
200
- // Context ID mode
201
275
  sitecoreEdgeContextId,
202
276
  sitecoreEdgeUrl = DEFAULT_EDGE_URL,
203
277
  siteName = "",
204
- // Legacy mode
205
278
  clientKey = "",
206
279
  pointOfSale = "",
207
280
  edgeUrl,
208
281
  apiKey,
209
- // Common
210
282
  channel = DEFAULT_CHANNEL,
211
283
  language = DEFAULT_LANGUAGE,
212
284
  currencyCode = DEFAULT_CURRENCY,
213
285
  timeout = DEFAULT_TIMEOUT,
214
286
  resolveDatasource,
215
- isEditing: isEditingProp
287
+ isEditing: isEditingProp,
288
+ debug = false
216
289
  }) {
217
290
  const useEdgeProxy = Boolean(sitecoreEdgeContextId);
218
291
  const [browserId, setBrowserId] = (0, import_react.useState)("");
219
292
  const [detectedEditing, setDetectedEditing] = (0, import_react.useState)(false);
293
+ (0, import_react.useEffect)(() => {
294
+ setDebug(debug);
295
+ }, [debug]);
296
+ (0, import_react.useEffect)(() => {
297
+ setDebug(debug);
298
+ log("Provider mounting", {
299
+ mode: useEdgeProxy ? "Context ID" : "Legacy",
300
+ sitecoreEdgeContextId: sitecoreEdgeContextId ?? "(none)",
301
+ sitecoreEdgeUrl,
302
+ siteName: siteName || "(none)",
303
+ clientKey: clientKey ? `${clientKey.slice(0, 8)}...` : "(none)",
304
+ pointOfSale: pointOfSale || "(none)",
305
+ edgeUrl: edgeUrl ?? "(none)",
306
+ apiKey: apiKey ? `${apiKey.slice(0, 8)}...` : "(none)",
307
+ language,
308
+ channel,
309
+ hasCustomResolver: Boolean(resolveDatasource),
310
+ isEditingProp: isEditingProp ?? "auto-detect"
311
+ });
312
+ }, []);
220
313
  (0, import_react.useEffect)(() => {
221
314
  if (useEdgeProxy) {
222
- getEdgeBrowserId(sitecoreEdgeUrl, sitecoreEdgeContextId, siteName).then(setBrowserId);
315
+ log("BrowserId: fetching via Edge init", { sitecoreEdgeUrl, sitecoreEdgeContextId, siteName });
316
+ getEdgeBrowserId(sitecoreEdgeUrl, sitecoreEdgeContextId, siteName).then((bid) => {
317
+ log("BrowserId: resolved via Edge init", bid);
318
+ setBrowserId(bid);
319
+ }).catch((err) => {
320
+ warn("BrowserId: Edge init failed, will use fallback", err);
321
+ });
223
322
  } else if (clientKey) {
224
- setBrowserId(getBrowserId(clientKey));
323
+ const bid = getBrowserId(clientKey);
324
+ log("BrowserId: resolved via local cookie", bid);
325
+ setBrowserId(bid);
326
+ } else {
327
+ warn("BrowserId: no clientKey and no sitecoreEdgeContextId \u2014 browserId will be empty");
225
328
  }
226
329
  }, [useEdgeProxy, sitecoreEdgeContextId, sitecoreEdgeUrl, siteName, clientKey]);
227
330
  (0, import_react.useEffect)(() => {
228
331
  if (isEditingProp === void 0) {
229
- setDetectedEditing(isEditingMode());
332
+ const detected = isEditingMode();
333
+ log("Editing mode auto-detected:", detected);
334
+ setDetectedEditing(detected);
335
+ } else {
336
+ log("Editing mode overridden via prop:", isEditingProp);
230
337
  }
231
338
  }, [isEditingProp]);
232
339
  const effectiveEditing = isEditingProp ?? detectedEditing;
233
- const effectiveResolver = (0, import_react.useCallback)(
234
- resolveDatasource ?? (useEdgeProxy ? createEdgeProxyResolver(sitecoreEdgeUrl, sitecoreEdgeContextId, language) : edgeUrl && apiKey ? createEdgeResolver(edgeUrl, apiKey, language) : noopResolver),
235
- [resolveDatasource, useEdgeProxy, sitecoreEdgeUrl, sitecoreEdgeContextId, edgeUrl, apiKey, language]
236
- );
340
+ const effectiveResolver = (0, import_react.useCallback)(() => {
341
+ if (resolveDatasource) {
342
+ log("Resolver: using custom resolveDatasource callback");
343
+ return resolveDatasource;
344
+ }
345
+ if (useEdgeProxy) {
346
+ log("Resolver: using Edge proxy GraphQL", { sitecoreEdgeUrl, sitecoreEdgeContextId });
347
+ return createEdgeProxyResolver(sitecoreEdgeUrl, sitecoreEdgeContextId, language);
348
+ }
349
+ if (edgeUrl && apiKey) {
350
+ log("Resolver: using direct Edge GraphQL", { edgeUrl });
351
+ return createEdgeResolver(edgeUrl, apiKey, language);
352
+ }
353
+ warn("Resolver: no resolver configured \u2014 resolveDatasource will return {}. Provide sitecoreEdgeContextId, edgeUrl+apiKey, or a custom resolveDatasource.");
354
+ return noopResolver;
355
+ }, [resolveDatasource, useEdgeProxy, sitecoreEdgeUrl, sitecoreEdgeContextId, edgeUrl, apiKey, language])();
237
356
  const effectiveBrowserId = browserId || (!useEdgeProxy && clientKey && typeof window !== "undefined" ? getBrowserId(clientKey) : "");
238
357
  const value = (0, import_react.useMemo)(
239
358
  () => ({
@@ -277,10 +396,23 @@ function usePersonalizeContext() {
277
396
  var DEFAULT_API_BASE = "https://api.boxever.com";
278
397
  async function callPersonalize(options) {
279
398
  const { config, context, apiBase = DEFAULT_API_BASE, componentRef, pageRoute } = options;
399
+ group(`callPersonalize [${config.friendlyId}]`);
400
+ log("Config:", { friendlyId: config.friendlyId, defaultKey: config.defaultKey, contentMapKeys: Object.keys(config.contentMap) });
401
+ log("BrowserId:", context.browserId || "(empty \u2014 call will likely fail)");
402
+ if (!context.browserId) {
403
+ warn("BrowserId is empty \u2014 personalize call may fail or return default");
404
+ }
405
+ let result;
280
406
  if (context.useEdgeProxy) {
281
- return callViaEdgeProxy(config, context, componentRef, pageRoute);
407
+ log("Route: Edge proxy");
408
+ result = await callViaEdgeProxy(config, context, componentRef, pageRoute);
409
+ } else {
410
+ log("Route: Legacy /v2/callFlows", { apiBase });
411
+ result = await callViaLegacy(config, context, apiBase, componentRef, pageRoute);
282
412
  }
283
- return callViaLegacy(config, context, apiBase, componentRef, pageRoute);
413
+ log("Result contentKey:", result ?? "(null \u2014 will use defaultKey)");
414
+ groupEnd();
415
+ return result;
284
416
  }
285
417
  async function callViaEdgeProxy(config, context, componentRef, pageRoute) {
286
418
  const base = context.edgeProxyUrl.replace(/\/$/, "");
@@ -298,6 +430,8 @@ async function callViaEdgeProxy(config, context, componentRef, pageRoute) {
298
430
  if (pageRoute) params.pageRoute = pageRoute;
299
431
  body.params = params;
300
432
  }
433
+ log("Edge proxy POST", url);
434
+ log("Request body:", body);
301
435
  const controller = new AbortController();
302
436
  const timeoutId = setTimeout(() => controller.abort(), context.timeout);
303
437
  try {
@@ -308,10 +442,18 @@ async function callViaEdgeProxy(config, context, componentRef, pageRoute) {
308
442
  signal: controller.signal
309
443
  });
310
444
  clearTimeout(timeoutId);
311
- if (!res.ok) return null;
312
- return extractContentKey(await res.json());
313
- } catch {
445
+ log("Edge proxy response status:", res.status);
446
+ if (!res.ok) {
447
+ const text = await res.text().catch(() => "");
448
+ warn("Edge proxy non-OK response:", res.status, text);
449
+ return null;
450
+ }
451
+ const data = await res.json();
452
+ log("Edge proxy response body:", data);
453
+ return extractContentKey(data);
454
+ } catch (e) {
314
455
  clearTimeout(timeoutId);
456
+ error("Edge proxy fetch error:", e);
315
457
  return null;
316
458
  }
317
459
  }
@@ -330,20 +472,31 @@ async function callViaLegacy(config, context, apiBase, componentRef, pageRoute)
330
472
  if (componentRef) body.params.componentRef = componentRef;
331
473
  if (pageRoute) body.params.pageRoute = pageRoute;
332
474
  }
475
+ const url = `${apiBase.replace(/\/$/, "")}/v2/callFlows`;
476
+ log("Legacy POST", url);
477
+ log("Request body:", body);
333
478
  const controller = new AbortController();
334
479
  const timeoutId = setTimeout(() => controller.abort(), context.timeout);
335
480
  try {
336
- const res = await fetch(`${apiBase.replace(/\/$/, "")}/v2/callFlows`, {
481
+ const res = await fetch(url, {
337
482
  method: "POST",
338
483
  headers: { "Content-Type": "application/json" },
339
484
  body: JSON.stringify(body),
340
485
  signal: controller.signal
341
486
  });
342
487
  clearTimeout(timeoutId);
343
- if (!res.ok) return null;
344
- return extractContentKey(await res.json());
345
- } catch {
488
+ log("Legacy response status:", res.status);
489
+ if (!res.ok) {
490
+ const text = await res.text().catch(() => "");
491
+ warn("Legacy non-OK response:", res.status, text);
492
+ return null;
493
+ }
494
+ const data = await res.json();
495
+ log("Legacy response body:", data);
496
+ return extractContentKey(data);
497
+ } catch (e) {
346
498
  clearTimeout(timeoutId);
499
+ error("Legacy fetch error:", e);
347
500
  return null;
348
501
  }
349
502
  }
@@ -357,14 +510,29 @@ function extractContentKey(data) {
357
510
  // src/contentResolver.ts
358
511
  async function resolveContent(options) {
359
512
  const { contentKey, config, resolveDatasource } = options;
360
- const effectiveKey = contentKey && contentKey in config.contentMap ? contentKey : config.defaultKey;
513
+ const keyInMap = contentKey && contentKey in config.contentMap;
514
+ const effectiveKey = keyInMap ? contentKey : config.defaultKey;
515
+ if (!keyInMap) {
516
+ warn(
517
+ `Content key "${contentKey}" not found in contentMap [${Object.keys(config.contentMap).join(", ")}], falling back to defaultKey "${config.defaultKey}"`
518
+ );
519
+ }
361
520
  const datasourceId = config.contentMap[effectiveKey];
362
- if (!datasourceId || typeof datasourceId !== "string") return null;
521
+ log("Resolving datasource:", { effectiveKey, datasourceId });
522
+ if (!datasourceId || typeof datasourceId !== "string") {
523
+ warn(`No datasource ID for key "${effectiveKey}" \u2014 contentMap may be misconfigured`);
524
+ return null;
525
+ }
363
526
  try {
364
527
  const fields = await resolveDatasource(datasourceId);
365
- if (!fields || typeof fields !== "object") return null;
528
+ if (!fields || typeof fields !== "object") {
529
+ warn("resolveDatasource returned empty/invalid fields for", datasourceId);
530
+ return null;
531
+ }
532
+ log("Datasource resolved:", { datasourceId, fieldNames: Object.keys(fields) });
366
533
  return { datasourceId, fields };
367
- } catch {
534
+ } catch (e) {
535
+ error("resolveDatasource threw for", datasourceId, e);
368
536
  return null;
369
537
  }
370
538
  }
@@ -397,24 +565,46 @@ var INDICATOR_BADGE = {
397
565
  pointerEvents: "none"
398
566
  };
399
567
  function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG) {
568
+ const componentName = WrappedComponent.displayName ?? WrappedComponent.name ?? "Component";
400
569
  function PersonalizeConnectWrapper(props) {
401
570
  const context = usePersonalizeContext();
402
571
  const config = getConfig(props);
403
572
  const [resolvedFields, setResolvedFields] = (0, import_react2.useState)(null);
404
573
  const mountedRef = (0, import_react2.useRef)(true);
574
+ if (!config) {
575
+ log(`[${componentName}] No personalizeConnect config on rendering \u2014 passthrough`);
576
+ } else {
577
+ log(`[${componentName}] Config found:`, { friendlyId: config.friendlyId, defaultKey: config.defaultKey, keys: Object.keys(config.contentMap) });
578
+ }
579
+ if (!context) {
580
+ warn(`[${componentName}] PersonalizeContext is null \u2014 is PersonalizeProvider mounted?`);
581
+ }
405
582
  const runPersonalization = (0, import_react2.useCallback)(async () => {
406
583
  if (!config || !context) return;
584
+ group(`[${componentName}] personalization flow`);
585
+ log("Calling Personalize for experience:", config.friendlyId);
407
586
  const contentKey = await callPersonalize({ config, context });
408
- if (!mountedRef.current) return;
587
+ if (!mountedRef.current) {
588
+ groupEnd();
589
+ return;
590
+ }
591
+ log("Resolving content for contentKey:", contentKey ?? "(null)");
409
592
  const resolved = await resolveContent({
410
593
  contentKey,
411
594
  config,
412
595
  resolveDatasource: context.resolveDatasource
413
596
  });
414
- if (!mountedRef.current) return;
597
+ if (!mountedRef.current) {
598
+ groupEnd();
599
+ return;
600
+ }
415
601
  if (resolved) {
602
+ log(`[${componentName}] Fields resolved \u2014 swapping props.fields`, { datasourceId: resolved.datasourceId, fieldNames: Object.keys(resolved.fields) });
416
603
  setResolvedFields(resolved.fields);
604
+ } else {
605
+ warn(`[${componentName}] Content resolution returned null \u2014 component keeps original fields`);
417
606
  }
607
+ groupEnd();
418
608
  }, [config, context]);
419
609
  (0, import_react2.useEffect)(() => {
420
610
  mountedRef.current = true;
@@ -431,6 +621,7 @@ function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG
431
621
  const mergedProps = resolvedFields ? { ...props, fields: resolvedFields } : props;
432
622
  const component = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(WrappedComponent, { ...mergedProps });
433
623
  if (context.isEditing) {
624
+ log(`[${componentName}] Editing mode \u2014 rendering indicator badge`);
434
625
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: INDICATOR_BORDER, "data-personalize-connect": config.friendlyId, children: [
435
626
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: INDICATOR_BADGE, title: `Personalize: ${config.friendlyId}`, children: "\u26A1 Personalized" }),
436
627
  component
@@ -438,7 +629,7 @@ function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG
438
629
  }
439
630
  return component;
440
631
  }
441
- PersonalizeConnectWrapper.displayName = `WithPersonalizeConnect(${WrappedComponent.displayName ?? WrappedComponent.name ?? "Component"})`;
632
+ PersonalizeConnectWrapper.displayName = `WithPersonalizeConnect(${componentName})`;
442
633
  return PersonalizeConnectWrapper;
443
634
  }
444
635
 
@@ -449,7 +640,7 @@ function usePersonalizeExperience(config) {
449
640
  const [contentKey, setContentKey] = (0, import_react3.useState)(null);
450
641
  const [resolvedFields, setResolvedFields] = (0, import_react3.useState)(null);
451
642
  const [isLoading, setIsLoading] = (0, import_react3.useState)(true);
452
- const [error, setError] = (0, import_react3.useState)(null);
643
+ const [error2, setError] = (0, import_react3.useState)(null);
453
644
  const mountedRef = (0, import_react3.useRef)(true);
454
645
  const runPersonalization = (0, import_react3.useCallback)(async () => {
455
646
  if (!config || !context) {
@@ -490,7 +681,7 @@ function usePersonalizeExperience(config) {
490
681
  contentKey,
491
682
  resolvedFields,
492
683
  isLoading,
493
- error
684
+ error: error2
494
685
  };
495
686
  }
496
687
  // Annotate the CommonJS export names for ESM import in node:
@@ -501,10 +692,12 @@ function usePersonalizeExperience(config) {
501
692
  createEdgeResolver,
502
693
  getBrowserId,
503
694
  getEdgeBrowserId,
695
+ isDebugEnabled,
504
696
  isEditingMode,
505
697
  resetEdgeInitCache,
506
698
  resetEditingDetectionCache,
507
699
  resolveContent,
700
+ setDebug,
508
701
  usePersonalizeContext,
509
702
  usePersonalizeExperience,
510
703
  withPersonalizeConnect
package/dist/index.mjs CHANGED
@@ -1,6 +1,31 @@
1
1
  // src/PersonalizeProvider.tsx
2
2
  import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
3
3
 
4
+ // src/logger.ts
5
+ var PREFIX = "[PersonalizeConnect]";
6
+ var enabled = false;
7
+ function setDebug(on) {
8
+ enabled = on;
9
+ }
10
+ function isDebugEnabled() {
11
+ return enabled;
12
+ }
13
+ function log(...args) {
14
+ if (enabled) console.log(PREFIX, ...args);
15
+ }
16
+ function warn(...args) {
17
+ if (enabled) console.warn(PREFIX, ...args);
18
+ }
19
+ function error(...args) {
20
+ if (enabled) console.error(PREFIX, ...args);
21
+ }
22
+ function group(label) {
23
+ if (enabled) console.groupCollapsed(`${PREFIX} ${label}`);
24
+ }
25
+ function groupEnd() {
26
+ if (enabled) console.groupEnd();
27
+ }
28
+
4
29
  // src/browserId.ts
5
30
  var LEGACY_COOKIE_PREFIX = "bid_";
6
31
  var CONTEXT_COOKIE_PREFIX = "sc_";
@@ -33,6 +58,9 @@ function getBrowserId(clientKey) {
33
58
  if (!bid || typeof bid !== "string" || bid.length < 10) {
34
59
  bid = generateBrowserId();
35
60
  setCookie(cookieName, bid);
61
+ log("BrowserId (legacy): generated new", bid, `cookie=${cookieName}`);
62
+ } else {
63
+ log("BrowserId (legacy): from cookie", bid, `cookie=${cookieName}`);
36
64
  }
37
65
  return bid;
38
66
  }
@@ -40,15 +68,21 @@ var edgeInitPromise = null;
40
68
  async function getEdgeBrowserId(edgeUrl, contextId, siteName) {
41
69
  const cookieName = CONTEXT_COOKIE_PREFIX + contextId + CONTEXT_COOKIE_SUFFIX;
42
70
  const cached = getCookie(cookieName);
43
- if (cached && cached.length >= 10) return cached;
71
+ if (cached && cached.length >= 10) {
72
+ log("BrowserId (edge): from cookie", cached, `cookie=${cookieName}`);
73
+ return cached;
74
+ }
75
+ log("BrowserId (edge): cookie miss, calling /v1/init", { edgeUrl, contextId, siteName });
44
76
  if (!edgeInitPromise) {
45
77
  edgeInitPromise = fetchEdgeInit(edgeUrl, contextId, siteName);
46
78
  }
47
79
  try {
48
80
  const result = await edgeInitPromise;
81
+ log("BrowserId (edge): init returned", result);
49
82
  setCookie(cookieName, result.browserId);
50
83
  return result.browserId;
51
- } catch {
84
+ } catch (e) {
85
+ warn("BrowserId (edge): init failed, generating fallback", e);
52
86
  const fallback = generateBrowserId();
53
87
  setCookie(cookieName, fallback);
54
88
  return fallback;
@@ -59,12 +93,17 @@ async function getEdgeBrowserId(edgeUrl, contextId, siteName) {
59
93
  async function fetchEdgeInit(edgeUrl, contextId, siteName) {
60
94
  const base = edgeUrl.replace(/\/$/, "");
61
95
  const url = `${base}/v1/init?sitecoreContextId=${encodeURIComponent(contextId)}&siteName=${encodeURIComponent(siteName)}`;
96
+ log("BrowserId (edge): GET", url);
62
97
  const res = await fetch(url, { method: "GET" });
98
+ log("BrowserId (edge): init response status:", res.status);
63
99
  if (!res.ok) {
100
+ const text = await res.text().catch(() => "");
101
+ error("BrowserId (edge): init non-OK:", res.status, text);
64
102
  throw new Error(`Edge init failed: ${res.status}`);
65
103
  }
66
104
  const data = await res.json();
67
105
  if (!data.browserId || typeof data.browserId !== "string") {
106
+ error("BrowserId (edge): init response missing browserId", data);
68
107
  throw new Error("Edge init response missing browserId");
69
108
  }
70
109
  return data;
@@ -94,6 +133,7 @@ function mapFields(fields) {
94
133
  return result;
95
134
  }
96
135
  async function queryEdge(url, headers, datasourceId, language) {
136
+ log("Edge GraphQL request:", { url, datasourceId, language });
97
137
  const res = await fetch(url, {
98
138
  method: "POST",
99
139
  headers: { "Content-Type": "application/json", ...headers },
@@ -102,16 +142,26 @@ async function queryEdge(url, headers, datasourceId, language) {
102
142
  variables: { itemId: datasourceId, language }
103
143
  })
104
144
  });
145
+ log("Edge GraphQL response status:", res.status);
105
146
  if (!res.ok) {
147
+ const text = await res.text().catch(() => "");
148
+ error("Edge GraphQL non-OK response:", res.status, text);
106
149
  throw new Error(`Experience Edge request failed: ${res.status}`);
107
150
  }
108
151
  const json = await res.json();
109
152
  if (json.errors?.length) {
110
- throw new Error(json.errors.map((e) => e.message ?? String(e)).join("; "));
153
+ const msg = json.errors.map((e) => e.message ?? String(e)).join("; ");
154
+ error("Edge GraphQL errors:", msg);
155
+ throw new Error(msg);
111
156
  }
112
157
  const fields = json.data?.item?.fields;
113
- if (!fields || !Array.isArray(fields)) return {};
114
- return mapFields(fields);
158
+ if (!fields || !Array.isArray(fields)) {
159
+ warn("Edge GraphQL: item not found or has no fields for", datasourceId);
160
+ return {};
161
+ }
162
+ const mapped = mapFields(fields);
163
+ log("Edge GraphQL resolved fields:", { datasourceId, fieldCount: Object.keys(mapped).length, fieldNames: Object.keys(mapped) });
164
+ return mapped;
115
165
  }
116
166
  function createEdgeResolver(edgeUrl, apiKey, language = "en") {
117
167
  return (datasourceId) => queryEdge(edgeUrl, { sc_apikey: apiKey }, datasourceId, language);
@@ -124,23 +174,46 @@ function createEdgeProxyResolver(edgeBaseUrl, contextId, language = "en") {
124
174
 
125
175
  // src/editingDetection.ts
126
176
  var cachedResult = null;
177
+ function getSitecoreContext() {
178
+ if (typeof window === "undefined") return void 0;
179
+ try {
180
+ const nd = window.__NEXT_DATA__;
181
+ return nd?.props?.pageProps?.layoutData?.sitecore?.context;
182
+ } catch {
183
+ return void 0;
184
+ }
185
+ }
127
186
  function isEditingMode() {
128
- if (typeof window === "undefined") return false;
129
- if (cachedResult !== null) return cachedResult;
130
- const params = new URLSearchParams(window.location.search);
131
- const scMode = params.get("sc_mode");
132
- if (scMode && scMode !== "normal") {
133
- cachedResult = true;
134
- return true;
187
+ if (typeof window === "undefined") {
188
+ log("Editing detection: SSR \u2014 returning false");
189
+ return false;
135
190
  }
136
- if (params.has("sc_layoutKind") || params.has("sc_itemid")) {
191
+ if (cachedResult !== null) {
192
+ log("Editing detection: returning cached result", cachedResult);
193
+ return cachedResult;
194
+ }
195
+ log("Editing detection: running checks...");
196
+ const sc = getSitecoreContext();
197
+ log("Editing detection: __NEXT_DATA__ sitecore context:", sc ?? "(not found)");
198
+ if (sc?.pageEditing === true || sc?.pageState && sc.pageState !== "normal") {
199
+ log("Editing detection: MATCH \u2014 JSS context pageEditing/pageState", { pageEditing: sc.pageEditing, pageState: sc.pageState });
137
200
  cachedResult = true;
138
201
  return true;
139
202
  }
140
- if (document.getElementById("scWebEditRibbon") || document.querySelector("[chrometype]")) {
203
+ try {
204
+ const inIframe = window.self !== window.top;
205
+ log("Editing detection: iframe check \u2014 window.self !== window.top:", inIframe);
206
+ if (inIframe) {
207
+ log("Editing detection: MATCH \u2014 running inside iframe (Page Builder)");
208
+ cachedResult = true;
209
+ return true;
210
+ }
211
+ } catch {
212
+ log("Editing detection: MATCH \u2014 cross-origin iframe access threw (Page Builder)");
141
213
  cachedResult = true;
142
214
  return true;
143
215
  }
216
+ log("Editing detection: no match \u2014 not in editing mode");
144
217
  cachedResult = false;
145
218
  return false;
146
219
  }
@@ -159,43 +232,87 @@ var DEFAULT_EDGE_URL = "https://edge-platform.sitecorecloud.io";
159
232
  var noopResolver = async () => ({});
160
233
  function PersonalizeProvider({
161
234
  children,
162
- // Context ID mode
163
235
  sitecoreEdgeContextId,
164
236
  sitecoreEdgeUrl = DEFAULT_EDGE_URL,
165
237
  siteName = "",
166
- // Legacy mode
167
238
  clientKey = "",
168
239
  pointOfSale = "",
169
240
  edgeUrl,
170
241
  apiKey,
171
- // Common
172
242
  channel = DEFAULT_CHANNEL,
173
243
  language = DEFAULT_LANGUAGE,
174
244
  currencyCode = DEFAULT_CURRENCY,
175
245
  timeout = DEFAULT_TIMEOUT,
176
246
  resolveDatasource,
177
- isEditing: isEditingProp
247
+ isEditing: isEditingProp,
248
+ debug = false
178
249
  }) {
179
250
  const useEdgeProxy = Boolean(sitecoreEdgeContextId);
180
251
  const [browserId, setBrowserId] = useState("");
181
252
  const [detectedEditing, setDetectedEditing] = useState(false);
253
+ useEffect(() => {
254
+ setDebug(debug);
255
+ }, [debug]);
256
+ useEffect(() => {
257
+ setDebug(debug);
258
+ log("Provider mounting", {
259
+ mode: useEdgeProxy ? "Context ID" : "Legacy",
260
+ sitecoreEdgeContextId: sitecoreEdgeContextId ?? "(none)",
261
+ sitecoreEdgeUrl,
262
+ siteName: siteName || "(none)",
263
+ clientKey: clientKey ? `${clientKey.slice(0, 8)}...` : "(none)",
264
+ pointOfSale: pointOfSale || "(none)",
265
+ edgeUrl: edgeUrl ?? "(none)",
266
+ apiKey: apiKey ? `${apiKey.slice(0, 8)}...` : "(none)",
267
+ language,
268
+ channel,
269
+ hasCustomResolver: Boolean(resolveDatasource),
270
+ isEditingProp: isEditingProp ?? "auto-detect"
271
+ });
272
+ }, []);
182
273
  useEffect(() => {
183
274
  if (useEdgeProxy) {
184
- getEdgeBrowserId(sitecoreEdgeUrl, sitecoreEdgeContextId, siteName).then(setBrowserId);
275
+ log("BrowserId: fetching via Edge init", { sitecoreEdgeUrl, sitecoreEdgeContextId, siteName });
276
+ getEdgeBrowserId(sitecoreEdgeUrl, sitecoreEdgeContextId, siteName).then((bid) => {
277
+ log("BrowserId: resolved via Edge init", bid);
278
+ setBrowserId(bid);
279
+ }).catch((err) => {
280
+ warn("BrowserId: Edge init failed, will use fallback", err);
281
+ });
185
282
  } else if (clientKey) {
186
- setBrowserId(getBrowserId(clientKey));
283
+ const bid = getBrowserId(clientKey);
284
+ log("BrowserId: resolved via local cookie", bid);
285
+ setBrowserId(bid);
286
+ } else {
287
+ warn("BrowserId: no clientKey and no sitecoreEdgeContextId \u2014 browserId will be empty");
187
288
  }
188
289
  }, [useEdgeProxy, sitecoreEdgeContextId, sitecoreEdgeUrl, siteName, clientKey]);
189
290
  useEffect(() => {
190
291
  if (isEditingProp === void 0) {
191
- setDetectedEditing(isEditingMode());
292
+ const detected = isEditingMode();
293
+ log("Editing mode auto-detected:", detected);
294
+ setDetectedEditing(detected);
295
+ } else {
296
+ log("Editing mode overridden via prop:", isEditingProp);
192
297
  }
193
298
  }, [isEditingProp]);
194
299
  const effectiveEditing = isEditingProp ?? detectedEditing;
195
- const effectiveResolver = useCallback(
196
- resolveDatasource ?? (useEdgeProxy ? createEdgeProxyResolver(sitecoreEdgeUrl, sitecoreEdgeContextId, language) : edgeUrl && apiKey ? createEdgeResolver(edgeUrl, apiKey, language) : noopResolver),
197
- [resolveDatasource, useEdgeProxy, sitecoreEdgeUrl, sitecoreEdgeContextId, edgeUrl, apiKey, language]
198
- );
300
+ const effectiveResolver = useCallback(() => {
301
+ if (resolveDatasource) {
302
+ log("Resolver: using custom resolveDatasource callback");
303
+ return resolveDatasource;
304
+ }
305
+ if (useEdgeProxy) {
306
+ log("Resolver: using Edge proxy GraphQL", { sitecoreEdgeUrl, sitecoreEdgeContextId });
307
+ return createEdgeProxyResolver(sitecoreEdgeUrl, sitecoreEdgeContextId, language);
308
+ }
309
+ if (edgeUrl && apiKey) {
310
+ log("Resolver: using direct Edge GraphQL", { edgeUrl });
311
+ return createEdgeResolver(edgeUrl, apiKey, language);
312
+ }
313
+ warn("Resolver: no resolver configured \u2014 resolveDatasource will return {}. Provide sitecoreEdgeContextId, edgeUrl+apiKey, or a custom resolveDatasource.");
314
+ return noopResolver;
315
+ }, [resolveDatasource, useEdgeProxy, sitecoreEdgeUrl, sitecoreEdgeContextId, edgeUrl, apiKey, language])();
199
316
  const effectiveBrowserId = browserId || (!useEdgeProxy && clientKey && typeof window !== "undefined" ? getBrowserId(clientKey) : "");
200
317
  const value = useMemo(
201
318
  () => ({
@@ -239,10 +356,23 @@ function usePersonalizeContext() {
239
356
  var DEFAULT_API_BASE = "https://api.boxever.com";
240
357
  async function callPersonalize(options) {
241
358
  const { config, context, apiBase = DEFAULT_API_BASE, componentRef, pageRoute } = options;
359
+ group(`callPersonalize [${config.friendlyId}]`);
360
+ log("Config:", { friendlyId: config.friendlyId, defaultKey: config.defaultKey, contentMapKeys: Object.keys(config.contentMap) });
361
+ log("BrowserId:", context.browserId || "(empty \u2014 call will likely fail)");
362
+ if (!context.browserId) {
363
+ warn("BrowserId is empty \u2014 personalize call may fail or return default");
364
+ }
365
+ let result;
242
366
  if (context.useEdgeProxy) {
243
- return callViaEdgeProxy(config, context, componentRef, pageRoute);
367
+ log("Route: Edge proxy");
368
+ result = await callViaEdgeProxy(config, context, componentRef, pageRoute);
369
+ } else {
370
+ log("Route: Legacy /v2/callFlows", { apiBase });
371
+ result = await callViaLegacy(config, context, apiBase, componentRef, pageRoute);
244
372
  }
245
- return callViaLegacy(config, context, apiBase, componentRef, pageRoute);
373
+ log("Result contentKey:", result ?? "(null \u2014 will use defaultKey)");
374
+ groupEnd();
375
+ return result;
246
376
  }
247
377
  async function callViaEdgeProxy(config, context, componentRef, pageRoute) {
248
378
  const base = context.edgeProxyUrl.replace(/\/$/, "");
@@ -260,6 +390,8 @@ async function callViaEdgeProxy(config, context, componentRef, pageRoute) {
260
390
  if (pageRoute) params.pageRoute = pageRoute;
261
391
  body.params = params;
262
392
  }
393
+ log("Edge proxy POST", url);
394
+ log("Request body:", body);
263
395
  const controller = new AbortController();
264
396
  const timeoutId = setTimeout(() => controller.abort(), context.timeout);
265
397
  try {
@@ -270,10 +402,18 @@ async function callViaEdgeProxy(config, context, componentRef, pageRoute) {
270
402
  signal: controller.signal
271
403
  });
272
404
  clearTimeout(timeoutId);
273
- if (!res.ok) return null;
274
- return extractContentKey(await res.json());
275
- } catch {
405
+ log("Edge proxy response status:", res.status);
406
+ if (!res.ok) {
407
+ const text = await res.text().catch(() => "");
408
+ warn("Edge proxy non-OK response:", res.status, text);
409
+ return null;
410
+ }
411
+ const data = await res.json();
412
+ log("Edge proxy response body:", data);
413
+ return extractContentKey(data);
414
+ } catch (e) {
276
415
  clearTimeout(timeoutId);
416
+ error("Edge proxy fetch error:", e);
277
417
  return null;
278
418
  }
279
419
  }
@@ -292,20 +432,31 @@ async function callViaLegacy(config, context, apiBase, componentRef, pageRoute)
292
432
  if (componentRef) body.params.componentRef = componentRef;
293
433
  if (pageRoute) body.params.pageRoute = pageRoute;
294
434
  }
435
+ const url = `${apiBase.replace(/\/$/, "")}/v2/callFlows`;
436
+ log("Legacy POST", url);
437
+ log("Request body:", body);
295
438
  const controller = new AbortController();
296
439
  const timeoutId = setTimeout(() => controller.abort(), context.timeout);
297
440
  try {
298
- const res = await fetch(`${apiBase.replace(/\/$/, "")}/v2/callFlows`, {
441
+ const res = await fetch(url, {
299
442
  method: "POST",
300
443
  headers: { "Content-Type": "application/json" },
301
444
  body: JSON.stringify(body),
302
445
  signal: controller.signal
303
446
  });
304
447
  clearTimeout(timeoutId);
305
- if (!res.ok) return null;
306
- return extractContentKey(await res.json());
307
- } catch {
448
+ log("Legacy response status:", res.status);
449
+ if (!res.ok) {
450
+ const text = await res.text().catch(() => "");
451
+ warn("Legacy non-OK response:", res.status, text);
452
+ return null;
453
+ }
454
+ const data = await res.json();
455
+ log("Legacy response body:", data);
456
+ return extractContentKey(data);
457
+ } catch (e) {
308
458
  clearTimeout(timeoutId);
459
+ error("Legacy fetch error:", e);
309
460
  return null;
310
461
  }
311
462
  }
@@ -319,14 +470,29 @@ function extractContentKey(data) {
319
470
  // src/contentResolver.ts
320
471
  async function resolveContent(options) {
321
472
  const { contentKey, config, resolveDatasource } = options;
322
- const effectiveKey = contentKey && contentKey in config.contentMap ? contentKey : config.defaultKey;
473
+ const keyInMap = contentKey && contentKey in config.contentMap;
474
+ const effectiveKey = keyInMap ? contentKey : config.defaultKey;
475
+ if (!keyInMap) {
476
+ warn(
477
+ `Content key "${contentKey}" not found in contentMap [${Object.keys(config.contentMap).join(", ")}], falling back to defaultKey "${config.defaultKey}"`
478
+ );
479
+ }
323
480
  const datasourceId = config.contentMap[effectiveKey];
324
- if (!datasourceId || typeof datasourceId !== "string") return null;
481
+ log("Resolving datasource:", { effectiveKey, datasourceId });
482
+ if (!datasourceId || typeof datasourceId !== "string") {
483
+ warn(`No datasource ID for key "${effectiveKey}" \u2014 contentMap may be misconfigured`);
484
+ return null;
485
+ }
325
486
  try {
326
487
  const fields = await resolveDatasource(datasourceId);
327
- if (!fields || typeof fields !== "object") return null;
488
+ if (!fields || typeof fields !== "object") {
489
+ warn("resolveDatasource returned empty/invalid fields for", datasourceId);
490
+ return null;
491
+ }
492
+ log("Datasource resolved:", { datasourceId, fieldNames: Object.keys(fields) });
328
493
  return { datasourceId, fields };
329
- } catch {
494
+ } catch (e) {
495
+ error("resolveDatasource threw for", datasourceId, e);
330
496
  return null;
331
497
  }
332
498
  }
@@ -359,24 +525,46 @@ var INDICATOR_BADGE = {
359
525
  pointerEvents: "none"
360
526
  };
361
527
  function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG) {
528
+ const componentName = WrappedComponent.displayName ?? WrappedComponent.name ?? "Component";
362
529
  function PersonalizeConnectWrapper(props) {
363
530
  const context = usePersonalizeContext();
364
531
  const config = getConfig(props);
365
532
  const [resolvedFields, setResolvedFields] = useState2(null);
366
533
  const mountedRef = useRef(true);
534
+ if (!config) {
535
+ log(`[${componentName}] No personalizeConnect config on rendering \u2014 passthrough`);
536
+ } else {
537
+ log(`[${componentName}] Config found:`, { friendlyId: config.friendlyId, defaultKey: config.defaultKey, keys: Object.keys(config.contentMap) });
538
+ }
539
+ if (!context) {
540
+ warn(`[${componentName}] PersonalizeContext is null \u2014 is PersonalizeProvider mounted?`);
541
+ }
367
542
  const runPersonalization = useCallback2(async () => {
368
543
  if (!config || !context) return;
544
+ group(`[${componentName}] personalization flow`);
545
+ log("Calling Personalize for experience:", config.friendlyId);
369
546
  const contentKey = await callPersonalize({ config, context });
370
- if (!mountedRef.current) return;
547
+ if (!mountedRef.current) {
548
+ groupEnd();
549
+ return;
550
+ }
551
+ log("Resolving content for contentKey:", contentKey ?? "(null)");
371
552
  const resolved = await resolveContent({
372
553
  contentKey,
373
554
  config,
374
555
  resolveDatasource: context.resolveDatasource
375
556
  });
376
- if (!mountedRef.current) return;
557
+ if (!mountedRef.current) {
558
+ groupEnd();
559
+ return;
560
+ }
377
561
  if (resolved) {
562
+ log(`[${componentName}] Fields resolved \u2014 swapping props.fields`, { datasourceId: resolved.datasourceId, fieldNames: Object.keys(resolved.fields) });
378
563
  setResolvedFields(resolved.fields);
564
+ } else {
565
+ warn(`[${componentName}] Content resolution returned null \u2014 component keeps original fields`);
379
566
  }
567
+ groupEnd();
380
568
  }, [config, context]);
381
569
  useEffect2(() => {
382
570
  mountedRef.current = true;
@@ -393,6 +581,7 @@ function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG
393
581
  const mergedProps = resolvedFields ? { ...props, fields: resolvedFields } : props;
394
582
  const component = /* @__PURE__ */ jsx2(WrappedComponent, { ...mergedProps });
395
583
  if (context.isEditing) {
584
+ log(`[${componentName}] Editing mode \u2014 rendering indicator badge`);
396
585
  return /* @__PURE__ */ jsxs("div", { style: INDICATOR_BORDER, "data-personalize-connect": config.friendlyId, children: [
397
586
  /* @__PURE__ */ jsx2("span", { style: INDICATOR_BADGE, title: `Personalize: ${config.friendlyId}`, children: "\u26A1 Personalized" }),
398
587
  component
@@ -400,7 +589,7 @@ function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG
400
589
  }
401
590
  return component;
402
591
  }
403
- PersonalizeConnectWrapper.displayName = `WithPersonalizeConnect(${WrappedComponent.displayName ?? WrappedComponent.name ?? "Component"})`;
592
+ PersonalizeConnectWrapper.displayName = `WithPersonalizeConnect(${componentName})`;
404
593
  return PersonalizeConnectWrapper;
405
594
  }
406
595
 
@@ -411,7 +600,7 @@ function usePersonalizeExperience(config) {
411
600
  const [contentKey, setContentKey] = useState3(null);
412
601
  const [resolvedFields, setResolvedFields] = useState3(null);
413
602
  const [isLoading, setIsLoading] = useState3(true);
414
- const [error, setError] = useState3(null);
603
+ const [error2, setError] = useState3(null);
415
604
  const mountedRef = useRef2(true);
416
605
  const runPersonalization = useCallback3(async () => {
417
606
  if (!config || !context) {
@@ -452,7 +641,7 @@ function usePersonalizeExperience(config) {
452
641
  contentKey,
453
642
  resolvedFields,
454
643
  isLoading,
455
- error
644
+ error: error2
456
645
  };
457
646
  }
458
647
  export {
@@ -462,10 +651,12 @@ export {
462
651
  createEdgeResolver,
463
652
  getBrowserId,
464
653
  getEdgeBrowserId,
654
+ isDebugEnabled,
465
655
  isEditingMode,
466
656
  resetEdgeInitCache,
467
657
  resetEditingDetectionCache,
468
658
  resolveContent,
659
+ setDebug,
469
660
  usePersonalizeContext,
470
661
  usePersonalizeExperience,
471
662
  withPersonalizeConnect
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "personalize-connect-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Runtime SDK for Personalize Connect - resolves active experience outcomes and datasources in your rendering host",
5
5
  "author": "Dylan Young",
6
6
  "keywords": [