@builder.io/sdk
Version:
This SDK is largely a wrapper over our [Content API](https://www.builder.io/c/docs/content-api)
1,224 lines • 78.2 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Builder = exports.BuilderComponent = exports.isIframe = exports.isBrowser = exports.validEnvList = exports.isReactNative = void 0;
require("./polyfills/custom-event-polyfill");
var next_tick_function_1 = require("./functions/next-tick.function");
var query_string_class_1 = require("./classes/query-string.class");
var observable_class_1 = require("./classes/observable.class");
var fetch_function_1 = require("./functions/fetch.function");
var assign_function_1 = require("./functions/assign.function");
var throttle_function_1 = require("./functions/throttle.function");
var animator_class_1 = require("./classes/animator.class");
var cookies_class_1 = __importDefault(require("./classes/cookies.class"));
var omit_function_1 = require("./functions/omit.function");
var uuid_1 = require("./functions/uuid");
var url_1 = require("./url");
// Do not change this to a require! It throws runtime errors - rollup
// will preserve the `require` and throw runtime errors
var hash_sum_1 = __importDefault(require("hash-sum"));
var to_error_1 = require("./functions/to-error");
var url_2 = require("./url");
var api_version_1 = require("./types/api-version");
var sdk_version_1 = require("./sdk-version");
function datePlusMinutes(minutes) {
if (minutes === void 0) { minutes = 30; }
return new Date(Date.now() + minutes * 60000);
}
var isPositiveNumber = function (thing) {
return typeof thing === 'number' && !isNaN(thing) && thing >= 0;
};
exports.isReactNative = typeof navigator === 'object' && navigator.product === 'ReactNative';
exports.validEnvList = [
'production',
'qa',
'test',
'development',
'dev',
'cdn-qa',
'cloud',
'fast',
'cdn2',
'cdn-prod',
];
function getQueryParam(url, variable) {
var query = url.split('?')[1] || '';
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
if (decodeURIComponent(pair[0]) === variable) {
return decodeURIComponent(pair[1]);
}
}
return null;
}
var urlParser = {
parse: function (url) {
var el = document.createElement('a');
el.href = url;
var out = {};
var props = [
'username',
'password',
'host',
'hostname',
'port',
'protocol',
'origin',
'pathname',
'search',
'hash',
];
for (var _i = 0, props_1 = props; _i < props_1.length; _i++) {
var prop = props_1[_i];
out[prop] = el[prop];
}
// IE 11 pathname handling workaround
// (IE omits preceeding '/', unlike other browsers)
if ((out.pathname || out.pathname === '') &&
typeof out.pathname === 'string' &&
out.pathname.indexOf('/') !== 0) {
out.pathname = '/' + out.pathname;
}
return out;
},
};
var parse = exports.isReactNative
? function () { return (0, url_2.emptyUrl)(); }
: typeof window === 'object'
? urlParser.parse
: url_1.parse;
function setCookie(name, value, expires) {
try {
var expiresString = '';
// TODO: need to know if secure server side
if (expires) {
expiresString = '; expires=' + expires.toUTCString();
}
var secure = exports.isBrowser ? location.protocol === 'https:' : true;
document.cookie =
name +
'=' +
(value || '') +
expiresString +
'; path=/' +
(secure ? '; secure; SameSite=None' : '');
}
catch (err) {
console.warn('Could not set cookie', err);
}
}
function getCookie(name) {
try {
return (decodeURIComponent(document.cookie.replace(new RegExp('(?:(?:^|.*;)\\s*' +
encodeURIComponent(name).replace(/[\-\.\+\*]/g, '\\$&') +
'\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1')) || null);
}
catch (err) {
console.warn('Could not get cookie', err);
}
}
function size(object) {
return Object.keys(object).length;
}
function find(target, callback) {
var list = target;
// Makes sures is always has an positive integer as length.
var length = list.length >>> 0;
var thisArg = arguments[1];
for (var i = 0; i < length; i++) {
var element = list[i];
if (callback.call(thisArg, element, i, list)) {
return element;
}
}
}
var sessionStorageKey = 'builderSessionId';
var localStorageKey = 'builderVisitorId';
exports.isBrowser = typeof window !== 'undefined' && !exports.isReactNative;
exports.isIframe = exports.isBrowser && window.top !== window.self;
function BuilderComponent(info) {
if (info === void 0) { info = {}; }
return Builder.Component(info);
}
exports.BuilderComponent = BuilderComponent;
var Builder = /** @class */ (function () {
function Builder(apiKey, request, response, forceNewInstance, authToken, apiVersion) {
if (apiKey === void 0) { apiKey = null; }
if (forceNewInstance === void 0) { forceNewInstance = false; }
if (authToken === void 0) { authToken = null; }
var _this = this;
this.request = request;
this.response = response;
this.eventsQueue = [];
this.throttledClearEventsQueue = (0, throttle_function_1.throttle)(function () {
_this.processEventsQueue();
// Extend the session cookie
_this.setCookie(sessionStorageKey, _this.sessionId, datePlusMinutes(30));
}, 5);
this.env = 'production';
this.sessionId = this.getSessionId();
this.targetContent = true;
this.contentPerRequest = 1;
// TODO: make array or function
this.allowCustomFonts = true;
this.cookies = null;
// TODO: api options object
this.cachebust = false;
this.overrideParams = '';
this.noCache = false;
this.preview = false;
/**
* Dictates which API endpoint is used when fetching content. Allows `'content'` and `'query'`.
* Defaults to `'query'`.
*/
this.apiEndpoint$ = new observable_class_1.BehaviorSubject('query');
this.apiVersion$ = new observable_class_1.BehaviorSubject(undefined);
this.canTrack$ = new observable_class_1.BehaviorSubject(!this.browserTrackingDisabled);
this.hasOverriddenCanTrack = false;
this.apiKey$ = new observable_class_1.BehaviorSubject(null);
this.authToken$ = new observable_class_1.BehaviorSubject(null);
this.userAttributesChanged = new observable_class_1.BehaviorSubject(null);
this.editingMode$ = new observable_class_1.BehaviorSubject(exports.isIframe);
// TODO: decorator to do this stuff with the get/set (how do with typing too? compiler?)
this.editingModel$ = new observable_class_1.BehaviorSubject(null);
this.userAgent = (typeof navigator === 'object' && navigator.userAgent) || '';
this.trackingHooks = [];
// Set this to control the userId
// TODO: allow changing it mid session and updating existing data to be associated
// e.g. for when a user navigates and then logs in
this.visitorId = this.getVisitorId();
this.autoTrack = !Builder.isBrowser
? false
: !this.isDevelopmentEnv &&
!(Builder.isBrowser && location.search.indexOf('builder.preview=') !== -1);
this.trackingUserAttributes = {};
this.blockContentLoading = '';
this.observersByKey = {};
this.noEditorUpdates = {};
this.overrides = {};
this.queryOptions = {};
this.getContentQueue = null;
this.priorContentQueue = null;
this.testCookiePrefix = 'builder.tests';
this.cookieQueue = [];
// TODO: use a window variable for this perhaps, e.g. bc webcomponents may be loading builder twice
// with it's and react (use rollup build to fix)
if (Builder.isBrowser && !forceNewInstance && Builder.singletonInstance) {
return Builder.singletonInstance;
}
if (this.request && this.response) {
this.setUserAgent(this.request.headers['user-agent'] || '');
this.cookies = new cookies_class_1.default(this.request, this.response);
}
if (apiKey) {
this.apiKey = apiKey;
}
if (apiVersion) {
this.apiVersion = apiVersion;
}
if (authToken) {
this.authToken = authToken;
}
if (exports.isBrowser) {
if (Builder.isEditing) {
this.bindMessageListeners();
parent.postMessage({
type: 'builder.animatorOptions',
data: {
options: {
version: 2,
},
},
}, '*');
}
// TODO: postmessage to parent the builder info for every package
// type: 'builder.sdk', data: { name: '@builder.io/react', version: '0.1.23' }
// (window as any).BUILDER_VERSION = Builder.VERSION;
// Ensure always one Builder global singleton
// TODO: some people won't want this, e.g. rakuten
// Maybe hide this behind symbol or on document, etc
// if ((window as any).Builder) {
// Builder.components = (window as any).Builder.components;
// } else {
// (window as any).Builder = Builder;
// }
}
if (exports.isIframe) {
this.messageFrameLoaded();
}
// TODO: on destroy clear subscription
this.canTrack$.subscribe(function (value) {
if (value) {
if (typeof sessionStorage !== 'undefined') {
try {
if (!sessionStorage.getItem(sessionStorageKey)) {
sessionStorage.setItem(sessionStorageKey, _this.sessionId);
}
}
catch (err) {
console.debug('Session storage error', err);
}
}
if (_this.eventsQueue.length) {
_this.throttledClearEventsQueue();
}
if (_this.cookieQueue.length) {
_this.cookieQueue.forEach(function (item) {
_this.setCookie(item[0], item[1]);
});
_this.cookieQueue.length = 0;
}
}
});
if (exports.isBrowser) {
// TODO: defer so subclass constructor runs and injects location service
this.setTestsFromUrl();
// TODO: do this on every request send?
this.getOverridesFromQueryString();
// cookies used in personalization container script, so need to set before hydration to match script result
var userAttrCookie = this.getCookie(Builder.attributesCookieName);
if (userAttrCookie) {
try {
var attributes = JSON.parse(userAttrCookie);
this.setUserAttributes(attributes);
}
catch (err) {
console.debug('Error parsing user attributes cookie', err);
}
}
}
}
Builder.register = function (type, info) {
if (type === 'plugin') {
info = this.serializeIncludingFunctions(info, true);
}
// TODO: all must have name and can't conflict?
var typeList = this.registry[type];
if (!typeList) {
typeList = this.registry[type] = [];
}
typeList.push(info);
if (Builder.isBrowser) {
var message = {
type: 'builder.register',
data: {
type: type,
info: info,
},
};
try {
parent.postMessage(message, '*');
if (parent !== window) {
window.postMessage(message, '*');
}
}
catch (err) {
console.debug('Could not postmessage', err);
}
}
this.registryChange.next(this.registry);
};
Builder.registerEditor = function (info) {
if (Builder.isBrowser) {
window.postMessage({
type: 'builder.registerEditor',
data: (0, omit_function_1.omit)(info, 'component'),
}, '*');
var hostname = location.hostname;
if (!Builder.isTrustedHost(hostname)) {
console.error('Builder.registerEditor() called in the wrong environment! You cannot load custom editors from your app, they must be loaded through the Builder.io app itself. Follow the readme here for more details: https://github.com/builderio/builder/tree/master/plugins/cloudinary or contact chat us in our Spectrum community for help: https://spectrum.chat/builder');
}
}
this.editors.push(info);
};
Builder.registerPlugin = function (info) {
this.plugins.push(info);
};
Builder.registerAction = function (action) {
var _a;
this.actions.push(action);
if (Builder.isBrowser) {
var actionClone = JSON.parse(JSON.stringify(action));
if (action.action) {
actionClone.action = action.action.toString();
}
(_a = window.parent) === null || _a === void 0 ? void 0 : _a.postMessage({
type: 'builder.registerAction',
data: actionClone,
}, '*');
}
};
Builder.registerTrustedHost = function (host) {
this.trustedHosts.push(host);
};
/**
* @param context @type {import('isolated-vm').Context}
* Use this function to control the execution context of custom code on the server.
* const ivm = require('isolated-vm');
* const isolate = new ivm.Isolate({ memoryLimit: 128 });
* const context = isolate.createContextSync();
* Builder.setServerContext(context);
*/
Builder.setServerContext = function (context) {
this.serverContext = context;
};
Builder.isTrustedHost = function (hostname) {
var isTrusted = this.trustedHosts.findIndex(function (trustedHost) {
return trustedHost.startsWith('*.')
? hostname.endsWith(trustedHost.slice(1))
: trustedHost === hostname;
}) > -1;
return isTrusted;
};
Builder.isTrustedHostForEvent = function (event) {
if (event.origin === 'null') {
return false;
}
var url = parse(event.origin);
return url.hostname && Builder.isTrustedHost(url.hostname);
};
Builder.runAction = function (action) {
// TODO
var actionObject = typeof action === 'string' ? find(this.actions, function (item) { return item.name === action; }) : action;
if (!actionObject) {
throw new Error("Action not found: ".concat(action));
}
};
Builder.fields = function (name, fields) {
var _a;
(_a = window.parent) === null || _a === void 0 ? void 0 : _a.postMessage({
type: 'builder.fields',
data: { name: name, fields: fields },
}, '*');
};
/**
* @deprecated
* @hidden
*
* Use Builder.register('editor.settings', {}) instead.
*/
Builder.set = function (settings) {
Builder.register('editor.settings', settings);
};
Builder.import = function (packageName) {
if (!Builder.isBrowser) {
// TODO: server side support *maybe*
console.warn('Builder.import used on the server - this should only be used in the browser');
return;
}
var System = window.System;
if (!System) {
console.warn('System.js not available. Please include System.js when using Builder.import');
return;
}
return System.import("https://cdn.builder.io/systemjs/".concat(packageName));
};
Object.defineProperty(Builder, "editingPage", {
// useCdnApi = false;
get: function () {
return this._editingPage;
},
set: function (editingPage) {
this._editingPage = editingPage;
if (exports.isBrowser && exports.isIframe) {
if (editingPage) {
document.body.classList.add('builder-editing-page');
}
else {
document.body.classList.remove('builder-editing-page');
}
}
},
enumerable: false,
configurable: true
});
Builder.serializeIncludingFunctions = function (info, isForPlugin) {
var serializeFn = function (fnValue) {
var fnStr = fnValue.toString().trim();
// we need to account for a few different fn syntaxes:
// 1. `function name(args) => {code}`
// 2. `name(args) => {code}`
// 3. `(args) => {}`
// 4. `args => {}`
// 5. `async function(args) {code}`
// 6. `async (args) => {}`
// 7. `async args => {}`
var isArrowWithoutParens = /^[a-zA-Z0-9_]+\s*=>/i.test(fnStr);
var appendFunction = !fnStr.startsWith('function') &&
!fnStr.startsWith('async') &&
!fnStr.startsWith('(') &&
!isArrowWithoutParens;
return "return (".concat(appendFunction ? 'function ' : '').concat(fnStr, ").apply(this, arguments)");
};
var objToReturn = JSON.parse(JSON.stringify(info, function (key, value) {
var shouldNotSerializeFn = isForPlugin && key === 'onSave';
if (typeof value === 'function' && !shouldNotSerializeFn) {
return serializeFn(value);
}
return value;
}));
if (isForPlugin) {
objToReturn.onSave = info.onSave;
}
return objToReturn;
};
Builder.prepareComponentSpecToSend = function (spec) {
return __assign(__assign({}, this.serializeIncludingFunctions(spec)), { class: undefined });
};
Builder.registerBlock = function (component, options) {
this.registerComponent(component, options);
};
Builder.registerComponent = function (component, options) {
var _a;
var spec = __assign(__assign({ class: component }, component.builderOptions), options);
this.addComponent(spec);
var editable = options.models && this.singletonInstance.editingModel
? exports.isBrowser && options.models.includes(this.singletonInstance.editingModel)
: exports.isBrowser;
if (editable) {
var sendSpec = this.prepareComponentSpecToSend(spec);
(_a = window.parent) === null || _a === void 0 ? void 0 : _a.postMessage({
type: 'builder.registerComponent',
data: sendSpec,
}, '*');
}
};
Builder.addComponent = function (component) {
var current = find(this.components, function (item) { return item.name === component.name; });
if (current) {
// FIXME: why does sometimes we get an extra post without class - probably
// from postMessage handler wrong in some place
if (current.class && !component.class) {
return;
}
this.components.splice(this.components.indexOf(current), 1, component);
}
else {
this.components.push(component);
}
};
// TODO: style guide, etc off this system as well?
Builder.component = function (info) {
var _this = this;
if (info === void 0) { info = {}; }
return function (component) {
var _a;
var spec = __assign(__assign({}, info), { class: component });
if (!spec.name) {
spec.name = component.name;
}
_this.addComponent(spec);
var sendSpec = _this.prepareComponentSpecToSend(spec);
// TODO: serialize component name and inputs
if (exports.isBrowser) {
(_a = window.parent) === null || _a === void 0 ? void 0 : _a.postMessage({
type: 'builder.registerComponent',
data: sendSpec,
}, '*');
}
return component;
};
};
Object.defineProperty(Builder, "Component", {
get: function () {
return this.component;
},
enumerable: false,
configurable: true
});
Builder.prototype.processEventsQueue = function () {
if (!this.eventsQueue.length) {
return;
}
var events = this.eventsQueue;
this.eventsQueue = [];
var fullUserAttributes = __assign(__assign({}, Builder.overrideUserAttributes), this.trackingUserAttributes);
for (var _i = 0, events_1 = events; _i < events_1.length; _i++) {
var event_1 = events_1[_i];
if (!event_1.data.metadata) {
event_1.data.metadata = {};
}
if (!event_1.data.metadata.user) {
event_1.data.metadata.user = {};
}
Object.assign(event_1.data.metadata.user, fullUserAttributes, event_1.data.metadata.user);
}
var host = this.host;
(0, fetch_function_1.getFetch)()("".concat(host, "/api/v1/track"), {
method: 'POST',
body: JSON.stringify({ events: events }),
headers: __assign({ 'content-type': 'application/json' }, this.getSdkHeaders()),
mode: 'cors',
}).catch(function () {
// Not the end of the world
});
};
Object.defineProperty(Builder.prototype, "browserTrackingDisabled", {
get: function () {
return Builder.isBrowser && Boolean(window.builderNoTrack || !navigator.cookieEnabled);
},
enumerable: false,
configurable: true
});
Object.defineProperty(Builder.prototype, "canTrack", {
get: function () {
return this.canTrack$.value;
},
set: function (canTrack) {
this.hasOverriddenCanTrack = true;
if (this.canTrack !== canTrack) {
this.canTrack$.next(canTrack);
}
},
enumerable: false,
configurable: true
});
Object.defineProperty(Builder.prototype, "apiVersion", {
get: function () {
return this.apiVersion$.value;
},
set: function (apiVersion) {
if (this.apiVersion !== apiVersion) {
this.apiVersion$.next(apiVersion);
}
},
enumerable: false,
configurable: true
});
Object.defineProperty(Builder.prototype, "apiEndpoint", {
get: function () {
return this.apiEndpoint$.value;
},
set: function (apiEndpoint) {
if (this.apiEndpoint !== apiEndpoint) {
this.apiEndpoint$.next(apiEndpoint);
}
},
enumerable: false,
configurable: true
});
Object.defineProperty(Builder.prototype, "editingMode", {
get: function () {
return this.editingMode$.value;
},
set: function (value) {
if (value !== this.editingMode) {
this.editingMode$.next(value);
}
},
enumerable: false,
configurable: true
});
Object.defineProperty(Builder.prototype, "editingModel", {
get: function () {
return this.editingModel$.value;
},
set: function (value) {
if (value !== this.editingModel) {
this.editingModel$.next(value);
}
},
enumerable: false,
configurable: true
});
Builder.prototype.findParentElement = function (target, callback, checkElement) {
if (checkElement === void 0) { checkElement = true; }
if (!(target instanceof HTMLElement)) {
return null;
}
var parent = checkElement ? target : target.parentElement;
do {
if (!parent) {
return null;
}
var matches = callback(parent);
if (matches) {
return parent;
}
} while ((parent = parent.parentElement));
return null;
};
Builder.prototype.findBuilderParent = function (target) {
return this.findParentElement(target, function (el) {
var id = el.getAttribute('builder-id') || el.id;
return Boolean(id && id.indexOf('builder-') === 0);
});
};
Builder.prototype.setUserAgent = function (userAgent) {
this.userAgent = userAgent || '';
};
/**
* Set a hook to modify events being tracked from builder, such as impressions and clicks
*
* For example, to track the model ID of each event associated with content for querying
* by mode, you can do
*
* builder.setTrackingHook((event, context) => {
* if (context.content) {
* event.data.metadata.modelId = context.content.modelId
* }
* })
*/
Builder.prototype.setTrackingHook = function (hook) {
this.trackingHooks.push(hook);
};
Builder.prototype.track = function (eventName, properties, context) {
if (properties === void 0) { properties = {}; }
// TODO: queue up track requests and fire them off when canTrack set to true - otherwise may get lots of clicks with no impressions
if (exports.isIframe || !exports.isBrowser || Builder.isPreviewing) {
return;
}
var apiKey = this.apiKey;
if (!apiKey) {
console.error('Builder integration error: Looks like the Builder SDK has not been initialized properly (your API key has not been set). Make sure you are calling `builder.init("«YOUR-API-KEY»");` as early as possible in your application\'s code.');
return;
}
var eventData = JSON.parse(JSON.stringify({
type: eventName,
data: __assign(__assign({}, (0, omit_function_1.omit)(properties, 'meta')), { metadata: __assign(__assign({ sdkVersion: Builder.VERSION, url: location.href }, properties.meta), properties.metadata), ownerId: apiKey, userAttributes: this.getUserAttributes(), sessionId: this.sessionId, visitorId: this.visitorId }),
}));
for (var _i = 0, _a = this.trackingHooks; _i < _a.length; _i++) {
var hook = _a[_i];
var returnValue = hook(eventData, context || {});
if (returnValue) {
eventData = returnValue;
}
}
// batch events
this.eventsQueue.push(eventData);
if (this.canTrack) {
this.throttledClearEventsQueue();
}
};
Builder.prototype.getSessionId = function () {
var _this = this;
var sessionId = null;
try {
if (Builder.isBrowser && typeof sessionStorage !== 'undefined') {
sessionId = this.getCookie(sessionStorageKey);
}
}
catch (err) {
console.debug('Session storage error', err);
// It's ok
}
if (!sessionId) {
sessionId = (0, uuid_1.uuid)();
}
// Give the app a second to start up and set canTrack to false if needed
if (Builder.isBrowser) {
setTimeout(function () {
try {
if (_this.canTrack) {
_this.setCookie(sessionStorageKey, sessionId, datePlusMinutes(30));
}
}
catch (err) {
console.debug('Cookie setting error', err);
}
});
}
return sessionId;
};
Builder.prototype.getVisitorId = function () {
var _this = this;
if (this.visitorId) {
return this.visitorId;
}
var visitorId = null;
try {
if (Builder.isBrowser && typeof localStorage !== 'undefined') {
// TODO: cookie instead?
visitorId = localStorage.getItem(localStorageKey);
}
}
catch (err) {
console.debug('Local storage error', err);
// It's ok
}
if (!visitorId) {
visitorId = (0, uuid_1.uuid)();
}
this.visitorId = visitorId;
// Give the app a second to start up and set canTrack to false if needed
if (Builder.isBrowser) {
setTimeout(function () {
try {
if (_this.canTrack && typeof localStorage !== 'undefined' && visitorId) {
localStorage.setItem(localStorageKey, visitorId);
}
}
catch (err) {
console.debug('Session storage error', err);
}
});
}
return visitorId;
};
Builder.prototype.trackImpression = function (contentId, variationId, properties, context) {
if (exports.isIframe || !exports.isBrowser || Builder.isPreviewing) {
return;
}
// TODO: use this.track method
this.track('impression', {
contentId: contentId,
variationId: variationId !== contentId ? variationId : undefined,
metadata: properties,
}, context);
};
Builder.prototype.trackConversion = function (amount, contentId, variationId, customProperties, context) {
if (exports.isIframe || !exports.isBrowser || Builder.isPreviewing) {
return;
}
var meta = typeof contentId === 'object' ? contentId : customProperties;
var useContentId = typeof contentId === 'string' ? contentId : undefined;
this.track('conversion', { amount: amount, variationId: variationId, meta: meta, contentId: useContentId }, context);
};
Object.defineProperty(Builder.prototype, "isDevelopmentEnv", {
// TODO: set this for QA
get: function () {
// Automatic determining of development environment
return (Builder.isIframe ||
(Builder.isBrowser && (location.hostname === 'localhost' || location.port !== '')) ||
this.env !== 'production');
},
enumerable: false,
configurable: true
});
Builder.prototype.trackInteraction = function (contentId, variationId, alreadyTrackedOne, event, context) {
if (alreadyTrackedOne === void 0) { alreadyTrackedOne = false; }
if (exports.isIframe || !exports.isBrowser || Builder.isPreviewing) {
return;
}
var target = event && event.target;
var targetBuilderElement = target && this.findBuilderParent(target);
function round(num) {
return Math.round(num * 1000) / 1000;
}
var metadata = {};
if (event) {
var clientX = event.clientX, clientY = event.clientY;
if (target) {
var targetRect = target.getBoundingClientRect();
var xOffset = clientX - targetRect.left;
var yOffset = clientY - targetRect.top;
var xRatio = round(xOffset / targetRect.width);
var yRatio = round(yOffset / targetRect.height);
metadata.targetOffset = {
x: xRatio,
y: yRatio,
};
}
if (targetBuilderElement) {
var targetRect = targetBuilderElement.getBoundingClientRect();
var xOffset = clientX - targetRect.left;
var yOffset = clientY - targetRect.top;
var xRatio = round(xOffset / targetRect.width);
var yRatio = round(yOffset / targetRect.height);
metadata.builderTargetOffset = {
x: xRatio,
y: yRatio,
};
}
}
var builderId = targetBuilderElement &&
(targetBuilderElement.getAttribute('builder-id') || targetBuilderElement.id);
if (builderId && targetBuilderElement) {
metadata.builderElementIndex = [].slice
.call(document.getElementsByClassName(builderId))
.indexOf(targetBuilderElement);
}
this.track('click', {
contentId: contentId,
metadata: metadata,
variationId: variationId !== contentId ? variationId : undefined,
unique: !alreadyTrackedOne,
targetBuilderElement: builderId || undefined,
}, context);
};
Builder.prototype.component = function (info) {
if (info === void 0) { info = {}; }
return Builder.component(info);
};
Object.defineProperty(Builder.prototype, "apiKey", {
get: function () {
return this.apiKey$.value;
},
set: function (key) {
this.apiKey$.next(key);
},
enumerable: false,
configurable: true
});
Object.defineProperty(Builder.prototype, "authToken", {
get: function () {
return this.authToken$.value;
},
set: function (token) {
this.authToken$.next(token);
},
enumerable: false,
configurable: true
});
Builder.prototype.modifySearch = function (search) {
return search.replace(/(^|&|\?)(builder_.*?)=/gi, function (_match, group1, group2) { return group1 + group2.replace(/_/g, '.') + '='; });
};
Builder.prototype.setTestsFromUrl = function () {
var search = this.getLocation().search;
try {
var params = query_string_class_1.QueryString.parseDeep(this.modifySearch(search || '').substr(1));
var tests = params.builder && params.builder.tests;
if (tests && typeof tests === 'object') {
for (var key in tests) {
if (tests.hasOwnProperty(key)) {
this.setTestCookie(key, tests[key]);
}
}
}
}
catch (e) {
console.debug('Error parsing tests from URL', e);
}
};
Builder.prototype.resetOverrides = function () {
// Ugly - pass down instances per request instead using react context
// or use builder.get('foo', { req, res }) in react...........
Builder.overrideUserAttributes = {};
this.cachebust = false;
this.noCache = false;
this.preview = false;
this.editingModel = null;
this.overrides = {};
this.env = 'production';
this.userAgent = '';
this.request = undefined;
this.response = undefined;
};
Builder.prototype.getOverridesFromQueryString = function () {
var location = this.getLocation();
try {
var params = query_string_class_1.QueryString.parseDeep(this.modifySearch(location.search || '').substr(1));
var builder = params.builder;
if (builder) {
var userAttributes = builder.userAttributes, overrides = builder.overrides, env = builder.env, host = builder.host, api = builder.api, cachebust = builder.cachebust, noCache = builder.noCache, preview = builder.preview, editing = builder.editing, frameEditing = builder.frameEditing, options = builder.options, overrideParams = builder.params;
if (userAttributes) {
this.setUserAttributes(userAttributes);
}
if (options) {
// picking only locale, includeRefs, and enrich for now
this.queryOptions = __assign(__assign(__assign({}, (options.locale && { locale: options.locale })), (options.includeRefs && { includeRefs: options.includeRefs })), (options.enrich && { enrich: options.enrich }));
}
if (overrides) {
this.overrides = overrides;
}
if (exports.validEnvList.indexOf(env || api) > -1) {
this.env = env || api;
}
if (Builder.isEditing) {
var editingModel = frameEditing || editing || preview;
if (editingModel && editingModel !== 'true') {
this.editingModel = editingModel;
}
}
if (cachebust) {
this.cachebust = true;
}
if (noCache) {
this.noCache = true;
}
if (preview) {
this.preview = true;
}
if (params) {
this.overrideParams = overrideParams;
}
}
}
catch (e) {
console.debug('Error parsing overrides from URL', e);
}
};
Builder.prototype.messageFrameLoaded = function () {
var _a;
(_a = window.parent) === null || _a === void 0 ? void 0 : _a.postMessage({
type: 'builder.loaded',
data: {
value: true,
},
}, '*');
};
Builder.prototype.bindMessageListeners = function () {
var _this = this;
addEventListener('message', function (event) {
var _a, _b, _c, _d;
var isTrusted = Builder.isTrustedHostForEvent(event);
if (!isTrusted)
return;
var data = event.data;
if (data) {
switch (data.type) {
case 'builder.ping': {
(_a = window.parent) === null || _a === void 0 ? void 0 : _a.postMessage({
type: 'builder.pong',
data: {},
}, '*');
break;
}
case 'builder.register': {
// TODO: possibly do this for all...
if (event.source === window) {
break;
}
var options = data.data;
if (!options) {
break;
}
var type = options.type, info = options.info;
// TODO: all must have name and can't conflict?
var typeList = Builder.registry[type];
if (!typeList) {
typeList = Builder.registry[type] = [];
}
typeList.push(info);
Builder.registryChange.next(Builder.registry);
break;
}
case 'builder.settingsChange': {
// TODO: possibly do this for all...
if (event.source === window) {
break;
}
var settings = data.data;
if (!settings) {
break;
}
Object.assign(Builder.settings, settings);
Builder.settingsChange.next(Builder.settings);
break;
}
case 'builder.registerEditor': {
// TODO: possibly do this for all...
if (event.source === window) {
break;
}
var info_1 = data.data;
if (!info_1) {
break;
}
var hasComponent_1 = !!info_1.component;
Builder.editors.every(function (thisInfo, index) {
if (info_1.name === thisInfo.name) {
if (thisInfo.component && !hasComponent_1) {
return false;
}
else {
Builder.editors[index] = thisInfo;
}
return false;
}
return true;
});
break;
}
case 'builder.triggerAnimation': {
Builder.animator.triggerAnimation(data.data);
break;
}
case 'builder.contentUpdate':
var key = data.data.key || data.data.alias || data.data.entry || data.data.modelName;
var contentData = data.data.data; // hmmm...
var observer = _this.observersByKey[key];
if (observer && !_this.noEditorUpdates[key]) {
observer.next([contentData]);
}
break;
case 'builder.getComponents':
(_b = window.parent) === null || _b === void 0 ? void 0 : _b.postMessage({
type: 'builder.components',
data: Builder.components.map(function (item) { return Builder.prepareComponentSpecToSend(item); }),
}, '*');
break;
case 'builder.editingModel':
_this.editingModel = data.data.model;
break;
case 'builder.registerComponent':
var componentData = data.data;
Builder.addComponent(componentData);
break;
case 'builder.blockContentLoading':
if (typeof data.data.model === 'string') {
_this.blockContentLoading = data.data.model;
}
break;
case 'builder.editingMode':
var editingMode = data.data;
if (editingMode) {
_this.editingMode = true;
document.body.classList.add('builder-editing');
}
else {
_this.editingMode = false;
document.body.classList.remove('builder-editing');
}
break;
case 'builder.editingPageMode':
var editingPageMode = data.data;
Builder.editingPage = editingPageMode;
break;
case 'builder.overrideUserAttributes':
var userAttributes = data.data;
(0, assign_function_1.assign)(Builder.overrideUserAttributes, userAttributes);
_this.flushGetContentQueue(true);
// TODO: refetch too
break;
case 'builder.overrideTestGroup':
var _e = data.data, variationId = _e.variationId, contentId = _e.contentId;
if (variationId && contentId) {
_this.setTestCookie(contentId, variationId);
_this.flushGetContentQueue(true);
}
break;
case 'builder.evaluate': {
var text = data.data.text;
var args = data.data.arguments || [];
var id_1 = data.data.id;
// tslint:disable-next-line:no-function-constructor-with-string-args
var fn = new Function(text);
var result = void 0;
var error = null;
try {
result = fn.apply(_this, args);
}
catch (err) {
error = (0, to_error_1.toError)(err);
}
if (error) {
(_c = window.parent) === null || _c === void 0 ? void 0 : _c.postMessage({
type: 'builder.evaluateError',
data: { id: id_1, error: error.message },
}, '*');
}
else {
if (result && typeof result.then === 'function') {
result
.then(function (finalResult) {
var _a;
(_a = window.parent) === null || _a === void 0 ? void 0 : _a.postMessage({
type: 'builder.evaluateResult',
data: { id: id_1, result: finalResult },
}, '*');
})
.catch(console.error);
}
else {
(_d = window.parent) === null || _d === void 0 ? void 0 : _d.postMessage({
type: 'builder.evaluateResult',
data: { result: result, id: id_1 },
}, '*');
}
}
break;
}
}
}
});
};
Object.defineProperty(Builder.prototype, "defaultCanTrack", {
get: function () {
return Boolean(Builder.isBrowser &&
navigator.userAgent.trim() &&
!navigator.userAgent.match(/bot|crawler|spider|robot|crawling|prerender|google|baidu|bing|msn|duckduckbot|teoma|slurp|yandex|phantom|headless|selenium|puppeteer/i) &&
!this.browserTrackingDisabled);
},
enumerable: false,
configurable: true
});
Builder.prototype.init = function (apiKey, canTrack, req, res, authToken, apiVersion) {
if (canTrack === void 0) { canTrack = this.defaultCanTrack; }
if (req) {
this.request = req;
}
if (res) {
this.response = res;
}
if (!this.hasOverriddenCanTrack) {
this.canTrack = canTrack;
}
this.apiKey = apiKey;
if (authToken) {
this.authToken = authToken;
}
if (apiVersion) {
this.apiVersion = apiVersion;
}
return this;
};
Object.defineProperty(Builder.prototype, "previewingModel", {
get: function () {
var search = this.getLocation().search;
var params = query_string_class_1.QueryString.parse((search || '').substr(1));
return params['builder.preview'];
},
enumerable: false,
configurable: true
});
// TODO: allow adding location object as property and/or in constructor
Builder.prototype.getLocation = function () {
var _a;
var parsedLocation = {};
// in ssr mode
if (this.request) {
parsedLocation = parse((_a = this.request.url) !== null && _a !== void 0 ? _a : '');
}
else if (typeof location === 'object') {
// in the browser
parsedLocation = parse(location.href);
}
// IE11 bug with parsed path being empty string
// causes issues with our user targeting
if (parsedLocation.pathname === '') {
parsedLocation.pathname = '/';
}
return parsedLocation;
};
Builder.prototype.getUserAttributes = function (userAgent) {
if (userAgent === void 0) { userAgent = this.userAgent || ''; }
var isMobile = {
Android: function () {
return userAgent.match(/Android/i);
},
BlackBerry: function () {
return userAgent.match(/BlackBerry/i);
},
iOS: function () {
return userAgent.match(/iPhone|iPod/i);
},
Opera: function () {
return userAgent.match(/Opera Mini/i);
},
Windows: function () {
return userAgent.match(/IEMobile/i) || userAgent.match(/WPDesktop/i);
},
any: function () {
return (isMobile.Android() ||
isMobile.BlackBerry() ||
isMobile.iOS() ||
isMobile.Opera() ||
isMobile.Windows());
},
};
va