// (c) Copyright Ascensio System SIA 2009-2024 // // This program is a free software product. // You can redistribute it and/or modify it under the terms // of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software // Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended // to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of // any third-party rights. // // This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see // the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html // // You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. // // The interactive user interfaces in modified source and object code versions of the Program must // display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. // // Pursuant to Section 7(b) of the License you must retain the original Product logo when // distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under // trademark law for use of our trademarks. // // All the Product's GUI elements, including illustrations and icon sets, as well as technical writing // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode (function () { const defaultConfig = { src: new URL(document.currentScript.src).origin, rootPath: "/rooms/shared/", requestToken: null, width: "100%", height: "100%", name: "frameDocSpace", type: "desktop", // TODO: ["desktop", "mobile"] frameId: "ds-frame", mode: "manager", //TODO: ["manager", "editor", "viewer","room-selector", "file-selector", "system"] id: null, locale: null, theme: "Base", editorType: "desktop", //TODO: ["desktop", "embedded"] editorGoBack: true, selectorType: "exceptPrivacyTrashArchiveFolders", //TODO: ["roomsOnly", "userFolderOnly", "exceptPrivacyTrashArchiveFolders", "exceptSortedByTagsFolders"] showSelectorCancel: false, showSelectorHeader: false, showHeader: false, showTitle: true, showMenu: false, showFilter: false, destroyText: "", viewAs: "row", //TODO: ["row", "table", "tile"] viewTableColumns: "Name,Type,Tags", checkCSP: true, disableActionButton: false, showSettings: false, withSearch: true, withBreadCrumbs: true, withSubtitle: true, filterParam: "ALL", buttonColor: "#5299E0", infoPanelVisible: true, downloadToEvent: false, filter: { // filterType: 0, // type: 0, count: 100, page: 1, sortorder: "descending", //TODO: ["descending", "ascending"] sortby: "DateAndTime", //TODO: ["DateAndTime", "AZ", "Type", "Size", "DateAndTimeCreation", "Author"] search: "", withSubfolders: false, }, editorCustomization: {}, keysForReload: [ "src", "rootPath", "width", "height", "name", "frameId", "id", "type", "editorType", "mode", ], events: { onSelectCallback: (items) => { alert(items[0].label); window.close(); }, onCloseCallback: () => { window.close(); }, onAppReady: null, onAppError: (e) => console.log("onAppError", e), onEditorCloseCallback: null, onAuthSuccess: null, onSignOut: null, onDownload: null, }, }; const lt = //g; const rgt = "&rt;"; const cspErrorText = "The current domain is not set in the Content Security Policy (CSP) settings."; const validateCSP = async (targetSrc) => { let currentSrc = window.location.origin; if (currentSrc.indexOf(targetSrc) !== -1) return; // skip check for the same domain const response = await fetch(`${targetSrc}/api/2.0/security/csp`); const res = await response.json(); currentSrc = window.location.host; // more flexible way to check const passed = res.response.header && res.response.header.toLowerCase().includes(currentSrc.toLowerCase()); if (!passed) throw new Error(cspErrorText); return; }; const getConfigFromParams = () => { const src = decodeURIComponent(document.currentScript.src); if (!src || !src.length) return null; const searchUrl = src.split("?")[1]; let object = {}; if (searchUrl && searchUrl.length) { object = JSON.parse( `{"${searchUrl.replace(/&/g, '","').replace(/=/g, '":"')}"}`, (k, v) => (v === "true" ? true : v === "false" ? false : v) ); object.filter = defaultConfig.filter; for (prop in object) { if (prop in defaultConfig.filter) { object.filter[prop] = object[prop]; delete object[prop]; } } } return { ...defaultConfig, ...object }; }; class DocSpace { #isConnected = false; #frameOpacity = 0; #callbacks = []; #tasks = []; #classNames = ""; constructor(config) { this.config = config; } #oneOfExistInObject = (array, object) => { return Object.keys(object).some((k) => array.includes(k)); }; #createLoader = (config) => { const container = document.createElement("div"); container.style.width = config.width; container.style.height = config.height; container.style.display = "flex"; container.style.justifyContent = "center"; container.style.alignItems = "center"; // const loader = document.createElement("img"); // loader.setAttribute("src", `${config.src}/static/images/loader.svg`); // loader.setAttribute("width", `64px`); // loader.setAttribute("height", `64px`); // if ( // config.theme === "Dark" || // (config.theme === "System" && // window.matchMedia("(prefers-color-scheme: dark)")) // ) { // container.style.backgroundColor = "#333333"; // loader.style.filter = // "invert(100%) sepia(100%) saturate(0%) hue-rotate(288deg) brightness(102%) contrast(102%)"; // } const loader = document.createElement("div"); const loaderClass = `${config.frameId}-loader__element`; loader.setAttribute("class", loaderClass); container.appendChild(loader); const style = document.createElement("style"); style.innerHTML = ` @keyframes rotate { 0%{ transform: rotate(-45deg); } 15%{ transform: rotate(45deg); } 30%{ transform: rotate(135deg); } 45%{ transform: rotate(225deg); } 60%, 100%{ transform: rotate(315deg); } } .${loaderClass} { width: 74px; height: 74px; border: 4px solid rgba(51,51,51, 0.1); border-top-color: #333333; border-radius: 50%; transform: rotate(-45deg); position: relative; box-sizing: border-box; animation: 1s linear infinite rotate; } @media (prefers-color-scheme: dark) { .${loaderClass} { border-color: rgba(204, 204, 204, 0.1); border-top-color: #CCCCCC; } } `; container.appendChild(style); container.setAttribute("id", config.frameId + "-loader"); return container; }; #createButtonView = (config) => { const button = document.createElement("button"); button.style.backgroundColor = config?.buttonColor || "#5299E0"; button.style.color = "#fff"; button.style.padding = "0 28px"; button.style.borderRadius = "3px"; button.style.border = `1px solid #5299E0`; button.style.height = "32px"; button.style.fontWeight = "600"; button.style.fontFamily = "Open Sans"; button.style.cursor = "pointer"; button.style.display = "flex"; button.style.alignItems = "center"; button.style.gap = "10px"; button.style.userSelect = "none"; button.style.borderColor = config?.buttonColor || "#5299E0"; const logoSrc = `${config.src}/static/images/light_small_logo.react.svg`; button.innerHTML = `${config?.buttonWithLogo ? `` : ""}${config?.buttonText || "Select to DocSpace"}`; const scriptUrl = `${window.location.origin}/static/scripts/api.js`; const configStringify = JSON.stringify(config, function (key, val) { return typeof val === "function" ? "" + val : val; }) .replace(lt, rlt) .replace(gt, rgt); button.addEventListener("click", () => { const winHtml = ` DocSpace
`; const winUrl = URL.createObjectURL( new Blob([winHtml], { type: "text/html" }) ); window.open(winUrl, "_blank", `width=610,height=778`); }); button.setAttribute("id", config.frameId + "-container"); return button; }; #createIframe = (config) => { const iframe = document.createElement("iframe"); let path = ""; switch (config.mode) { case "manager": { if (config.filter) { if (config.id) config.filter.folder = config.id; const params = config.requestToken ? { key: config.requestToken, ...config.filter } : config.filter; if (!params.withSubfolders) { delete params.withSubfolders; } const urlParams = new URLSearchParams(params).toString(); path = `${config.rootPath}${ config.requestToken ? `?${urlParams}` : `${config.id ? config.id + "/" : ""}filter?${urlParams}` }`; } break; } case "room-selector": { path = `/sdk/room-selector`; break; } case "file-selector": { path = `/sdk/file-selector?selectorType=${config.selectorType}`; break; } case "system": { path = `/sdk/system`; break; } case "editor": { let goBack = config.editorGoBack; config.editorCustomization.uiTheme = config.theme; const customization = JSON.stringify(config.editorCustomization); if ( config.events.onEditorCloseCallback && typeof config.events.onEditorCloseCallback === "function" ) { goBack = "event"; } path = `/doceditor/?fileId=${config.id}&editorType=${config.editorType}&editorGoBack=${goBack}&customization=${customization}`; if (config.requestToken) { path = `${path}&share=${config.requestToken}`; } break; } case "viewer": { let goBack = config.editorGoBack; config.editorCustomization.uiTheme = config.theme; const customization = JSON.stringify(config.editorCustomization); if ( config.events.onEditorCloseCallback && typeof config.events.onEditorCloseCallback === "function" ) { goBack = "event"; } path = `/doceditor/?fileId=${config.id}&editorType=${config.editorType}&action=view&editorGoBack=${goBack}&customization=${customization}`; if (config.requestToken) { path = `${path}&share=${config.requestToken}`; } break; } default: path = config.rootPath; } iframe.src = config.src + path; iframe.style.width = config.width; iframe.style.height = config.height; iframe.name = config.name; iframe.id = config.frameId; iframe.frameBorder = 0; iframe.allowFullscreen = true; iframe.setAttribute("allow", "storage-access *"); if (config.type == "mobile") { iframe.style.position = "fixed"; iframe.style.overflow = "hidden"; document.body.style.overscrollBehaviorY = "contain"; } if (this.config.checkCSP) { validateCSP(this.config.src).catch((e) => { const html = `
${cspErrorText} Please add it via the Developer Tools section.
`; iframe.srcdoc = html; e.message && config.events.onAppError(e.message); this.setIsLoaded(); }); } return iframe; }; #sendMessage = (message) => { let mes = { frameId: this.config.frameId, type: "", data: message, }; const targetFrame = document.getElementById(this.config.frameId); if (targetFrame && !!targetFrame.contentWindow) { targetFrame.contentWindow.postMessage( JSON.stringify(mes, (key, value) => typeof value === "function" ? value.toString() : value ), this.config.src ); } }; #onMessage = (e) => { if (typeof e.data == "string") { let data = {}; try { data = JSON.parse(e.data); } catch (err) { data = {}; } switch (data.type) { case "onMethodReturn": { if (this.#callbacks.length > 0) { const callback = this.#callbacks.shift(); callback && callback(data.methodReturnData); } if (this.#tasks.length > 0) { this.#sendMessage(this.#tasks.shift()); } break; } case "onEventReturn": { if (Object.keys(this.config).length === 0) return; if ( data?.eventReturnData?.event in this.config.events && typeof this.config.events[data?.eventReturnData.event] === "function" ) { this.config.events[data?.eventReturnData.event]( data?.eventReturnData?.data ); } break; } case "onCallCommand": { this[data.commandName].call(this, data.commandData); break; } default: break; } } }; #executeMethod = (methodName, params, callback) => { if (!this.#isConnected) { this.config.events.onAppError( "Message bus is not connected with frame" ); return; } this.#callbacks.push(callback); const message = { type: "method", methodName, data: params, }; if (this.#callbacks.length !== 1) { this.#tasks.push(message); return; } this.#sendMessage(message); }; initButton(config) { const configFull = { ...defaultConfig, ...config }; this.config = { ...this.config, ...configFull, events: { ...defaultConfig.events }, }; const target = document.getElementById(this.config.frameId); let button = null; if (target) { button = this.#createButtonView(this.config); this.#classNames = target.className; const isSelfReplace = target.parentNode.isEqualNode( document.getElementById(this.config.frameId + "-container") ); target && isSelfReplace ? target.parentNode.replaceWith(button) : target.replaceWith(button); window.addEventListener("message", this.#onMessage, false); this.#isConnected = true; } window.DocSpace.SDK.frames = window.DocSpace.SDK.frames || []; window.DocSpace.SDK.frames[this.config.frameId] = this; return button; } initFrame(config) { const configFull = { ...defaultConfig, ...config }; Object.entries(configFull).map(([key, value]) => { if (typeof value === "string") configFull[key] = value.replaceAll(rlt, "<").replaceAll(rgt, ">"); }); this.config = { ...this.config, ...configFull }; const target = document.getElementById(this.config.frameId); let iframe = null; if (target) { iframe = this.#createIframe(this.config); iframe.style.opacity = this.#frameOpacity; iframe.style.zIndex = 2; iframe.style.position = "absolute"; iframe.style.width = "100%"; iframe.style.height = "100%"; iframe.style.top = 0; iframe.style.left = 0; const frameLoader = this.#createLoader(this.config); this.#classNames = target.className; const renderContainer = document.createElement("div"); renderContainer.id = this.config.frameId + "-container"; renderContainer.classList = ["frame-container"]; renderContainer.style.position = "relative"; renderContainer.style.width = "100%"; renderContainer.style.height = "100%"; renderContainer.appendChild(iframe); renderContainer.appendChild(frameLoader); const isSelfReplace = target.parentNode.isEqualNode( document.getElementById(this.config.frameId + "-container") ); target && isSelfReplace ? target.parentNode.replaceWith(renderContainer) : target.replaceWith(renderContainer); window.addEventListener("message", this.#onMessage, false); this.#isConnected = true; } window.DocSpace.SDK.frames = window.DocSpace.SDK.frames || []; window.DocSpace.SDK.frames[this.config.frameId] = this; return iframe; } initManager(config = {}) { config.mode = "manager"; return this.initFrame(config); } setIsLoaded() { const targetFrame = document.getElementById(this.config.frameId); const loader = document.getElementById(this.config.frameId + "-loader"); if (targetFrame) { targetFrame.style.opacity = 1; targetFrame.style.position = "relative"; targetFrame.style.width = this.config.width; targetFrame.style.height = this.config.height; targetFrame.parentNode.style.height = "inherit"; if (loader) loader.remove(); } } initEditor(config = {}) { config.mode = "editor"; return this.initFrame(config); } initViewer(config = {}) { config.mode = "viewer"; return this.initFrame(config); } initRoomSelector(config = {}) { config.mode = "room-selector"; return this.initFrame(config); } initFileSelector(config = {}) { config.mode = "file-selector"; return this.initFrame(config); } initSystem(config = {}) { config.mode = "system"; return this.initFrame(config); } destroyFrame() { const target = document.createElement("div"); target.setAttribute("id", this.config.frameId); target.innerHTML = this.config.destroyText; target.className = this.#classNames; const targetFrame = document.getElementById( this.config.frameId + "-container" ); window.removeEventListener("message", this.#onMessage, false); this.#isConnected = false; delete window.DocSpace.SDK.frames[this.config.frameId]; targetFrame?.parentNode?.replaceChild(target, targetFrame); this.config = {}; } #getMethodPromise = (methodName, params = null, withReload = false) => { return new Promise((resolve) => { if (withReload) { this.initFrame(this.config); resolve(this.config); } else { this.#executeMethod(methodName, params, (data) => resolve(data)); } }); }; getFolderInfo() { return this.#getMethodPromise("getFolderInfo"); } getSelection() { return this.#getMethodPromise("getSelection"); } getFiles() { return this.#getMethodPromise("getFiles"); } getFolders() { return this.#getMethodPromise("getFolders"); } getList() { return this.#getMethodPromise("getList"); } getRooms(filter) { return this.#getMethodPromise("getRooms", filter); } getUserInfo() { return this.#getMethodPromise("getUserInfo"); } getConfig() { return this.config; } getHashSettings() { return this.#getMethodPromise("getHashSettings"); } setConfig(newConfig = {}, reload = false) { if (this.#oneOfExistInObject(this.config.keysForReload, newConfig)) reload = true; this.config = { ...this.config, ...newConfig }; return this.#getMethodPromise("setConfig", this.config, reload); } openModal(type, options) { return this.#getMethodPromise("openModal", { type, options }); } createFile(folderId, title, templateId, formId) { return this.#getMethodPromise("createFile", { folderId, title, templateId, formId, }); } createFolder(parentFolderId, title) { return this.#getMethodPromise("createFolder", { parentFolderId, title, }); } createRoom(title, roomType) { return this.#getMethodPromise("createRoom", { title, roomType, }); } setListView(type) { return this.#getMethodPromise("setItemsView", type); } createHash(password, hashSettings) { return this.#getMethodPromise("createHash", { password, hashSettings }); } login(email, passwordHash) { return this.#getMethodPromise("login", { email, passwordHash }); } logout() { return this.#getMethodPromise("logout"); } createTag(name) { return this.#getMethodPromise("createTag", name); } addTagsToRoom(roomId, tags) { return this.#getMethodPromise("addTagsToRoom", { roomId, tags }); } removeTagsFromRoom(roomId, tags) { return this.#getMethodPromise("removeTagsFromRoom", { roomId, tags }); } } const config = getConfigFromParams(); window.DocSpace = window.DocSpace || {}; window.DocSpace.SDK = new DocSpace(config); if (config.init) { config?.isButtonMode ? window.DocSpace.SDK.initButton(config) : window.DocSpace.SDK.initFrame(config); } })();