UNPKG

@builder.io/sdk

Version:

1,510 lines 63 kB
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }

var throttle = _interopDefault(require('lodash-es/throttle'));
var last = _interopDefault(require('lodash-es/last'));
var kebabCase = _interopDefault(require('lodash-es/kebabCase'));
var uniqueSelector = _interopDefault(require('unique-selector'));
var Cookies = _interopDefault(require('js-cookie'));
var stringify = _interopDefault(require('json-stable-stringify'));
var includes = _interopDefault(require('lodash-es/includes'));
var omit = _interopDefault(require('lodash-es/omit'));
var round = _interopDefault(require('lodash-es/round'));
var sortBy = _interopDefault(require('lodash-es/sortBy'));
var queryString = _interopDefault(require('query-string'));
var rxjs = require('rxjs');
var parser = _interopDefault(require('ua-parser-js'));
var url = require('url');
require('whatwg-fetch');

/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0

THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.

See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */

var __assign = Object.assign || function __assign(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;
};

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 " + 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 = last(animation.steps).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 element = document.getElementById(animation.elementId);
        if (!element) {
            this.warnElementNotPresent(animation.elementId);
            return;
        }
        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', '');
        var styledUsed = this.getAllStylesUsed(animation);
        element.style.transition = 'none';
        element.style.transitionDelay = '0';
        Object.assign(element.style, animation.steps[0].styles);
        setTimeout(function () {
            element.style.transition = "all " + animation.duration + "s " + kebabCase(animation.easing);
            if (animation.delay) {
                element.style.transitionDelay = animation.delay + 's';
            }
            Object.assign(element.style, animation.steps[1].styles);
            // TODO: maybe remove/reset transitoin property after animation duration
            setTimeout(function () {
                element.style.transition = '';
                element.style.transitionDelay = '';
            }, (animation.delay || 0) * 1000 + animation.duration * 1000);
        });
    };
    Animator.prototype.bindHoverAnimation = function (animation) {
        // TODO: unbind on element remove
        var element = document.getElementById(animation.elementId);
        if (!element) {
            this.warnElementNotPresent(animation.elementId);
            return;
        }
        this.augmentAnimation(animation, element);
        var defaultState = animation.steps[0].styles;
        var hoverState = animation.steps[1].styles;
        function attachDefaultState() {
            Object.assign(element.style, defaultState);
        }
        function attachHoverState() {
            Object.assign(element.style, hoverState);
        }
        attachDefaultState();
        element.addEventListener('mouseenter', attachHoverState);
        element.addEventListener('mouseleave', attachDefaultState);
        setTimeout(function () {
            element.style.transition = "all " + animation.duration + "s " + kebabCase(animation.easing);
            if (animation.delay) {
                element.style.transitionDelay = animation.delay + 's';
            }
        });
    };
    // TODO: unbind on element remove
    Animator.prototype.bindScrollInViewAnimation = function (animation) {
        var element = document.getElementById(animation.elementId);
        if (!element) {
            this.warnElementNotPresent(animation.elementId);
            return;
        }
        this.augmentAnimation(animation, element);
        var triggered = false;
        // TODO: roll all of these in one for more efficiency of checking all the rects
        var onScroll = throttle(function () {
            if (!triggered && isScrolledIntoView(element)) {
                triggered = true;
                Object.assign(element.style, animation.steps[1].styles);
                document.removeEventListener('scroll', onScroll);
                setTimeout(function () {
                    element.style.transition = '';
                    element.style.transitionDelay = '';
                }, animation.duration * 1000);
            }
        }, 100, { leading: false });
        // TODO: fully in view or partially
        function isScrolledIntoView(elem) {
            var rect = elem.getBoundingClientRect();
            var windowHeight = window.innerHeight;
            var thresholdPrecent = 0.2;
            var threshold = thresholdPrecent * 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
            );
        }
        var defaultState = animation.steps[0].styles;
        function attachDefaultState() {
            Object.assign(element.style, defaultState);
        }
        attachDefaultState();
        setTimeout(function () {
            element.style.transition = "all " + animation.duration + "s " + kebabCase(animation.easing);
            if (animation.delay) {
                element.style.transitionDelay = animation.delay + 's';
            }
        });
        // TODO: one listener for everything
        document.addEventListener('scroll', onScroll, { capture: true, passive: true });
        element.addEventListener('remove', function () {
            document.removeEventListener('scroll', onScroll);
        });
        // Do an initial check
        onScroll();
    };
    return Animator;
}());

var version = "2";

var isVnode = isVirtualNode;

function isVirtualNode(x) {
    return x && x.type === "VirtualNode" && x.version === version
}

var isWidget_1 = isWidget;

function isWidget(w) {
    return w && w.type === "Widget"
}

var isThunk_1 = isThunk;

function isThunk(t) {
    return t && t.type === "Thunk"
}

var isVhook = isHook;

function isHook(hook) {
    return hook &&
      (typeof hook.hook === "function" && !hook.hasOwnProperty("hook") ||
       typeof hook.unhook === "function" && !hook.hasOwnProperty("unhook"))
}

var vnode = VirtualNode;

var noProperties = {};
var noChildren = [];

