UNPKG

@bloomreach/spa-sdk

Version:
1,384 lines (1,240 loc) 96 kB
import { injectable, inject, ContainerModule, optional, Container } from "inversify"; import "reflect-metadata"; import { Typed } from "emittery"; import render from "dom-serializer"; import { getElementsByTagName, hasAttrib, getAttributeValue } from "domutils"; import { parseDocument } from "htmlparser2"; import cookie from "cookie"; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } function __param(paramIndex, decorator) { return function(target, key) { decorator(target, key, paramIndex); }; } function __metadata(metadataKey, metadataValue) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); } function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P((function(resolve) { resolve(value); })); } return new (P || (P = Promise))((function(resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); })); } typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var Level; (function(Level) { Level["Debug"] = "debug"; Level["Info"] = "info"; Level["Warn"] = "warn"; Level["Error"] = "error"; })(Level || (Level = {})); let Logger = class Logger { constructor() { this.level = Level.Error; this.debug = this.log.bind(this, Level.Debug); this.info = this.log.bind(this, Level.Info); this.warn = this.log.bind(this, Level.Warn); this.error = this.log.bind(this, Level.Error); } log(level, ...message) { const levels = Object.values(Level); if (!levels.includes(level) || levels.indexOf(level) < levels.indexOf(this.level)) { return; } this.write(level, "[SPA]", `[${level.toUpperCase()}]`, ...message); } }; Logger = __decorate([ injectable(), __metadata("design:paramtypes", []) ], Logger); const ConsoleToken = Symbol.for("ConsoleToken"); let ConsoleLogger = class ConsoleLogger extends Logger { constructor(console) { super(); this.console = console; } write(level, ...message) { this.console[level](...message); } }; ConsoleLogger = __decorate([ injectable(), __param(0, inject(ConsoleToken)), __metadata("design:paramtypes", [ Object ]) ], ConsoleLogger); function LoggerModule() { return new ContainerModule((bind => { bind(ConsoleToken).toConstantValue(console); bind(ConsoleLogger).toSelf().inSingletonScope(); bind(Logger).toService(ConsoleLogger); })); } const CmsEventBusService = Symbol("CmsEventBusService"); function EmitterMixin(Super) { return class EmitterMixin extends Super { constructor() { super(...arguments); /** * @todo should be private * @see https://github.com/Microsoft/TypeScript/issues/17293 */ this.emitter = new Typed; this.on = this.emitter.on.bind(this.emitter); this.off = this.emitter.off.bind(this.emitter); /** * @todo should be private * @see https://github.com/Microsoft/TypeScript/issues/17293 */ this.emit = this.emitter.emit.bind(this.emitter); } }; } const RpcClientService = Symbol.for("RpcClientService"); const RpcServerService = Symbol.for("RpcServerService"); const TYPE_EVENT = "brxm:event"; const TYPE_RESPONSE = "brxm:response"; const TYPE_REQUEST = "brxm:request"; const STATE_FULFILLED = "fulfilled"; const STATE_REJECTED = "rejected"; class Dummy {} class Rpc extends(EmitterMixin(Dummy)){ constructor() { super(...arguments); this.calls = new Map; this.callbacks = new Map; } generateId() { let id; do { id = `${Math.random()}`.slice(2); } while (this.calls.has(id)); return id; } call(command, ...payload) { return new Promise(((resolve, reject) => { const id = this.generateId(); this.calls.set(id, [ resolve, reject ]); this.send({ id, command, payload, type: TYPE_REQUEST }); })); } register(command, callback) { this.callbacks.set(command, callback); } trigger(event, payload) { this.send({ event, payload, type: TYPE_EVENT }); } process(message) { switch (message === null || message === void 0 ? void 0 : message.type) { case TYPE_EVENT: this.processEvent(message); break; case TYPE_RESPONSE: this.processResponse(message); break; case TYPE_REQUEST: this.processRequest(message); break; } } processEvent(event) { this.emit(event.event, event.payload); } processResponse(response) { if (!this.calls.has(response.id)) { return; } const [resolve, reject] = this.calls.get(response.id); this.calls.delete(response.id); if (response.state === STATE_REJECTED) { reject(response.result); } resolve(response.result); } processRequest(request) { return __awaiter(this, void 0, void 0, (function*() { const callback = this.callbacks.get(request.command); if (!callback) { return; } try { this.send({ type: TYPE_RESPONSE, id: request.id, state: STATE_FULFILLED, result: yield callback(...request.payload) }); } catch (result) { this.send({ result, type: TYPE_RESPONSE, id: request.id, state: STATE_REJECTED }); } })); } } const CmsService = Symbol.for("CmsService"); const GLOBAL_WINDOW$2 = typeof window === "undefined" ? undefined : window; let CmsImpl = class CmsImpl { constructor(rpcClient, rpcServer, cmsEventBus, logger) { var _a; this.rpcClient = rpcClient; this.rpcServer = rpcServer; this.cmsEventBus = cmsEventBus; this.logger = logger; this.onStateChange = this.onStateChange.bind(this); (_a = this.cmsEventBus) === null || _a === void 0 ? void 0 : _a.on("page.ready", this.onPageReady.bind(this)); this.rpcClient.on("update", this.onUpdate.bind(this)); this.rpcServer.register("inject", this.inject.bind(this)); } initialize({window = GLOBAL_WINDOW$2}) { var _a, _b, _c, _d; if (this.window === window) { return; } this.window = window; if (((_b = (_a = this.window) === null || _a === void 0 ? void 0 : _a.document) === null || _b === void 0 ? void 0 : _b.readyState) !== "loading") { this.onInitialize(); return; } (_d = (_c = this.window) === null || _c === void 0 ? void 0 : _c.document) === null || _d === void 0 ? void 0 : _d.addEventListener("readystatechange", this.onStateChange); } onInitialize() { var _a; (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug("The page is ready to accept incoming messages."); this.rpcServer.trigger("ready", undefined); } onStateChange() { if (this.window.document.readyState === "loading") { return; } this.onInitialize(); this.window.document.removeEventListener("readystatechange", this.onStateChange); } onPageReady() { var _a; (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug("Synchronizing the page."); this.rpcClient.call("sync"); } onUpdate(event) { var _a, _b, _c; (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug("Received update event."); (_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug("Event:", event); (_c = this.cmsEventBus) === null || _c === void 0 ? void 0 : _c.emit("cms.update", event); } inject(resource) { var _a, _b, _c; if (!((_a = this.window) === null || _a === void 0 ? void 0 : _a.document)) { return Promise.reject(new Error("SPA document is not ready.")); } (_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug("Received request to inject a resource."); (_c = this.logger) === null || _c === void 0 ? void 0 : _c.debug("Resource:", resource); return new Promise(((resolve, reject) => { const script = this.window.document.createElement("script"); script.type = "text/javascript"; script.src = resource; script.addEventListener("load", (() => resolve())); script.addEventListener("error", (() => reject(new Error(`Failed to load resource '${resource}'.`)))); this.window.document.body.appendChild(script); })); } }; CmsImpl = __decorate([ injectable(), __param(0, inject(RpcClientService)), __param(1, inject(RpcServerService)), __param(2, inject(CmsEventBusService)), __param(2, optional()), __param(3, inject(Logger)), __param(3, optional()), __metadata("design:paramtypes", [ Object, Object, Object, Logger ]) ], CmsImpl); const GLOBAL_WINDOW$1 = typeof window === "undefined" ? undefined : window; let Cms14Impl = class Cms14Impl { constructor(eventBus, logger) { this.eventBus = eventBus; this.logger = logger; this.postponed = []; } flush() { return __awaiter(this, void 0, void 0, (function*() { this.postponed.splice(0).forEach((task => task())); })); } postpone(task) { return (...args) => { if (this.api) { return task.apply(this, args); } this.postponed.push(task.bind(this, ...args)); return undefined; }; } initialize({window = GLOBAL_WINDOW$1}) { var _a, _b; if (this.api || !window || window.SPA) { return; } (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug("Initiating a handshake with the Experience Manager."); (_b = this.eventBus) === null || _b === void 0 ? void 0 : _b.on("page.ready", this.postpone(this.sync)); window.SPA = { init: this.onInit.bind(this), renderComponent: this.onRenderComponent.bind(this) }; } onInit(api) { var _a; (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug("Completed the handshake with the Experience Manager."); this.api = api; this.flush(); } onRenderComponent(id, properties) { var _a, _b, _c, _d; (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug("Received component rendering request."); (_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug("Component:", id); (_c = this.logger) === null || _c === void 0 ? void 0 : _c.debug("Properties", properties); (_d = this.eventBus) === null || _d === void 0 ? void 0 : _d.emit("cms.update", { id, properties }); } sync() { var _a; (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug("Synchronizing the page."); this.api.sync(); } }; Cms14Impl = __decorate([ injectable(), __param(0, inject(CmsEventBusService)), __param(0, optional()), __param(1, inject(Logger)), __param(1, optional()), __metadata("design:paramtypes", [ Object, Logger ]) ], Cms14Impl); function parseUrl(url) { const DUMMY_BASE_URL = "http://example.com"; const parsedUrl = new URL(url, DUMMY_BASE_URL); const {hash, search, searchParams} = parsedUrl; let {origin, pathname} = parsedUrl; origin = origin !== DUMMY_BASE_URL ? origin : ""; if (url.startsWith("//")) { origin = origin.replace(parsedUrl.protocol, ""); } if (url.startsWith(origin) && !url.replace(origin, "").startsWith("/") && pathname.startsWith("/")) { pathname = pathname.substring(1); } return { hash, origin, pathname, search, searchParams, path: `${pathname}${search}${hash}` }; } function buildUrl(url) { var _a, _b, _c, _d, _e, _f, _g; const searchParams = (_b = (_a = url.searchParams) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : ""; const search = (_c = url.search) !== null && _c !== void 0 ? _c : `${searchParams && `?${searchParams}`}`; const path = (_d = url.path) !== null && _d !== void 0 ? _d : `${(_e = url.pathname) !== null && _e !== void 0 ? _e : ""}${search}${(_f = url.hash) !== null && _f !== void 0 ? _f : ""}`; return `${(_g = url.origin) !== null && _g !== void 0 ? _g : ""}${path}`; } function mergeSearchParams(params, ...rest) { const result = new URLSearchParams(params); rest.forEach((restParams => restParams.forEach(((value, key) => result.set(key, value))))); return result; } function appendSearchParams(url, params) { const {hash, origin, pathname, searchParams} = parseUrl(url); return buildUrl({ hash, origin, pathname, searchParams: mergeSearchParams(searchParams, params) }); } /** * Extracts query parameters from URL and returns URL object that contains URL path and extracted parameters * * @param url The URL of the page. * @param params Parameters to extract. */ function extractSearchParams(url, params) { const {hash, origin, pathname, searchParams} = parseUrl(url); const extracted = new URLSearchParams; params.forEach((param => { if (searchParams.has(param)) { extracted.set(param, searchParams.get(param)); searchParams.delete(param); } })); return { searchParams: extracted, url: buildUrl({ hash, origin, pathname, searchParams }) }; } function isAbsoluteUrl(url) { const {origin, pathname} = parseUrl(url); return !!origin || pathname.startsWith("/"); } function isMatchedOrigin(origin, baseOrigin) { const [schema, host = ""] = origin.split("//", 2); const [baseSchema, baseHost = ""] = baseOrigin.split("//", 2); return !baseOrigin || !origin || (!schema || !baseSchema || schema === baseSchema) && baseHost === host; } function isMatchedPathname(pathname, basePathname) { return !basePathname || pathname.startsWith(basePathname); } function isMatchedQuery(search, baseSearch) { let match = true; baseSearch.forEach(((value, key) => { match = match && (!value && search.has(key) || search.getAll(key).includes(value)); })); return match; } function isMatched(link, base = "") { const linkUrl = parseUrl(link); const baseUrl = parseUrl(base); return isMatchedOrigin(linkUrl.origin, baseUrl.origin) && isMatchedPathname(linkUrl.pathname, baseUrl.pathname) && isMatchedQuery(linkUrl.searchParams, baseUrl.searchParams); } function resolveUrl(url, base) { const baseUrl = parseUrl(base); const sourceUrl = parseUrl(url); const pathname = sourceUrl.pathname.startsWith("/") ? sourceUrl.pathname : `${baseUrl.pathname}${baseUrl.pathname.endsWith("/") || !sourceUrl.pathname ? "" : "/"}${sourceUrl.pathname}`; return buildUrl({ pathname, hash: sourceUrl.hash || baseUrl.hash, origin: sourceUrl.origin || baseUrl.origin, searchParams: mergeSearchParams(baseUrl.searchParams, sourceUrl.searchParams) }); } const UrlBuilderOptionsToken = Symbol.for("UrlBuilderOptionsToken"); const UrlBuilderService = Symbol.for("UrlBuilderService"); let UrlBuilderImpl$1 = class UrlBuilderImpl { constructor(options) { var _a, _b; this.endpoint = parseUrl((_a = options.endpoint) !== null && _a !== void 0 ? _a : ""); this.baseUrl = parseUrl((_b = options.baseUrl) !== null && _b !== void 0 ? _b : ""); } getApiUrl(link) { const {pathname, searchParams} = parseUrl(link); if (this.baseUrl.pathname && !pathname.startsWith(this.baseUrl.pathname)) { throw new Error(`The path "${pathname}" does not start with the base path "${this.baseUrl.pathname}".`); } const route = pathname.substring(this.baseUrl.pathname.length); return buildUrl({ origin: this.endpoint.origin, pathname: `${this.endpoint.pathname}${route}`, searchParams: mergeSearchParams(searchParams, this.endpoint.searchParams) }); } getSpaUrl(link) { const {hash, pathname, searchParams} = parseUrl(link); const route = !pathname.startsWith("/") && !this.baseUrl.pathname ? `/${pathname}` : pathname; return buildUrl({ origin: this.baseUrl.origin, pathname: `${this.baseUrl.pathname}${route}`, searchParams: mergeSearchParams(searchParams, this.baseUrl.searchParams), hash: hash || this.baseUrl.hash }); } }; UrlBuilderImpl$1 = __decorate([ injectable(), __param(0, inject(UrlBuilderOptionsToken)), __metadata("design:paramtypes", [ Object ]) ], UrlBuilderImpl$1); function UrlModule$1() { return new ContainerModule((bind => { bind(UrlBuilderService).to(UrlBuilderImpl$1).inSingletonScope(); })); } const DEFAULT_API_BASE_URL = "/resourceapi"; const DEFAULT_SPA_BASE_URL = ""; let UrlBuilderImpl = class UrlBuilderImpl { constructor(options) { var _a, _b, _c, _d; this.apiBaseUrl = parseUrl((_a = options.apiBaseUrl) !== null && _a !== void 0 ? _a : `${(_b = options.cmsBaseUrl) !== null && _b !== void 0 ? _b : ""}${DEFAULT_API_BASE_URL}`); this.cmsBaseUrl = parseUrl((_c = options.cmsBaseUrl) !== null && _c !== void 0 ? _c : ""); this.spaBaseUrl = parseUrl((_d = options.spaBaseUrl) !== null && _d !== void 0 ? _d : DEFAULT_SPA_BASE_URL); } getApiUrl(link) { const {pathname, searchParams} = parseUrl(link); if (this.apiBaseUrl.pathname && pathname.startsWith(this.apiBaseUrl.pathname)) { return buildUrl({ pathname, origin: this.apiBaseUrl.origin, searchParams: mergeSearchParams(this.apiBaseUrl.searchParams, searchParams) }); } if (this.spaBaseUrl.pathname && !pathname.startsWith(this.spaBaseUrl.pathname)) { throw new Error(`The path "${pathname}" does not start with the base path "${this.spaBaseUrl.pathname}".`); } const route = pathname.substring(this.spaBaseUrl.pathname.length); return buildUrl({ origin: this.apiBaseUrl.origin, pathname: `${this.apiBaseUrl.pathname}${route}`, searchParams: mergeSearchParams(searchParams, this.apiBaseUrl.searchParams) }); } getSpaUrl(link) { const {hash, pathname, searchParams} = parseUrl(link); let route = pathname.startsWith(this.cmsBaseUrl.pathname) ? pathname.substring(this.cmsBaseUrl.pathname.length) : pathname; if (!route.startsWith("/") && !this.spaBaseUrl.pathname) { route = `/${route}`; } return buildUrl({ origin: this.spaBaseUrl.origin, pathname: `${this.spaBaseUrl.pathname}${route}`, searchParams: mergeSearchParams(searchParams, this.spaBaseUrl.searchParams), hash: hash || this.spaBaseUrl.hash }); } }; UrlBuilderImpl = __decorate([ injectable(), __param(0, inject(UrlBuilderOptionsToken)), __metadata("design:paramtypes", [ Object ]) ], UrlBuilderImpl); function UrlModule() { return new ContainerModule((bind => { bind(UrlBuilderService).to(UrlBuilderImpl).inSingletonScope(); })); } const PostMessageService = Symbol.for("PostMessageService"); const GLOBAL_WINDOW = typeof window === "undefined" ? undefined : window; let PostMessage = class PostMessage extends Rpc { constructor(logger) { super(); this.logger = logger; this.onMessage = this.onMessage.bind(this); } initialize({origin, window = GLOBAL_WINDOW}) { var _a, _b; (_a = this.window) === null || _a === void 0 ? void 0 : _a.removeEventListener("message", this.onMessage, false); this.origin = origin; this.window = window; (_b = this.window) === null || _b === void 0 ? void 0 : _b.addEventListener("message", this.onMessage, false); } send(message) { var _a, _b, _c; if (!this.origin) { return; } (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug("[OUTGOING]", `[${this.origin}]`, message); (_c = (_b = this.window) === null || _b === void 0 ? void 0 : _b.parent) === null || _c === void 0 ? void 0 : _c.postMessage(message, this.origin); } onMessage(event) { var _a, _b; if (!event.data || !isMatched(event.origin, this.origin === "*" ? "" : this.origin)) { return; } if ((_a = event.data) === null || _a === void 0 ? void 0 : _a.type) { (_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug("[INCOMING]", `[${event.origin}]`, event.data); } this.process(event.data); } }; PostMessage = __decorate([ injectable(), __param(0, inject(Logger)), __param(0, optional()), __metadata("design:paramtypes", [ Logger ]) ], PostMessage); function CmsModule() { return new ContainerModule((bind => { bind(CmsEventBusService).toDynamicValue((() => new Typed)).inSingletonScope().when((() => typeof window !== "undefined")); bind(PostMessageService).to(PostMessage).inSingletonScope(); bind(RpcClientService).toService(PostMessageService); bind(RpcServerService).toService(PostMessageService); bind(CmsService).to(CmsImpl).inSingletonScope().whenTargetIsDefault(); bind(CmsService).to(Cms14Impl).inSingletonScope().whenTargetNamed("cms14"); })); } function isConfigurationWithProxy(value) { var _a, _b; return !!(((_a = value === null || value === void 0 ? void 0 : value.options) === null || _a === void 0 ? void 0 : _a.live) && ((_b = value === null || value === void 0 ? void 0 : value.options) === null || _b === void 0 ? void 0 : _b.preview)); } function isConfigurationWithJwt09(value) { return !!(value === null || value === void 0 ? void 0 : value.cmsBaseUrl); } /** * Link to a page outside the current application. */ const TYPE_LINK_EXTERNAL = "external"; /** * Link to a page inside the current application. */ const TYPE_LINK_INTERNAL = "internal"; /** * Link to a CMS resource. */ const TYPE_LINK_RESOURCE = "resource"; /** * Unresolved link. */ const TYPE_LINK_UNKNOWN = "unknown"; /** * Checks whether a value is a link. * @param value The value to check. */ function isLink(value) { return !!value && (Object.prototype.hasOwnProperty.call(value, "href") || Object.prototype.hasOwnProperty.call(value, "type") && [ TYPE_LINK_EXTERNAL, TYPE_LINK_INTERNAL, TYPE_LINK_RESOURCE, TYPE_LINK_UNKNOWN ].includes(value.type)); } class SimpleFactory { constructor() { this.mapping = new Map; } /** * Registers a builder for the specified type. * @param type The entity type. * @param builder The entity builder. */ register(type, builder) { this.mapping.set(type, builder); return this; } } let LinkFactory = class LinkFactory extends SimpleFactory { create(link) { if (isLink(link)) { return this.createLink(link); } return this.createPath(link); } createLink(link) { if (!link.type || typeof link.href === "undefined" || !this.mapping.has(link.type)) { return link.href; } const builder = this.mapping.get(link.type); return builder(link.href); } createPath(path) { return this.createLink({ href: path, type: TYPE_LINK_INTERNAL }); } }; LinkFactory = __decorate([ injectable() ], LinkFactory); const MetaCollectionFactory = Symbol.for("MetaCollectionFactory"); const ComponentChildrenToken = Symbol.for("ComponentChildrenToken"); const ComponentModelToken = Symbol.for("ComponentModelToken"); /** * Generic component type. */ const TYPE_COMPONENT$1 = "component"; /** * Container type. */ const TYPE_COMPONENT_CONTAINER$1 = "container"; /** * Container item type. */ const TYPE_COMPONENT_CONTAINER_ITEM$1 = "container-item"; /** * Container item content type. */ const TYPE_COMPONENT_CONTAINER_ITEM_CONTENT = "componentcontent"; let ComponentImpl$1 = class ComponentImpl { constructor(model, children, linkFactory, metaFactory) { this.model = model; this.children = children; this.linkFactory = linkFactory; this.meta = metaFactory(this.model.meta); } getId() { return this.model.id; } getMeta() { return this.meta; } getModels() { return this.model.models || {}; } getUrl() { return this.linkFactory.create(this.model.links.self); } getName() { return this.model.name || ""; } getParameters() { var _a; return (_a = this.model.meta.params) !== null && _a !== void 0 ? _a : {}; } getProperties() { return this.getParameters(); } getChildren() { return this.children; } getComponent(...componentNames) { let component = this; while (componentNames.length && component) { const name = componentNames.shift(); component = component.getChildren().find((childComponent => childComponent.getName() === name)); } return component; } getComponentById(id) { const queue = [ this ]; while (queue.length) { const component = queue.shift(); if (component.getId() === id) { return component; } queue.push(...component.getChildren()); } return undefined; } }; ComponentImpl$1 = __decorate([ injectable(), __param(0, inject(ComponentModelToken)), __param(1, inject(ComponentChildrenToken)), __param(2, inject(LinkFactory)), __param(3, inject(MetaCollectionFactory)), __metadata("design:paramtypes", [ Object, Array, LinkFactory, Function ]) ], ComponentImpl$1); /** * Checks whether a value is a page component. * @param value The value to check. */ function isComponent$2(value) { return value instanceof ComponentImpl$1; } /** * Generic component type. */ const TYPE_COMPONENT = "COMPONENT"; /** * Container item type. */ const TYPE_COMPONENT_CONTAINER_ITEM = "CONTAINER_ITEM_COMPONENT"; /** * Container type. */ const TYPE_COMPONENT_CONTAINER = "CONTAINER_COMPONENT"; let ComponentImpl = class ComponentImpl { constructor(model, children, metaFactory, urlBuilder) { this.model = model; this.children = children; this.urlBuilder = urlBuilder; this.meta = metaFactory(this.model._meta); } getId() { return this.model.id; } getMeta() { return this.meta; } getModels() { return this.model.models || {}; } getUrl() { return this.urlBuilder.getApiUrl(this.model._links.componentRendering.href); } getName() { return this.model.name || ""; } getParameters() { var _a; return (_a = this.model._meta.params) !== null && _a !== void 0 ? _a : {}; } getProperties() { return this.getParameters(); } getChildren() { return this.children; } getComponent(...componentNames) { let component = this; while (componentNames.length && component) { const name = componentNames.shift(); component = component.getChildren().find((childComponent => childComponent.getName() === name)); } return component; } getComponentById(id) { const queue = [ this ]; while (queue.length) { const component = queue.shift(); if (component.getId() === id) { return component; } queue.push(...component.getChildren()); } return undefined; } }; ComponentImpl = __decorate([ injectable(), __param(0, inject(ComponentModelToken)), __param(1, inject(ComponentChildrenToken)), __param(2, inject(MetaCollectionFactory)), __param(3, inject(UrlBuilderService)), __metadata("design:paramtypes", [ Object, Array, Function, Object ]) ], ComponentImpl); /** * Checks whether a value is a page component. * @param value The value to check. */ function isComponent$1(value) { return value instanceof ComponentImpl; } /** * A blocked container with blocked items. */ const TYPE_CONTAINER_BOX = "hst.vbox"; /** * An unordered list container. */ const TYPE_CONTAINER_UNORDERED_LIST = "hst.unorderedlist"; /** * An ordered list container. */ const TYPE_CONTAINER_ORDERED_LIST = "hst.orderedlist"; /** * A blocked container with inline items. */ const TYPE_CONTAINER_INLINE = "hst.span"; /** * A container without surrounding markup. */ const TYPE_CONTAINER_NO_MARKUP = "hst.nomarkup"; let ContainerImpl$1 = class ContainerImpl extends ComponentImpl$1 { getChildren() { return this.children; } getType() { var _a; return (_a = this.model.xtype) === null || _a === void 0 ? void 0 : _a.toLowerCase(); } }; ContainerImpl$1 = __decorate([ injectable() ], ContainerImpl$1); /** * Checks whether a value is a page container. * @param value The value to check. */ function isContainer$2(value) { return value instanceof ContainerImpl$1; } let ContainerImpl = class ContainerImpl extends ComponentImpl { getChildren() { return this.children; } getType() { var _a; return (_a = this.model.xtype) === null || _a === void 0 ? void 0 : _a.toLowerCase(); } }; ContainerImpl = __decorate([ injectable() ], ContainerImpl); /** * Checks whether a value is a page container. * @param value The value to check. */ function isContainer$1(value) { return value instanceof ContainerImpl; } const PageEventBusService = Symbol("PageEventBusService"); /** * Checks whether a value is a reference. * @param value The value to check. */ function isReference(value) { return !!(value === null || value === void 0 ? void 0 : value.$ref); } function resolve(object, reference) { return reference.$ref.split("/").reduce(((value, key) => key ? value === null || value === void 0 ? void 0 : value[key] : object), object); } /** * A container item without mapping. */ const TYPE_CONTAINER_ITEM_UNDEFINED = Symbol.for("ContainerItemUndefined"); /** * Returns the content of this component. * * @param component The component that references the content * @param page The page that contains the content */ function getContainerItemContent(component, page) { const contentRef = component.getContentReference(); if (!contentRef) { return null; } const componentContent = page.getContent(contentRef); if (!componentContent) { return null; } if ((componentContent === null || componentContent === void 0 ? void 0 : componentContent.type) !== TYPE_COMPONENT_CONTAINER_ITEM_CONTENT) { return null; } return componentContent.data; } let ContainerItemImpl$1 = class ContainerItemImpl extends(EmitterMixin(ComponentImpl$1)){ constructor(model, linkFactory, metaFactory, eventBus, logger) { super(model, [], linkFactory, metaFactory); this.model = model; this.metaFactory = metaFactory; this.logger = logger; eventBus === null || eventBus === void 0 ? void 0 : eventBus.on("page.update", this.onPageUpdate.bind(this)); } onPageUpdate(event) { var _a, _b; const page = event.page; const model = resolve(page, page.root); if ((model === null || model === void 0 ? void 0 : model.id) !== this.getId()) { return; } (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug("Received container item update event."); (_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug("Event:", event); this.model = model; this.meta = this.metaFactory(model.meta); this.emit("update", {}); } getLabel() { return this.model.label; } getType() { var _a; return (_a = this.model.ctype) !== null && _a !== void 0 ? _a : this.model.label; } isHidden() { return !!this.model.meta.hidden; } getParameters() { var _a; return (_a = this.model.meta.paramsInfo) !== null && _a !== void 0 ? _a : {}; } getContent(page) { return getContainerItemContent(this, page); } getContentReference() { return this.model.content; } }; ContainerItemImpl$1 = __decorate([ injectable(), __param(0, inject(ComponentModelToken)), __param(1, inject(LinkFactory)), __param(2, inject(MetaCollectionFactory)), __param(3, inject(PageEventBusService)), __param(3, optional()), __param(4, inject(Logger)), __param(4, optional()), __metadata("design:paramtypes", [ Object, LinkFactory, Function, Object, Logger ]) ], ContainerItemImpl$1); /** * Checks whether a value is a page container item. * @param value The value to check. */ function isContainerItem$2(value) { return value instanceof ContainerItemImpl$1; } let ContainerItemImpl = class ContainerItemImpl extends(EmitterMixin(ComponentImpl)){ constructor(model, metaFactory, urlBuilder, eventBus, logger) { super(model, [], metaFactory, urlBuilder); this.model = model; this.metaFactory = metaFactory; this.logger = logger; eventBus === null || eventBus === void 0 ? void 0 : eventBus.on("page.update", this.onPageUpdate.bind(this)); } onPageUpdate(event) { var _a, _b; const {page: model} = event.page; if (model.id !== this.getId()) { return; } (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug("Received container item update event."); (_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug("Event:", event); this.model = model; this.meta = this.metaFactory(model._meta); this.emit("update", {}); } getLabel() { return this.model.label; } getType() { var _a; return (_a = this.model.ctype) !== null && _a !== void 0 ? _a : this.model.label; } isHidden() { return !!this.model._meta.hidden; } getParameters() { var _a; return (_a = this.model._meta.paramsInfo) !== null && _a !== void 0 ? _a : {}; } getContent() { var _a; (_a = this.logger) === null || _a === void 0 ? void 0 : _a.warn('The method "getContent" is not supported in PMA 0.9 and always returns "null".'); return null; } getContentReference() { var _a; (_a = this.logger) === null || _a === void 0 ? void 0 : _a.warn('The method "getContentReference" is not supported in PMA 0.9 and always returns "undefined".'); return undefined; } }; ContainerItemImpl = __decorate([ injectable(), __param(0, inject(ComponentModelToken)), __param(1, inject(MetaCollectionFactory)), __param(2, inject(UrlBuilderService)), __param(3, inject(PageEventBusService)), __param(3, optional()), __param(4, inject(Logger)), __param(4, optional()), __metadata("design:paramtypes", [ Object, Function, Object, Object, Logger ]) ], ContainerItemImpl); /** * Checks whether a value is a page container item. * @param value The value to check. */ function isContainerItem$1(value) { return value instanceof ContainerItemImpl; } const TYPE_META_COMMENT = "comment"; /** * Meta-data following before a page component. */ const META_POSITION_BEGIN = "begin"; /** * Meta-data following after a page component. */ const META_POSITION_END = "end"; class MetaImpl { constructor(model, position) { this.model = model; this.position = position; } getData() { return this.model.data; } getPosition() { return this.position; } } /** * Checks whether a value is a meta-data object. * @param value The value to check. */ function isMeta(value) { return value instanceof MetaImpl; } const HTML_COMMENT = /^<!--(.*)-->$/; /** * Meta information stored in HST-comments. */ class MetaCommentImpl extends MetaImpl { getData() { const data = super.getData(); const [, payload = data] = data.match(HTML_COMMENT) || []; return payload; } } /** * Checks whether a value is a meta-data comment. * @param value The value to check. */ function isMetaComment(value) { return value instanceof MetaCommentImpl; } /** * The factory to produce meta-data collection from the page model meta-data. */ let MetaFactory = class MetaFactory extends SimpleFactory { create(meta, position) { const builder = this.mapping.get(meta.type); if (!builder) { throw new Error(`Unsupported meta type: '${meta.type}'.`); } return builder(meta, position); } }; MetaFactory = __decorate([ injectable() ], MetaFactory); var MetaCollectionImpl_1; const MetaCollectionModelToken = Symbol.for("MetaCollectionModelToken"); let MetaCollectionImpl = MetaCollectionImpl_1 = class MetaCollectionImpl extends Array { constructor(model, metaFactory) { super(...(model.beginNodeSpan || []).map((beginModel => metaFactory.create(beginModel, META_POSITION_BEGIN))), ...(model.endNodeSpan || []).map((endModel => metaFactory.create(endModel, META_POSITION_END)))); this.comments = []; const prototype = Object.create(MetaCollectionImpl_1.prototype); prototype.constructor = Array.prototype.constructor; Object.setPrototypeOf(this, prototype); Object.freeze(this); } clear(comments = [ ...this.comments ]) { comments.forEach((comment => { comment.remove(); const index = this.comments.indexOf(comment); if (index > -1) { this.comments.splice(index, 1); } })); } render(head, tail) { var _a; const document = (_a = head.ownerDocument) !== null && _a !== void 0 ? _a : tail.ownerDocument; const comments = document ? [ ...this.filter(isMetaComment).filter((meta => meta.getPosition() === META_POSITION_BEGIN)).map((meta => document.createComment(meta.getData()))).map((comment => { var _a; (_a = head.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(comment, head); return comment; })), ...this.filter(isMetaComment).filter((meta => meta.getPosition() === META_POSITION_END)).reverse().map((meta => document.createComment(meta.getData()))).map((comment => { var _a, _b; if (tail.nextSibling) { (_a = tail.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(comment, tail.nextSibling); } else { (_b = tail.parentNode) === null || _b === void 0 ? void 0 : _b.appendChild(comment); } return comment; })) ] : []; this.comments.push(...comments); return this.clear.bind(this, comments); } }; MetaCollectionImpl = MetaCollectionImpl_1 = __decorate([ injectable(), __param(0, inject(MetaCollectionModelToken)), __param(1, inject(MetaFactory)), __metadata("design:paramtypes", [ Object, MetaFactory ]) ], MetaCollectionImpl); /** * Checks whether a value is a meta-data collection. * @param value The value to check. */ function isMetaCollection(value) { return value instanceof MetaCollectionImpl; } let ButtonFactory = class ButtonFactory extends SimpleFactory { constructor(metaCollectionFactory) { super(); this.metaCollectionFactory = metaCollectionFactory; } create(type, ...params) { if (!this.mapping.has(type)) { throw new Error(`Unsupported button type: '${type}'.`); } const meta = this.mapping.get(type)(...params); return isMetaCollection(meta) ? meta : this.metaCollectionFactory(meta); } }; ButtonFactory = __decorate([ injectable(), __param(0, inject(MetaCollectionFactory)), __metadata("design:paramtypes", [ Function ]) ], ButtonFactory); /** * A component factory producing components based on a type. */ let ComponentFactory$1 = class ComponentFactory extends SimpleFactory { /** * Produces a component based on the page model. * @param page The page model. */ create(page) { var _a, _b; const heap = [ page.root ]; const pool = new Map; for (let i = 0; i < heap.length; i++) { heap.push(...(_b = (_a = resolve(page, heap[i])) === null || _a === void 0 ? void 0 : _a.children) !== null && _b !== void 0 ? _b : []); } return heap.reverse().reduce(((previous, reference) => { var _a, _b; const model = resolve(page, reference); const children = (_b = (_a = model === null || model === void 0 ? void 0 : model.children) === null || _a === void 0 ? void 0 : _a.map((child => pool.get(resolve(page, child))))) !== null && _b !== void 0 ? _b : []; const component = this.buildComponent(model, children); pool.set(model, component); return component; }), undefined); } buildComponent(model, children) { const builder = this.mapping.get(model.type); if (!builder) { throw new Error(`Unsupported component type: '${model.type}'.`); } return builder(model, children); } }; ComponentFactory$1 = __decorate([ injectable() ], ComponentFactory$1); let ContentFactory$1 = class ContentFactory extends SimpleFactory { create(model) { if (!this.mapping.has(model.type)) { return model; } return this.mapping.get(model.type)(model); } }; ContentFactory$1 = __decorate([ injectable() ], ContentFactory$1); const LinkRewriterService = Symbol.for("LinkRewriterService"); let LinkRewriterImpl = class LinkRewriterImpl { constructor(linkFactory) { this.linkFactory = linkFactory; } rewrite(content, type = "text/html") { const document = parseDocument(content, { xmlMode: type !== "text/html" }); this.rewriteAnchors(document); this.rewriteImages(document); return render(document, { selfClosingTags: true }); } rewriteAnchors(document) { Array.from(getElementsByTagName("a", document)).filter((element => hasAttrib(element, "href") && hasAttrib(element, "data-type"))).forEach((element => { const url = this.linkFactory.create({ href: getAttributeValue(element, "href"), type: getAttributeValue(element, "data-type") }); if (url) { element.attribs.href = url; } return element; })); } rewriteImages(document) { Array.from(getElementsByTagName("img", document)).filter((element => hasAttrib(element, "src"))).forEach((element => { const url = this.linkFactory.create({ href: getAttributeValue(element, "src"), type: TYPE_LINK_RESOURCE }); if (url) { element.attribs.src = url; } })); } }; LinkRewriterImpl = __decorate([ injectable(), __param(0, inject(LinkFactory)), __metadata("design:paramtypes", [ LinkFactory ]) ], LinkRewriterImpl); const PageModelToken = Symbol.for("PageModelToken"); let PageImpl$1 = class PageImpl { constructor(model, buttonFactory, componentFactory, contentFactory, linkFactory, linkRewriter, metaFactory, cmsEventBus, pageEventBus, logger) { this.model = model; this.buttonFactory = buttonFactory; this.contentFactory = contentFactory; this.linkFactory = linkFactory; this.linkRewriter = linkRewriter; this.metaFactory = metaFactory; this.cmsEventBus = cmsEventBus; this.logger = logger; this.content = new WeakMap; pageEventBus === null || pageEventBus === void 0 ? void 0 : pageEventBus.on("page.update", this.onPageUpdate.bind(this)); this.root = componentFactory.create(model); } onPageUpdate(event) { Object.assign(this.model.page, event.page.page); } getButton(type, ...params) { return this.buttonFactory.create(type, ...params); } getChannelParameters() { return this.model.channel.info.props; } getComponent(...componentNames) { var _a; return (_a = this.root) === null || _a === void 0 ? void 0 : _a.getComponent(...componentNames); } getContent(reference) { const model = resolve(this.model, isReference(reference) ? reference : { $ref: `/page/${reference}` }); if (!model) { return undefined; } if (!this.content.has(model)) { this.content.set(model, this.contentFactory.create(model)); } return this.content.get(model); } getDocument() { return this.model.document && this.getContent(this.model.document); } getLocale() { return this.model.meta.locale || "en_US"; } getMeta(meta) { return this.metaFactory(meta); } getTitle() { var _a, _b; return (_b = (_a = resolve(this.model, this.model.root)) === null || _a === void 0 ? void 0 : _a.meta) === null || _b === void 0 ? void 0 : _b.pageTitle; } getUrl(link) { var _a, _b, _c; if (typeof link === "undefined" || isLink(link) || isAbsoluteUrl(link)) { return this.linkFactory.create((_b = (_a = link) !== null && _a !== void 0 ? _a : this.model.links.site) !== null && _b !== void 0 ? _b : ""); } return resolveUrl(link, (_c = this.linkFactory.create(this.model.links.site)) !== null && _c !== void 0 ? _c : ""); } getVersion() { return this.model.meta.version; } getVisitor() { return this.model.meta.visitor; } getVisit() { return this.model.meta.visit; } isPreview() { return !!this.model.meta.preview; } rewriteLinks(content, type = "text/html") { return this.linkRewriter.rewrite(content, type); } sync() { var _a; (_a = this.cmsEventBus) === null || _a === void 0 ? void 0 : _a.emit("page.ready", {}); } toJSON() { return this.model; } }; PageImpl$1 = __decorate([ injectable(), __param(0, inject(PageModelToken)), __param(1, inject(ButtonFactory)), __param(2, inject(ComponentFactory$1)), __param(3, inject(ContentFactory$1)), __param(4, inject(LinkFactory)), __param(5, inject(LinkRewriterService)), __param(6, inject(MetaCollectionFactory)), __param(7, inject(CmsEventBusService)), __param(7, optional()), __param(8, inject(PageEventBusService)), __param(8, optional()), __param(9, inject(Logger)), __param(9, optional()), __metadata("design:paramtypes", [ Object, ButtonFactory, ComponentFactory$1, ContentFactory$1, LinkFactory, Object, Function, Object, Object, Logger ]) ], PageImpl$1); /** * Checks whether a value is a page. * @param value The value to check. */ function isPage$2(value) { return value instanceof PageImpl$1; } /** * A component factory producing components based on a type.