165 lines
6.2 KiB
TypeScript
165 lines
6.2 KiB
TypeScript
// Main interface with the servers
|
|
|
|
import * as electronRemote from '@electron/remote';
|
|
const electronConsole = electronRemote.getGlobal('console') as Console;
|
|
import Logger from '../../logger/logger';
|
|
const LOG = new Logger(__filename, electronConsole);
|
|
|
|
import { EventEmitter } from "stream";
|
|
|
|
import ClientController from "./client-controller";
|
|
|
|
import socketio, { Socket } from 'socket.io-client';
|
|
|
|
import * as crypto from 'crypto';
|
|
|
|
import DBCache from './db-cache';
|
|
import { ServerConfig } from './data-types';
|
|
import { IAddServerData } from './elements/overlay-add-server';
|
|
|
|
export default class Controller extends EventEmitter {
|
|
public servers: ClientController[] = [];
|
|
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
async _connectFromConfig(serverConfig: ServerConfig): Promise<ClientController> {
|
|
LOG.debug(`connecting to server#${serverConfig.serverId} at ${serverConfig.url}`);
|
|
|
|
let server = new ClientController(serverConfig);
|
|
|
|
await DBCache.clearAllMemberStatus(server.id);
|
|
|
|
this.servers.push(server);
|
|
|
|
// Forward server events through this event emitter
|
|
let serverEvents = [
|
|
'connected',
|
|
'disconnected',
|
|
'verified',
|
|
'new-message',
|
|
'update-server',
|
|
'deleted-members',
|
|
'updated-members',
|
|
'added-members',
|
|
'deleted-channels',
|
|
'updated-channels',
|
|
'added-channels',
|
|
'deleted-messages',
|
|
'updated-messages',
|
|
'added-messages',
|
|
];
|
|
for (let event of serverEvents) {
|
|
server.on(event, (...args) => {
|
|
this.emit(event, server, ...args);
|
|
});
|
|
}
|
|
return server;
|
|
}
|
|
|
|
async init(): Promise<void> {
|
|
this.servers = [];
|
|
|
|
let serverConfigs = await DBCache.getServerConfigs();
|
|
|
|
// TODO: HTML prompt if no server configs
|
|
if (serverConfigs.length == 0) {
|
|
LOG.warn('no server configs found in client-side db');
|
|
}
|
|
|
|
for (let serverConfig of serverConfigs) {
|
|
await this._connectFromConfig(serverConfig);
|
|
}
|
|
}
|
|
|
|
static _socketEmitTimeout(socket: Socket, ms: number, name: string, ...args: any[]) { // see also client-controller.js
|
|
let socketArgs = args.slice(0, args.length - 1);
|
|
let respond = args[args.length - 1];
|
|
|
|
let cutoff = false;
|
|
let timeout = setTimeout(() => {
|
|
cutoff = true;
|
|
respond('emit timeout');
|
|
}, ms);
|
|
|
|
socket.emit(name, ...socketArgs, (...respondArgs: any[]) => {
|
|
if (cutoff) {
|
|
return;
|
|
}
|
|
clearTimeout(timeout);
|
|
respond(...respondArgs);
|
|
});
|
|
}
|
|
|
|
async addNewServer(serverConfig: IAddServerData, displayName: string, avatarBuff: Buffer): Promise<ClientController> {
|
|
const { name, url, cert, token } = serverConfig;
|
|
LOG.debug('Adding new server', { name, url, cert, token, displayName, avatarBuff });
|
|
|
|
|
|
let socket = await new Promise<Socket>((resolve, reject) => {
|
|
let socket = socketio(url, {
|
|
forceNew: true,
|
|
ca: cert, // verifies the server's identity
|
|
reconnection: false, // a bit stricter for registration
|
|
});
|
|
socket.on('connect_error', (e) => {
|
|
LOG.error('connect error', e);
|
|
reject(new Error('unable to connect to server'));
|
|
});
|
|
socket.on('connect', () => {
|
|
resolve(socket);
|
|
});
|
|
});
|
|
try {
|
|
// Create a new Public/Private key pair to identify ourselves with this server
|
|
let { publicKey, privateKey } = await new Promise((resolve, reject) => {
|
|
crypto.generateKeyPair('rsa', { modulusLength: 4096 }, (err, publicKey, privateKey) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve({ publicKey, privateKey });
|
|
}
|
|
});
|
|
});
|
|
|
|
return await new Promise((resolve, reject) => {
|
|
let clientPublicKeyDerBuff = publicKey.export({ type: 'spki', format: 'der' });
|
|
Controller._socketEmitTimeout(socket, 5000, 'register-with-token',
|
|
token, clientPublicKeyDerBuff, displayName, avatarBuff, async (errStr, member) => {
|
|
if (errStr) {
|
|
reject(new Error(errStr));
|
|
} else {
|
|
try {
|
|
let serverConfig: ServerConfig | null = null;
|
|
await DBCache.queueTransaction(async () => {
|
|
let publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' });
|
|
let privateKeyPem = privateKey.export({ type: 'pkcs8', format: 'pem' });
|
|
let identityId = await DBCache.addIdentity(publicKeyPem, privateKeyPem);
|
|
let serverId = await DBCache.addServer(url, cert, name);
|
|
await DBCache.addServerIdentity(serverId, identityId);
|
|
serverConfig = await DBCache.getServerConfig(serverId, identityId);
|
|
});
|
|
if (serverConfig == null) {
|
|
throw new Error('unable to get server config');
|
|
}
|
|
let server = await this._connectFromConfig(serverConfig);
|
|
resolve(server);
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
}
|
|
}
|
|
);
|
|
});
|
|
} finally {
|
|
socket.disconnect();
|
|
}
|
|
}
|
|
|
|
async removeServer(server: ClientController): Promise<void> {
|
|
await DBCache.removeServer(server.id);
|
|
this.servers = this.servers.filter(s => s != server);
|
|
}
|
|
}
|