function VirtualNode(tagName, properties, children, key, namespace) {
    this.tagName = tagName;
    this.properties = properties || noProperties;
    this.children = children || noChildren;
    this.key = key != null ? String(key) : undefined;
    this.namespace = (typeof namespace === "string") ? namespace : null;

    var count = (children && children.length) || 0;
    var descendants = 0;
    var hasWidgets = false;
    var hasThunks = false;
    var descendantHooks = false;
    var hooks;

    for (var propName in properties) {
        if (properties.hasOwnProperty(propName)) {
            var property = properties[propName];
            if (isVhook(property) && property.unhook) {
                if (!hooks) {
                    hooks = {};
                }

                hooks[propName] = property;
            }
        }
    }

    for (var i = 0; i < count; i++) {
        var child = children[i];
        if (isVnode(child)) {
            descendants += child.count || 0;

            if (!hasWidgets && child.hasWidgets) {
                hasWidgets = true;
            }

            if (!hasThunks && child.hasThunks) {
                hasThunks = true;
            }

            if (!descendantHooks && (child.hooks || child.descendantHooks)) {
                descendantHooks = true;
            }
        } else if (!hasWidgets && isWidget_1(child)) {
            if (typeof child.destroy === "function") {
                hasWidgets = true;
            }
        } else if (!hasThunks && isThunk_1(child)) {
            hasThunks = true;
        }
    }

    this.count = count + descendants;
    this.hasWidgets = hasWidgets;
    this.hasThunks = hasThunks;
    this.hooks = hooks;
    this.descendantHooks = descendantHooks;
}

VirtualNode.prototype.version = version;
VirtualNode.prototype.type = "VirtualNode";

var vtext = VirtualText;

function VirtualText(text) {
    this.text = String(text);
}

VirtualText.prototype.version = version;
VirtualText.prototype.type = "VirtualText";

// CREDIT: adopted from https://raw.githubusercontent.com/marcelklehr/vdom-virtualize/master/index.js
// TODO: move to util - firefox friendly getBoundingClientRect
function getClientRect(element) {
    if (!element) {
        return null;
    }
    var properties = ['top', 'left', 'bottom', 'right', 'height', 'width'];
    var newObject = {};
    var rect = element.getBoundingClientRect();
    for (var _i = 0, properties_1 = properties; _i < properties_1.length; _i++) {
        var key = properties_1[_i];
        newObject[key] = rect[key];
    }
    return newObject;
}
function createVNode(domNode, deep, addBuilderMetadata) {
    if (deep === void 0) { deep = false; }
    if (addBuilderMetadata === void 0) { addBuilderMetadata = true; }
    if (domNode.nodeType === 1)
        return createFromElement(domNode, deep);
    if (domNode.nodeType === 3)
        return createFromTextNode(domNode);
    return;
}
function createFromTextNode(tNode) {
    return new vtext(tNode.nodeValue);
}
function createFromElement(el, deep, addBuilderMetadata) {
    if (deep === void 0) { deep = false; }
    if (addBuilderMetadata === void 0) { addBuilderMetadata = true; }
    var tagName = el.tagName;
    var namespace = el.namespaceURI === 'http://www.w3.org/1999/xhtml' ? null : el.namespaceURI;
    var properties = getElementProperties(el);
    var children = [];
    if (deep) {
        for (var i = 0; i < el.childNodes.length; i++) {
            children.push(createVNode(el.childNodes[i]));
        }
    }
    if (addBuilderMetadata && properties.id && properties.id.indexOf('builder-') === 0) {
        properties.builderMetadata = {
            clientRect: getClientRect(el),
            // TODO: only do this for the element(s) necessary
            selector: uniqueSelector(el),
            // TODO: only do this for the element(s) necessary
            computedStyle: getComputedStyle(el),
        };
    }
    return new vnode(tagName, properties, children, null, namespace);
}
function getElementProperties(el) {
    var obj = {};
    for (var i = 0; i < props.length; i++) {
        var propName = props[i];
        if (!el[propName]) {
            continue;
        }
        if (propName === 'style') {
            var css = {};
            if ('undefined' !== typeof el.style.length) {
                for (var j = 0; j < el.style.length; j++) {
                    var styleProp = el.style[j];
                    css[styleProp] = el.style.getPropertyValue(styleProp); // TODO: add support for "!important" via getPropertyPriority()!
                }
            }
            if (Object.keys(css).length) {
                obj[propName] = css;
            }
            continue;
        }
        // https://msdn.microsoft.com/en-us/library/cc848861%28v=vs.85%29.aspx
        // The img element does not support the HREF content attribute.
        // In addition, the href property is read-only for the img Document Object Model (DOM) object
        if (el.tagName.toLowerCase() === 'img' && propName === 'href') {
            continue;
        }
        if (propName === 'attributes') {
            var atts = Array.prototype.slice.call(el[propName]);
            var hash = {};
            for (var k = 0; k < atts.length; k++) {
                var name_1 = atts[k].name;
                if (obj[name_1] || obj[attrBlacklist[name_1]]) {
                    continue;
                }
                hash[name_1] = el.getAttribute(name_1);
            }
            obj[propName] = hash;
            continue;
        }
        if (propName === 'tabIndex' && el.tabIndex === -1) {
            continue;
        }
        // Special case: contentEditable
        // browser use 'inherit' by default on all nodes, but does not allow setting it to ''
        // diffing virtualize dom will trigger error
        // ref: https://github.com/Matt-Esch/virtual-dom/issues/176
        if (propName === 'contentEditable' && el[propName] === 'inherit') {
            continue;
        }
        if ('object' === typeof el[propName]) {
            continue;
        }
        // default: just copy the property
        obj[propName] = el[propName];
    }
    return obj;
}
/**
 * DOMNode property white list
 * Taken from https://github.com/Raynos/react/blob/dom-property-config/src/browser/ui/dom/DefaultDOMPropertyConfig.js
 */
