mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-12 19:49:59 +05:00
Remove duplicated files
This commit is contained in:
parent
b5706da75c
commit
c2a3fbd60c
@ -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',
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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'));
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 XWiki CryptPad Team <contact@cryptpad.org> 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);
|
||||
});
|
||||
@ -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',
|
||||
|
||||
@ -1,245 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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 <base64 string>
|
||||
|
||||
// signature <base64 string>
|
||||
|
||||
// block <base64 string>
|
||||
|
||||
// [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
|
||||
}
|
||||
|
||||
})();
|
||||
@ -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')
|
||||
);
|
||||
@ -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')
|
||||
@ -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
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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")
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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")
|
||||
);
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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'),
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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'),
|
||||
);
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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',
|
||||
|
||||
|
||||
@ -1,908 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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
|
||||
}
|
||||
})();
|
||||
@ -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',
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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
|
||||
}
|
||||
})();
|
||||
|
||||
@ -1,116 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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);
|
||||
});
|
||||
}
|
||||
}());
|
||||
@ -1,79 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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
|
||||
}
|
||||
|
||||
})();
|
||||
@ -1,766 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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 : {}));
|
||||
@ -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',
|
||||
|
||||
@ -1,161 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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
|
||||
}
|
||||
})();
|
||||
@ -1,36 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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
|
||||
}
|
||||
|
||||
})();
|
||||
@ -1,110 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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);
|
||||
}
|
||||
}());
|
||||
@ -1,882 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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<a.length; i++) {
|
||||
for(var j=i+1; j<a.length; j++) {
|
||||
if(a[i] === a[j]) { a.splice(j--, 1); }
|
||||
}
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
/*
|
||||
* Saving files
|
||||
*/
|
||||
Util.fixFileName = function (filename) {
|
||||
return filename.replace(/ /g, '-').replace(/[\/\?]/g, '_')
|
||||
.replace(/_+/g, '_');
|
||||
};
|
||||
|
||||
var oneKilobyte = 1024;
|
||||
var oneMegabyte = 1024 * oneKilobyte;
|
||||
var oneGigabyte = 1024 * oneMegabyte;
|
||||
|
||||
Util.bytesToGigabytes = function (bytes) {
|
||||
return Math.ceil(bytes / oneGigabyte * 100) / 100;
|
||||
};
|
||||
|
||||
Util.bytesToMegabytes = function (bytes) {
|
||||
return Math.ceil(bytes / oneMegabyte * 100) / 100;
|
||||
};
|
||||
|
||||
Util.bytesToKilobytes = function (bytes) {
|
||||
return Math.ceil(bytes / oneKilobyte * 100) / 100;
|
||||
};
|
||||
|
||||
Util.magnitudeOfBytes = function (bytes) {
|
||||
if (bytes >= 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<split.length; i++) {
|
||||
var char = split[i];
|
||||
if (char !== "") {
|
||||
arr.push(char);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
Util.getFirstCharacter = function (str) {
|
||||
if (!str || !str.trim()) { return '?'; }
|
||||
var emojis = emojiStringToArray(str);
|
||||
return isEmoji(emojis[0])? emojis[0]: str[0];
|
||||
};
|
||||
|
||||
Util.getRandomColor = function (light) {
|
||||
var getColor = function () {
|
||||
if (light) {
|
||||
return Math.floor(Math.random() * 156) + 70;
|
||||
}
|
||||
return Math.floor(Math.random() * 200) + 25;
|
||||
};
|
||||
return '#' + getColor().toString(16) +
|
||||
getColor().toString(16) +
|
||||
getColor().toString(16);
|
||||
};
|
||||
|
||||
Util.checkRestrictedApp = function (app, AppConfig, earlyTypes, plan, loggedIn) {
|
||||
// If this is an early access app, make sure this instance allows them
|
||||
if (Array.isArray(earlyTypes) && earlyTypes.includes(app) && !AppConfig.enableEarlyAccess) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
var premiumTypes = AppConfig.premiumTypes;
|
||||
// If this is not a premium app, don't disable it
|
||||
if (!Array.isArray(premiumTypes) || !premiumTypes.includes(app)) { return 2; }
|
||||
// This is a premium app
|
||||
// if you're not logged in, disable it
|
||||
if (!loggedIn) { return -1; }
|
||||
// if you're logged in, enable it only if you're a premium user
|
||||
return plan ? 1 : 0;
|
||||
|
||||
};
|
||||
|
||||
/* Chrome 92 dropped support for SharedArrayBuffer in cross-origin contexts
|
||||
where window.crossOriginIsolated is false.
|
||||
|
||||
Their blog (https://blog.chromium.org/2021/02/restriction-on-sharedarraybuffers.html)
|
||||
isn't clear about why they're doing this, but since it's related to site-isolation
|
||||
it seems they're trying to do vague security things.
|
||||
|
||||
In any case, there seems to be a workaround where you can still create them
|
||||
by using `new WebAssembly.Memory({shared: true, ...})` instead of `new SharedArrayBuffer`.
|
||||
|
||||
This seems unreliable, but it's better than not being able to export, since
|
||||
we actively rely on postMessage between iframes and therefore can't afford
|
||||
to opt for full isolation.
|
||||
*/
|
||||
var supportsSharedArrayBuffers = function () {
|
||||
try {
|
||||
return Object.prototype.toString.call(new window.WebAssembly.Memory({shared: true, initial: 0, maximum: 0}).buffer) === '[object SharedArrayBuffer]';
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
Util.supportsWasm = function () {
|
||||
return !(typeof(Atomics) === "undefined" || !supportsSharedArrayBuffers() || typeof(WebAssembly) === 'undefined');
|
||||
};
|
||||
|
||||
//Returns an array of integers in range 0 to (length-1)
|
||||
Util.getKeysArray = function (length) {
|
||||
return [...Array(length).keys()];
|
||||
};
|
||||
|
||||
Util.getVersionFromUrlArgs = urlArgs => {
|
||||
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));
|
||||
@ -1,238 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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
|
||||
}
|
||||
})();
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -1,201 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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
|
||||
}
|
||||
|
||||
})();
|
||||
@ -1,19 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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);
|
||||
}
|
||||
})();
|
||||
@ -1,211 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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
|
||||
}
|
||||
|
||||
})();
|
||||
@ -1,130 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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
|
||||
}
|
||||
|
||||
})();
|
||||
@ -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',
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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
|
||||
}
|
||||
|
||||
})();
|
||||
@ -1,964 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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"); }
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
{
|
||||
<ephemeralCurveKey>: {
|
||||
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
|
||||
}
|
||||
}());
|
||||
@ -1,417 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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
|
||||
}
|
||||
|
||||
})();
|
||||
@ -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 = {};
|
||||
|
||||
@ -1,68 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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
|
||||
}
|
||||
|
||||
})();
|
||||
@ -1,235 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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); });
|
||||
}
|
||||
}());
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,422 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
(function () {
|
||||
var factory = function (Util, Nacl) {
|
||||
// we will send messages with a unique id for each RPC
|
||||
// that id is returned with each response, indicating which call it was in response to
|
||||
var uid = Util.uid;
|
||||
|
||||
// safely parse json messages, because they might cause parse errors
|
||||
var tryParse = Util.tryParse;
|
||||
|
||||
// we will sign various message with our edPrivate keys
|
||||
// this handles that in a generic way
|
||||
var signMsg = function (data, signKey) {
|
||||
var buffer = Util.decodeUTF8(JSON.stringify(data));
|
||||
return Util.encodeBase64(Nacl.sign.detached(buffer, signKey));
|
||||
};
|
||||
|
||||
// sendMsg takes a pre-formed message, does a little validation
|
||||
// adds a transaction id to the message and stores its callback
|
||||
// and finally sends it off to the historyKeeper, which delegates its
|
||||
// processing to the RPC submodule
|
||||
var sendMsg = function (ctx, data, cb) {
|
||||
if (typeof(cb) !== 'function') { throw new Error('expected callback'); }
|
||||
|
||||
var network = ctx.network;
|
||||
var hkn = network.historyKeeper;
|
||||
if (typeof(hkn) !== 'string') { return void cb("NO_HISTORY_KEEPER"); }
|
||||
|
||||
var txid = uid();
|
||||
|
||||
var pending = ctx.pending[txid] = function (err, response) {
|
||||
cb(err, response);
|
||||
};
|
||||
pending.data = data;
|
||||
pending.called = 0;
|
||||
|
||||
return network.sendto(hkn, JSON.stringify([txid, data]));
|
||||
};
|
||||
|
||||
var matchesAnon = function (ctx, txid) {
|
||||
if (!ctx.anon) { return false; }
|
||||
if (typeof(ctx.anon.pending[txid]) !== 'function') { return false; }
|
||||
return true;
|
||||
};
|
||||
|
||||
var handleAnon = function (ctx /* anon_ctx */, txid, body /* parsed messages without txid */) {
|
||||
// if anon is handling it we know there's a pending callback
|
||||
var pending = ctx.pending[txid];
|
||||
if (body[0] === 'ERROR') { pending(body[1]); }
|
||||
else { pending(void 0, body.slice(1)); }
|
||||
delete ctx.pending[txid];
|
||||
};
|
||||
|
||||
var onMsg = function (ctx /* network context */, msg /* string message */) {
|
||||
if (typeof(msg) !== 'string') {
|
||||
console.error("received non-string message [%s]", msg);
|
||||
}
|
||||
|
||||
var parsed = tryParse(msg);
|
||||
if (!parsed) {
|
||||
return void console.error(new Error('could not parse message: %s', msg));
|
||||
}
|
||||
|
||||
// RPC messages are always arrays.
|
||||
if (!Array.isArray(parsed)) { return; }
|
||||
// ignore FULL_HISTORY messages
|
||||
if (/(FULL_HISTORY|HISTORY_RANGE)/.test(parsed[0])) { return; }
|
||||
|
||||
var txid = parsed[0];
|
||||
|
||||
// txid must be a string, or this message is not meant for us
|
||||
if (typeof(txid) !== 'string') { return; }
|
||||
|
||||
if (matchesAnon(ctx, txid)) {
|
||||
return void handleAnon(ctx.anon, txid, parsed.slice(1));
|
||||
}
|
||||
|
||||
// iterate over authenticated rpc contexts and check if they are expecting
|
||||
// a message with this txid
|
||||
if (ctx.authenticated.some(function (rpc_ctx) {
|
||||
var pending = rpc_ctx.pending[txid];
|
||||
// not meant for you
|
||||
if (typeof(pending) !== 'function') { return false; }
|
||||
|
||||
// if you're here, the message is for you...
|
||||
|
||||
if (parsed[1] !== 'ERROR') {
|
||||
// if the server sent you a new cookie, replace the old one
|
||||
if (/\|/.test(parsed[1]) && rpc_ctx.cookie !== parsed[1]) {
|
||||
rpc_ctx.cookie = parsed[1];
|
||||
}
|
||||
pending(void 0, parsed.slice(2));
|
||||
|
||||
// if successful, delete the callback...
|
||||
delete rpc_ctx.pending[txid];
|
||||
// prevent further iteration
|
||||
return true;
|
||||
}
|
||||
|
||||
// NO_COOKIE errors mean you failed to authenticate.
|
||||
// request a new cookie and resend the query
|
||||
if (parsed[2] === 'NO_COOKIE') {
|
||||
rpc_ctx.send('COOKIE', "", function (e) {
|
||||
if (e) {
|
||||
console.error(e);
|
||||
return void pending(e);
|
||||
}
|
||||
|
||||
// resend the same command again
|
||||
// give up if you've already tried resending
|
||||
if (rpc_ctx.resend(txid)) { delete rpc_ctx.pending[txid]; }
|
||||
});
|
||||
// prevent further iteration
|
||||
return true;
|
||||
}
|
||||
|
||||
// if you're here then your RPC passed authentication but had some other error
|
||||
// call back with the error message
|
||||
pending(parsed[2]);
|
||||
// and delete the pending callback
|
||||
delete rpc_ctx.pending[txid];
|
||||
|
||||
// prevent further iteration
|
||||
return true;
|
||||
})) {
|
||||
// the message was handled, so stop here
|
||||
return;
|
||||
}
|
||||
|
||||
console.error("UNHANDLED RPC MESSAGE", msg);
|
||||
};
|
||||
|
||||
var networks = [];
|
||||
var contexts = [];
|
||||
|
||||
var initNetworkContext = function (network) {
|
||||
var ctx = {
|
||||
network: network,
|
||||
connected: true,
|
||||
anon: undefined,
|
||||
authenticated: [],
|
||||
};
|
||||
networks.push(network);
|
||||
contexts.push(ctx);
|
||||
|
||||
// add listeners...
|
||||
network.on('message', function (msg, sender) {
|
||||
if (sender !== network.historyKeeper) { return; }
|
||||
onMsg(ctx, msg);
|
||||
});
|
||||
|
||||
network.on('disconnect', function () {
|
||||
ctx.connected = false;
|
||||
if (ctx.anon) { ctx.anon.connected = false; }
|
||||
ctx.authenticated.forEach(function (ctx) {
|
||||
ctx.connected = false;
|
||||
});
|
||||
});
|
||||
|
||||
network.on('reconnect', function () {
|
||||
if (ctx.anon) { ctx.anon.connected = true; }
|
||||
ctx.authenticated.forEach(function (ctx) {
|
||||
ctx.connected = true;
|
||||
});
|
||||
});
|
||||
return ctx;
|
||||
};
|
||||
|
||||
var getNetworkContext = function (network) {
|
||||
var i;
|
||||
networks.some(function (current, j) {
|
||||
if (network !== current) { return false; }
|
||||
i = j;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (contexts[i]) { return contexts[i]; }
|
||||
return initNetworkContext(network);
|
||||
};
|
||||
|
||||
var initAuthenticatedRpc = function (networkContext, keys) {
|
||||
var ctx = {
|
||||
network: networkContext.network,
|
||||
publicKey: keys.publicKeyString,
|
||||
timeouts: {},
|
||||
pending: {},
|
||||
cookie: null,
|
||||
connected: true,
|
||||
};
|
||||
|
||||
var send = ctx.send = function (type, msg, _cb) {
|
||||
var cb = Util.mkAsync(_cb);
|
||||
|
||||
if (!ctx.connected && type !== 'COOKIE') {
|
||||
return void cb("DISCONNECTED");
|
||||
}
|
||||
|
||||
// construct a signed message...
|
||||
|
||||
var data = [type, msg];
|
||||
if (ctx.cookie && ctx.cookie.join) {
|
||||
data.unshift(ctx.cookie.join('|'));
|
||||
} else {
|
||||
data.unshift(ctx.cookie);
|
||||
}
|
||||
|
||||
var sig = signMsg(data, keys.signKey);
|
||||
|
||||
data.unshift(keys.publicKeyString);
|
||||
data.unshift(sig);
|
||||
|
||||
// [sig, edPublicKey, cookie, type, msg]
|
||||
return sendMsg(ctx, data, cb);
|
||||
};
|
||||
|
||||
ctx.resend = function (txid) {
|
||||
var pending = ctx.pending[txid];
|
||||
if (pending.called) {
|
||||
console.error("[%s] called too many times", txid);
|
||||
return true;
|
||||
}
|
||||
pending.called++;
|
||||
|
||||
// update the cookie and signature...
|
||||
pending.data[2] = ctx.cookie;
|
||||
pending.data[0] = signMsg(pending.data.slice(2), keys.signKey);
|
||||
|
||||
// store the callback with a new txid
|
||||
var new_txid = uid();
|
||||
ctx.pending[new_txid] = pending;
|
||||
// and delete the old one
|
||||
delete ctx.pending[txid];
|
||||
|
||||
try {
|
||||
return ctx.network.sendto(ctx.network.historyKeeper,
|
||||
JSON.stringify([new_txid, pending.data]));
|
||||
} catch (e) {
|
||||
console.log("failed to resend");
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
send.unauthenticated = function (type, msg, _cb) {
|
||||
var cb = Util.mkAsync(_cb);
|
||||
if (!ctx.connected) { return void cb('DISCONNECTED'); }
|
||||
|
||||
// construct an unsigned message
|
||||
var data = [null, keys.publicKeyString, null, type, msg];
|
||||
if (ctx.cookie && ctx.cookie.join) {
|
||||
data[2] = ctx.cookie.join('|');
|
||||
} else {
|
||||
data[2] = ctx.cookie;
|
||||
}
|
||||
|
||||
return sendMsg(ctx, data, cb);
|
||||
};
|
||||
|
||||
ctx.destroy = function () {
|
||||
// clear all pending timeouts
|
||||
Object.keys(ctx.timeouts).forEach(function (to) {
|
||||
clearTimeout(to);
|
||||
});
|
||||
|
||||
// remove the ctx from the network's stack
|
||||
var idx = networkContext.authenticated.indexOf(ctx);
|
||||
if (idx === -1) { return; }
|
||||
networkContext.authenticated.splice(idx, 1);
|
||||
};
|
||||
|
||||
networkContext.authenticated.push(ctx);
|
||||
return ctx;
|
||||
};
|
||||
|
||||
var getAuthenticatedContext = function (networkContext, keys) {
|
||||
if (!networkContext) { throw new Error('expected network context'); }
|
||||
|
||||
var publicKey = keys.publicKeyString;
|
||||
|
||||
var i;
|
||||
networkContext.authenticated.some(function (ctx, j) {
|
||||
if (ctx.publicKey !== publicKey) { return false; }
|
||||
i = j;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (networkContext.authenticated[i]) { return networkContext.authenticated[i]; }
|
||||
|
||||
return initAuthenticatedRpc(networkContext, keys);
|
||||
};
|
||||
|
||||
var create = function (network, edPrivateKey, edPublicKey, _cb) {
|
||||
if (typeof(_cb) !== 'function') { throw new Error("expected callback"); }
|
||||
|
||||
var cb = Util.mkAsync(_cb);
|
||||
|
||||
var signKey;
|
||||
|
||||
try {
|
||||
signKey = Util.decodeBase64(edPrivateKey);
|
||||
if (signKey.length !== 64) {
|
||||
throw new Error('private key did not match expected length of 64');
|
||||
}
|
||||
} catch (err) {
|
||||
return void cb(err);
|
||||
}
|
||||
|
||||
try {
|
||||
if (Util.decodeBase64(edPublicKey).length !== 32) {
|
||||
return void cb('expected public key to be 32 uint');
|
||||
}
|
||||
} catch (err) { return void cb(err); }
|
||||
|
||||
if (!network) { return void cb('NO_NETWORK'); }
|
||||
|
||||
// get or create a context for the provided network
|
||||
var net_ctx = getNetworkContext(network);
|
||||
|
||||
var rpc_ctx = getAuthenticatedContext(net_ctx, {
|
||||
publicKeyString: edPublicKey,
|
||||
signKey: signKey,
|
||||
});
|
||||
|
||||
rpc_ctx.send('COOKIE', "", function (e) {
|
||||
if (e) { return void cb(e); }
|
||||
// callback to provide 'send' method to whatever needs it
|
||||
cb(void 0, {
|
||||
send: rpc_ctx.send,
|
||||
destroy: rpc_ctx.destroy,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var initAnonRpc = function (networkContext) {
|
||||
var ctx = {
|
||||
network: networkContext.network,
|
||||
timeouts: {},
|
||||
pending: {},
|
||||
connected: true,
|
||||
};
|
||||
|
||||
// any particular network will only ever need one anonymous rpc
|
||||
networkContext.anon = ctx;
|
||||
|
||||
ctx.send = function (type, msg, _cb) {
|
||||
var cb = Util.mkAsync(_cb);
|
||||
if (!ctx.connected) { return void cb('DISCONNECTED'); }
|
||||
|
||||
// construct an unsigned message...
|
||||
var data = [type, msg];
|
||||
|
||||
// [type, msg]
|
||||
return sendMsg(ctx, data, cb);
|
||||
};
|
||||
|
||||
ctx.resend = function (txid) {
|
||||
var pending = ctx.pending[txid];
|
||||
if (pending.called) {
|
||||
console.error("[%s] called too many times", txid);
|
||||
return true;
|
||||
}
|
||||
pending.called++;
|
||||
|
||||
try {
|
||||
return ctx.network.sendto(ctx.network.historyKeeper,
|
||||
JSON.stringify([txid, pending.data]));
|
||||
} catch (e) {
|
||||
console.log("failed to resend");
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
ctx.destroy = function () {
|
||||
// clear all pending timeouts
|
||||
Object.keys(ctx.timeouts).forEach(function (to) {
|
||||
clearTimeout(to);
|
||||
});
|
||||
|
||||
networkContext.anon = undefined;
|
||||
};
|
||||
|
||||
return ctx;
|
||||
};
|
||||
|
||||
var getAnonContext = function (networkContext) {
|
||||
return networkContext.anon || initAnonRpc(networkContext);
|
||||
};
|
||||
|
||||
var createAnonymous = function (network, _cb) {
|
||||
// enforce asynchrony
|
||||
var cb = Util.mkAsync(_cb);
|
||||
|
||||
if (typeof(cb) !== 'function') { throw new Error("expected callback"); }
|
||||
if (!network) { return void cb('NO_NETWORK'); }
|
||||
|
||||
// get or create a context for the provided network
|
||||
var ctx = getAnonContext(getNetworkContext(network));
|
||||
|
||||
cb(void 0, {
|
||||
send: ctx.send,
|
||||
destroy: ctx.destroy,
|
||||
});
|
||||
};
|
||||
|
||||
return { create: create, createAnonymous: createAnonymous };
|
||||
};
|
||||
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = factory(require("./common-util"), require("tweetnacl/nacl-fast"));
|
||||
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
|
||||
define([
|
||||
'/common/common-util.js',
|
||||
'/components/tweetnacl/nacl-fast.min.js',
|
||||
], function (Util) {
|
||||
return factory(Util, window.nacl);
|
||||
});
|
||||
} else {
|
||||
// I'm not gonna bother supporting any other kind of instanciation
|
||||
}
|
||||
}());
|
||||
@ -138,7 +138,7 @@ define([
|
||||
'/common/cryptpad-common.js',
|
||||
'/components/chainpad-crypto/crypto.js',
|
||||
'/common/cryptget.js',
|
||||
'/common/outer/worker-channel.js',
|
||||
'/common/events-channel.js',
|
||||
'/secureiframe/main.js',
|
||||
'/unsafeiframe/main.js',
|
||||
'/common/onlyoffice/ooiframe.js',
|
||||
@ -151,7 +151,7 @@ define([
|
||||
'/common/common-feedback.js',
|
||||
'/common/outer/local-store.js',
|
||||
'/common/outer/login-block.js',
|
||||
'/common/outer/cache-store.js',
|
||||
'/common/cache-store.js',
|
||||
'/customize/application_config.js',
|
||||
//'/common/test.js',
|
||||
'/common/user-object.js',
|
||||
|
||||
@ -8,7 +8,7 @@ define([
|
||||
'/components/nthen/index.js',
|
||||
'/customize/messages.js',
|
||||
'/common/sframe-chainpad-netflux-inner.js',
|
||||
'/common/outer/worker-channel.js',
|
||||
'/common/events-channel.js',
|
||||
'/common/sframe-common-title.js',
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/sframe-common-history.js',
|
||||
|
||||
@ -1,999 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
(() => {
|
||||
const factory = (Util, Hash, Realtime) => {
|
||||
let window = globalThis;
|
||||
var module = {};
|
||||
|
||||
module.setCustomize = (/*data*/) => {
|
||||
};
|
||||
|
||||
var clone = function (o) {
|
||||
try { return JSON.parse(JSON.stringify(o)); }
|
||||
catch (e) { return undefined; }
|
||||
};
|
||||
|
||||
module.init = function (config, exp, files) {
|
||||
var loggedIn = config.loggedIn;
|
||||
var sharedFolder = config.sharedFolder;
|
||||
|
||||
var readOnly = config.readOnly;
|
||||
|
||||
var Messages = config.Messages || {};
|
||||
var ROOT = exp.ROOT;
|
||||
var FILES_DATA = exp.FILES_DATA;
|
||||
var STATIC_DATA = exp.STATIC_DATA;
|
||||
var OLD_FILES_DATA = exp.OLD_FILES_DATA;
|
||||
var UNSORTED = exp.UNSORTED;
|
||||
var TRASH = exp.TRASH;
|
||||
var TEMPLATE = exp.TEMPLATE;
|
||||
var SHARED_FOLDERS = exp.SHARED_FOLDERS;
|
||||
var SHARED_FOLDERS_TEMP = exp.SHARED_FOLDERS_TEMP;
|
||||
|
||||
var debug = exp.debug;
|
||||
|
||||
exp._setReadOnly = function (state) {
|
||||
readOnly = state;
|
||||
if (!readOnly) { exp.fixFiles(); }
|
||||
};
|
||||
|
||||
exp.setHref = function (channel, id, href) {
|
||||
if (!id && !channel) { return; }
|
||||
if (readOnly) { return; }
|
||||
var ids = id ? [id] : exp.findChannels([channel]);
|
||||
ids.forEach(function (i) {
|
||||
var data = exp.getFileData(i, true);
|
||||
var oldHref = exp.getHref(data);
|
||||
if (oldHref === href) { return; }
|
||||
data.href = exp.cryptor.encrypt(href);
|
||||
});
|
||||
};
|
||||
|
||||
exp.setPadAttribute = function (href, attr, value, cb) {
|
||||
cb = cb || function () {};
|
||||
if (readOnly) { return void cb('EFORBIDDEN'); }
|
||||
var id = exp.getIdFromHref(href);
|
||||
if (!id) { return void cb("E_INVAL_HREF"); }
|
||||
if (!attr || !attr.trim()) { return void cb("E_INVAL_ATTR"); }
|
||||
var data = exp.getFileData(id, true);
|
||||
if (attr === "href") {
|
||||
exp.setHref(null, id, value);
|
||||
} else {
|
||||
data[attr] = clone(value);
|
||||
}
|
||||
cb(null);
|
||||
};
|
||||
exp.getPadAttribute = function (href, attr, cb) {
|
||||
cb = cb || function () {};
|
||||
var id = exp.getIdFromHref(href);
|
||||
if (!id) { return void cb(null, undefined); }
|
||||
var data = exp.getFileData(id);
|
||||
cb(null, clone(data[attr]));
|
||||
};
|
||||
|
||||
exp.pushData = function (_data, cb) {
|
||||
if (typeof cb !== "function") { cb = function () {}; }
|
||||
if (readOnly) { return void cb('EFORBIDDEN'); }
|
||||
var id = Util.createRandomInteger();
|
||||
var data = clone(_data);
|
||||
// If we were given an edit link, encrypt its value if needed
|
||||
if (data.href && data.href.indexOf('#') !== -1) { data.href = exp.cryptor.encrypt(data.href); }
|
||||
files[FILES_DATA][id] = data;
|
||||
cb(null, id);
|
||||
};
|
||||
exp.pushLink = function (_data, cb) {
|
||||
if (typeof cb !== "function") { cb = function () {}; }
|
||||
if (readOnly) { return void cb('EFORBIDDEN'); }
|
||||
var id = Util.createRandomInteger();
|
||||
var data = clone(_data);
|
||||
files[STATIC_DATA][id] = data;
|
||||
cb(null, id);
|
||||
};
|
||||
|
||||
exp.pushSharedFolder = function (_data, cb) {
|
||||
if (typeof cb !== "function") { cb = function () {}; }
|
||||
if (readOnly) { return void cb('EFORBIDDEN'); }
|
||||
var data = clone(_data);
|
||||
|
||||
// Check if we already have this shared folder in our drive
|
||||
var exists;
|
||||
if (Object.keys(files[SHARED_FOLDERS]).some(function (k) {
|
||||
if (files[SHARED_FOLDERS][k].channel === data.channel) {
|
||||
// We already know this shared folder. Check if we can get better access rights
|
||||
if (data.href && !files[SHARED_FOLDERS][k].href) {
|
||||
files[SHARED_FOLDERS][k].href = data.href;
|
||||
}
|
||||
exists = k;
|
||||
return true;
|
||||
}
|
||||
})) {
|
||||
return void cb ('EEXISTS', exists);
|
||||
}
|
||||
|
||||
// Add the folder
|
||||
if (!loggedIn || config.testMode) {
|
||||
return void cb("EAUTH");
|
||||
}
|
||||
var id = Util.createRandomInteger();
|
||||
if (data.href && data.href.indexOf('#') !== -1) { data.href = exp.cryptor.encrypt(data.href); }
|
||||
files[SHARED_FOLDERS][id] = data;
|
||||
cb(null, id);
|
||||
};
|
||||
|
||||
exp.deprecateSharedFolder = function (id, reason) {
|
||||
if (readOnly) { return; }
|
||||
var data = files[SHARED_FOLDERS][id];
|
||||
if (!data) { return; }
|
||||
var ro = !data.href || exp.cryptor.decrypt(data.href).indexOf('#') === -1;
|
||||
if (!ro) {
|
||||
var obj = files[SHARED_FOLDERS_TEMP][id] = JSON.parse(JSON.stringify(data));
|
||||
obj.legacy = reason !== "PASSWORD_CHANGE";
|
||||
}
|
||||
var paths = exp.findFile(Number(id));
|
||||
exp.delete(paths, null, true);
|
||||
delete files[SHARED_FOLDERS][id];
|
||||
};
|
||||
|
||||
// FILES DATA
|
||||
var spliceFileData = function (id) {
|
||||
if (readOnly) { return; }
|
||||
delete files[FILES_DATA][id];
|
||||
};
|
||||
|
||||
// Find files in FILES_DATA that are not anymore in the drive, and remove them from
|
||||
// FILES_DATA.
|
||||
exp.checkDeletedFiles = function (cb) {
|
||||
if (!loggedIn && !config.testMode) { return void cb(); }
|
||||
if (readOnly) { return void cb('EFORBIDDEN'); }
|
||||
|
||||
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
|
||||
var toClean = [];
|
||||
exp.getFiles([FILES_DATA, SHARED_FOLDERS, STATIC_DATA]).forEach(function (id) {
|
||||
if (filesList.indexOf(id) === -1) {
|
||||
var fd = exp.isSharedFolder(id) ? files[SHARED_FOLDERS][id] : exp.getFileData(id);
|
||||
var channelId = fd.channel;
|
||||
if (fd.lastVersion) { toClean.push(Hash.hrefToHexChannelId(fd.lastVersion)); }
|
||||
if (fd.rtChannel) { toClean.push(fd.rtChannel); }
|
||||
if (channelId) { toClean.push(channelId); }
|
||||
if (exp.isSharedFolder(id)) {
|
||||
delete files[SHARED_FOLDERS][id];
|
||||
if (config.removeProxy) { config.removeProxy(id); }
|
||||
} else if (files[STATIC_DATA][id]) {
|
||||
delete files[STATIC_DATA][id];
|
||||
} else {
|
||||
spliceFileData(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!toClean.length) { return void cb(); }
|
||||
cb(null, toClean);
|
||||
};
|
||||
var deleteHrefs = function (ids) {
|
||||
if (readOnly) { return; }
|
||||
ids.forEach(function (obj) {
|
||||
var idx = files[obj.root].indexOf(obj.id);
|
||||
files[obj.root].splice(idx, 1);
|
||||
});
|
||||
};
|
||||
var deleteMultipleTrashRoot = function (roots) {
|
||||
if (readOnly) { return; }
|
||||
roots.forEach(function (obj) {
|
||||
var idx = files[TRASH][obj.name].indexOf(obj.el);
|
||||
files[TRASH][obj.name].splice(idx, 1);
|
||||
});
|
||||
};
|
||||
exp.deleteMultiplePermanently = function (paths, nocheck, cb) {
|
||||
if (readOnly) { return void cb('EFORBIDDEN'); }
|
||||
|
||||
var allFilesPaths = paths.filter(function(x) { return exp.isPathIn(x, [FILES_DATA]); });
|
||||
|
||||
if (!loggedIn && !config.testMode) {
|
||||
allFilesPaths.forEach(function (path) {
|
||||
var id = path[1];
|
||||
if (!id) { return; }
|
||||
spliceFileData(id);
|
||||
});
|
||||
return void cb();
|
||||
}
|
||||
|
||||
var hrefPaths = paths.filter(function(x) { return exp.isPathIn(x, ['hrefArray']); });
|
||||
var rootPaths = paths.filter(function(x) { return exp.isPathIn(x, [ROOT]); });
|
||||
var trashPaths = paths.filter(function(x) { return exp.isPathIn(x, [TRASH]); });
|
||||
|
||||
var ids = [];
|
||||
hrefPaths.forEach(function (path) {
|
||||
var id = exp.find(path);
|
||||
ids.push({
|
||||
root: path[0],
|
||||
id: id
|
||||
});
|
||||
});
|
||||
deleteHrefs(ids);
|
||||
|
||||
rootPaths.forEach(function (path) {
|
||||
var parentPath = path.slice();
|
||||
var key = parentPath.pop();
|
||||
var parentEl = exp.find(parentPath);
|
||||
delete parentEl[key];
|
||||
});
|
||||
|
||||
var trashRoot = [];
|
||||
trashPaths.forEach(function (path) {
|
||||
var parentPath = path.slice();
|
||||
var key = parentPath.pop();
|
||||
var parentEl = exp.find(parentPath);
|
||||
// Trash root: we have array here, we can't just splice with the path otherwise we might break the path
|
||||
// of another element in the loop
|
||||
if (path.length === 4) {
|
||||
trashRoot.push({
|
||||
name: path[1],
|
||||
el: parentEl
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Trash but not root: it's just a tree so remove the key
|
||||
delete parentEl[key];
|
||||
});
|
||||
deleteMultipleTrashRoot(trashRoot);
|
||||
|
||||
// In some cases, we want to remove pads from a location without removing them from
|
||||
// FILES_DATA (replaceHref)
|
||||
if (!nocheck) { exp.checkDeletedFiles(cb); }
|
||||
else { cb(); }
|
||||
};
|
||||
|
||||
// Move
|
||||
|
||||
// From another drive
|
||||
exp.copyFromOtherDrive = function (path, element, data, key) {
|
||||
if (readOnly) { return; }
|
||||
// Copy files data
|
||||
// We have to remove pads that are already in the current proxy to make sure
|
||||
// we won't create duplicates
|
||||
|
||||
var toRemove = [];
|
||||
Object.keys(data).forEach(function (id) {
|
||||
id = Number(id);
|
||||
// Find and maybe update existing pads with the same channel id
|
||||
var d = data[id];
|
||||
// If we were given a static link, copy to STATIC_DATA
|
||||
if (d.static) {
|
||||
delete d.static;
|
||||
files[STATIC_DATA][id] = d;
|
||||
return;
|
||||
}
|
||||
// If we were given an edit link, encrypt its value if needed
|
||||
if (d.href) { d.href = exp.cryptor.encrypt(d.href); }
|
||||
var found = false;
|
||||
for (var i in files[FILES_DATA]) {
|
||||
if (files[FILES_DATA][i].channel === d.channel) {
|
||||
// Update href?
|
||||
if (!files[FILES_DATA][i].href) {
|
||||
files[FILES_DATA][i].href = d.href;
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
toRemove.push(id);
|
||||
return;
|
||||
}
|
||||
files[FILES_DATA][id] = d;
|
||||
});
|
||||
|
||||
// Remove existing pads from the "element" variable
|
||||
if (exp.isFile(element) && toRemove.indexOf(element) !== -1) {
|
||||
exp.log(Messages.sharedFolders_duplicate);
|
||||
return;
|
||||
} else if (exp.isFolder(element)) {
|
||||
var _removeExisting = function (root) {
|
||||
for (var k in root) {
|
||||
if (exp.isFile(root[k])) {
|
||||
if (toRemove.indexOf(root[k]) !== -1) {
|
||||
exp.log(Messages.sharedFolders_duplicate);
|
||||
delete root[k];
|
||||
}
|
||||
} else if (exp.isFolder(root[k])) {
|
||||
_removeExisting(root[k]);
|
||||
}
|
||||
}
|
||||
};
|
||||
_removeExisting(element);
|
||||
}
|
||||
|
||||
|
||||
// Copy file or folder
|
||||
var newParent = exp.find(path);
|
||||
var tempName = exp.isFile(element) ? Hash.createChannelId() : key;
|
||||
var newName = exp.getAvailableName(newParent, tempName);
|
||||
if (Array.isArray(newParent)) {
|
||||
newParent.push(element);
|
||||
return;
|
||||
}
|
||||
newParent[newName] = element;
|
||||
};
|
||||
|
||||
// From the same drive
|
||||
var pushToTrash = function (name, element, path) {
|
||||
if (readOnly) { return; }
|
||||
|
||||
var trash = files[TRASH];
|
||||
if (typeof(trash[name]) === "undefined") { trash[name] = []; }
|
||||
var trashArray = trash[name];
|
||||
var trashElement = {
|
||||
element: element,
|
||||
path: path
|
||||
};
|
||||
trashArray.push(trashElement);
|
||||
};
|
||||
exp.copyElement = function (elementPath, newParentPath) {
|
||||
if (readOnly) { return; }
|
||||
if (exp.comparePath(elementPath, newParentPath)) { return; } // Nothing to do...
|
||||
var element = exp.find(elementPath);
|
||||
var newParent = exp.find(newParentPath);
|
||||
|
||||
// Move to Trash
|
||||
if (exp.isPathIn(newParentPath, [TRASH])) {
|
||||
if (!elementPath || elementPath.length < 2 || elementPath[0] === TRASH) {
|
||||
debug("Can't move an element from the trash to the trash: ", elementPath);
|
||||
return;
|
||||
}
|
||||
var key = elementPath[elementPath.length - 1];
|
||||
var elName = exp.isPathIn(elementPath, ['hrefArray']) ? exp.getTitle(element) : key;
|
||||
var parentPath = elementPath.slice();
|
||||
parentPath.pop();
|
||||
pushToTrash(elName, element, parentPath);
|
||||
return true;
|
||||
}
|
||||
// Move to hrefArray
|
||||
if (exp.isPathIn(newParentPath, ['hrefArray'])) {
|
||||
if (exp.isFolder(element)) {
|
||||
exp.log(Messages.fo_moveUnsortedError);
|
||||
return;
|
||||
} else {
|
||||
if (elementPath[0] === newParentPath[0]) { return; }
|
||||
var fileRoot = newParentPath[0];
|
||||
if (files[fileRoot].indexOf(element) === -1) {
|
||||
files[fileRoot].push(element);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Move to root
|
||||
var newName = exp.isFile(element) ?
|
||||
exp.getAvailableName(newParent, Hash.createChannelId()) :
|
||||
exp.isInTrashRoot(elementPath) ?
|
||||
elementPath[1] : elementPath.pop();
|
||||
|
||||
if (typeof(newParent[newName]) !== "undefined") {
|
||||
exp.log(Messages.fo_unavailableName);
|
||||
return;
|
||||
}
|
||||
newParent[newName] = element;
|
||||
return true;
|
||||
};
|
||||
|
||||
// FORGET (move with href not path)
|
||||
exp.forget = function (href) {
|
||||
if (readOnly) { return; }
|
||||
|
||||
var id = exp.getIdFromHref(href);
|
||||
if (!id) { return; }
|
||||
if (!loggedIn && !config.testMode) {
|
||||
// delete permanently
|
||||
spliceFileData(id);
|
||||
return true;
|
||||
}
|
||||
var paths = exp.findFile(id);
|
||||
exp.move(paths, [TRASH]);
|
||||
return true;
|
||||
};
|
||||
|
||||
// REPLACE
|
||||
// If all the occurences of an href are in the trash, remove them and add the file in root.
|
||||
// This is use with setPadTitle when we open a stronger version of a deleted pad
|
||||
exp.restoreHref = function (href) {
|
||||
if (readOnly) { return; }
|
||||
|
||||
var idO = exp.getIdFromHref(href);
|
||||
|
||||
if (!idO || !exp.isFile(idO)) { return; }
|
||||
|
||||
var paths = exp.findFile(idO);
|
||||
|
||||
// Remove all the occurences in the trash
|
||||
// If all the occurences are in the trash or no occurence, add the pad to root
|
||||
var allInTrash = true;
|
||||
paths.forEach(function (p) {
|
||||
if (p[0] === TRASH) {
|
||||
exp.delete(p, null, true); // 3rd parameter means skip "checkDeletedFiles"
|
||||
return;
|
||||
}
|
||||
allInTrash = false;
|
||||
});
|
||||
if (allInTrash) {
|
||||
exp.add(idO);
|
||||
}
|
||||
};
|
||||
|
||||
exp.add = function (id, path) {
|
||||
if (readOnly) { return; }
|
||||
|
||||
if (!loggedIn && !config.testMode) { return; }
|
||||
id = Number(id);
|
||||
var data = files[FILES_DATA][id] || files[STATIC_DATA][id] || files[SHARED_FOLDERS][id];
|
||||
if (!data || typeof(data) !== "object") { return; }
|
||||
var newPath = path, parentEl;
|
||||
if (path && !Array.isArray(path)) {
|
||||
newPath = decodeURIComponent(path).split(',');
|
||||
}
|
||||
// Add to href array
|
||||
if (path && exp.isPathIn(newPath, ['hrefArray'])) {
|
||||
parentEl = exp.find(newPath);
|
||||
parentEl.push(id);
|
||||
return;
|
||||
}
|
||||
// Add to root if no path
|
||||
var filesList = exp.getFiles([ROOT, TRASH, 'hrefArray']);
|
||||
if (filesList.indexOf(id) === -1 && !newPath) {
|
||||
newPath = [ROOT];
|
||||
}
|
||||
// Add to root
|
||||
if (path && exp.isPathIn(newPath, [ROOT])) {
|
||||
parentEl = exp.find(newPath);
|
||||
if (parentEl) {
|
||||
var newName = exp.getAvailableName(parentEl, Hash.createChannelId());
|
||||
parentEl[newName] = id;
|
||||
return;
|
||||
} else {
|
||||
parentEl = exp.find([ROOT]);
|
||||
newPath.slice(1).forEach(function (folderName) {
|
||||
parentEl = parentEl[folderName] = parentEl[folderName] || {};
|
||||
});
|
||||
parentEl[Hash.createChannelId()] = id;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exp.setFolderData = function (path, key, value, cb) {
|
||||
if (readOnly) { return; }
|
||||
|
||||
var folder = exp.find(path);
|
||||
if (!exp.isFolder(folder) || exp.isSharedFolder(folder)) { return; }
|
||||
if (!exp.hasFolderData(folder)) {
|
||||
var hashKey = "000" + Hash.createChannelId().slice(0, -3);
|
||||
folder[hashKey] = {
|
||||
metadata: true
|
||||
};
|
||||
}
|
||||
exp.getFolderData(folder)[key] = value;
|
||||
cb();
|
||||
};
|
||||
|
||||
/**
|
||||
* INTEGRITY CHECK
|
||||
*/
|
||||
|
||||
var onSync = function (next) {
|
||||
if (exp.rt) {
|
||||
exp.rt.sync();
|
||||
Realtime.whenRealtimeSyncs(exp.rt, next);
|
||||
} else {
|
||||
window.setTimeout(next, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
exp.migrateReadOnly = function (cb) {
|
||||
if (readOnly || !config.editKey) { return void cb({error: 'EFORBIDDEN'}); }
|
||||
if (files.version >= 2) { return void cb(); } // Already migrated, nothing to do
|
||||
files.migrateRo = 1;
|
||||
var next = function () {
|
||||
var copy = JSON.parse(JSON.stringify(files));
|
||||
exp.reencrypt(config.editKey, config.editKey, copy);
|
||||
setTimeout(function () {
|
||||
if (files.version >= 2) {
|
||||
// Already migrated by another user while we were re-encrypting
|
||||
return void cb();
|
||||
}
|
||||
Object.keys(copy).forEach(function (k) {
|
||||
files[k] = copy[k];
|
||||
});
|
||||
files.version = 2;
|
||||
delete files.migrateRo;
|
||||
|
||||
onSync(cb);
|
||||
}, 1000);
|
||||
};
|
||||
onSync(next);
|
||||
};
|
||||
|
||||
exp.migrate = function (cb) {
|
||||
if (readOnly) { return void cb(); }
|
||||
// Make sure unsorted doesn't exist anymore
|
||||
// Note: Unsorted only works with the old structure where pads are href
|
||||
// It should be called before the migration code
|
||||
var fixUnsorted = function () {
|
||||
if (!files[UNSORTED] || !files[OLD_FILES_DATA]) { return; }
|
||||
debug("UNSORTED still exists in the object, removing it...");
|
||||
var us = files[UNSORTED];
|
||||
if (us.length === 0) {
|
||||
delete files[UNSORTED];
|
||||
return;
|
||||
}
|
||||
us.forEach(function (el) {
|
||||
if (typeof el !== "string") {
|
||||
return;
|
||||
}
|
||||
var data = files[OLD_FILES_DATA].filter(function (x) {
|
||||
return x.href === el;
|
||||
});
|
||||
if (data.length === 0) {
|
||||
files[OLD_FILES_DATA].push({
|
||||
href: el
|
||||
});
|
||||
}
|
||||
return;
|
||||
});
|
||||
delete files[UNSORTED];
|
||||
};
|
||||
// mergeDrive...
|
||||
var migrateToNewFormat = function (todo) {
|
||||
if (!files[OLD_FILES_DATA]) {
|
||||
return void todo();
|
||||
}
|
||||
try {
|
||||
debug("Migrating file system...");
|
||||
files.migrate = 1;
|
||||
var next = function () {
|
||||
var oldData = files[OLD_FILES_DATA].slice();
|
||||
if (!files[FILES_DATA]) {
|
||||
files[FILES_DATA] = {};
|
||||
}
|
||||
var newData = files[FILES_DATA];
|
||||
//var oldFiles = oldData.map(function (o) { return o.href; });
|
||||
oldData.forEach(function (obj) {
|
||||
if (!obj || !obj.href) { return; }
|
||||
var href = obj.href;
|
||||
var id = Util.createRandomInteger();
|
||||
var paths = exp.findFile(href);
|
||||
var data = obj;
|
||||
var key = Hash.createChannelId();
|
||||
if (data) {
|
||||
newData[id] = data;
|
||||
} else {
|
||||
newData[id] = {href: href};
|
||||
}
|
||||
paths.forEach(function (p) {
|
||||
var parentPath = p.slice();
|
||||
var okey = parentPath.pop(); // get the parent
|
||||
var parent = exp.find(parentPath);
|
||||
if (exp.isInTrashRoot(p)) {
|
||||
parent.element = id;
|
||||
newData[id].filename = p[1];
|
||||
return;
|
||||
}
|
||||
if (exp.isPathIn(p, ['hrefArray'])) {
|
||||
parent[okey] = id;
|
||||
return;
|
||||
}
|
||||
// else root or trash (not trashroot)
|
||||
parent[key] = id;
|
||||
newData[id].filename = okey;
|
||||
delete parent[okey];
|
||||
});
|
||||
});
|
||||
delete files[OLD_FILES_DATA];
|
||||
delete files.migrate;
|
||||
todo();
|
||||
};
|
||||
onSync(next);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
todo();
|
||||
}
|
||||
};
|
||||
|
||||
fixUnsorted();
|
||||
migrateToNewFormat(cb);
|
||||
};
|
||||
|
||||
exp.fixFiles = function (silent) {
|
||||
// Explore the tree and check that everything is correct:
|
||||
// * 'root', 'trash', 'unsorted' and 'filesData' exist and are objects
|
||||
// * ROOT: Folders are objects, files are href
|
||||
// * TRASH: Trash root contains only arrays, each element of the array is an object {element:.., path:..}
|
||||
// * OLD_FILES_DATA: - Data (title, cdate, adte) are stored in filesData. filesData contains only href keys linking to object with title, cdate, adate.
|
||||
// - Dates (adate, cdate) can be parsed/formatted
|
||||
// - All files in filesData should be either in 'root', 'trash' or 'unsorted'. If that's not the case, copy the fily to 'unsorted'
|
||||
// * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT
|
||||
|
||||
// We can't fix anything in read-only mode: abort
|
||||
if (readOnly) { return; }
|
||||
|
||||
if (silent) { debug = function () {}; }
|
||||
|
||||
var t0 = +new Date();
|
||||
debug("Cleaning file system...");
|
||||
|
||||
var before = JSON.stringify(files);
|
||||
|
||||
var fixRoot = function (elem) {
|
||||
if (typeof(files[ROOT]) !== "object") { debug("ROOT was not an object"); files[ROOT] = {}; }
|
||||
var element = elem || files[ROOT];
|
||||
if (!element) { return console.error("Invalid element in root"); }
|
||||
var nbMetadataFolders = 0;
|
||||
// caching this variables saves a lot of hashmap lookups in this loop
|
||||
var static_data = files[STATIC_DATA];
|
||||
var files_data = files[FILES_DATA];
|
||||
var element_el;
|
||||
for (var el in element) {
|
||||
element_el = element[el];
|
||||
if (element_el === null) {
|
||||
console.error('element[%s] is null', el);
|
||||
delete element[el];
|
||||
continue;
|
||||
}
|
||||
if (exp.isFolderData(element_el)) {
|
||||
if (nbMetadataFolders !== 0) {
|
||||
debug("Multiple metadata files in folder");
|
||||
delete element[el];
|
||||
}
|
||||
nbMetadataFolders++;
|
||||
continue;
|
||||
}
|
||||
if (!exp.isFile(element_el, true) && !exp.isFolder(element_el)) {
|
||||
debug("An element in ROOT was not a folder nor a file. ", element_el);
|
||||
delete element[el];
|
||||
continue;
|
||||
}
|
||||
if (exp.isFolder(element_el)) {
|
||||
fixRoot(element_el);
|
||||
continue;
|
||||
}
|
||||
if (typeof element_el === "string") {
|
||||
// We have an old file (href) which is not in filesData: add it
|
||||
var id = Util.createRandomInteger();
|
||||
var key = Hash.createChannelId();
|
||||
files_data[id] = {
|
||||
href: exp.cryptor.encrypt(element_el),
|
||||
filename: el
|
||||
};
|
||||
element[key] = id;
|
||||
delete element[el];
|
||||
}
|
||||
if (typeof element_el === "number") {
|
||||
var data = files_data[element_el] || static_data[element_el];
|
||||
if (!data) {
|
||||
debug("An element in ROOT doesn't have associated data", element_el, el);
|
||||
delete element[el];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var fixTrashRoot = function () {
|
||||
if (sharedFolder) { return; }
|
||||
if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; }
|
||||
var tr = files[TRASH];
|
||||
var toClean;
|
||||
var addToClean = function (obj, idx, el) {
|
||||
if (typeof(obj) !== "object") { toClean.push(idx); return; }
|
||||
|
||||
// shared folders have their own userObject
|
||||
if (exp.isSharedFolder(obj.element)) { return; }
|
||||
|
||||
if (!exp.isFile(obj.element, true) &&
|
||||
!exp.isFolder(obj.element)) { toClean.push(idx); return; }
|
||||
if (!Array.isArray(obj.path)) { toClean.push(idx); return; }
|
||||
if (typeof obj.element === "string") {
|
||||
// We have an old file (href) which is not in filesData: add it
|
||||
var id = Util.createRandomInteger();
|
||||
files[FILES_DATA][id] = {
|
||||
href: exp.cryptor.encrypt(obj.element),
|
||||
filename: el
|
||||
};
|
||||
obj.element = id;
|
||||
}
|
||||
if (exp.isFolder(obj.element)) { fixRoot(obj.element); }
|
||||
if (typeof obj.element === "number") {
|
||||
var data = files[FILES_DATA][obj.element] || files[STATIC_DATA][obj.element];
|
||||
if (!data) {
|
||||
debug("An element in TRASH doesn't have associated data", obj.element, el);
|
||||
toClean.push(idx);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
for (var el in tr) {
|
||||
if (!Array.isArray(tr[el])) {
|
||||
debug("An element in TRASH root is not an array. ", tr[el]);
|
||||
delete tr[el];
|
||||
} else if (tr[el].length === 0) {
|
||||
debug("Empty array in TRASH root. ", tr[el]);
|
||||
delete tr[el];
|
||||
} else {
|
||||
toClean = [];
|
||||
for (var j=0; j<tr[el].length; j++) {
|
||||
addToClean(tr[el][j], j, el);
|
||||
}
|
||||
for (var i = toClean.length-1; i>=0; i--) {
|
||||
tr[el].splice(toClean[i], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var fixTemplate = function () {
|
||||
if (sharedFolder) { return; }
|
||||
if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; }
|
||||
var dedup = Util.deduplicateString(files[TEMPLATE]);
|
||||
if (dedup.length !== files[TEMPLATE].length) {
|
||||
files[TEMPLATE] = dedup;
|
||||
}
|
||||
var us = files[TEMPLATE];
|
||||
var rootFiles = exp.getFiles([ROOT]);
|
||||
var toClean = [];
|
||||
us.forEach(function (el, idx) {
|
||||
if (!exp.isFile(el, true) || rootFiles.indexOf(el) !== -1) {
|
||||
toClean.push(el);
|
||||
return;
|
||||
}
|
||||
if (typeof el === "string") {
|
||||
// We have an old file (href) which is not in filesData: add it
|
||||
var id = Util.createRandomInteger();
|
||||
files[FILES_DATA][id] = {
|
||||
href: exp.cryptor.encrypt(el)
|
||||
};
|
||||
us[idx] = id;
|
||||
return;
|
||||
}
|
||||
if (typeof el === "number") {
|
||||
var data = files[FILES_DATA][el];
|
||||
if (!data) {
|
||||
debug("An element in TEMPLATE doesn't have associated data", el);
|
||||
toClean.push(el);
|
||||
}
|
||||
}
|
||||
});
|
||||
toClean.forEach(function (el) {
|
||||
var idx = us.indexOf(el);
|
||||
if (idx !== -1) {
|
||||
us.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
var fixFilesData = function () {
|
||||
if (typeof files[FILES_DATA] !== "object") { debug("FILES_DATA was not an object"); files[FILES_DATA] = {}; }
|
||||
var fd = files[FILES_DATA];
|
||||
var rootFiles = exp.getFiles([ROOT, TRASH, 'hrefArray']);
|
||||
var root = exp.find([ROOT]);
|
||||
var toClean = [];
|
||||
for (var id in fd) {
|
||||
if (String(id) !== String(Number(id))) {
|
||||
debug("Invalid file ID in filesData.", id);
|
||||
toClean.push(id);
|
||||
continue;
|
||||
}
|
||||
id = Number(id);
|
||||
var el = fd[id];
|
||||
|
||||
// Clean corrupted data
|
||||
if (!el || typeof(el) !== "object") {
|
||||
debug("An element in filesData was not an object.", el);
|
||||
toClean.push(id);
|
||||
continue;
|
||||
}
|
||||
// Clean missing href
|
||||
if (!el.href && !el.roHref) {
|
||||
debug("Removing an element in filesData with a missing href.", el);
|
||||
toClean.push(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
var decryptedHref;
|
||||
try {
|
||||
decryptedHref = el.href && ((el.href.indexOf('#') !== -1) ? el.href : exp.cryptor.decrypt(el.href));
|
||||
} catch (e) {}
|
||||
|
||||
if (decryptedHref && decryptedHref.indexOf('#') === -1) {
|
||||
// If we can't decrypt the href, it means we don't have the correct secondaryKey and we're in readOnly mode:
|
||||
// abort now, we won't be able to fix anything anyway
|
||||
continue;
|
||||
}
|
||||
|
||||
var parsed = Hash.parsePadUrl(decryptedHref || el.roHref);
|
||||
var secret;
|
||||
|
||||
// Clean invalid hash
|
||||
if (!parsed.hash) {
|
||||
debug("Removing an element in filesData with a invalid href.", el);
|
||||
toClean.push(id);
|
||||
continue;
|
||||
}
|
||||
// Clean invalid type
|
||||
if (!parsed.type) {
|
||||
debug("Removing an element in filesData with a invalid type.", el);
|
||||
toClean.push(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we have an edit link, check the view link
|
||||
if (decryptedHref && parsed.hashData.type === "pad" && parsed.hashData.version) {
|
||||
if (parsed.hashData.mode === "view") {
|
||||
el.roHref = decryptedHref;
|
||||
delete el.href;
|
||||
} else if (!el.roHref) {
|
||||
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
|
||||
el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret);
|
||||
} else {
|
||||
var parsed2 = Hash.parsePadUrl(el.roHref);
|
||||
if (!parsed2.hash || !parsed2.type) {
|
||||
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
|
||||
el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret);
|
||||
}
|
||||
}
|
||||
}
|
||||
// v0 hashes don't support read-only
|
||||
if (parsed.hashData.version === 0) {
|
||||
delete el.roHref;
|
||||
}
|
||||
|
||||
// Fix href
|
||||
if (decryptedHref && decryptedHref.slice(0,1) !== '/') {
|
||||
el.href = exp.cryptor.encrypt(Hash.getRelativeHref(decryptedHref));
|
||||
}
|
||||
// Fix creation time
|
||||
if (!el.ctime) { el.ctime = el.atime; }
|
||||
// Fix title
|
||||
if (!el.title) { el.title = exp.getDefaultName(parsed); }
|
||||
// Fix channel
|
||||
if (!el.channel) {
|
||||
try {
|
||||
if (!secret) {
|
||||
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
|
||||
}
|
||||
el.channel = secret.channel;
|
||||
console.log(el);
|
||||
debug('Adding missing channel in filesData ', el.channel);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
if (!Hash.isValidChannel(el.channel)) {
|
||||
// FIXME delete channel? replace with parsed.channel?
|
||||
console.error('Remove invalid channel', el.channel, el);
|
||||
// toClean.push(id);
|
||||
}
|
||||
|
||||
if ((loggedIn || config.testMode) && rootFiles.indexOf(id) === -1) {
|
||||
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el);
|
||||
var newName = Hash.createChannelId();
|
||||
root[newName] = id;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
toClean.forEach(function (id) {
|
||||
spliceFileData(id);
|
||||
});
|
||||
// make sure that links are displayed at least once in your drive if you are going to keep them
|
||||
var sd = files[STATIC_DATA];
|
||||
var toCleanSD = [];
|
||||
for (var id2 in sd) {
|
||||
id2 = Number(id2);
|
||||
var el2 = sd[id2];
|
||||
if (!el2 || typeof(el2) !== "object" || !el2.href) {
|
||||
toCleanSD.push(id2);
|
||||
continue;
|
||||
}
|
||||
if ((loggedIn || config.testMode) && rootFiles.indexOf(id2) === -1) {
|
||||
toCleanSD.push(id2);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
var spliceSD = function (id) {
|
||||
if (readOnly) { return; }
|
||||
delete files[STATIC_DATA][id];
|
||||
};
|
||||
toCleanSD.forEach(spliceSD);
|
||||
};
|
||||
var fixSharedFolders = function () {
|
||||
if (sharedFolder) { return; }
|
||||
if (typeof(files[SHARED_FOLDERS]) !== "object") { debug("SHARED_FOLDER was not an object"); files[SHARED_FOLDERS] = {}; }
|
||||
var sf = files[SHARED_FOLDERS];
|
||||
var rootFiles = exp.getFiles([ROOT, TRASH]);
|
||||
var root = exp.find([ROOT]);
|
||||
var parsed /*, secret */, el;
|
||||
for (var id in sf) {
|
||||
el = sf[id];
|
||||
id = Number(id);
|
||||
|
||||
var href;
|
||||
try {
|
||||
href = el.href && ((el.href.indexOf('#') !== -1) ? el.href : exp.cryptor.decrypt(el.href));
|
||||
} catch (e) {}
|
||||
|
||||
// Fix undefined hash
|
||||
parsed = Hash.parsePadUrl(href || el.roHref);
|
||||
if (!parsed || !parsed.hash || parsed.hash === "undefined") {
|
||||
delete sf[id];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fix shared folder not displayed in root
|
||||
if (rootFiles.indexOf(id) === -1) {
|
||||
console.log('missing' + id);
|
||||
var newName = Hash.createChannelId();
|
||||
root[newName] = id;
|
||||
}
|
||||
}
|
||||
};
|
||||
var fixSharedFoldersTemp = function () {
|
||||
if (sharedFolder) { return; }
|
||||
if (typeof(files[SHARED_FOLDERS_TEMP]) !== "object") {
|
||||
debug("SHARED_FOLDER_TEMP was not an object");
|
||||
files[SHARED_FOLDERS_TEMP] = {};
|
||||
}
|
||||
// Remove deprecated shared folder if they were already added back
|
||||
var sft = files[SHARED_FOLDERS_TEMP];
|
||||
var sf = files[SHARED_FOLDERS];
|
||||
for (var id in sft) {
|
||||
if (sf[id]) {
|
||||
delete sft[id];
|
||||
}
|
||||
}
|
||||
};
|
||||
var fixStaticData = function () {
|
||||
if (!Util.isObject(files[STATIC_DATA])) {
|
||||
debug("STATIC_DATA was not an object");
|
||||
files[STATIC_DATA] = {};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var fixDrive = function () {
|
||||
Object.keys(files).forEach(function (key) {
|
||||
if (key.slice(0,1) === '/') { delete files[key]; }
|
||||
});
|
||||
};
|
||||
|
||||
fixStaticData();
|
||||
fixRoot();
|
||||
fixTrashRoot();
|
||||
fixTemplate();
|
||||
fixFilesData();
|
||||
fixDrive();
|
||||
fixSharedFolders();
|
||||
fixSharedFoldersTemp();
|
||||
|
||||
var ms = (+new Date() - t0) + 'ms';
|
||||
if (JSON.stringify(files) !== before) {
|
||||
debug("Your file system was corrupted. It has been cleaned so that the pads you visit can be stored safely.", ms);
|
||||
return;
|
||||
}
|
||||
debug("File system was clean.", ms);
|
||||
};
|
||||
|
||||
return exp;
|
||||
};
|
||||
|
||||
return module;
|
||||
};
|
||||
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = factory(
|
||||
require('./common-util'),
|
||||
require('./common-hash'),
|
||||
require('./common-realtime'),
|
||||
);
|
||||
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
|
||||
define([
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-realtime.js',
|
||||
], factory);
|
||||
} else {
|
||||
// unsupported initialization
|
||||
}
|
||||
|
||||
})();
|
||||
File diff suppressed because it is too large
Load Diff
2
www/common/worker.bundle.min.js
vendored
2
www/common/worker.bundle.min.js
vendored
File diff suppressed because one or more lines are too long
@ -13,7 +13,7 @@ define([
|
||||
'/customize/messages.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-util.js',
|
||||
'/common/outer/worker-channel.js',
|
||||
'/common/events-channel.js',
|
||||
'/common/outer/x2t.js',
|
||||
'/components/file-saver/FileSaver.min.js',
|
||||
'css!/components/bootstrap/dist/css/bootstrap.min.css',
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
define([
|
||||
'/common/common-util.js',
|
||||
'/file/file-crypto.js',
|
||||
'/common/outer/cache-store.js',
|
||||
'/common/cache-store.js',
|
||||
'/components/x2js/x2js.js',
|
||||
'/components/pako/dist/pako.min.js',
|
||||
'/common/common-hash.js',
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
define([
|
||||
'/components/localforage/dist/localforage.min.js',
|
||||
'/common/outer/cache-store.js',
|
||||
'/common/cache-store.js',
|
||||
'/components/nthen/index.js',
|
||||
], function (localForage, Cache, nThen) {
|
||||
nThen(function (w) {
|
||||
|
||||
@ -13,14 +13,14 @@ define([
|
||||
'/common/common-constants.js',
|
||||
'/common/cryptget.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/outer/cache-store.js',
|
||||
'/common/cache-store.js',
|
||||
'/common/common-interface.js',
|
||||
'chainpad-netflux',
|
||||
'/components/chainpad-crypto/crypto.js',
|
||||
'/common/user-object.js',
|
||||
'/common/clipboard.js',
|
||||
'/common/outer/login-block.js',
|
||||
'/common/outer/roster.js',
|
||||
//'/common/outer/roster.js',
|
||||
'/common/rpc.js',
|
||||
'/common/pinpad.js',
|
||||
'/common/outer/local-store.js',
|
||||
@ -31,7 +31,7 @@ define([
|
||||
'less!/customize/src/less2/pages/page-report.less',
|
||||
], function ($, ApiConfig, h, Messages,
|
||||
nThen, Hash, Util, Constants, Crypt, Cryptpad, Cache, UI, CPNetflux,
|
||||
Crypto, UserObject, Clipboard, Block, Roster, Rpc, Pinpad, LocalStore) {
|
||||
Crypto, UserObject, Clipboard, Block, /*Roster,*/ Rpc, Pinpad, LocalStore) {
|
||||
var $report = $('#cp-report');
|
||||
var blockHash = localStorage.Block_hash;
|
||||
if (!blockHash) {
|
||||
@ -226,6 +226,7 @@ define([
|
||||
|
||||
|
||||
// Repair roster mode
|
||||
/*
|
||||
if (!report) {
|
||||
let roster = obj.keys.roster;
|
||||
let rpc, anon_rpc;
|
||||
@ -269,7 +270,7 @@ define([
|
||||
}));
|
||||
}).nThen(next);
|
||||
return;
|
||||
}
|
||||
}*/
|
||||
|
||||
// Check team drive
|
||||
nThen(function (ww) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user