Add Account module

This commit is contained in:
yflory 2025-02-03 15:28:25 +01:00
parent 258c015e24
commit fee4c79b9d
4 changed files with 227 additions and 141 deletions

View File

@ -6,12 +6,13 @@
const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
Migrate, Hash, Util, Constants, Feedback,
Realtime, Messaging, Pinpad, Rpc, Merge, Cache,
SF, Cursor, Support, Integration, OnlyOffice,
SF, AccountTS, Cursor, Support, Integration, OnlyOffice,
Mailbox, Profile, Team, Messenger, History,
Calendar, Block, NetConfig, AppConfig = {},
Crypto, ChainPad, CpNetflux, Listmap,
Netflux, nThen) => {
const Account = AccountTS.Account;
const window = globalThis;
const Saferphore = Util.Saferphore;
var onReadyEvt = Util.mkEvent(true);
@ -2894,160 +2895,56 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
});
};
var connect = function (clientId, data, cb) {
var hash = data.userHash || data.anonHash || Hash.createRandomHash('drive');
const connect = (clientId, data, cb) => {
const account = Account.init({
userHash: data.userHash,
anonHash: data.anonHash,
cache: data.cache,
form_seed: data.form_seed,
store,
broadcast,
postMessage
if (!hash) {
return void cb({error: '[Store.init] Unable to find or create a drive hash. Aborting...'});
}
});
const {
channel, onAccountReady, onAccountCacheReady,
onDisconnect, onReconnect
} = account;
var updateProgress = function (data) {
data.type = 'drive';
postMessage(clientId, 'LOADING_DRIVE', data);
};
// No password for drive
var secret = Hash.getSecrets('drive', hash);
store.driveChannel = secret.channel;
var listmapConfig = {
data: {},
websocketURL: NetConfig.getWebsocketURL(),
network: store.network,
channel: secret.channel,
readOnly: false,
validateKey: secret.keys.validateKey || undefined,
crypto: Crypto.createEncryptor(secret.keys),
Cache: Cache,
userName: 'fs',
logLevel: 1,
ChainPad: ChainPad,
updateProgress: updateProgress,
classic: true,
};
var rt = window.rt = Listmap.create(listmapConfig);
store.driveSecret = secret;
store.proxy = rt.proxy;
store.onRpcReadyEvt = Util.mkEvent(true);
store.loggedIn = typeof(data.userHash) !== "undefined";
var returned = {
loggedIn: Boolean(data.userHash)
};
rt.proxy.on('create', function (info) {
store.realtime = info.realtime;
store.network = info.network;
if (!data.userHash) {
returned.anonHash = Hash.getEditHashFromKeys(secret);
}
}).on('cacheready', function (info) {
store.offline = true;
store.realtime = info.realtime;
store.networkPromise = info.networkPromise;
store.cacheReturned = returned;
if (store.networkPromise && store.networkPromise.then) {
// Check if we can connect
var to = setTimeout(function () {
store.networkTimeout = true;
broadcast([], "LOADING_DRIVE", {
type: "offline"
});
}, 5000);
store.networkPromise.then(function () {
clearTimeout(to);
}, function (err) {
console.error(err);
clearTimeout(to);
});
}
if (!data.cache) { return; }
// Make sure we have a valid user object before emitting cacheready
if (rt.proxy && !rt.proxy.drive) { return; }
returned.edPublic = rt.proxy.edPublic;
store.driveChannel = channel;
// XXX don't always call onCacheReady and onReady?
// it depends on what was required by the first tab
onAccountCacheReady(returned => {
onCacheReady(clientId, function () {
if (typeof(cb) === "function") { cb(returned); }
if (typeof(cb) === "function") {
cb(returned);
}
onCacheReadyEvt.fire();
});
}).on('ready', function (info) {
delete store.networkTimeout;
if (store.ready) { return; } // the store is already ready, it is a reconnection
store.driveMetadata = info.metadata;
if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; }
if (!rt.proxy[Constants.displayNameKey] && store.noDriveName) {
rt.proxy[Constants.displayNameKey] = store.noDriveName;
}
if (!rt.proxy.uid && store.noDriveUid) {
rt.proxy.uid = store.noDriveUid;
}
if (!rt.proxy.form_seed && data.form_seed) {
rt.proxy.form_seed = data.form_seed;
}
if (rt.proxy.edPublic && Array.isArray(ApiConfig.adminKeys) &&
ApiConfig.adminKeys.indexOf(rt.proxy.edPublic) !== -1) {
store.isAdmin = true;
}
/*
// deprecating localStorage migration as of 4.2.0
var drive = rt.proxy.drive;
// Creating a new anon drive: import anon pads from localStorage
if ((!drive[Constants.oldStorageKey] || !Array.isArray(drive[Constants.oldStorageKey]))
&& !drive['filesData']) {
drive[Constants.oldStorageKey] = [];
}
*/
returned.edPublic = rt.proxy.edPublic;
// Drive already exist: return the existing drive, don't load data from legacy store
});
onAccountReady(returned => {
if (store.manager) {
// If a cache is loading, make sure it is complete before calling onReady
return void onCacheReadyEvt.reg(function () {
onReady(clientId, returned, cb);
});
}
onReady(clientId, returned, cb);
})
.on('change', ['drive', 'migrate'], function () {
var path = arguments[2];
var value = arguments[1];
if (path[0] === 'drive' && path[1] === "migrate" && value === 1) {
rt.network.disconnect();
rt.realtime.abort();
sendDriveEvent('NETWORK_DISCONNECT');
}
})
.on('error', function (info) {
if (info.error && info.error === 'EDELETED') {
if (store.ownDeletion) { return; }
store.isDeleted = true;
broadcast([], "DRIVE_DELETED", info.message);
}
});
// XXX move to drive
onDisconnect(() => {
sendDriveEvent('NETWORK_DISCONNECT');
});
onReconnect(() => {
sendDriveEvent('NETWORK_RECONNECT');
});
// Proxy handlers (reconnect only called when the proxy is ready)
rt.proxy.on('disconnect', function () {
store.offline = true;
sendDriveEvent('NETWORK_DISCONNECT');
broadcast([], "UPDATE_METADATA");
});
rt.proxy.on('reconnect', function () {
store.offline = false;
sendDriveEvent('NETWORK_RECONNECT');
broadcast([], "UPDATE_METADATA");
});
// Ping clients regularly to make sure one tab was not closed without sending a removeClient()
// command. This allow us to avoid phantom viewers in pads.
var PING_INTERVAL = 120000;
var MAX_PING = 30000;
var MAX_FAILED_PING = 2;
const PING_INTERVAL = 120000;
const MAX_PING = 30000;
const MAX_FAILED_PING = 2;
setInterval(function () {
var clients = [];
@ -3302,6 +3199,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
require('./components/merge-drive'),
require('../common/cache-store'),
require('./components/sharedfolder'),
require('./components/account'), // XXX
require('./modules/cursor'),
require('./modules/support'),
require('./modules/integration'),

View File

@ -0,0 +1,163 @@
import nacl from 'tweetnacl/nacl-fast';
import { Account } from '../types'
// node modules
import * as Listmap from 'chainpad-listmap';
import * as ChainPad from 'chainpad';
import * as Crypto from 'chainpad-crypto';
// custom modules
import * as NetConfig from '../../common/network-config.js';
import * as Constants from '../../common/common-constants.js';
import * as Hash from '../../common/common-hash.js';
import * as Util from '../../common/common-util.js';
import * as Cache from '../../common/cache-store.js';
const onCacheReadyEvt: any = Util.mkEvent(true);
const onReadyEvt: any = Util.mkEvent(true);
const onDisconnectEvt: any = Util.mkEvent();
const onReconnectEvt: any = Util.mkEvent();
let ApiConfig:any = {};
const init = (config) => {
const { broadcast, userHash, anonHash } = config;
const hash:string = userHash || anonHash || Hash.createRandomHash('drive');
const store = config.store;
// Update loading screen status
const updateProgress = function (data) {
data.type = 'drive';
broadcast([], 'LOADING_DRIVE', data);
};
// Create account secret from hash
const secret = Hash.getSecrets('drive', hash);
const listmapConfig = {
data: {},
websocketURL: NetConfig.getWebsocketURL(),
network: config.store?.network,
channel: secret.channel,
readOnly: false,
validateKey: secret.keys?.validateKey || undefined,
crypto: Crypto.createEncryptor(secret.keys),
Cache: Cache,
userName: 'fs',
logLevel: 1,
ChainPad: ChainPad,
updateProgress: updateProgress,
classic: true,
};
const rt = globalThis.CP_account_rt = Listmap.create(listmapConfig);
store.driveSecret = secret;
store.proxy = rt.proxy;
store.onRpcReadyEvt = Util.mkEvent(true);
store.loggedIn = typeof(config.userHash) !== "undefined";
const returned:any = {
loggedIn: store.loggedIn
};
rt.proxy.on('create', function (info) {
store.realtime = info.realtime; // XXX move to account
store.network = info.network; // init if not exists
if (!store.loggedIn) {
returned.anonHash = Hash.getEditHashFromKeys(secret);
}
}).on('cacheready', function (info) {
store.offline = true; // XXX move to account
store.realtime = info.realtime; // XXX move to account
store.networkPromise = info.networkPromise;
store.cacheReturned = returned; // XXX move to account
if (store.networkPromise && store.networkPromise.then) {
// Check if we can connect
const to = setTimeout(function () {
store.networkTimeout = true;
broadcast([], "LOADING_DRIVE", {
type: "offline"
});
}, 5000);
store.networkPromise.then(function () {
clearTimeout(to);
}, function (err) {
console.error(err);
clearTimeout(to);
});
}
if (!config.cache) { return; }
// Make sure we have a valid user object before emitting cacheready
if (rt.proxy && !rt.proxy.drive) { return; }
returned.edPublic = rt.proxy.edPublic;
onCacheReadyEvt.fire(returned);
}).on('ready', function (info) {
delete store.networkTimeout;
if (store.ready) { return; } // the store is already ready, it is a reconnection
// XXX DRIVE
store.driveMetadata = info.metadata; // XXX move to drive
// XXX DRIVE
// New drive? create empty object
if (!rt.proxy.drive) { rt.proxy.drive = {}; }
// New drive: recover data from noDrive session
if (!rt.proxy[Constants.displayNameKey] && store.noDriveName) {
rt.proxy[Constants.displayNameKey] = store.noDriveName;
}
if (!rt.proxy.uid && store.noDriveUid) {
rt.proxy.uid = store.noDriveUid;
}
if (!rt.proxy.form_seed && config.form_seed) {
rt.proxy.form_seed = config.form_seed;
}
// Are we an admin?
if (rt.proxy.edPublic && Array.isArray(ApiConfig.adminKeys) &&
ApiConfig.adminKeys.indexOf(rt.proxy.edPublic) !== -1) {
store.isAdmin = true;
}
returned.edPublic = rt.proxy.edPublic;
onReadyEvt.fire(returned);
}).on('error', function (info) {
if (info.error !== 'EDELETED') { return; }
if (store.ownDeletion) { return; }
store.isDeleted = true;
broadcast([], "DRIVE_DELETED", info.message);
}).on('disconnect', function () {
store.offline = true;
onDisconnectEvt.fire();
broadcast([], "UPDATE_METADATA");
}).on('reconnect', function () {
store.offline = false;
onReconnectEvt.fire();
broadcast([], "UPDATE_METADATA");
});
return {
channel: secret.channel,
onAccountCacheReady: onCacheReadyEvt.reg,
onAccountReady: onReadyEvt.reg,
onDisconnect: onDisconnectEvt.reg,
onReconnect: onReconnectEvt.reg
};
};
const Account: Account = {
setCustomize: (data) => {
ApiConfig = data.ApiConfig;
},
init: init
};
export { Account }

View File

@ -2,6 +2,8 @@ import nacl from 'tweetnacl/nacl-fast';
import { Module } from './types';
import { TestModule, TestModuleObject } from './modules/test';
import { Account } from './components/account';
// Manage all imports in order to provide them ApiConfig, AppConfig, etc.
// Common
@ -52,7 +54,6 @@ interface StoreConfig {
Messages: any
}
let start = (cfg: StoreConfig):void => {
// Provide custom data to the modules
[
@ -68,7 +69,8 @@ let start = (cfg: StoreConfig):void => {
Cursor,
Support,
Calendar,
Store
Store,
Account
].forEach(dep => {
if (typeof(dep.setCustomize) === "function") {
dep.setCustomize(cfg);

View File

@ -1,6 +1,6 @@
// Default CryptPad worker module, extended with
// specific methods for each module
type Callback = (...args: any[]) => void
export type Callback = (...args: any[]) => void
export type ModuleConfig = {
emit: Function
@ -20,3 +20,26 @@ export interface Module<T> {
init: (config: ModuleConfig, cb: Callback) => T
}
export type AccountConfig = {
anonHash: string,
userHash: string,
store: any,
cache: boolean,
form_seed: string,
broadcast: (exclude: object, cmd: string, data?: any, cb?: any) => void,
postMessage: (clientId: string, cmd: string, data?: any, cb?: any) => void
}
export interface AccountObject {
channel: string,
onAccountCacheReady: any,
onAccountReady: any,
onDisconnect: any,
onReconnect: any
}
export interface Account {
init: (config: AccountConfig) => AccountObject,
setCustomize: Callback
}