var props = [
    'accept',
    'accessKey',
    'action',
    'alt',
    'async',
    'autoComplete',
    'autoPlay',
    'cellPadding',
    'cellSpacing',
    'checked',
    'className',
    'colSpan',
    'content',
    'contentEditable',
    'controls',
    'crossOrigin',
    'data',
    'defer',
    'dir',
    'download',
    'draggable',
    'encType',
    'formNoValidate',
    'href',
    'hrefLang',
    'htmlFor',
    'httpEquiv',
    'icon',
    'id',
    'label',
    'lang',
    'list',
    'loop',
    'max',
    'mediaGroup',
    'method',
    'min',
    'multiple',
    'muted',
    'name',
    'noValidate',
    'pattern',
    'placeholder',
    'poster',
    'preload',
    'radioGroup',
    'readOnly',
    'rel',
    'required',
    'rowSpan',
    'sandbox',
    'scope',
    'scrollLeft',
    'scrolling',
    'scrollTop',
    'selected',
    'span',
    'spellCheck',
    'src',
    'srcDoc',
    'srcSet',
    'start',
    'step',
    'style',
    'tabIndex',
    'target',
    'title',
    'type',
    'value',
    // Non-standard Properties
    'autoCapitalize',
    'autoCorrect',
    'property',
    'attributes',
];
var attrBlacklist = {
    class: 'className',
};

