mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-12 19:49:59 +05:00
Replace common files with their umd version
This commit is contained in:
parent
6b660c0e75
commit
3188a457f0
@ -38,3 +38,7 @@ www/debug/chainpad.dist.js
|
||||
www/pad/mathjax/
|
||||
www/code/mermaid*.js
|
||||
www/code/orgmode.js
|
||||
|
||||
www/common/worker.bundle.js
|
||||
src/tweetnacl/
|
||||
_build/
|
||||
|
||||
@ -6,6 +6,11 @@
|
||||
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',
|
||||
@ -41,5 +46,5 @@ Object.keys(map).forEach(newPath => {
|
||||
}
|
||||
const from = REVERSE ? oldPath : newPath;
|
||||
const to = REVERSE ? newPath : oldPath;
|
||||
Fs.copySync(from, to);
|
||||
Fs.cpSync(from, to);
|
||||
});
|
||||
|
||||
@ -121,7 +121,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
|
||||
'/api/config',
|
||||
'/components/tweetnacl/nacl-fast.min.js',
|
||||
], (nThen, Util, ApiConfig) => {
|
||||
factory(nThen, Util, ApiConfig, window.nacl);
|
||||
return factory(nThen, Util, ApiConfig, window.nacl);
|
||||
});
|
||||
} else {
|
||||
// unsupported initialization
|
||||
|
||||
@ -236,7 +236,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
|
||||
'/common/outer/http-command.js',
|
||||
'/components/tweetnacl/nacl-fast.min.js',
|
||||
], (Util, ApiConfig, ServerCommand) => {
|
||||
factory(Util, ApiConfig, ServerCommand, window.nacl);
|
||||
return factory(Util, ApiConfig, ServerCommand, window.nacl);
|
||||
});
|
||||
} else {
|
||||
// unsupported initialization
|
||||
|
||||
@ -22,6 +22,10 @@ const factory = (AppConfig = {}, ApiConfig = {},
|
||||
);
|
||||
};
|
||||
|
||||
// Initialize values when using in browser directly
|
||||
if (Object.keys(AppConfig).length) {
|
||||
setCustomize({AppConfig,ApiConfig});
|
||||
}
|
||||
|
||||
const Types = { setCustomize };
|
||||
|
||||
|
||||
@ -899,11 +899,9 @@ const factory = (Util) => {
|
||||
};
|
||||
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = factory(
|
||||
require('../../common/common-util')
|
||||
);
|
||||
module.exports = factory(require('./common-util'));
|
||||
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
|
||||
define(['/common/common-util'], factory);
|
||||
define(['/common/common-util.js'], factory);
|
||||
} else {
|
||||
// unsupported initialization
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
const factory = (Util) => {
|
||||
var Rec = {};
|
||||
|
||||
const window = globalThis;
|
||||
var debug = function () {};
|
||||
|
||||
// Get week number with any "WKST" (firts day of the week)
|
||||
@ -898,7 +899,7 @@ const factory = (Util) => {
|
||||
};
|
||||
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = factory(require('../../common/common-util'));
|
||||
module.exports = factory(require('./common-util'));
|
||||
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
|
||||
define(['/common/common-util.js'], factory);
|
||||
} else {
|
||||
|
||||
@ -2,8 +2,13 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
define(['/customize/application_config.js'], function (AppConfig) {
|
||||
(() => {
|
||||
const factory = function (AppConfig = {}) {
|
||||
return {
|
||||
setCustomize: data => {
|
||||
AppConfig = data.AppConfig;
|
||||
},
|
||||
|
||||
// localStorage
|
||||
userHashKey: 'User_hash',
|
||||
userNameKey: 'User_name',
|
||||
@ -29,4 +34,14 @@ define(['/customize/application_config.js'], function (AppConfig) {
|
||||
criticalApps: ['profile', 'settings', 'debug', 'admin', 'support', 'notifications', 'calendar', 'moderation', 'oldadmin'], // XXX oldadmin
|
||||
earlyAccessApps: ['doc', 'presentation']
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
@ -3,9 +3,13 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
(function () {
|
||||
var factory = function (AppConfig, Scrypt) {
|
||||
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
|
||||
|
||||
@ -98,8 +102,8 @@ var factory = function (AppConfig, Scrypt) {
|
||||
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = factory(
|
||||
{}, //require("../../customize.dist/application_config.js"),
|
||||
require("../components/scrypt-async/scrypt-async.min.js")
|
||||
undefined, //require("../../customize.dist/application_config.js"),
|
||||
require("scrypt-async")
|
||||
);
|
||||
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
|
||||
define([
|
||||
|
||||
@ -2,12 +2,15 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
define([
|
||||
'/customize/messages.js',
|
||||
'/customize/application_config.js'
|
||||
], function (Messages, AppConfig) {
|
||||
(() => {
|
||||
const factory = (AppConfig = {}, Messages= {}) => {
|
||||
var Feedback = {};
|
||||
|
||||
Feedback.setCustomize = data => {
|
||||
Messages = data.Messages;
|
||||
AppConfig = data.AppConfig;
|
||||
};
|
||||
|
||||
Feedback.init = function (state) {
|
||||
Feedback.state = state;
|
||||
};
|
||||
@ -53,9 +56,24 @@ define([
|
||||
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
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@ -2,15 +2,8 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
define([
|
||||
'/components/chainpad-crypto/crypto.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-constants.js',
|
||||
'/customize/messages.js',
|
||||
|
||||
'/common/common-realtime.js',
|
||||
], function (Crypto, Hash, Util, Constants, Messages, Realtime) {
|
||||
(() => {
|
||||
const factory = (Crypto, Hash, Util, Constants, Realtime) => {
|
||||
var Msg = {};
|
||||
|
||||
var createData = Msg.createData = function (proxy, hash) {
|
||||
@ -144,4 +137,25 @@ define([
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
})();
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
define([], function () {
|
||||
(() => {
|
||||
const factory = () => {
|
||||
var common = {};
|
||||
|
||||
/*
|
||||
@ -22,4 +23,14 @@ define([], function () {
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@ -368,6 +368,42 @@
|
||||
};
|
||||
|
||||
|
||||
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(url.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));
|
||||
|
||||
@ -761,7 +797,6 @@
|
||||
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
|
||||
@ -797,9 +832,7 @@
|
||||
}
|
||||
};
|
||||
};
|
||||
/**
|
||||
* End of code copied from saferphore
|
||||
*/
|
||||
/* End of code copied from saferphore */
|
||||
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = Util;
|
||||
|
||||
@ -2,19 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
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',
|
||||
], function (Crypto, CPNetflux, Netflux, Util, Hash, Realtime, NetConfig, Cache, Pinpad, nThen) {
|
||||
(() => {
|
||||
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);
|
||||
@ -211,4 +201,38 @@ define([
|
||||
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
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
@ -4,11 +4,19 @@
|
||||
|
||||
/* eslint compat/compat: "off" */
|
||||
|
||||
define(['/api/config'], function (ApiConfig) {
|
||||
(() => {
|
||||
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.pathname.slice(1, -1); // remove "/" at the beginnin and the end
|
||||
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;
|
||||
@ -180,4 +188,14 @@ define(['/api/config'], function (ApiConfig) {
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@ -2,6 +2,16 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
define({
|
||||
currentVersion: 'v7'
|
||||
});
|
||||
(() => {
|
||||
const factory = () => {
|
||||
return {
|
||||
currentVersion: 'v7'
|
||||
};
|
||||
};
|
||||
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = factory();
|
||||
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
|
||||
define([], factory);
|
||||
}
|
||||
})();
|
||||
|
||||
@ -2,10 +2,10 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
define([
|
||||
'/common/common-util.js',
|
||||
'/components/localforage/dist/localforage.min.js',
|
||||
], function (Util, localForage) {
|
||||
(() => {
|
||||
const factory = (Util, localForage) => {
|
||||
let window = globalThis;
|
||||
let self = globalThis;
|
||||
var S = window.CryptPad_Cache = {};
|
||||
var onReady = Util.mkEvent(true);
|
||||
|
||||
@ -191,4 +191,20 @@ define([
|
||||
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
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@ -2,14 +2,29 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
define([
|
||||
'/components/nthen/index.js',
|
||||
'/common/common-util.js',
|
||||
'/api/config',
|
||||
(() => {
|
||||
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();
|
||||
};
|
||||
|
||||
'/components/tweetnacl/nacl-fast.min.js',
|
||||
], function (nThen, Util, ApiConfig) {
|
||||
var Nacl = window.nacl;
|
||||
var clone = o => JSON.parse(JSON.stringify(o));
|
||||
var randomToken = () => Nacl.util.encodeBase64(Nacl.randomBytes(24));
|
||||
var postData = function (url, data, cb) {
|
||||
@ -36,18 +51,6 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var API_ORIGIN = (function () {
|
||||
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 serverCommand = function (keypair, my_data, cb) {
|
||||
var obj = clone(my_data);
|
||||
obj.publicKey = Nacl.util.encodeBase64(keypair.publicKey);
|
||||
@ -99,5 +102,29 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@ -2,16 +2,16 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
define([
|
||||
'/common/common-util.js',
|
||||
'/api/config',
|
||||
'/common/outer/http-command.js',
|
||||
'/components/tweetnacl/nacl-fast.min.js',
|
||||
], function (Util, ApiConfig, ServerCommand) {
|
||||
var Nacl = window.nacl;
|
||||
(() => {
|
||||
const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
|
||||
|
||||
var Block = {};
|
||||
|
||||
Block.setCustomize = data => {
|
||||
ApiConfig = data.ApiConfig;
|
||||
ServerCommand.setCustomize(data);
|
||||
};
|
||||
|
||||
Block.join = Util.uint8ArrayJoin;
|
||||
|
||||
// publickey <base64 string>
|
||||
@ -220,4 +220,26 @@ define([
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@ -2,18 +2,20 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
define([
|
||||
'/api/config'
|
||||
], function (ApiConfig) {
|
||||
(() => {
|
||||
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 = window.location;
|
||||
if (origin && window && window.document) {
|
||||
l = document.createElement("a");
|
||||
var l = new URL(origin || self?.location?.href);
|
||||
if (origin) {
|
||||
l.href = origin;
|
||||
}
|
||||
var protocol = l.protocol.replace(/http/, 'ws');
|
||||
@ -24,4 +26,16 @@ define([
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
958
www/common/outer/roster.js
Normal file
958
www/common/outer/roster.js
Normal file
@ -0,0 +1,958 @@
|
||||
// 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 onMessage = function (msg, user, vKey, isCp , hash, author) {
|
||||
// count messages received since the last checkpoint
|
||||
// even if they fail to parse
|
||||
ref.internal.sinceLastCheckpoint++;
|
||||
|
||||
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
|
||||
}
|
||||
}());
|
||||
417
www/common/outer/sharedfolder.js
Normal file
417
www/common/outer/sharedfolder.js
Normal file
@ -0,0 +1,417 @@
|
||||
// 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));
|
||||
var drive = proxy.drive || proxy;
|
||||
// View access: can't migrate
|
||||
if (!secondaryKey) { return void cb(); }
|
||||
// Already migrated: nothing to do
|
||||
if (drive.version >= 2) { return void cb(); }
|
||||
// Not yet migrating: migrate
|
||||
if (!drive.migrateRo) { return void uo.migrateReadOnly(cb); }
|
||||
// Already migrating: wait for the end...
|
||||
var done = false;
|
||||
var to;
|
||||
var it = setInterval(function () {
|
||||
if (drive.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 = proxy.drive ? ['drive', 'version'] : ['version'];
|
||||
proxy.on('change', path, function () {
|
||||
if (done) { return; }
|
||||
if (drive.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, userObject, waitFor, progress, cache) {
|
||||
var shared = Util.find(store.proxy, ['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
|
||||
}
|
||||
|
||||
})();
|
||||
@ -3,12 +3,8 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
// This file provides the API for the channel for talking to and from the sandbox iframe.
|
||||
define([
|
||||
//'/common/sframe-protocol.js',
|
||||
'/common/common-util.js',
|
||||
'/api/config',
|
||||
], function (/*SFrameProtocol,*/ Util, ApiConfig) {
|
||||
|
||||
(() => {
|
||||
const factory = (Util, ApiConfig = {}) => {
|
||||
var mkTxid = function () {
|
||||
return Math.random().toString(16).replace('0.', '') + Math.random().toString(16).replace('0.', '');
|
||||
};
|
||||
@ -161,11 +157,13 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var trusted = [
|
||||
ApiConfig.httpUnsafeOrigin,
|
||||
ApiConfig.httpSafeOrigin,
|
||||
'', // sharedworkers
|
||||
];
|
||||
var trusted = [''];
|
||||
if (ApiConfig.httpUnsafeOrigin) {
|
||||
trusted.push(ApiConfig.httpUnsafeOrigin);
|
||||
trusted.push(ApiConfig.httpSafeOrigin);
|
||||
} else if (globalThis.location) {
|
||||
trusted.push(globalThis.location.origin);
|
||||
}
|
||||
|
||||
onMsg.reg(function (msg) {
|
||||
if (!chanLoaded) { return; }
|
||||
@ -220,4 +218,18 @@ define([
|
||||
};
|
||||
|
||||
return { create: create };
|
||||
});
|
||||
};
|
||||
|
||||
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',
|
||||
'/api/config',
|
||||
], factory);
|
||||
} else {
|
||||
// unsupported initialization
|
||||
}
|
||||
})();
|
||||
|
||||
@ -2,35 +2,67 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
define([
|
||||
"/customize/application_config.js",
|
||||
"/api/config",
|
||||
"/common/onlyoffice/current-version.js",
|
||||
], function (AppConfig, ApiConfig, OOCurrentVersion) {
|
||||
(() => {
|
||||
const factory = (AppConfig = {}, ApiConfig = {},
|
||||
OOCurrentVersion) => {
|
||||
|
||||
let availablePadTypes = [];
|
||||
const OO_APPS = ["sheet", "doc", "presentation"];
|
||||
const ooEnabled = ApiConfig.onlyOffice && ApiConfig.onlyOffice.availableVersions.includes(
|
||||
OOCurrentVersion.currentVersion,
|
||||
);
|
||||
|
||||
let availablePadTypes = AppConfig.availablePadTypes.filter(
|
||||
(t) => ooEnabled || !OO_APPS.includes(t)
|
||||
);
|
||||
const setCustomize = data => {
|
||||
AppConfig = data.AppConfig;
|
||||
ApiConfig = data.ApiConfig;
|
||||
|
||||
let availableTypes;
|
||||
if (ApiConfig.appsToDisable) {
|
||||
availableTypes = availablePadTypes.filter(value => !ApiConfig.appsToDisable.includes(value));
|
||||
} else {
|
||||
availableTypes = availablePadTypes;
|
||||
}
|
||||
|
||||
var appsToSelect = availablePadTypes.filter(value => !['drive', 'teams', 'file', 'contacts', 'convert'].includes(value));
|
||||
|
||||
return {
|
||||
availableTypes,
|
||||
appsToSelect,
|
||||
|
||||
isAvailable: function (type) {
|
||||
return availableTypes.includes(type);
|
||||
},
|
||||
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
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@ -8,7 +8,7 @@ const factory = (UserObject, Util, Hash,
|
||||
|
||||
let setCustomize = data => {
|
||||
Messages = data.Messages;
|
||||
UO.setCustomize(data);
|
||||
UserObject.setCustomize(data);
|
||||
};
|
||||
|
||||
var getConfig = function (Env) {
|
||||
@ -1788,17 +1788,17 @@ const factory = (UserObject, Util, Hash,
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
// We don't need Messages in worker or node
|
||||
module.exports = factory(
|
||||
require('./userObject'),
|
||||
require('./user-object'),
|
||||
require('./common-util'),
|
||||
require('./common-hash'),
|
||||
require('../worker/modules/sharedfolder'),
|
||||
require('../worker/components/sharedfolder'),
|
||||
undefined,
|
||||
require('./common-feedback'),
|
||||
require('nthen')
|
||||
);
|
||||
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
|
||||
define([
|
||||
'/common/userObject.js',
|
||||
'/common/user-object.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/outer/sharedfolder.js',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user