diff --git a/customize.dist/login.js b/customize.dist/login.js index a21de0cfc..2c1ebe5bd 100644 --- a/customize.dist/login.js +++ b/customize.dist/login.js @@ -7,7 +7,7 @@ define([ 'chainpad-listmap', '/components/chainpad-crypto/crypto.js', '/common/common-util.js', - '/common/outer/network-config.js', + '/common/network-config.js', '/common/common-login.js', '/common/common-credential.js', '/components/chainpad/chainpad.dist.js', diff --git a/docs/example-advanced.nginx.conf b/docs/example-advanced.nginx.conf index e9458dbab..e6cb5b06d 100644 --- a/docs/example-advanced.nginx.conf +++ b/docs/example-advanced.nginx.conf @@ -288,5 +288,5 @@ server { } # Finally, serve anything the above exceptions don't govern. - try_files /customize/www/$uri /customize/www/$uri/index.html /www/$uri /www/$uri/index.html /customize/$uri; + try_files /customize/www/$uri /customize/www/$uri/index.html /www/$uri /www/$uri/index.html /src/$uri /customize/$uri; } diff --git a/lib/common-hash.js b/lib/common-hash.js index 7a3beddab..20dec3f80 100644 --- a/lib/common-hash.js +++ b/lib/common-hash.js @@ -2,5 +2,5 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -module.exports = require("../www/common/common-hash"); +module.exports = require("../src/common/common-hash"); diff --git a/lib/common-util.js b/lib/common-util.js index 400d8472d..497e25b28 100644 --- a/lib/common-util.js +++ b/lib/common-util.js @@ -2,4 +2,4 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -module.exports = require("../www/common/common-util"); +module.exports = require("../src/common/common-util"); diff --git a/lib/http-worker.js b/lib/http-worker.js index 91232d348..f90d9a101 100644 --- a/lib/http-worker.js +++ b/lib/http-worker.js @@ -339,6 +339,8 @@ Object.keys(plugins || {}).forEach(name => { app.use(Express.static(Path.resolve('./customize/www'))); app.use(Express.static(Path.resolve('./www'))); +app.use("/common", Express.static('./src/common')); + var mainPages = Env.mainPages || Default.mainPages(); var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$'); app.get(mainPagePattern, Express.static('./customize')); diff --git a/lib/keys.js b/lib/keys.js index 7d5a63934..bb454db98 100644 --- a/lib/keys.js +++ b/lib/keys.js @@ -2,4 +2,4 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -module.exports = require("../www/common/common-signing-keys"); +module.exports = require("../src/common/common-signing-keys"); diff --git a/move-merge-src.js b/move-merge-src.js deleted file mode 100644 index f3c40bdc3..000000000 --- a/move-merge-src.js +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2025 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -// Move file from "src" to "www" -// This script can be used to copy src files into their old location in order to -// merge code more easily. -// Set REVERSE to true to copy from "www" to "src" once the changes have been merged. - -const REVERSE = false; -const Fs = require('node:fs'); -const map = { - './src/worker/components/roster.js': './www/common/outer/roster.js', - './src/worker/components/sharedfolder.js': './www/common/outer/sharedfolder.js', - - - - './src/common/cache-store.js': './www/common/outer/cache-store.js', - './src/common/common-constants.js': './www/common/common-constants.js', - './src/common/common-credential.js': './www/common/common-credential.js', - './src/common/common-feedback.js': './www/common/common-feedback.js', - './src/common/common-hash.js': './www/common/common-hash.js', - './src/common/common-messaging.js': './www/common/common-messaging.js', - './src/common/common-realtime.js': './www/common/common-realtime.js', - './src/common/common-signing-keys.js': './www/common/common-signing-keys.js', - './src/common/common-util.js': './www/common/common-util.js', - './src/common/cryptget.js': './www/common/cryptget.js', - './src/common/http-command.js': './www/common/outer/http-command.js', - './src/common/login-block.js': './www/common/outer/login-block.js', - './src/common/network-config.js': './www/common/outer/network-config.js', - './src/common/notify.js': './www/common/notify.js', - './src/common/onlyoffice/current-version.js': './www/common/onlyoffice/current-version.js', - './src/common/pad-types.js': './www/common/pad-types.js', - './src/common/pinpad.js': './www/common/pinpad.js', - './src/common/proxy-manager.js': './www/common/proxy-manager.js', - './src/common/recurrence.js': './www/calendar/recurrence.js', - './src/common/rpc.js': './www/common/rpc.js', - './src/common/user-object.js': './www/common/user-object.js', - './src/common/user-object-setter.js': './www/common/user-object-setter.js', - './src/common/worker-channel.js': './www/common/outer/worker-channel.js' -}; - -Object.keys(map).forEach(newPath => { - let oldPath = map[newPath]; - if (!Fs.existsSync(newPath)) { - throw new Error("File path mismatch: " + newPath); - } - if (!Fs.existsSync(oldPath)) { - throw new Error("File path mismatch: " + oldPath); - } - const from = REVERSE ? oldPath : newPath; - const to = REVERSE ? newPath : oldPath; - Fs.cpSync(from, to); -}); diff --git a/src/common/cryptget.js b/src/common/cryptget.js index df3453225..3d4b60921 100644 --- a/src/common/cryptget.js +++ b/src/common/cryptget.js @@ -225,8 +225,8 @@ if (typeof(module) !== 'undefined' && module.exports) { '/common/common-util.js', '/common/common-hash.js', '/common/common-realtime.js', - '/common/outer/network-config.js', - '/common/outer/cache-store.js', + '/common/network-config.js', + '/common/cache-store.js', '/common/pinpad.js', '/components/nthen/index.js', '/components/chainpad/chainpad.dist.js', diff --git a/src/common/worker-channel.js b/src/common/events-channel.js similarity index 100% rename from src/common/worker-channel.js rename to src/common/events-channel.js diff --git a/src/common/login-block.js b/src/common/login-block.js deleted file mode 100644 index 7b1223026..000000000 --- a/src/common/login-block.js +++ /dev/null @@ -1,245 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(() => { -const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => { - - var Block = {}; - - Block.setCustomize = data => { - ApiConfig = data.ApiConfig; - ServerCommand.setCustomize(data); - }; - - Block.join = Util.uint8ArrayJoin; - - // publickey - - // signature - - // block - - // [b64_public, b64_sig, b64_block [version, nonce, content]] - - Block.seed = function () { - return Nacl.hash(Util.decodeUTF8('pewpewpew')); - }; - - // should be deterministic from a seed... - Block.genkeys = function (seed) { - if (!(seed instanceof Uint8Array)) { - throw new Error('INVALID_SEED_FORMAT'); - } - if (!seed || typeof(seed.length) !== 'number' || seed.length < 64) { - throw new Error('INVALID_SEED_LENGTH'); - } - - var signSeed = seed.subarray(0, Nacl.sign.seedLength); - var symmetric = seed.subarray(Nacl.sign.seedLength, - Nacl.sign.seedLength + Nacl.secretbox.keyLength); - - return { - sign: Nacl.sign.keyPair.fromSeed(signSeed), // 32 bytes - symmetric: symmetric, // 32 bytes ... - }; - }; - - Block.keysToRPCFormat = function (keys) { - try { - var sign = keys.sign; - return { - edPrivate: Util.encodeBase64(sign.secretKey), - edPublic: Util.encodeBase64(sign.publicKey), - }; - } catch (err) { - console.error(err); - return; - } - }; - - // (UTF8 content, keys object) => Uint8Array block - Block.encrypt = function (version, content, keys) { - var u8 = Util.decodeUTF8(content); - var nonce = Nacl.randomBytes(Nacl.secretbox.nonceLength); - return Block.join([ - [0], - nonce, - Nacl.secretbox(u8, nonce, keys.symmetric) - ]); - }; - - // (uint8Array block) => payload object - Block.decrypt = function (u8_content, keys) { - // version is currently ignored since there is only one - var nonce = u8_content.subarray(1, 1 + Nacl.secretbox.nonceLength); - var box = u8_content.subarray(1 + Nacl.secretbox.nonceLength); - - var plaintext = Nacl.secretbox.open(box, nonce, keys.symmetric); - try { - return JSON.parse(Util.encodeUTF8(plaintext)); - } catch (e) { - console.error(e); - return; - } - }; - - // (Uint8Array block) => signature - Block.sign = function (ciphertext, keys) { - return Nacl.sign.detached(Nacl.hash(ciphertext), keys.sign.secretKey); - }; - - Block.serialize = function (content, keys) { - // encrypt the content - var ciphertext = Block.encrypt(0, content, keys); - - // generate a detached signature - var sig = Block.sign(ciphertext, keys); - - // serialize {publickey, sig, ciphertext} - return { - publicKey: Util.encodeBase64(keys.sign.publicKey), - signature: Util.encodeBase64(sig), - ciphertext: Util.encodeBase64(ciphertext), - }; - }; - - Block.proveAncestor = function (O /* oldBlockKeys, N, newBlockKeys */) { - var u8_pub = Util.find(O, ['sign', 'publicKey']); - var u8_secret = Util.find(O, ['sign', 'secretKey']); - try { - // sign your old publicKey with your old privateKey - var u8_sig = Nacl.sign.detached(u8_pub, u8_secret); - // return an array with the sig and the pubkey - return JSON.stringify([u8_pub, u8_sig].map(Util.encodeBase64)); - } catch (err) { - return void console.error(err); - } - }; - - var urlSafeB64 = function (u8) { - return Util.encodeBase64(u8).replace(/\//g, '-'); - }; - - Block.getBlockUrl = function (keys) { - var publicKey = urlSafeB64(keys.sign.publicKey); - // 'block/' here is hardcoded because it's hardcoded on the server - // if we want to make CryptPad work in server subfolders, we'll need - // to update this path derivation - return (ApiConfig.fileHost || ApiConfig.httpUnsafeOrigin || window.location.origin) - + '/block/' + publicKey.slice(0, 2) + '/' + publicKey; - }; - - Block.getBlockHash = function (keys) { - var absolute = Block.getBlockUrl(keys); - var symmetric = urlSafeB64(keys.symmetric); - return absolute + '#' + symmetric; - }; - - var decodeSafeB64 = function (b64) { - try { - return Util.decodeBase64(b64.replace(/\-/g, '/')); - } catch (e) { - console.error(e); - return; - } - }; - - Block.parseBlockHash = function (hash) { - if (typeof(hash) !== 'string') { return; } - var parts = hash.split('#'); - if (parts.length !== 2) { return; } - - try { - return { - href: parts[0], - keys: { - symmetric: decodeSafeB64(parts[1]), - } - }; - } catch (e) { - console.error(e); - return; - } - }; - - Block.checkRights = function (data, _cb) { - const cb = Util.mkAsync(_cb); - const { blockKeys, auth } = data; - - var command = 'MFA_CHECK'; - if (auth && auth.type) { command = `${auth.type.toUpperCase()}_` + command; } - - ServerCommand(blockKeys.sign, { - command: command, - auth: auth && auth.data - }, cb); - }; - Block.writeLoginBlock = function (data, cb) { - const { content, blockKeys, oldBlockKeys, auth, pw, session, token, userData } = data; - - var command = 'WRITE_BLOCK'; - if (auth && auth.type) { command = `${auth.type.toUpperCase()}_` + command; } - - var block = Block.serialize(JSON.stringify(content), blockKeys); - block.auth = auth && auth.data; - block.hasPassword = pw; - block.registrationProof = oldBlockKeys && Block.proveAncestor(oldBlockKeys); - if (token) { block.inviteToken = token; } - if (userData) { block.userData = userData; } - - ServerCommand(blockKeys.sign, { - command: command, - content: block, - session: session // sso session - }, cb); - }; - Block.removeLoginBlock = function (data, cb) { - const { reason, blockKeys, auth, edPublic } = data; - - var command = 'REMOVE_BLOCK'; - if (auth && auth.type) { command = `${auth.type.toUpperCase()}_` + command; } - - ServerCommand(blockKeys.sign, { - command: command, - auth: auth && auth.data, - edPublic: edPublic, - reason: reason - }, cb); - }; - - Block.updateSSOBlock = function (data, cb) { - const { blockKeys, oldBlockKeys } = data; - var oldProof = oldBlockKeys && Block.proveAncestor(oldBlockKeys); - - ServerCommand(blockKeys.sign, { - command: 'SSO_UPDATE_BLOCK', - ancestorProof: oldProof - }, cb); - - }; - - return Block; -}; - -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - require('./common-util'), - undefined, - require('./http-command'), - require('tweetnacl/nacl-fast') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/common-util.js', - '/api/config', - '/common/outer/http-command.js', - '/components/tweetnacl/nacl-fast.min.js', - ], (Util, ApiConfig, ServerCommand) => { - return factory(Util, ApiConfig, ServerCommand, window.nacl); - }); -} else { - // unsupported initialization -} - -})(); diff --git a/src/common/http-command.js b/src/common/outer/http-command.js similarity index 99% rename from src/common/http-command.js rename to src/common/outer/http-command.js index 6d62f809f..19839469d 100644 --- a/src/common/http-command.js +++ b/src/common/outer/http-command.js @@ -110,7 +110,7 @@ const factory = (nThen, Util, ApiConfig = {}, Nacl) => { if (typeof(module) !== 'undefined' && module.exports) { module.exports = factory( require('nthen'), - require('./common-util'), + require('../common-util'), undefined, require('tweetnacl/nacl-fast') ); diff --git a/www/common/outer/login-block.js b/src/common/outer/login-block.js similarity index 99% rename from www/common/outer/login-block.js rename to src/common/outer/login-block.js index 7b1223026..312e8b7e2 100644 --- a/www/common/outer/login-block.js +++ b/src/common/outer/login-block.js @@ -224,7 +224,7 @@ const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => { if (typeof(module) !== 'undefined' && module.exports) { module.exports = factory( - require('./common-util'), + require('../common-util'), undefined, require('./http-command'), require('tweetnacl/nacl-fast') diff --git a/src/common/proxy-manager.js b/src/common/proxy-manager.js index cb465ebd1..4469d3822 100644 --- a/src/common/proxy-manager.js +++ b/src/common/proxy-manager.js @@ -4,7 +4,7 @@ (() => { const factory = (UserObject, Util, Hash, - SF, Messages = {}, Feedback, nThen) => { + Messages = {}, Feedback, nThen, SF = {}) => { let setCustomize = data => { Messages = data.Messages; @@ -1843,20 +1843,20 @@ if (typeof(module) !== 'undefined' && module.exports) { require('./user-object'), require('./common-util'), require('./common-hash'), - require('../worker/components/sharedfolder'), undefined, require('./common-feedback'), - require('nthen') + require('nthen'), + require('../worker/components/sharedfolder'), ); } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { define([ '/common/user-object.js', '/common/common-util.js', '/common/common-hash.js', - '/common/outer/sharedfolder.js', '/customize/messages.js', '/common/common-feedback.js', '/components/nthen/index.js', + // sharedfolder.js not needed outside of worker ], factory); } else { // unsupported initialization diff --git a/src/worker/async-store.js b/src/worker/async-store.js index 69362c97c..e1410fb1c 100644 --- a/src/worker/async-store.js +++ b/src/worker/async-store.js @@ -2,14 +2,13 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { -const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager, +const factory = (Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Pinpad, Rpc, Merge, Cache, SF, AccountTS, DriveTS, Cursor, Support, Integration, OnlyOffice, Mailbox, Profile, Team, Messenger, History, - Calendar, Block, NetConfig, AppConfig = {}, + Calendar, Block, NetConfig, Crypto, ChainPad, CpNetflux, Listmap, Netflux, nThen) => { @@ -18,6 +17,9 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager, const window = globalThis; globalThis.nacl = globalThis.nacl || Crypto.Nacl; + let ApiConfig = {}; + let AppConfig = {}; + const Saferphore = Util.Saferphore; var onReadyEvt = Util.mkEvent(true); var onCacheReadyEvt = Util.mkEvent(true); @@ -3452,49 +3454,40 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager, }; }; -if (typeof(module) !== 'undefined' && module.exports) { - // Code from customize can't be laoded directly in the build - module.exports = factory( - undefined, - require('json.sortify'), - require('../common/user-object'), - require('../common/proxy-manager'), - require('./components/migrate-user-object'), - require('../common/common-hash'), - require('../common/common-util'), - require('../common/common-constants'), - require('../common/common-feedback'), - require('../common/common-realtime'), - require('../common/common-messaging'), - require('../common/pinpad'), - require('../common/rpc'), - require('./components/merge-drive'), - require('../common/cache-store'), - require('./components/sharedfolder'), - require('./components/account'), // .ts - require('./components/drive'), // .ts - require('./modules/cursor'), - require('./modules/support'), - require('./modules/integration'), - require('./modules/onlyoffice'), - require('./modules/mailbox'), - require('./modules/profile'), - require('./modules/team'), - require('./modules/messenger'), - require('./modules/history'), - require('./modules/calendar'), - require('../common/login-block'), - require('../common/network-config'), - undefined, - require('chainpad-crypto'), - require('chainpad'), - require('chainpad-netflux'), - require('chainpad-listmap'), - require('netflux-websocket'), - require('nthen') - ); -} else { - // unsupported initialization -} - -})(); +module.exports = factory( + require('json.sortify'), + require('../common/user-object'), + require('../common/proxy-manager'), + require('./components/migrate-user-object'), + require('../common/common-hash'), + require('../common/common-util'), + require('../common/common-constants'), + require('../common/common-feedback'), + require('../common/common-realtime'), + require('../common/common-messaging'), + require('../common/pinpad'), + require('../common/rpc'), + require('./components/merge-drive'), + require('../common/cache-store'), + require('./components/sharedfolder'), + require('./components/account'), // .ts + require('./components/drive'), // .ts + require('./modules/cursor'), + require('./modules/support'), + require('./modules/integration'), + require('./modules/onlyoffice'), + require('./modules/mailbox'), + require('./modules/profile'), + require('./modules/team'), + require('./modules/messenger'), + require('./modules/history'), + require('./modules/calendar'), + require('../common/outer/login-block'), + require('../common/network-config'), + require('chainpad-crypto'), + require('chainpad'), + require('chainpad-netflux'), + require('chainpad-listmap'), + require('netflux-websocket'), + require('nthen') +); diff --git a/src/worker/components/invitation.js b/src/worker/components/invitation.js index c066c7c21..0424809e5 100644 --- a/src/worker/components/invitation.js +++ b/src/worker/components/invitation.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(function () { var factory = function (Util, Cred, Nacl, Crypto) { var Invite = {}; @@ -102,21 +101,9 @@ var factory = function (Util, Cred, Nacl, Crypto) { return Invite; }; - if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - require("../../common/common-util"), - require("../../common/common-credential"), - require("tweetnacl/nacl-fast"), - require("chainpad-crypto") - ); - } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/common-util.js', - '/common/common-credential.js', - '/components/chainpad-crypto/crypto.js', - '/components/tweetnacl/nacl-fast.min.js', - ], function (Util, Cred, Crypto) { - return factory(Util, Cred, window.nacl, Crypto); - }); - } -}()); +module.exports = factory( + require("../../common/common-util"), + require("../../common/common-credential"), + require("tweetnacl/nacl-fast"), + require("chainpad-crypto") +); diff --git a/src/worker/components/mailbox-handlers.js b/src/worker/components/mailbox-handlers.js index 3f918e519..479d5c523 100644 --- a/src/worker/components/mailbox-handlers.js +++ b/src/worker/components/mailbox-handlers.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { const factory = (Messaging, Hash, Util, Crypto, Block) => { // Random timeout between 10 and 30 times your sync time (lag + chainpad sync) @@ -983,24 +982,10 @@ const factory = (Messaging, Hash, Util, Crypto, Block) => { }; }; -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - require('../../common/common-messaging'), - require('../../common/common-hash'), - require('../../common/common-util'), - require('chainpad-crypto'), - require('../../common/login-block') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/common-messaging.js', - '/common/common-hash.js', - '/common/common-util.js', - '/components/chainpad-crypto/crypto.js', - '/common/outer/login-block.js', - ], factory); -} else { - // unsupported initialization -} - -})(); +module.exports = factory( + require('../../common/common-messaging'), + require('../../common/common-hash'), + require('../../common/common-util'), + require('chainpad-crypto'), + require('../../common/outer/login-block') +); diff --git a/src/worker/components/merge-drive.js b/src/worker/components/merge-drive.js index c7dc5163a..6f950962b 100644 --- a/src/worker/components/merge-drive.js +++ b/src/worker/components/merge-drive.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { const factory = (Crypt, FO, Hash, Realtime) => { var exp = {}; @@ -91,22 +90,9 @@ const factory = (Crypt, FO, Hash, Realtime) => { return exp; }; -if (typeof(module) !== 'undefined' && module.exports) { - // Code from customize can't be laoded directly in the build - module.exports = factory( - require('../../common/cryptget'), - require('../../common/user-object'), - require('../../common/common-hash'), - require('../../common/common-realtime') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/cryptget.js', - '/common/user-object.js', - '/common/common-hash.js', - '/common/common-realtime.js', - ], factory); -} else { - // unsupported initialization -} -})(); +module.exports = factory( + require('../../common/cryptget'), + require('../../common/user-object'), + require('../../common/common-hash'), + require('../../common/common-realtime') +); diff --git a/src/worker/components/migrate-user-object.js b/src/worker/components/migrate-user-object.js index 638c062a6..c35442aa8 100644 --- a/src/worker/components/migrate-user-object.js +++ b/src/worker/components/migrate-user-object.js @@ -2,11 +2,11 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { const factory = (Feedback, Hash, Util, - Messaging, Crypt, Mailbox, Messages = {}, + Messaging, Crypt, Mailbox, Realtime, nThen, Crypto) => { + let Messages = {}; const setCustomize = data => { Messages = data.Messages; }; @@ -507,35 +507,14 @@ const factory = (Feedback, Hash, Util, return migrate; }; -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - require('../../common/common-feedback'), - require('../../common/common-hash'), - require('../../common/common-util'), - require('../../common/common-messaging'), - require('../../common/cryptget'), - require('../modules/mailbox'), - undefined, - require('../../common/common-realtime'), - require('nthen'), - require('chainpad-crypto') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/common-feedback.js', - '/common/common-hash.js', - '/common/common-util.js', - '/common/common-messaging.js', - '/common/cryptget.js', - '/common/outer/mailbox.js', - '/customize/messages.js', - '/common/common-realtime.js', - '/components/nthen/index.js', - '/components/chainpad-crypto/crypto.js', - ], factory); -} else { - // unsupported initialization -} - -})(); - +module.exports = factory( + require('../../common/common-feedback'), + require('../../common/common-hash'), + require('../../common/common-util'), + require('../../common/common-messaging'), + require('../../common/cryptget'), + require('../modules/mailbox'), + require('../../common/common-realtime'), + require('nthen'), + require('chainpad-crypto') +); diff --git a/src/worker/components/roster.js b/src/worker/components/roster.js index 9db315c3a..198b37f3c 100644 --- a/src/worker/components/roster.js +++ b/src/worker/components/roster.js @@ -2,8 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(function () { -var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto, Feedback) { +var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) { var Roster = {}; // this constant is somewhat arbitrary. @@ -618,7 +617,6 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto, Feedback) // deleted while you are open // emit an event var onChannelError = function (info) { - if (Feedback) { Feedback.send('ROSTER_CHANNEL_ERROR='+(info && info.type)); } if (info && info.type === "EUNKNOWN") { // chainpad-netflux should recover by itself return; @@ -926,39 +924,11 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto, Feedback) return Roster; }; - if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - require("../../common/common-util"), - require("../../common/common-hash"), - require('chainpad-netflux'), - require('json.sortify'), - require("nthen"), - require("chainpad-crypto"), - null // no feedback here - ); - } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - require.config({ paths: { 'json.sortify': '/components/json.sortify/dist/JSON.sortify' } }); - define([ - '/common/common-util.js', - '/common/common-hash.js', - 'chainpad-netflux', - 'json.sortify', - '/components/nthen/index.js', - '/components/chainpad-crypto/crypto.js', - '/common/common-feedback.js', - //'/components/tweetnacl/nacl-fast.min.js', - ], function (Util, Hash, CPNF, Sortify, nThen, Crypto, Feedback) { - return factory.apply(null, [ - Util, - Hash, - CPNF, - Sortify, - nThen, - Crypto, - Feedback - ]); - }); - } else { - // I'm not gonna bother supporting any other kind of instanciation - } -}()); +module.exports = factory( + require("../../common/common-util"), + require("../../common/common-hash"), + require('chainpad-netflux'), + require('json.sortify'), + require("nthen"), + require("chainpad-crypto") +); diff --git a/src/worker/components/sharedfolder.js b/src/worker/components/sharedfolder.js index 86832b9fa..4a65e599a 100644 --- a/src/worker/components/sharedfolder.js +++ b/src/worker/components/sharedfolder.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { const factory = (Hash, Util, UserObject, Cache, nThen, Crypto, Listmap, ChainPad) => { var SF = {}; @@ -387,31 +386,14 @@ const factory = (Hash, Util, UserObject, Cache, return SF; }; -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - require('../../common/common-hash'), - require('../../common/common-util'), - require('../../common/user-object'), - require('../../common/cache-store'), - require('nthen'), - require('chainpad-crypto'), - require('chainpad-listmap'), - require('chainpad') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/common-hash.js', - '/common/common-util.js', - '/common/user-object.js', - '/common/outer/cache-store.js', +module.exports = factory( + require('../../common/common-hash'), + require('../../common/common-util'), + require('../../common/user-object'), + require('../../common/cache-store'), + require('nthen'), + require('chainpad-crypto'), + require('chainpad-listmap'), + require('chainpad') +); - '/components/nthen/index.js', - '/components/chainpad-crypto/crypto.js', - 'chainpad-listmap', - '/components/chainpad/chainpad.dist.js', - ], factory); -} else { - // unsupported initialization -} - -})(); diff --git a/src/worker/core/interface.js b/src/worker/core/interface.js index 0ddc77c4f..e413eeae9 100644 --- a/src/worker/core/interface.js +++ b/src/worker/core/interface.js @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { const factory = (SRpc, Channel, Util) => { const Interface = {}; let store; @@ -122,19 +121,8 @@ const factory = (SRpc, Channel, Util) => { return Interface; }; -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - require('./store-rpc'), - require('../../common/worker-channel'), - require('../../common/common-util') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/outer/store-rpc.js', - '/common/outer/worker-channel.js', - '/common/common-util.js' - ], factory); -} else { - // unsupported initialization -} -})(); +module.exports = factory( + require('./store-rpc'), + require('../../common/events-channel'), + require('../../common/common-util') +); diff --git a/src/worker/core/store-rpc.js b/src/worker/core/store-rpc.js index 329a9a75c..1f048f764 100644 --- a/src/worker/core/store-rpc.js +++ b/src/worker/core/store-rpc.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { const factory = AStore => { var create = function (config) { var Store = AStore.create(config); @@ -118,16 +117,6 @@ const factory = AStore => { return { create }; }; -if (typeof(module) !== 'undefined' && module.exports) { - // Code from customize can't be laoded directly in the build - module.exports = factory( - require('../async-store') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/outer/async-store.js' - ], factory); -} else { - // unsupported initialization -} -})(); +module.exports = factory( + require('../async-store') +); diff --git a/src/worker/modules/calendar.js b/src/worker/modules/calendar.js index 22d1fe5e2..5aa9571a5 100644 --- a/src/worker/modules/calendar.js +++ b/src/worker/modules/calendar.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { const factory = (Util, Hash, Constants, Realtime, Cache, Rec, nThen, Listmap, FP, Crypto, ChainPad) => { var Calendar = {}; @@ -1208,38 +1207,16 @@ const factory = (Util, Hash, Constants, Realtime, Cache, Rec, return Calendar; }; -if (typeof(module) !== 'undefined' && module.exports) { - // Code from customize can't be laoded directly in the build - module.exports = factory( - require('../../common/common-util'), - require('../../common/common-hash'), - require('../../common/common-constants'), - require('../../common/common-realtime'), - require('../../common/cache-store'), - require('../../common/recurrence'), - require('nthen'), - require('chainpad-listmap'), - require('../../../www/lib/datepicker/flatpickr'), - require('chainpad-crypto'), - require('chainpad'), - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/common-util.js', - '/common/common-hash.js', - '/common/common-constants.js', - '/common/common-realtime.js', - '/common/outer/cache-store.js', - '/calendar/recurrence.js', - '/components/nthen/index.js', - 'chainpad-listmap', - '/lib/datepicker/flatpickr.js', - '/components/chainpad-crypto/crypto.js', - '/components/chainpad/chainpad.dist.js', - ], factory); -} else { - // unsupported initialization -} - -})(); - +module.exports = factory( + require('../../common/common-util'), + require('../../common/common-hash'), + require('../../common/common-constants'), + require('../../common/common-realtime'), + require('../../common/cache-store'), + require('../../common/recurrence'), + require('nthen'), + require('chainpad-listmap'), + require('../../../www/lib/datepicker/flatpickr'), + require('chainpad-crypto'), + require('chainpad'), +); diff --git a/src/worker/modules/cursor.js b/src/worker/modules/cursor.js index e7aa5840b..9a27f4dd9 100644 --- a/src/worker/modules/cursor.js +++ b/src/worker/modules/cursor.js @@ -2,10 +2,10 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { -const factory = (Util, Constants, Messages = {}, - AppConfig = {}, Crypto) => { - var Cursor = {}; +const factory = (Util, Constants, Crypto) => { + const Cursor = {}; + let Messages = {}; + let AppConfig = {}; Cursor.setCustomize = data => { Messages = data.Messages; @@ -286,25 +286,8 @@ const factory = (Util, Constants, Messages = {}, return Cursor; }; -if (typeof(module) !== 'undefined' && module.exports) { - // Code from customize can't be laoded directly in the build - module.exports = factory( - require('../../common/common-util'), - require('../../common/common-constants'), - undefined, - undefined, - require('chainpad-crypto') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/common-util.js', - '/common/common-constants.js', - '/customize/messages.js', - '/customize/application_config.js', - '/components/chainpad-crypto/crypto.js', - ], factory); -} else { - // unsupported initialization -} - -})(); +module.exports = factory( + require('../../common/common-util'), + require('../../common/common-constants'), + require('chainpad-crypto') +); diff --git a/src/worker/modules/history.js b/src/worker/modules/history.js index f27b57b36..1852272e9 100644 --- a/src/worker/modules/history.js +++ b/src/worker/modules/history.js @@ -2,10 +2,9 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { const factory = (Util, Hash, UserObject, nThen) => { - var History = {}; - var commands = {}; + const History = {}; + const commands = {}; var getAccountChannels = function (ctx) { var channels = []; @@ -257,22 +256,9 @@ const factory = (Util, Hash, UserObject, nThen) => { return History; }; -if (typeof(module) !== 'undefined' && module.exports) { - // Code from customize can't be laoded directly in the build - module.exports = factory( - require('../../common/common-util'), - require('../../common/common-hash'), - require('../../common/user-object'), - require('nthen') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/common-util.js', - '/common/common-hash.js', - '/common/user-object.js', - '/components/nthen/index.js', - ], factory); -} else { - // unsupported initialization -} -})(); +module.exports = factory( + require('../../common/common-util'), + require('../../common/common-hash'), + require('../../common/user-object'), + require('nthen') +); diff --git a/src/worker/modules/integration.js b/src/worker/modules/integration.js index a3ee75c72..ac733cfcd 100644 --- a/src/worker/modules/integration.js +++ b/src/worker/modules/integration.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { const factory = (Crypto) => { var Integration = {}; @@ -203,17 +202,6 @@ const factory = (Crypto) => { return Integration; }; -if (typeof(module) !== 'undefined' && module.exports) { - // Code from customize can't be laoded directly in the build - module.exports = factory( - require('chainpad-crypto') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/components/chainpad-crypto/crypto.js', - ], factory); -} else { - // unsupported initialization -} - -})(); +module.exports = factory( + require('chainpad-crypto') +); diff --git a/src/worker/modules/mailbox.js b/src/worker/modules/mailbox.js index a908886f4..16d569758 100644 --- a/src/worker/modules/mailbox.js +++ b/src/worker/modules/mailbox.js @@ -2,10 +2,10 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { -const factory = (BCast = {}, Util, Hash, +const factory = (Util, Hash, Realtime, Messaging, Notify, Handlers, CpNetflux, Crypto) => { - var Mailbox = {}; + const Mailbox = {}; + let BCast = {}; Mailbox.setCustomize = data => { BCast = data.Broadcast; @@ -673,32 +673,13 @@ proxy.mailboxes = { return Mailbox; }; -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - undefined, - require('../../common/common-util'), - require('../../common/common-hash'), - require('../../common/common-realtime'), - require('../../common/common-messaging'), - require('../../common/notify'), - require('../components/mailbox-handlers'), - require('chainpad-netflux'), - require('chainpad-crypto') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/api/broadcast', - '/common/common-util.js', - '/common/common-hash.js', - '/common/common-realtime.js', - '/common/common-messaging.js', - '/common/notify.js', - '/common/outer/mailbox-handlers.js', - 'chainpad-netflux', - '/components/chainpad-crypto/crypto.js', - ], factory); -} else { - // unsupported initialization -} - -})(); +module.exports = factory( + require('../../common/common-util'), + require('../../common/common-hash'), + require('../../common/common-realtime'), + require('../../common/common-messaging'), + require('../../common/notify'), + require('../components/mailbox-handlers'), + require('chainpad-netflux'), + require('chainpad-crypto') +); diff --git a/src/worker/modules/messenger.js b/src/worker/modules/messenger.js index 30cf1908d..c08131652 100644 --- a/src/worker/modules/messenger.js +++ b/src/worker/modules/messenger.js @@ -2,12 +2,12 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { const factory = (Crypto, Hash, Util, Realtime, Messaging, - Constants, Messages = {}, PadTypes, nThen) => { + Constants, PadTypes, nThen) => { var Curve = Crypto.Curve; - var Msg = {}; + const Msg = {}; + let Messages = {}; Msg.setCustomize = data => { Messages = data.Messages; @@ -1119,34 +1119,13 @@ const factory = (Crypto, Hash, Util, Realtime, Messaging, return Msg; }; -if (typeof(module) !== 'undefined' && module.exports) { - // Code from customize can't be laoded directly in the build - module.exports = factory( - require('chainpad-crypto'), - require('../../common/common-hash'), - require('../../common/common-util'), - require('../../common/common-realtime'), - require('../../common/common-messaging'), - require('../../common/common-constants'), - undefined, - require('../../common/pad-types'), - require('nthen') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/components/chainpad-crypto/crypto.js', - '/common/common-hash.js', - '/common/common-util.js', - '/common/common-realtime.js', - '/common/common-messaging.js', - '/common/common-constants.js', - '/customize/messages.js', - '/common/pad-types.js', - - '/components/nthen/index.js', - ], factory); -} else { - // unsupported initialization -} - -})(); +module.exports = factory( + require('chainpad-crypto'), + require('../../common/common-hash'), + require('../../common/common-util'), + require('../../common/common-realtime'), + require('../../common/common-messaging'), + require('../../common/common-constants'), + require('../../common/pad-types'), + require('nthen') +); diff --git a/src/worker/modules/onlyoffice.js b/src/worker/modules/onlyoffice.js index fd72ac8d6..8126584fb 100644 --- a/src/worker/modules/onlyoffice.js +++ b/src/worker/modules/onlyoffice.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { const factory = () => { var OO = {}; @@ -350,12 +349,4 @@ const factory = () => { return OO; }; -if (typeof(module) !== 'undefined' && module.exports) { - // Code from customize can't be laoded directly in the build - module.exports = factory(); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([], factory); -} else { - // unsupported initialization -} -})(); +module.exports = factory(); diff --git a/src/worker/modules/profile.js b/src/worker/modules/profile.js index 0a170fc11..9568e265f 100644 --- a/src/worker/modules/profile.js +++ b/src/worker/modules/profile.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { const factory = (Util, Hash, Constants, Realtime, Listmap, Crypto, ChainPad) => { var Profile = {}; @@ -156,29 +155,12 @@ const factory = (Util, Hash, Constants, Realtime, return Profile; }; -if (typeof(module) !== 'undefined' && module.exports) { - // Code from customize can't be laoded directly in the build - module.exports = factory( - require('../../common/common-util'), - require('../../common/common-hash'), - require('../../common/common-constants'), - require('../../common/common-realtime'), - require('chainpad-listmap'), - require('chainpad-crypto'), - require('chainpad') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/common-util.js', - '/common/common-hash.js', - '/common/common-constants.js', - '/common/common-realtime.js', - 'chainpad-listmap', - '/components/chainpad-crypto/crypto.js', - '/components/chainpad/chainpad.dist.js', - ], factory); -} else { - // unsupported initialization -} - -})(); +module.exports = factory( + require('../../common/common-util'), + require('../../common/common-hash'), + require('../../common/common-constants'), + require('../../common/common-realtime'), + require('chainpad-listmap'), + require('chainpad-crypto'), + require('chainpad') +); diff --git a/src/worker/modules/support.js b/src/worker/modules/support.js index 3250e218c..262cb37f7 100644 --- a/src/worker/modules/support.js +++ b/src/worker/modules/support.js @@ -2,10 +2,10 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { -const factory = (ApiConfig = {}, Util, Hash, Realtime, Pinpad, Crypt, +const factory = (Util, Hash, Realtime, Pinpad, Crypt, nThen, Crypto, Listmap, ChainPad, CpNetflux) => { - var Support = {}; + const Support = {}; + let ApiConfig = {}; Support.setCustomize = data => { ApiConfig = data.ApiConfig; @@ -1420,37 +1420,15 @@ const factory = (ApiConfig = {}, Util, Hash, Realtime, Pinpad, Crypt, return Support; }; -if (typeof(module) !== 'undefined' && module.exports) { - // Code from customize can't be laoded directly in the build - module.exports = factory( - undefined, - require('../../common/common-util'), - require('../../common/common-hash'), - require('../../common/common-realtime'), - require('../../common/pinpad'), - require('../../common/cryptget'), - require('nthen'), - require('chainpad-crypto'), - require('chainpad-listmap'), - require('chainpad'), - require('chainpad-netflux') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/api/config', - '/common/common-util.js', - '/common/common-hash.js', - '/common/common-realtime.js', - '/common/pinpad.js', - '/common/cryptget.js', - '/components/nthen/index.js', - '/components/chainpad-crypto/crypto.js', - 'chainpad-listmap', - '/components/chainpad/chainpad.dist.js', - 'chainpad-netflux' - ], factory); -} else { - // unsupported initialization -} - -})(); +module.exports = factory( + require('../../common/common-util'), + require('../../common/common-hash'), + require('../../common/common-realtime'), + require('../../common/pinpad'), + require('../../common/cryptget'), + require('nthen'), + require('chainpad-crypto'), + require('chainpad-listmap'), + require('chainpad'), + require('chainpad-netflux') +); diff --git a/src/worker/modules/team.js b/src/worker/modules/team.js index 8a3c240cb..79b675e9b 100644 --- a/src/worker/modules/team.js +++ b/src/worker/modules/team.js @@ -2,12 +2,11 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -(() => { const factory = (Util, Hash, Constants, Realtime, ProxyManager, UserObject, SF, Roster, Messaging, Feedback, Invite, Crypt, Cache, Pinpad, Listmap, Crypto, CpNetflux, ChainPad, nThen, Nacl) => { - var Team = {}; + const Team = {}; Nacl = Nacl || (typeof(window) !== "undefined" && window.nacl); var onStoreReady = Util.mkEvent(true); @@ -2241,59 +2240,27 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager, return Team; }; -if (typeof(module) !== 'undefined' && module.exports) { - // Code from customize can't be laoded directly in the build - module.exports = factory( - require('../../common/common-util'), - require('../../common/common-hash'), - require('../../common/common-constants'), - require('../../common/common-realtime'), +module.exports = factory( + require('../../common/common-util'), + require('../../common/common-hash'), + require('../../common/common-constants'), + require('../../common/common-realtime'), - require('../../common/proxy-manager'), - require('../../common/user-object'), - require('../components/sharedfolder'), - require('../components/roster'), - require('../../common/common-messaging'), - require('../../common/common-feedback'), - require('../components/invitation'), - require('../../common/cryptget'), - require('../../common/cache-store'), - require('../../common/pinpad'), + require('../../common/proxy-manager'), + require('../../common/user-object'), + require('../components/sharedfolder'), + require('../components/roster'), + require('../../common/common-messaging'), + require('../../common/common-feedback'), + require('../components/invitation'), + require('../../common/cryptget'), + require('../../common/cache-store'), + require('../../common/pinpad'), - require('chainpad-listmap'), - require('chainpad-crypto'), - require('chainpad-netflux'), - require('chainpad'), - require('nthen'), - require('tweetnacl/nacl-fast'), - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/common-util.js', - '/common/common-hash.js', - '/common/common-constants.js', - '/common/common-realtime.js', - - '/common/proxy-manager.js', - '/common/user-object.js', - '/common/outer/sharedfolder.js', - '/common/outer/roster.js', - '/common/common-messaging.js', - '/common/common-feedback.js', - '/common/outer/invitation.js', - '/common/cryptget.js', - '/common/outer/cache-store.js', - '/common/pinpad.js', - - 'chainpad-listmap', - '/components/chainpad-crypto/crypto.js', - 'chainpad-netflux', - '/components/chainpad/chainpad.dist.js', - '/components/nthen/index.js', - '/components/tweetnacl/nacl-fast.min.js', - ], factory); -} else { - // unsupported initialization -} - -})(); + require('chainpad-listmap'), + require('chainpad-crypto'), + require('chainpad-netflux'), + require('chainpad'), + require('nthen'), + require('tweetnacl/nacl-fast'), +); diff --git a/src/worker/store.ts b/src/worker/store.ts index 8ab95321a..7dfd0d7e3 100644 --- a/src/worker/store.ts +++ b/src/worker/store.ts @@ -24,7 +24,7 @@ import * as UOSetter from '../common/user-object-setter.js'; import * as Pinpad from '../common/pinpad.js'; import * as PadTypes from '../common/pad-types.js'; import * as NetworkConfig from '../common/network-config.js'; -import * as LoginBlock from '../common/login-block.js'; +import * as LoginBlock from '../common/outer/login-block.js'; // Core import * as Store from './async-store.js'; diff --git a/www/calendar/export.js b/www/calendar/export.js index 92ab261f4..261d810aa 100644 --- a/www/calendar/export.js +++ b/www/calendar/export.js @@ -7,7 +7,7 @@ define([ '/customize/pages.js', '/common/common-util.js', - '/calendar/recurrence.js', + '/common/recurrence.js', '/lib/ical.min.js' ], function (Pages, Util, Rec) { diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 82482d4ba..1a8cf6956 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -21,7 +21,7 @@ define([ '/customize/application_config.js', '/lib/calendar/tui-calendar.min.js', '/calendar/export.js', - '/calendar/recurrence.js', + '/common/recurrence.js', '/lib/datepicker/flatpickr.js', 'tui-date-picker', diff --git a/www/calendar/recurrence.js b/www/calendar/recurrence.js deleted file mode 100644 index 9fbb786ba..000000000 --- a/www/calendar/recurrence.js +++ /dev/null @@ -1,908 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(() => { -const factory = (Util) => { - var Rec = {}; - - const window = globalThis; - var debug = function () {}; - - // Get week number with any "WKST" (firts day of the week) - // Week 1 is the first week of the year containing at least 4 days in this year - // It depends on which day is considered the first day of the week (default Monday) - // In our case, wkst is a number matching the JS rule: 0 == Sunday - var getWeekNo = Rec.getWeekNo = function (date, wkst) { - if (typeof(wkst) !== "number") { wkst = 1; } // Default monday - - var newYear = new Date(date.getFullYear(),0,1); - var day = newYear.getDay() - wkst; //the day of week the year begins on - day = (day >= 0 ? day : day + 7); - var daynum = Math.floor((date.getTime() - newYear.getTime())/86400000) + 1; - var weeknum; - // Week 1 / week 53 - if (day < 4) { - weeknum = Math.floor((daynum+day-1)/7) + 1; - if (weeknum > 52) { - var nYear = new Date(date.getFullYear() + 1,0,1); - var nday = nYear.getDay() - wkst; - nday = nday >= 0 ? nday : nday + 7; - weeknum = nday < 4 ? 1 : 53; - } - } - else { - weeknum = Math.floor((daynum+day-1)/7); - } - return weeknum; - }; - - var getYearDay = function (date) { - var start = new Date(date.getFullYear(), 0, 0); - var diff = (date - start) + - ((start.getTimezoneOffset() - date.getTimezoneOffset()) * 60 * 1000); - var oneDay = 1000 * 60 * 60 * 24; - return Math.floor(diff / oneDay); - }; - var setYearDay = function (date, day) { - if (typeof(day) !== "number" || Math.abs(day) < 1 || Math.abs(day) > 366) { return; } - if (day < 0) { - var max = getYearDay(new Date(date.getFullYear(), 11, 31)); - day = max + day + 1; - } - date.setMonth(0); - date.setDate(day); - return true; - }; - - var getEndData = function (s, e) { - if (s > e) { return void console.error("Wrong data"); } - var days; - if (e.getFullYear() === s.getFullYear()) { - days = getYearDay(e) - getYearDay(s); - } else { // eYear < sYear - var tmp = new Date(s.getFullYear(), 11, 31); - var d1 = getYearDay(tmp) - getYearDay(s); // Number of days before December 31st - var de = getYearDay(e); - days = d1 + de; - while ((tmp.getFullYear()+1) < e.getFullYear()) { - tmp.setFullYear(tmp.getFullYear()+1); - days += getYearDay(tmp); - } - } - return { - h: e.getHours(), - m: e.getMinutes(), - days: days - }; - }; - var setEndData = function (s, e, data) { - e.setTime(+s); - if (!data) { return; } - e.setHours(data.h); - e.setMinutes(data.m); - e.setSeconds(0); - e.setDate(s.getDate() + data.days); - }; - - var DAYORDER = Rec.DAYORDER = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"]; - var getDayData = function (str) { - var pos = Number(str.slice(0,-2)); - var day = DAYORDER.indexOf(str.slice(-2)); - return pos ? [pos, day] : day; - }; - - var goToFirstWeekDay = function (date, wkst) { - var d = date.getDay(); - wkst = typeof(wkst) === "number" ? wkst : 1; - if (d >= wkst) { - date.setDate(date.getDate() - (d-wkst)); - } else { - date.setDate(date.getDate() - (7+d-wkst)); - } - }; - - var getDateStr = function (date) { - return date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate(); - }; - var FREQ = {}; - FREQ['daily'] = function (s, i) { - s.setDate(s.getDate()+i); - }; - FREQ['weekly'] = function (s,i) { - s.setDate(s.getDate()+(i*7)); - }; - FREQ['monthly'] = function (s,i) { - s.setMonth(s.getMonth()+i); - }; - FREQ['yearly'] = function (s,i) { - s.setFullYear(s.getFullYear()+i); - }; - - // EXPAND is used to create iterations added from a BYxxx rule - // dateA is the start date and b is the number or id of the BYxxx rule item - var EXPAND = {}; - EXPAND['month'] = function (dateS, origin, b) { - var oS = new Date(origin.start); - var a = dateS.getMonth() + 1; - var toAdd = (b-a+12)%12; - var m = dateS.getMonth() + toAdd; - dateS.setMonth(m); - dateS.setDate(oS.getDate()); - if (dateS.getMonth() !== m) { return; } // Day 31 may move us to the next month - return true; - }; - - - EXPAND['weekno'] = function (dateS, origin, week, rule) { - var wkst = rule && rule.wkst; - if (typeof(wkst) !== "number") { wkst = 1; } // Default monday - var oS = new Date(origin.start); - - var lastD = new Date(dateS.getFullYear(), 11, 31); // December 31st - var lastW = getWeekNo(lastD, wkst); // Last week of the year is either 52 or 53 - - var doubleOne = lastW === 1; - if (lastW === 1) { lastW = 52; } - - var a = getWeekNo(dateS, wkst); - if (!week || week > lastW) { return false; } // Week 53 may not exist this year - - if (week < 0) { week = lastW + week + 1; } // Turn negative week number into positive - - var toAdd = week - a; - var weekS = new Date(+dateS); - // Go to the selected week - weekS.setDate(weekS.getDate() + (toAdd * 7)); - goToFirstWeekDay(weekS, wkst); - - // Then make sure we are in the correct start day - var all = 'aaaaaaa'.split('').map(function (o, i) { - var date = new Date(+weekS); - date.setDate(date.getDate() + i); - if (date.getFullYear() !== dateS.getFullYear()) { return; } - return date.toLocaleDateString() !== oS.toLocaleDateString() && date; - }).filter(Boolean); - - // If we're looking for week 1 and the last week is a week 1, add the days - if (week === 1 && doubleOne) { - goToFirstWeekDay(lastD, wkst); - 'aaaaaaa'.split('').some(function (o, i) { - var date = new Date(+lastD); - date.setDate(date.getDate() + i); - if (date.toLocaleDateString() === oS.toLocaleDateString()) { return; } - if (date.getFullYear() > dateS.getFullYear()) { return true; } - all.push(date); - }); - } - - return all.length ? all : undefined; - }; - EXPAND['yearday'] = function (dateS, origin, b) { - var y = dateS.getFullYear(); - var state = setYearDay(dateS, b); - if (!state) { return; } // Invalid day "b" - if (dateS.getFullYear() !== y) { return; } // Day 366 make move us to the next year - return true; - }; - EXPAND['monthday'] = function (dateS, origin, b, rule) { - if (typeof(b) !== "number" || Math.abs(b) < 1 || Math.abs(b) > 31) { return false; } - - var setMonthDay = function (date, day) { - var m = date.getMonth(); - if (day < 0) { - var tmp = new Date(date.getFullYear(), date.getMonth()+1, 0); // Last day - day = tmp.getDate() + day + 1; - } - date.setDate(day); - return date.getMonth() === m; // Don't push if day 31 moved us to the next month - - }; - - // Monthly events - if (rule.freq === 'monthly') { - return setMonthDay(dateS, b); - } - - var all = 'aaaaaaaaaaaa'.split('').map(function (o, i) { - var date = new Date(dateS.getFullYear(), i, 1); - var ok = setMonthDay(date, b); - return ok ? date : undefined; - }).filter(Boolean); - return all.length ? all : undefined; - }; - EXPAND['day'] = function (dateS, origin, b, rule) { - // Here "b" can be a single day ("TU") or a position and a day ("1MO") - var day = getDayData(b); - var pos; - if (Array.isArray(day)) { - pos = day[0]; - day = day[1]; - } - - var all = []; - if (![0,1,2,3,4,5,6].includes(day)) { return false; } - - var filterPos = function (m) { - if (!pos) { return; } - - var _all = []; - 'aaaaaaaaaaaa'.split('').some(function (a, i) { - if (typeof(m) !== "undefined" && i !== m) { return; } - - var _pos; - var tmp = all.filter(function (d) { - return d.getMonth() === i; - }); - if (pos < 0) { - _pos = tmp.length + pos; - } else { - _pos = pos - 1; // An array starts at 0 but the recurrence rule starts at 1 - } - _all.push(tmp[_pos]); - - return typeof(m) !== "undefined" && i === m; - }); - all = _all.filter(Boolean); // The "5th" {day} won't always exist - }; - - var tmp; - if (rule.freq === 'yearly') { - tmp = new Date(+dateS); - var y = dateS.getFullYear(); - while (tmp.getDay() !== day) { tmp.setDate(tmp.getDate()+1); } - while (tmp.getFullYear() === y) { - all.push(new Date(+tmp)); - tmp.setDate(tmp.getDate()+7); - } - filterPos(); - return all; - } - - if (rule.freq === 'monthly') { - tmp = new Date(+dateS); - var m = dateS.getMonth(); - while (tmp.getDay() !== day) { tmp.setDate(tmp.getDate()+1); } - while (tmp.getMonth() === m) { - all.push(new Date(+tmp)); - tmp.setDate(tmp.getDate()+7); - } - filterPos(m); - return all; - } - - if (rule.freq === 'weekly') { - while (dateS.getDay() !== day) { dateS.setDate(dateS.getDate()+1); } - } - return true; - }; - - var LIMIT = {}; - LIMIT['month'] = function (events, rule) { - return events.filter(function (s) { - return rule.includes(s.getMonth()+1); - }); - }; - LIMIT['weekno'] = function (events, weeks, rules) { - return events.filter(function (s) { - var wkst = rules && rules.wkst; - if (typeof(wkst) !== "number") { wkst = 1; } // Default monday - - var lastD = new Date(s.getFullYear(), 11, 31); // December 31st - var lastW = getWeekNo(lastD, wkst); // Last week of the year is either 52 or 53 - if (lastW === 1) { lastW = 52; } - - var w = getWeekNo(s, wkst); - - return weeks.some(function (week) { - if (week > 0) { return week === w; } - return w === (lastW + week + 1); - }); - }); - }; - LIMIT['yearday'] = function (events, days) { - return events.filter(function (s) { - var d = getYearDay(s); - var max = getYearDay(new Date(s.getFullYear(), 11, 31)); - - return days.some(function (day) { - if (day > 0) { return day === d; } - return d === (max + day + 1); - }); - }); - }; - LIMIT['monthday'] = function (events, rule) { - return events.filter(function (s) { - var r = Util.clone(rule); - // Transform the negative monthdays into positive for this specific month - r = r.map(function (b) { - if (b < 0) { - var tmp = new Date(s.getFullYear(), s.getMonth()+1, 0); // Last day - b = tmp.getDate() + b + 1; - } - return b; - }); - return r.includes(s.getDate()); - }); - }; - LIMIT['day'] = function (events, days, rules) { - return events.filter(function (s) { - var dayStr = s.toLocaleDateString(); - - // Check how to handle position in BYDAY rules (last day of the month or the year?) - var type = 'yearly'; - if (rules.freq === 'monthly' || - (rules.freq === 'yearly' && rules.by && rules.by.month)) { - type = 'monthly'; - } - - // Check if this event matches one of the allowed days - return days.some(function (r) { - // rule elements are strings with pos and day - var day = getDayData(r); - var pos; - if (Array.isArray(day)) { - pos = day[0]; - day = day[1]; - } - if (!pos) { - return s.getDay() === day; - } - - // If we have a position, we can use EXPAND.day to get the nth {day} of the - // year/month and compare if it matches with - var d = new Date(s.getFullYear(), s.getMonth(), 1); - if (type === 'yearly') { d.setMonth(0); } - var res = EXPAND["day"](d, {}, r, {freq: type}); - return res.some(function (date) { - return date.toLocaleDateString() === dayStr; - }); - }); - }); - }; - LIMIT['setpos'] = function (events, rule) { - var init = events.slice(); - var rules = Util.deduplicateString(rule.slice().map(function (n) { - if (n > 0) { return (n-1); } - if (n === 0) { return; } - return init.length + n; - })); - return events.filter(function (ev) { - var idx = init.indexOf(ev); - return rules.includes(idx); - }); - }; - - var BYORDER = ['month','weekno','yearday','monthday','day']; - var BYDAYORDER = ['month','monthday','day']; - - Rec.getMonthId = function (d) { - return d.getFullYear() + '-' + d.getMonth(); - }; - var cache = window.CP_calendar_cache = {}; - var recurringAcross = {}; - Rec.resetCache = function () { - cache = window.CP_calendar_cache = {}; - recurringAcross = {}; - }; - - var iterate = function (rule, _origin, s) { - // "origin" is the original event to detect the start of BYxxx - var origin = Util.clone(_origin); - var oS = new Date(origin.start); - - var id = origin.id.split('|')[0]; // Use same cache when updating recurrence rule - - // "uid" is used for the cache - var uid = s.toLocaleDateString(); - cache[id] = cache[id] || {}; - - var inter = rule.interval || 1; - var freq = rule.freq; - - var all = []; - var limit = function (byrule, n) { - all = LIMIT[byrule](all, n, rule); - }; - var expand = function (byrule) { - return function (n) { - // Set the start date at the beginning of the current FREQ - var _s = new Date(+s); - if (rule.freq === 'yearly') { - // January 1st - _s.setMonth(0); - _s.setDate(1); - } else if (rule.freq === 'monthly') { - _s.setDate(1); - } else if (rule.freq === 'weekly') { - goToFirstWeekDay(_s, rule.wkst); - } else if (rule.freq === 'daily') { - // We don't have < byday rules so we can't expand daily rules - } - - var add = EXPAND[byrule](_s, origin, n, rule); - - if (!add) { return; } - - if (Array.isArray(add)) { - add = add.filter(function (dateS) { - return dateS.toLocaleDateString() !== oS.toLocaleDateString(); - }); - Array.prototype.push.apply(all, add); - } else { - if (_s.toLocaleDateString() === oS.toLocaleDateString()) { return; } - all.push(_s); - } - }; - }; - - // Manage interval for the next iteration - var it = Util.once(function () { - FREQ[freq](s, inter); - }); - var addDefault = function () { - if (freq === "monthly") { - s.setDate(15); - } else if (freq === "yearly" && oS.getMonth() === 1 && oS.getDate() === 29) { - s.setDate(28); - } - - it(); - - var _s = new Date(+s); - if (freq === "monthly" || freq === "yearly") { - _s.setDate(oS.getDate()); - if (_s.getDate() !== oS.getDate()) { return; } // If 31st or Feb 29th doesn't exist - if (freq === "yearly" && _s.getMonth() !== oS.getMonth()) { return; } - - // FIXME if there is a recUpdate that moves the 31st to the 30th, the event - // will still only be displayed on months with 31 days - } - all.push(_s); - }; - - if (Array.isArray(cache[id][uid])) { - debug('Get cache', id, uid); - if (freq === "monthly") { - s.setDate(15); - } else if (freq === "yearly" && oS.getMonth() === 1 && oS.getDate() === 29) { - s.setDate(28); - } - it(); - return cache[id][uid]; - } - - if (rule.by && freq === 'yearly') { - var order = BYORDER.slice(); - var monthLimit = false; - if (rule.by.weekno || rule.by.yearday || rule.by.monthday || rule.by.day) { - order.shift(); - monthLimit = true; - } - var first = true; - order.forEach(function (_order) { - var r = rule.by[_order]; - if (!r) { return; } - if (first) { - r.forEach(expand(_order)); - first = false; - } else if (_order === "day") { - if (rule.by.yearday || rule.by.monthday || rule.by.weekno) { - limit('day', rule.by.day); - } else { - rule.by.day.forEach(expand('day')); - } - } else { - limit(_order, r); - } - }); - if (rule.by.month && monthLimit) { - limit('month', rule.by.month); - } - } - if (rule.by && freq === 'monthly') { - // We're going to compute all the entries for the coming month - if (!rule.by.monthday && !rule.by.day) { - addDefault(); - } else if (rule.by.monthday) { - rule.by.monthday.forEach(expand('monthday')); - } else if (rule.by.day) { - rule.by.day.forEach(expand('day')); - } - if (rule.by.month) { - limit('month', rule.by.month); - } - if (rule.by.day && rule.by.monthday) { - limit('day', rule.by.day); - } - } - if (rule.by && freq === 'weekly') { - // We're going to compute all the entries for the coming week - if (!rule.by.day) { - addDefault(); - } else { - rule.by.day.forEach(expand('day')); - } - if (rule.by.month) { - limit('month', rule.by.month); - } - } - if (rule.by && freq === 'daily') { - addDefault(); - BYDAYORDER.forEach(function (_order) { - var r = rule.by[_order]; - if (!r) { return; } - limit(_order, r); - }); - } - - all.sort(function (a, b) { - return a-b; - }); - - if (rule.by && rule.by.setpos) { - limit('setpos', rule.by.setpos); - } - - if (!rule.by || !Object.keys(rule.by).length) { - addDefault(); - } else { - it(); - } - - - var done = []; - all = all.filter(function (newS) { - var start = new Date(+newS).toLocaleDateString(); - if (done.includes(start)) { return false; } - done.push(start); - return true; - }); - - debug('Set cache', id, uid); - cache[id][uid] = all; - - return all; - }; - - var getNextRules = function (obj) { - if (!obj.recUpdate) { return []; } - var _allRules = {}; - var _obj = obj.recUpdate.from; - Object.keys(_obj || {}).forEach(function (d) { - var u = _obj[d]; - if (u.recurrenceRule) { _allRules[d] = u.recurrenceRule; } - }); - return Object.keys(_allRules).sort(function (a, b) { return Number(a)-Number(b); }) - .map(function (k) { - var r = Util.clone(_allRules[k]); - if (!FREQ[r.freq]) { return; } - if (r.interval && r.interval < 1) { return; } - r._start = Number(k); - return r; - }).filter(Boolean); - }; - - var fixTimeZone = function (evTimeZone, origin, target) { - var getOffset = function (date, tz) { - // Get an ISO string using Canadian local format - let iso = date.toLocaleString('en-CA', { timeZone:tz, hour12: false }).replace(', ', 'T'); - iso += '.' + date.getMilliseconds().toString().padStart(3, '0'); - - // Get a UTC version of this time - let utcDate = new Date(iso + 'Z'); - - // Return the difference in timestamps, as minutes (60*1000) - return -(utcDate - date); - }; - - var myTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; - var offset = getOffset(origin, evTimeZone) - getOffset(target, evTimeZone); - var myOffset = getOffset(origin, myTimeZone) - getOffset(target, myTimeZone); - - return myOffset - offset; - }; - - Rec.getRecurring = function (months, events) { - if (window.CP_DEV_MODE) { debug = console.warn; } - - var toAdd = []; - months.forEach(function (monthId) { - // from 1st day of the month at 00:00 to last day at 23:59:59:999 - var ms = monthId.split('-'); - var _startMonth = new Date(ms[0], ms[1]); - var _endMonth = new Date(+_startMonth); - _endMonth.setMonth(_endMonth.getMonth() + 1); - _endMonth.setMilliseconds(-1); - - debug('Compute month', _startMonth.toLocaleDateString()); - - var rec = events || []; - rec.forEach(function (obj) { - var _start = new Date(obj.start); - var _end = new Date(obj.end); - var _origin = obj; - var rule = obj.recurrenceRule; - if (!rule) { return; } - - var nextRules = getNextRules(obj); - var nextRule = nextRules.shift(); - - if (_start >= _endMonth) { return; } - - // Check the "until" date of the latest rule we can use and stop now - // if the recurrence ends before the current month - var until = rule.until; - var _nextRules = nextRules.slice(); - var _nextRule = nextRule; - while (_nextRule && _nextRule._start && _nextRule._start < _startMonth) { - until = nextRule.until; - _nextRule = _nextRules.shift(); - } - if (until < _startMonth) { return; } - - var endData = getEndData(_start, _end); - - if (rule.interval && rule.interval < 1) { return; } - if (!FREQ[rule.freq]) { return; } - - /* - // Rule examples - rule.by = { - //month: [1, 4, 5, 8, 12], - //weekno: [1, 2, 4, 5, 32, 34, 35, 50], - //yearday: [1, 2, 29, 30, -2, -1, 250], - //monthday: [1, 2, 3, -3, -2, -1], - //day: ["MO", "WE", "FR"], - //setpos: [1, 2, -1, -2] - }; - rule.wkst = 0; - rule.interval = 2; - rule.freq = 'yearly'; - rule.count = 10; - */ - debug('Iterate over', obj.title, obj); - debug('Use rule', rule); - - var count = rule.count; - var c = 1; - - var next = function (start) { - var evS = new Date(+start); - - if (count && c >= count) { return; } - - debug('Start iteration', evS.toLocaleDateString()); - - var _toAdd = iterate(rule, obj, evS); - - debug('Iteration results', JSON.stringify(_toAdd.map(function (o) { return new Date(o).toLocaleDateString();}))); - - // Make sure to continue if the current year doesn't provide any result - if (!_toAdd.length) { - if (evS.getFullYear() < _startMonth.getFullYear() || - evS < _endMonth) { - return void next(evS); - } - return; - } - - - var stop = false; - var newrule = false; - _toAdd.some(function (_newS) { - // Make event with correct start and end time - var _ev = Util.clone(obj); - _ev.id = _origin.id + '|' + (+_newS); - var _evS = new Date(+_newS); - var _evE = new Date(+_newS); - setEndData(_evS, _evE, endData); - _ev.start = +_evS; - _ev.end = +_evE; - _ev._count = c; - if (_ev.isAllDay && _ev.startDay) { _ev.startDay = getDateStr(_evS); } - if (_ev.isAllDay && _ev.endDay) { _ev.endDay = getDateStr(_evE); } - - if (nextRule && _ev.start === nextRule._start) { - newrule = true; - } - - var useNewRule = function () { - if (!newrule) { return; } - debug('Use new rule', nextRule); - _ev._count = c; - count = nextRule.count; - c = 1; - evS = +_evS; - obj = _ev; - rule = nextRule; - nextRule = nextRules.shift(); - }; - - - if (c >= count) { // Limit reached - debug(_evS.toLocaleDateString(), 'count'); - stop = true; - return true; - } - if (_evS >= _endMonth) { // Won't affect us anymore - debug(_evS.toLocaleDateString(), 'endMonth'); - stop = true; - return true; - } - if (rule.until && _evS > rule.until) { - debug(_evS.toLocaleDateString(), 'until'); - stop = true; - return true; - } - if (_evS < _start) { // "Expand" rules may create events before the _start - debug(_evS.toLocaleDateString(), 'start'); - return; - } - c++; - if (_evE < _startMonth) { // Ended before the current month - // Nothing to display but continue the recurrence - debug(_evS.toLocaleDateString(), 'startMonth'); - if (newrule) { useNewRule(); } - return; - } - // If a recurring event start and end in different months, make sure - // it is only added once - if ((_evS < _endMonth && _evE >= _endMonth) || - (_evS < _startMonth && _evE >= _startMonth)) { - if (recurringAcross[_ev.id] && recurringAcross[_ev.id].includes(_ev.start)) { - return; - } else { - recurringAcross[_ev.id] = recurringAcross[_ev.id] || []; - recurringAcross[_ev.id].push(_ev.start); - } - - } - - // Add this event - if (_origin.timeZone && !_ev.isAllDay) { - var offset = fixTimeZone(_origin.timeZone, _start, _evS); - _ev.start += offset; - _ev.end += offset; - } - toAdd.push(_ev); - if (newrule) { - useNewRule(); - return true; - } - }); - if (!stop) { next(evS); } - }; - next(_start); - debug('Added this month (all events)', toAdd.map(function (ev) { - return new Date(ev.start).toLocaleDateString(); - })); - }); - }); - return toAdd; - }; - Rec.getAllOccurrences = function (ev) { - if (!ev.recurrenceRule) { return [ev.start]; } - var r = ev.recurrenceRule; - // In case of infinite recursion, we can't get all - if (!r.until && !r.count) { return false; } - var all = [ev.start]; - var d = new Date(ev.start); - d.setDate(15); // Make sure we won't skip a month if the event starts on day > 28 - var toAdd = []; - - var i = 0; - var check = function () { - return r.count ? (all.length < r.count) : (+d <= r.until); - }; - while ((toAdd = Rec.getRecurring([Rec.getMonthId(d)], [ev])) && check() && i < (r.count*12)) { - Array.prototype.push.apply(all, toAdd.map(function (_ev) { return _ev.start; })); - d.setMonth(d.getMonth() + 1); - i++; - } - - return all; - }; - - Rec.diffDate = function (oldTime, newTime) { - var n = new Date(newTime); - var o = new Date(oldTime); - - // Diff Days - var d = 0; - var mult = n < o ? -1 : 1; - while (n.toLocaleDateString() !== o.toLocaleDateString() || mult >= 10000) { - n.setDate(n.getDate() - mult); - d++; - } - d = mult * d; - - // Diff hours - n = new Date(newTime); - var h = n.getHours() - o.getHours(); - - // Diff minutes - var m = n.getMinutes() - o.getMinutes(); - - return { - d: d, - h: h, - m: m - }; - }; - - var sortUpdate = function (obj) { - return Object.keys(obj).sort(function (d1, d2) { - return Number(d1) - Number(d2); - }); - }; - Rec.applyUpdates = function (events) { - events.forEach(function (ev) { - ev.raw = { - start: ev.start, - end: ev.end, - }; - - if (!ev.recUpdate) { return; } - - var from = ev.recUpdate.from || {}; - var one = ev.recUpdate.one || {}; - var s = ev.start; - - // Add "until" date to our recurrenceRule if it has been modified in future occurences - var nextRules = getNextRules(ev).filter(function (r) { - return r._start > s; - }); - var nextRule = nextRules.shift(); - - var applyDiff = function (obj, k) { - var diff = obj[k]; // Diff is always compared to origin start/end - var d = new Date(ev.raw[k]); - d.setDate(d.getDate() + diff.d); - d.setHours(d.getHours() + diff.h); - d.setMinutes(d.getMinutes() + diff.m); - ev[k] = +d; - }; - - sortUpdate(from).forEach(function (d) { - if (s < Number(d)) { return; } - Object.keys(from[d]).forEach(function (k) { - if (k === 'start' || k === 'end') { return void applyDiff(from[d], k); } - if (k === "recurrenceRule" && !from[d][k]) { return; } - ev[k] = from[d][k]; - }); - }); - Object.keys(one[s] || {}).forEach(function (k) { - if (k === 'start' || k === 'end') { return void applyDiff(one[s], k); } - if (k === "recurrenceRule" && !one[s][k]) { return; } - ev[k] = one[s][k]; - }); - if (ev.deleted) { - Object.keys(ev).forEach(function (k) { - delete ev[k]; - }); - } - - if (nextRule && ev.recurrenceRule) { - ev.recurrenceRule._next = nextRule._start - 1; - } - - if (ev.reminders) { - ev.raw.reminders = ev.reminders; - } - }); - return events; - }; - - - return Rec; -}; - -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory(require('./common-util')); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define(['/common/common-util.js'], factory); -} else { - // unsupported initialization -} -})(); diff --git a/www/checkup/main.js b/www/checkup/main.js index 0f2f8cf6c..051c53c9f 100644 --- a/www/checkup/main.js +++ b/www/checkup/main.js @@ -15,7 +15,7 @@ define([ '/common/common-hash.js', '/common/common-util.js', '/common/pinpad.js', - '/common/outer/network-config.js', + '/common/network-config.js', '/common/outer/login-block.js', '/customize/pages.js', '/checkup/checkup-tools.js', diff --git a/www/common/common-constants.js b/www/common/common-constants.js deleted file mode 100644 index 1764ffbd1..000000000 --- a/www/common/common-constants.js +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(() => { -const factory = function (AppConfig = {}) { - return { - setCustomize: data => { - AppConfig = data.AppConfig; - }, - - // localStorage - userHashKey: 'User_hash', - userNameKey: 'User_name', - blockHashKey: 'Block_hash', - fileHashKey: 'FS_hash', - sessionJWT: 'Session_JWT', - ssoSeed: 'SSO_seed', - - // Store - displayNameKey: 'cryptpad.username', - oldStorageKey: 'CryptPad_RECENTPADS', - storageKey: 'filesData', - tokenKey: 'loginToken', - prefersDriveRedirectKey: 'prefersDriveRedirect', - isPremiumKey: 'isPremiumUser', - displayPadCreationScreen: 'displayPadCreationScreen', - deprecatedKey: 'deprecated', - MAX_TEAMS_SLOTS: AppConfig.maxTeamsSlots || 5, - MAX_TEAMS_OWNED: AppConfig.maxOwnedTeams || 5, - MAX_PREMIUM_TEAMS_SLOTS: Math.max(AppConfig.maxTeamsSlots || 0, AppConfig.maxPremiumTeamsSlots || 0) || 5, - MAX_PREMIUM_TEAMS_OWNED: Math.max(AppConfig.maxOwnedTeams || 0, AppConfig.maxPremiumTeamsOwned || 0) || 5, - // Apps - criticalApps: ['profile', 'settings', 'debug', 'admin', 'support', 'notifications', 'calendar', 'moderation', 'oldadmin'], // XXX oldadmin - earlyAccessApps: [] - }; -}; - -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory(undefined); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define(['/customize/application_config.js'], factory); -} else { - // unsupported initialization -} -})(); - diff --git a/www/common/common-credential.js b/www/common/common-credential.js deleted file mode 100644 index 151a1ced4..000000000 --- a/www/common/common-credential.js +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(function () { -var factory = function (AppConfig = {}, Scrypt) { - var Cred = {}; - - Cred.setCustomize = data => { - AppConfig = data.AppConfig; - }; - - Cred.MINIMUM_PASSWORD_LENGTH = typeof(AppConfig.minimumPasswordLength) === 'number'? - AppConfig.minimumPasswordLength: 8; // TODO 14 or higher is a decent default for 2023 - - Cred.MINIMUM_NAME_LENGTH = 1; - Cred.MAXIMUM_NAME_LENGTH = 64; - - // https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript - Cred.isEmail = function (email) { - var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - return re.test(String(email).toLowerCase()); - }; - - Cred.isLongEnoughPassword = function (passwd) { - return passwd.length >= Cred.MINIMUM_PASSWORD_LENGTH; - }; - - var isString = Cred.isString = function (x) { - return typeof(x) === 'string'; - }; - - // Maximum username length is enforced at registration time - // rather than in this function - // in order to maintain backwards compatibility with accounts - // that might have already registered with a longer name. - Cred.isValidUsername = function (name) { - return !!(isString(name) && name.length >= Cred.MINIMUM_NAME_LENGTH); - }; - - Cred.isValidPassword = function (passwd) { - return !!(passwd && isString(passwd)); - }; - - Cred.passwordsMatch = function (a, b) { - return isString(a) && isString(b) && a === b; - }; - - Cred.customSalt = function () { - return typeof(AppConfig.loginSalt) === 'string'? - AppConfig.loginSalt: ''; - }; - - Cred.deriveFromPassphrase = function (username, password, len, cb) { - Scrypt(password, - username + Cred.customSalt(), // salt - 8, // memoryCost (n) - 1024, // block size parameter (r) - len || 128, // dkLen - 200, // interruptStep - cb, - undefined); // format, could be 'base64' - }; - - Cred.dispenser = function (bytes) { - var entropy = { - used: 0, - }; - - // crypto hygeine - var consume = function (n) { - // explode if you run out of bytes - if (entropy.used + n > bytes.length) { - throw new Error('exceeded available entropy'); - } - if (typeof(n) !== 'number') { throw new Error('expected a number'); } - if (n <= 0) { - throw new Error('expected to consume a positive number of bytes'); - } - - // grab an unused slice of the entropy - // Note: Internet Explorer doesn't support .slice on Uint8Array - var A; - if (bytes.slice) { - A = bytes.slice(entropy.used, entropy.used + n); - } else { - A = bytes.subarray(entropy.used, entropy.used + n); - } - - // account for the bytes you used so you don't reuse bytes - entropy.used += n; - - //console.info("%s bytes of entropy remaining", bytes.length - entropy.used); - return A; - }; - - return consume; - }; - - return Cred; -}; - - if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - undefined, //require("../../customize.dist/application_config.js"), - require("scrypt-async") - ); - } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/customize/application_config.js', - '/components/scrypt-async/scrypt-async.min.js', - ], function (AppConfig) { - return factory(AppConfig, window.scrypt); - }); - } -}()); diff --git a/www/common/common-feedback.js b/www/common/common-feedback.js deleted file mode 100644 index e4ab25a14..000000000 --- a/www/common/common-feedback.js +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(() => { -const factory = (AppConfig = {}, Messages= {}) => { - var Feedback = {}; - - Feedback.setCustomize = data => { - Messages = data.Messages; - AppConfig = data.AppConfig; - }; - - Feedback.init = function (state) { - Feedback.state = state; - }; - - var randomToken = function () { - return Math.random().toString(16).replace(/0./, ''); - }; - var ajax = function (url, cb) { - var http = new XMLHttpRequest(); - http.open('HEAD', url); - http.onreadystatechange = function() { - if (this.readyState === this.DONE) { - if (cb) { cb(); } - } - }; - http.send(); - }; - Feedback.send = function (action, force, cb) { - if (typeof(cb) !== 'function') { cb = function () {}; } - if (AppConfig.disableFeedback) { return void cb(); } - if (!action) { return void cb(); } - if (force !== true) { - if (!Feedback.state) { return void cb(); } - } - - var href = '/common/feedback.html?' + action + '=' + randomToken(); - ajax(href, cb); - }; - - Feedback.reportAppUsage = function () { - var pattern = window.location.pathname.split('/') - .filter(function (x) { return x; }).join('.'); - if (/^#\/1\/view\//.test(window.location.hash)) { - Feedback.send(pattern + '_VIEW'); - } else { - Feedback.send(pattern); - } - }; - - Feedback.reportScreenDimensions = function () { - var h = window.innerHeight; - var w = window.innerWidth; - Feedback.send('DIMENSIONS:' + h + 'x' + w); - }; - Feedback.reportLanguage = function () { - if (!Messages) { return; } - Feedback.send('LANG_' + Messages._languageUsed); - }; - - - return Feedback; -}; - -if (typeof(module) !== 'undefined' && module.exports) { - // Code from customize can't be laoded directly in the build - module.exports = factory(undefined, undefined); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/customize/application_config.js', - '/customize/messages.js' - ], factory); -} else { - // unsupported initialization -} - -})(); diff --git a/www/common/common-hash.js b/www/common/common-hash.js deleted file mode 100644 index 0eb4faa37..000000000 --- a/www/common/common-hash.js +++ /dev/null @@ -1,766 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(function (window) { -var factory = function (Util, Crypto, Keys, Nacl) { - var Hash = window.CryptPad_Hash = {}; - - var uint8ArrayToHex = Util.uint8ArrayToHex; - var hexToBase64 = Util.hexToBase64; - var base64ToHex = Util.base64ToHex; - Hash.encodeBase64 = Util.encodeBase64; - Hash.decodeBase64 = Util.decodeBase64; - - // This implementation must match that on the server - // it's used for a checksum - Hash.hashChannelList = function (list) { - return Util.encodeBase64(Nacl.hash(Util - .decodeUTF8(JSON.stringify(list)))); - }; - - Hash.generateSignPair = function () { - var ed = Nacl.sign.keyPair(); - var makeSafe = function (key) { - return Crypto.b64RemoveSlashes(key).replace(/=+$/g, ''); - }; - return { - validateKey: Hash.encodeBase64(ed.publicKey), - signKey: Hash.encodeBase64(ed.secretKey), - safeValidateKey: makeSafe(Hash.encodeBase64(ed.publicKey)), - safeSignKey: makeSafe(Hash.encodeBase64(ed.secretKey)), - }; - }; - - Hash.getSignPublicFromPrivate = function (edPrivateSafeStr) { - var edPrivateStr = Crypto.b64AddSlashes(edPrivateSafeStr); - var privateKey = Util.decodeBase64(edPrivateStr); - var keyPair = Nacl.sign.keyPair.fromSecretKey(privateKey); - return Util.encodeBase64(keyPair.publicKey); - }; - Hash.getCurvePublicFromPrivate = function (curvePrivateSafeStr) { - var curvePrivateStr = Crypto.b64AddSlashes(curvePrivateSafeStr); - var privateKey = Util.decodeBase64(curvePrivateStr); - var keyPair = Nacl.box.keyPair.fromSecretKey(privateKey); - return Util.encodeBase64(keyPair.publicKey); - }; - - var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) { - var version = secret.version; - var data = secret.keys; - if (version === 0) { - return secret.channel + secret.key; - } - if (version === 1) { - if (!data.editKeyStr) { return; } - return '/1/edit/' + hexToBase64(secret.channel) + - '/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/'; - } - if (version === 2) { - if (!data.editKeyStr) { return; } - var pass = secret.password ? 'p/' : ''; - return '/2/' + secret.type + '/edit/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/' + pass; - } - }; - var getViewHashFromKeys = Hash.getViewHashFromKeys = function (secret) { - var version = secret.version; - var data = secret.keys; - if (version === 0) { return; } - if (version === 1) { - if (!data.viewKeyStr) { return; } - return '/1/view/' + hexToBase64(secret.channel) + - '/'+Crypto.b64RemoveSlashes(data.viewKeyStr)+'/'; - } - if (version === 2) { - if (!data.viewKeyStr) { return; } - var pass = secret.password ? 'p/' : ''; - return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass; - } - }; - - Hash.getHiddenHashFromKeys = function (type, secret, opts) { - opts = opts || {}; - var canEdit = (secret.keys && secret.keys.editKeyStr) || secret.key; - var mode = (!opts.view && canEdit) ? 'edit/' : 'view/'; - var pass = secret.password ? 'p/' : ''; - - if (secret.keys && secret.keys.fileKeyStr) { mode = ''; } - - var hash = '/3/' + type + '/' + mode + secret.channel + '/' + pass; - var hashData = Hash.parseTypeHash(type, hash); - if (hashData && hashData.getHash) { - return hashData.getHash(opts || {}); - } - return hash; - }; - - var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) { - var version = secret.version; - var data = secret.keys; - if (version === 0) { return; } - if (version === 1) { - return '/1/' + hexToBase64(secret.channel) + '/' + - Crypto.b64RemoveSlashes(data.fileKeyStr) + '/'; - } - if (version === 2) { - if (!data.fileKeyStr) { return; } - var pass = secret.password ? 'p/' : ''; - return '/2/' + secret.type + '/' + Crypto.b64RemoveSlashes(data.fileKeyStr) + '/' + pass; - } - }; - - Hash.getPublicSigningKeyString = Keys.serialize; - - var fixDuplicateSlashes = function (s) { - return s.replace(/\/+/g, '/'); - }; - - Hash.ephemeralChannelLength = 34; - Hash.createChannelId = function (ephemeral) { - var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(ephemeral? 17: 16)); - if ([32, 34].indexOf(id.length) === -1 || /[^a-f0-9]/.test(id)) { - throw new Error('channel ids must consist of 32 hex characters'); - } - return id; - }; - - /* Given a base64-encoded public key, deterministically derive a channel id - Used for support mailboxes - */ - Hash.getChannelIdFromKey = function (publicKey) { - if (!publicKey) { return; } - return uint8ArrayToHex(Hash.decodeBase64(publicKey).subarray(0,16)); - }; - - /* Given a base64-encoded asymmetric private key - derive the corresponding public key - */ - Hash.getBoxPublicFromSecret = function (priv) { - if (!priv) { return; } - var u8_priv = Hash.decodeBase64(priv); - var pair = Nacl.box.keyPair.fromSecretKey(u8_priv); - return Hash.encodeBase64(pair.publicKey); - }; - - /* Given a base64-encoded private key and public key - check that the keys are part of a valid keypair - */ - Hash.checkBoxKeyPair = function (priv, pub) { - if (!pub || !priv) { return false; } - var u8_priv = Hash.decodeBase64(priv); - var pair = Nacl.box.keyPair.fromSecretKey(u8_priv); - return pub === Hash.encodeBase64(pair.publicKey); - }; - - Hash.createRandomHash = function (type, password) { - var cryptor; - if (type === 'file') { - cryptor = Crypto.createFileCryptor2(void 0, password); - return getFileHashFromKeys({ - password: Boolean(password), - version: 2, - type: type, - keys: cryptor - }); - } - cryptor = Crypto.createEditCryptor2(void 0, void 0, password); - return getEditHashFromKeys({ - password: Boolean(password), - version: 2, - type: type, - keys: cryptor - }); - }; - -/* -Version 0 - /pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy -Version 1: Add support for read-only access - /code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI -Version 2: Add support for password-protection - /code/#/2/code/edit/u5ACvxAYmhvG0FtrNn9FJQcf/p/ -Version 3: Safe links - /code/#/3/code/edit/f0d8055aa640a97e7fd25020ca4e93b3/ -Version 4: Data URL when not a realtime link yet (new pad or "static" app) - /login/#/4/login/newpad=eyJocmVmIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwL2NvZGUvIy8yL2NvZGUvZWRpdC91NUFDdnhBWW1odkcwRnRyTm45RklRY2YvIn0%3D/ - /drive/#/4/drive/login=e30%3D/ -*/ - - var getLoginOpts = function (hashArr) { - var k; - // Check if we have a ownerKey for this pad - hashArr.some(function (data) { - if (/^login=/.test(data)) { - k = data.slice(6); - return true; - } - }); - return k || ''; - }; - var getNewPadOpts = function (hashArr) { - var k; - // Check if we have a ownerKey for this pad - hashArr.some(function (data) { - if (/^newpad=/.test(data)) { - k = data.slice(7); - return true; - } - }); - return k || ''; - }; - var getVersionHash = function (hashArr) { - var k; - // Check if we have a ownerKey for this pad - hashArr.some(function (data) { - if (/^hash=/.test(data)) { - k = data.slice(5); - return true; - } - }); - return k ? Crypto.b64AddSlashes(k) : ''; - }; - var getAuditorKey = function (hashArr) { - var k; - // Check if we have a ownerKey for this pad - hashArr.some(function (data) { - if (/^auditor=/.test(data)) { - k = data.slice(8); - return true; - } - }); - return k ? Crypto.b64AddSlashes(k) : ''; - }; - var getOwnerKey = function (hashArr) { - var k; - // Check if we have a ownerKey for this pad - hashArr.some(function (data) { - if (data.length === 86) { - k = data; - return true; - } - }); - return k; - }; - var parseTypeHash = Hash.parseTypeHash = function (type, hash) { - if (!hash) { return; } - var options = []; - var parsed = {}; - var hashArr = fixDuplicateSlashes(hash).split('/'); - - var addOptions = function () { - parsed.password = options.indexOf('p') !== -1; - parsed.present = options.indexOf('present') !== -1; - parsed.embed = options.indexOf('embed') !== -1; - parsed.versionHash = getVersionHash(options); - parsed.auditorKey = getAuditorKey(options); - parsed.newPadOpts = getNewPadOpts(options); - parsed.loginOpts = getLoginOpts(options); - parsed.ownerKey = getOwnerKey(options); - }; - - // Version 4: only login or newpad options, same for all the apps - if (hashArr[1] && hashArr[1] === '4') { - parsed.getHash = function (opts) { - if (!opts || !Object.keys(opts).length) { return ''; } - var hash = '/4/' + type + '/'; - if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; } - if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; } - return hash; - }; - parsed.getOptions = function () { - var options = {}; - if (parsed.newPadOpts) { options.newPadOpts = parsed.newPadOpts; } - if (parsed.loginOpts) { options.loginOpts = parsed.loginOpts; } - return options; - }; - - parsed.version = 4; - parsed.app = hashArr[2]; - options = hashArr.slice(3); - addOptions(); - - return parsed; - } - - // The other versions depends on the type - if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) { - parsed.type = 'pad'; - parsed.getHash = function () { - return hash; - }; - parsed.getOptions = function () { - return { - embed: parsed.embed, - present: parsed.present, - ownerKey: parsed.ownerKey, - versionHash: parsed.versionHash, - auditorKey: parsed.auditorKey, - newPadOpts: parsed.newPadOpts, - loginOpts: parsed.loginOpts, - password: parsed.password - }; - }; - - if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0 - // Old hash - parsed.channel = hash.slice(0, 32); - parsed.key = hash.slice(32, 56); - parsed.version = 0; - return parsed; - } - - // Version >= 1: more hash options - parsed.getHash = function (opts) { - var hash = hashArr.slice(0, 5).join('/') + '/'; - var owner = typeof(opts.ownerKey) !== "undefined" ? opts.ownerKey : parsed.ownerKey; - if (owner) { hash += owner + '/'; } - if (parsed.password || opts.password) { hash += 'p/'; } - if (opts.embed) { hash += 'embed/'; } - if (opts.present) { hash += 'present/'; } - var versionHash = typeof(opts.versionHash) !== "undefined" ? opts.versionHash : parsed.versionHash; - if (versionHash) { - hash += 'hash=' + Crypto.b64RemoveSlashes(versionHash) + '/'; - } - var auditorKey = typeof(opts.auditorKey) !== "undefined" ? opts.auditorKey : parsed.auditorKey; - if (auditorKey) { - hash += 'auditor=' + Crypto.b64RemoveSlashes(auditorKey) + '/'; - } - if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; } - if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; } - return hash; - }; - - if (hashArr[1] && hashArr[1] === '1') { // Version 1 - parsed.version = 1; - parsed.mode = hashArr[2]; - parsed.channel = hashArr[3]; - parsed.key = Crypto.b64AddSlashes(hashArr[4]); - - options = hashArr.slice(5); - addOptions(); - - return parsed; - } - if (hashArr[1] && hashArr[1] === '2') { // Version 2 - parsed.version = 2; - parsed.app = hashArr[2]; - parsed.mode = hashArr[3]; - parsed.key = hashArr[4]; - - options = hashArr.slice(5); - addOptions(); - - return parsed; - } - if (hashArr[1] && hashArr[1] === '3') { // Version 3: hidden hash - parsed.version = 3; - parsed.app = hashArr[2]; - parsed.mode = hashArr[3]; - parsed.channel = hashArr[4]; - - options = hashArr.slice(5); - addOptions(); - - return parsed; - } - return parsed; - } - parsed.getHash = function () { return hashArr.join('/'); }; - if (['media', 'file'].indexOf(type) !== -1) { - parsed.type = 'file'; - - parsed.getOptions = function () { - return { - embed: parsed.embed, - present: parsed.present, - ownerKey: parsed.ownerKey, - newPadOpts: parsed.newPadOpts, - loginOpts: parsed.loginOpts, - password: parsed.password - }; - }; - - parsed.getHash = function (opts) { - var hash = hashArr.slice(0, 4).join('/') + '/'; - var owner = typeof(opts.ownerKey) !== "undefined" ? opts.ownerKey : parsed.ownerKey; - if (owner) { hash += owner + '/'; } - if (parsed.password || opts.password) { hash += 'p/'; } - if (opts.embed) { hash += 'embed/'; } - if (opts.present) { hash += 'present/'; } - if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; } - if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; } - return hash; - }; - - if (hashArr[1] && hashArr[1] === '1') { - parsed.version = 1; - parsed.channel = hashArr[2].replace(/-/g, '/'); - parsed.key = hashArr[3].replace(/-/g, '/'); - options = hashArr.slice(4); - addOptions(); - return parsed; - } - - if (hashArr[1] && hashArr[1] === '2') { // Version 2 - parsed.version = 2; - parsed.app = hashArr[2]; - parsed.key = hashArr[3]; - - options = hashArr.slice(4); - addOptions(); - - return parsed; - } - - if (hashArr[1] && hashArr[1] === '3') { // Version 3: hidden hash - parsed.version = 3; - parsed.app = hashArr[2]; - parsed.channel = hashArr[3]; - - options = hashArr.slice(4); - addOptions(); - - return parsed; - } - return parsed; - } - if (['user'].indexOf(type) !== -1) { - parsed.type = 'user'; - if (hashArr[1] && hashArr[1] === '1') { - parsed.version = 1; - parsed.user = hashArr[2]; - parsed.pubkey = hashArr[3].replace(/-/g, '/'); - return parsed; - } - return parsed; - } - if (['invite'].indexOf(type) !== -1) { - parsed.type = 'invite'; - if (hashArr[1] && hashArr[1] === '2') { - parsed.version = 2; - parsed.app = hashArr[2]; - parsed.mode = hashArr[3]; - parsed.key = hashArr[4]; - - options = hashArr.slice(5); - parsed.password = options.indexOf('p') !== -1; - return parsed; - } - return parsed; - } - return; - }; - var parsePadUrl = Hash.parsePadUrl = function (href) { - var patt = /^https*:\/\/([^\/]*)\/(.*?)\//i; - - var ret = {}; - - if (!href) { return ret; } - if (href.slice(-1) !== '/' && href.slice(-1) !== '#') { href += '/'; } - href = href.replace(/\/\?[^#]+#/, '/#'); - - var idx; - - // When we start without a hash, use version 4 links to add login or newpad options - var getHash = function (opts) { - if (!opts || !Object.keys(opts).length) { return ''; } - var hash = '/4/' + ret.type + '/'; - if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; } - if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; } - return hash; - }; - ret.getUrl = function (options) { - options = options || {}; - var url = '/'; - if (!ret.type) { return url; } - url += ret.type + '/'; - // New pad with options: append the options to the hash - if (!ret.hashData && options && Object.keys(options).length) { - return url + '#' + getHash(options); - } - if (!ret.hashData) { return url; } - //if (ret.hashData.version === 0) { return url + '#' + ret.hash; } - //if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; } - var hash = ret.hashData.getHash(options); - url += '#' + hash; - return url; - }; - ret.getOptions = function () { - if (!ret.hashData || !ret.hashData.getOptions) { return {}; } - return ret.hashData.getOptions(); - }; - - if (!/^https*:\/\//.test(href)) { - // If it doesn't start with http(s), it should be a relative href - if (!/^\/($|[^\/])/.test(href)) { return ret; } - idx = href.indexOf('/#'); - ret.type = href.slice(1, idx); - if (idx === -1) { return ret; } - ret.hash = href.slice(idx + 2); - ret.hashData = parseTypeHash(ret.type, ret.hash); - return ret; - } - - href.replace(patt, function (a, domain, type) { - ret.domain = domain; - ret.type = type; - return ''; - }); - idx = href.indexOf('/#'); - if (idx === -1) { return ret; } - ret.hash = href.slice(idx + 2); - ret.hashData = parseTypeHash(ret.type, ret.hash); - return ret; - }; - - Hash.hashToHref = function (hash, type) { - return '/' + type + '/#' + hash; - }; - Hash.hrefToHash = function (href) { - var parsed = Hash.parsePadUrl(href); - return parsed.hash; - }; - - Hash.getRelativeHref = function (href) { - if (!href) { return; } - if (href.indexOf('#') === -1) { return; } - var parsed = parsePadUrl(href); - return '/' + parsed.type + '/#' + parsed.hash; - }; - - /* - * Returns all needed keys for a realtime channel - * - no argument: use the URL hash or create one if it doesn't exist - * - secretHash provided: use secretHash to find the keys - */ - Hash.getSecrets = function (type, secretHash, password) { - var secret = {}; - var generate = function () { - secret.keys = Crypto.createEditCryptor2(void 0, void 0, password); - secret.channel = base64ToHex(secret.keys.chanId); - secret.version = 2; - secret.type = type; - }; - if (!secretHash) { - generate(); - return secret; - } else { - var parsed; - var hash; - if (secretHash) { - if (!type) { throw new Error("getSecrets with a hash requires a type parameter"); } - parsed = parseTypeHash(type, secretHash); - hash = secretHash; - } - if (hash.length === 0) { - generate(); - return secret; - } - // old hash system : #{hexChanKey}{cryptKey} - // new hash system : #/{hashVersion}/{b64ChanKey}/{cryptKey} - if (parsed.version === 0) { - // Old hash - secret.channel = parsed.channel; - secret.key = parsed.key; - secret.version = 0; - } else if (parsed.version === 1) { - // New hash - secret.version = 1; - if (parsed.type === "pad") { - secret.channel = base64ToHex(parsed.channel); - if (parsed.mode === 'edit') { - secret.keys = Crypto.createEditCryptor(parsed.key); - secret.key = secret.keys.editKeyStr; - if (secret.channel.length !== 32 || secret.key.length !== 24) { - throw new Error("The channel key and/or the encryption key is invalid"); - } - } - else if (parsed.mode === 'view') { - secret.keys = Crypto.createViewCryptor(parsed.key); - if (secret.channel.length !== 32) { - throw new Error("The channel key is invalid"); - } - } - } else if (parsed.type === "file") { - secret.channel = base64ToHex(parsed.channel); - secret.keys = { - fileKeyStr: parsed.key, - cryptKey: Util.decodeBase64(parsed.key) - }; - } else if (parsed.type === "user") { - throw new Error("User hashes can't be opened (yet)"); - } - } else if (parsed.version === 2) { - // New hash - secret.version = 2; - secret.type = type; - secret.password = password; - if (parsed.type === "pad") { - if (parsed.mode === 'edit') { - secret.keys = Crypto.createEditCryptor2(parsed.key, void 0, password); - secret.channel = base64ToHex(secret.keys.chanId); - secret.key = secret.keys.editKeyStr; - if (secret.channel.length !== 32 || secret.key.length !== 24) { - throw new Error("The channel key and/or the encryption key is invalid"); - } - } - else if (parsed.mode === 'view') { - secret.keys = Crypto.createViewCryptor2(parsed.key, password); - secret.channel = base64ToHex(secret.keys.chanId); - if (secret.channel.length !== 32) { - throw new Error("The channel key is invalid"); - } - } - } else if (parsed.type === "file") { - secret.keys = Crypto.createFileCryptor2(parsed.key, password); - secret.channel = base64ToHex(secret.keys.chanId); - secret.key = secret.keys.fileKeyStr; - if (secret.channel.length !== 48 || secret.key.length !== 24) { - throw new Error("The channel key and/or the encryption key is invalid"); - } - } else if (parsed.type === "user") { - throw new Error("User hashes can't be opened (yet)"); - } - } - } - return secret; - }; - - Hash.getHashes = function (secret) { - var hashes = {}; - secret = JSON.parse(JSON.stringify(secret)); - - if (!secret.keys && !secret.key) { - return hashes; - } else if (!secret.keys) { - secret.keys = {}; - } - - if (secret.keys.editKeyStr || (secret.version === 0 && secret.key)) { - hashes.editHash = getEditHashFromKeys(secret); - } - if (secret.keys.viewKeyStr) { - hashes.viewHash = getViewHashFromKeys(secret); - } - if (secret.keys.fileKeyStr) { - hashes.fileHash = getFileHashFromKeys(secret); - } - return hashes; - }; - - Hash.getFormData = function (secret, hash, password) { - secret = secret || Hash.getSecrets('form', hash, password); - var keys = secret && secret.keys; - var secondary = keys && keys.secondaryKey; - if (!secondary) { return; } - var curvePair = Nacl.box.keyPair.fromSecretKey(Util.decodeUTF8(secondary).slice(0,32)); - var ret = {}; - ret.form_public = Util.encodeBase64(curvePair.publicKey); - var privateKey = ret.form_private = Util.encodeBase64(curvePair.secretKey); - - var auditorHash = Hash.getViewHashFromKeys({ - version: 1, - channel: secret.channel, - keys: { viewKeyStr: Util.encodeBase64(keys.cryptKey) } - }); - var _parsed = Hash.parseTypeHash('pad', auditorHash); - ret.form_auditorHash = _parsed.getHash({auditorKey: privateKey}); - - return ret; - }; - - // STORAGE - Hash.hrefToHexChannelId = function (href, password) { - var parsed = Hash.parsePadUrl(href); - if (!parsed || !parsed.hash) { return; } - var secret = Hash.getSecrets(parsed.type, parsed.hash, password); - return secret.channel; - }; - - Hash.getBlobPathFromHex = function (id) { - return '/blob/' + id.slice(0,2) + '/' + id; - }; - - Hash.serializeHash = function (hash) { - if (hash && hash.slice(-1) !== "/") { hash += "/"; } - return hash; - }; - - Hash.createInviteUrl = function (curvePublic, channel) { - channel = channel || Hash.createChannelId(); - return window.location.origin + '/invite/#/1/' + channel + - '/' + curvePublic.replace(/\//g, '-') + '/'; - }; - - Hash.isValidChannel = function (channelId) { - return /^[a-zA-Z0-9]{32,48}$/.test(channelId); - }; - - Hash.isValidHref = function (href) { - // Non-empty href? - if (!href) { return; } - var parsed = Hash.parsePadUrl(href); - // Can be parsed? - if (!parsed) { return; } - // Link to a CryptPad app? - if (!parsed.type) { return; } - // Valid hash? - if (parsed.hash) { - if (!parsed.hashData) { return; } - // Version should be a number - if (typeof(parsed.hashData.version) === "undefined") { return; } - // pads and files should have a base64 (or hex) key - if (parsed.hashData.type === 'pad' || parsed.hashData.type === 'file') { - if (!parsed.hashData.key && !parsed.hashData.channel) { return; } - if (parsed.hashData.key && !/^[a-zA-Z0-9+-/=]+$/.test(parsed.hashData.key)) { return; } - } - } - return parsed; - }; - - Hash.decodeDataOptions = function (opts) { - var b64 = decodeURIComponent(opts); - var str = Util.encodeUTF8(Util.decodeBase64(b64)); - return Util.tryParse(str) || {}; - }; - Hash.encodeDataOptions = function (opts) { - var str = JSON.stringify(opts); - var b64 = Util.encodeBase64(Util.decodeUTF8(str)); - return encodeURIComponent(b64); - }; - Hash.getNewPadURL = function (href, opts) { - var parsed = Hash.parsePadUrl(href); - var options = parsed.getOptions(); - options.newPadOpts = Hash.encodeDataOptions(opts); - return parsed.getUrl(options); - }; - Hash.getLoginURL = function (href, opts) { - var parsed = Hash.parsePadUrl(href); - var options = parsed.getOptions(); - options.loginOpts = Hash.encodeDataOptions(opts); - return parsed.getUrl(options); - }; - - return Hash; -}; - - if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - require("./common-util"), - require("chainpad-crypto"), - require("./common-signing-keys"), - require("tweetnacl/nacl-fast") - ); - } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/common-util.js', - '/components/chainpad-crypto/crypto.js', - '/common/common-signing-keys.js', - '/components/tweetnacl/nacl-fast.min.js' - ], function (Util, Crypto, Keys) { - return factory(Util, Crypto, Keys, window.nacl); - }); - } else { - // unsupported initialization - } -}(typeof(window) !== 'undefined'? window : {})); diff --git a/www/common/common-login.js b/www/common/common-login.js index f2c2a4a90..37cd08aa0 100644 --- a/www/common/common-login.js +++ b/www/common/common-login.js @@ -6,7 +6,7 @@ define([ 'chainpad-listmap', '/components/chainpad-crypto/crypto.js', '/common/common-util.js', - '/common/outer/network-config.js', + '/common/network-config.js', '/common/common-credential.js', '/components/chainpad/chainpad.dist.js', '/common/common-realtime.js', diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js deleted file mode 100644 index f9b7fde24..000000000 --- a/www/common/common-messaging.js +++ /dev/null @@ -1,161 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(() => { -const factory = (Crypto, Hash, Util, Constants, Realtime) => { - var Msg = {}; - - var createData = Msg.createData = function (proxy, hash) { - var data = { - channel: hash || Hash.createChannelId(), - displayName: proxy['cryptpad.username'], - profile: proxy.profile && proxy.profile.view, - edPublic: proxy.edPublic, - curvePublic: proxy.curvePublic, - notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']), - avatar: proxy.profile && proxy.profile.avatar, - uid: proxy.uid, - }; - if (hash === false) { delete data.channel; } - return data; - }; - - var getFriend = Msg.getFriend = function (proxy, pubkey) { - if (!pubkey) { return; } - if (pubkey === proxy.curvePublic) { - var data = createData(proxy); - delete data.channel; - return data; - } - return proxy.friends ? proxy.friends[pubkey] : undefined; - }; - - var getFriendList = Msg.getFriendList = function (proxy) { - if (!proxy.friends) { proxy.friends = {}; } - return proxy.friends; - }; - - var eachFriend = function (friends, cb) { - Object.keys(friends).forEach(function (id) { - if (id === 'me') { return; } - cb(friends[id], id, friends); - }); - }; - - Msg.getFriendChannelsList = function (proxy) { - var list = []; - eachFriend(proxy.friends, function (friend) { - list.push(friend.channel); - }); - return list; - }; - - Msg.declineFriendRequest = function (store, data, cb) { - store.mailbox.sendTo('DECLINE_FRIEND_REQUEST', {}, { - channel: data.notifications, - curvePublic: data.curvePublic - }, function (obj) { - cb(obj); - }); - }; - Msg.acceptFriendRequest = function (store, data, cb) { - var friend = getFriend(store.proxy, data.curvePublic) || {}; - var myData = createData(store.proxy, friend.channel || data.channel); - store.mailbox.sendTo('ACCEPT_FRIEND_REQUEST', { user: myData }, { - channel: data.notifications, - curvePublic: data.curvePublic - }, function (obj) { - cb(obj); - }); - }; - Msg.addToFriendList = function (cfg, data, cb) { - var proxy = cfg.proxy; - var friends = getFriendList(proxy); - var pubKey = data.curvePublic; // todo validata data - - if (pubKey === proxy.curvePublic) { return void cb("E_MYKEY"); } - - friends[pubKey] = data; - - Realtime.whenRealtimeSyncs(cfg.realtime, function () { - cb(); - cfg.pinPads([data.channel], function (res) { - if (res.error) { console.error(res.error); } - }); - }); - }; - - Msg.updateMyData = function (store, curve) { - var myData = createData(store.proxy, false); - if (store.proxy.friends) { - store.proxy.friends.me = Util.clone(myData); - delete store.proxy.friends.me.channel; - } - if (store.modules['team']) { - store.modules['team'].updateMyData(myData); - } - var todo = function (friend) { - if (!friend || !friend.notifications) { return; } - delete friend.user; - myData.channel = friend.channel; - store.mailbox.sendTo('UPDATE_DATA', myData, { - channel: friend.notifications, - curvePublic: friend.curvePublic - }, function (obj) { - if (obj && obj.error) { console.error(obj); } - }); - }; - if (curve) { - var friend = getFriend(store.proxy, curve); - return void todo(friend); - } - eachFriend(store.proxy.friends || {}, todo); - }; - - Msg.removeFriend = function (store, curvePublic, cb) { - var proxy = store.proxy; - var friend = proxy.friends[curvePublic]; - if (!friend) { return void cb({error: 'ENOENT'}); } - if (!friend.notifications) { return void cb({error: 'EINVAL'}); } - - store.mailbox.sendTo('UNFRIEND', { - curvePublic: proxy.curvePublic - }, { - channel: friend.notifications, - curvePublic: friend.curvePublic - }, function (obj) { - if (obj && obj.error) { - return void cb(obj); - } - store.messenger.onFriendRemoved(curvePublic, friend.channel); - delete proxy.friends[curvePublic]; - Realtime.whenRealtimeSyncs(store.realtime, function () { - cb(obj); - }); - }); - }; - - return Msg; -}; - -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - require('chainpad-crypto'), - require('./common-hash'), - require('./common-util'), - require('./common-constants'), - require('./common-realtime') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/components/chainpad-crypto/crypto.js', - '/common/common-hash.js', - '/common/common-util.js', - '/common/common-constants.js', - '/common/common-realtime.js', - ], factory); -} else { - // unsupported initialization -} -})(); diff --git a/www/common/common-realtime.js b/www/common/common-realtime.js deleted file mode 100644 index cdf942609..000000000 --- a/www/common/common-realtime.js +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(() => { -const factory = () => { - var common = {}; - - /* - TODO make this not blow up when disconnected or lagging... - */ - common.whenRealtimeSyncs = function (realtime, cb) { - if (typeof(realtime.getAuthDoc) !== 'function') { - return void console.error('improper use of this function'); - } - setTimeout(function () { - if (realtime.getAuthDoc() === realtime.getUserDoc()) { - return void cb(); - } else { - realtime.onSettle(cb); - } - }, 0); - }; - - return common; -}; - -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory(); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([], factory); -} else { - // unsupported initialization -} - -})(); diff --git a/www/common/common-signing-keys.js b/www/common/common-signing-keys.js deleted file mode 100644 index 1d1132c34..000000000 --- a/www/common/common-signing-keys.js +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(function () { -var factory = function () { - var Keys = {}; - - var unescape = function (s) { - return s.replace(/-/g, '/'); - }; - -/* Parse the new format of "Signing Public Keys". - If anything about the input is found to be invalid, return; - this will fall back to the old parsing method - - -*/ - var parseNewUser = function (userString) { - if (!/^\[.*?@.*\]$/.test(userString)) { return; } - var temp = userString.slice(1, -1); - var domain, username, pubkey; - - temp = temp - .replace(/\/([a-zA-Z0-9+-]{43}=)$/, function (all, k) { - pubkey = unescape(k); - return ''; - }); - if (!pubkey) { return; } - - var index = temp.lastIndexOf('@'); - if (index < 1) { return; } - - domain = temp.slice(index + 1); - username = temp.slice(0, index); - - return { - domain: domain, - user: username, - pubkey: pubkey - }; - }; - - var isValidUser = function (parsed) { - if (!parsed) { return; } - if (!(parsed.domain && parsed.user && parsed.pubkey)) { return; } - return true; - }; - - Keys.parseUser = function (user) { - var parsed = parseNewUser(user); - if (isValidUser(parsed)) { return parsed; } - - var domain, username, pubkey; - user.replace(/^https*:\/\/([^\/]+)\/user\/#\/1\/([^\/]+)\/([a-zA-Z0-9+-]{43}=)$/, - function (a, d, u, k) { - domain = d; - username = u; - pubkey = unescape(k); - return ''; - }); - if (!domain) { throw new Error("Could not parse user id [" + user + "]"); } - return { - domain: domain, - user: username, - pubkey: pubkey - }; - }; - -/* - -0. usernames may contain spaces or many other wacky characters, so enclose the whole thing in square braces so we know its boundaries. If the formatted string does not include these we know it is either a _v1 public key string_ or _an incomplete string_. Start parsing by removing them. -1. public keys should have a fixed length, so slice them off of the end of the string. -2. domains cannot include `@`, so find the last occurence of it in the signing key and slice everything thereafter. -3. the username is everything before the `@`. - -*/ - Keys.serialize = function (origin, username, pubkey) { - return '[' + - username + - '@' + - origin.replace(/https*:\/\//, '') + - '/' + - pubkey.replace(/\//g, '-') + - ']'; - // return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-'); - }; - - Keys.canonicalize = function (input) { - if (typeof(input) !== 'string') { return; } - // key is already in simple form. ensure that it is an 'unsafeKey' - if (input.length === 44) { - return unescape(input); - } - try { - return Keys.parseUser(input).pubkey; - } catch (err) { - return; - } - }; - - return Keys; -}; - - if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory(); - } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([], factory); - } -}()); diff --git a/www/common/common-util.js b/www/common/common-util.js deleted file mode 100644 index c48bf809f..000000000 --- a/www/common/common-util.js +++ /dev/null @@ -1,882 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(function (window) { -const factory = (NaclUtil) => { - var Util = window.CryptPad_Util = {}; - - // polyfill for atob in case you're using this from node... - window.atob = window.atob || function (str) { return Buffer.from(str, 'base64').toString('binary'); }; - window.btoa = window.btoa || function (str) { return Buffer.from(str, 'binary').toString('base64'); }; - - Util.encodeBase64 = NaclUtil.encodeBase64; - Util.decodeBase64 = str => { - let i = str.length % 4; - if (i) { str += '='.repeat(4-i); } - return NaclUtil.decodeBase64(str); - }; - Util.encodeUTF8 = NaclUtil.encodeUTF8; - Util.decodeUTF8 = NaclUtil.decodeUTF8; - - Util.slice = function (A, start, end) { - return Array.prototype.slice.call(A, start, end); - }; - - Util.u8ToBase64 = (u8, cb) => { - const reader = new FileReader(); - reader.onload = () => { - let res = reader.result; - let trim = res.slice(res.indexOf(',') + 1); - cb(trim); - }; - reader.readAsDataURL(new Blob([u8])); - }; - - - - Util.shuffleArray = function (a) { - for (var i = a.length - 1; i > 0; i--) { - var j = Math.floor(Math.random() * (i + 1)); - var tmp = a[i]; - a[i] = a[j]; - a[j] = tmp; - } - }; - - Util.bake = function (f, args) { - if (typeof(args) === 'undefined') { args = []; } - if (!Array.isArray(args)) { args = [args]; } - return function () { - return f.apply(null, args); - }; - }; - - Util.both = function (pre, post) { - if (typeof(pre) !== 'function') { throw new Error('INVALID_USAGE'); } - if (typeof(post) !== 'function') { post = function (x) { return x; }; } - return function () { - pre.apply(null, arguments); - return post.apply(null, arguments); - }; - }; - - Util.clone = function (o) { - if (o === undefined || o === null) { return o; } - return JSON.parse(JSON.stringify(o)); - }; - - Util.serializeError = function (err) { - if (!(err instanceof Error)) { return err; } - var ser = {}; - Object.getOwnPropertyNames(err).forEach(function (key) { - ser[key] = err[key]; - }); - return ser; - }; - - Util.tryParse = function (s) { - try { return JSON.parse(s); } catch (e) { return;} - }; - - Util.mkAsync = function (f, ms) { - if (typeof(f) !== 'function') { - throw new Error('EXPECTED_FUNCTION'); - } - return function () { - var args = Array.prototype.slice.call(arguments); - setTimeout(function () { - f.apply(null, args); - }, ms); - }; - }; - - // If once is true, after the event has been fired, any further handlers which are - // registered will fire immediately, and this type of event cannot be fired twice. - Util.mkEvent = function (once) { - var handlers = []; - var fired = false; - let promiseResolve; - - const promise = new Promise(resolve => { - promiseResolve = resolve; - }); - - return { - reg: function (cb) { - if (once && fired) { return void setTimeout(cb); } - handlers.push(cb); - }, - unreg: function (cb) { - if (handlers.indexOf(cb) === -1) { - return void console.log("event handler was already unregistered"); - } - handlers.splice(handlers.indexOf(cb), 1); - }, - fire: function () { - if (once && fired) { return; } - var args = Array.prototype.slice.call(arguments); - if (!fired) { promiseResolve.apply(null, args); } - fired = true; - handlers.forEach(function (h) { h.apply(null, args); }); - }, - // Since a promise can only resolve once only the 1st call to fire() is reflected here. Even is `once` is `false`. - promise - }; - }; - - Util.mkTimeout = function (_f, ms) { - ms = ms || 0; - var f = Util.once(_f); - - var timeout = setTimeout(function () { - f('TIMEOUT'); - }, ms); - - return Util.both(f, function () { - clearTimeout(timeout); - }); - }; - - Util.onClickEnter = function ($element, handler, cfg) { - $element.on('click keydown', function (e) { - var isClick = e.type === 'click'; - var isEnter = e.type === 'keydown' && e.which === 13; - var isSpace = e.type === 'keydown' && e.which === 32 && cfg && cfg.space; - if (!isClick && !isEnter && !isSpace) { return; } - - // "enter" on a button triggers a click, disable it - if (e.type === 'keydown') { e.preventDefault(); } - - handler(e); - }); - }; - - Util.response = function (errorHandler) { - var pending = {}; - var timeouts = {}; - - if (typeof(errorHandler) !== 'function') { - errorHandler = function (label) { - throw new Error(label); - }; - } - - var clear = function (id) { - clearTimeout(timeouts[id]); - delete timeouts[id]; - delete pending[id]; - }; - - var expect = function (id, fn, ms) { - if (typeof(id) !== 'string') { errorHandler('EXPECTED_STRING'); } - if (typeof(fn) !== 'function') { errorHandler('EXPECTED_CALLBACK'); } - pending[id] = fn; - if (typeof(ms) === 'number' && ms) { - timeouts[id] = setTimeout(function () { - if (typeof(pending[id]) === 'function') { pending[id]('TIMEOUT'); } - clear(id); - }, ms); - } - }; - - var handle = function (id, args) { - var fn = pending[id]; - if (typeof(fn) !== 'function') { - return void errorHandler("MISSING_CALLBACK", { - id: id, - args: args, - }); - } - try { - fn.apply(null, Array.isArray(args)? args : [args]); - } catch (err) { - errorHandler('HANDLER_ERROR', { - error: err, - id: id, - args: args, - }); - } - clear(id); - }; - - return { - clear: clear, - expected: function (id) { - return Boolean(pending[id]); - }, - expectation: function (id) { - return pending[id]; - }, - expect: expect, - handle: handle, - _pending: pending, - }; - }; - - Util.inc = function (map, key, val) { - map[key] = (map[key] || 0) + (typeof(val) === 'number'? val: 1); - }; - - Util.values = function (obj) { - return Object.keys(obj).map(function (k) { - return obj[k]; - }); - }; - - Util.find = function (map, path) { - var l = path.length; - for (var i = 0; i < l; i++) { - if (typeof(map[path[i]]) === 'undefined') { return; } - map = map[path[i]]; - } - return map; - }; - - Util.uid = function () { - return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)) - .toString(32).replace(/\./g, ''); - }; - - Util.guid = function (map) { - var id = Util.uid(); - // the guid (globally unique id) is valid if it does already exist in the map - if (typeof(map[id]) === 'undefined') { return id; } - // otherwise try again - return Util.guid(map); - }; - - Util.fixHTML = function (str) { - if (!str) { return ''; } - return str.replace(/[<>&"']/g, function (x) { - return ({ "<": "<", ">": ">", "&": "&", '"': """, "'": "'" })[x]; - }); - }; - - Util.hexToBase64 = function (hex) { - var hexArray = hex - .replace(/\r|\n/g, "") - .replace(/([\da-fA-F]{2}) ?/g, "0x$1 ") - .replace(/ +$/, "") - .split(" "); - var byteString = String.fromCharCode.apply(null, hexArray); - return window.btoa(byteString).replace(/\//g, '-').replace(/=+$/, ''); - }; - - Util.base64ToHex = function (b64String) { - var hexArray = []; - window.atob(b64String.replace(/-/g, '/')).split("").forEach(function(e){ - var h = e.charCodeAt(0).toString(16); - if (h.length === 1) { h = "0"+h; } - hexArray.push(h); - }); - return hexArray.join(""); - }; - - Util.uint8ArrayToHex = function (bytes) { - var hexString = ''; - for (var i = 0; i < bytes.length; i++) { - if (bytes[i] < 16) { hexString += '0'; } - hexString += bytes[i].toString(16); - } - return hexString; - }; - - Util.hexToUint8Array = function (hexString) { - var bytes = new Uint8Array(Math.ceil(hexString.length / 2)); - for (var i = 0; i < bytes.length; i++) { - bytes[i] = parseInt(hexString.substr(i * 2, 2), 16); - } - return bytes; - }; - - // given an array of Uint8Arrays, return a new Array with all their values - Util.uint8ArrayJoin = function (AA) { - var l = 0; - var i = 0; - for (; i < AA.length; i++) { l += AA[i].length; } - var C = new Uint8Array(l); - - i = 0; - for (var offset = 0; i < AA.length; i++) { - C.set(AA[i], offset); - offset += AA[i].length; - } - return C; - }; - - Util.escapeKeyCharacters = function (key) { - return key && key.replace && key.replace(/\//g, '-'); - }; - - Util.unescapeKeyCharacters = function (key) { - return key.replace(/\-/g, '/'); - }; - - Util.deduplicateString = function (array) { - var a = array.slice(); - for(var i=0; i= oneGigabyte) { return 'GB'; } - else if (bytes >= oneMegabyte) { return 'MB'; } - else { return 'KB'; } - }; - - // given a path, asynchronously return an arraybuffer - var getCacheKey = function (src) { - var _src = src.replace(/(\/)*$/, ''); // Remove trailing slashes - var idx = _src.lastIndexOf('/'); - var cacheKey = _src.slice(idx+1); - if (!/^[a-f0-9]{48}$/.test(cacheKey)) { cacheKey = undefined; } - return cacheKey; - }; - - - Util.getBlock = function (src, opt, cb) { - var CB = Util.once(Util.mkAsync(cb)); - - var headers = {}; - - if (typeof(opt.bearer) === 'string' && opt.bearer) { - headers.authorization = `Bearer ${opt.bearer}`; - } - - - fetch(src, { - method: 'GET', - credentials: 'include', - headers: headers, - }).then(response => { - if (response.ok) { - // TODO this should probably be returned as an arraybuffer or something rather than a promise - // this is resulting in some code duplication - return void CB(void 0, response); - } - if (response.status === 401 || response.status === 404) { - response.json().then((data) => { - CB(response.status, data); - }).catch(() => { - CB(response.status); - }); - - return; - } - CB(response.status, response); - }).catch(error => { - CB(error); - }); - }; - - - Util.fetchApi = function (origin, type, ignoreCache, cb) { - const url = new URL(origin); - url.pathname = `api/${type}`; - let href = url.href + (ignoreCache ? '?'+(+new Date()) : ''); - if (typeof(self) !== "undefined" && self.crypto) { - // Browser - fetch(href).then(res => { - if (!res.ok) { - throw new Error(`Fetch error: ${res.status}`); - } - return res.text(); - }).then(body => { - cb(JSON.parse(body.slice(27,-5))); - }).catch(err => { - console.error(err.message); - cb({}); - }); - } else if (typeof(require) !== "undefined") { - // NodeJS - const H = url.protocol === 'http:' ? - require('node:http') : require('node:https'); - H.get(url.href, res => { - let body = ''; - res.on('data', data => { body += data; }); - res.on('end', () => { - try { - cb(JSON.parse(body.slice(27,-5))); - } catch (e) { - console.error(e); - cb({}); - } - }); - }); - } - }; - - Util.fetch = function (src, cb, progress, cache) { - var CB = Util.once(Util.mkAsync(cb)); - - var cacheKey = getCacheKey(src); - var getBlobCache = function (id, cb) { - if (!cache || typeof(cache.getBlobCache) !== "function") { return void cb('EINVAL'); } - cache.getBlobCache(id, cb); - }; - var setBlobCache = function (id, u8, cb) { - if (!cache || typeof(cache.setBlobCache) !== "function") { return void cb('EINVAL'); } - cache.setBlobCache(id, u8, cb); - }; - - var xhr; - - var fetch = function () { - xhr = new XMLHttpRequest(); - xhr.open("GET", src, true); - if (progress) { - xhr.addEventListener("progress", function (evt) { - if (evt.lengthComputable) { - var percentComplete = evt.loaded / evt.total; - progress(percentComplete); - } - }, false); - } - xhr.responseType = "arraybuffer"; - xhr.onerror = function (err) { CB(err); }; - xhr.onload = function () { - if (/^4/.test(''+this.status)) { - return CB('XHR_ERROR'); - } - - var arrayBuffer = xhr.response; - if (arrayBuffer) { - var u8 = new Uint8Array(arrayBuffer); - if (cacheKey) { - return void setBlobCache(cacheKey, u8, function () { - CB(null, u8); - }); - } - return void CB(void 0, u8); - } - CB('ENOENT'); - }; - xhr.send(null); - }; - - if (!cacheKey) { return void fetch(); } - - getBlobCache(cacheKey, function (err, u8) { - if (err || !u8) { return void fetch(); } - CB(void 0, u8); - }); - - return { - cancel: function () { - if (xhr && xhr.abort) { xhr.abort(); } - } - }; - }; - - Util.dataURIToBlob = function (dataURI) { - var byteString = atob(dataURI.split(',')[1]); - var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; - - // write the bytes of the string to an ArrayBuffer - var ab = new ArrayBuffer(byteString.length); - var ia = new Uint8Array(ab); - for (var i = 0; i < byteString.length; i++) { - ia[i] = byteString.charCodeAt(i); - } - - // write the ArrayBuffer to a blob, and you're done - var bb = new Blob([ab], {type: mimeString}); - return bb; - }; - - Util.throttle = function (f, ms) { - var last = 0; - var to; - var args; - - var defer = function (delay) { - // no timeout: run function `f` in `ms` milliseconds - // unless `g` is called again in the meantime - to = setTimeout(function () { - // wipe the current timeout handler - to = undefined; - - // take the current time - var now = +new Date(); - // compute time passed since `last` - var diff = now - last; - if (diff < ms) { - // don't run `f` if `g` was called since this timeout was set - // instead calculate how much further in the future your next - // timeout should be scheduled - return void defer(ms - diff); - } - - // else run `f` with the most recently supplied arguments - f.apply(null, args); - }, delay); - }; - - var g = function () { - // every time you call this function store the time - last = +new Date(); - // remember what arguments were passed - args = Util.slice(arguments); - // if there is a pending timeout then do nothing - if (to) { return; } - defer(ms); - }; - - g.clear = function () { - clearTimeout(to); - to = undefined; - }; - return g; - }; - - /* takes a function (f) and a time (t) in ms. returns a function wrapper - which prevents the internal function from being called more than once - every t ms. if the function is prevented, returns time til next valid - execution, else null. - */ - Util.notAgainForAnother = function (f, t) { - if (typeof(f) !== 'function' || typeof(t) !== 'number') { - throw new Error("invalid inputs"); - } - var last = null; - return function () { - var now = +new Date(); - if (last && now <= last + t) { return t - (now - last); } - last = now; - f.apply(null, Util.slice(arguments)); - return null; - }; - }; - - Util.createRandomInteger = function () { - return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); - }; - - Util.noop = function () {}; - - /* for wrapping async functions such that they can only be called once */ - Util.once = function (f, g) { - return function () { - if (!f) { return; } - f.apply(this, Array.prototype.slice.call(arguments)); - f = g; - }; - }; - - Util.blobToImage = function (blob, cb) { - var reader = new FileReader(); - reader.onloadend = function() { - cb(reader.result); - }; - reader.readAsDataURL(blob); - }; - Util.blobURLToImage = function (url, cb) { - var xhr = new XMLHttpRequest(); - xhr.onload = function() { - var reader = new FileReader(); - reader.onloadend = function() { - cb(reader.result); - }; - reader.readAsDataURL(xhr.response); - }; - xhr.open('GET', url); - xhr.responseType = 'blob'; - xhr.send(); - }; - - // Check if an element is a plain object - Util.isObject = function (o) { - return typeof (o) === "object" && - Object.prototype.toString.call(o) === '[object Object]'; - }; - - Util.isCircular = function (o) { - try { - JSON.stringify(o); - return false; - } catch (e) { return true; } - }; - - /* recursively adds the properties of an object 'b' to 'a' - arrays are only shallow copies, so references to the original - might still be present. Be mindful if you will modify 'a' in the future */ - Util.extend = function (a, b) { - if (!Util.isObject(a) || !Util.isObject(b)) { - return void console.log("Extend only works with 2 objects"); - } - if (Util.isCircular(b)) { - return void console.log("Extend doesn't accept circular objects"); - } - for (var k in b) { - if (Util.isObject(b[k])) { - a[k] = Util.isObject(a[k]) ? a[k] : {}; - Util.extend(a[k], b[k]); - continue; - } - if (Array.isArray(b[k])) { - a[k] = b[k].slice(); - continue; - } - a[k] = b[k]; - } - }; - - Util.isChecked = function (el) { - // could be nothing... - if (!el) { return false; } - // check if it's a dom element - if (typeof(el.tagName) !== 'undefined') { - return Boolean(el.checked); - } - // sketchy test to see if it's jquery - if (typeof(el.prop) === 'function') { - return Boolean(el.prop('checked')); - } - // else just say it's not checked - return false; - }; - - Util.hexToRGB = function (hex) { - var h = hex.replace(/^#/, ''); - return [ - parseInt(h.slice(0,2), 16), - parseInt(h.slice(2,4), 16), - parseInt(h.slice(4,6), 16), - ]; - }; - Util.rgbToHex = function (rgb) { - return `#${rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/).slice(1).map(n => parseInt(n, 10).toString(16).padStart(2, '0')).join('')}`; - }; - - Util.isSmallScreen = function () { - return window.innerHeight < 800 || window.innerWidth < 800; - }; - - Util.stripTags = function (text) { - var div = document.createElement("div"); - div.innerHTML = text; - return div.innerText; - }; - - // return an object containing {name, ext} - // or {} if the name could not be parsed - Util.parseFilename = function (filename) { - if (!filename || !filename.trim()) { return {}; } - var parsedName = /^(\.?.+?)(\.[^.]+)?$/.exec(filename) || []; - return { - name: parsedName[1], - ext: parsedName[2], - }; - }; - - // Tell if a file is plain text from its metadata={title, fileType} - Util.isPlainTextFile = function (type, name) { - // does its type begins with "text/" - if (type && type.indexOf("text/") === 0) { return true; } - // no type and no file extension -> let's guess it's plain text - var parsedName = Util.parseFilename(name); - if (!type && name && !parsedName.ext) { return true; } - // other exceptions - if (type === 'application/x-javascript') { return true; } - if (type === 'application/xml') { return true; } - return false; - }; - - // Tell if a file is spreadsheet from its metadata={title, fileType} - Util.isSpreadsheet = function (type, name) { - return (type && - (type === 'application/vnd.oasis.opendocument.spreadsheet' || - type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')) - || (name && (name.endsWith('.xlsx') || name.endsWith('.ods'))); - }; - Util.isOfficeDoc = function (type, name) { - return (type && - (type === 'application/vnd.oasis.opendocument.text' || - type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')) - || (name && (name.endsWith('.docx') || name.endsWith('.odt'))); - }; - Util.isPresentation = function (type, name) { - return (type && - (type === 'application/vnd.oasis.opendocument.presentation' || - type === 'application/vnd.openxmlformats-officedocument.presentationml.presentation')) - || (name && (name.endsWith('.pptx') || name.endsWith('.odp'))); - }; - - Util.isValidURL = function (str) { - var pattern = new RegExp('^(https?:\\/\\/)'+ // protocol - '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name - '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address - '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path - '(\\?[;&a-z\\d%_.~+=-]*)?'); // query string - //'(\\#[-a-z\\d_]*)?$','i'); // fragment locator - return !!pattern.test(str); - }; - - var emoji_patt = /([\uD800-\uDBFF][\uDC00-\uDFFF])/; - var isEmoji = function (str) { - return emoji_patt.test(str); - }; - var emojiStringToArray = function (str) { - var split = str.split(emoji_patt); - var arr = []; - for (var i=0; i { - let arr = /ver=([0-9.]+)(-[0-9]*)?/.exec(urlArgs); - let ver = Array.isArray(arr) && arr[1]; - return ver || undefined; - }; - - /** Saferphore copied from the npm package: - * https://www.npmjs.com/package/saferphore (MIT license) - * because the umd definition doesn't work with rollup build - */ - Util.Saferphore = { - create: resourceCount => { - var queue = []; - var check; - var mkRa = function () { - var outerCalled = 0; - return function (func) { - if (outerCalled++) { throw new Error("returnAfter() called multiple times"); } - var called = 0; - return function () { - if (called++) { - throw new Error("returnAfter wrapped callback called multiple times"); - } - if (func) { func.apply(null, arguments); } - resourceCount++; - check(); - }; - }; - }; - check = function () { - if (resourceCount < 0) { throw new Error("(resourceCount < 0) should never happen"); } - if (resourceCount === 0 || queue.length === 0) { return; } - resourceCount--; - queue.shift()(mkRa()); - }; - return { - take: function (func) { - queue.push(func); - check(); - } - }; - } - }; - /* End of code copied from saferphore */ - - return Util; -}; - - if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory(require('tweetnacl-util')); - } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define(['/components/tweetnacl-util/nacl-util.min.js'], function () { - return factory(globalThis?.nacl?.util); - }); - } else { - // Unsupported initialization - } -}(typeof(self) !== 'undefined'? self: this)); diff --git a/www/common/cryptget.js b/www/common/cryptget.js deleted file mode 100644 index df3453225..000000000 --- a/www/common/cryptget.js +++ /dev/null @@ -1,238 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(() => { -const factory = (Crypto, CPNetflux, Netflux, Util, - Hash, Realtime, NetConfig, Cache, Pinpad, nThen) => { - var finish = function (S, err, doc) { - if (S.done) { return; } - S.cb((err && err.error), doc, err); - S.done = true; - - if (!S.hasNetwork) { - var disconnect = Util.find(S, ['network', 'disconnect']); - if (typeof(disconnect) === 'function') { disconnect(); } - } - if (S.realtime && S.realtime.stop) { - try { - S.realtime.stop(); - } catch (e) { console.error(e); } - } - var abort = Util.find(S, ['session', 'realtime', 'abort']); - if (typeof(abort) === 'function') { - S.session.realtime.sync(); - abort(); - } - }; - - var makeNetwork = function (cb) { - var wsUrl = NetConfig.getWebsocketURL(); - Netflux.connect(wsUrl).then(function (network) { - cb(null, network); - }, function (err) { - cb(err); - }); - }; - - var start = function (Session, config) { - // Create a network and authenticate with all our keys if necessary, - // then start chainpad-netflux - nThen(function (waitFor) { - if (Session.hasNetwork) { return; } - makeNetwork(waitFor(function (err, network) { - if (err) { return; } - config.network = network; - })); - }).nThen(function () { - Session.realtime = CPNetflux.start(config); - }); - }; - - var onRejected = function (config, Session, data, cb) { - // Check if we can authenticate - if (!Array.isArray(data) || !data.length || data[0].length !== 16) { - return void cb(true); - } - if (!Array.isArray(Session.accessKeys)) { return void cb(true); } - - // Authenticate - config.network.historyKeeper = data[0]; - nThen(function (waitFor) { - Session.accessKeys.forEach(function (obj) { - Pinpad.create(config.network, obj, waitFor(function (e) { - console.log('done', obj); - if (e) { console.error(e); } - })); - }); - }).nThen(function () { - cb(); - }); - }; - - var makeConfig = function (hash, opt) { - var secret; - if (typeof(hash) === 'string') { - // We can't use cryptget with a file or a user so we can use 'pad' as hash type - secret = Hash.getSecrets('pad', hash, opt.password); - } else if (typeof(hash) === 'object') { - // we may want to just supply options directly - // and this is the easiest place to do it - secret = hash; - } - if (!secret.keys) { secret.keys = secret.key; } // support old hashses - var config = { - websocketURL: NetConfig.getWebsocketURL(opt.origin), - channel: secret.channel, - validateKey: secret.keys.validateKey || undefined, - crypto: Crypto.createEncryptor(secret.keys), - logLevel: 0, - initialState: opt.initialState, - Cache: Cache - }; - return config; - }; - - var isObject = function (o) { - return typeof(o) === 'object'; - }; - - var overwrite = function (a, b) { - if (!(isObject(a) && isObject(b))) { return; } - Object.keys(b).forEach(function (k) { a[k] = b[k]; }); - }; - - var get = function (hash, cb, opt, progress) { - if (typeof(cb) !== 'function') { - throw new Error('Cryptget expects a callback'); - } - opt = opt || {}; - progress = progress || function () {}; - - var config = makeConfig(hash, opt); - var Session = { - cb: cb, - accessKeys: opt.accessKeys, - hasNetwork: Boolean(opt.network) - }; - - config.onRejected = function (data, cb) { - onRejected(config, Session, data, cb); - }; - - config.onReady = function (info) { - var rt = Session.session = info.realtime; - Session.network = info.network; - progress(1); - finish(Session, void 0, rt.getUserDoc()); - }; - - config.onError = function (info) { - console.warn(info); - finish(Session, info); - }; - config.onChannelError = function (info) { - console.error(info); - finish(Session, info); - }; - - config.onCacheReady = opt.onCacheReady; - - // We use the new onMessage handler to compute the progress: - // we should receive 2 checkpoints max, so 100 messages max - // We're going to consider that 1 message = 1%, and we'll send 100% - // at the end - var i = 0; - config.onMessage = function () { - i++; - progress(Math.min(0.99, i/100)); - }; - - overwrite(config, opt); - - start(Session, config); - }; - - var put = function (hash, doc, cb, opt) { - if (typeof(cb) !== 'function') { - throw new Error('Cryptput expects a callback'); - } - opt = opt || {}; - - var config = makeConfig(hash, opt); - var Session = { - cb: cb, - accessKeys: opt.accessKeys, - hasNetwork: Boolean(opt.network) - }; - - config.onRejected = function (data, cb) { - onRejected(config, Session, data, cb); - }; - - config.onReady = function (info) { - var realtime = Session.session = info.realtime; - Session.network = info.network; - - realtime.contentUpdate(doc); - - var to = setTimeout(function () { - cb(new Error("Timeout")); - }, 15000); - - Realtime.whenRealtimeSyncs(realtime, function () { - clearTimeout(to); - var doc = realtime.getAuthDoc(); - realtime.abort(); - finish(Session, void 0, doc); - }); - }; - - config.onChannelError = function (info) { - finish(Session, info); - }; - - overwrite(config, opt); - - start(Session, config); - }; - - return { - get: get, - put: put, - }; -}; - -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - require('chainpad-crypto'), - require('chainpad-netflux'), - require('netflux-websocket'), - require('./common-util'), - require('./common-hash'), - require('./common-realtime'), - require('./network-config'), - require('./cache-store'), - require('./pinpad'), - require('nthen'), - require('chainpad') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/components/chainpad-crypto/crypto.js', - 'chainpad-netflux', - 'netflux-client', - '/common/common-util.js', - '/common/common-hash.js', - '/common/common-realtime.js', - '/common/outer/network-config.js', - '/common/outer/cache-store.js', - '/common/pinpad.js', - '/components/nthen/index.js', - '/components/chainpad/chainpad.dist.js', - ], factory); -} else { - // unsupported initialization -} -})(); - diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 816689160..d48926b18 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -8,14 +8,13 @@ define([ '/customize/messages.js', '/common/common-util.js', '/common/common-hash.js', - '/common/outer/cache-store.js', - '/common/common-messaging.js', + '/common/cache-store.js', '/common/common-constants.js', '/common/common-feedback.js', '/common/visible.js', '/common/user-object.js', '/common/outer/local-store.js', - '/common/outer/worker-channel.js', + '/common/events-channel.js', '/common/outer/login-block.js', '/common/common-credential.js', '/customize/login.js', @@ -25,7 +24,7 @@ define([ '/components/nthen/index.js', '/components/tweetnacl/nacl-fast.min.js' ], function (Config, Broadcast, Messages, Util, Hash, Cache, - Messaging, Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block, + Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block, Cred, Login, Store, AppConfig, nThen) { /* This file exposes functionality which is specific to Cryptpad, but not to @@ -310,7 +309,7 @@ define([ common.makeNetwork = function (cb) { require([ 'netflux-client', - '/common/outer/network-config.js' + '/common/network-config.js' ], function (Netflux, NetConfig) { var wsUrl = NetConfig.getWebsocketURL(); Netflux.connect(wsUrl).then(function (network) { diff --git a/www/common/notify.js b/www/common/notify.js deleted file mode 100644 index 35ff04d65..000000000 --- a/www/common/notify.js +++ /dev/null @@ -1,201 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -/* eslint compat/compat: "off" */ - -(() => { -const factory = (ApiConfig = {}) => { - let window = globalThis; - var Module = {}; - - ApiConfig.requireConf = ApiConfig.requireConf || {}; - - Module.setCustomize = data => { - ApiConfig = data.ApiConfig; - }; - - var apps = ['code', 'slide', 'pad', 'kanban', 'whiteboard', 'diagram', 'sheet', 'poll', 'teams', 'form', 'doc', 'presentation']; - var app = window.location && window.location.pathname.slice(1, -1); // remove "/" at the beginnin and the end - var suffix = apps.indexOf(app) !== -1 ? '-'+app : ''; - - var DEFAULT_MAIN = '/customize/favicon/main-favicon' + suffix + '.png?' + ApiConfig.requireConf.urlArgs; - var DEFAULT_ALT = '/customize/favicon/alt-favicon' + suffix + '.png?' + ApiConfig.requireConf.urlArgs; - var DEFAULT_MAIN_ICO = '/customize/favicon/main-favicon' + suffix + '.ico?' + ApiConfig.requireConf.urlArgs; - var DEFAULT_ALT_ICO = '/customize/favicon/alt-favicon' + suffix + '.ico?' + ApiConfig.requireConf.urlArgs; - - var document = window.document; - - var isSupported = Module.isSupported = function () { - return typeof(window.Notification) === 'function' && window.isSecureContext; - }; - - var hasPermission = Module.hasPermission = function () { - return Notification.permission === 'granted'; - }; - - var getPermission = Module.getPermission = function (f) { - f = f || function () {}; - // "Notification.requestPermission is not a function" on Firefox 68.11.0esr - if (!Notification || typeof(Notification.requestPermission) !== 'function') { return void f(false); } - Notification.requestPermission(function (permission) { - if (permission === "granted") { f(true); } - else { f(false); } - }); - }; - - var create = Module.create = function (msg, title, icon) { - if (document && !icon) { - var favicon = document.getElementById('favicon'); - icon = favicon.getAttribute('data-main-favicon') || DEFAULT_ALT; - } else if (!icon) { - icon = DEFAULT_ALT; - } - - var n = new Notification(title,{ - icon: icon, - body: msg, - }); - n.onclick = function () { - if (!document) { return; } - try { - parent.focus(); - window.focus(); //just in case, older browsers - this.close(); - } catch (e) {} - }; - return n; - }; - - Module.system = function (msg, title, icon) { - // Let's check if the browser supports notifications - if (!isSupported()) { return; /*console.log("Notifications are not supported");*/ } - - // Let's check whether notification permissions have already been granted - else if (hasPermission()) { - // If it's okay let's create a notification - return create(msg, title, icon); - } - - // Otherwise, we need to ask the user for permission - else if (Notification.permission !== 'denied') { - getPermission(function (state) { - if (state) { create(msg, title, icon); } - }); - } - }; - - var createFavicon = function () { - if (!document) { - return void console.error('document is not available in this context'); - } - console.debug("creating favicon"); - var attrs = { - id: 'favicon', - type: 'image/png', - rel: 'icon', - 'data-main-favicon': DEFAULT_MAIN, - 'data-alt-favicon': DEFAULT_ALT, - href: DEFAULT_MAIN, - }; - if(!document.getElementById("favicon")) { - var fav = document.createElement('link'); - Object.keys(attrs).forEach(function (k) { - fav.setAttribute(k, attrs[k]); - }); - document.head.appendChild(fav); - } - - if(!document.getElementById("favicon-ico")) { - var faviconLink = document.createElement('link'); - attrs.href = attrs.href.replace(/\.png/g, ".ico"); - attrs.id = 'favicon-ico'; - attrs.type = 'image/x-icon'; - - Object.keys(attrs).forEach(function (k) { - faviconLink.setAttribute(k, attrs[k]); - }); - - document.head.appendChild(faviconLink); - } - }; - - if (document && !document.getElementById('favicon')) { createFavicon(); } - - Module.tab = function (frequency, count) { - if (!document) { - return void console.error('document is not available in this context'); - } - var key = '_pendingTabNotification'; - - var favicon = document.getElementById('favicon'); - var faviconIco = document.getElementById('favicon-ico'); - - var main = DEFAULT_MAIN; - var alt = DEFAULT_ALT; - var mainIco = DEFAULT_MAIN_ICO; - var altIco = DEFAULT_ALT_ICO; - - if (favicon) { - main = favicon.getAttribute('data-main-favicon') || DEFAULT_MAIN; - alt = favicon.getAttribute('data-alt-favicon') || DEFAULT_ALT; - favicon.setAttribute('href', main); - } - if (faviconIco) { - mainIco = faviconIco.getAttribute('data-main-favicon') || DEFAULT_MAIN_ICO; - altIco = faviconIco.getAttribute('data-alt-favicon') || DEFAULT_ALT_ICO; - faviconIco.setAttribute('href', mainIco); - } - - var cancel = function (pending) { - // only run one tab notification at a time - if (Module[key]) { - window.clearInterval(Module[key]); - if (favicon) { - favicon.setAttribute('href', pending? alt : main); - } - if (faviconIco) { - faviconIco.setAttribute('href', pending? altIco : mainIco); - } - - return true; - } - return false; - }; - - cancel(); - - var step = function () { - if (favicon) { - favicon.setAttribute('href', favicon.getAttribute('href') === main? alt : main); - } - if (faviconIco) { - faviconIco.setAttribute('href', faviconIco.getAttribute('href') === mainIco? altIco : mainIco); - } - --count; - }; - - Module[key] = window.setInterval(function () { - if (count > 0) { return step(); } - cancel(true); - - }, frequency); - step(); - - return { - cancel: cancel, - }; - }; - - return Module; -}; - -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory(undefined); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define(['/api/config'], factory); -} else { - // unsupported initialization -} - -})(); diff --git a/www/common/onlyoffice/current-version.js b/www/common/onlyoffice/current-version.js deleted file mode 100644 index ff70ffbad..000000000 --- a/www/common/onlyoffice/current-version.js +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(() => { -const factory = () => { - const version = 8; - return { - currentVersionNumber: version, - currentVersion: 'v' + version - }; -}; - -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory(); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([], factory); -} -})(); diff --git a/www/common/outer/cache-store.js b/www/common/outer/cache-store.js deleted file mode 100644 index 0ee3695ce..000000000 --- a/www/common/outer/cache-store.js +++ /dev/null @@ -1,211 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(() => { -const factory = (Util, localForage) => { - let window = globalThis; - let self = globalThis; - var S = window.CryptPad_Cache = {}; - var onReady = Util.mkEvent(true); - - // Check if indexedDB is allowed - var allowed = false; - var disabled = false; - var supported = false; - - try { - var request = window.indexedDB.open('test_db', 1); - request.onsuccess = function () { - supported = true; - allowed = supported && !disabled; - onReady.fire(); - }; - request.onerror = function () { - onReady.fire(); - }; - } catch (e) { - onReady.fire(); - } - - S.enable = function () { - disabled = false; - allowed = supported && !disabled; - }; - S.disable = function () { - disabled = true; - allowed = supported && !disabled; - }; - S.isEnabled = () => allowed; - - var cache = localForage.createInstance({ - driver: localForage.INDEXEDDB, - name: "cp_cache" - }); - - S.getBlobCache = function (id, cb) { - cb = Util.once(Util.mkAsync(cb || function () {})); - - onReady.reg(function () { - if (!allowed) { return void cb('NOCACHE'); } - cache.getItem(id, function (err, obj) { - if (err || !obj || !obj.c) { - return void cb(Util.serializeError(err || 'EINVAL')); - } - cb(null, obj.c); - obj.t = +new Date(); - cache.setItem(id, obj, function (err) { - if (!err) { return; } - console.error(err); - }); - }); - }); - }; - S.setBlobCache = function (id, u8, cb) { - cb = Util.once(Util.mkAsync(cb || function () {})); - - onReady.reg(function () { - if (!allowed) { return void cb('NOCACHE'); } - if (!u8) { return void cb('EINVAL'); } - cache.setItem(id, { - c: u8, - t: (+new Date()) // 't' represent the "lastAccess" of this cache (get or set) - }, function (err) { - cb(Util.serializeError(err)); - }); - }); - }; - - // id: channel ID or blob ID - // returns array of messages - S.getChannelCache = function (id, cb) { - cb = Util.once(Util.mkAsync(cb || function () {})); - - onReady.reg(function () { - if (!allowed) { return void cb('NOCACHE'); } - cache.getItem(id, function (err, obj) { - if (err || !obj || !Array.isArray(obj.c)) { - return void cb(Util.serializeError(err || 'EINVAL')); - } - cb(null, obj); - obj.t = +new Date(); - cache.setItem(id, obj, function (err) { - if (!err) { return; } - console.error(err); - }); - }); - }); - }; - - // Keep the last two checkpoint + any checkpoint that may exist in the last 100 messages - // FIXME: duplicate system with sliceCpIndex from lib/hk-util.js - var checkCheckpoints = function (array) { - if (!Array.isArray(array)) { return; } - // Keep the last 100 messages - if (array.length > 100) { // FIXME this behaviour is only valid for chainpad-style documents - array.splice(0, array.length - 100); - } - // Remove every message before the first checkpoint - var firstCpIdx; - array.some(function (el, i) { - if (!el.isCheckpoint) { return; } - firstCpIdx = i; - return true; - }); - array.splice(0, firstCpIdx); - }; - - var t = {}; - S.storeCache = function (id, validateKey, val, onError) { - onError = Util.once(Util.mkAsync(onError || function () {})); - - onReady.reg(function () { - - // Make a throttle or use the existing one to avoid calling - // storeCache with the same array multiple times - t[id] = t[id] || Util.throttle(function (validateKey, val, onError) { - if (!allowed) { return void onError('NOCACHE'); } - if (!Array.isArray(val) || !validateKey) { return void onError('EINVAL'); } - checkCheckpoints(val); - cache.setItem(id, { - k: validateKey, - c: val, - t: (+new Date()) // 't' represent the "lastAccess" of this cache (get or set) - }, function (err) { - if (err) { onError(Util.serializeError(err)); } - }); - - }, 50); - t[id](validateKey, val, onError); - - }); - }; - - S.leaveChannel = function (id) { - delete t[id]; - }; - - S.clearChannel = function (id, cb) { - cb = Util.once(Util.mkAsync(cb || function () {})); - - onReady.reg(function () { - if (!allowed) { return void cb('NOCACHE'); } - cache.removeItem(id, function () { - cb(); - }); - }); - }; - - S.clear = function (cb) { - cb = Util.once(Util.mkAsync(cb || function () {})); - - onReady.reg(function () { - if (!allowed) { return void cb('NOCACHE'); } - cache.clear(cb); - }); - }; - - S.getKeys = function (cb) { - cb = Util.once(Util.mkAsync(cb || function () {})); - onReady.reg(function () { - if (!allowed) { return void cb('NOCACHE'); } - cache.keys().then(function (keys) { - cb(null, keys); - }).catch(function (err) { - cb(err); - }); - }); - }; - S.getTime = function (id, cb) { - cb = Util.once(Util.mkAsync(cb || function () {})); - onReady.reg(function () { - if (!allowed) { return void cb('NOCACHE'); } - cache.getItem(id, function (err, obj) { - if (err || !obj || !obj.c) { - return void cb(Util.serializeError(err || 'EINVAL')); - } - cb(null, obj.t); - }); - }); - }; - - self.CryptPad_clearIndexedDB = S.clear; - - return S; -}; - -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - require('./common-util'), - require('localforage'), - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/common-util.js', - '/components/localforage/dist/localforage.min.js', - ], factory); -} else { - // unsupported initialization -} - -})(); diff --git a/www/common/outer/http-command.js b/www/common/outer/http-command.js deleted file mode 100644 index 6d62f809f..000000000 --- a/www/common/outer/http-command.js +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(() => { -const factory = (nThen, Util, ApiConfig = {}, Nacl) => { - - const getApiOrigin = function () { - if (!Object.keys(ApiConfig).length) { return; } - var url; - var unsafeOriginURL = new URL(ApiConfig.httpUnsafeOrigin); - try { - url = new URL(ApiConfig.websocketPath, ApiConfig.httpUnsafeOrigin); - url.protocol = unsafeOriginURL.protocol; - return url.origin; - } catch (err) { - console.error(err); - return ApiConfig.httpUnsafeOrigin; - } - }; - var API_ORIGIN = getApiOrigin(); - - const setCustomize = data => { - ApiConfig = data.ApiConfig; - API_ORIGIN = getApiOrigin(); - }; - - var clone = o => JSON.parse(JSON.stringify(o)); - var randomToken = () => Util.encodeBase64(Nacl.randomBytes(24)); - var postData = function (url, data, cb) { - var CB = Util.once(Util.mkAsync(cb)); - fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }).then(response => { - if (response.ok) { - - return void response.text().then(result => { CB(void 0, Util.tryParse(result)); }); // checkup error when using .json() - //return void response.json().then(result => { CB(void 0, result); }); - } - - response.json().then().then(result => { - CB(response.status, result); - }); - //CB(response.status, response); - }).catch(error => { - CB(error); - }); - }; - - var serverCommand = function (keypair, my_data, cb) { - var obj = clone(my_data); - obj.publicKey = Util.encodeBase64(keypair.publicKey); - obj.nonce = randomToken(); - var href = new URL('/api/auth/', API_ORIGIN); - var txid, date; - nThen(function (w) { - // Tell the server we want to do some action - postData(href, obj, w((err, data) => { - if (err) { - w.abort(); - console.error(err); - // there might be more info here - if (data) { console.error(data); } - return void cb(err); - } - - // if the requested action is valid, it responds with a txid and a nonce - // bundle all that up into an object, stringify it, and sign it. - // respond with an object: {sig, txid} - if (!data.date || !data.txid) { - w.abort(); - return void cb('REQUEST_REJECTED'); - } - txid = data.txid; - date = data.date; - })); - }).nThen(function (w) { - var copy = clone(obj); - copy.txid = txid; - copy.date = date; - var toSign = Util.decodeUTF8(JSON.stringify(copy)); - var sig = Nacl.sign.detached(toSign, keypair.secretKey); - var encoded = Util.encodeBase64(sig); - var obj2 = { - sig: encoded, - txid: txid, - }; - postData(href, obj2, w((err, data) => { - if (err) { - w.abort(); - console.error(err); - // there might be more info here - if (data) { console.error(data); } - return void cb("RESPONSE_REJECTED", data); - } - cb(void 0, data); - })); - }); - }; - - serverCommand.setCustomize = setCustomize; - - return serverCommand; -}; - -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - require('nthen'), - require('./common-util'), - undefined, - require('tweetnacl/nacl-fast') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/components/nthen/index.js', - '/common/common-util.js', - '/api/config', - '/components/tweetnacl/nacl-fast.min.js', - ], (nThen, Util, ApiConfig) => { - return factory(nThen, Util, ApiConfig, window.nacl); - }); -} else { - // unsupported initialization -} - -})(); diff --git a/www/common/outer/local-store.js b/www/common/outer/local-store.js index 42a8b9cca..a260f3def 100644 --- a/www/common/outer/local-store.js +++ b/www/common/outer/local-store.js @@ -5,7 +5,7 @@ define([ '/common/common-constants.js', '/common/common-hash.js', - '/common/outer/cache-store.js', + '/common/cache-store.js', '/components/localforage/dist/localforage.min.js', '/customize/application_config.js', '/common/common-util.js', diff --git a/www/common/outer/network-config.js b/www/common/outer/network-config.js deleted file mode 100644 index af09e70ff..000000000 --- a/www/common/outer/network-config.js +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(() => { -const factory = (ApiConfig = {}) => { - var Config = {}; - - Config.setCustomize = data => { - ApiConfig = data.ApiConfig; - }; - - Config.getWebsocketURL = function (origin) { - var path = ApiConfig.websocketPath || '/cryptpad_websocket'; - if (/^ws{1,2}:\/\//.test(path)) { return path; } - - var l = new URL(origin || self?.location?.href); - if (origin) { - l.href = origin; - } - var protocol = l.protocol.replace(/http/, 'ws'); - var host = l.host; - var url = protocol + '//' + host + path; - - return url; - }; - - return Config; -}; - -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory(); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/api/config' - ], factory); -} else { - // unsupported initialization -} - -})(); diff --git a/www/common/outer/roster.js b/www/common/outer/roster.js deleted file mode 100644 index 9db315c3a..000000000 --- a/www/common/outer/roster.js +++ /dev/null @@ -1,964 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(function () { -var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto, Feedback) { - var Roster = {}; - - // this constant is somewhat arbitrary. - // Adjust it as you like to suit performance expectations - var CHECKPOINT_INTERVAL = 25; - var TIMEOUT_INTERVAL = 30000; // TIMEOUT after 30s - - /* - roster: { - state: { - members: { - user0CurveKey: { - notifications: "", // required - displayName: "", // required - role: "OWNER|ADMIN|MEMBER|VIEWER", // VIEWER if not specified - profile: "", - title: "" - }, - user1CurveKey: { - ... - } - }, - metadata: { - // guaranteed to be strings, but may be empty - topic: '', - name: '', - avatar: '', - // anything else you use may not be defined - } - } - } - */ - - var isMap = function (obj) { - return Boolean(obj && typeof(obj) === 'object' && !Array.isArray(obj)); - }; - - var getMessageId = function (msgString) { - return msgString.slice(0, 64); - }; - - var canCheckpoint = function (author, members) { - // if you're here then you've received a checkpoint message - // that you don't necessarily trust. - - // find the author's role from your knoweldge of the state - var role = Util.find(members, [author, 'role']); - - // and check if it is 'OWNER' or 'ADMIN' - return ['OWNER', 'ADMIN'].indexOf(role) !== -1; - }; - - var isValidRole = function (role) { - return ['OWNER', 'ADMIN', 'MEMBER', 'VIEWER'].indexOf(role) !== -1; - }; - - var isSelfDowngrade = function (author, curve, role, state) { - // Make sure you want to describe yourself - var selfDescribe = author === curve && state[curve]; - if (!selfDescribe) { return false; } - // ADMIN and OWNER can always update roles - // we only need to allow MEMBER to downgrade themselves to VIEWER - var authorRole = Util.find(state, [author, 'role']); - if (authorRole === "MEMBER") { return role === 'VIEWER'; } - }; - - var canAddRole = function (author, role, members) { - var authorRole = Util.find(members, [author, 'role']); - if (!authorRole) { return false; } - - // nobody can add an invalid role - if (!isValidRole(role)) { return false; } - - // owners can add any valid role they want - if (authorRole === 'OWNER') { return true; } - // admins can add other admins or members or viewers - if (authorRole === "ADMIN") { return ['ADMIN', 'MEMBER', 'VIEWER'].indexOf(role) !== -1; } - // (MEMBER, other) can't add anyone of any role - return false; - }; - - var isValidId = function (id) { - return typeof(id) === 'string' && id.length === 44; - }; - - var canDescribeTarget = function (author, curve, state) { - // you must be in the group to describe anyone - if (!state[curve]) { return false; } - - // anyone can describe themself - if (author === curve && state[curve]) { return true; } - - var authorRole = Util.find(state, [author, 'role']); - var targetRole = Util.find(state, [curve, 'role']); - - // something is really wrong if there's no authorRole - if (!authorRole) { return false; } - - // owners can do whatever they want - if (authorRole === 'OWNER') { return true; } - - // admins can describe anyone escept owners - if (authorRole === 'ADMIN' && targetRole !== 'OWNER') { return true; } - - // members can't describe others - return false; - }; - - var canRemoveRole = function (author, role, members) { - var authorRole = Util.find(members, [author, 'role']); - if (!authorRole) { return false; } - - // owners can remove anyone they want - if (authorRole === 'OWNER') { return true; } - // admins can remove other admins or members - if (authorRole === "ADMIN") { return ["ADMIN", "MEMBER", "VIEWER"].indexOf(role) !== -1; } - // MEMBERS and non-members cannot remove anyone of any role - return false; - }; - - var canUpdateMetadata = function (author, members) { - var authorRole = Util.find(members, [author, 'role']); - return Boolean(authorRole && ['OWNER', 'ADMIN'].indexOf(authorRole) !== -1); - }; - - var shouldCheckpoint = function (me, ref) { - // if you can't send valid checkpoints, don't try - if (!canCheckpoint(me, ref.state.members)) { return false; } - - // avoid sending checkpoints too often - // it's a balance between network constraints - // and the size of the roster's log - var since = ref.internal.sinceLastCheckpoint; - - if (!since || typeof(since) !== 'number' || since < CHECKPOINT_INTERVAL) { - return false; - } - - // if you can't think of any other reason not to... - return true; - }; - - var commands = Roster.commands = {}; - /* Commands are functions with the signature - (args_any, base46_author_string, roster_map, optional_base64_message_id) => boolean - - they: - * throw if any of their arguments are invalid - * return true if their application to previous state results in a change - * mutate the local account of the current state - - changes to the state can be simulated locally before being sent. - if the simulation throws or returns false, don't send. - - */ - - // the author is trying to add someone to the roster - // owners can add any role - commands.ADD = function (args, author, roster) { - if (!isMap(args)) { throw new Error("INVALID ARGS"); } - if (!roster.internal.initialized) { throw new Error("UNITIALIZED"); } - if (typeof(roster.state.members) === 'undefined') { - throw new Error("CANNOT_ADD_TO_UNITIALIZED_ROSTER"); - } - var members = roster.state.members; - - // iterate over everything and make sure it is valid, throw if not - Object.keys(args).forEach(function (curve) { - // FIXME only allow valid curve keys, anything else is pollution - if (!isValidId(curve)) { - console.log(curve, curve.length); - throw new Error("INVALID_CURVE_KEY"); - } - // reject commands where the members are not proper objects - if (!isMap(args[curve])) { throw new Error("INVALID_CONTENT"); } - if (members[curve]) { throw new Error("ALREADY_PRESENT"); } - - var data = args[curve]; - // if no role was provided, assume MEMBER - if (typeof(data.role) !== 'string') { data.role = 'MEMBER'; } - - if (!canAddRole(author, data.role, members)) { - throw new Error("INSUFFICIENT_PERMISSIONS"); - } - - if (typeof(data.displayName) !== 'string') { throw new Error("DISPLAYNAME_REQUIRED"); } - if (typeof(data.notifications) !== 'string') { throw new Error("NOTIFICATIONS_REQUIRED"); } - }); - - var changed = false; - // then iterate again and apply it - Object.keys(args).forEach(function (curve) { - // this will result in a change - changed = true; - members[curve] = args[curve]; - }); - - return changed; - }; - - commands.RM = function (args, author, roster) { - if (!Array.isArray(args)) { throw new Error("INVALID_ARGS"); } - - if (typeof(roster.state.members) === 'undefined') { - throw new Error("CANNOT_RM_FROM_UNITIALIZED_ROSTER"); - } - var members = roster.state.members; - - // validate first... - args.forEach(function (curve) { - if (!isValidId(curve)) { throw new Error("INVALID_CURVE_KEY"); } - - // even members can remove themselves - if (curve === author) { return; } - - // but if it concerns anyone else, validate that the author has sufficient permissions - var role = members[curve].role; - if (!canRemoveRole(author, role, members)) { throw new Error("INSUFFICIENT_PERMISSIONS"); } - }); - - var changed = false; - args.forEach(function (curve) { - // don't try to remove something that isn't there - if (!members[curve]) { return; } - changed = true; - delete members[curve]; - }); - return changed; - }; - - commands.DESCRIBE = function (args, author, roster) { - if (!args || typeof(args) !== 'object' || Array.isArray(args)) { - throw new Error("INVALID_ARGUMENTS"); - } - - if (typeof(roster.state.members) === 'undefined') { - throw new Error("NOT_READY"); - } - var members = roster.state.members; - - // iterate over all the data and make sure it is valid, throw otherwise - Object.keys(args).forEach(function (curve) { - if (!isValidId(curve)) { throw new Error("INVALID_ID"); } - if (!members[curve]) { throw new Error("NOT_PRESENT"); } - - if (!canDescribeTarget(author, curve, members)) { throw new Error("INSUFFICIENT_PERMISSIONS"); } - - var data = args[curve]; - if (!isMap(data)) { throw new Error("INVALID_ARGUMENTS"); } - - var current = Util.clone(members[curve]); - - if (typeof(data.role) === 'string') { // they're trying to change the role... - // throw if they're trying to upgrade to something greater - if (!isSelfDowngrade(author, curve, data.role, members) && - !canAddRole(author, data.role, members)) { - throw new Error("INSUFFICIENT_PERMISSIONS"); - } - } - // DESCRIBE commands must initialize a displayName if it isn't already present - if (typeof(current.displayName) !== 'string' && typeof(data.displayName) !== 'string') { - throw new Error('DISPLAYNAME_REQUIRED'); - } - - if (['undefined', 'string'].indexOf(typeof(data.displayName)) === -1) { - throw new Error("INVALID_DISPLAYNAME"); - } - - // DESCRIBE commands must initialize a mailbox channel if it isn't already present - if (typeof(current.notifications) !== 'string' && typeof(data.notifications) !== 'string') { - throw new Error('NOTIFICATIONS_REQUIRED'); - } - if (['undefined', 'string'].indexOf(typeof(data.notifications)) === -1) { - throw new Error("INVALID_NOTIFICATIONS"); - } - }); - - var changed = false; - // then do a second pass and apply it if there were changes - Object.keys(args).forEach(function (curve) { - var current = Util.clone(members[curve]); - - var data = args[curve]; - - Object.keys(data).forEach(function (key) { - // when null is passed as new data and it wasn't considered an invalid change - // remove it from the map. This is how you delete things properly - if (typeof(current[key]) !== 'undefined' && data[key] === null) { return void delete current[key]; } - current[key] = data[key]; - }); - - if (Sortify(current) !== Sortify(members[curve])) { - changed = true; - members[curve] = current; - } - }); - - return changed; - }; - - commands.CHECKPOINT = function (args, author, roster) { - // args: complete state - - // args should be a map - if (!isMap(args)) { throw new Error("INVALID_CHECKPOINT_STATE"); } - - if (!roster.internal.initialized) { - //console.log("INITIALIZING"); - // either you're connecting from the beginning of the log - // or from a trusted lastKnownHash. - // Either way, initialize the roster state - - roster.state = args; - var metadata = roster.state.metadata = roster.state.metadata || {}; - metadata.topic = metadata.topic || ''; - metadata.name = metadata.name || ''; - metadata.avatar = metadata.avatar || ''; - - roster.internal.initialized = true; - return true; - } else if (Sortify(args) !== Sortify(roster.state)) { - // a checkpoint must reinsert the previous state - throw new Error("CHECKPOINT_DOES_NOT_MATCH_PREVIOUS_STATE"); - } - - // otherwise, you're iterating over the log from a previous checkpoint - // so you should know everyone's role - - // owners and admins can checkpoint. members and non-members cannot - if (!canCheckpoint(author, roster.state.members)) { throw new Error("INSUFFICIENT_PERMISSIONS"); } - - // set the state, and indicate that a change was made - roster.state = args; - return true; - }; - - var MANDATORY_METADATA_FIELDS = [ - 'avatar', - 'name', - 'topic', - ]; - - // only admin/owner can change group metadata - commands.METADATA = function (args, author, roster) { - if (!isMap(args)) { throw new Error("INVALID_ARGS"); } - - if (!canUpdateMetadata(author, roster.state.members)) { throw new Error("INSUFFICIENT_PERMISSIONS"); } - - // validate inputs - Object.keys(args).forEach(function (k) { - if (args[k] === null) { - if (MANDATORY_METADATA_FIELDS.indexOf(k) === -1) { return; } - throw new Error('CANNOT_REMOVE_MANDATORY_METADATA'); - } - - // can't set metadata to anything other than strings - // use empty string to unset a value if you must - if (typeof(args[k]) !== 'string') { throw new Error("INVALID_ARGUMENTS"); } - }); - - var changed = false; - // {topic, name, avatar} are all strings... - Object.keys(args).forEach(function (k) { - if (typeof(roster.state.metadata[k]) !== 'undefined' && args[k] === null) { - changed = true; - delete roster.state.metadata[k]; - } - - // ignore things that won't cause changes - if (args[k] === roster.state.metadata[k]) { return; } - - changed = true; - roster.state.metadata[k] = args[k]; - }); - return changed; - }; - - commands.INVITE = function (args, author, roster) { - // an invitation is created with an ephemeral curve public key - // that key is ultimately given to the user you'd like on your team - // that user can exploit their possession of the public key to remove - // the pending invitation with their actual data. - if (!isMap(args)) { throw new Error('INVALID_ARGS'); } - if (!roster.internal.initialized) { throw new Error("UNINITIALIED"); } - if (typeof(roster.state.members) === 'undefined') { - throw new Error("CANNOT+INVITE_TO_UNINITIALIED_ROSTER"); - } - - var members = roster.state.members; - - Object.keys(args).forEach(function (curve) { - if (!isValidId(curve)) { - console.log(curve, curve.length); - throw new Error("INVALID_CURVE_KEY"); - } - // reject commandws wehere the members are not proper objects - if (!isMap(args[curve])) { throw new Error("INVALID_CONTENT"); } - if (members[curve]) { throw new Error("ARLEADY_PRESENT"); } - - var data = args[curve]; - // if no role was provided, assume VIEWER - if (typeof(data.role) !== 'string') { data.role = "VIEWER"; } - - // assume that invitations are 'pending' unless stated otherwise - if (typeof(data.pending) === 'undefined') { data.pending = true; } - - if (!canAddRole(author, data.role, members)) { - throw new Error("INSUFFICIENT_PERMISSIONS"); - } - - if (typeof(data.displayName) !== 'string' || !data.displayName) { throw new Error("DISPLAYNAME_REQUIRED"); } - //if (typeof(data.notifications) !== 'string') { throw new Error("NOTIFICATIONS_REQUIRED"); } - }); - - - /* - { - : { - role: ??? || 'VIEWER', - displayName: '', - pending: true, - } - } - */ - - var changed = false; - - Object.keys(args).forEach(function (curve) { - changed = true; - members[curve] = args[curve]; - }); - - return changed; - }; - - commands.ACCEPT = function (args, author, roster) { - if (!roster.internal.initialized) { throw new Error("UNINITIALIED"); } - if (typeof(roster.state.members) === 'undefined') { - throw new Error("CANNOT_ADD_TO_UNINITIALIED_ROSTER"); - } - - // an ACCEPT command replaces a pending invitation's curve key with a new one - // after which the invited member can use their actual curve key to describe themselves - - // the author must have been invited already... - var members = roster.state.members; - - // so you must already be in the members list - if (!isMap(members[author])) { throw new Error("INSUFFICIENT_PERMISSIONS"); } - // and your membership must indicate that you are 'pending' - if (!members[author].pending) { throw new Error("ALREADY_PRESENT"); } - - // args should be a string - if (typeof(args) !== 'string') { throw new Error("INVALID_ARGS"); } - // ...and a valid curve key - if (!isValidId(args)) { throw new Error("INVALID_CURVE_KEY"); } - - var curve = args; - - // and the curve key must not already be a member - if (typeof(members[curve]) !== 'undefined') { throw new Error("MEMBER_ALREADY_PRESENT"); } - - // copy the new profile from the old one - var clone = Util.clone(members[author]); - delete clone.remaining; - delete clone.totalUses; - delete clone.inviteChannel; - delete clone.previewChannel; - members[curve] = clone; - - var remaining = members[author].remaining || 1; - if (remaining === -1) { return true; } // Infinite uses, keep the link - if (remaining > 1) { // Remove 1 use - members[author].remaining = remaining - 1; - } else { // Disable link - delete members[author]; - } - - return true; - }; - - var handleCommand = function (content, author, roster) { - if (!(Array.isArray(content) && typeof(author) === 'string')) { - throw new Error("INVALID ARGUMENTS"); - } - - var command = content[0]; - if (typeof(commands[command]) !== 'function') { throw new Error('INVALID_COMMAND'); } - - return commands[command](content[1], author, roster); - }; - - var simulate = function (content, author, roster) { - return handleCommand(content, author, Util.clone(roster)); - }; - - Roster.create = function (config, _cb) { - if (typeof(_cb) !== 'function') { throw new Error("EXPECTED_CALLBACK"); } - var cb = Util.once(Util.mkAsync(_cb)); - - if (!config.network) { return void cb("EXPECTED_NETWORK"); } - if (!config.channel || typeof(config.channel) !== 'string' || config.channel.length !== 32) { return void cb("EXPECTED_CHANNEL"); } - if (!config.keys || typeof(config.keys) !== 'object') { return void cb("EXPECTED_CRYPTO_KEYS"); } - if (!config.store) { return void cb("EXPECTED_STORE"); } - - - var response = Util.response(function (label, info) { - console.error('ROSTER_RESPONSE__' + label, info); - }); - var store = config.store; - var keys = config.keys; - var me = keys.myCurvePublic; - var channel = config.channel; - var lastKnownHash = config.lastKnownHash || -1; - - // make sure we don't send -1 (ask for full history) when we are trying to create a new team - if (config.newTeam) { - lastKnownHash = undefined; - } - - var ref = { - state: { - members: { }, - metadata: { }, - }, - internal: { - initialized: false, - sinceLastCheckpoint: 0, - lastCheckpointHash: lastKnownHash, - }, - }; - var roster = {}; - var events = { - change: Util.mkEvent(), - checkpoint: Util.mkEvent(), - }; - - roster.on = function (key, handler) { - if (typeof(events[key]) !== 'object') { throw new Error("unsupported event"); } - events[key].reg(handler); - return roster; - }; - - roster.off = function (key, handler) { - if (typeof(events[key]) !== 'object') { throw new Error("unsupported event"); } - events[key].unreg(handler); - return roster; - }; - - roster.once = function (key, handler) { - if (typeof(events[key]) !== 'object') { throw new Error("unsupported event"); } - var f = function () { - handler.apply(null, Array.prototype.slice.call(arguments)); - events[key].unreg(f); - }; - events[key].reg(f); - return roster; - }; - - roster.getState = function () { - //if (!isMap(ref.state)) { return; } - return Util.clone(ref.state); - }; - - roster.getLastCheckpointHash = function () { - return ref.internal.lastCheckpointHash || -1; - }; - - var clearPendingCheckpoints = function () { - // clear any pending checkpoints you might have... - if (ref.internal.pendingCheckpointId) { - response.clear(ref.internal.pendingCheckpointId); - delete ref.internal.pendingCheckpointId; - } - clearTimeout(ref.internal.checkpointTimeout); - delete ref.internal.checkpointTimeout; - }; - - roster.stop = function () { - if (ref.internal.cpNetflux && typeof(ref.internal.cpNetflux.stop) === "function") { - ref.internal.cpNetflux.stop(); - clearPendingCheckpoints(); - } else { - console.log("FAILED TO LEAVE"); - } - }; - var ready = false; - var onCacheReady = function () { - if (!config.onCacheReady) { return; } - var state = ref.state; - if (!Object.keys(state.members || {}).length) { - // No member, corrupted cache - try { - ref.internal.cpNetflux.resetCache(); - } catch (e) { console.error(e); } - return void config.onCacheReady({error: "CORRUPTED"}); - } - config.onCacheReady(roster); - }; - var onReady = function () { - //console.log("READY"); - ready = true; - cb(void 0, roster); - }; - - // onError (deleted or expired) - // you won't be able to connect - - // onMetadataUpdate - // update owners? - - // deleted while you are open - // emit an event - var onChannelError = function (info) { - if (Feedback) { Feedback.send('ROSTER_CHANNEL_ERROR='+(info && info.type)); } - if (info && info.type === "EUNKNOWN") { - // chainpad-netflux should recover by itself - return; - } - if (!ready) { return void cb(info); } - console.error("CHANNEL_ERROR", info); - }; - - var onConnectionChange = function (info) { - if (info.state) { return; } - // Disconnect: don't send event anymore until ready - ready = false; - }; - - var onConnect = function (/* wc, sendMessage */) { - console.log("ROSTER CONNECTED"); - }; - - var isReady = function () { - return Boolean(ready && me); - }; - - var lastHash; - var onMessage = function (msg, user, vKey, isCp , hash, author) { - // count messages received since the last checkpoint - // even if they fail to parse - - if (lastHash !== hash) { - // Don't count duplicate lkh message on reconnect - ref.internal.sinceLastCheckpoint++; - } - - lastHash = hash; - var parsed = Util.tryParse(msg); - - if (!parsed) { return void console.error("could not parse"); } - - var changed; - var error; - try { - changed = handleCommand(parsed, author, ref); - } catch (err) { - error = err.message; - } - - var id = getMessageId(hash); - - if (response.expected(id)) { - if (error) { return void response.handle(id, [error]); } - try { - if (!changed) { - response.handle(id, ['NO_CHANGE']); - console.log(msg); - } else { - response.handle(id, [void 0, roster.getState()]); - } - } catch (err) { - console.log('CAUGHT', err); - } - } - - // if a checkpoint was successfully applied, emit an event - if (parsed[0] === 'CHECKPOINT' && changed) { - if (isReady()) { events.checkpoint.fire(hash); } - // reset the counter for messages since the last checkpoint - ref.internal.sinceLastCheckpoint = 0; - ref.internal.lastCheckpointHash = hash; - } else if (changed) { - if (isReady()) { events.change.fire(); } - } - - // CHECKPOINT logic... - clearPendingCheckpoints(); - if (!isReady() || !shouldCheckpoint(me, ref)) { return; } - // a random number of seconds between 5 and 25 - var delay = (1000 * Math.floor(Math.random() * 20)) + 5000; - - // if you're here then you can and should send a checkpoint - // but since multiple users who can and should might be online at once - // and since they'll all trigger this process at the same time... - // we want to stagger attempts at random intervals - ref.internal.checkpointTimeout = setTimeout(function () { - ref.internal.pendingCheckpointId = roster.checkpoint(function (err) { - if (err) { console.error(err); } - }); - }, delay); - }; - - var isCacheCheckpoint = function (msg, author) { - var parsed = Util.tryParse(msg); - if (parsed[0] !== 'CHECKPOINT') { return false; } - var changed = simulate(parsed, author, ref); - return changed; - }; - - var metadata, crypto; - var send = function (msg, cb) { - if (!isReady()) { return void cb("NOT_READY"); } - var anon_rpc = store.anon_rpc; - if (!anon_rpc) { return void cb("ANON_RPC_NOT_READY"); } - - var changed = false; - try { - // simulate the command before you send it - changed = simulate(msg, keys.myCurvePublic, ref); - } catch (err) { - return void cb(err.message); - } - if (!changed) { return void cb("NO_CHANGE"); } - - var ciphertext = crypto.encrypt(Sortify(msg)); - - var id = getMessageId(ciphertext); - - //console.log("Sending with id [%s]", id, msg); - //console.log(); - - response.expect(id, function (err, state) { - if (err) { return void cb(err); } - cb(void 0, state, id); - }, TIMEOUT_INTERVAL); - anon_rpc.send('WRITE_PRIVATE_MESSAGE', [ - channel, - ciphertext - ], function (err) { - if (err) { return response.handle(id, [err.message || err]); } - }); - return id; - }; - - roster.init = function (_data, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - if (ref.internal.initialized) { return void cb("ALREADY_INITIALIZED"); } - if (!isMap(_data)) { return void cb("INVALID_ARGUMENTS"); } - var data = Util.clone(_data); - data.role = 'OWNER'; - var members = {}; - members[me] = data; - send([ 'CHECKPOINT', { members: members } ], cb); - }; - - // commands - roster.checkpoint = function (_cb) { - var cb = Util.once(Util.mkAsync(_cb)); - send([ 'CHECKPOINT', Util.clone(ref.state)], cb); - }; - - roster.add = function (_data, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - //var state = ref.state; - if (!ref.internal.initialized) { return cb("UNINITIALIZED"); } - if (!isMap(_data)) { return void cb("INVALID_ARGUMENTS"); } - var data = Util.clone(_data); - - // don't add members that are already present - // use DESCRIBE to amend - Object.keys(data).forEach(function (curve) { - if (!isValidId(curve) || isMap(ref.state.members[curve])) { return delete data[curve]; } - }); - - send([ 'ADD', data ], cb); - }; - - roster.remove = function (_data, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - var state = ref.state; - if (!state) { return cb("UNINITIALIZED"); } - - if (!Array.isArray(_data)) { return void cb("INVALID_ARGUMENTS"); } - var data = Util.clone(_data); - - var toRemove = []; - var current = Object.keys(state.members); - data.forEach(function (curve) { - // don't try to remove elements which are not in the current state - if (current.indexOf(curve) === -1) { return; } - toRemove.push(curve); - }); - - send([ 'RM', toRemove ], cb); - }; - - roster.describe = function (_data, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - var state = ref.state; - - if (!state) { return cb("UNINITIALIZED"); } - if (!isMap(_data)) { return void cb("INVALID_ARGUMENTS"); } - var data = Util.clone(_data); - - if (Object.keys(data).some(function (curve) { - var member = data[curve]; - if (!isMap(member)) { delete data[curve]; } - // validate that you're trying to describe a user that is present - if (!isMap(state.members[curve])) { return true; } - // don't send fields that won't result in a change - Object.keys(member).forEach(function (k) { - if (member[k] === state.members[curve][k]) { delete member[k]; } - }); - })) { - // returning true in the above loop indicates that something was invalid - return void cb("INVALID_ARGUMENTS"); - } - - send(['DESCRIBE', data], cb); - }; - - roster.metadata = function (_data, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - var metadata = ref.state.metadata; - if (!isMap(_data)) { return void cb("INVALID_ARGUMENTS"); } - var data = Util.clone(_data); - - Object.keys(data).forEach(function (k) { - if (data[k] === metadata[k]) { delete data[k]; } - }); - send(['METADATA', data], cb); - }; - - // supports multiple invite - roster.invite = function (_data, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - var state = ref.state; - if (!state) { return cb("UNINITIALIZED"); } - if (!ref.internal.initialized) { return cb("UNINITIALIZED"); } - if (!isMap(_data)) { return void cb("INVALID_ARGUMENTS"); } - var data = Util.clone(_data); - - Object.keys(data).forEach(function (curve) { - if (!isValidId(curve) || isMap(ref.state.members[curve])) { return delete data[curve]; } - }); - - send(['INVITE', data], cb); - }; - - roster.accept = function (_data, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - if (typeof(_data) !== 'string' || !isValidId(_data)) { - return void cb("INVALID_ARGUMENTS"); - } - - send([ 'ACCEPT', _data ], cb); - }; - - nThen(function (w) { - // get metadata so we know the owners and validateKey - if (!store.anon_rpc) { return; } - store.anon_rpc.send('GET_METADATA', channel, function (err, data) { - if (err) { - w.abort(); - return void console.error(err); - } - metadata = ref.internal.metadata = (data && data[0]) || undefined; - }); - }).nThen(function (w) { - if (!config.keys.teamEdPublic && metadata && metadata.validateKey) { - config.keys.teamEdPublic = metadata.validateKey; - } - if (!config.keys.teamEdPublic) { - w.abort(); - return void cb("NO_VALIDATE_KEY"); - } - - try { - crypto = Crypto.Team.createEncryptor(config.keys); - } catch (err) { - w.abort(); - return void cb(err); - } - }).nThen(function () { - if (typeof(lastKnownHash) === 'string') { - console.log("Synchronizing from checkpoint"); - } - - ref.internal.cpNetflux = CPNetflux.start({ - // if you don't have a lastKnownHash you will need the full history - // passing -1 forces the server to send all messages, otherwise - // malicious users with the signing key could send cp| messages - // and fool new users into initializing their session incorrectly - lastKnownHash: lastKnownHash, - - network: config.network, - channel: config.channel, - - crypto: crypto, - validateKey: config.keys.teamEdPublic, - - owners: config.owners, - - Cache: config.Cache, - isCacheCheckpoint: isCacheCheckpoint, - onCacheReady: onCacheReady, - - onChannelError: onChannelError, - onReady: onReady, - onConnect: onConnect, - onConnectionChange: onConnectionChange, - onMessage: onMessage, - - noChainPad: true, - }); - }); - }; - - return Roster; -}; - - if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - require("../../common/common-util"), - require("../../common/common-hash"), - require('chainpad-netflux'), - require('json.sortify'), - require("nthen"), - require("chainpad-crypto"), - null // no feedback here - ); - } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - require.config({ paths: { 'json.sortify': '/components/json.sortify/dist/JSON.sortify' } }); - define([ - '/common/common-util.js', - '/common/common-hash.js', - 'chainpad-netflux', - 'json.sortify', - '/components/nthen/index.js', - '/components/chainpad-crypto/crypto.js', - '/common/common-feedback.js', - //'/components/tweetnacl/nacl-fast.min.js', - ], function (Util, Hash, CPNF, Sortify, nThen, Crypto, Feedback) { - return factory.apply(null, [ - Util, - Hash, - CPNF, - Sortify, - nThen, - Crypto, - Feedback - ]); - }); - } else { - // I'm not gonna bother supporting any other kind of instanciation - } -}()); diff --git a/www/common/outer/sharedfolder.js b/www/common/outer/sharedfolder.js deleted file mode 100644 index 86832b9fa..000000000 --- a/www/common/outer/sharedfolder.js +++ /dev/null @@ -1,417 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(() => { -const factory = (Hash, Util, UserObject, Cache, - nThen, Crypto, Listmap, ChainPad) => { - var SF = {}; - - /* load - create and load a proxy using listmap for a given shared folder - - config: network and "manager" (either the user one or a team manager) - - id: shared folder id - */ - - var allSharedFolders = {}; - - // No version: visible edit - // Version 2: encrypted edit links - SF.checkMigration = function (secondaryKey, proxy, uo, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - if (!proxy) { return void cb(); } - // View access: can't migrate - if (!secondaryKey) { return void cb(); } - // Already migrated: nothing to do - if (proxy.version >= 2) { return void cb(); } - // Not yet migrating: migrate - if (!proxy.migrateRo) { return void uo.migrateReadOnly(cb); } - // Already migrating: wait for the end... - var done = false; - var to; - var it = setInterval(function () { - if (proxy.version >= 2) { - done = true; - clearTimeout(to); - clearInterval(it); - return void cb(); - } - }, 100); - to = setTimeout(function () { - clearInterval(it); - uo.migrateReadOnly(function () { - done = true; - cb(); - }); - }, 20000); - var path = ['version']; - proxy.on('change', path, function () { - if (done) { return; } - if (proxy.version >= 2) { - done = true; - clearTimeout(to); - clearInterval(it); - cb(); - } - }); - }; - - // SFMIGRATION: only needed if we want a manual migration from the share modal... - SF.migrate = function (channel) { - var sf = allSharedFolders[channel]; - if (!sf) { return; } - var clients = sf.teams; - if (!Array.isArray(clients) || !clients.length) { return; } - var c = clients[0]; - // No secondaryKey? ==> already migrated ==> abort - if (!c.secondaryKey) { return; } - var f = Util.find(c, ['store', 'manager', 'folders', c.id]); - // Can't find the folder: abort - if (!f) { return; } - // Already migrated: abort - if (!f.proxy || f.proxy.version) { return; } - f.userObject.migrateReadOnly(function () { - clients.forEach(function (obj) { - var uo = Util.find(obj, ['store', 'manager', 'folders', obj.id, 'userObject']); - uo.setReadOnly(false, obj.secondarykey); - }); - }); - }; - - SF.load = function (config, id, data, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - var network = config.network; - var store = config.store; - var isNew = config.isNew; - var isNewChannel = config.isNewChannel; - var teamId = store.id; - var handler = store.handleSharedFolder; - - var href = store.manager.user.userObject.getHref(data); - - var parsed = Hash.parsePadUrl(href); - var secret = Hash.getSecrets('drive', parsed.hash, data.password); - // If we don't have valid keys, abort and remove the proxy to make sure - // we don't block the drive permanently - if (!secret.keys) { - store.manager.deprecateProxy(id); - return void cb(null); - } - var secondaryKey = secret.keys.secondaryKey; - - // If we try to load an existing shared folder (isNew === false) but this folder - // doesn't exist in the database, abort and cb - nThen(function (waitFor) { - // If we're in onCacheReady, make sure we have a cache for this shared folder - if (config.cache) { - Cache.getChannelCache(secret.channel, waitFor(function (err) { - if (err === "EINVAL") { // Cache not found - waitFor.abort(); - store.manager.restrictedProxy(id, secret.channel); - return void cb(null); - } - })); - } - }).nThen(function (waitFor) { - isNewChannel(null, { channel: secret.channel }, waitFor(function (obj) { - if (obj.isNew && !isNew) { - store.manager.deprecateProxy(id, secret.channel, obj.reason); - waitFor.abort(); - return void cb(null); - } - })); - }).nThen(function () { - var sf = allSharedFolders[secret.channel]; - if (sf && sf.readOnly && secondaryKey) { - // We were in readOnly mode and now we know the edit keys! - SF.upgrade(secret.channel, secret); - } - if (sf && sf.ready && sf.rt) { - // The shared folder is already loaded, return its data - setTimeout(function () { - var leave = function () { SF.leave(secret.channel, teamId); }; - /* - var uo = store.manager.addProxy(id, sf.rt, leave, secondaryKey); - // NOTE: Shared folder migration, disable for now - SF.checkMigration(secondaryKey, sf.rt.proxy, uo, function () { - cb(sf.rt); - }); - */ - store.manager.addProxy(id, sf.rt, leave, secondaryKey); - cb(sf.rt); - }); - sf.teams.push({ - cb: cb, - store: store, - id: id - }); - if (handler) { handler(id, sf.rt); } - return; - } - if (sf && !sf.ready && sf.rt) { - // The shared folder is loading, add our callbacks to the queue - sf.teams.push({ - cb: cb, - store: store, - secondaryKey: secondaryKey, - id: id - }); - if (handler) { handler(id, sf.rt); } - return; - } - - sf = allSharedFolders[secret.channel] = { - teams: [{ - cb: cb, - store: store, - secondaryKey: secondaryKey, - id: id - }], - readOnly: !Boolean(secondaryKey) - }; - - var owners = data.owners; - var listmapConfig = { - data: {}, - channel: secret.channel, - readOnly: !Boolean(secondaryKey), - crypto: Crypto.createEncryptor(secret.keys), - userName: 'sharedFolder', - logLevel: 1, - ChainPad: ChainPad, - classic: true, - network: network, - Cache: Cache, // shared-folder cache - metadata: { - validateKey: secret.keys.validateKey || undefined, - owners: owners - }, - onRejected: config.Store && config.Store.onRejected - }; - var rt = sf.rt = Listmap.create(listmapConfig); - rt.proxy.on('cacheready', function () { - if (!sf.teams) { - return; - } - sf.teams.forEach(function (obj) { - var leave = function () { SF.leave(secret.channel, obj.store.id); }; - - // We can safely call addProxy and obj.cb here because - // 1. addProxy won't re-add the same folder twice on 'ready' - // 2. obj.cb is using Util.once - rt.cache = true; - - // If we're updating the password of an existing folder, force the creation - // of a new userobject in proxy-manager. Once it's done, remove this flag - // to make sure we won't create a second new userobject on 'ready' - obj.store.manager.addProxy(obj.id, rt, leave, obj.secondaryKey, config.updatePassword); - config.updatePassword = false; - obj.cb(sf.rt); - }); - sf.ready = true; - }); - rt.proxy.on('ready', function () { - if (isNew && !Object.keys(rt.proxy).length) { - // New Shared folder: no migration required - rt.proxy.version = 2; - } - if (!sf.teams) { - return; - } - sf.teams.forEach(function (obj) { - var leave = function () { SF.leave(secret.channel, obj.store.id); }; - /* - var uo = obj.store.manager.addProxy(obj.id, rt, leave, obj.secondaryKey); - // NOTE: Shared folder migration, disable for now - SF.checkMigration(secondaryKey, rt.proxy, uo, function () { - obj.cb(sf.rt); - }); - */ - rt.cache = false; - obj.store.manager.addProxy(obj.id, rt, leave, obj.secondaryKey, config.updatePassword); - obj.cb(sf.rt); - }); - sf.ready = true; - }); - rt.proxy.on('error', function (info) { - if (info && info.error) { - if (info.error === "EDELETED" ) { - try { - // Deprecate the shared folder from each team - // We can only hide it - sf.teams.forEach(function (obj) { - obj.store.manager.deprecateProxy(obj.id, secret.channel, info.message); - if (obj.store.handleSharedFolder) { - obj.store.handleSharedFolder(obj.id, null); - } - obj.cb(); - }); - } catch (e) {} - delete allSharedFolders[secret.channel]; - // This shouldn't be called on init because we're calling "isNewChannel" first, - // but we can still call "cb" just in case. This wait we make sure we won't block - // the initial "waitFor" - return void cb(); - } - if (info.error === "ERESTRICTED" ) { - sf.teams.forEach(function (obj) { - obj.store.manager.restrictedProxy(obj.id, secret.channel); - obj.cb(); - }); - delete allSharedFolders[secret.channel]; - return void cb(); - } - } - }); - - if (handler) { handler(id, rt); } - }); - }; - - - SF.upgrade = function (channel, secret) { - var sf = allSharedFolders[channel]; - if (!sf || !sf.readOnly) { return; } - if (!sf.rt.setReadOnly) { return; } - - if (!secret.keys || !secret.keys.editKeyStr) { return; } - var crypto = Crypto.createEncryptor(secret.keys); - sf.readOnly = false; - sf.rt.setReadOnly(false, crypto); - }; - - SF.leave = function (channel, teamId) { - var sf = allSharedFolders[channel]; - if (!sf) { return; } - var clients = sf.teams; - if (!Array.isArray(clients)) { return; } - // Remove the shared folder from the client's store and - // remove the client/team from our list - var idx; - clients.some(function (obj, i) { - if (obj.store.id === teamId) { - if (obj.store.handleSharedFolder) { - obj.store.handleSharedFolder(obj.id, null); - } - idx = i; - return true; - } - }); - if (typeof (idx) === "undefined") { return; } - // Remove the selected team - clients.splice(idx, 1); - - //If all the teams have closed this shared folder, stop it - if (clients.length) { return; } - if (sf.rt && sf.rt.stop) { - sf.rt.stop(); - } - }; - - // Update the password locally - SF.updatePassword = function (Store, data, network, cb) { - var oldChannel = data.oldChannel; - var href = data.href; - var password = data.password; - var parsed = Hash.parsePadUrl(href); - var secret = Hash.getSecrets(parsed.type, parsed.hash, password); - var sf = allSharedFolders[oldChannel]; - if (!sf) { return void cb({ error: 'ENOTFOUND' }); } - if (sf.rt && sf.rt.stop) { - try { sf.rt.stop(); } catch (e) {} - } - var nt = nThen; - sf.teams.forEach(function (obj) { - nt = nt(function (waitFor) { - var s = obj.store; - var sfId = obj.id; - var shared = Util.find(s.proxy, ['drive', UserObject.SHARED_FOLDERS]) || {}; - if (!sfId || !shared[sfId]) { return; } - var sf = JSON.parse(JSON.stringify(shared[sfId])); - sf.password = password; - SF.load({ - network: network, - store: s, - updatePassword: true, - Store: Store, - isNewChannel: Store.isNewChannel - }, sfId, sf, waitFor()); - if (!s.rpc) { return; } - s.rpc.unpin([oldChannel], waitFor()); - s.rpc.pin([secret.channel], waitFor()); - }).nThen; - }); - nt(function () { - cb(); - }); - }; - - /* loadSharedFolders - load all shared folder stored in a given drive - - store: user or team main store - - userObject: userObject associated to the main drive - - handler: a function (sfid, rt) called for each shared folder loaded - */ - SF.loadSharedFolders = function (Store, network, store, drive, userObject, waitFor, progress, cache) { - var shared = drive[UserObject.SHARED_FOLDERS] || {}; - var steps = Object.keys(shared).length; - var i = 1; - var w = waitFor(); - progress = progress || function () {}; - nThen(function (waitFor) { - Object.keys(shared).forEach(function (id) { - var sf = shared[id]; - SF.load({ - network: network, - store: store, - Store: Store, - cache: cache, - isNewChannel: Store.isNewChannel - }, id, sf, waitFor(function () { - progress({ - progress: i, - max: steps - }); - i++; - })); - }); - }).nThen(function () { - setTimeout(w); - }); - }; - - SF.isSharedFolderChannel = function (chanId) { - return Object.keys(allSharedFolders).includes(chanId); - }; - - return SF; -}; - -if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - require('../../common/common-hash'), - require('../../common/common-util'), - require('../../common/user-object'), - require('../../common/cache-store'), - require('nthen'), - require('chainpad-crypto'), - require('chainpad-listmap'), - require('chainpad') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/common/common-hash.js', - '/common/common-util.js', - '/common/user-object.js', - '/common/outer/cache-store.js', - - '/components/nthen/index.js', - '/components/chainpad-crypto/crypto.js', - 'chainpad-listmap', - '/components/chainpad/chainpad.dist.js', - ], factory); -} else { - // unsupported initialization -} - -})(); diff --git a/www/common/outer/upload.js b/www/common/outer/upload.js index 6c85d0eed..32c49ff20 100644 --- a/www/common/outer/upload.js +++ b/www/common/outer/upload.js @@ -6,7 +6,7 @@ define([ '/file/file-crypto.js', '/common/common-hash.js', '/common/common-util.js', - '/common/outer/cache-store.js', + '/common/cache-store.js', '/components/nthen/index.js', ], function (FileCrypto, Hash, Util, Cache, nThen) { var module = {}; diff --git a/www/common/pad-types.js b/www/common/pad-types.js deleted file mode 100644 index 8c2f8014b..000000000 --- a/www/common/pad-types.js +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(() => { -const factory = (AppConfig = {}, ApiConfig = {}, - OOCurrentVersion) => { - - let availablePadTypes = []; - const OO_APPS = ["sheet", "doc", "presentation"]; - - const setCustomize = data => { - AppConfig = data.AppConfig; - ApiConfig = data.ApiConfig; - - const ooEnabled = ApiConfig.onlyOffice && - ApiConfig.onlyOffice.availableVersions.includes( - OOCurrentVersion.currentVersion - ); - availablePadTypes = AppConfig.availablePadTypes.filter( - (t) => ooEnabled || !OO_APPS.includes(t) - ); - }; - - // Initialize values when using in browser directly - if (Object.keys(AppConfig).length) { - setCustomize({AppConfig,ApiConfig}); - } - - const Types = { setCustomize }; - - Types.__defineGetter__("availableTypes", function () { - if (ApiConfig.appsToDisable) { - return availablePadTypes.filter(value => { - return !ApiConfig.appsToDisable.includes(value); - }); - } - return availablePadTypes; - }); - Types.__defineGetter__("appsToSelect", function () { - return availablePadTypes.filter(value => !['drive', 'teams', 'file', 'contacts', 'convert'].includes(value)); - }); - Types.isAvailable = type => { - return Array.isArray(Types.availableTypes) && - Types.availableTypes.includes(type); - }; - - return Types; -}; - -if (typeof(module) !== 'undefined' && module.exports) { - // Code from customize can't be laoded directly in the build - module.exports = factory( - undefined, - undefined, - require('./onlyoffice/current-version') - ); -} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ - '/customize/application_config.js', - "/api/config", - "/common/onlyoffice/current-version.js", - ], factory); -} else { - // unsupported initialization -} - -})(); diff --git a/www/common/pinpad.js b/www/common/pinpad.js deleted file mode 100644 index c7b6f74e2..000000000 --- a/www/common/pinpad.js +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(function () { -var factory = function (Util, Rpc) { - var create = function (network, proxy, _cb, Cache) { - if (typeof(_cb) !== 'function') { throw new Error("Expected callback"); } - var cb = Util.once(Util.mkAsync(_cb)); - - if (!network) { return void cb('INVALID_NETWORK'); } - if (!proxy) { return void cb('INVALID_PROXY'); } - - var edPrivate = proxy.edPrivate; - var edPublic = proxy.edPublic; - - if (!(edPrivate && edPublic)) { return void cb('INVALID_KEYS'); } - - Rpc.create(network, edPrivate, edPublic, function (e, rpc) { - if (e) { return void cb(e); } - - var exp = {}; - - exp.destroy = rpc.destroy; - - // expose the supplied publicKey as an identifier - exp.publicKey = edPublic; - - // expose the RPC module's raw 'send' command - exp.send = rpc.send; - - // you can ask the server to pin a particular channel for you - exp.pin = function (channels, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - if (!Array.isArray(channels)) { - return void cb('[TypeError] pin expects an array'); - } - rpc.send('PIN', channels, cb); - }; - - // you can also ask to unpin a particular channel - exp.unpin = function (channels, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - if (!Array.isArray(channels)) { - return void cb('[TypeError] pin expects an array'); - } - rpc.send('UNPIN', channels, cb); - }; - - // Get data for the admin panel - exp.adminRpc = function (obj, cb) { - if (!obj.cmd) { - setTimeout(function () { - cb('[TypeError] admin rpc expects a command'); - }); - return; - } - var params = [obj.cmd, obj.data]; - rpc.send('ADMIN', params, cb); - }; - - // ask the server what it thinks your hash is - exp.getServerHash = function (cb) { - rpc.send('GET_HASH', edPublic, function (e, hash) { - if (!(hash && hash[0])) { - return void cb('NO_HASH_RETURNED'); - } - cb(e, Array.isArray(hash) && hash[0] || undefined); - }); - }; - - // if local and remote hashes don't match, send a reset - exp.reset = function (channels, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - if (!Array.isArray(channels)) { - return void cb('[TypeError] pin expects an array'); - } - rpc.send('RESET', channels, cb); - }; - - // get the combined size of all channels (in bytes) for all the - // channels which the server has pinned for your publicKey - exp.getFileListSize = function (cb) { - rpc.send('GET_TOTAL_SIZE', undefined, function (e, response) { - if (e) { return void cb(e); } - if (response && response.length && typeof(response[0]) === 'number') { - cb(void 0, response[0]); - } else { - cb('INVALID_RESPONSE'); - } - }); - }; - - // Update the limit value for all the users and return the limit for your publicKey - exp.updatePinLimits = function (cb) { - rpc.send('UPDATE_LIMITS', undefined, function (e, response) { - if (e) { return void cb(e); } - if (response && response.length && typeof(response[0]) === "number") { - cb (void 0, response[0], response[1], response[2]); - } else { - cb('INVALID_RESPONSE'); - } - }); - }; - // Get the storage limit associated with your publicKey - exp.getLimit = function (cb) { - rpc.send('GET_LIMIT', undefined, function (e, response) { - if (e) { return void cb(e); } - if (response && response.length && typeof(response[0]) === "number") { - cb (void 0, response[0], response[1], response[2]); - } else { - cb('INVALID_RESPONSE'); - } - }); - }; - - exp.trimHistory = function (data, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - if (typeof(data) !== 'object' || !data.channel || !data.hash) { - return void cb('INVALID_ARGUMENTS'); - } - rpc.send('TRIM_HISTORY', data, function (e) { - if (e) { return cb(e); } - cb(); - }); - }; - - exp.clearOwnedChannel = function (channel, cb) { - if (typeof(channel) !== 'string' || channel.length !== 32) { - return void cb('INVALID_ARGUMENTS'); - } - rpc.send('CLEAR_OWNED_CHANNEL', channel, function (e) { - if (e) { return cb(e); } - cb(); - }); - }; - - exp.removeOwnedChannel = function (channel, cb, reason) { - if (typeof(channel) !== 'string' || [32,48].indexOf(channel.length) === -1) { - console.error('invalid channel to remove', channel); - return void cb('INVALID_ARGUMENTS'); - } - rpc.send('REMOVE_OWNED_CHANNEL', { - channel: channel, - reason: reason - }, function (e, response) { - if (e) { return void cb(e); } - if (response && response.length && response[0] === "OK") { - cb(); - if (Cache && Cache.clearChannel) { - Cache.clearChannel(channel); - } - } else { - cb('INVALID_RESPONSE'); - } - }); - }; - - exp.removePins = function (cb) { - rpc.send('REMOVE_PINS', undefined, function (e, response) { - if (e) { return void cb(e); } - if (response && response.length && response[0] === "OK") { - cb(); - } else { - cb('INVALID_RESPONSE'); - } - }); - }; - - exp.uploadComplete = function (id, cb) { - rpc.send('UPLOAD_COMPLETE', id, function (e, res) { - if (e) { return void cb(e); } - var id = res[0]; - if (typeof(id) !== 'string') { - return void cb('INVALID_ID'); - } - cb(void 0, id); - }); - }; - - exp.ownedUploadComplete = function (id, cb) { - rpc.send('OWNED_UPLOAD_COMPLETE', id, function (e, res) { - if (e) { return void cb(e); } - var id = res[0]; - if (typeof(id) !== 'string') { - return void cb('INVALID_ID'); - } - cb(void 0, id); - }); - }; - - exp.uploadStatus = function (size, cb) { - if (typeof(size) !== 'number') { - return void setTimeout(function () { - cb('INVALID_SIZE'); - }); - } - rpc.send('UPLOAD_STATUS', size, function (e, res) { - if (e) { return void cb(e); } - var pending = res[0]; - if (typeof(pending) !== 'boolean') { - return void cb('INVALID_RESPONSE'); - } - cb(void 0, pending); - }); - }; - - exp.uploadCancel = function (size, cb) { - rpc.send('UPLOAD_CANCEL', size, function (e) { - if (e) { return void cb(e); } - cb(); - }); - }; - - // Get data for the admin panel - exp.setMetadata = function (obj, cb) { - rpc.send('SET_METADATA', { - channel: obj.channel, - command: obj.command, - value: obj.value - }, cb); - }; - - cb(e, exp); - }); - }; - - return { create: create }; -}; - if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory(require('./common-util'), require("./rpc")); - } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { - define([ '/common/common-util.js', '/common/rpc.js', ], function (Util, Rpc) { return factory(Util, Rpc); }); - } -}()); diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js deleted file mode 100644 index cb465ebd1..000000000 --- a/www/common/proxy-manager.js +++ /dev/null @@ -1,1865 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(() => { -const factory = (UserObject, Util, Hash, - SF, Messages = {}, Feedback, nThen) => { - - let setCustomize = data => { - Messages = data.Messages; - UserObject.setCustomize(data); - }; - - var getConfig = function (Env) { - var cfg = {}; - for (var k in Env.cfg) { cfg[k] = Env.cfg[k]; } - return cfg; - }; - - // Add a shared folder to the list - var addProxy = function (Env, id, lm, leave, editKey, force) { - if (Env.folders[id] && !force && !Env.folders[id].restricted) { - // Shared folder already added to the proxy-manager, probably - // a cached version - if (Env.folders[id].offline && !lm.cache && Env.Store) { - Env.folders[id].offline = false; - if (Env.folders[id].userObject.fixFiles) { Env.folders[id].userObject.fixFiles(); } - Env.Store.refreshDriveUI(); - } - return; - } - var cfg = getConfig(Env); - cfg.sharedFolder = true; - cfg.id = id; - cfg.editKey = editKey; - cfg.rt = lm.realtime; - cfg.readOnly = Boolean(!editKey); - var userObject = UserObject.init(lm.proxy, cfg); - if (userObject.fixFiles) { - // Only in outer - userObject.fixFiles(); - } - var proxy = lm.proxy; - if (proxy.metadata && proxy.metadata.title) { - var sf = Env.user.proxy[UserObject.SHARED_FOLDERS][id]; - if (sf) { - sf.lastTitle = proxy.metadata.title; - } - } - Env.folders[id] = { - proxy: lm.proxy, - userObject: userObject, - leave: leave, - restricted: proxy.restricted, - offline: Boolean(lm.cache) - }; - if (proxy.on) { - proxy.on('disconnect', function () { - Env.folders[id].offline = true; - }); - proxy.on('reconnect', function () { - Env.folders[id].offline = false; - }); - } - return userObject; - }; - - var removeProxy = function (Env, id) { - var f = Env.folders[id]; - if (!f) { return; } - f.leave(); - delete Env.folders[id]; - }; - - var sendNotification = (Env, sfId, title) => { - var mailbox = Env.store.mailbox; - if (!mailbox) { return; } - var team = Env.cfg.teamId; - var box; - if (team) { - let teams = Env.store.modules['team'].getTeamsData(); - box = teams[team]; - } else { - let md = Env.Store.getMetadata(null, null, () => {}); - box = md.user; - } - mailbox.sendTo('SF_DELETED', { - sfId: sfId, - team: team, - title: title - }, { - curvePublic: box.curvePublic, - channel: box.notifications - }, (err) => { - console.error(err); - }); - }; - - // Password may have changed - var deprecateProxy = function (Env, id, channel, reason) { - if (Env.folders[id] && Env.folders[id].deleting) { - // Folder is being deleted by its owner, don't deprecate it - return; - } - if (Env.user.userObject.readOnly) { - // In a read-only team, we can't deprecate a shared folder - // Use a empty object with a deprecated flag... - var lm = { proxy: { deprecated: true } }; - removeProxy(Env, id); - addProxy(Env, id, lm, function () {}); - return void Env.Store.refreshDriveUI(); - } - if (channel) { Env.unpinPads([channel], function () {}); } - - // If it's explicitely a deletion, no need to deprecate, just delete - if (reason && reason !== "PASSWORD_CHANGE") { - let temp = Util.find(Env, ['user', 'proxy', UserObject.SHARED_FOLDERS]); - let title = temp[id] && temp[id].lastTitle; - if (title) { sendNotification(Env, id, title); } - - delete temp[id]; - - if (Env.Store && Env.Store.refreshDriveUI) { Env.Store.refreshDriveUI(); } - return; - } - - // It's explicitely a password change, better message in drive: provide the "reason" to the UI - Env.user.userObject.deprecateSharedFolder(id, reason); - removeProxy(Env, id); - if (Env.Store && Env.Store.refreshDriveUI) { Env.Store.refreshDriveUI(); } - }; - - var restrictedProxy = function (Env, id) { - var lm = { proxy: { restricted: true, root: {}, filesData: {} } }; - removeProxy(Env, id); - addProxy(Env, id, lm, function () {}); - return void Env.Store.refreshDriveUI(); - }; - - /* - Tools - */ - var _ownedByMe = function (Env, owners) { - return Array.isArray(owners) && owners.indexOf(Env.edPublic) !== -1; - }; - var _ownedByOther = function (Env, owners) { - return Array.isArray(owners) && owners.length && - (!Env.edPublic || owners.indexOf(Env.edPublic) === -1); - }; - - var _getUserObjects = function (Env) { - var userObjects = [Env.user.userObject]; - var foldersUO = Object.keys(Env.folders).map(function (k) { - return Env.folders[k].userObject; - }); - Array.prototype.push.apply(userObjects, foldersUO); - return userObjects; - }; - - var _getUserObjectFromId = function (Env, id) { - var userObjects = _getUserObjects(Env); - var userObject = Env.user.userObject; - userObjects.some(function (uo) { - if (Object.keys(uo.getFileData(id)).length) { - userObject = uo; - return true; - } - }); - return userObject; - }; - - var _getUserObjectPath = function (Env, uo) { - var fId = Number(uo.id); - if (!fId) { return; } - var fPath = Env.user.userObject.findFile(fId)[0]; - return fPath; - }; - - // Return files data objects associated to a channel for setPadTitle - // All occurences are returned, in drive or shared folders - // If "editable" is true, the data returned is a proxy, otherwise - // it's a cloned object (NOTE: href should never be edited directly) - var findChannel = function (Env, channel, editable) { - var ret = []; - Env.user.userObject.findChannels([channel], true).forEach(function (id) { - // Check in shared folders, then clone if needed - var data = Env.user.proxy[UserObject.SHARED_FOLDERS][id]; - if (data && !editable) { data = JSON.parse(JSON.stringify(data)); } - // If it's not a shared folder, check the pads - if (!data) { data = Env.user.userObject.getFileData(id, editable); } - ret.push({ - id: id, - data: data, - userObject: Env.user.userObject - }); - }); - Object.keys(Env.folders).forEach(function (fId) { - Env.folders[fId].userObject.findChannels([channel]).forEach(function (id) { - ret.push({ - id: id, - fId: fId, - data: Env.folders[fId].userObject.getFileData(id, editable), - userObject: Env.folders[fId].userObject - }); - }); - }); - return ret; - }; - // Return files data objects associated to a given href for setPadAttribute... - // If "editable" is true, the data returned is a proxy, otherwise - // it's a cloned object (NOTE: href should never be edited directly) - var findHref = function (Env, href) { - var ret = []; - var id = Env.user.userObject.getIdFromHref(href); - if (id) { - ret.push({ - data: Env.user.userObject.getFileData(id), - userObject: Env.user.userObject - }); - } - Object.keys(Env.folders).forEach(function (fId) { - var id = Env.folders[fId].userObject.getIdFromHref(href); - if (!id) { return; } - ret.push({ - fId: fId, - data: Env.folders[fId].userObject.getFileData(id), - userObject: Env.folders[fId].userObject - }); - }); - return ret; - }; - // Return paths linked to a file ID - var findFile = function (Env, id) { - var ret = []; - var userObjects = _getUserObjects(Env); - let mainRes = {}; - userObjects.forEach(function (uo) { - var fId = Number(uo.id); - let results; - if (!fId) { - // Main drive: get the paths of all the SF - // in addition to the paths of the requested file - let ids = [id]; - let fIds = userObjects.map(uo => { - return +uo.id; - }).filter(Boolean); - Array.prototype.push.apply(ids, fIds); - mainRes = uo.findFiles(ids); // Store paths of each SF - results = mainRes[id]; // Paths of requested file - } else { - results = uo.findFile(id); - // This is a shared folder, we have to fix the paths in the results - let fPath = (mainRes[fId] || [])[0]; - if (!fPath) { return; } // Can't search into this sf - results.forEach(function (p) { - Array.prototype.unshift.apply(p, fPath); - }); - } - // Push the results from this proxy - Array.prototype.push.apply(ret, results); - }); - return ret; - }; - var findFiles = function (Env, ids) { - var ret = {}; - var userObjects = _getUserObjects(Env); - let mainRes = {}; - userObjects.forEach(function (uo) { - //var fPath = _getUserObjectPath(Env, uo); - var fId = Number(uo.id); - if (!uo.id) { - let fIds = userObjects.map(uo => { - return +uo.id; - }).filter(Boolean); - Array.prototype.push.apply(ids, fIds); - } - var results = uo.findFiles(ids); - if (!uo.id) { mainRes = results; } - //console.error(results); - if (fId) { - // This is a shared folder, we have to fix the paths in the results - let fPath = (mainRes[fId] || [])[0]; - if (!fPath) { return; } // Can't search into this sf - Object.keys(results).forEach(file => { - results[file].forEach(p => { - Array.prototype.unshift.apply(p, fPath); - }); - }); - } - // Push the results from this proxy - Object.keys(results).forEach(file => { - ret[file] ||= []; - Array.prototype.push.apply(ret[file], results[file]); - }); - }); - return ret; - }; - - - // Returns file IDs corresponding to the provided channels - var _findChannels = function (Env, channels, onlyMain) { - if (onlyMain) { - return Env.user.userObject.findChannels(channels); - } - var ret = []; - var userObjects = _getUserObjects(Env); - userObjects.forEach(function (uo) { - var results = uo.findChannels(channels); - Array.prototype.push.apply(ret, results); - }); - ret = Util.deduplicateString(ret); - return ret; - }; - - var _getFileData = function (Env, id, editable) { - var userObjects = _getUserObjects(Env); - var data = {}; - userObjects.some(function (uo) { - data = uo.getFileData(id, editable); - if (data && Object.keys(data).length) { return true; } - }); - return data; - }; - - var getSharedFolderData = function (Env, id) { - var inHistory; - if (Env.isHistoryMode && !Env.folders[id]) { inHistory = true; } - else if (!Env.folders[id]) { return {}; } - var proxy = inHistory? {}: Env.folders[id].proxy; - - // Clean deprecated values - if (Object.keys(proxy.metadata || {}).length > 1) { - proxy.metadata = { title: proxy.metadata.title }; - } - - var obj = Util.clone(proxy.metadata || {}); - - for (var k in Env.user.proxy[UserObject.SHARED_FOLDERS][id] || {}) { - if (typeof(Env.user.proxy[UserObject.SHARED_FOLDERS][id][k]) === "undefined") { // TODO "deleted folder" for restricted shared folders when viewer in a team - continue; - } - var data = Util.clone(Env.user.proxy[UserObject.SHARED_FOLDERS][id][k]); - if (k === "href" && data.indexOf('#') === -1) { - try { - data = Env.user.userObject.cryptor.decrypt(data); - } catch (e) {} - } - if (k === "href" && data.indexOf('#') === -1) { data = undefined; } - obj[k] = data; - } - return obj; - }; - - - // Transform an absolute path into a path relative to the correct shared folder - var _resolvePath = function (Env, path) { - var res = { - id: null, - userObject: Env.user.userObject, - path: path - }; - if (!Array.isArray(path) || path.length <= 1) { - return res; - } - var current; - var uo = Env.user.userObject; - // We don't need to check the last element of the path because we only need to split it - // when the path contains an element inside the shared folder - for (var i=2; i