// TODO: listen for mutations?
// TODO: listen to resize?
var EventCapturer = /** @class */ (function () {
    function EventCapturer(events) {
        var _this = this;
        this.events = [
            'mousemove',
            'keydown',
            'mousedown',
            'click',
            'mouseup',
            'scroll',
            'mouseover',
            'mouseout',
            'dblclick',
            'resize',
            'contextmenu',
        ];
        this.element = window;
        // TODO: maybe take in message to turn this off and on
        // In edit mode likely want to capture everything
        this.captureEvents = false;
        this.usePassive = false;
        this.includePath = true;
        // FIXME: this causing infinite loop - why?
        this.observeMutations = false;
        this.handleEvent = function (event) {
            // TODO: maybe need to put this on window or document to ensure *all*
            // events are caught before reaching anywhere else
            if (_this.captureEvents && event.type !== 'scroll' && event.type !== 'resize') {
                event.preventDefault();
                event.stopImmediatePropagation();
                event.stopPropagation();
            }
            var message = _this.formatMessage(event);
            _this.postMessage(message);
        };
        if (events) {
            this.events = events;
        }
    }
    EventCapturer.prototype.getElementForEvent = function (eventName) {
        return eventName === 'resize' || eventName === 'scroll' ? window : this.element;
    };
    // TODO: in edit mode make these not passive and make them preventDefault() and stopPropagation()
    EventCapturer.prototype.listen = function () {
        for (var _i = 0, _a = this.events; _i < _a.length; _i++) {
            var event_1 = _a[_i];
            this.getElementForEvent(event_1).addEventListener(event_1, this.handleEvent, {
                capture: true,
                passive: this.usePassive,
            });
        }
        if (this.observeMutations) {
            this.startObservingMutations();
        }
        return this;
    };
    EventCapturer.prototype.removeListeners = function () {
        for (var _i = 0, _a = this.events; _i < _a.length; _i++) {
            var event_2 = _a[_i];
            this.getElementForEvent(event_2).removeEventListener(event_2, this.handleEvent);
        }
        this.stopObservingMutations();
    };
    EventCapturer.prototype.startObservingMutations = function () {
        var _this = this;
        if (!this.observer && typeof MutationObserver !== 'undefined') {
            this.observer = new MutationObserver(function () {
                _this.triggerMutationEvent();
            });
            this.observer.observe(document.documentElement, {
                attributes: true,
                childList: true,
                subtree: true,
                characterData: true,
            });
        }
    };
    EventCapturer.prototype.stopObservingMutations = function () {
        if (this.observer) {
            this.observer.disconnect();
            this.observer = undefined;
        }
    };
    EventCapturer.prototype.getBuilderClientRects = function () {
        var _this = this;
        // TODO: only get those within editing canvas
        // possibly add class "editing" to canvas when editing it (by selector)
        var map = {};
        [].slice.call(document.querySelectorAll('[id*="builder-"]')).forEach(function (el) {
            map[el.id] = _this.getClientRect(el);
        });
        return map;
    };
    EventCapturer.prototype.triggerMutationEvent = function () {
        window.parent.postMessage({
            type: 'builder.documentMutation',
            data: {
                builderElementRectMap: this.fastClone(this.getBuilderClientRects()),
            },
        }, '*');
    };
    EventCapturer.prototype.postMessage = function (message) {
        window.parent.postMessage(message, '*');
    };
    EventCapturer.prototype.formatMessage = function (event) {
        return {
            type: 'builder.documentEvent',
            data: {
                event: this.virtualizeEvent(event),
            },
        };
    };
    EventCapturer.prototype.findParent = 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;
    };
    EventCapturer.prototype.getCssForElement = function (el, seekProperty) {
        if (seekProperty === void 0) { seekProperty = null; }
        var sheets = document.styleSheets;
        var ret = [];
        for (var i in sheets) {
            var sheet = sheets[i];
            var rules = sheet.rules || sheet.cssRules;
            for (var ruleIndex in rules) {
                // TODO: handle other rules like media queries etc? are the style rules nested in those?
                var rule = rules[ruleIndex];
                if (!rule.style) {
                    continue;
                }
                if (seekProperty && !rule.style[seekProperty]) {
                    continue;
                }
                if (rule.selectorText && el.matches(rule.selectorText)) {
                    ret.push(rule);
                }
            }
        }
        return ret;
    };
    EventCapturer.prototype.findPageBlockParent = function (target) {
        var _this = this;
        var innerWidth = window.innerWidth;
        return this.findParent(target, function (el) {
            if (el.tagName === 'BUILDER-CANVAS') {
                return true;
            }
            var rect = _this.getClientRect(el);
            return (
            // At least 50% of page width
            el.clientWidth / innerWidth > 0.5 &&
                // At least 30px tall
                rect.height > 30 &&
                // Has no parent whose bottom lines is within 10px of it's buttom
                // (i.e. get topmost parent that shares the same bottom point)
                !_this.findParent(el, function (parent) {
                    return parent !== el && Math.abs(_this.getClientRect(parent).bottom - rect.bottom) < 10;
                }, false));
        });
    };
    EventCapturer.prototype.findBuilderParent = function (target) {
        return this.findParent(target, function (el) { return !!el.getAttribute('builder-model'); });
    };
    EventCapturer.prototype.findBuilderRenderParent = function (target) {
        return this.findParent(target, function (el) { return !!el.getAttribute('builder-render'); });
    };
    EventCapturer.prototype.findContentEditableParent = function (target) {
        return this.findParent(target, function (el) { return !!el.getAttribute('contenteditable'); });
    };
    EventCapturer.prototype.fastClone = function (obj) {
        return JSON.parse(JSON.stringify(obj));
    };
    // For firefox support, we need to copy manually, ClientRect in firefox
    // is not enumerable or copyable
    EventCapturer.prototype.getClientRect = function (element) {
        if (!element) {
            return null;
        }
        var properties = ['top', 'left', 'bottom', 'right', 'height', 'width'];
        var newObject = {};
        var rect = element.getBoundingClientRect();
        for (var _i = 0, properties_1 = properties; _i < properties_1.length; _i++) {
            var key = properties_1[_i];
            newObject[key] = rect[key];
        }
        return newObject;
    };
    EventCapturer.prototype.getAllParents = function (element) {
        var el = element;
        var els = [];
        while (el) {
            els.push(el);
            el = el.parentElement;
        }
        return els;
    };
    EventCapturer.prototype.virtualizeEvent = function (event) {
        // TODO: only get target on mouseover too?
        var target = event.target;
        var builderTarget = event.type !== 'mouseover' && this.findBuilderParent(target);
        var builderRenderTarget = event.type !== 'mouseover' && this.findBuilderRenderParent(target);
        // This still needed?
        var pageBlockTarget = event.type === 'mouseover' && this.findPageBlockParent(target);
        if (event.type === 'keydown') ;
        var path = this.includePath && event.type !== 'mousemove'
            ? this.getAllParents(event.target).map(function (el) { return createVNode(el); })
            : [];
        // PERF: don't gather all of these things on every mousemove
        // TODO: mousemove
        return this.fastClone(__assign({ 
            // TODO: stop sending computed style except just the one element that needs it
            // TODO: no more fast cloning if can avoid
            path: path, which: event.which, type: event.type, altKey: event.altKey, metaKey: event.metaKey, ctrlKey: event.ctrlKey, shiftKey: event.shiftKey, clientX: event.clientX, clientY: event.clientY }, (event.type !== 'mousemove' && {
            target: createVNode(target),
            targetRect: target.getBoundingClientRect && this.getClientRect(target),
            targetSelector: uniqueSelector(target),
            builderTarget: (builderTarget && createVNode(builderTarget)) || undefined,
            builderTargetSelector: (builderTarget && uniqueSelector(builderTarget)) || undefined,
            builderTargetRect: (builderTarget && this.getClientRect(builderTarget)) || undefined,
            builderRenderTarget: (builderRenderTarget && createVNode(builderRenderTarget)) ||
                undefined,
            builderRenderTargetSelector: (builderRenderTarget && uniqueSelector(builderRenderTarget)) || undefined,
            builderRenderTargetRect: (builderRenderTarget && this.getClientRect(builderRenderTarget)) || undefined,
            // builderElementRectMap: this.getBuilderClientRects(),
            // TODO: only grab these from mouseenter and mouseleave events because they are expensive
            pageBlockTarget: (pageBlockTarget && createVNode(pageBlockTarget)) || undefined,
            pageBlockTargetRect: (pageBlockTarget && this.getClientRect(pageBlockTarget)) || undefined,
            pageBlockTargetSelector: (pageBlockTarget && uniqueSelector(pageBlockTarget)) || undefined,
        })));
    };
    return EventCapturer;
}());

var isSafari = typeof window !== 'undefined' &&
    /^((?!chrome|android).)*safari/i.test(window.navigator.userAgent);
