UNPKG

@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
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