personalize-connect-sdk 1.0.0
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 +82 -0
- package/dist/index.d.mts +144 -0
- package/dist/index.d.ts +144 -0
- package/dist/index.js +283 -0
- package/dist/index.mjs +250 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Personalize Connect SDK
|
|
2
|
+
|
|
3
|
+
A lightweight Next.js package that bridges Sitecore Personalize Interactive Experiences with XM Cloud component datasources at runtime. The SDK reads configuration authored by the Marketplace app, calls Personalize for a decision, and swaps component content accordingly — with zero per-component code.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install personalize-connect-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Peer dependency: `react` >= 18.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### 1. Wrap your app with `PersonalizeProvider`
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { PersonalizeProvider } from "personalize-connect-sdk";
|
|
19
|
+
|
|
20
|
+
export default function RootLayout({ children }) {
|
|
21
|
+
return (
|
|
22
|
+
<PersonalizeProvider
|
|
23
|
+
clientKey={process.env.NEXT_PUBLIC_PERSONALIZE_CLIENT_KEY!}
|
|
24
|
+
pointOfSale={process.env.NEXT_PUBLIC_PERSONALIZE_POINT_OF_SALE!}
|
|
25
|
+
resolveDatasource={async (datasourceId) => {
|
|
26
|
+
// Fetch datasource fields via Experience Edge GraphQL or Layout Service
|
|
27
|
+
const res = await fetch(`/api/datasource/${datasourceId}`);
|
|
28
|
+
return res.json();
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
{children}
|
|
32
|
+
</PersonalizeProvider>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Wrap components with `withPersonalizeConnect`
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { withPersonalizeConnect } from "personalize-connect-sdk";
|
|
41
|
+
import MyComponent from "./MyComponent";
|
|
42
|
+
|
|
43
|
+
// Config is read from props.rendering.personalizeConnect (or customize via getConfig)
|
|
44
|
+
export default withPersonalizeConnect(MyComponent);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The HOC renders immediately with the default datasource, calls Personalize asynchronously, and re-renders with personalized content when resolved. No changes needed inside the component.
|
|
48
|
+
|
|
49
|
+
### 3. Or use the `usePersonalizeExperience` hook
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import { usePersonalizeExperience } from "personalize-connect-sdk";
|
|
53
|
+
|
|
54
|
+
function MyComponent({ personalizeConnect }) {
|
|
55
|
+
const { contentKey, resolvedFields, isLoading, error } = usePersonalizeExperience(personalizeConnect);
|
|
56
|
+
|
|
57
|
+
if (isLoading) return <Skeleton />;
|
|
58
|
+
return <div>{resolvedFields?.heading}</div>;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Config shape
|
|
63
|
+
|
|
64
|
+
Config is attached to each rendering in XMC layout data (authored by the Marketplace app):
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
interface PersonalizeConnectConfig {
|
|
68
|
+
friendlyId: string; // Personalize Interactive Experience ID
|
|
69
|
+
contentMap: Record<string, string>; // contentKey -> datasource GUID
|
|
70
|
+
defaultKey: string; // Fallback key if experience fails
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Exports
|
|
75
|
+
|
|
76
|
+
- `PersonalizeProvider`, `usePersonalizeContext` — Provider and context
|
|
77
|
+
- `withPersonalizeConnect` — HOC for zero-code personalization
|
|
78
|
+
- `usePersonalizeExperience` — Hook for manual control
|
|
79
|
+
- `callPersonalize` — Low-level API client
|
|
80
|
+
- `resolveContent` — Map contentKey to datasource fields
|
|
81
|
+
- `getBrowserId` — Browser ID cookie helper
|
|
82
|
+
- Types: `PersonalizeConnectConfig`, `PersonalizeConnectProviderProps`, etc.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { ReactNode, ComponentType } from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Personalize Connect SDK types.
|
|
6
|
+
* Stored on the rendering in XMC layout data.
|
|
7
|
+
*/
|
|
8
|
+
interface PersonalizeConnectConfig {
|
|
9
|
+
/** The Personalize Interactive Experience friendly ID */
|
|
10
|
+
friendlyId: string;
|
|
11
|
+
/**
|
|
12
|
+
* Maps contentKey values (returned by the experience)
|
|
13
|
+
* to XMC datasource item GUIDs
|
|
14
|
+
*/
|
|
15
|
+
contentMap: Record<string, string>;
|
|
16
|
+
/**
|
|
17
|
+
* Which contentMap key to render on initial load
|
|
18
|
+
* and use as fallback if the experience fails
|
|
19
|
+
* or returns an unrecognized key
|
|
20
|
+
*/
|
|
21
|
+
defaultKey: string;
|
|
22
|
+
}
|
|
23
|
+
/** The standardized API response contract from Personalize */
|
|
24
|
+
interface PersonalizeConnectResponse {
|
|
25
|
+
contentKey: string;
|
|
26
|
+
}
|
|
27
|
+
/** What the SDK sends to Personalize /v2/callFlows */
|
|
28
|
+
interface CallFlowsRequest {
|
|
29
|
+
clientKey: string;
|
|
30
|
+
channel: string;
|
|
31
|
+
language: string;
|
|
32
|
+
currencyCode: string;
|
|
33
|
+
pointOfSale: string;
|
|
34
|
+
browserId: string;
|
|
35
|
+
friendlyId: string;
|
|
36
|
+
params?: {
|
|
37
|
+
componentRef?: string;
|
|
38
|
+
pageRoute?: string;
|
|
39
|
+
[key: string]: unknown;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Provider configuration */
|
|
43
|
+
interface PersonalizeConnectProviderProps {
|
|
44
|
+
clientKey: string;
|
|
45
|
+
pointOfSale: string;
|
|
46
|
+
channel?: string;
|
|
47
|
+
language?: string;
|
|
48
|
+
currencyCode?: string;
|
|
49
|
+
timeout?: number;
|
|
50
|
+
/** Custom function to fetch datasource fields by item ID (e.g. via GraphQL or Layout Service) */
|
|
51
|
+
resolveDatasource?: (datasourceId: string) => Promise<ComponentFields>;
|
|
52
|
+
children: ReactNode;
|
|
53
|
+
}
|
|
54
|
+
/** Resolved datasource fields passed as component props */
|
|
55
|
+
type ComponentFields = Record<string, unknown>;
|
|
56
|
+
/** Value exposed by PersonalizeContext */
|
|
57
|
+
interface PersonalizeContextValue {
|
|
58
|
+
clientKey: string;
|
|
59
|
+
pointOfSale: string;
|
|
60
|
+
channel: string;
|
|
61
|
+
language: string;
|
|
62
|
+
currencyCode: string;
|
|
63
|
+
timeout: number;
|
|
64
|
+
browserId: string;
|
|
65
|
+
resolveDatasource: (datasourceId: string) => Promise<ComponentFields>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
declare function PersonalizeProvider({ children, clientKey, pointOfSale, channel, language, currencyCode, timeout, resolveDatasource, }: PersonalizeConnectProviderProps): react_jsx_runtime.JSX.Element;
|
|
69
|
+
declare function usePersonalizeContext(): PersonalizeContextValue | null;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Thin async wrapper around POST /v2/callFlows.
|
|
73
|
+
* Enforces timeout, parses response, validates contentKey.
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
interface CallPersonalizeOptions {
|
|
77
|
+
config: PersonalizeConnectConfig;
|
|
78
|
+
context: PersonalizeContextValue;
|
|
79
|
+
/** Optional: override API base URL (e.g. region-specific) */
|
|
80
|
+
apiBase?: string;
|
|
81
|
+
/** Optional: component ref for params */
|
|
82
|
+
componentRef?: string;
|
|
83
|
+
/** Optional: page route for params */
|
|
84
|
+
pageRoute?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Call Personalize /v2/callFlows to get the active content key.
|
|
88
|
+
* Returns the contentKey on success, null on any failure.
|
|
89
|
+
*/
|
|
90
|
+
declare function callPersonalize(options: CallPersonalizeOptions): Promise<string | null>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Maps contentKey to datasource GUID via contentMap,
|
|
94
|
+
* fetches datasource fields, and falls back to defaultKey on failure.
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
interface ResolveContentOptions {
|
|
98
|
+
contentKey: string | null;
|
|
99
|
+
config: PersonalizeConnectConfig;
|
|
100
|
+
resolveDatasource: (datasourceId: string) => Promise<ComponentFields>;
|
|
101
|
+
}
|
|
102
|
+
interface ResolvedContent {
|
|
103
|
+
datasourceId: string;
|
|
104
|
+
fields: ComponentFields;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Resolve content key to datasource fields.
|
|
108
|
+
* Falls back to defaultKey if contentKey is null or not in contentMap.
|
|
109
|
+
* Returns null on any fetch failure.
|
|
110
|
+
*/
|
|
111
|
+
declare function resolveContent(options: ResolveContentOptions): Promise<ResolvedContent | null>;
|
|
112
|
+
|
|
113
|
+
type GetConfigFromProps<P> = (props: P) => PersonalizeConnectConfig | undefined;
|
|
114
|
+
/**
|
|
115
|
+
* HOC that wraps any JSS/Content SDK component.
|
|
116
|
+
* If the rendering has a personalizeConnect config, it renders with defaultKey first,
|
|
117
|
+
* then asynchronously fetches the personalized content and re-renders.
|
|
118
|
+
* If no config, passes through unchanged.
|
|
119
|
+
*/
|
|
120
|
+
declare function withPersonalizeConnect<P extends object>(WrappedComponent: ComponentType<P>, getConfig?: GetConfigFromProps<P>): ComponentType<P>;
|
|
121
|
+
|
|
122
|
+
interface UsePersonalizeExperienceResult {
|
|
123
|
+
contentKey: string | null;
|
|
124
|
+
resolvedFields: ComponentFields | null;
|
|
125
|
+
isLoading: boolean;
|
|
126
|
+
error: Error | null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Hook alternative for cases where the HOC pattern doesn't fit.
|
|
130
|
+
* Returns contentKey, resolvedFields, isLoading, and error.
|
|
131
|
+
*/
|
|
132
|
+
declare function usePersonalizeExperience(config: PersonalizeConnectConfig | undefined): UsePersonalizeExperienceResult;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Manages the Sitecore CDP browser ID cookie (bid_<clientKey>).
|
|
136
|
+
* Used for session continuity across Personalize decisions.
|
|
137
|
+
*/
|
|
138
|
+
/**
|
|
139
|
+
* Get or create the browser ID for the given client key.
|
|
140
|
+
* Reads from cookie bid_<clientKey>, writes if missing, and returns the value.
|
|
141
|
+
*/
|
|
142
|
+
declare function getBrowserId(clientKey: string): string;
|
|
143
|
+
|
|
144
|
+
export { type CallFlowsRequest, type CallPersonalizeOptions, type ComponentFields, type GetConfigFromProps, type PersonalizeConnectConfig, type PersonalizeConnectProviderProps, type PersonalizeConnectResponse, type PersonalizeContextValue, PersonalizeProvider, type ResolveContentOptions, type ResolvedContent, type UsePersonalizeExperienceResult, callPersonalize, getBrowserId, resolveContent, usePersonalizeContext, usePersonalizeExperience, withPersonalizeConnect };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { ReactNode, ComponentType } from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Personalize Connect SDK types.
|
|
6
|
+
* Stored on the rendering in XMC layout data.
|
|
7
|
+
*/
|
|
8
|
+
interface PersonalizeConnectConfig {
|
|
9
|
+
/** The Personalize Interactive Experience friendly ID */
|
|
10
|
+
friendlyId: string;
|
|
11
|
+
/**
|
|
12
|
+
* Maps contentKey values (returned by the experience)
|
|
13
|
+
* to XMC datasource item GUIDs
|
|
14
|
+
*/
|
|
15
|
+
contentMap: Record<string, string>;
|
|
16
|
+
/**
|
|
17
|
+
* Which contentMap key to render on initial load
|
|
18
|
+
* and use as fallback if the experience fails
|
|
19
|
+
* or returns an unrecognized key
|
|
20
|
+
*/
|
|
21
|
+
defaultKey: string;
|
|
22
|
+
}
|
|
23
|
+
/** The standardized API response contract from Personalize */
|
|
24
|
+
interface PersonalizeConnectResponse {
|
|
25
|
+
contentKey: string;
|
|
26
|
+
}
|
|
27
|
+
/** What the SDK sends to Personalize /v2/callFlows */
|
|
28
|
+
interface CallFlowsRequest {
|
|
29
|
+
clientKey: string;
|
|
30
|
+
channel: string;
|
|
31
|
+
language: string;
|
|
32
|
+
currencyCode: string;
|
|
33
|
+
pointOfSale: string;
|
|
34
|
+
browserId: string;
|
|
35
|
+
friendlyId: string;
|
|
36
|
+
params?: {
|
|
37
|
+
componentRef?: string;
|
|
38
|
+
pageRoute?: string;
|
|
39
|
+
[key: string]: unknown;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Provider configuration */
|
|
43
|
+
interface PersonalizeConnectProviderProps {
|
|
44
|
+
clientKey: string;
|
|
45
|
+
pointOfSale: string;
|
|
46
|
+
channel?: string;
|
|
47
|
+
language?: string;
|
|
48
|
+
currencyCode?: string;
|
|
49
|
+
timeout?: number;
|
|
50
|
+
/** Custom function to fetch datasource fields by item ID (e.g. via GraphQL or Layout Service) */
|
|
51
|
+
resolveDatasource?: (datasourceId: string) => Promise<ComponentFields>;
|
|
52
|
+
children: ReactNode;
|
|
53
|
+
}
|
|
54
|
+
/** Resolved datasource fields passed as component props */
|
|
55
|
+
type ComponentFields = Record<string, unknown>;
|
|
56
|
+
/** Value exposed by PersonalizeContext */
|
|
57
|
+
interface PersonalizeContextValue {
|
|
58
|
+
clientKey: string;
|
|
59
|
+
pointOfSale: string;
|
|
60
|
+
channel: string;
|
|
61
|
+
language: string;
|
|
62
|
+
currencyCode: string;
|
|
63
|
+
timeout: number;
|
|
64
|
+
browserId: string;
|
|
65
|
+
resolveDatasource: (datasourceId: string) => Promise<ComponentFields>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
declare function PersonalizeProvider({ children, clientKey, pointOfSale, channel, language, currencyCode, timeout, resolveDatasource, }: PersonalizeConnectProviderProps): react_jsx_runtime.JSX.Element;
|
|
69
|
+
declare function usePersonalizeContext(): PersonalizeContextValue | null;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Thin async wrapper around POST /v2/callFlows.
|
|
73
|
+
* Enforces timeout, parses response, validates contentKey.
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
interface CallPersonalizeOptions {
|
|
77
|
+
config: PersonalizeConnectConfig;
|
|
78
|
+
context: PersonalizeContextValue;
|
|
79
|
+
/** Optional: override API base URL (e.g. region-specific) */
|
|
80
|
+
apiBase?: string;
|
|
81
|
+
/** Optional: component ref for params */
|
|
82
|
+
componentRef?: string;
|
|
83
|
+
/** Optional: page route for params */
|
|
84
|
+
pageRoute?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Call Personalize /v2/callFlows to get the active content key.
|
|
88
|
+
* Returns the contentKey on success, null on any failure.
|
|
89
|
+
*/
|
|
90
|
+
declare function callPersonalize(options: CallPersonalizeOptions): Promise<string | null>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Maps contentKey to datasource GUID via contentMap,
|
|
94
|
+
* fetches datasource fields, and falls back to defaultKey on failure.
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
interface ResolveContentOptions {
|
|
98
|
+
contentKey: string | null;
|
|
99
|
+
config: PersonalizeConnectConfig;
|
|
100
|
+
resolveDatasource: (datasourceId: string) => Promise<ComponentFields>;
|
|
101
|
+
}
|
|
102
|
+
interface ResolvedContent {
|
|
103
|
+
datasourceId: string;
|
|
104
|
+
fields: ComponentFields;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Resolve content key to datasource fields.
|
|
108
|
+
* Falls back to defaultKey if contentKey is null or not in contentMap.
|
|
109
|
+
* Returns null on any fetch failure.
|
|
110
|
+
*/
|
|
111
|
+
declare function resolveContent(options: ResolveContentOptions): Promise<ResolvedContent | null>;
|
|
112
|
+
|
|
113
|
+
type GetConfigFromProps<P> = (props: P) => PersonalizeConnectConfig | undefined;
|
|
114
|
+
/**
|
|
115
|
+
* HOC that wraps any JSS/Content SDK component.
|
|
116
|
+
* If the rendering has a personalizeConnect config, it renders with defaultKey first,
|
|
117
|
+
* then asynchronously fetches the personalized content and re-renders.
|
|
118
|
+
* If no config, passes through unchanged.
|
|
119
|
+
*/
|
|
120
|
+
declare function withPersonalizeConnect<P extends object>(WrappedComponent: ComponentType<P>, getConfig?: GetConfigFromProps<P>): ComponentType<P>;
|
|
121
|
+
|
|
122
|
+
interface UsePersonalizeExperienceResult {
|
|
123
|
+
contentKey: string | null;
|
|
124
|
+
resolvedFields: ComponentFields | null;
|
|
125
|
+
isLoading: boolean;
|
|
126
|
+
error: Error | null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Hook alternative for cases where the HOC pattern doesn't fit.
|
|
130
|
+
* Returns contentKey, resolvedFields, isLoading, and error.
|
|
131
|
+
*/
|
|
132
|
+
declare function usePersonalizeExperience(config: PersonalizeConnectConfig | undefined): UsePersonalizeExperienceResult;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Manages the Sitecore CDP browser ID cookie (bid_<clientKey>).
|
|
136
|
+
* Used for session continuity across Personalize decisions.
|
|
137
|
+
*/
|
|
138
|
+
/**
|
|
139
|
+
* Get or create the browser ID for the given client key.
|
|
140
|
+
* Reads from cookie bid_<clientKey>, writes if missing, and returns the value.
|
|
141
|
+
*/
|
|
142
|
+
declare function getBrowserId(clientKey: string): string;
|
|
143
|
+
|
|
144
|
+
export { type CallFlowsRequest, type CallPersonalizeOptions, type ComponentFields, type GetConfigFromProps, type PersonalizeConnectConfig, type PersonalizeConnectProviderProps, type PersonalizeConnectResponse, type PersonalizeContextValue, PersonalizeProvider, type ResolveContentOptions, type ResolvedContent, type UsePersonalizeExperienceResult, callPersonalize, getBrowserId, resolveContent, usePersonalizeContext, usePersonalizeExperience, withPersonalizeConnect };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
PersonalizeProvider: () => PersonalizeProvider,
|
|
24
|
+
callPersonalize: () => callPersonalize,
|
|
25
|
+
getBrowserId: () => getBrowserId,
|
|
26
|
+
resolveContent: () => resolveContent,
|
|
27
|
+
usePersonalizeContext: () => usePersonalizeContext,
|
|
28
|
+
usePersonalizeExperience: () => usePersonalizeExperience,
|
|
29
|
+
withPersonalizeConnect: () => withPersonalizeConnect
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/PersonalizeProvider.tsx
|
|
34
|
+
var import_react = require("react");
|
|
35
|
+
|
|
36
|
+
// src/browserId.ts
|
|
37
|
+
var COOKIE_PREFIX = "bid_";
|
|
38
|
+
var COOKIE_MAX_AGE = 63072e3;
|
|
39
|
+
var COOKIE_PATH = "/";
|
|
40
|
+
var COOKIE_SAME_SITE = "Lax";
|
|
41
|
+
function generateBrowserId() {
|
|
42
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
43
|
+
return crypto.randomUUID();
|
|
44
|
+
}
|
|
45
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
46
|
+
const r = Math.random() * 16 | 0;
|
|
47
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
48
|
+
return v.toString(16);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function getCookie(name) {
|
|
52
|
+
if (typeof document === "undefined") return null;
|
|
53
|
+
const match = document.cookie.match(new RegExp("(?:^|; )" + encodeURIComponent(name) + "=([^;]*)"));
|
|
54
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
55
|
+
}
|
|
56
|
+
function setCookie(name, value) {
|
|
57
|
+
if (typeof document === "undefined") return;
|
|
58
|
+
document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + "; path=" + COOKIE_PATH + "; max-age=" + COOKIE_MAX_AGE + "; SameSite=" + COOKIE_SAME_SITE;
|
|
59
|
+
}
|
|
60
|
+
function getBrowserId(clientKey) {
|
|
61
|
+
const cookieName = COOKIE_PREFIX + clientKey;
|
|
62
|
+
let bid = getCookie(cookieName);
|
|
63
|
+
if (!bid || typeof bid !== "string" || bid.length < 10) {
|
|
64
|
+
bid = generateBrowserId();
|
|
65
|
+
setCookie(cookieName, bid);
|
|
66
|
+
}
|
|
67
|
+
return bid;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/PersonalizeProvider.tsx
|
|
71
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
72
|
+
var PersonalizeContext = (0, import_react.createContext)(null);
|
|
73
|
+
var DEFAULT_CHANNEL = "WEB";
|
|
74
|
+
var DEFAULT_LANGUAGE = "EN";
|
|
75
|
+
var DEFAULT_CURRENCY = "USD";
|
|
76
|
+
var DEFAULT_TIMEOUT = 600;
|
|
77
|
+
var noopResolver = async () => ({});
|
|
78
|
+
function PersonalizeProvider({
|
|
79
|
+
children,
|
|
80
|
+
clientKey,
|
|
81
|
+
pointOfSale,
|
|
82
|
+
channel = DEFAULT_CHANNEL,
|
|
83
|
+
language = DEFAULT_LANGUAGE,
|
|
84
|
+
currencyCode = DEFAULT_CURRENCY,
|
|
85
|
+
timeout = DEFAULT_TIMEOUT,
|
|
86
|
+
resolveDatasource = noopResolver
|
|
87
|
+
}) {
|
|
88
|
+
const [browserId, setBrowserId] = (0, import_react.useState)("");
|
|
89
|
+
(0, import_react.useEffect)(() => {
|
|
90
|
+
setBrowserId(getBrowserId(clientKey));
|
|
91
|
+
}, [clientKey]);
|
|
92
|
+
const stableResolveDatasource = (0, import_react.useCallback)(resolveDatasource, [resolveDatasource]);
|
|
93
|
+
const effectiveBrowserId = browserId || (typeof window !== "undefined" ? getBrowserId(clientKey) : "");
|
|
94
|
+
const value = (0, import_react.useMemo)(
|
|
95
|
+
() => ({
|
|
96
|
+
clientKey,
|
|
97
|
+
pointOfSale,
|
|
98
|
+
channel,
|
|
99
|
+
language,
|
|
100
|
+
currencyCode,
|
|
101
|
+
timeout,
|
|
102
|
+
browserId: effectiveBrowserId,
|
|
103
|
+
resolveDatasource: stableResolveDatasource
|
|
104
|
+
}),
|
|
105
|
+
[
|
|
106
|
+
clientKey,
|
|
107
|
+
pointOfSale,
|
|
108
|
+
channel,
|
|
109
|
+
language,
|
|
110
|
+
currencyCode,
|
|
111
|
+
timeout,
|
|
112
|
+
effectiveBrowserId,
|
|
113
|
+
stableResolveDatasource
|
|
114
|
+
]
|
|
115
|
+
);
|
|
116
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PersonalizeContext.Provider, { value, children });
|
|
117
|
+
}
|
|
118
|
+
function usePersonalizeContext() {
|
|
119
|
+
return (0, import_react.useContext)(PersonalizeContext);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/personalizeClient.ts
|
|
123
|
+
var DEFAULT_API_BASE = "https://api.boxever.com";
|
|
124
|
+
async function callPersonalize(options) {
|
|
125
|
+
const { config, context, apiBase = DEFAULT_API_BASE, componentRef, pageRoute } = options;
|
|
126
|
+
const body = {
|
|
127
|
+
clientKey: context.clientKey,
|
|
128
|
+
channel: context.channel,
|
|
129
|
+
language: context.language,
|
|
130
|
+
currencyCode: context.currencyCode,
|
|
131
|
+
pointOfSale: context.pointOfSale,
|
|
132
|
+
browserId: context.browserId,
|
|
133
|
+
friendlyId: config.friendlyId
|
|
134
|
+
};
|
|
135
|
+
if (componentRef || pageRoute) {
|
|
136
|
+
body.params = {};
|
|
137
|
+
if (componentRef) body.params.componentRef = componentRef;
|
|
138
|
+
if (pageRoute) body.params.pageRoute = pageRoute;
|
|
139
|
+
}
|
|
140
|
+
const controller = new AbortController();
|
|
141
|
+
const timeoutId = setTimeout(() => controller.abort(), context.timeout);
|
|
142
|
+
try {
|
|
143
|
+
const res = await fetch(`${apiBase.replace(/\/$/, "")}/v2/callFlows`, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: { "Content-Type": "application/json" },
|
|
146
|
+
body: JSON.stringify(body),
|
|
147
|
+
signal: controller.signal
|
|
148
|
+
});
|
|
149
|
+
clearTimeout(timeoutId);
|
|
150
|
+
if (!res.ok) return null;
|
|
151
|
+
const data = await res.json();
|
|
152
|
+
if (!data || typeof data !== "object") return null;
|
|
153
|
+
const contentKey = data.contentKey;
|
|
154
|
+
if (typeof contentKey !== "string" || contentKey.trim() === "") return null;
|
|
155
|
+
return contentKey;
|
|
156
|
+
} catch {
|
|
157
|
+
clearTimeout(timeoutId);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/contentResolver.ts
|
|
163
|
+
async function resolveContent(options) {
|
|
164
|
+
const { contentKey, config, resolveDatasource } = options;
|
|
165
|
+
const effectiveKey = contentKey && contentKey in config.contentMap ? contentKey : config.defaultKey;
|
|
166
|
+
const datasourceId = config.contentMap[effectiveKey];
|
|
167
|
+
if (!datasourceId || typeof datasourceId !== "string") return null;
|
|
168
|
+
try {
|
|
169
|
+
const fields = await resolveDatasource(datasourceId);
|
|
170
|
+
if (!fields || typeof fields !== "object") return null;
|
|
171
|
+
return { datasourceId, fields };
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/withPersonalizeConnect.tsx
|
|
178
|
+
var import_react2 = require("react");
|
|
179
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
180
|
+
var DEFAULT_GET_CONFIG = (props) => props.rendering?.personalizeConnect;
|
|
181
|
+
function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG) {
|
|
182
|
+
function PersonalizeConnectWrapper(props) {
|
|
183
|
+
const context = usePersonalizeContext();
|
|
184
|
+
const config = getConfig(props);
|
|
185
|
+
const [resolvedFields, setResolvedFields] = (0, import_react2.useState)(null);
|
|
186
|
+
const mountedRef = (0, import_react2.useRef)(true);
|
|
187
|
+
const runPersonalization = (0, import_react2.useCallback)(async () => {
|
|
188
|
+
if (!config || !context) return;
|
|
189
|
+
const contentKey = await callPersonalize({ config, context });
|
|
190
|
+
if (!mountedRef.current) return;
|
|
191
|
+
const resolved = await resolveContent({
|
|
192
|
+
contentKey,
|
|
193
|
+
config,
|
|
194
|
+
resolveDatasource: context.resolveDatasource
|
|
195
|
+
});
|
|
196
|
+
if (!mountedRef.current) return;
|
|
197
|
+
if (resolved) {
|
|
198
|
+
setResolvedFields(resolved.fields);
|
|
199
|
+
}
|
|
200
|
+
}, [config, context]);
|
|
201
|
+
(0, import_react2.useEffect)(() => {
|
|
202
|
+
mountedRef.current = true;
|
|
203
|
+
if (config && context) {
|
|
204
|
+
runPersonalization();
|
|
205
|
+
}
|
|
206
|
+
return () => {
|
|
207
|
+
mountedRef.current = false;
|
|
208
|
+
};
|
|
209
|
+
}, [config?.friendlyId, context?.clientKey, runPersonalization]);
|
|
210
|
+
if (!config || !context) {
|
|
211
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(WrappedComponent, { ...props });
|
|
212
|
+
}
|
|
213
|
+
const mergedProps = {
|
|
214
|
+
...props,
|
|
215
|
+
...resolvedFields ?? {}
|
|
216
|
+
};
|
|
217
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(WrappedComponent, { ...mergedProps });
|
|
218
|
+
}
|
|
219
|
+
PersonalizeConnectWrapper.displayName = `WithPersonalizeConnect(${WrappedComponent.displayName ?? WrappedComponent.name ?? "Component"})`;
|
|
220
|
+
return PersonalizeConnectWrapper;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/usePersonalizeExperience.ts
|
|
224
|
+
var import_react3 = require("react");
|
|
225
|
+
function usePersonalizeExperience(config) {
|
|
226
|
+
const context = usePersonalizeContext();
|
|
227
|
+
const [contentKey, setContentKey] = (0, import_react3.useState)(null);
|
|
228
|
+
const [resolvedFields, setResolvedFields] = (0, import_react3.useState)(null);
|
|
229
|
+
const [isLoading, setIsLoading] = (0, import_react3.useState)(true);
|
|
230
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
231
|
+
const mountedRef = (0, import_react3.useRef)(true);
|
|
232
|
+
const runPersonalization = (0, import_react3.useCallback)(async () => {
|
|
233
|
+
if (!config || !context) {
|
|
234
|
+
setIsLoading(false);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
setError(null);
|
|
238
|
+
setContentKey(null);
|
|
239
|
+
setResolvedFields(null);
|
|
240
|
+
setIsLoading(true);
|
|
241
|
+
try {
|
|
242
|
+
const key = await callPersonalize({ config, context });
|
|
243
|
+
if (!mountedRef.current) return;
|
|
244
|
+
setContentKey(key);
|
|
245
|
+
const resolved = await resolveContent({
|
|
246
|
+
contentKey: key,
|
|
247
|
+
config,
|
|
248
|
+
resolveDatasource: context.resolveDatasource
|
|
249
|
+
});
|
|
250
|
+
if (!mountedRef.current) return;
|
|
251
|
+
if (resolved) setResolvedFields(resolved.fields);
|
|
252
|
+
} catch (e) {
|
|
253
|
+
if (mountedRef.current) {
|
|
254
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
255
|
+
}
|
|
256
|
+
} finally {
|
|
257
|
+
if (mountedRef.current) setIsLoading(false);
|
|
258
|
+
}
|
|
259
|
+
}, [config, context]);
|
|
260
|
+
(0, import_react3.useEffect)(() => {
|
|
261
|
+
mountedRef.current = true;
|
|
262
|
+
runPersonalization();
|
|
263
|
+
return () => {
|
|
264
|
+
mountedRef.current = false;
|
|
265
|
+
};
|
|
266
|
+
}, [runPersonalization]);
|
|
267
|
+
return {
|
|
268
|
+
contentKey,
|
|
269
|
+
resolvedFields,
|
|
270
|
+
isLoading,
|
|
271
|
+
error
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
275
|
+
0 && (module.exports = {
|
|
276
|
+
PersonalizeProvider,
|
|
277
|
+
callPersonalize,
|
|
278
|
+
getBrowserId,
|
|
279
|
+
resolveContent,
|
|
280
|
+
usePersonalizeContext,
|
|
281
|
+
usePersonalizeExperience,
|
|
282
|
+
withPersonalizeConnect
|
|
283
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// src/PersonalizeProvider.tsx
|
|
2
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
3
|
+
|
|
4
|
+
// src/browserId.ts
|
|
5
|
+
var COOKIE_PREFIX = "bid_";
|
|
6
|
+
var COOKIE_MAX_AGE = 63072e3;
|
|
7
|
+
var COOKIE_PATH = "/";
|
|
8
|
+
var COOKIE_SAME_SITE = "Lax";
|
|
9
|
+
function generateBrowserId() {
|
|
10
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
11
|
+
return crypto.randomUUID();
|
|
12
|
+
}
|
|
13
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
14
|
+
const r = Math.random() * 16 | 0;
|
|
15
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
16
|
+
return v.toString(16);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function getCookie(name) {
|
|
20
|
+
if (typeof document === "undefined") return null;
|
|
21
|
+
const match = document.cookie.match(new RegExp("(?:^|; )" + encodeURIComponent(name) + "=([^;]*)"));
|
|
22
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
23
|
+
}
|
|
24
|
+
function setCookie(name, value) {
|
|
25
|
+
if (typeof document === "undefined") return;
|
|
26
|
+
document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + "; path=" + COOKIE_PATH + "; max-age=" + COOKIE_MAX_AGE + "; SameSite=" + COOKIE_SAME_SITE;
|
|
27
|
+
}
|
|
28
|
+
function getBrowserId(clientKey) {
|
|
29
|
+
const cookieName = COOKIE_PREFIX + clientKey;
|
|
30
|
+
let bid = getCookie(cookieName);
|
|
31
|
+
if (!bid || typeof bid !== "string" || bid.length < 10) {
|
|
32
|
+
bid = generateBrowserId();
|
|
33
|
+
setCookie(cookieName, bid);
|
|
34
|
+
}
|
|
35
|
+
return bid;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/PersonalizeProvider.tsx
|
|
39
|
+
import { jsx } from "react/jsx-runtime";
|
|
40
|
+
var PersonalizeContext = createContext(null);
|
|
41
|
+
var DEFAULT_CHANNEL = "WEB";
|
|
42
|
+
var DEFAULT_LANGUAGE = "EN";
|
|
43
|
+
var DEFAULT_CURRENCY = "USD";
|
|
44
|
+
var DEFAULT_TIMEOUT = 600;
|
|
45
|
+
var noopResolver = async () => ({});
|
|
46
|
+
function PersonalizeProvider({
|
|
47
|
+
children,
|
|
48
|
+
clientKey,
|
|
49
|
+
pointOfSale,
|
|
50
|
+
channel = DEFAULT_CHANNEL,
|
|
51
|
+
language = DEFAULT_LANGUAGE,
|
|
52
|
+
currencyCode = DEFAULT_CURRENCY,
|
|
53
|
+
timeout = DEFAULT_TIMEOUT,
|
|
54
|
+
resolveDatasource = noopResolver
|
|
55
|
+
}) {
|
|
56
|
+
const [browserId, setBrowserId] = useState("");
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
setBrowserId(getBrowserId(clientKey));
|
|
59
|
+
}, [clientKey]);
|
|
60
|
+
const stableResolveDatasource = useCallback(resolveDatasource, [resolveDatasource]);
|
|
61
|
+
const effectiveBrowserId = browserId || (typeof window !== "undefined" ? getBrowserId(clientKey) : "");
|
|
62
|
+
const value = useMemo(
|
|
63
|
+
() => ({
|
|
64
|
+
clientKey,
|
|
65
|
+
pointOfSale,
|
|
66
|
+
channel,
|
|
67
|
+
language,
|
|
68
|
+
currencyCode,
|
|
69
|
+
timeout,
|
|
70
|
+
browserId: effectiveBrowserId,
|
|
71
|
+
resolveDatasource: stableResolveDatasource
|
|
72
|
+
}),
|
|
73
|
+
[
|
|
74
|
+
clientKey,
|
|
75
|
+
pointOfSale,
|
|
76
|
+
channel,
|
|
77
|
+
language,
|
|
78
|
+
currencyCode,
|
|
79
|
+
timeout,
|
|
80
|
+
effectiveBrowserId,
|
|
81
|
+
stableResolveDatasource
|
|
82
|
+
]
|
|
83
|
+
);
|
|
84
|
+
return /* @__PURE__ */ jsx(PersonalizeContext.Provider, { value, children });
|
|
85
|
+
}
|
|
86
|
+
function usePersonalizeContext() {
|
|
87
|
+
return useContext(PersonalizeContext);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/personalizeClient.ts
|
|
91
|
+
var DEFAULT_API_BASE = "https://api.boxever.com";
|
|
92
|
+
async function callPersonalize(options) {
|
|
93
|
+
const { config, context, apiBase = DEFAULT_API_BASE, componentRef, pageRoute } = options;
|
|
94
|
+
const body = {
|
|
95
|
+
clientKey: context.clientKey,
|
|
96
|
+
channel: context.channel,
|
|
97
|
+
language: context.language,
|
|
98
|
+
currencyCode: context.currencyCode,
|
|
99
|
+
pointOfSale: context.pointOfSale,
|
|
100
|
+
browserId: context.browserId,
|
|
101
|
+
friendlyId: config.friendlyId
|
|
102
|
+
};
|
|
103
|
+
if (componentRef || pageRoute) {
|
|
104
|
+
body.params = {};
|
|
105
|
+
if (componentRef) body.params.componentRef = componentRef;
|
|
106
|
+
if (pageRoute) body.params.pageRoute = pageRoute;
|
|
107
|
+
}
|
|
108
|
+
const controller = new AbortController();
|
|
109
|
+
const timeoutId = setTimeout(() => controller.abort(), context.timeout);
|
|
110
|
+
try {
|
|
111
|
+
const res = await fetch(`${apiBase.replace(/\/$/, "")}/v2/callFlows`, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: { "Content-Type": "application/json" },
|
|
114
|
+
body: JSON.stringify(body),
|
|
115
|
+
signal: controller.signal
|
|
116
|
+
});
|
|
117
|
+
clearTimeout(timeoutId);
|
|
118
|
+
if (!res.ok) return null;
|
|
119
|
+
const data = await res.json();
|
|
120
|
+
if (!data || typeof data !== "object") return null;
|
|
121
|
+
const contentKey = data.contentKey;
|
|
122
|
+
if (typeof contentKey !== "string" || contentKey.trim() === "") return null;
|
|
123
|
+
return contentKey;
|
|
124
|
+
} catch {
|
|
125
|
+
clearTimeout(timeoutId);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/contentResolver.ts
|
|
131
|
+
async function resolveContent(options) {
|
|
132
|
+
const { contentKey, config, resolveDatasource } = options;
|
|
133
|
+
const effectiveKey = contentKey && contentKey in config.contentMap ? contentKey : config.defaultKey;
|
|
134
|
+
const datasourceId = config.contentMap[effectiveKey];
|
|
135
|
+
if (!datasourceId || typeof datasourceId !== "string") return null;
|
|
136
|
+
try {
|
|
137
|
+
const fields = await resolveDatasource(datasourceId);
|
|
138
|
+
if (!fields || typeof fields !== "object") return null;
|
|
139
|
+
return { datasourceId, fields };
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/withPersonalizeConnect.tsx
|
|
146
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
147
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
148
|
+
var DEFAULT_GET_CONFIG = (props) => props.rendering?.personalizeConnect;
|
|
149
|
+
function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG) {
|
|
150
|
+
function PersonalizeConnectWrapper(props) {
|
|
151
|
+
const context = usePersonalizeContext();
|
|
152
|
+
const config = getConfig(props);
|
|
153
|
+
const [resolvedFields, setResolvedFields] = useState2(null);
|
|
154
|
+
const mountedRef = useRef(true);
|
|
155
|
+
const runPersonalization = useCallback2(async () => {
|
|
156
|
+
if (!config || !context) return;
|
|
157
|
+
const contentKey = await callPersonalize({ config, context });
|
|
158
|
+
if (!mountedRef.current) return;
|
|
159
|
+
const resolved = await resolveContent({
|
|
160
|
+
contentKey,
|
|
161
|
+
config,
|
|
162
|
+
resolveDatasource: context.resolveDatasource
|
|
163
|
+
});
|
|
164
|
+
if (!mountedRef.current) return;
|
|
165
|
+
if (resolved) {
|
|
166
|
+
setResolvedFields(resolved.fields);
|
|
167
|
+
}
|
|
168
|
+
}, [config, context]);
|
|
169
|
+
useEffect2(() => {
|
|
170
|
+
mountedRef.current = true;
|
|
171
|
+
if (config && context) {
|
|
172
|
+
runPersonalization();
|
|
173
|
+
}
|
|
174
|
+
return () => {
|
|
175
|
+
mountedRef.current = false;
|
|
176
|
+
};
|
|
177
|
+
}, [config?.friendlyId, context?.clientKey, runPersonalization]);
|
|
178
|
+
if (!config || !context) {
|
|
179
|
+
return /* @__PURE__ */ jsx2(WrappedComponent, { ...props });
|
|
180
|
+
}
|
|
181
|
+
const mergedProps = {
|
|
182
|
+
...props,
|
|
183
|
+
...resolvedFields ?? {}
|
|
184
|
+
};
|
|
185
|
+
return /* @__PURE__ */ jsx2(WrappedComponent, { ...mergedProps });
|
|
186
|
+
}
|
|
187
|
+
PersonalizeConnectWrapper.displayName = `WithPersonalizeConnect(${WrappedComponent.displayName ?? WrappedComponent.name ?? "Component"})`;
|
|
188
|
+
return PersonalizeConnectWrapper;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/usePersonalizeExperience.ts
|
|
192
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
|
|
193
|
+
function usePersonalizeExperience(config) {
|
|
194
|
+
const context = usePersonalizeContext();
|
|
195
|
+
const [contentKey, setContentKey] = useState3(null);
|
|
196
|
+
const [resolvedFields, setResolvedFields] = useState3(null);
|
|
197
|
+
const [isLoading, setIsLoading] = useState3(true);
|
|
198
|
+
const [error, setError] = useState3(null);
|
|
199
|
+
const mountedRef = useRef2(true);
|
|
200
|
+
const runPersonalization = useCallback3(async () => {
|
|
201
|
+
if (!config || !context) {
|
|
202
|
+
setIsLoading(false);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
setError(null);
|
|
206
|
+
setContentKey(null);
|
|
207
|
+
setResolvedFields(null);
|
|
208
|
+
setIsLoading(true);
|
|
209
|
+
try {
|
|
210
|
+
const key = await callPersonalize({ config, context });
|
|
211
|
+
if (!mountedRef.current) return;
|
|
212
|
+
setContentKey(key);
|
|
213
|
+
const resolved = await resolveContent({
|
|
214
|
+
contentKey: key,
|
|
215
|
+
config,
|
|
216
|
+
resolveDatasource: context.resolveDatasource
|
|
217
|
+
});
|
|
218
|
+
if (!mountedRef.current) return;
|
|
219
|
+
if (resolved) setResolvedFields(resolved.fields);
|
|
220
|
+
} catch (e) {
|
|
221
|
+
if (mountedRef.current) {
|
|
222
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
223
|
+
}
|
|
224
|
+
} finally {
|
|
225
|
+
if (mountedRef.current) setIsLoading(false);
|
|
226
|
+
}
|
|
227
|
+
}, [config, context]);
|
|
228
|
+
useEffect3(() => {
|
|
229
|
+
mountedRef.current = true;
|
|
230
|
+
runPersonalization();
|
|
231
|
+
return () => {
|
|
232
|
+
mountedRef.current = false;
|
|
233
|
+
};
|
|
234
|
+
}, [runPersonalization]);
|
|
235
|
+
return {
|
|
236
|
+
contentKey,
|
|
237
|
+
resolvedFields,
|
|
238
|
+
isLoading,
|
|
239
|
+
error
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
export {
|
|
243
|
+
PersonalizeProvider,
|
|
244
|
+
callPersonalize,
|
|
245
|
+
getBrowserId,
|
|
246
|
+
resolveContent,
|
|
247
|
+
usePersonalizeContext,
|
|
248
|
+
usePersonalizeExperience,
|
|
249
|
+
withPersonalizeConnect
|
|
250
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "personalize-connect-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Runtime SDK for Personalize Connect - resolves active experience outcomes and datasources in your rendering host",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"sitecore",
|
|
7
|
+
"personalize",
|
|
8
|
+
"personalization",
|
|
9
|
+
"sitecore-personalize",
|
|
10
|
+
"rendering",
|
|
11
|
+
"marketplace"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/your-org/2026-Team-Solo.git",
|
|
17
|
+
"directory": "packages/sdk"
|
|
18
|
+
},
|
|
19
|
+
"main": "dist/index.js",
|
|
20
|
+
"module": "dist/index.mjs",
|
|
21
|
+
"types": "dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.mjs",
|
|
26
|
+
"require": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": ">=18.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^24.2.0",
|
|
38
|
+
"@types/react": "^18.0.0",
|
|
39
|
+
"react": "^18.0.0",
|
|
40
|
+
"tsup": "^8.3.5",
|
|
41
|
+
"typescript": "^5.9.2"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
45
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch"
|
|
46
|
+
}
|
|
47
|
+
}
|