@builder.io/sdk
Version:
This SDK is largely a wrapper over our [Content API](https://www.builder.io/c/docs/content-api)
1,313 lines (1,293 loc) • 113 kB
JavaScript
import { __assign } from 'tslib';
import hash from 'hash-sum';
(function () {
if (typeof window === 'undefined' || typeof window.CustomEvent === 'function') return false;
function CustomEvent(event, params) {
params = params || { bubbles: false, cancelable: false, detail: null };
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}
window.CustomEvent = CustomEvent;
})();
var isSafari = typeof window !== 'undefined' &&
/^((?!chrome|android).)*safari/i.test(window.navigator.userAgent);
var isClient = typeof window !== 'undefined';
// TODO: queue all of these in a debounceNextTick
function nextTick(fn) {
// if (typeof process !== 'undefined' && process.nextTick) {
// console.log('process.nextTick?');
// process.nextTick(fn);
// return;
// }
// FIXME: fix the real safari issue of this randomly not working
if (!isClient || isSafari || typeof MutationObserver === 'undefined') {
setTimeout(fn);
return;
}
var called = 0;
var observer = new MutationObserver(function () { return fn(); });
var element = document.createTextNode('');
observer.observe(element, {
characterData: true,
});
// tslint:disable-next-line
element.data = String((called = ++called));
}
var PROPERTY_NAME_DENY_LIST = Object.freeze(['__proto__', 'prototype', 'constructor']);
// TODO: unit tests
var QueryString = /** @class */ (function () {
function QueryString() {
}
QueryString.parseDeep = function (queryString) {
var obj = this.parse(queryString);
return this.deepen(obj);
};
QueryString.stringifyDeep = function (obj) {
var map = this.flatten(obj);
return this.stringify(map);
};
QueryString.parse = function (queryString) {
var query = {};
var pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&');
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('=');
// TODO: node support?
try {
query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '');
}
catch (error) {
// Ignore malformed URI components
}
}
return query;
};
QueryString.stringify = function (map) {
var str = '';
for (var key in map) {
if (map.hasOwnProperty(key)) {
var value = map[key];
if (str) {
str += '&';
}
str += encodeURIComponent(key) + '=' + encodeURIComponent(value);
}
}
return str;
};
QueryString.deepen = function (map) {
// FIXME; Should be type Tree = Record<string, string | Tree>
// requires a typescript upgrade.
var output = {};
for (var k in map) {
var t = output;
var parts = k.split('.');
var key = parts.pop();
for (var _i = 0, parts_1 = parts; _i < parts_1.length; _i++) {
var part = parts_1[_i];
assertAllowedPropertyName(part);
t = t[part] = t[part] || {};
}
t[key] = map[k];
}
return output;
};
QueryString.flatten = function (obj, _current, _res) {
if (_res === void 0) { _res = {}; }
for (var key in obj) {
var value = obj[key];
var newKey = _current ? _current + '.' + key : key;
if (value && typeof value === 'object') {
this.flatten(value, newKey, _res);
}
else {
_res[newKey] = value;
}
}
return _res;
};
return QueryString;
}());
function assertAllowedPropertyName(name) {
if (PROPERTY_NAME_DENY_LIST.indexOf(name) >= 0)
throw new Error("Property name \"".concat(name, "\" is not allowed"));
}
var Subscription = /** @class */ (function () {
function Subscription(listeners, listener) {
this.listeners = listeners;
this.listener = listener;
this.unsubscribed = false;
this.otherSubscriptions = [];
}
Object.defineProperty(Subscription.prototype, "closed", {
get: function () {
return this.unsubscribed;
},
enumerable: false,
configurable: true
});
Subscription.prototype.add = function (subscription) {
this.otherSubscriptions.push(subscription);
};
Subscription.prototype.unsubscribe = function () {
if (this.unsubscribed) {
return;
}
if (this.listener && this.listeners) {
var index = this.listeners.indexOf(this.listener);
if (index > -1) {
this.listeners.splice(index, 1);
}
}
this.otherSubscriptions.forEach(function (sub) { return sub.unsubscribe(); });
this.unsubscribed = true;
};
return Subscription;
}());
// TODO: follow minimal basic spec: https://github.com/tc39/proposal-observable
var BehaviorSubject = /** @class */ (function () {
function BehaviorSubject(value) {
var _this = this;
this.value = value;
this.listeners = [];
this.errorListeners = [];
this.then = function () {
var _a;
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return (_a = _this.toPromise()).then.apply(_a, args);
};
}
BehaviorSubject.prototype.next = function (value) {
this.value = value;
for (var _i = 0, _a = this.listeners; _i < _a.length; _i++) {
var listener = _a[_i];
listener(value);
}
};
// TODO: implement this as PIPE instead
BehaviorSubject.prototype.map = function (fn) {
var newSubject = new BehaviorSubject(fn(this.value));
// TODO: on destroy delete these
this.subscribe(function (val) {
newSubject.next(fn(val));
});
this.catch(function (err) {
newSubject.error(err);
});
return newSubject;
};
BehaviorSubject.prototype.catch = function (errorListener) {
this.errorListeners.push(errorListener);
return new Subscription(this.errorListeners, errorListener);
};
BehaviorSubject.prototype.error = function (error) {
for (var _i = 0, _a = this.errorListeners; _i < _a.length; _i++) {
var listener = _a[_i];
listener(error);
}
};
BehaviorSubject.prototype.subscribe = function (listener, errorListener) {
this.listeners.push(listener);
if (errorListener) {
this.errorListeners.push(errorListener);
}
return new Subscription(this.listeners, listener);
};
BehaviorSubject.prototype.toPromise = function () {
var _this = this;
return new Promise(function (resolve, reject) {
var subscription = _this.subscribe(function (value) {
resolve(value);
subscription.unsubscribe();
}, function (err) {
reject(err);
subscription.unsubscribe();
});
});
};
BehaviorSubject.prototype.promise = function () {
return this.toPromise();
};
return BehaviorSubject;
}());
var State = {
Pending: 'Pending',
Fulfilled: 'Fulfilled',
Rejected: 'Rejected',
};
function isFunction(val) {
return val && typeof val === 'function';
}
function isObject(val) {
return val && typeof val === 'object';
}
var TinyPromise = /** @class */ (function () {
function TinyPromise(executor) {
this._state = State.Pending;
this._handlers = [];
this._value = null;
executor(this._resolve.bind(this), this._reject.bind(this));
}
TinyPromise.prototype._resolve = function (x) {
var _this = this;
if (x instanceof TinyPromise) {
x.then(this._resolve.bind(this), this._reject.bind(this));
}
else if (isObject(x) || isFunction(x)) {
var called_1 = false;
try {
var thenable = x.then;
if (isFunction(thenable)) {
thenable.call(x, function (result) {
if (!called_1)
_this._resolve(result);
called_1 = true;
return undefined;
}, function (error) {
if (!called_1)
_this._reject(error);
called_1 = true;
return undefined;
});
}
else {
this._fulfill(x);
}
}
catch (ex) {
if (!called_1) {
this._reject(ex);
}
}
}
else {
this._fulfill(x);
}
};
TinyPromise.prototype._fulfill = function (result) {
var _this = this;
this._state = State.Fulfilled;
this._value = result;
this._handlers.forEach(function (handler) { return _this._callHandler(handler); });
};
TinyPromise.prototype._reject = function (error) {
var _this = this;
this._state = State.Rejected;
this._value = error;
this._handlers.forEach(function (handler) { return _this._callHandler(handler); });
};
TinyPromise.prototype._isPending = function () {
return this._state === State.Pending;
};
TinyPromise.prototype._isFulfilled = function () {
return this._state === State.Fulfilled;
};
TinyPromise.prototype._isRejected = function () {
return this._state === State.Rejected;
};
TinyPromise.prototype._addHandler = function (onFulfilled, onRejected) {
this._handlers.push({
onFulfilled: onFulfilled,
onRejected: onRejected,
});
};
TinyPromise.prototype._callHandler = function (handler) {
if (this._isFulfilled() && isFunction(handler.onFulfilled)) {
handler.onFulfilled(this._value);
}
else if (this._isRejected() && isFunction(handler.onRejected)) {
handler.onRejected(this._value);
}
};
TinyPromise.prototype.then = function (onFulfilled, onRejected) {
var _this = this;
switch (this._state) {
case State.Pending: {
return new TinyPromise(function (resolve, reject) {
_this._addHandler(function (value) {
nextTick(function () {
try {
if (isFunction(onFulfilled)) {
resolve(onFulfilled(value));
}
else {
resolve(value);
}
}
catch (ex) {
reject(ex);
}
});
}, function (error) {
nextTick(function () {
try {
if (isFunction(onRejected)) {
resolve(onRejected(error));
}
else {
reject(error);
}
}
catch (ex) {
reject(ex);
}
});
});
});
}
case State.Fulfilled: {
return new TinyPromise(function (resolve, reject) {
nextTick(function () {
try {
if (isFunction(onFulfilled)) {
resolve(onFulfilled(_this._value));
}
else {
resolve(_this._value);
}
}
catch (ex) {
reject(ex);
}
});
});
}
case State.Rejected: {
return new TinyPromise(function (resolve, reject) {
nextTick(function () {
try {
if (isFunction(onRejected)) {
resolve(onRejected(_this._value));
}
else {
reject(_this._value);
}
}
catch (ex) {
reject(ex);
}
});
});
}
}
};
return TinyPromise;
}());
var Promise$1 = (typeof Promise !== 'undefined' ? Promise : TinyPromise);
// Webpack workaround to conditionally require certain external modules
// only on the server and not bundle them on the client
var serverOnlyRequire;
try {
// tslint:disable-next-line:no-eval
serverOnlyRequire = eval('require');
}
catch (err) {
// all good
serverOnlyRequire = (function () { return null; });
}
var serverOnlyRequire$1 = serverOnlyRequire;
function promiseResolve(value) {
return new Promise$1(function (resolve) { return resolve(value); });
}
// Adapted from https://raw.githubusercontent.com/developit/unfetch/master/src/index.mjs
function tinyFetch(url, options) {
if (options === void 0) { options = {}; }
return new Promise$1(function (resolve, reject) {
var request = new XMLHttpRequest();
request.open(options.method || 'get', url, true);
if (options.headers) {
for (var i in options.headers) {
request.setRequestHeader(i, options.headers[i]);
}
}
request.withCredentials = options.credentials === 'include';
request.onload = function () {
resolve(response());
};
request.onerror = reject;
request.send(options.body);
function response() {
var keys = [];
var all = [];
var headers = {};
var header = undefined;
request
.getAllResponseHeaders()
.replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm, function (_match, _key, value) {
var key = _key;
keys.push((key = key.toLowerCase()));
all.push([key, value]);
header = headers[key];
headers[key] = header ? "".concat(header, ",").concat(value) : value;
return '';
});
return {
ok: ((request.status / 100) | 0) === 2,
status: request.status,
statusText: request.statusText,
url: request.responseURL,
clone: response,
text: function () { return promiseResolve(request.responseText); },
json: function () { return promiseResolve(request.responseText).then(JSON.parse); },
blob: function () { return promiseResolve(new Blob([request.response])); },
headers: {
keys: function () { return keys; },
entries: function () { return all; },
get: function (n) { return headers[n.toLowerCase()]; },
has: function (n) { return n.toLowerCase() in headers; },
},
};
}
});
}
function getFetch() {
// If fetch is defined, in the browser, via polyfill, or in a Cloudflare worker, use it.
var _fetch = undefined;
if (globalThis.fetch) {
_fetch !== null && _fetch !== void 0 ? _fetch : (_fetch = globalThis.fetch);
}
else if (typeof window === 'undefined') {
// If fetch is not defined, in a Node.js environment, use node-fetch.
try {
// node-fetch@^3 is ESM only, and will throw error on require.
_fetch !== null && _fetch !== void 0 ? _fetch : (_fetch = serverOnlyRequire$1('node-fetch'));
}
catch (e) {
// If node-fetch is not installed, use tiny-fetch.
console.warn('node-fetch is not installed. consider polyfilling fetch or installing node-fetch.');
console.warn(e);
}
}
// Otherwise, use tiny-fetch.
return _fetch !== null && _fetch !== void 0 ? _fetch : tinyFetch;
}
function assign(target) {
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) {
// Skip over if undefined or null
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
}
function throttle(func, wait, options) {
if (options === void 0) { options = {}; }
var context;
var args;
var result;
var timeout = null;
var previous = 0;
var later = function () {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(context, args);
if (!timeout)
context = args = null;
};
return function () {
var now = Date.now();
if (!previous && options.leading === false)
previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout)
context = args = null;
}
else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
}
var camelCaseToKebabCase = function (str) {
return str ? str.replace(/([A-Z])/g, function (g) { return "-".concat(g[0].toLowerCase()); }) : '';
};
var Animator = /** @class */ (function () {
function Animator() {
}
Animator.prototype.bindAnimations = function (animations) {
for (var _i = 0, animations_1 = animations; _i < animations_1.length; _i++) {
var animation = animations_1[_i];
switch (animation.trigger) {
case 'pageLoad':
this.triggerAnimation(animation);
break;
case 'hover':
this.bindHoverAnimation(animation);
break;
case 'scrollInView':
this.bindScrollInViewAnimation(animation);
break;
}
}
};
Animator.prototype.warnElementNotPresent = function (id) {
console.warn("Cannot animate element: element with ID ".concat(id, " not found!"));
};
Animator.prototype.augmentAnimation = function (animation, element) {
var stylesUsed = this.getAllStylesUsed(animation);
var computedStyle = getComputedStyle(element);
// const computedStyle = getComputedStyle(element);
// // FIXME: this will break if original load is in one reponsive size then resize to another hmmm
// Need to use transform instead of left since left can change on screen sizes
var firstStyles = animation.steps[0].styles;
var lastStyles = animation.steps[animation.steps.length - 1].styles;
var bothStyles = [firstStyles, lastStyles];
// FIXME: this won't work as expected for augmented animations - may need the editor itself to manage this
for (var _i = 0, bothStyles_1 = bothStyles; _i < bothStyles_1.length; _i++) {
var styles = bothStyles_1[_i];
for (var _a = 0, stylesUsed_1 = stylesUsed; _a < stylesUsed_1.length; _a++) {
var style = stylesUsed_1[_a];
if (!(style in styles)) {
styles[style] = computedStyle[style];
}
}
}
};
Animator.prototype.getAllStylesUsed = function (animation) {
var properties = [];
for (var _i = 0, _a = animation.steps; _i < _a.length; _i++) {
var step = _a[_i];
for (var key in step.styles) {
if (properties.indexOf(key) === -1) {
properties.push(key);
}
}
}
return properties;
};
Animator.prototype.triggerAnimation = function (animation) {
var _this = this;
// TODO: do for ALL elements
var elements = Array.prototype.slice.call(document.getElementsByClassName(animation.elementId || animation.id || ''));
if (!elements.length) {
this.warnElementNotPresent(animation.elementId || animation.id || '');
return;
}
Array.from(elements).forEach(function (element) {
_this.augmentAnimation(animation, element);
// TODO: do this properly, may have other animations of different properties
// TODO: only override the properties
// TODO: if there is an entrance and hover animation, the transition duration will get effed
// element.setAttribute('style', '');
// const styledUsed = this.getAllStylesUsed(animation);
element.style.transition = 'none';
element.style.transitionDelay = '0';
assign(element.style, animation.steps[0].styles);
// TODO: queue/batch these timeouts
// TODO: only include properties explicitly set in the animation
// using Object.keys(styles)
setTimeout(function () {
element.style.transition = "all ".concat(animation.duration, "s ").concat(camelCaseToKebabCase(animation.easing));
if (animation.delay) {
element.style.transitionDelay = animation.delay + 's';
}
assign(element.style, animation.steps[1].styles);
// TODO: maybe remove/reset transitoin property after animation duration
// TODO: queue timers
setTimeout(function () {
// TODO: what if has other transition (reset back to what it was)
element.style.transition = '';
element.style.transitionDelay = '';
}, (animation.delay || 0) * 1000 + animation.duration * 1000 + 100);
});
});
};
Animator.prototype.bindHoverAnimation = function (animation) {
var _this = this;
// TODO: is it multiple binding when editing...?
// TODO: unbind on element remove
// TODO: apply to ALL elements
var elements = Array.prototype.slice.call(document.getElementsByClassName(animation.elementId || animation.id || ''));
if (!elements.length) {
this.warnElementNotPresent(animation.elementId || animation.id || '');
return;
}
Array.from(elements).forEach(function (element) {
_this.augmentAnimation(animation, element);
var defaultState = animation.steps[0].styles;
var hoverState = animation.steps[1].styles;
function attachDefaultState() {
assign(element.style, defaultState);
}
function attachHoverState() {
assign(element.style, hoverState);
}
attachDefaultState();
element.addEventListener('mouseenter', attachHoverState);
element.addEventListener('mouseleave', attachDefaultState);
// TODO: queue/batch these timeouts
setTimeout(function () {
element.style.transition = "all ".concat(animation.duration, "s ").concat(camelCaseToKebabCase(animation.easing));
if (animation.delay) {
element.style.transitionDelay = animation.delay + 's';
}
});
});
};
// TODO: unbind on element remove
Animator.prototype.bindScrollInViewAnimation = function (animation) {
var _this = this;
// TODO: apply to ALL matching elements
var elements = Array.prototype.slice.call(document.getElementsByClassName(animation.elementId || animation.id || ''));
if (!elements.length) {
this.warnElementNotPresent(animation.elementId || animation.id || '');
return;
}
// TODO: if server side rendered and scrolled into view don't animate...
Array.from(elements).forEach(function (element) {
_this.augmentAnimation(animation, element);
var triggered = false;
var pendingAnimation = false;
function immediateOnScroll() {
if (!triggered && isScrolledIntoView(element)) {
triggered = true;
pendingAnimation = true;
setTimeout(function () {
assign(element.style, animation.steps[1].styles);
if (!animation.repeat) {
document.removeEventListener('scroll', onScroll);
}
setTimeout(function () {
pendingAnimation = false;
if (!animation.repeat) {
element.style.transition = '';
element.style.transitionDelay = '';
}
}, (animation.duration + (animation.delay || 0)) * 1000 + 100);
});
}
else if (animation.repeat &&
triggered &&
!pendingAnimation &&
!isScrolledIntoView(element)) {
// we want to repeat the animation every time the the element is out of view and back again
triggered = false;
assign(element.style, animation.steps[0].styles);
}
}
// TODO: roll all of these in one for more efficiency of checking all the rects
var onScroll = throttle(immediateOnScroll, 200, { leading: false });
// TODO: fully in view or partially
function isScrolledIntoView(elem) {
var rect = elem.getBoundingClientRect();
var windowHeight = window.innerHeight;
var thresholdPercent = (animation.thresholdPercent || 0) / 100;
var threshold = thresholdPercent * windowHeight;
// TODO: partial in view? or what if element is larger than screen itself
return (rect.bottom > threshold && rect.top < windowHeight - threshold // Element is peeking top or bottom
// (rect.top > 0 && rect.bottom < window.innerHeight) || // element fits within the screen and is fully on screen (not hanging off at all)
// (rect.top < 0 && rect.bottom > window.innerHeight) // element is larger than the screen and hangs over the top and bottom
);
}
var defaultState = animation.steps[0].styles;
function attachDefaultState() {
assign(element.style, defaultState);
}
attachDefaultState();
// TODO: queue/batch these timeouts!
setTimeout(function () {
element.style.transition = "all ".concat(animation.duration, "s ").concat(camelCaseToKebabCase(animation.easing));
if (animation.delay) {
element.style.transitionDelay = animation.delay + 's';
}
});
// TODO: one listener for everything
document.addEventListener('scroll', onScroll, { capture: true, passive: true });
// Do an initial check
immediateOnScroll();
});
};
return Animator;
}());
/**
* RegExp to match field-content in RFC 7230 sec 3.2
*
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
* field-vchar = VCHAR / obs-text
* obs-text = %x80-FF
*/
var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
var Cookies = /** @class */ (function () {
function Cookies(request, response) {
this.request = request;
this.response = response;
}
Cookies.prototype.get = function (name) {
var header = this.request.headers['cookie'];
if (!header) {
return;
}
var match = header.match(getPattern(name));
if (!match) {
return;
}
var value = match[1];
return value;
};
Cookies.prototype.set = function (name, value, opts) {
var res = this.response;
var req = this.request;
var headers = res.getHeader('Set-Cookie') || [];
// TODO: just make this always true
var secure = this.secure !== undefined
? !!this.secure
: req.protocol === 'https' || req.connection.encrypted;
var cookie = new Cookie(name, value, opts);
if (typeof headers === 'string') {
headers = [headers];
}
if (!secure && opts && opts.secure) {
throw new Error('Cannot send secure cookie over unencrypted connection');
}
cookie.secure = secure;
if (opts && 'secure' in opts) {
cookie.secure = !!opts.secure;
}
pushCookie(headers, cookie);
var setHeader = res.setHeader;
setHeader.call(res, 'Set-Cookie', headers);
return this;
};
return Cookies;
}());
var Cookie = /** @class */ (function () {
function Cookie(name, value, attrs) {
this.path = '/';
this.domain = undefined;
this.httpOnly = true;
this.sameSite = false;
this.secure = false;
this.overwrite = false;
this.name = '';
this.value = '';
if (!fieldContentRegExp.test(name)) {
throw new TypeError('argument name is invalid');
}
if (value && !fieldContentRegExp.test(value)) {
throw new TypeError('argument value is invalid');
}
if (!value) {
this.expires = new Date(0);
}
this.name = name;
this.value = value || '';
if (attrs.expires) {
this.expires = attrs.expires;
}
if (attrs.secure) {
this.secure = attrs.secure;
}
}
Cookie.prototype.toString = function () {
return "".concat(this.name, "=").concat(this.value);
};
Cookie.prototype.toHeader = function () {
var header = this.toString();
if (this.maxAge) {
this.expires = new Date(Date.now() + this.maxAge);
}
if (this.path) {
header += "; path=".concat(this.path);
}
if (this.expires) {
header += "; expires=".concat(this.expires.toUTCString());
}
if (this.domain) {
header += "; domain=".concat(this.domain);
}
// TODO: samesite=none by default (?)
header += "; SameSite=".concat(this.sameSite === true ? 'strict' : 'None');
// TODO: On by default
if (this.secure) {
header += '; secure';
}
if (this.httpOnly) {
header += '; httponly';
}
return header;
};
return Cookie;
}());
function getPattern(name) {
return new RegExp("(?:^|;) *".concat(name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'), "=([^;]*)"));
}
function pushCookie(headers, cookie) {
if (cookie.overwrite) {
for (var i = headers.length - 1; i >= 0; i--) {
if (headers[i].indexOf("".concat(cookie.name, "=")) === 0) {
headers.splice(i, 1);
}
}
}
headers.push(cookie.toHeader());
}
function omit(obj) {
var values = [];
for (var _i = 1; _i < arguments.length; _i++) {
values[_i - 1] = arguments[_i];
}
var newObject = Object.assign({}, obj);
for (var _a = 0, values_1 = values; _a < values_1.length; _a++) {
var key = values_1[_a];
delete newObject[key];
}
return newObject;
}
/**
* @credit https://stackoverflow.com/a/2117523
*/
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
/**
* Slightly cleaner and smaller UUIDs
*/
function uuid() {
return uuidv4().replace(/-/g, '');
}
function emptyUrl() {
return {
query: null,
port: null,
auth: null,
hash: null,
host: null,
hostname: null,
href: null,
path: null,
pathname: null,
protocol: null,
search: null,
slashes: null,
};
}
// Replacement for `url.parse` using `URL` global object that works with relative paths.
// Assumptions: this function operates in a NodeJS environment.
function parse$1(url) {
var _a;
var out = emptyUrl();
var u;
var pathOnly = url === '' || url[0] === '/';
if (pathOnly) {
u = new URL(url, 'http://0.0.0.0/');
out.href = u.href;
out.href = (_a = out.href) === null || _a === void 0 ? void 0 : _a.slice(14); // remove 'http://0.0.0.0/'
}
else {
u = new URL(url);
out.href = u.href;
out.port = u.port === '' ? null : u.port;
out.hash = u.hash === '' ? null : u.hash;
out.host = u.host;
out.hostname = u.hostname;
out.href = u.href;
out.pathname = u.pathname;
out.protocol = u.protocol;
out.slashes = url[u.protocol.length] === '/'; // check if the mimetype is proceeded by a slash
}
out.search = u.search;
out.query = u.search.slice(1); // remove '?'
out.path = "".concat(u.pathname).concat(u.search);
out.pathname = u.pathname;
return out;
}
/**
* Safe conversion to error type. Intended to be used in catch blocks where the
* value is not guaranteed to be an error.
*
* @example
* try {
* throw new Error('Something went wrong')
* }
* catch (err: unknown) {
* const error: Error = toError(err)
* }
*/
function toError(err) {
if (err instanceof Error)
return err;
return new Error(String(err));
}
var DEFAULT_API_VERSION = 'v3';
var SDK_VERSION = '6.1.0';
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;
};
var isReactNative = typeof navigator === 'object' && navigator.product === 'ReactNative';
var 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 = isReactNative
? function () { return emptyUrl(); }
: typeof window === 'object'
? urlParser.parse
: parse$1;
function setCookie(name, value, expires) {
try {
var expiresString = '';
// TODO: need to know if secure server side
if (expires) {
expiresString = '; expires=' + expires.toUTCString();
}
var secure = 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';
var isBrowser = typeof window !== 'undefined' && !isReactNative;
var isIframe = isBrowser && window.top !== window.self;
function BuilderComponent(info) {
if (info === void 0) { info = {}; }
return Builder.Component(info);
}
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 = 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 BehaviorSubject('query');
this.apiVersion$ = new BehaviorSubject(undefined);
this.canTrack$ = new BehaviorSubject(!this.browserTrackingDisabled);
this.hasOverriddenCanTrack = false;
this.apiKey$ = new BehaviorSubject(null);
this.authToken$ = new BehaviorSubject(null);
this.userAttributesChanged = new BehaviorSubject(null);
this.editingMode$ = new BehaviorSubject(isIframe);
// TODO: decorator to do this stuff with the get/set (how do with typing too? compiler?)
this.editingModel$ = new 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(this.request, this.response);
}
if (apiKey) {
this.apiKey = apiKey;
}
if (apiVersion) {
this.apiVersion = apiVersion;
}
if (authToken) {
this.authToken = authToken;
}
if (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 (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 (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: 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 actio