function nextTick(fn) {
    // TODO: should this be setImmediate instead? Forgot if that is micro or macro task
    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 (isSafari || typeof MutationObserver === 'undefined') {
        console.log('isSafari or no mutation observer, using setTimeout');
        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 sessionStorageKey = 'builderSessionId';
// Annoying workaround for module loading
var stableStringify = stringify.default || stringify;
var anyParser = parser;
var isBrowser = typeof window !== 'undefined';
var isIframe = isBrowser && window.top !== window.self;
var fetch = (isBrowser && window.fetch) || require('node-fetch').default;
// Workaround for oddly random module loading issues
var UaParser = typeof anyParser.default === 'function' ? anyParser.default : anyParser;
function BuilderComponent(info) {
    if (info === void 0) { info = {}; }
    return function (component) {
        var spec = __assign({}, info, { class: component });
        if (!spec.name) {
            spec.name = component.name;
        }
        if (!Builder.components.find(function (item) { return item.name === spec.name; })) {
            Builder.components.push(spec);
            // TODO: serialize component name and inputs
            if (isBrowser) {
                window.parent.postMessage({
                    type: 'builder.registerComponent',
                    data: omit(spec, 'class'),
                }, '*');
            }
        }
    };
}
var Builder = /** @class */ (function () {
    function Builder(apiKey) {
        if (apiKey === void 0) { apiKey = null; }
        var _this = this;
        this.apiKey = apiKey;
        this.eventsQueue = [];
        this.throttledClearEventsQueue = throttle(function () {
            _this.processEventsQueue();
        }, 100);
        this.isUsed = false;
        this.sessionId = this.getSessionId();
        this.canTrack$ = new rxjs.BehaviorSubject(!this.browserTrackingDisabled);
        this.editingMode$ = new rxjs.BehaviorSubject(isIframe);
        // TODO: decorator to do this stuff with the get/set (how do with typing too? compiler?)
        this.editingModel$ = new rxjs.BehaviorSubject(null);
        this.userAgent = (typeof navigator === 'object' && navigator.userAgent) || '';
        this.autoTrack = !this.isDevelopmentEnv;
        this.blockContentLoading = '';
        this.observersByModelType = {};
        this.getContentQueue = null;
        this.priorContentQueue = null;
        // get env() {
        //   // builder.env query param, hash, cookie
        //   return 'prod';
        // }
        this.env = 'production';
        // TODO: how prune deprecated tests
        this.testCookiePrefix = 'builder.tests';
        this.cookieQueue = [];
        if (isBrowser) {
            this.bindMessageListeners();
        }
        if (isIframe) {
            this.loadFullStory();
            this.messageFrameLoaded();
        }
        // TODO: on destroy clear subscription
        this.canTrack$.subscribe(function (value) {
            if (value) {
                if (typeof sessionStorage !== 'undefined') {
                    if (!sessionStorage.getItem(sessionStorageKey)) {
                        sessionStorage.setItem(sessionStorageKey, _this.sessionId);
                    }
                }
                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;
                }
            }
        });
    }
    Builder.loadGoogleFont = function (fontName) {
        if (!this.isBrowser) {
            return;
        }
        var link = document.createElement('link');
        link.rel = 'stylesheet';
        link.href = "https://fonts.googleapis.com/css?family=" + fontName.replace(/\s/g, '+');
        document.body.appendChild(link);
    };
    Object.defineProperty(Builder, "editingPage", {
        get: function () {
            return this._editingPage;
        },
        set: function (editingPage) {
            this._editingPage = editingPage;
            if (isBrowser && isIframe) {
                if (editingPage) {
                    document.body.classList.add('builder-editing-page');
                }
                else {
                    document.body.classList.remove('builder-editing-page');
                }
            }
        },
        enumerable: true,
        configurable: true
    });
    // 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 spec = __assign({}, info, { class: component });
            if (!spec.name) {
                spec.name = component.name;
            }
            if (!_this.components.find(function (item) { return item.name === spec.name; })) {
                _this.components.push(spec);
                // TODO: serialize component name and inputs
                if (isBrowser) {
                    window.parent.postMessage({
                        type: 'builder.registerComponent',
                        data: omit(spec, 'class'),
                    }, '*');
                }
            }
        };
    };
    Object.defineProperty(Builder, "Component", {
        get: function () {
            return this.component;
        },
        enumerable: true,
        configurable: true
    });
    Builder.prototype.processEventsQueue = function () {
        if (!this.eventsQueue.length) {
            return;
        }
        var events = this.eventsQueue;
        this.eventsQueue = [];
        // TODO: centralize this
        var host = this.env === 'development' ? 'http://localhost:5000' : 'https://builder.io';
        fetch(host + "/api/v1/track", {
            method: 'POST',
            body: JSON.stringify({ events: events }),
            headers: {
                'content-type': 'application/json',
            },
            mode: 'cors',
        });
    };
    Object.defineProperty(Builder.prototype, "browserTrackingDisabled", {
        get: function () {
            return navigator.doNotTrack === '1';
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Builder.prototype, "canTrack", {
        get: function () {
            return this.canTrack$.value && !this.browserTrackingDisabled;
        },
        set: function (canTrack) {
            if (this.canTrack !== canTrack) {
                this.canTrack$.next(canTrack);
            }
        },
        enumerable: true,
        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: true,
        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: true,
        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) { return Boolean(el.id && el.id.indexOf('builder-') === 0); });
    };
    Builder.prototype.setUserAgent = function (userAgent) {
        this.userAgent = userAgent;
    };
    Builder.prototype.track = function (eventName, properties) {
        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 (isIframe || !isBrowser) {
            return;
        }
        // batch events
        this.eventsQueue.push({
            type: 'impression',
            data: __assign({}, properties, { userAttributes: this.getUserAttributes(), sessionId: this.sessionId }),
        });
        if (this.canTrack) {
            this.throttledClearEventsQueue();
        }
    };
    Builder.prototype.getSessionId = function () {
        var _this = this;
        // TODO: don't set this until gdpr allowed....
        var sessionId = typeof sessionStorage !== 'undefined' && sessionStorage.getItem(sessionStorageKey);
        if (!sessionId) {
            sessionId = btoa(String(Date.now() + Math.random()));
        }
        // Give the app a second to start up and set canTrack to false if needed
        setTimeout(function () {
            if (_this.canTrack && typeof sessionStorage !== 'undefined') {
                sessionStorage.set(sessionStorageKey, sessionId);
            }
        });
        return sessionId;
    };
    Builder.prototype.trackImpression = function (contentId, variationId) {
        if (isIframe || !isBrowser) {
            return;
        }
        // TODO: use this.track method
        this.eventsQueue.push({
            type: 'impression',
            data: {
                contentId: contentId,
                variationId: variationId !== contentId ? variationId : undefined,
                ownerId: this.apiKey,
                userAttributes: this.getUserAttributes(),
                sessionId: this.sessionId,
            },
        });
        this.throttledClearEventsQueue();
    };
    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 !== '')));
        },
        enumerable: true,
        configurable: true
    });
    Builder.prototype.trackInteraction = function (contentId, variationId, alreadyTrackedOne, event) {
        if (alreadyTrackedOne === void 0) { alreadyTrackedOne = false; }
        if (isIframe || !isBrowser) {
            return;
        }
        var target = event && event.target;
        var targetBuilderElement = target && this.findBuilderParent(target);
        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, 4);
                var yRatio = round(yOffset / targetRect.height, 4);
                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, 4);
                var yRatio = round(yOffset / targetRect.height, 4);
                metadata.builderTargetOffset = {
                    x: xRatio,
                    y: yRatio,
                };
            }
        }
        // TODO: use this.track method
        this.eventsQueue.push({
            type: 'click',
            data: {
                contentId: contentId,
                variationId: variationId !== contentId ? variationId : undefined,
                ownerId: this.apiKey,
                unique: !alreadyTrackedOne,
                targetSelector: (target && uniqueSelector(target)) || undefined,
                targetBuilderElement: (targetBuilderElement && targetBuilderElement.id) || undefined,
                userAttributes: this.getUserAttributes(),
                metadata: metadata,
                sessionId: this.sessionId,
            },
        });
        this.throttledClearEventsQueue();
    };
    Builder.prototype.component = function (info) {
        if (info === void 0) { info = {}; }
        return Builder.component(info);
    };
    Builder.prototype.messageFrameLoaded = function () {
        window.parent.postMessage({
            type: 'builder.loaded',
            data: {
                value: true,
            },
        }, '*');
    };
    Builder.prototype.bindMessageListeners = function () {
        var _this = this;
        // TODO: handle race condition of content already loading
        // TODO: move to another file
        if (isBrowser) {
            addEventListener('message', function (event) {
                // FIXME: what was this for?
                // if (!this.isUsed) {
                //   return;
                // }
                var url$$1 = new URL(event.origin);
                var allowedHosts = ['builder.io', 'localhost'];
                if (!includes(allowedHosts, url$$1.hostname)) {
                    return;
                }
                var data = event.data;
                if (data) {
                    switch (data.type) {
                        case 'builder.ping': {
                            window.parent.postMessage({
                                type: 'builder.pong',
                                data: {},
                            }, '*');
                            break;
                        }
                        case 'builder.triggerAnimation': {
                            Builder.animator.triggerAnimation(data.data);
                            break;
                        }
                        case 'builder.contentUpdate':
                            var model = data.data.modelName;
                            var contentData = data.data.data; // hmmm...
                            var observer = _this.observersByModelType[model];
                            if (observer) {
                                observer.next([contentData]);
                            }
                            break;
                        case 'builder.getComponents':
                            // TODO: serialize component name and inputs
                            window.parent.postMessage({
                                type: 'builder.components',
                                data: Builder.components.map(function (item) { return omit(item, 'class'); }),
                            }, '*');
                            break;
                        case 'builder.editingModel':
                            _this.editingModel = data.data.model;
                            break;
                        case 'builder.registerComponent':
                            var componentData = data.data;
                            Builder.components.push(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;
                            Object.assign(Builder.overrideUserAttributes, data.data);
                            _this.flushGetContentQueue(true);
                            // TODO: refetch too
                            break;
                        case 'builder.overrideTestGroup':
                            var _a = data.data, variationId = _a.variationId, contentId = _a.contentId;
                            if (variationId && contentId) {
                                _this.setTestCookie(contentId, variationId);
                                _this.flushGetContentQueue(true);
                            }
                        case 'builder.evaluate': {
                            var text_1 = 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_1);
                            var result = fn.apply(_this, args);
                            if (result && typeof result.then === 'function') {
                                result
                                    .then(function (finalResult) {
                                    window.parent.postMessage({
                                        type: 'builder.evaluateResult',
                                        data: {
                                            result: finalResult,
                                            id: id_1,
                                            text: text_1,
                                        },
                                    }, '*');
                                })
                                    .catch(console.error);
                            }
                            else {
                                window.parent.postMessage({
                                    type: 'builder.evaluateResult',
                                    data: {
                                        result: result,
                                        id: id_1,
                                        text: text_1,
                                    },
                                }, '*');
                            }
                        }
                    }
                }
            });
        }
    };
    Builder.prototype.init = function (apiKey, canTrack) {
        if (canTrack === void 0) { canTrack = true; }
        this.canTrack = canTrack;
        this.apiKey = apiKey;
        return this;
    };
    Builder.prototype.getLocation = function () {
        return (typeof location === 'object' && url.parse(location.href)) || {};
    };
    Builder.prototype.getUserAttributes = function (userAgent) {
        if (userAgent === void 0) { userAgent = this.userAgent; }
        this.isUsed = true;
        if (!userAgent) {
            console.warn('No user agent set! For help on how to set this please contact steve@builder.io');
        }
        var ua = new UaParser(userAgent);
        // FIXME
        var url$$1 = this.getLocation();
        var device = ua.getDevice();
        // TODO: get these from exension as well
        return __assign({ queryString: url$$1.search, urlPath: url$$1.pathname, 
            // Removinf for now because of cache keys
            // referrer: document.referrer,
            // language: navigator.language.split('-')[0],
            device: device.type || 'desktop', operatingSystem: (ua.getOS().name || '').toLowerCase() || undefined, browser: (ua.getBrowser().name || '').toLowerCase() || undefined }, Builder.overrideUserAttributes);
    };
    Builder.prototype.setUserAttributes = function (options) {
        Object.assign(Builder.overrideUserAttributes, options);
    };
    Builder.prototype.loadFullStory = function () {
        // TODO: check that the iframe's parent is buidler.io
        if (!Builder.isIframe) {
            return;
        }
        // If fullstory already loaded return
        if (window['_fs_org']) {
            return;
        }
        var script = document.createElement('script');
        script.innerHTML = "\n      window['_fs_run_in_iframe'] = true\n      window['_fs_debug'] = false;\n      window['_fs_host'] = 'fullstory.com';\n      window['_fs_org'] = 'B9193';\n      window['_fs_namespace'] = 'FS';\n      (function(m,n,e,t,l,o,g,y){\n          if (e in m) {if(m.console && m.console.log) { m.console.log('FullStory namespace conflict. Please set window[\"_fs_namespace\"].');} return;}\n          g=m[e]=function(a,b){g.q?g.q.push([a,b]):g._api(a,b);};g.q=[];\n          o=n.createElement(t);o.async=1;o.src='https://'+_fs_host+'/s/fs.js';\n          y=n.getElementsByTagName(t)[0];y.parentNode.insertBefore(o,y);\n          g.identify=function(i,v){g(l,{uid:i});if(v)g(l,v)};g.setUserVars=function(v){g(l,v)};\n          y=\"rec\";g.shutdown=function(i,v){g(y,!1)};g.restart=function(i,v){g(y,!0)};\n          y=\"consent\";g[y]=function(a){g(y,!arguments.length||a)};\n          g.identifyAccount=function(i,v){o='account';v=v||{};v.acctId=i;g(o,v)};\n          g.clearUserCookie=function(){};\n      })(window,document,window['_fs_namespace'],'script','user');\n    ";
        document.head.appendChild(script);
        // const script2 = document.createElement('script');
        // script2.innerHTML = `
        //     (function(h,o,t,j,a,r){
        //         h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
        //         h._hjSettings={hjid:939017,hjsv:6};
        //         a=o.getElementsByTagName('head')[0];
        //         r=o.createElement('script');r.async=1;
        //         r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
        //         a.appendChild(r);
        //     })(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
        // `;
        // document.head.appendChild(script2);
    };
    // TODO: also take priority or group name/number so can
    // group fast stuff to show right away and slow or lower priority after'
    // how does graphql defer work?
    // TODO: add defaultContent to regular getContent method
    Builder.prototype.queueGetContent = function (modelName, options) {
        var _this = this;
        if (options === void 0) { options = {}; }
        // TODO: possibly in node send this to getContent instead, though that would have the consequence of less cache
        // warming / utilization
        var initialContent = options.initialContent;
        if (!initialContent) {
            if (!this.getContentQueue) {
                this.getContentQueue = [];
                nextTick(function () {
                    _this.flushGetContentQueue();
                });
            }
            this.getContentQueue.push(modelName);
        }
        return new rxjs.Observable(function (observer) {
            _this.observersByModelType[modelName] = observer;
            if (initialContent) {
                nextTick(function () {
                    observer.next(initialContent);
                });
            }
        });
    };
    Builder.prototype.requestUrl = function (url$$1) {
        return fetch(url$$1).then(function (res) { return res.json(); });
    };
    Object.defineProperty(Builder.prototype, "host", {
        get: function () {
            // TODO: how test images....
            return this.env === 'development' ? 'http://localhost:5000' : 'https://builder.io';
        },
        enumerable: true,
        configurable: true
    });
    Builder.prototype.flushGetContentQueue = function (usePastQueue) {
        var _this = this;
        if (usePastQueue === void 0) { usePastQueue = false; }
        if (!this.apiKey) {
            throw new Error('Builder needs to be initialized with an API key!');
        }
        if (!usePastQueue && !this.getContentQueue) {
            return;
        }
        var queryParams = {};
        var pageQueryParams = typeof location !== 'undefined' ? queryString.parse(location.search) : undefined || {};
        // TODO: merge in the attribute from query string ones
        queryParams.userAttributes = stableStringify(this.getUserAttributes());
        var queue = (usePastQueue ? this.priorContentQueue : this.getContentQueue) || [];
        if (!usePastQueue) {
            this.priorContentQueue = queue;
            this.getContentQueue = null;
        }
        // TODO: cachebust if bd.noCache in request, also perhaps if in iframe
        // if (options.cachebust) {
        //   queryParams.t = Date.now().toString();
        // }
        var cachebust = isIframe || pageQueryParams.cachebust;
        if (cachebust) {
            queryParams.cachebuster = Date.now().toString();
        }
        var hasParams = Object.keys(queryParams).length > 0;
        var host = this.getLocation().host === 'localhost:4205' ? 'http://localhost:5000' : 'https://builder.io';
        var modelNames = queue.join(',');
        // FIXME: have a "core" SDK that doesn't implement http,
        // so SDKs like angular can use it's own http method
        var promise = this.requestUrl(host + "/api/v1/content/" + this.apiKey + "/" + modelNames +
            (queryParams && hasParams ? "?" + queryString.stringify(queryParams) : ''))
            .then(function (result) {
            for (var _i = 0, queue_1 = queue; _i < queue_1.length; _i++) {
                var modelName = queue_1[_i];
                if (modelName === _this.blockContentLoading) {
                    continue;
                }
                var observer = _this.observersByModelType[modelName];
                if (!observer) {
                    return;
                }
                var data = result[modelName];
                var sorted = sortBy(data, function (item) { return item.priority; });
                var testModifiedResults = _this.processResultsForTests(sorted);
                observer.next(testModifiedResults);
                // observer.next(sorted);
            }
        })
            .catch(function (err) {
            for (var _i = 0, queue_2 = queue; _i < queue_2.length; _i++) {
                var modelName = queue_2[_i];
                var observer = _this.observersByModelType[modelName];
                if (!observer) {
                    return;
                }
                observer.error(err);
            }
        });
    };
    Builder.prototype.processResultsForTests = function (results) {
        var _this = this;
        var mappedResults = results.map(function (item) {
            if (!item.variations) {
                return item;
            }
            var cookieValue = _this.getTestCookie(item.id);
            var cookieVariation = cookieValue === item.id ? item : item.variations[cookieValue];
            if (cookieVariation) {
                return __assign({}, item, { data: cookieVariation.data, variationId: cookieValue });
            }
            if (item.variations) {
                var n = 0;
                var random = Math.random();
                for (var id in item.variations) {
                    var variation = item.variations[id];
                    var testRatio = variation.testRatio;
                    n += testRatio;
                    if (random < n) {
                        _this.setTestCookie(item.id, variation.id);
                        return __assign({}, item, { data: variation.data, variationId: variation.id });
                    }
                }
            }
            _this.setTestCookie(item.id, item.id);
            return item;
        });
        if (isIframe) {
            window.parent.postMessage({ type: 'builder.contentResults', data: { results: mappedResults } }, '*');
        }
        return mappedResults;
    };
    Builder.prototype.getTestCookie = function (contentId) {
        return this.getCookie(this.testCookiePrefix + "." + contentId);
    };
    Builder.prototype.setTestCookie = function (contentId, variationId) {
        if (!this.canTrack) {
            this.cookieQueue.push([contentId, variationId]);
            return;
        }
        return this.setCookie(this.testCookiePrefix + "." + contentId, variationId, {
            expires: 30,
        });
    };
    Builder.prototype.getCookie = function (name) {
        return Cookies.get(name);
    };
    Builder.prototype.setCookie = function (name, value, options /* Cookies.CookieAttributes */) {
        return Cookies.set(name, value, options);
    };
    // TODO:˝ param overrides
    // TODO: gather user attributes
    // Forward query to a server call so params like no cache or overrides can be extraced and applied
    Builder.prototype.getContent = function (modelName, options) {
        var _this = this;
        if (options === void 0) { options = {}; }
        if (!this.apiKey) {
            throw new Error('Builder needs to be initialized with an API key!');
        }
        var queryParams = (typeof options.queryString === 'string'
            ? queryString.parse(options.queryString)
            : typeof location !== 'undefined'
                ? queryString.parse(location.search)
                : undefined) || {};
        // TODO: merge in the attribute from query string ones
        queryParams.userAttributes = stableStringify(this.getUserAttributes());
        // TODO: cachebust if bd.noCache in request, also perhaps if in iframe
        if (options.cachebust || isIframe) {
            queryParams.cachebuster = Date.now().toString();
        }
        var hasParams = Object.keys(queryParams).length > 0;
        // TODO: check for query params or global variable for overriding content (e.g. what to use for preview)
        // TODO: query params and sorting
        // TODO: with extension set up live connection to this query and send updates
        // TODO: return observable with subscribe function that returns unsubscriber?
        // TODO: (shorter than uuid) org namespace
        // /api/v1/everlane-1/homepage-2
        // "space" names? everlane-qa everlane-prod etc
        // TODO: if in preview (maybe any iframe?) always no cache
        // HACK: have a global option for setting dev
        var host = options.dev || this.getLocation().host === 'localhost:4205'
            ? 'http://localhost:5000'
            : 'https://builder.io';
        return new rxjs.Observable(function (observer) {
            // TODO: will there by use cases of multiple separate requests for same model type?
            _this.observersByModelType[modelName] = observer;
            // FIXME: have a "core" SDK that doesn't implement http,
            // so SDKs like angular can use it's own http method
            var promise = _this.requestUrl(host + "/api/v1/content/" + _this.apiKey + "/" + modelName +
                (queryParams && hasParams ? "?" + queryString.stringify(queryParams) : ''))
                .then(function (data) { return data[modelName]; })
                .then(function (list) { return sortBy(list, function (item) { return item.priority; }); })
                .then(function (result) {
                if (modelName === _this.blockContentLoading) {
                    return;
                }
                var testModifiedResults = _this.processResultsForTests(result);
                observer.next(testModifiedResults);
            })
                .catch(function (err) {
                observer.error(err);
            });
        });
    };
    Builder.eventCapturer = isIframe ? new EventCapturer().listen() : null;
    Builder.animator = new Animator();
    Builder.components = [];
    Builder.nextTick = nextTick;
    Builder._editingPage = false;
    Builder.isIframe = isIframe;
    Builder.isBrowser = isBrowser;
    Builder.overrideUserAttributes = {};
    return Builder;
}());

var builder = new Builder();

exports.Builder = Builder;
exports.BuilderComponent = BuilderComponent;
exports.isBrowser = isBrowser;
exports.builder = builder;