diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 3c9055f29..469279baf 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -38,11 +38,10 @@ var getStoredLanguage = function () { return localStorage && localStorage.getIte var getBrowserLanguage = function () { return navigator.language || navigator.userLanguage || ''; }; var getLanguage = Messages._getLanguage = function () { if (window.cryptpadLanguage) { return window.cryptpadLanguage; } - try { - if (getStoredLanguage()) { return getStoredLanguage(); } - } catch (e) { console.log(e); } var l = getBrowserLanguage(); - // Edge returns 'fr-FR' --> transform it to 'fr' and check again + try { + l = getStoredLanguage() || getBrowserLanguage(); + } catch (e) { console.log(e); } return map[l] ? l : (map[l.split('-')[0]] ? l.split('-')[0] : (map[l.split('_')[0]] ? l.split('_')[0] : 'en')); diff --git a/lib/historyKeeper.js b/lib/historyKeeper.js index 7178b5f76..e0a9fea2c 100644 --- a/lib/historyKeeper.js +++ b/lib/historyKeeper.js @@ -67,7 +67,7 @@ module.exports.create = function (Env, cb) { } if (metadata && metadata.selfdestruct && metadata.selfdestruct !== Env.id) { - HK.expireChannel(Env, channelName); + HK.removeChannel(Env, channelName); return void cb('ESELFDESTRUCT'); } diff --git a/lib/hk-util.js b/lib/hk-util.js index c8d07a6f6..d9c4ddd1e 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -136,6 +136,13 @@ const expireChannel = HK.expireChannel = function (Env, channel) { }); }; +const removeChannel = HK.removeChannel = function (Env, channel) { + if (!Env.store) { return; } + Env.store.archiveChannel(channel, void 0, () => {}); + delete Env.metadata_cache[channel]; + delete Env.channel_cache[channel]; +}; + /* dropChannel * cleans up memory structures which are managed entirely by the historyKeeper */ @@ -145,7 +152,7 @@ const dropChannel = HK.dropChannel = function (Env, chanName) { delete Env.channel_cache[chanName]; if (meta && meta.selfdestruct && Env.selfDestructTo) { Env.selfDestructTo[chanName] = setTimeout(function () { - expireChannel(Env, chanName); + removeChannel(Env, chanName); }, TEMPORARY_CHANNEL_LIFETIME); } if (Env.store) { Env.store.closeChannel(chanName, function () {}); } @@ -698,6 +705,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) { }, (err, reason) => { // Any error but ENOENT: abort // ENOENT is allowed in case we want to create a new pad + if (err && err.error) { err = err.error; } if (err && err.code !== 'ENOENT') { if (err.message === "EUNKNOWN") { Log.error("HK_GET_HISTORY", { @@ -713,7 +721,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) { stack: err && err.stack, }); } // FIXME err.message isn't useful for users - const parsedMsg = {error:err.message, channel: channelName, txid: txid}; + const parsedMsg = {error:err.message || 'ERROR', channel: channelName, txid: txid}; Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]); return; } diff --git a/package-lock.json b/package-lock.json index 4f4a1b3b6..0b7ca9ccc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "chainpad": "^5.2.6", "chainpad-crypto": "^0.2.5", "chainpad-listmap": "^1.1.0", - "chainpad-netflux": "^1.2.0", + "chainpad-netflux": "^1.2.2", "chainpad-server": "^5.2.4", "ckeditor": "npm:ckeditor4@~4.22.1", "codemirror": "^5.19.0", @@ -1443,9 +1443,10 @@ } }, "node_modules/chainpad-netflux": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/chainpad-netflux/-/chainpad-netflux-1.2.0.tgz", - "integrity": "sha512-j3qzrL/tugpTNQTk1I7VMZuQJHAYRNmFaiAxLbEFre/gLIPPJqM4gHXuyTU2OFzIFSd5m5i4G3+GwZ32jLV3cA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/chainpad-netflux/-/chainpad-netflux-1.2.2.tgz", + "integrity": "sha512-fcKugW29BE4wo4l3WYLc56yeFznu15bGi6tU3uPtsLGSExXGqNwS+3kmpzI0AFAsuoI0OKPeT4uLX4YzKT/D+A==", + "license": "LGPL-2.1", "dependencies": { "netflux-websocket": "^1.2.0" } diff --git a/package.json b/package.json index 82e83b3e5..b5f56136c 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "chainpad": "^5.2.6", "chainpad-crypto": "^0.2.5", "chainpad-listmap": "^1.1.0", - "chainpad-netflux": "^1.2.0", + "chainpad-netflux": "^1.2.2", "chainpad-server": "^5.2.4", "ckeditor": "npm:ckeditor4@~4.22.1", "codemirror": "^5.19.0", diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 614c7e7ce..631aff970 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -398,6 +398,10 @@ define([ }; var onUploaded = function (ev, data, err) { + if (!ev && err) { + console.error(err); + return void UI.warn(Messages.error); + } if (ev.newTemplate) { if (err) { console.error(err); @@ -555,7 +559,7 @@ define([ var saveToServer = function (blob, title) { if (APP.cantCheckpoint) { return; } // TOO_LARGE - var text = getContent(); + var text = !blob && getContent(); if (!text && !blob) { setEditable(false, true); sframeChan.query('Q_CLEAR_CACHE_CHANNELS', [ @@ -949,7 +953,9 @@ define([ const realParticipants = Object.entries(content.ids).map(([id, user]) => { const nId = id.slice(0,32); - const username = (users[nId] || {}).name || Messages.anonymous; + const username = Util.find(privateData, ['integrationConfig', 'user', 'name']) || + (users[nId] || {}).name || Messages.anonymous; + return { id: String(user.ooid) + user.index, idOriginal: String(user.ooid), @@ -1469,6 +1475,20 @@ define([ }); } break; + case "forceSaveStart": + if (APP.integrationSave) { + APP.integrationSave(obj => { + if (obj?.error) { + console.error(obj.error); + return void UI.warn(Messages.error); + } + content.integrationSave = `${myUniqueOOId}-${+new Date()}`; + APP.integrationSaved = content.integrationSave; + APP.onLocal(); + UI.log(Messages.saved); + }); + } + break; case "getLock": handleLock(obj, send); break; @@ -1677,16 +1697,24 @@ define([ var lang = (window.cryptpadLanguage || navigator.language || navigator.userLanguage || '').slice(0,2); + let username = Util.find(privateData, ['integrationConfig', 'user', 'name']) + || metadataMgr.getUserData().name + || Messages.anonymous; + + let integrationConfig = privateData?.integrationConfig?._; + //let ec = integrationConfig?.editorConfig; + let dc = integrationConfig?.document; + // Config APP.ooconfig = { - "document": { - "fileType": file.type, - "key": "fresh", - "title": file.title, - "url": url, - "permissions": { - "download": false, - "print": true, + document: { + fileType: file.type, + key: "fresh", + title: dc?.title || file.title, + url: url, + permissions: { + download: dc?.permissions?.download || false, + print: dc?.permissions?.print || true, } }, "documentType": file.doc, @@ -1703,8 +1731,8 @@ define([ }, "user": { "id": String(myOOId), //"c0c3bf82-20d7-4663-bf6d-7fa39c598b1d", - "firstname": metadataMgr.getUserData().name || Messages.anonymous, - "name": metadataMgr.getUserData().name || Messages.anonymous, + "firstname": username, + "name": username }, "mode": "edit", "lang": lang @@ -2135,6 +2163,30 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null }, void 0, common.getCache()); }; + let copy = (a, b) => { + Object.keys(b).forEach(k => { + if (k === "user") { return; } // Don't change user values + if (a[k]) { + if (typeof(a[k]) === "object" && typeof(b[k]) === "object") { + copy(a[k], b[k]); + } + return; + } + a[k] = b[k]; + }); + }; + if (integrationConfig) { + let ec = integrationConfig.editorConfig; + let c = APP.ooconfig.editorConfig.customization; + copy(APP.ooconfig.editorConfig, ec); + // Open "goback" in new tabs because of csp and + // iframes + if (ec.editorConfig?.customization?.goback) { + c.goback.blank = true; + } + c.forcesave = true; + } + // Always hide right menu localStorage?.original.removeItem('sse-hide-right-settings'); localStorage?.original.removeItem('de-hide-right-settings'); @@ -3180,9 +3232,9 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null return void UI.errorLoadingScreen(Messages.error); } var blob = new Blob([bin], {type: 'text/plain'}); - var file = getFileType(); - resetData(blob, file); - //saveToServer(blob, title); + //var file = getFileType(); + //resetData(blob, file); + saveToServer(blob, title); Title.updateTitle(title); UI.removeLoadingScreen(); }); @@ -3193,13 +3245,17 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null let cfg = privateData.integrationConfig || {}; common.openIntegrationChannel(APP.onLocal); integrationChannel = common.getSframeChannel(); + let hasUnsavedChanges = false; var integrationSave = function (cb) { var ext = cfg.fileType; var upload = Util.once(function (_blob) { integrationChannel.query('Q_INTEGRATION_SAVE', { blob: _blob - }, cb, { + }, obj => { + if (!obj?.error) { hasUnsavedChanges = false; } + cb(obj); + }, { raw: true }); }); @@ -3220,21 +3276,46 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null }; var inte = common.createIntegration(integrationSave, integrationHasUnsavedChanges); - if (inte) { + if (inte && cfg.autosave) { evIntegrationSave.reg(function () { inte.changed(); }); + } else { + APP.integrationSave = integrationSave; + APP.integrationSetSaved = () => { + hasUnsavedChanges = false; + }; + evIntegrationSave.reg(function () { + hasUnsavedChanges = true; + }); } + $(window).on('beforeunload', function (ev) { + if (hasUnsavedChanges) { return false; } + ev.returnValue = ''; + }); integrationChannel.on('Q_INTEGRATION_NEEDSAVE', function (data, cb) { + if (!cfg.autosave) { return; } integrationSave(function (obj) { if (obj && obj.error) { console.error(obj.error); } cb(); }); }); - if (privateData.initialState) { + + /* test button + if (!cfg.autosave) { + let $save = common.createButton('save', true, {}, function () { + $save.attr('disabled', 'disabled'); + integrationSave(() => { + $save.removeAttr('disabled'); + }); + }); + $('body').prepend($save); + } + */ + + if (privateData.initialState && (!content || !content.hashes || !Object.keys(content.hashes).length)) { var blob = privateData.initialState; let title = `document.${cfg.fileType}`; - console.error(blob, title); return convertImportBlob(blob, title); } } @@ -3369,6 +3450,7 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null var wasMigrating = content.migration; var myLocks = getUserLock(getId(), true); + //var integrationSave = content.integrationSave; content = json.content; @@ -3379,6 +3461,15 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null checkCheckpoint(); } + // Integration: mark the current content as saved + // if manually saved by someone else + if (content.integrationSave !== APP.integrationSaved) { + APP.integrationSaved = content.integrationSave; + if (APP.integrationSetSaved) { + APP.integrationSetSaved(); + } + } + var editor = getEditor(); if (content.hashes) { var latest = getLastCp(true); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index e9eb8226a..8439af38f 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -467,6 +467,19 @@ define([ }); }; + var initTempRpc = (clientId, cb) => { + if (store.rpc) { return void cb(store.rpc); } + var kp = Crypto.Nacl.sign.keyPair(); + var keys = { + edPublic: Crypto.Nacl.util.encodeBase64(kp.publicKey), + edPrivate: Crypto.Nacl.util.encodeBase64(kp.secretKey) + }; + Pinpad.create(store.network, keys, function (e, call) { + if (e) { return void cb({error: e}); } + store.rpc = call; + cb(call); + }); + }; var initRpc = function (clientId, data, cb) { if (!store.loggedIn) { return cb(); } if (store.rpc) { return void cb(account); } @@ -1830,7 +1843,7 @@ define([ Store.leavePad(null, data, function () {}); }; var conf = { - Cache: Cache, // ICE pad cache + Cache: store.neverCache ? undefined : Cache, onCacheStart: function () { postMessage(clientId, "PAD_CACHE"); }, @@ -3160,7 +3173,7 @@ define([ // If we load CryptPad for the first time from an existing pad, don't create a // drive automatically. - var onNoDrive = function (clientId, cb) { + var onNoDrive = function (clientId, cb, initRpc) { var andThen = function () { // To be able to use all the features inside the pad, we need to // initialize the chat (messenger) and the cursor modules. @@ -3171,9 +3184,16 @@ define([ store.messenger = store.modules['messenger']; // And now we're ready - initAnonRpc(null, null, function () { - cb({}); - }); + let getAnon = () => { + initAnonRpc(null, null, function () { + cb({}); + }); + }; + + if (initRpc) { + return initTempRpc(clientId, getAnon); + } + getAnon(); }; // We need an anonymous RPC to be able to check if the pad exists and to get @@ -3256,7 +3276,8 @@ define([ // First tab, no user hash, no anon hash and this app doesn't need a drive // ==> don't create a drive // Or "neverDrive" (integration into another platform?) - // ==> don't create a drive + // ==> don't create a drive BUT create temp RPC (we may need to upload) + if (data.neverDrive) { store.neverCache = true; } if (data.neverDrive || (data.noDrive && !data.userHash && !data.anonHash)) { return void onNoDrive(clientId, function (obj) { if (obj && obj.error) { @@ -3270,7 +3291,7 @@ define([ } Feedback.send("NO_DRIVE", true); callback(obj); - }); + }, !!data.neverDrive); } initialized = true; diff --git a/www/common/sframe-app-outer.js b/www/common/sframe-app-outer.js index b1210796f..b634f51b4 100644 --- a/www/common/sframe-app-outer.js +++ b/www/common/sframe-app-outer.js @@ -17,7 +17,9 @@ define([ nThen(function (waitFor) { DomReady.onReady(waitFor()); }).nThen(function (waitFor) { - var obj = SFCommonO.initIframe(waitFor, true, integration.pathname); + let lang = integration && integration.config && integration.config.editorConfig + && integration.config.editorConfig.lang; + var obj = SFCommonO.initIframe(waitFor, true, integration.pathname, lang); href = obj.href; hash = obj.hash; if (isIntegration) { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 8138aec2d..58d3023ff 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -842,6 +842,13 @@ define([ additionalPriv.initialState = cfg.initialState instanceof Blob ? cfg.initialState : undefined; + if (cfg.integrationConfig) { + if (metaObj?.user && !metaObj.user.name) { + metaObj.user.name = cfg.integrationConfig?.user?.name || + cfg.integrationConfig?.user?.firstname; + } + } + // Early access var priv = metaObj.priv; var _plan = typeof(priv.plan) === "undefined" ? Utils.LocalStore.getPremium() : priv.plan; @@ -2242,7 +2249,7 @@ define([ } }; - // on server crash, try to save to Nextcloud + // on server crash, try to save to the outer platform if (ready) { return integrationSave(reload); } // if error during loading, reload without saving diff --git a/www/cryptpad-api.js b/www/cryptpad-api.js index 01f9f6946..9efd6c569 100644 --- a/www/cryptpad-api.js +++ b/www/cryptpad-api.js @@ -18,7 +18,9 @@ var scripts = document.getElementsByTagName('script'); for (var i = scripts.length - 1; i >= 0; i--) { var match = scripts[i].src.match(/(.*)web-apps\/apps\/api\/documents\/api.js/i); + var match2 = scripts[i].src.match(/(.*)\/cryptpad-api.js/i); if (match) { return match[1]; } + else if (match2) { return match2[1]; } } }; @@ -86,6 +88,7 @@ var start = function (config, chan) { return new Promise(function (resolve, reject) { setTimeout(function () { + var docID = config.document.key; var key = config.document.key; var blob; @@ -109,14 +112,31 @@ xhr.send(); }; + let serializedConfig = () => { + let _config = {}; + _config.editorConfig = config.editorConfig; + _config.document = { + permissions: config.document?.permissions, + title: config.document?.title, + info: config.document?.info, + referenceData: config.document?.referenceData + }; + return _config; + }; + var start = function () { - config.document.key = key; + //config.document.key = key; chan.send('START', { key: key, application: config.documentType, + name: config.document.title, + url: config.document.url, + documentKey: docID, document: blob, ext: config.document.fileType, - autosave: config.autosave || 10 + autosave: config.events.onSave && (config.autosave || 10), + editorConfig: config.editorConfig || {}, + _config: serializedConfig() }, function (obj) { if (obj && obj.error) { reject(obj.error); return console.error(obj.error); } resolve({}); @@ -130,8 +150,17 @@ blob = config.document.blob; return start(); } + // NOTE: Nextcloud will log us out if we try from the client + // TODO: make sure the server plugin is installed if we don't + // call getBlob() + if (!config.events?.onSave) { + return void start(); + } getBlob(function (err, _blob) { - if (err) { reject(err); return console.error(err); } + if (err) { // Can't get blob from client, try from server + console.warn(err); + return void start(); + } _blob.name = `document.${config.document.fileType}`; blob = _blob; start(); @@ -188,6 +217,7 @@ chan.on('ON_DOWNLOADAS', blob => { let url = URL.createObjectURL(blob); + if (!config.events.onDownloadAs) { return; } config.events.onDownloadAs({ data: { fileType: config.document && config.document.fileType, @@ -198,6 +228,7 @@ chan.on('SAVE', function (data, cb) { blob = data; + if (!config.events.onSave) { return void cb(); } config.events.onSave(data, cb); }); chan.on('RELOAD', function () { @@ -242,22 +273,28 @@ */ var init = function (cryptpadURL, containerId, config) { // OnlyOffice shim: don't provide a URL - if (!config && typeof(containerId) === "object") { + if (typeof(config) !== "object" && typeof(containerId) === "object") { config = containerId; containerId = cryptpadURL; cryptpadURL = getInstanceURL(); } + config.events = config.events || {}; + // OnlyOffice shim let url = config.document.url; if (/^http:\/\/localhost\/cache\/files\//.test(url)) { url = url.replace(/(http:\/\/localhost\/cache\/files\/)/, getInstanceURL() + 'ooapi/'); } config.document.url = url; - if (config.documentType === "spreadsheet") { + if (config.documentType === "spreadsheet" || config.documentType === "cell") { config.documentType = "sheet"; } - if (config.documentType === "text") { + + if (config.documentType === "slide") { + config.documentType = "presentation"; + } + if (config.documentType === "word" || config.documentType === "text") { config.documentType = "doc"; } @@ -308,8 +345,8 @@ iframe.setAttribute('name', 'frameEditor'); iframe.setAttribute('align', 'top'); iframe.setAttribute("src", url); - iframe.setAttribute("width", config.width); - iframe.setAttribute("height", config.height); + iframe.setAttribute("width", config.width || '100%'); + iframe.setAttribute("height", config.height || '100%'); if (config.editorConfig) { // OnlyOffice container.replaceWith(iframe); container = iframe; diff --git a/www/integration/main.js b/www/integration/main.js index fe3ffd010..7db6c73c7 100644 --- a/www/integration/main.js +++ b/www/integration/main.js @@ -3,10 +3,13 @@ // SPDX-License-Identifier: AGPL-3.0-or-later define([ + '/api/config', '/common/sframe-common-outer.js', '/common/common-hash.js', -], function (SCO, Hash) { + '/components/tweetnacl/nacl-fast.min.js' +], function (Config, SCO, Hash) { + let Nacl = window.nacl; var getTxid = function () { return Math.random().toString(16).replace('0.', ''); }; @@ -95,9 +98,17 @@ define([ }; http.send(); }; + let sanitizeKey = key => { + try { + Nacl.util.decodeBase64(key); + return key; + } catch (e) { + return Nacl.util.encodeBase64(Nacl.util.decodeUTF8(key)).replaceAll('=', ''); + } + }; chan.on('GET_SESSION', function (data, cb) { - if (data.keepOld) { - var key = data.key + "000000000000000000000000000000000"; + if (data.keepOld) { // they provide their own key, we must turn it into a hash + var key = sanitizeKey(data.key) + "000000000000000000000000000000000"; console.warn('KEY', key); return void cb({ key: `/2/integration/edit/${key.slice(0,24)}/` @@ -118,12 +129,6 @@ define([ }); }); - var save = function (obj, cb) { - chan.send('SAVE', obj.blob, function (err) { - if (err) { return cb({error: err}); } - cb(); - }); - }; var reload = function (data) { chan.send('RELOAD', data); }; @@ -152,36 +157,122 @@ define([ chan.send('ON_DOWNLOADAS', blob); }; - chan.on('START', function (data) { - console.warn('INNER START', data); - var href = Hash.hashToHref(data.key, data.application); - console.error(Hash.hrefToHexChannelId(href)); - window.CP_integration_outer = { - pathname: `/${data.application}/`, - hash: data.key, - href: href, - initialState: data.document, - config: { - fileType: data.ext, - autosave: data.autosave - }, - utils: { - onReady: onReady, - onDownloadAs, - setDownloadAs, - save: save, - reload: reload, - onHasUnsavedChanges: onHasUnsavedChanges, - onInsertImage: onInsertImage + + let getInstanceURL = function () { + return Config.httpUnsafeOrigin; + }; + let getBlobServer = function (documentURL, cb) { + let xhr = new XMLHttpRequest(); + let data = encodeURIComponent(documentURL); + let url = getInstanceURL() + '/ooapidl?url=' + data; + xhr.open('GET', url, true); + xhr.responseType = 'blob'; + //xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.onload = function () { + if (this.status === 200) { + var blob = this.response; + // myBlob is now the blob that the object URL pointed to. + cb(null, blob); + } else { + cb(this.status); } }; - let path = "/common/sframe-app-outer.js"; - if (['sheet', 'doc', 'presentation'].includes(data.application)) { - path = '/common/onlyoffice/main.js'; + xhr.onerror = function (e) { + cb(e.message); + }; + xhr.send(); + }; + let saveBlobServer = function (cfg, blob, cb) { + let {callbackUrl, name, key} = cfg; + let xhr = new XMLHttpRequest(); + name = encodeURIComponent(name); + callbackUrl = encodeURIComponent(callbackUrl); + key = encodeURIComponent(key); + let query = `name=${name}&cb=${callbackUrl}&key=${key}`; + let url = getInstanceURL() + `/oosave?${query}`; + xhr.open('POST', url, true); + xhr.responseType = 'blob'; + //xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.onload = function () { + console.error(this.status); + if (this.status === 200) { + cb(); + } else { + cb(this.status); + } + }; + xhr.onerror = function (e) { + cb(e.message); + }; + xhr.send(blob); + }; + chan.on('START', function (data, cb) { + console.warn('INNER START', data); + // data.key is a hash + var href = Hash.hashToHref(data.key, data.application); + if (data.editorConfig.lang) { + var LS_LANG = "CRYPTPAD_LANG"; + localStorage.setItem(LS_LANG, data.editorConfig.lang); } - require([path], function () { - console.warn('SAO REQUIRED'); - delete window.CP_integration_outer; + + let fileName = data.name || `document.${data.ext}`; + var save = function (obj, cb) { + let cbUrl = data.editorConfig.callbackUrl; + if (!data.autosave && cbUrl) { + saveBlobServer({ + callbackUrl: cbUrl, + name: fileName, + key: data.documentKey + }, obj.blob, cb); + return; + } + chan.send('SAVE', obj.blob, function (err) { + if (err) { return cb({error: err}); } + cb(); + }); + }; + + console.error(Hash.hrefToHexChannelId(href)); + let startApp = function (blob) { + window.CP_integration_outer = { + pathname: `/${data.application}/`, + hash: data.key, + href: href, + initialState: blob, + config: { + fileName: data.name, + fileType: data.ext, + autosave: data.autosave, + user: data.editorConfig.user, + _: data._config + }, + utils: { + onReady: onReady, + onDownloadAs, + setDownloadAs, + save: save, + reload: reload, + onHasUnsavedChanges: onHasUnsavedChanges, + onInsertImage: onInsertImage + } + }; + let path = "/common/sframe-app-outer.js"; + if (['sheet', 'doc', 'presentation'].includes(data.application)) { + path = '/common/onlyoffice/main.js'; + } + require([path], function () { + console.warn('SAO REQUIRED'); + delete window.CP_integration_outer; + cb(); + }); + }; + + if (data.document) { return void startApp(data.document); } + getBlobServer(data.url, (err, blob) => { + if (err) { + return void cb({error: err}); + } + startApp(blob); }); });