@builder.io/sdk-svelte 5.2.12 → 5.2.14
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/lib/browser/blocks/image/image.svelte +0 -1
- package/lib/browser/blocks/personalization-container/helpers/inlined-fns.d.ts +8 -2
- package/lib/browser/blocks/personalization-container/helpers/inlined-fns.js +22 -2
- package/lib/browser/blocks/personalization-container/helpers.js +5 -1
- package/lib/browser/constants/sdk-version.d.ts +1 -1
- package/lib/browser/constants/sdk-version.js +1 -1
- package/lib/browser/helpers/studio-user-attributes.d.ts +6 -0
- package/lib/browser/helpers/studio-user-attributes.js +31 -0
- package/lib/browser/helpers/user-attributes.d.ts +6 -2
- package/lib/browser/helpers/user-attributes.js +20 -6
- package/lib/edge/blocks/image/image.svelte +0 -1
- package/lib/edge/blocks/personalization-container/helpers/inlined-fns.d.ts +8 -2
- package/lib/edge/blocks/personalization-container/helpers/inlined-fns.js +22 -2
- package/lib/edge/blocks/personalization-container/helpers.js +5 -1
- package/lib/edge/constants/sdk-version.d.ts +1 -1
- package/lib/edge/constants/sdk-version.js +1 -1
- package/lib/edge/helpers/studio-user-attributes.d.ts +6 -0
- package/lib/edge/helpers/studio-user-attributes.js +31 -0
- package/lib/edge/helpers/user-attributes.d.ts +6 -2
- package/lib/edge/helpers/user-attributes.js +20 -6
- package/lib/node/blocks/image/image.svelte +0 -1
- package/lib/node/blocks/personalization-container/helpers/inlined-fns.d.ts +8 -2
- package/lib/node/blocks/personalization-container/helpers/inlined-fns.js +22 -2
- package/lib/node/blocks/personalization-container/helpers.js +5 -1
- package/lib/node/constants/sdk-version.d.ts +1 -1
- package/lib/node/constants/sdk-version.js +1 -1
- package/lib/node/helpers/studio-user-attributes.d.ts +6 -0
- package/lib/node/helpers/studio-user-attributes.js +31 -0
- package/lib/node/helpers/user-attributes.d.ts +6 -2
- package/lib/node/helpers/user-attributes.js +20 -6
- package/package.json +1 -1
- package/src/blocks/image/image.svelte +0 -1
- package/src/blocks/personalization-container/helpers/inlined-fns.ts +23 -2
- package/src/blocks/personalization-container/helpers.ts +5 -1
- package/src/constants/sdk-version.ts +1 -1
- package/src/helpers/studio-user-attributes.ts +31 -0
- package/src/helpers/user-attributes.ts +20 -6
|
@@ -88,7 +88,6 @@ onMount(() => { });
|
|
|
88
88
|
fetchpriority={highPriority ? "high" : "auto"}
|
|
89
89
|
alt={altText || ""}
|
|
90
90
|
{title}
|
|
91
|
-
role={altText ? undefined : "presentation"}
|
|
92
91
|
class={"builder-image" + (className ? " " + className : "") + " img"}
|
|
93
92
|
src={image}
|
|
94
93
|
srcset={srcSetToUse()}
|
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { Query, UserAttributes } from '../helpers';
|
|
6
6
|
import { type PersonalizationContainerProps } from '../personalization-container.types';
|
|
7
|
+
/**
|
|
8
|
+
* Mirrors helpers/studio-user-attributes.ts; stringification rules out sharing the code.
|
|
9
|
+
* Anything referenced from module scope becomes an undefined global at runtime.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getStudioUserAttributes(): Record<string, unknown>;
|
|
7
12
|
export declare function filterWithCustomTargeting(userAttributes: UserAttributes, query: Query[], startDate?: string, endDate?: string): boolean;
|
|
8
13
|
export declare function updateVisibilityStylesScript(variants: PersonalizationContainerProps['variants'], blockId: string, isHydrationTarget: boolean, locale?: string): void;
|
|
9
|
-
export declare const
|
|
14
|
+
export declare const STUDIO_USER_ATTRIBUTES_SCRIPT = "function getStudioUserAttributes() {\n const params = new URLSearchParams(window.location.search);\n if (params.get('builder.preview') !== 'BUILDER_STUDIO') {\n return {};\n }\n const prefix = 'builder.userAttributes.';\n const attributes = {};\n params.forEach(function (value, key) {\n if (key.indexOf(prefix) === 0) {\n attributes[key.slice(prefix.length)] = value === 'true' ? true : value === 'false' ? false : value;\n }\n });\n return attributes;\n}";
|
|
15
|
+
export declare const PERSONALIZATION_SCRIPT = "function getPersonalizedVariant(variants, blockId, isHydrationTarget, locale) {\n if (!navigator.cookieEnabled) {\n return;\n }\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n Object.assign(attributes, window.builderIoStudioUserAttributes());\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n const parentDiv = document.currentScript?.parentElement;\n const variantId = parentDiv?.getAttribute('data-variant-id');\n const isDefaultVariant = variantId === `${blockId}-default`;\n const isWinningVariant = winningVariantIndex !== -1 && variantId === `${blockId}-${winningVariantIndex}` || winningVariantIndex === -1 && isDefaultVariant;\n if (isWinningVariant && !isDefaultVariant) {\n parentDiv?.removeAttribute('hidden');\n parentDiv?.removeAttribute('aria-hidden');\n } else if (!isWinningVariant && isDefaultVariant) {\n parentDiv?.setAttribute('hidden', 'true');\n parentDiv?.setAttribute('aria-hidden', 'true');\n }\n if (isHydrationTarget) {\n if (!isWinningVariant) {\n const itsStyleEl = parentDiv?.previousElementSibling;\n if (itsStyleEl) {\n itsStyleEl.remove();\n }\n parentDiv?.remove();\n }\n const thisScript = document.currentScript;\n if (thisScript) {\n thisScript.remove();\n }\n }\n}";
|
|
10
16
|
export declare const FILTER_WITH_CUSTOM_TARGETING_SCRIPT = "function filterWithCustomTargeting(userAttributes, query, startDate, endDate) {\n function isString(val) {\n return typeof val === 'string';\n }\n function isNumber(val) {\n return typeof val === 'number';\n }\n function objectMatchesQuery(userattr, query) {\n const result = (() => {\n const property = query.property;\n const operator = query.operator;\n let testValue = query.value;\n if (query && query.property === 'urlPath' && query.value && typeof query.value === 'string' && query.value !== '/' && query.value.endsWith('/')) {\n testValue = query.value.slice(0, -1);\n }\n if (!(property && operator)) {\n return true;\n }\n if (Array.isArray(testValue)) {\n if (operator === 'isNot') {\n return testValue.every(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n return !!testValue.find(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n const value = userattr[property];\n if (Array.isArray(value)) {\n return value.includes(testValue);\n }\n switch (operator) {\n case 'is':\n return value === testValue;\n case 'isNot':\n return value !== testValue;\n case 'contains':\n return (isString(value) || Array.isArray(value)) && value.includes(String(testValue));\n case 'startsWith':\n return isString(value) && value.startsWith(String(testValue));\n case 'endsWith':\n return isString(value) && value.endsWith(String(testValue));\n case 'greaterThan':\n return isNumber(value) && isNumber(testValue) && value > testValue;\n case 'lessThan':\n return isNumber(value) && isNumber(testValue) && value < testValue;\n case 'greaterThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value >= testValue;\n case 'lessThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value <= testValue;\n default:\n return false;\n }\n })();\n return result;\n }\n const item = {\n query,\n startDate,\n endDate\n };\n const now = userAttributes.date && new Date(userAttributes.date) || new Date();\n if (item.startDate && new Date(item.startDate) > now) {\n return false;\n } else if (item.endDate && new Date(item.endDate) < now) {\n return false;\n }\n if (!item.query || !item.query.length) {\n return true;\n }\n return item.query.every(filter => {\n return objectMatchesQuery(userAttributes, filter);\n });\n}";
|
|
11
|
-
export declare const UPDATE_VISIBILITY_STYLES_SCRIPT = "function updateVisibilityStylesScript(variants, blockId, isHydrationTarget, locale) {\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const visibilityStylesEl = document.currentScript?.previousElementSibling;\n if (!visibilityStylesEl) {\n return;\n }\n if (isHydrationTarget) {\n visibilityStylesEl.remove();\n const currentScript = document.currentScript;\n if (currentScript) {\n currentScript.remove();\n }\n } else {\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n if (winningVariantIndex !== -1) {\n let newStyleStr = variants?.map((_, index) => {\n if (index === winningVariantIndex) return '';\n return `div[data-variant-id=\"${blockId}-${index}\"] { display: none !important; } `;\n }).join('') || '';\n newStyleStr += `div[data-variant-id=\"${blockId}-default\"] { display: none !important; } `;\n visibilityStylesEl.innerHTML = newStyleStr;\n }\n }\n}";
|
|
17
|
+
export declare const UPDATE_VISIBILITY_STYLES_SCRIPT = "function updateVisibilityStylesScript(variants, blockId, isHydrationTarget, locale) {\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const visibilityStylesEl = document.currentScript?.previousElementSibling;\n if (!visibilityStylesEl) {\n return;\n }\n if (isHydrationTarget) {\n visibilityStylesEl.remove();\n const currentScript = document.currentScript;\n if (currentScript) {\n currentScript.remove();\n }\n } else {\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n Object.assign(attributes, window.builderIoStudioUserAttributes());\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n if (winningVariantIndex !== -1) {\n let newStyleStr = variants?.map((_, index) => {\n if (index === winningVariantIndex) return '';\n return `div[data-variant-id=\"${blockId}-${index}\"] { display: none !important; } `;\n }).join('') || '';\n newStyleStr += `div[data-variant-id=\"${blockId}-default\"] { display: none !important; } `;\n visibilityStylesEl.innerHTML = newStyleStr;\n }\n }\n}";
|
|
@@ -3,6 +3,24 @@
|
|
|
3
3
|
* They cannot import anything.
|
|
4
4
|
*/
|
|
5
5
|
import '../personalization-container.types';
|
|
6
|
+
/**
|
|
7
|
+
* Mirrors helpers/studio-user-attributes.ts; stringification rules out sharing the code.
|
|
8
|
+
* Anything referenced from module scope becomes an undefined global at runtime.
|
|
9
|
+
*/
|
|
10
|
+
export function getStudioUserAttributes() {
|
|
11
|
+
const params = new URLSearchParams(window.location.search);
|
|
12
|
+
if (params.get('builder.preview') !== 'BUILDER_STUDIO') {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
const prefix = 'builder.userAttributes.';
|
|
16
|
+
const attributes = {};
|
|
17
|
+
params.forEach(function (value, key) {
|
|
18
|
+
if (key.indexOf(prefix) === 0) {
|
|
19
|
+
attributes[key.slice(prefix.length)] = value === 'true' ? true : value === 'false' ? false : value;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return attributes;
|
|
23
|
+
}
|
|
6
24
|
export function filterWithCustomTargeting(userAttributes, query, startDate, endDate) {
|
|
7
25
|
function isString(val) {
|
|
8
26
|
return typeof val === 'string';
|
|
@@ -112,6 +130,7 @@ export function updateVisibilityStylesScript(variants, blockId, isHydrationTarge
|
|
|
112
130
|
if (locale) {
|
|
113
131
|
attributes.locale = locale;
|
|
114
132
|
}
|
|
133
|
+
Object.assign(attributes, window.builderIoStudioUserAttributes());
|
|
115
134
|
const winningVariantIndex = variants?.findIndex(function (variant) {
|
|
116
135
|
return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);
|
|
117
136
|
});
|
|
@@ -126,6 +145,7 @@ export function updateVisibilityStylesScript(variants, blockId, isHydrationTarge
|
|
|
126
145
|
}
|
|
127
146
|
}
|
|
128
147
|
}
|
|
129
|
-
export const
|
|
148
|
+
export const STUDIO_USER_ATTRIBUTES_SCRIPT = "function getStudioUserAttributes() {\n const params = new URLSearchParams(window.location.search);\n if (params.get('builder.preview') !== 'BUILDER_STUDIO') {\n return {};\n }\n const prefix = 'builder.userAttributes.';\n const attributes = {};\n params.forEach(function (value, key) {\n if (key.indexOf(prefix) === 0) {\n attributes[key.slice(prefix.length)] = value === 'true' ? true : value === 'false' ? false : value;\n }\n });\n return attributes;\n}";
|
|
149
|
+
export const PERSONALIZATION_SCRIPT = "function getPersonalizedVariant(variants, blockId, isHydrationTarget, locale) {\n if (!navigator.cookieEnabled) {\n return;\n }\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n Object.assign(attributes, window.builderIoStudioUserAttributes());\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n const parentDiv = document.currentScript?.parentElement;\n const variantId = parentDiv?.getAttribute('data-variant-id');\n const isDefaultVariant = variantId === `${blockId}-default`;\n const isWinningVariant = winningVariantIndex !== -1 && variantId === `${blockId}-${winningVariantIndex}` || winningVariantIndex === -1 && isDefaultVariant;\n if (isWinningVariant && !isDefaultVariant) {\n parentDiv?.removeAttribute('hidden');\n parentDiv?.removeAttribute('aria-hidden');\n } else if (!isWinningVariant && isDefaultVariant) {\n parentDiv?.setAttribute('hidden', 'true');\n parentDiv?.setAttribute('aria-hidden', 'true');\n }\n if (isHydrationTarget) {\n if (!isWinningVariant) {\n const itsStyleEl = parentDiv?.previousElementSibling;\n if (itsStyleEl) {\n itsStyleEl.remove();\n }\n parentDiv?.remove();\n }\n const thisScript = document.currentScript;\n if (thisScript) {\n thisScript.remove();\n }\n }\n}";
|
|
130
150
|
export const FILTER_WITH_CUSTOM_TARGETING_SCRIPT = "function filterWithCustomTargeting(userAttributes, query, startDate, endDate) {\n function isString(val) {\n return typeof val === 'string';\n }\n function isNumber(val) {\n return typeof val === 'number';\n }\n function objectMatchesQuery(userattr, query) {\n const result = (() => {\n const property = query.property;\n const operator = query.operator;\n let testValue = query.value;\n if (query && query.property === 'urlPath' && query.value && typeof query.value === 'string' && query.value !== '/' && query.value.endsWith('/')) {\n testValue = query.value.slice(0, -1);\n }\n if (!(property && operator)) {\n return true;\n }\n if (Array.isArray(testValue)) {\n if (operator === 'isNot') {\n return testValue.every(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n return !!testValue.find(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n const value = userattr[property];\n if (Array.isArray(value)) {\n return value.includes(testValue);\n }\n switch (operator) {\n case 'is':\n return value === testValue;\n case 'isNot':\n return value !== testValue;\n case 'contains':\n return (isString(value) || Array.isArray(value)) && value.includes(String(testValue));\n case 'startsWith':\n return isString(value) && value.startsWith(String(testValue));\n case 'endsWith':\n return isString(value) && value.endsWith(String(testValue));\n case 'greaterThan':\n return isNumber(value) && isNumber(testValue) && value > testValue;\n case 'lessThan':\n return isNumber(value) && isNumber(testValue) && value < testValue;\n case 'greaterThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value >= testValue;\n case 'lessThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value <= testValue;\n default:\n return false;\n }\n })();\n return result;\n }\n const item = {\n query,\n startDate,\n endDate\n };\n const now = userAttributes.date && new Date(userAttributes.date) || new Date();\n if (item.startDate && new Date(item.startDate) > now) {\n return false;\n } else if (item.endDate && new Date(item.endDate) < now) {\n return false;\n }\n if (!item.query || !item.query.length) {\n return true;\n }\n return item.query.every(filter => {\n return objectMatchesQuery(userAttributes, filter);\n });\n}";
|
|
131
|
-
export const UPDATE_VISIBILITY_STYLES_SCRIPT = "function updateVisibilityStylesScript(variants, blockId, isHydrationTarget, locale) {\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const visibilityStylesEl = document.currentScript?.previousElementSibling;\n if (!visibilityStylesEl) {\n return;\n }\n if (isHydrationTarget) {\n visibilityStylesEl.remove();\n const currentScript = document.currentScript;\n if (currentScript) {\n currentScript.remove();\n }\n } else {\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n if (winningVariantIndex !== -1) {\n let newStyleStr = variants?.map((_, index) => {\n if (index === winningVariantIndex) return '';\n return `div[data-variant-id=\"${blockId}-${index}\"] { display: none !important; } `;\n }).join('') || '';\n newStyleStr += `div[data-variant-id=\"${blockId}-default\"] { display: none !important; } `;\n visibilityStylesEl.innerHTML = newStyleStr;\n }\n }\n}";
|
|
151
|
+
export const UPDATE_VISIBILITY_STYLES_SCRIPT = "function updateVisibilityStylesScript(variants, blockId, isHydrationTarget, locale) {\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const visibilityStylesEl = document.currentScript?.previousElementSibling;\n if (!visibilityStylesEl) {\n return;\n }\n if (isHydrationTarget) {\n visibilityStylesEl.remove();\n const currentScript = document.currentScript;\n if (currentScript) {\n currentScript.remove();\n }\n } else {\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n Object.assign(attributes, window.builderIoStudioUserAttributes());\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n if (winningVariantIndex !== -1) {\n let newStyleStr = variants?.map((_, index) => {\n if (index === winningVariantIndex) return '';\n return `div[data-variant-id=\"${blockId}-${index}\"] { display: none !important; } `;\n }).join('') || '';\n newStyleStr += `div[data-variant-id=\"${blockId}-default\"] { display: none !important; } `;\n visibilityStylesEl.innerHTML = newStyleStr;\n }\n }\n}";
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { TARGET } from '../../constants/target';
|
|
2
2
|
import { isBrowser } from '../../functions/is-browser';
|
|
3
3
|
import { isEditing } from '../../functions/is-editing';
|
|
4
|
-
import { FILTER_WITH_CUSTOM_TARGETING_SCRIPT, PERSONALIZATION_SCRIPT, UPDATE_VISIBILITY_STYLES_SCRIPT } from './helpers/inlined-fns';
|
|
4
|
+
import { FILTER_WITH_CUSTOM_TARGETING_SCRIPT, PERSONALIZATION_SCRIPT, STUDIO_USER_ATTRIBUTES_SCRIPT, UPDATE_VISIBILITY_STYLES_SCRIPT } from './helpers/inlined-fns';
|
|
5
5
|
export const DEFAULT_INDEX = 'default';
|
|
6
6
|
const FILTER_WITH_CUSTOM_TARGETING_SCRIPT_FN_NAME = 'filterWithCustomTargeting';
|
|
7
7
|
const BUILDER_IO_PERSONALIZATION_SCRIPT_FN_NAME = 'builderIoPersonalization';
|
|
8
8
|
const UPDATE_VARIANT_VISIBILITY_SCRIPT_FN_NAME = 'updateVisibilityStylesScript';
|
|
9
|
+
const STUDIO_USER_ATTRIBUTES_SCRIPT_FN_NAME = 'builderIoStudioUserAttributes';
|
|
9
10
|
const PERSONALIZATION_CONTAINER_COMPONENT_NAME = 'PersonalizationContainer';
|
|
10
11
|
/**
|
|
11
12
|
* SDKs that support Variant Containers
|
|
@@ -94,6 +95,9 @@ export const hasPersonalizationContainer = (content) => containsPersonalizationC
|
|
|
94
95
|
export const getInitPersonalizationVariantsFnsScriptString = () => {
|
|
95
96
|
return `
|
|
96
97
|
(function() {
|
|
98
|
+
if (!window.${STUDIO_USER_ATTRIBUTES_SCRIPT_FN_NAME}) {
|
|
99
|
+
window.${STUDIO_USER_ATTRIBUTES_SCRIPT_FN_NAME} = ${STUDIO_USER_ATTRIBUTES_SCRIPT};
|
|
100
|
+
}
|
|
97
101
|
if (!window.${FILTER_WITH_CUSTOM_TARGETING_SCRIPT_FN_NAME}) {
|
|
98
102
|
window.${FILTER_WITH_CUSTOM_TARGETING_SCRIPT_FN_NAME} = ${FILTER_WITH_CUSTOM_TARGETING_SCRIPT};
|
|
99
103
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "5.2.
|
|
1
|
+
export declare const SDK_VERSION = "5.2.14";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "5.2.
|
|
1
|
+
export const SDK_VERSION = "5.2.14";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { UserAttributes } from './user-attributes';
|
|
2
|
+
/**
|
|
3
|
+
* Studio passes its targeting overrides as query params because it cannot write the
|
|
4
|
+
* builder.userAttributes cookie on the previewed site's origin.
|
|
5
|
+
*/
|
|
6
|
+
export declare const getStudioUserAttributes: () => UserAttributes;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isBrowser } from '../functions/is-browser';
|
|
2
|
+
const STUDIO_PREVIEW_PARAM = 'builder.preview';
|
|
3
|
+
const STUDIO_PREVIEW_VALUE = 'BUILDER_STUDIO';
|
|
4
|
+
const USER_ATTRIBUTE_PARAM_PREFIX = 'builder.userAttributes.';
|
|
5
|
+
const parseStudioValue = (value) => {
|
|
6
|
+
if (value === 'true')
|
|
7
|
+
return true;
|
|
8
|
+
if (value === 'false')
|
|
9
|
+
return false;
|
|
10
|
+
return value;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Studio passes its targeting overrides as query params because it cannot write the
|
|
14
|
+
* builder.userAttributes cookie on the previewed site's origin.
|
|
15
|
+
*/
|
|
16
|
+
export const getStudioUserAttributes = () => {
|
|
17
|
+
if (!isBrowser()) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const params = new URLSearchParams(window.location.search);
|
|
21
|
+
if (params.get(STUDIO_PREVIEW_PARAM) !== STUDIO_PREVIEW_VALUE) {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
const attributes = {};
|
|
25
|
+
params.forEach((value, key) => {
|
|
26
|
+
if (key.startsWith(USER_ATTRIBUTE_PARAM_PREFIX)) {
|
|
27
|
+
attributes[key.slice(USER_ATTRIBUTE_PARAM_PREFIX.length)] = parseStudioValue(value);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return attributes;
|
|
31
|
+
};
|
|
@@ -4,7 +4,9 @@ export interface UserAttributes {
|
|
|
4
4
|
export declare const USER_ATTRIBUTES_COOKIE_NAME = "builder.userAttributes";
|
|
5
5
|
export declare function createUserAttributesService(): {
|
|
6
6
|
setUserAttributes(newAttrs: UserAttributes): void;
|
|
7
|
-
getUserAttributes():
|
|
7
|
+
getUserAttributes(): {
|
|
8
|
+
[x: string]: any;
|
|
9
|
+
};
|
|
8
10
|
subscribeOnUserAttributesChange(callback: (attrs: UserAttributes) => void, { fireImmediately }?: {
|
|
9
11
|
fireImmediately?: boolean | undefined;
|
|
10
12
|
}): () => void;
|
|
@@ -12,7 +14,9 @@ export declare function createUserAttributesService(): {
|
|
|
12
14
|
};
|
|
13
15
|
export declare const userAttributesService: {
|
|
14
16
|
setUserAttributes(newAttrs: UserAttributes): void;
|
|
15
|
-
getUserAttributes():
|
|
17
|
+
getUserAttributes(): {
|
|
18
|
+
[x: string]: any;
|
|
19
|
+
};
|
|
16
20
|
subscribeOnUserAttributesChange(callback: (attrs: UserAttributes) => void, { fireImmediately }?: {
|
|
17
21
|
fireImmediately?: boolean | undefined;
|
|
18
22
|
}): () => void;
|
|
@@ -2,17 +2,25 @@ import { TARGET } from '../constants/target';
|
|
|
2
2
|
import { isBrowser } from '../functions/is-browser';
|
|
3
3
|
import { getCookieSync, setCookie } from './cookie';
|
|
4
4
|
import { noSerializeWrapper } from './no-serialize-wrapper';
|
|
5
|
+
import { getStudioUserAttributes } from './studio-user-attributes';
|
|
5
6
|
export const USER_ATTRIBUTES_COOKIE_NAME = 'builder.userAttributes';
|
|
6
7
|
export function createUserAttributesService() {
|
|
7
8
|
let canTrack = true;
|
|
8
9
|
const subscribers = new Set();
|
|
10
|
+
const getCookieUserAttributes = () => {
|
|
11
|
+
const cookie = getCookieSync({
|
|
12
|
+
name: USER_ATTRIBUTES_COOKIE_NAME,
|
|
13
|
+
canTrack
|
|
14
|
+
});
|
|
15
|
+
return cookie ? JSON.parse(cookie) : {};
|
|
16
|
+
};
|
|
9
17
|
return {
|
|
10
18
|
setUserAttributes(newAttrs) {
|
|
11
19
|
if (!isBrowser()) {
|
|
12
20
|
return;
|
|
13
21
|
}
|
|
14
22
|
const userAttributes = {
|
|
15
|
-
...
|
|
23
|
+
...getCookieUserAttributes(),
|
|
16
24
|
...newAttrs
|
|
17
25
|
};
|
|
18
26
|
setCookie({
|
|
@@ -20,16 +28,22 @@ export function createUserAttributesService() {
|
|
|
20
28
|
value: JSON.stringify(userAttributes),
|
|
21
29
|
canTrack
|
|
22
30
|
});
|
|
23
|
-
|
|
31
|
+
// Kept out of the cookie above so previewing never persists into a real visitor's
|
|
32
|
+
// session, but re-applied here so the site's own attributes cannot clobber it.
|
|
33
|
+
const studioAttributes = getStudioUserAttributes();
|
|
34
|
+
subscribers.forEach(callback => callback({
|
|
35
|
+
...userAttributes,
|
|
36
|
+
...studioAttributes
|
|
37
|
+
}));
|
|
24
38
|
},
|
|
25
39
|
getUserAttributes() {
|
|
26
40
|
if (!isBrowser()) {
|
|
27
41
|
return {};
|
|
28
42
|
}
|
|
29
|
-
return
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
43
|
+
return {
|
|
44
|
+
...getCookieUserAttributes(),
|
|
45
|
+
...getStudioUserAttributes()
|
|
46
|
+
};
|
|
33
47
|
},
|
|
34
48
|
subscribeOnUserAttributesChange(callback, { fireImmediately } = {}) {
|
|
35
49
|
subscribers.add(callback);
|
|
@@ -88,7 +88,6 @@ onMount(() => { });
|
|
|
88
88
|
fetchpriority={highPriority ? "high" : "auto"}
|
|
89
89
|
alt={altText || ""}
|
|
90
90
|
{title}
|
|
91
|
-
role={altText ? undefined : "presentation"}
|
|
92
91
|
class={"builder-image" + (className ? " " + className : "") + " img"}
|
|
93
92
|
src={image}
|
|
94
93
|
srcset={srcSetToUse()}
|
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { Query, UserAttributes } from '../helpers';
|
|
6
6
|
import { type PersonalizationContainerProps } from '../personalization-container.types';
|
|
7
|
+
/**
|
|
8
|
+
* Mirrors helpers/studio-user-attributes.ts; stringification rules out sharing the code.
|
|
9
|
+
* Anything referenced from module scope becomes an undefined global at runtime.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getStudioUserAttributes(): Record<string, unknown>;
|
|
7
12
|
export declare function filterWithCustomTargeting(userAttributes: UserAttributes, query: Query[], startDate?: string, endDate?: string): boolean;
|
|
8
13
|
export declare function updateVisibilityStylesScript(variants: PersonalizationContainerProps['variants'], blockId: string, isHydrationTarget: boolean, locale?: string): void;
|
|
9
|
-
export declare const
|
|
14
|
+
export declare const STUDIO_USER_ATTRIBUTES_SCRIPT = "function getStudioUserAttributes() {\n const params = new URLSearchParams(window.location.search);\n if (params.get('builder.preview') !== 'BUILDER_STUDIO') {\n return {};\n }\n const prefix = 'builder.userAttributes.';\n const attributes = {};\n params.forEach(function (value, key) {\n if (key.indexOf(prefix) === 0) {\n attributes[key.slice(prefix.length)] = value === 'true' ? true : value === 'false' ? false : value;\n }\n });\n return attributes;\n}";
|
|
15
|
+
export declare const PERSONALIZATION_SCRIPT = "function getPersonalizedVariant(variants, blockId, isHydrationTarget, locale) {\n if (!navigator.cookieEnabled) {\n return;\n }\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n Object.assign(attributes, window.builderIoStudioUserAttributes());\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n const parentDiv = document.currentScript?.parentElement;\n const variantId = parentDiv?.getAttribute('data-variant-id');\n const isDefaultVariant = variantId === `${blockId}-default`;\n const isWinningVariant = winningVariantIndex !== -1 && variantId === `${blockId}-${winningVariantIndex}` || winningVariantIndex === -1 && isDefaultVariant;\n if (isWinningVariant && !isDefaultVariant) {\n parentDiv?.removeAttribute('hidden');\n parentDiv?.removeAttribute('aria-hidden');\n } else if (!isWinningVariant && isDefaultVariant) {\n parentDiv?.setAttribute('hidden', 'true');\n parentDiv?.setAttribute('aria-hidden', 'true');\n }\n if (isHydrationTarget) {\n if (!isWinningVariant) {\n const itsStyleEl = parentDiv?.previousElementSibling;\n if (itsStyleEl) {\n itsStyleEl.remove();\n }\n parentDiv?.remove();\n }\n const thisScript = document.currentScript;\n if (thisScript) {\n thisScript.remove();\n }\n }\n}";
|
|
10
16
|
export declare const FILTER_WITH_CUSTOM_TARGETING_SCRIPT = "function filterWithCustomTargeting(userAttributes, query, startDate, endDate) {\n function isString(val) {\n return typeof val === 'string';\n }\n function isNumber(val) {\n return typeof val === 'number';\n }\n function objectMatchesQuery(userattr, query) {\n const result = (() => {\n const property = query.property;\n const operator = query.operator;\n let testValue = query.value;\n if (query && query.property === 'urlPath' && query.value && typeof query.value === 'string' && query.value !== '/' && query.value.endsWith('/')) {\n testValue = query.value.slice(0, -1);\n }\n if (!(property && operator)) {\n return true;\n }\n if (Array.isArray(testValue)) {\n if (operator === 'isNot') {\n return testValue.every(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n return !!testValue.find(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n const value = userattr[property];\n if (Array.isArray(value)) {\n return value.includes(testValue);\n }\n switch (operator) {\n case 'is':\n return value === testValue;\n case 'isNot':\n return value !== testValue;\n case 'contains':\n return (isString(value) || Array.isArray(value)) && value.includes(String(testValue));\n case 'startsWith':\n return isString(value) && value.startsWith(String(testValue));\n case 'endsWith':\n return isString(value) && value.endsWith(String(testValue));\n case 'greaterThan':\n return isNumber(value) && isNumber(testValue) && value > testValue;\n case 'lessThan':\n return isNumber(value) && isNumber(testValue) && value < testValue;\n case 'greaterThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value >= testValue;\n case 'lessThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value <= testValue;\n default:\n return false;\n }\n })();\n return result;\n }\n const item = {\n query,\n startDate,\n endDate\n };\n const now = userAttributes.date && new Date(userAttributes.date) || new Date();\n if (item.startDate && new Date(item.startDate) > now) {\n return false;\n } else if (item.endDate && new Date(item.endDate) < now) {\n return false;\n }\n if (!item.query || !item.query.length) {\n return true;\n }\n return item.query.every(filter => {\n return objectMatchesQuery(userAttributes, filter);\n });\n}";
|
|
11
|
-
export declare const UPDATE_VISIBILITY_STYLES_SCRIPT = "function updateVisibilityStylesScript(variants, blockId, isHydrationTarget, locale) {\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const visibilityStylesEl = document.currentScript?.previousElementSibling;\n if (!visibilityStylesEl) {\n return;\n }\n if (isHydrationTarget) {\n visibilityStylesEl.remove();\n const currentScript = document.currentScript;\n if (currentScript) {\n currentScript.remove();\n }\n } else {\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n if (winningVariantIndex !== -1) {\n let newStyleStr = variants?.map((_, index) => {\n if (index === winningVariantIndex) return '';\n return `div[data-variant-id=\"${blockId}-${index}\"] { display: none !important; } `;\n }).join('') || '';\n newStyleStr += `div[data-variant-id=\"${blockId}-default\"] { display: none !important; } `;\n visibilityStylesEl.innerHTML = newStyleStr;\n }\n }\n}";
|
|
17
|
+
export declare const UPDATE_VISIBILITY_STYLES_SCRIPT = "function updateVisibilityStylesScript(variants, blockId, isHydrationTarget, locale) {\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const visibilityStylesEl = document.currentScript?.previousElementSibling;\n if (!visibilityStylesEl) {\n return;\n }\n if (isHydrationTarget) {\n visibilityStylesEl.remove();\n const currentScript = document.currentScript;\n if (currentScript) {\n currentScript.remove();\n }\n } else {\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n Object.assign(attributes, window.builderIoStudioUserAttributes());\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n if (winningVariantIndex !== -1) {\n let newStyleStr = variants?.map((_, index) => {\n if (index === winningVariantIndex) return '';\n return `div[data-variant-id=\"${blockId}-${index}\"] { display: none !important; } `;\n }).join('') || '';\n newStyleStr += `div[data-variant-id=\"${blockId}-default\"] { display: none !important; } `;\n visibilityStylesEl.innerHTML = newStyleStr;\n }\n }\n}";
|
|
@@ -3,6 +3,24 @@
|
|
|
3
3
|
* They cannot import anything.
|
|
4
4
|
*/
|
|
5
5
|
import '../personalization-container.types';
|
|
6
|
+
/**
|
|
7
|
+
* Mirrors helpers/studio-user-attributes.ts; stringification rules out sharing the code.
|
|
8
|
+
* Anything referenced from module scope becomes an undefined global at runtime.
|
|
9
|
+
*/
|
|
10
|
+
export function getStudioUserAttributes() {
|
|
11
|
+
const params = new URLSearchParams(window.location.search);
|
|
12
|
+
if (params.get('builder.preview') !== 'BUILDER_STUDIO') {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
const prefix = 'builder.userAttributes.';
|
|
16
|
+
const attributes = {};
|
|
17
|
+
params.forEach(function (value, key) {
|
|
18
|
+
if (key.indexOf(prefix) === 0) {
|
|
19
|
+
attributes[key.slice(prefix.length)] = value === 'true' ? true : value === 'false' ? false : value;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return attributes;
|
|
23
|
+
}
|
|
6
24
|
export function filterWithCustomTargeting(userAttributes, query, startDate, endDate) {
|
|
7
25
|
function isString(val) {
|
|
8
26
|
return typeof val === 'string';
|
|
@@ -112,6 +130,7 @@ export function updateVisibilityStylesScript(variants, blockId, isHydrationTarge
|
|
|
112
130
|
if (locale) {
|
|
113
131
|
attributes.locale = locale;
|
|
114
132
|
}
|
|
133
|
+
Object.assign(attributes, window.builderIoStudioUserAttributes());
|
|
115
134
|
const winningVariantIndex = variants?.findIndex(function (variant) {
|
|
116
135
|
return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);
|
|
117
136
|
});
|
|
@@ -126,6 +145,7 @@ export function updateVisibilityStylesScript(variants, blockId, isHydrationTarge
|
|
|
126
145
|
}
|
|
127
146
|
}
|
|
128
147
|
}
|
|
129
|
-
export const
|
|
148
|
+
export const STUDIO_USER_ATTRIBUTES_SCRIPT = "function getStudioUserAttributes() {\n const params = new URLSearchParams(window.location.search);\n if (params.get('builder.preview') !== 'BUILDER_STUDIO') {\n return {};\n }\n const prefix = 'builder.userAttributes.';\n const attributes = {};\n params.forEach(function (value, key) {\n if (key.indexOf(prefix) === 0) {\n attributes[key.slice(prefix.length)] = value === 'true' ? true : value === 'false' ? false : value;\n }\n });\n return attributes;\n}";
|
|
149
|
+
export const PERSONALIZATION_SCRIPT = "function getPersonalizedVariant(variants, blockId, isHydrationTarget, locale) {\n if (!navigator.cookieEnabled) {\n return;\n }\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n Object.assign(attributes, window.builderIoStudioUserAttributes());\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n const parentDiv = document.currentScript?.parentElement;\n const variantId = parentDiv?.getAttribute('data-variant-id');\n const isDefaultVariant = variantId === `${blockId}-default`;\n const isWinningVariant = winningVariantIndex !== -1 && variantId === `${blockId}-${winningVariantIndex}` || winningVariantIndex === -1 && isDefaultVariant;\n if (isWinningVariant && !isDefaultVariant) {\n parentDiv?.removeAttribute('hidden');\n parentDiv?.removeAttribute('aria-hidden');\n } else if (!isWinningVariant && isDefaultVariant) {\n parentDiv?.setAttribute('hidden', 'true');\n parentDiv?.setAttribute('aria-hidden', 'true');\n }\n if (isHydrationTarget) {\n if (!isWinningVariant) {\n const itsStyleEl = parentDiv?.previousElementSibling;\n if (itsStyleEl) {\n itsStyleEl.remove();\n }\n parentDiv?.remove();\n }\n const thisScript = document.currentScript;\n if (thisScript) {\n thisScript.remove();\n }\n }\n}";
|
|
130
150
|
export const FILTER_WITH_CUSTOM_TARGETING_SCRIPT = "function filterWithCustomTargeting(userAttributes, query, startDate, endDate) {\n function isString(val) {\n return typeof val === 'string';\n }\n function isNumber(val) {\n return typeof val === 'number';\n }\n function objectMatchesQuery(userattr, query) {\n const result = (() => {\n const property = query.property;\n const operator = query.operator;\n let testValue = query.value;\n if (query && query.property === 'urlPath' && query.value && typeof query.value === 'string' && query.value !== '/' && query.value.endsWith('/')) {\n testValue = query.value.slice(0, -1);\n }\n if (!(property && operator)) {\n return true;\n }\n if (Array.isArray(testValue)) {\n if (operator === 'isNot') {\n return testValue.every(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n return !!testValue.find(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n const value = userattr[property];\n if (Array.isArray(value)) {\n return value.includes(testValue);\n }\n switch (operator) {\n case 'is':\n return value === testValue;\n case 'isNot':\n return value !== testValue;\n case 'contains':\n return (isString(value) || Array.isArray(value)) && value.includes(String(testValue));\n case 'startsWith':\n return isString(value) && value.startsWith(String(testValue));\n case 'endsWith':\n return isString(value) && value.endsWith(String(testValue));\n case 'greaterThan':\n return isNumber(value) && isNumber(testValue) && value > testValue;\n case 'lessThan':\n return isNumber(value) && isNumber(testValue) && value < testValue;\n case 'greaterThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value >= testValue;\n case 'lessThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value <= testValue;\n default:\n return false;\n }\n })();\n return result;\n }\n const item = {\n query,\n startDate,\n endDate\n };\n const now = userAttributes.date && new Date(userAttributes.date) || new Date();\n if (item.startDate && new Date(item.startDate) > now) {\n return false;\n } else if (item.endDate && new Date(item.endDate) < now) {\n return false;\n }\n if (!item.query || !item.query.length) {\n return true;\n }\n return item.query.every(filter => {\n return objectMatchesQuery(userAttributes, filter);\n });\n}";
|
|
131
|
-
export const UPDATE_VISIBILITY_STYLES_SCRIPT = "function updateVisibilityStylesScript(variants, blockId, isHydrationTarget, locale) {\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const visibilityStylesEl = document.currentScript?.previousElementSibling;\n if (!visibilityStylesEl) {\n return;\n }\n if (isHydrationTarget) {\n visibilityStylesEl.remove();\n const currentScript = document.currentScript;\n if (currentScript) {\n currentScript.remove();\n }\n } else {\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n if (winningVariantIndex !== -1) {\n let newStyleStr = variants?.map((_, index) => {\n if (index === winningVariantIndex) return '';\n return `div[data-variant-id=\"${blockId}-${index}\"] { display: none !important; } `;\n }).join('') || '';\n newStyleStr += `div[data-variant-id=\"${blockId}-default\"] { display: none !important; } `;\n visibilityStylesEl.innerHTML = newStyleStr;\n }\n }\n}";
|
|
151
|
+
export const UPDATE_VISIBILITY_STYLES_SCRIPT = "function updateVisibilityStylesScript(variants, blockId, isHydrationTarget, locale) {\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const visibilityStylesEl = document.currentScript?.previousElementSibling;\n if (!visibilityStylesEl) {\n return;\n }\n if (isHydrationTarget) {\n visibilityStylesEl.remove();\n const currentScript = document.currentScript;\n if (currentScript) {\n currentScript.remove();\n }\n } else {\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n Object.assign(attributes, window.builderIoStudioUserAttributes());\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n if (winningVariantIndex !== -1) {\n let newStyleStr = variants?.map((_, index) => {\n if (index === winningVariantIndex) return '';\n return `div[data-variant-id=\"${blockId}-${index}\"] { display: none !important; } `;\n }).join('') || '';\n newStyleStr += `div[data-variant-id=\"${blockId}-default\"] { display: none !important; } `;\n visibilityStylesEl.innerHTML = newStyleStr;\n }\n }\n}";
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { TARGET } from '../../constants/target';
|
|
2
2
|
import { isBrowser } from '../../functions/is-browser';
|
|
3
3
|
import { isEditing } from '../../functions/is-editing';
|
|
4
|
-
import { FILTER_WITH_CUSTOM_TARGETING_SCRIPT, PERSONALIZATION_SCRIPT, UPDATE_VISIBILITY_STYLES_SCRIPT } from './helpers/inlined-fns';
|
|
4
|
+
import { FILTER_WITH_CUSTOM_TARGETING_SCRIPT, PERSONALIZATION_SCRIPT, STUDIO_USER_ATTRIBUTES_SCRIPT, UPDATE_VISIBILITY_STYLES_SCRIPT } from './helpers/inlined-fns';
|
|
5
5
|
export const DEFAULT_INDEX = 'default';
|
|
6
6
|
const FILTER_WITH_CUSTOM_TARGETING_SCRIPT_FN_NAME = 'filterWithCustomTargeting';
|
|
7
7
|
const BUILDER_IO_PERSONALIZATION_SCRIPT_FN_NAME = 'builderIoPersonalization';
|
|
8
8
|
const UPDATE_VARIANT_VISIBILITY_SCRIPT_FN_NAME = 'updateVisibilityStylesScript';
|
|
9
|
+
const STUDIO_USER_ATTRIBUTES_SCRIPT_FN_NAME = 'builderIoStudioUserAttributes';
|
|
9
10
|
const PERSONALIZATION_CONTAINER_COMPONENT_NAME = 'PersonalizationContainer';
|
|
10
11
|
/**
|
|
11
12
|
* SDKs that support Variant Containers
|
|
@@ -94,6 +95,9 @@ export const hasPersonalizationContainer = (content) => containsPersonalizationC
|
|
|
94
95
|
export const getInitPersonalizationVariantsFnsScriptString = () => {
|
|
95
96
|
return `
|
|
96
97
|
(function() {
|
|
98
|
+
if (!window.${STUDIO_USER_ATTRIBUTES_SCRIPT_FN_NAME}) {
|
|
99
|
+
window.${STUDIO_USER_ATTRIBUTES_SCRIPT_FN_NAME} = ${STUDIO_USER_ATTRIBUTES_SCRIPT};
|
|
100
|
+
}
|
|
97
101
|
if (!window.${FILTER_WITH_CUSTOM_TARGETING_SCRIPT_FN_NAME}) {
|
|
98
102
|
window.${FILTER_WITH_CUSTOM_TARGETING_SCRIPT_FN_NAME} = ${FILTER_WITH_CUSTOM_TARGETING_SCRIPT};
|
|
99
103
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "5.2.
|
|
1
|
+
export declare const SDK_VERSION = "5.2.14";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "5.2.
|
|
1
|
+
export const SDK_VERSION = "5.2.14";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { UserAttributes } from './user-attributes';
|
|
2
|
+
/**
|
|
3
|
+
* Studio passes its targeting overrides as query params because it cannot write the
|
|
4
|
+
* builder.userAttributes cookie on the previewed site's origin.
|
|
5
|
+
*/
|
|
6
|
+
export declare const getStudioUserAttributes: () => UserAttributes;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isBrowser } from '../functions/is-browser';
|
|
2
|
+
const STUDIO_PREVIEW_PARAM = 'builder.preview';
|
|
3
|
+
const STUDIO_PREVIEW_VALUE = 'BUILDER_STUDIO';
|
|
4
|
+
const USER_ATTRIBUTE_PARAM_PREFIX = 'builder.userAttributes.';
|
|
5
|
+
const parseStudioValue = (value) => {
|
|
6
|
+
if (value === 'true')
|
|
7
|
+
return true;
|
|
8
|
+
if (value === 'false')
|
|
9
|
+
return false;
|
|
10
|
+
return value;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Studio passes its targeting overrides as query params because it cannot write the
|
|
14
|
+
* builder.userAttributes cookie on the previewed site's origin.
|
|
15
|
+
*/
|
|
16
|
+
export const getStudioUserAttributes = () => {
|
|
17
|
+
if (!isBrowser()) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const params = new URLSearchParams(window.location.search);
|
|
21
|
+
if (params.get(STUDIO_PREVIEW_PARAM) !== STUDIO_PREVIEW_VALUE) {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
const attributes = {};
|
|
25
|
+
params.forEach((value, key) => {
|
|
26
|
+
if (key.startsWith(USER_ATTRIBUTE_PARAM_PREFIX)) {
|
|
27
|
+
attributes[key.slice(USER_ATTRIBUTE_PARAM_PREFIX.length)] = parseStudioValue(value);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return attributes;
|
|
31
|
+
};
|
|
@@ -4,7 +4,9 @@ export interface UserAttributes {
|
|
|
4
4
|
export declare const USER_ATTRIBUTES_COOKIE_NAME = "builder.userAttributes";
|
|
5
5
|
export declare function createUserAttributesService(): {
|
|
6
6
|
setUserAttributes(newAttrs: UserAttributes): void;
|
|
7
|
-
getUserAttributes():
|
|
7
|
+
getUserAttributes(): {
|
|
8
|
+
[x: string]: any;
|
|
9
|
+
};
|
|
8
10
|
subscribeOnUserAttributesChange(callback: (attrs: UserAttributes) => void, { fireImmediately }?: {
|
|
9
11
|
fireImmediately?: boolean | undefined;
|
|
10
12
|
}): () => void;
|
|
@@ -12,7 +14,9 @@ export declare function createUserAttributesService(): {
|
|
|
12
14
|
};
|
|
13
15
|
export declare const userAttributesService: {
|
|
14
16
|
setUserAttributes(newAttrs: UserAttributes): void;
|
|
15
|
-
getUserAttributes():
|
|
17
|
+
getUserAttributes(): {
|
|
18
|
+
[x: string]: any;
|
|
19
|
+
};
|
|
16
20
|
subscribeOnUserAttributesChange(callback: (attrs: UserAttributes) => void, { fireImmediately }?: {
|
|
17
21
|
fireImmediately?: boolean | undefined;
|
|
18
22
|
}): () => void;
|
|
@@ -2,17 +2,25 @@ import { TARGET } from '../constants/target';
|
|
|
2
2
|
import { isBrowser } from '../functions/is-browser';
|
|
3
3
|
import { getCookieSync, setCookie } from './cookie';
|
|
4
4
|
import { noSerializeWrapper } from './no-serialize-wrapper';
|
|
5
|
+
import { getStudioUserAttributes } from './studio-user-attributes';
|
|
5
6
|
export const USER_ATTRIBUTES_COOKIE_NAME = 'builder.userAttributes';
|
|
6
7
|
export function createUserAttributesService() {
|
|
7
8
|
let canTrack = true;
|
|
8
9
|
const subscribers = new Set();
|
|
10
|
+
const getCookieUserAttributes = () => {
|
|
11
|
+
const cookie = getCookieSync({
|
|
12
|
+
name: USER_ATTRIBUTES_COOKIE_NAME,
|
|
13
|
+
canTrack
|
|
14
|
+
});
|
|
15
|
+
return cookie ? JSON.parse(cookie) : {};
|
|
16
|
+
};
|
|
9
17
|
return {
|
|
10
18
|
setUserAttributes(newAttrs) {
|
|
11
19
|
if (!isBrowser()) {
|
|
12
20
|
return;
|
|
13
21
|
}
|
|
14
22
|
const userAttributes = {
|
|
15
|
-
...
|
|
23
|
+
...getCookieUserAttributes(),
|
|
16
24
|
...newAttrs
|
|
17
25
|
};
|
|
18
26
|
setCookie({
|
|
@@ -20,16 +28,22 @@ export function createUserAttributesService() {
|
|
|
20
28
|
value: JSON.stringify(userAttributes),
|
|
21
29
|
canTrack
|
|
22
30
|
});
|
|
23
|
-
|
|
31
|
+
// Kept out of the cookie above so previewing never persists into a real visitor's
|
|
32
|
+
// session, but re-applied here so the site's own attributes cannot clobber it.
|
|
33
|
+
const studioAttributes = getStudioUserAttributes();
|
|
34
|
+
subscribers.forEach(callback => callback({
|
|
35
|
+
...userAttributes,
|
|
36
|
+
...studioAttributes
|
|
37
|
+
}));
|
|
24
38
|
},
|
|
25
39
|
getUserAttributes() {
|
|
26
40
|
if (!isBrowser()) {
|
|
27
41
|
return {};
|
|
28
42
|
}
|
|
29
|
-
return
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
43
|
+
return {
|
|
44
|
+
...getCookieUserAttributes(),
|
|
45
|
+
...getStudioUserAttributes()
|
|
46
|
+
};
|
|
33
47
|
},
|
|
34
48
|
subscribeOnUserAttributesChange(callback, { fireImmediately } = {}) {
|
|
35
49
|
subscribers.add(callback);
|
|
@@ -88,7 +88,6 @@ onMount(() => { });
|
|
|
88
88
|
fetchpriority={highPriority ? "high" : "auto"}
|
|
89
89
|
alt={altText || ""}
|
|
90
90
|
{title}
|
|
91
|
-
role={altText ? undefined : "presentation"}
|
|
92
91
|
class={"builder-image" + (className ? " " + className : "") + " img"}
|
|
93
92
|
src={image}
|
|
94
93
|
srcset={srcSetToUse()}
|
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { Query, UserAttributes } from '../helpers';
|
|
6
6
|
import { type PersonalizationContainerProps } from '../personalization-container.types';
|
|
7
|
+
/**
|
|
8
|
+
* Mirrors helpers/studio-user-attributes.ts; stringification rules out sharing the code.
|
|
9
|
+
* Anything referenced from module scope becomes an undefined global at runtime.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getStudioUserAttributes(): Record<string, unknown>;
|
|
7
12
|
export declare function filterWithCustomTargeting(userAttributes: UserAttributes, query: Query[], startDate?: string, endDate?: string): boolean;
|
|
8
13
|
export declare function updateVisibilityStylesScript(variants: PersonalizationContainerProps['variants'], blockId: string, isHydrationTarget: boolean, locale?: string): void;
|
|
9
|
-
export declare const
|
|
14
|
+
export declare const STUDIO_USER_ATTRIBUTES_SCRIPT = "function getStudioUserAttributes() {\n const params = new URLSearchParams(window.location.search);\n if (params.get('builder.preview') !== 'BUILDER_STUDIO') {\n return {};\n }\n const prefix = 'builder.userAttributes.';\n const attributes = {};\n params.forEach(function (value, key) {\n if (key.indexOf(prefix) === 0) {\n attributes[key.slice(prefix.length)] = value === 'true' ? true : value === 'false' ? false : value;\n }\n });\n return attributes;\n}";
|
|
15
|
+
export declare const PERSONALIZATION_SCRIPT = "function getPersonalizedVariant(variants, blockId, isHydrationTarget, locale) {\n if (!navigator.cookieEnabled) {\n return;\n }\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n Object.assign(attributes, window.builderIoStudioUserAttributes());\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n const parentDiv = document.currentScript?.parentElement;\n const variantId = parentDiv?.getAttribute('data-variant-id');\n const isDefaultVariant = variantId === `${blockId}-default`;\n const isWinningVariant = winningVariantIndex !== -1 && variantId === `${blockId}-${winningVariantIndex}` || winningVariantIndex === -1 && isDefaultVariant;\n if (isWinningVariant && !isDefaultVariant) {\n parentDiv?.removeAttribute('hidden');\n parentDiv?.removeAttribute('aria-hidden');\n } else if (!isWinningVariant && isDefaultVariant) {\n parentDiv?.setAttribute('hidden', 'true');\n parentDiv?.setAttribute('aria-hidden', 'true');\n }\n if (isHydrationTarget) {\n if (!isWinningVariant) {\n const itsStyleEl = parentDiv?.previousElementSibling;\n if (itsStyleEl) {\n itsStyleEl.remove();\n }\n parentDiv?.remove();\n }\n const thisScript = document.currentScript;\n if (thisScript) {\n thisScript.remove();\n }\n }\n}";
|
|
10
16
|
export declare const FILTER_WITH_CUSTOM_TARGETING_SCRIPT = "function filterWithCustomTargeting(userAttributes, query, startDate, endDate) {\n function isString(val) {\n return typeof val === 'string';\n }\n function isNumber(val) {\n return typeof val === 'number';\n }\n function objectMatchesQuery(userattr, query) {\n const result = (() => {\n const property = query.property;\n const operator = query.operator;\n let testValue = query.value;\n if (query && query.property === 'urlPath' && query.value && typeof query.value === 'string' && query.value !== '/' && query.value.endsWith('/')) {\n testValue = query.value.slice(0, -1);\n }\n if (!(property && operator)) {\n return true;\n }\n if (Array.isArray(testValue)) {\n if (operator === 'isNot') {\n return testValue.every(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n return !!testValue.find(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n const value = userattr[property];\n if (Array.isArray(value)) {\n return value.includes(testValue);\n }\n switch (operator) {\n case 'is':\n return value === testValue;\n case 'isNot':\n return value !== testValue;\n case 'contains':\n return (isString(value) || Array.isArray(value)) && value.includes(String(testValue));\n case 'startsWith':\n return isString(value) && value.startsWith(String(testValue));\n case 'endsWith':\n return isString(value) && value.endsWith(String(testValue));\n case 'greaterThan':\n return isNumber(value) && isNumber(testValue) && value > testValue;\n case 'lessThan':\n return isNumber(value) && isNumber(testValue) && value < testValue;\n case 'greaterThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value >= testValue;\n case 'lessThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value <= testValue;\n default:\n return false;\n }\n })();\n return result;\n }\n const item = {\n query,\n startDate,\n endDate\n };\n const now = userAttributes.date && new Date(userAttributes.date) || new Date();\n if (item.startDate && new Date(item.startDate) > now) {\n return false;\n } else if (item.endDate && new Date(item.endDate) < now) {\n return false;\n }\n if (!item.query || !item.query.length) {\n return true;\n }\n return item.query.every(filter => {\n return objectMatchesQuery(userAttributes, filter);\n });\n}";
|
|
11
|
-
export declare const UPDATE_VISIBILITY_STYLES_SCRIPT = "function updateVisibilityStylesScript(variants, blockId, isHydrationTarget, locale) {\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const visibilityStylesEl = document.currentScript?.previousElementSibling;\n if (!visibilityStylesEl) {\n return;\n }\n if (isHydrationTarget) {\n visibilityStylesEl.remove();\n const currentScript = document.currentScript;\n if (currentScript) {\n currentScript.remove();\n }\n } else {\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n if (winningVariantIndex !== -1) {\n let newStyleStr = variants?.map((_, index) => {\n if (index === winningVariantIndex) return '';\n return `div[data-variant-id=\"${blockId}-${index}\"] { display: none !important; } `;\n }).join('') || '';\n newStyleStr += `div[data-variant-id=\"${blockId}-default\"] { display: none !important; } `;\n visibilityStylesEl.innerHTML = newStyleStr;\n }\n }\n}";
|
|
17
|
+
export declare const UPDATE_VISIBILITY_STYLES_SCRIPT = "function updateVisibilityStylesScript(variants, blockId, isHydrationTarget, locale) {\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const visibilityStylesEl = document.currentScript?.previousElementSibling;\n if (!visibilityStylesEl) {\n return;\n }\n if (isHydrationTarget) {\n visibilityStylesEl.remove();\n const currentScript = document.currentScript;\n if (currentScript) {\n currentScript.remove();\n }\n } else {\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n Object.assign(attributes, window.builderIoStudioUserAttributes());\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n if (winningVariantIndex !== -1) {\n let newStyleStr = variants?.map((_, index) => {\n if (index === winningVariantIndex) return '';\n return `div[data-variant-id=\"${blockId}-${index}\"] { display: none !important; } `;\n }).join('') || '';\n newStyleStr += `div[data-variant-id=\"${blockId}-default\"] { display: none !important; } `;\n visibilityStylesEl.innerHTML = newStyleStr;\n }\n }\n}";
|
|
@@ -3,6 +3,24 @@
|
|
|
3
3
|
* They cannot import anything.
|
|
4
4
|
*/
|
|
5
5
|
import '../personalization-container.types';
|
|
6
|
+
/**
|
|
7
|
+
* Mirrors helpers/studio-user-attributes.ts; stringification rules out sharing the code.
|
|
8
|
+
* Anything referenced from module scope becomes an undefined global at runtime.
|
|
9
|
+
*/
|
|
10
|
+
export function getStudioUserAttributes() {
|
|
11
|
+
const params = new URLSearchParams(window.location.search);
|
|
12
|
+
if (params.get('builder.preview') !== 'BUILDER_STUDIO') {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
const prefix = 'builder.userAttributes.';
|
|
16
|
+
const attributes = {};
|
|
17
|
+
params.forEach(function (value, key) {
|
|
18
|
+
if (key.indexOf(prefix) === 0) {
|
|
19
|
+
attributes[key.slice(prefix.length)] = value === 'true' ? true : value === 'false' ? false : value;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return attributes;
|
|
23
|
+
}
|
|
6
24
|
export function filterWithCustomTargeting(userAttributes, query, startDate, endDate) {
|
|
7
25
|
function isString(val) {
|
|
8
26
|
return typeof val === 'string';
|
|
@@ -112,6 +130,7 @@ export function updateVisibilityStylesScript(variants, blockId, isHydrationTarge
|
|
|
112
130
|
if (locale) {
|
|
113
131
|
attributes.locale = locale;
|
|
114
132
|
}
|
|
133
|
+
Object.assign(attributes, window.builderIoStudioUserAttributes());
|
|
115
134
|
const winningVariantIndex = variants?.findIndex(function (variant) {
|
|
116
135
|
return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);
|
|
117
136
|
});
|
|
@@ -126,6 +145,7 @@ export function updateVisibilityStylesScript(variants, blockId, isHydrationTarge
|
|
|
126
145
|
}
|
|
127
146
|
}
|
|
128
147
|
}
|
|
129
|
-
export const
|
|
148
|
+
export const STUDIO_USER_ATTRIBUTES_SCRIPT = "function getStudioUserAttributes() {\n const params = new URLSearchParams(window.location.search);\n if (params.get('builder.preview') !== 'BUILDER_STUDIO') {\n return {};\n }\n const prefix = 'builder.userAttributes.';\n const attributes = {};\n params.forEach(function (value, key) {\n if (key.indexOf(prefix) === 0) {\n attributes[key.slice(prefix.length)] = value === 'true' ? true : value === 'false' ? false : value;\n }\n });\n return attributes;\n}";
|
|
149
|
+
export const PERSONALIZATION_SCRIPT = "function getPersonalizedVariant(variants, blockId, isHydrationTarget, locale) {\n if (!navigator.cookieEnabled) {\n return;\n }\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n Object.assign(attributes, window.builderIoStudioUserAttributes());\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n const parentDiv = document.currentScript?.parentElement;\n const variantId = parentDiv?.getAttribute('data-variant-id');\n const isDefaultVariant = variantId === `${blockId}-default`;\n const isWinningVariant = winningVariantIndex !== -1 && variantId === `${blockId}-${winningVariantIndex}` || winningVariantIndex === -1 && isDefaultVariant;\n if (isWinningVariant && !isDefaultVariant) {\n parentDiv?.removeAttribute('hidden');\n parentDiv?.removeAttribute('aria-hidden');\n } else if (!isWinningVariant && isDefaultVariant) {\n parentDiv?.setAttribute('hidden', 'true');\n parentDiv?.setAttribute('aria-hidden', 'true');\n }\n if (isHydrationTarget) {\n if (!isWinningVariant) {\n const itsStyleEl = parentDiv?.previousElementSibling;\n if (itsStyleEl) {\n itsStyleEl.remove();\n }\n parentDiv?.remove();\n }\n const thisScript = document.currentScript;\n if (thisScript) {\n thisScript.remove();\n }\n }\n}";
|
|
130
150
|
export const FILTER_WITH_CUSTOM_TARGETING_SCRIPT = "function filterWithCustomTargeting(userAttributes, query, startDate, endDate) {\n function isString(val) {\n return typeof val === 'string';\n }\n function isNumber(val) {\n return typeof val === 'number';\n }\n function objectMatchesQuery(userattr, query) {\n const result = (() => {\n const property = query.property;\n const operator = query.operator;\n let testValue = query.value;\n if (query && query.property === 'urlPath' && query.value && typeof query.value === 'string' && query.value !== '/' && query.value.endsWith('/')) {\n testValue = query.value.slice(0, -1);\n }\n if (!(property && operator)) {\n return true;\n }\n if (Array.isArray(testValue)) {\n if (operator === 'isNot') {\n return testValue.every(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n return !!testValue.find(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n const value = userattr[property];\n if (Array.isArray(value)) {\n return value.includes(testValue);\n }\n switch (operator) {\n case 'is':\n return value === testValue;\n case 'isNot':\n return value !== testValue;\n case 'contains':\n return (isString(value) || Array.isArray(value)) && value.includes(String(testValue));\n case 'startsWith':\n return isString(value) && value.startsWith(String(testValue));\n case 'endsWith':\n return isString(value) && value.endsWith(String(testValue));\n case 'greaterThan':\n return isNumber(value) && isNumber(testValue) && value > testValue;\n case 'lessThan':\n return isNumber(value) && isNumber(testValue) && value < testValue;\n case 'greaterThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value >= testValue;\n case 'lessThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value <= testValue;\n default:\n return false;\n }\n })();\n return result;\n }\n const item = {\n query,\n startDate,\n endDate\n };\n const now = userAttributes.date && new Date(userAttributes.date) || new Date();\n if (item.startDate && new Date(item.startDate) > now) {\n return false;\n } else if (item.endDate && new Date(item.endDate) < now) {\n return false;\n }\n if (!item.query || !item.query.length) {\n return true;\n }\n return item.query.every(filter => {\n return objectMatchesQuery(userAttributes, filter);\n });\n}";
|
|
131
|
-
export const UPDATE_VISIBILITY_STYLES_SCRIPT = "function updateVisibilityStylesScript(variants, blockId, isHydrationTarget, locale) {\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const visibilityStylesEl = document.currentScript?.previousElementSibling;\n if (!visibilityStylesEl) {\n return;\n }\n if (isHydrationTarget) {\n visibilityStylesEl.remove();\n const currentScript = document.currentScript;\n if (currentScript) {\n currentScript.remove();\n }\n } else {\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n if (winningVariantIndex !== -1) {\n let newStyleStr = variants?.map((_, index) => {\n if (index === winningVariantIndex) return '';\n return `div[data-variant-id=\"${blockId}-${index}\"] { display: none !important; } `;\n }).join('') || '';\n newStyleStr += `div[data-variant-id=\"${blockId}-default\"] { display: none !important; } `;\n visibilityStylesEl.innerHTML = newStyleStr;\n }\n }\n}";
|
|
151
|
+
export const UPDATE_VISIBILITY_STYLES_SCRIPT = "function updateVisibilityStylesScript(variants, blockId, isHydrationTarget, locale) {\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const visibilityStylesEl = document.currentScript?.previousElementSibling;\n if (!visibilityStylesEl) {\n return;\n }\n if (isHydrationTarget) {\n visibilityStylesEl.remove();\n const currentScript = document.currentScript;\n if (currentScript) {\n currentScript.remove();\n }\n } else {\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n Object.assign(attributes, window.builderIoStudioUserAttributes());\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n if (winningVariantIndex !== -1) {\n let newStyleStr = variants?.map((_, index) => {\n if (index === winningVariantIndex) return '';\n return `div[data-variant-id=\"${blockId}-${index}\"] { display: none !important; } `;\n }).join('') || '';\n newStyleStr += `div[data-variant-id=\"${blockId}-default\"] { display: none !important; } `;\n visibilityStylesEl.innerHTML = newStyleStr;\n }\n }\n}";
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { TARGET } from '../../constants/target';
|
|
2
2
|
import { isBrowser } from '../../functions/is-browser';
|
|
3
3
|
import { isEditing } from '../../functions/is-editing';
|
|
4
|
-
import { FILTER_WITH_CUSTOM_TARGETING_SCRIPT, PERSONALIZATION_SCRIPT, UPDATE_VISIBILITY_STYLES_SCRIPT } from './helpers/inlined-fns';
|
|
4
|
+
import { FILTER_WITH_CUSTOM_TARGETING_SCRIPT, PERSONALIZATION_SCRIPT, STUDIO_USER_ATTRIBUTES_SCRIPT, UPDATE_VISIBILITY_STYLES_SCRIPT } from './helpers/inlined-fns';
|
|
5
5
|
export const DEFAULT_INDEX = 'default';
|
|
6
6
|
const FILTER_WITH_CUSTOM_TARGETING_SCRIPT_FN_NAME = 'filterWithCustomTargeting';
|
|
7
7
|
const BUILDER_IO_PERSONALIZATION_SCRIPT_FN_NAME = 'builderIoPersonalization';
|
|
8
8
|
const UPDATE_VARIANT_VISIBILITY_SCRIPT_FN_NAME = 'updateVisibilityStylesScript';
|
|
9
|
+
const STUDIO_USER_ATTRIBUTES_SCRIPT_FN_NAME = 'builderIoStudioUserAttributes';
|
|
9
10
|
const PERSONALIZATION_CONTAINER_COMPONENT_NAME = 'PersonalizationContainer';
|
|
10
11
|
/**
|
|
11
12
|
* SDKs that support Variant Containers
|
|
@@ -94,6 +95,9 @@ export const hasPersonalizationContainer = (content) => containsPersonalizationC
|
|
|
94
95
|
export const getInitPersonalizationVariantsFnsScriptString = () => {
|
|
95
96
|
return `
|
|
96
97
|
(function() {
|
|
98
|
+
if (!window.${STUDIO_USER_ATTRIBUTES_SCRIPT_FN_NAME}) {
|
|
99
|
+
window.${STUDIO_USER_ATTRIBUTES_SCRIPT_FN_NAME} = ${STUDIO_USER_ATTRIBUTES_SCRIPT};
|
|
100
|
+
}
|
|
97
101
|
if (!window.${FILTER_WITH_CUSTOM_TARGETING_SCRIPT_FN_NAME}) {
|
|
98
102
|
window.${FILTER_WITH_CUSTOM_TARGETING_SCRIPT_FN_NAME} = ${FILTER_WITH_CUSTOM_TARGETING_SCRIPT};
|
|
99
103
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "5.2.
|
|
1
|
+
export declare const SDK_VERSION = "5.2.14";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "5.2.
|
|
1
|
+
export const SDK_VERSION = "5.2.14";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { UserAttributes } from './user-attributes';
|
|
2
|
+
/**
|
|
3
|
+
* Studio passes its targeting overrides as query params because it cannot write the
|
|
4
|
+
* builder.userAttributes cookie on the previewed site's origin.
|
|
5
|
+
*/
|
|
6
|
+
export declare const getStudioUserAttributes: () => UserAttributes;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isBrowser } from '../functions/is-browser';
|
|
2
|
+
const STUDIO_PREVIEW_PARAM = 'builder.preview';
|
|
3
|
+
const STUDIO_PREVIEW_VALUE = 'BUILDER_STUDIO';
|
|
4
|
+
const USER_ATTRIBUTE_PARAM_PREFIX = 'builder.userAttributes.';
|
|
5
|
+
const parseStudioValue = (value) => {
|
|
6
|
+
if (value === 'true')
|
|
7
|
+
return true;
|
|
8
|
+
if (value === 'false')
|
|
9
|
+
return false;
|
|
10
|
+
return value;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Studio passes its targeting overrides as query params because it cannot write the
|
|
14
|
+
* builder.userAttributes cookie on the previewed site's origin.
|
|
15
|
+
*/
|
|
16
|
+
export const getStudioUserAttributes = () => {
|
|
17
|
+
if (!isBrowser()) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const params = new URLSearchParams(window.location.search);
|
|
21
|
+
if (params.get(STUDIO_PREVIEW_PARAM) !== STUDIO_PREVIEW_VALUE) {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
const attributes = {};
|
|
25
|
+
params.forEach((value, key) => {
|
|
26
|
+
if (key.startsWith(USER_ATTRIBUTE_PARAM_PREFIX)) {
|
|
27
|
+
attributes[key.slice(USER_ATTRIBUTE_PARAM_PREFIX.length)] = parseStudioValue(value);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return attributes;
|
|
31
|
+
};
|
|
@@ -4,7 +4,9 @@ export interface UserAttributes {
|
|
|
4
4
|
export declare const USER_ATTRIBUTES_COOKIE_NAME = "builder.userAttributes";
|
|
5
5
|
export declare function createUserAttributesService(): {
|
|
6
6
|
setUserAttributes(newAttrs: UserAttributes): void;
|
|
7
|
-
getUserAttributes():
|
|
7
|
+
getUserAttributes(): {
|
|
8
|
+
[x: string]: any;
|
|
9
|
+
};
|
|
8
10
|
subscribeOnUserAttributesChange(callback: (attrs: UserAttributes) => void, { fireImmediately }?: {
|
|
9
11
|
fireImmediately?: boolean | undefined;
|
|
10
12
|
}): () => void;
|
|
@@ -12,7 +14,9 @@ export declare function createUserAttributesService(): {
|
|
|
12
14
|
};
|
|
13
15
|
export declare const userAttributesService: {
|
|
14
16
|
setUserAttributes(newAttrs: UserAttributes): void;
|
|
15
|
-
getUserAttributes():
|
|
17
|
+
getUserAttributes(): {
|
|
18
|
+
[x: string]: any;
|
|
19
|
+
};
|
|
16
20
|
subscribeOnUserAttributesChange(callback: (attrs: UserAttributes) => void, { fireImmediately }?: {
|
|
17
21
|
fireImmediately?: boolean | undefined;
|
|
18
22
|
}): () => void;
|
|
@@ -2,17 +2,25 @@ import { TARGET } from '../constants/target';
|
|
|
2
2
|
import { isBrowser } from '../functions/is-browser';
|
|
3
3
|
import { getCookieSync, setCookie } from './cookie';
|
|
4
4
|
import { noSerializeWrapper } from './no-serialize-wrapper';
|
|
5
|
+
import { getStudioUserAttributes } from './studio-user-attributes';
|
|
5
6
|
export const USER_ATTRIBUTES_COOKIE_NAME = 'builder.userAttributes';
|
|
6
7
|
export function createUserAttributesService() {
|
|
7
8
|
let canTrack = true;
|
|
8
9
|
const subscribers = new Set();
|
|
10
|
+
const getCookieUserAttributes = () => {
|
|
11
|
+
const cookie = getCookieSync({
|
|
12
|
+
name: USER_ATTRIBUTES_COOKIE_NAME,
|
|
13
|
+
canTrack
|
|
14
|
+
});
|
|
15
|
+
return cookie ? JSON.parse(cookie) : {};
|
|
16
|
+
};
|
|
9
17
|
return {
|
|
10
18
|
setUserAttributes(newAttrs) {
|
|
11
19
|
if (!isBrowser()) {
|
|
12
20
|
return;
|
|
13
21
|
}
|
|
14
22
|
const userAttributes = {
|
|
15
|
-
...
|
|
23
|
+
...getCookieUserAttributes(),
|
|
16
24
|
...newAttrs
|
|
17
25
|
};
|
|
18
26
|
setCookie({
|
|
@@ -20,16 +28,22 @@ export function createUserAttributesService() {
|
|
|
20
28
|
value: JSON.stringify(userAttributes),
|
|
21
29
|
canTrack
|
|
22
30
|
});
|
|
23
|
-
|
|
31
|
+
// Kept out of the cookie above so previewing never persists into a real visitor's
|
|
32
|
+
// session, but re-applied here so the site's own attributes cannot clobber it.
|
|
33
|
+
const studioAttributes = getStudioUserAttributes();
|
|
34
|
+
subscribers.forEach(callback => callback({
|
|
35
|
+
...userAttributes,
|
|
36
|
+
...studioAttributes
|
|
37
|
+
}));
|
|
24
38
|
},
|
|
25
39
|
getUserAttributes() {
|
|
26
40
|
if (!isBrowser()) {
|
|
27
41
|
return {};
|
|
28
42
|
}
|
|
29
|
-
return
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
43
|
+
return {
|
|
44
|
+
...getCookieUserAttributes(),
|
|
45
|
+
...getStudioUserAttributes()
|
|
46
|
+
};
|
|
33
47
|
},
|
|
34
48
|
subscribeOnUserAttributesChange(callback, { fireImmediately } = {}) {
|
|
35
49
|
subscribers.add(callback);
|
package/package.json
CHANGED
|
@@ -5,6 +5,25 @@
|
|
|
5
5
|
|
|
6
6
|
import type { Query, UserAttributes } from '../helpers';
|
|
7
7
|
import { type PersonalizationContainerProps } from '../personalization-container.types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Mirrors helpers/studio-user-attributes.ts; stringification rules out sharing the code.
|
|
11
|
+
* Anything referenced from module scope becomes an undefined global at runtime.
|
|
12
|
+
*/
|
|
13
|
+
export function getStudioUserAttributes() {
|
|
14
|
+
const params = new URLSearchParams(window.location.search);
|
|
15
|
+
if (params.get('builder.preview') !== 'BUILDER_STUDIO') {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
const prefix = 'builder.userAttributes.';
|
|
19
|
+
const attributes: Record<string, unknown> = {};
|
|
20
|
+
params.forEach(function (value, key) {
|
|
21
|
+
if (key.indexOf(prefix) === 0) {
|
|
22
|
+
attributes[key.slice(prefix.length)] = value === 'true' ? true : value === 'false' ? false : value;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
return attributes;
|
|
26
|
+
}
|
|
8
27
|
export function filterWithCustomTargeting(userAttributes: UserAttributes, query: Query[], startDate?: string, endDate?: string) {
|
|
9
28
|
function isString(val: unknown): val is string {
|
|
10
29
|
return typeof val === 'string';
|
|
@@ -110,6 +129,7 @@ export function updateVisibilityStylesScript(variants: PersonalizationContainerP
|
|
|
110
129
|
if (locale) {
|
|
111
130
|
attributes.locale = locale;
|
|
112
131
|
}
|
|
132
|
+
Object.assign(attributes, (window as any).builderIoStudioUserAttributes());
|
|
113
133
|
const winningVariantIndex = variants?.findIndex(function (variant) {
|
|
114
134
|
return (window as any).filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);
|
|
115
135
|
});
|
|
@@ -123,6 +143,7 @@ export function updateVisibilityStylesScript(variants: PersonalizationContainerP
|
|
|
123
143
|
}
|
|
124
144
|
}
|
|
125
145
|
}
|
|
126
|
-
export const
|
|
146
|
+
export const STUDIO_USER_ATTRIBUTES_SCRIPT = "function getStudioUserAttributes() {\n const params = new URLSearchParams(window.location.search);\n if (params.get('builder.preview') !== 'BUILDER_STUDIO') {\n return {};\n }\n const prefix = 'builder.userAttributes.';\n const attributes = {};\n params.forEach(function (value, key) {\n if (key.indexOf(prefix) === 0) {\n attributes[key.slice(prefix.length)] = value === 'true' ? true : value === 'false' ? false : value;\n }\n });\n return attributes;\n}";
|
|
147
|
+
export const PERSONALIZATION_SCRIPT = "function getPersonalizedVariant(variants, blockId, isHydrationTarget, locale) {\n if (!navigator.cookieEnabled) {\n return;\n }\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n Object.assign(attributes, window.builderIoStudioUserAttributes());\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n const parentDiv = document.currentScript?.parentElement;\n const variantId = parentDiv?.getAttribute('data-variant-id');\n const isDefaultVariant = variantId === `${blockId}-default`;\n const isWinningVariant = winningVariantIndex !== -1 && variantId === `${blockId}-${winningVariantIndex}` || winningVariantIndex === -1 && isDefaultVariant;\n if (isWinningVariant && !isDefaultVariant) {\n parentDiv?.removeAttribute('hidden');\n parentDiv?.removeAttribute('aria-hidden');\n } else if (!isWinningVariant && isDefaultVariant) {\n parentDiv?.setAttribute('hidden', 'true');\n parentDiv?.setAttribute('aria-hidden', 'true');\n }\n if (isHydrationTarget) {\n if (!isWinningVariant) {\n const itsStyleEl = parentDiv?.previousElementSibling;\n if (itsStyleEl) {\n itsStyleEl.remove();\n }\n parentDiv?.remove();\n }\n const thisScript = document.currentScript;\n if (thisScript) {\n thisScript.remove();\n }\n }\n}";
|
|
127
148
|
export const FILTER_WITH_CUSTOM_TARGETING_SCRIPT = "function filterWithCustomTargeting(userAttributes, query, startDate, endDate) {\n function isString(val) {\n return typeof val === 'string';\n }\n function isNumber(val) {\n return typeof val === 'number';\n }\n function objectMatchesQuery(userattr, query) {\n const result = (() => {\n const property = query.property;\n const operator = query.operator;\n let testValue = query.value;\n if (query && query.property === 'urlPath' && query.value && typeof query.value === 'string' && query.value !== '/' && query.value.endsWith('/')) {\n testValue = query.value.slice(0, -1);\n }\n if (!(property && operator)) {\n return true;\n }\n if (Array.isArray(testValue)) {\n if (operator === 'isNot') {\n return testValue.every(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n return !!testValue.find(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n const value = userattr[property];\n if (Array.isArray(value)) {\n return value.includes(testValue);\n }\n switch (operator) {\n case 'is':\n return value === testValue;\n case 'isNot':\n return value !== testValue;\n case 'contains':\n return (isString(value) || Array.isArray(value)) && value.includes(String(testValue));\n case 'startsWith':\n return isString(value) && value.startsWith(String(testValue));\n case 'endsWith':\n return isString(value) && value.endsWith(String(testValue));\n case 'greaterThan':\n return isNumber(value) && isNumber(testValue) && value > testValue;\n case 'lessThan':\n return isNumber(value) && isNumber(testValue) && value < testValue;\n case 'greaterThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value >= testValue;\n case 'lessThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value <= testValue;\n default:\n return false;\n }\n })();\n return result;\n }\n const item = {\n query,\n startDate,\n endDate\n };\n const now = userAttributes.date && new Date(userAttributes.date) || new Date();\n if (item.startDate && new Date(item.startDate) > now) {\n return false;\n } else if (item.endDate && new Date(item.endDate) < now) {\n return false;\n }\n if (!item.query || !item.query.length) {\n return true;\n }\n return item.query.every(filter => {\n return objectMatchesQuery(userAttributes, filter);\n });\n}";
|
|
128
|
-
export const UPDATE_VISIBILITY_STYLES_SCRIPT = "function updateVisibilityStylesScript(variants, blockId, isHydrationTarget, locale) {\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const visibilityStylesEl = document.currentScript?.previousElementSibling;\n if (!visibilityStylesEl) {\n return;\n }\n if (isHydrationTarget) {\n visibilityStylesEl.remove();\n const currentScript = document.currentScript;\n if (currentScript) {\n currentScript.remove();\n }\n } else {\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n if (winningVariantIndex !== -1) {\n let newStyleStr = variants?.map((_, index) => {\n if (index === winningVariantIndex) return '';\n return `div[data-variant-id=\"${blockId}-${index}\"] { display: none !important; } `;\n }).join('') || '';\n newStyleStr += `div[data-variant-id=\"${blockId}-default\"] { display: none !important; } `;\n visibilityStylesEl.innerHTML = newStyleStr;\n }\n }\n}";
|
|
149
|
+
export const UPDATE_VISIBILITY_STYLES_SCRIPT = "function updateVisibilityStylesScript(variants, blockId, isHydrationTarget, locale) {\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n const visibilityStylesEl = document.currentScript?.previousElementSibling;\n if (!visibilityStylesEl) {\n return;\n }\n if (isHydrationTarget) {\n visibilityStylesEl.remove();\n const currentScript = document.currentScript;\n if (currentScript) {\n currentScript.remove();\n }\n } else {\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n Object.assign(attributes, window.builderIoStudioUserAttributes());\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return window.filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n if (winningVariantIndex !== -1) {\n let newStyleStr = variants?.map((_, index) => {\n if (index === winningVariantIndex) return '';\n return `div[data-variant-id=\"${blockId}-${index}\"] { display: none !important; } `;\n }).join('') || '';\n newStyleStr += `div[data-variant-id=\"${blockId}-default\"] { display: none !important; } `;\n visibilityStylesEl.innerHTML = newStyleStr;\n }\n }\n}";
|
|
@@ -4,12 +4,13 @@ import { isEditing } from '../../functions/is-editing';
|
|
|
4
4
|
import type { BuilderBlock } from '../../types/builder-block';
|
|
5
5
|
import type { BuilderContent } from '../../types/builder-content';
|
|
6
6
|
import type { Target } from '../../types/targets';
|
|
7
|
-
import { FILTER_WITH_CUSTOM_TARGETING_SCRIPT, PERSONALIZATION_SCRIPT, UPDATE_VISIBILITY_STYLES_SCRIPT } from './helpers/inlined-fns';
|
|
7
|
+
import { FILTER_WITH_CUSTOM_TARGETING_SCRIPT, PERSONALIZATION_SCRIPT, STUDIO_USER_ATTRIBUTES_SCRIPT, UPDATE_VISIBILITY_STYLES_SCRIPT } from './helpers/inlined-fns';
|
|
8
8
|
import type { PersonalizationContainerProps } from './personalization-container.types';
|
|
9
9
|
export const DEFAULT_INDEX = 'default';
|
|
10
10
|
const FILTER_WITH_CUSTOM_TARGETING_SCRIPT_FN_NAME = 'filterWithCustomTargeting';
|
|
11
11
|
const BUILDER_IO_PERSONALIZATION_SCRIPT_FN_NAME = 'builderIoPersonalization';
|
|
12
12
|
const UPDATE_VARIANT_VISIBILITY_SCRIPT_FN_NAME = 'updateVisibilityStylesScript';
|
|
13
|
+
const STUDIO_USER_ATTRIBUTES_SCRIPT_FN_NAME = 'builderIoStudioUserAttributes';
|
|
13
14
|
const PERSONALIZATION_CONTAINER_COMPONENT_NAME = 'PersonalizationContainer';
|
|
14
15
|
export type UserAttributes = {
|
|
15
16
|
date?: string | Date;
|
|
@@ -126,6 +127,9 @@ export const hasPersonalizationContainer = (content: BuilderContent | null | und
|
|
|
126
127
|
export const getInitPersonalizationVariantsFnsScriptString = () => {
|
|
127
128
|
return `
|
|
128
129
|
(function() {
|
|
130
|
+
if (!window.${STUDIO_USER_ATTRIBUTES_SCRIPT_FN_NAME}) {
|
|
131
|
+
window.${STUDIO_USER_ATTRIBUTES_SCRIPT_FN_NAME} = ${STUDIO_USER_ATTRIBUTES_SCRIPT};
|
|
132
|
+
}
|
|
129
133
|
if (!window.${FILTER_WITH_CUSTOM_TARGETING_SCRIPT_FN_NAME}) {
|
|
130
134
|
window.${FILTER_WITH_CUSTOM_TARGETING_SCRIPT_FN_NAME} = ${FILTER_WITH_CUSTOM_TARGETING_SCRIPT};
|
|
131
135
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "5.2.
|
|
1
|
+
export const SDK_VERSION = "5.2.14"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isBrowser } from '../functions/is-browser';
|
|
2
|
+
import type { UserAttributes } from './user-attributes';
|
|
3
|
+
const STUDIO_PREVIEW_PARAM = 'builder.preview';
|
|
4
|
+
const STUDIO_PREVIEW_VALUE = 'BUILDER_STUDIO';
|
|
5
|
+
const USER_ATTRIBUTE_PARAM_PREFIX = 'builder.userAttributes.';
|
|
6
|
+
const parseStudioValue = (value: string) => {
|
|
7
|
+
if (value === 'true') return true;
|
|
8
|
+
if (value === 'false') return false;
|
|
9
|
+
return value;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Studio passes its targeting overrides as query params because it cannot write the
|
|
14
|
+
* builder.userAttributes cookie on the previewed site's origin.
|
|
15
|
+
*/
|
|
16
|
+
export const getStudioUserAttributes = (): UserAttributes => {
|
|
17
|
+
if (!isBrowser()) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const params = new URLSearchParams(window.location.search);
|
|
21
|
+
if (params.get(STUDIO_PREVIEW_PARAM) !== STUDIO_PREVIEW_VALUE) {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
const attributes: UserAttributes = {};
|
|
25
|
+
params.forEach((value, key) => {
|
|
26
|
+
if (key.startsWith(USER_ATTRIBUTE_PARAM_PREFIX)) {
|
|
27
|
+
attributes[key.slice(USER_ATTRIBUTE_PARAM_PREFIX.length)] = parseStudioValue(value);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return attributes;
|
|
31
|
+
}
|
|
@@ -2,6 +2,7 @@ import { TARGET } from '../constants/target';
|
|
|
2
2
|
import { isBrowser } from '../functions/is-browser';
|
|
3
3
|
import { getCookieSync, setCookie } from './cookie';
|
|
4
4
|
import { noSerializeWrapper } from './no-serialize-wrapper';
|
|
5
|
+
import { getStudioUserAttributes } from './studio-user-attributes';
|
|
5
6
|
export interface UserAttributes {
|
|
6
7
|
[key: string]: any;
|
|
7
8
|
}
|
|
@@ -9,13 +10,20 @@ export const USER_ATTRIBUTES_COOKIE_NAME = 'builder.userAttributes';
|
|
|
9
10
|
export function createUserAttributesService() {
|
|
10
11
|
let canTrack = true;
|
|
11
12
|
const subscribers = new Set<(attrs: UserAttributes) => void>();
|
|
13
|
+
const getCookieUserAttributes = (): UserAttributes => {
|
|
14
|
+
const cookie = getCookieSync({
|
|
15
|
+
name: USER_ATTRIBUTES_COOKIE_NAME,
|
|
16
|
+
canTrack
|
|
17
|
+
});
|
|
18
|
+
return cookie ? JSON.parse(cookie) : {};
|
|
19
|
+
};
|
|
12
20
|
return {
|
|
13
21
|
setUserAttributes(newAttrs: UserAttributes) {
|
|
14
22
|
if (!isBrowser()) {
|
|
15
23
|
return;
|
|
16
24
|
}
|
|
17
25
|
const userAttributes: UserAttributes = {
|
|
18
|
-
...
|
|
26
|
+
...getCookieUserAttributes(),
|
|
19
27
|
...newAttrs
|
|
20
28
|
};
|
|
21
29
|
setCookie({
|
|
@@ -23,16 +31,22 @@ export function createUserAttributesService() {
|
|
|
23
31
|
value: JSON.stringify(userAttributes),
|
|
24
32
|
canTrack
|
|
25
33
|
});
|
|
26
|
-
|
|
34
|
+
// Kept out of the cookie above so previewing never persists into a real visitor's
|
|
35
|
+
// session, but re-applied here so the site's own attributes cannot clobber it.
|
|
36
|
+
const studioAttributes = getStudioUserAttributes();
|
|
37
|
+
subscribers.forEach(callback => callback({
|
|
38
|
+
...userAttributes,
|
|
39
|
+
...studioAttributes
|
|
40
|
+
}));
|
|
27
41
|
},
|
|
28
42
|
getUserAttributes() {
|
|
29
43
|
if (!isBrowser()) {
|
|
30
44
|
return {};
|
|
31
45
|
}
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
46
|
+
return {
|
|
47
|
+
...getCookieUserAttributes(),
|
|
48
|
+
...getStudioUserAttributes()
|
|
49
|
+
};
|
|
36
50
|
},
|
|
37
51
|
subscribeOnUserAttributesChange(callback: (attrs: UserAttributes) => void, {
|
|
38
52
|
fireImmediately
|