@jsxc/jsxc
Version:
Real-time XMPP chat application with video calls, file transfer and encrypted communication
194 lines (149 loc) • 6.09 kB
text/typescript
import Account from '../../Account';
import PersistentMap from '../../util/PersistentMap';
import Log from '../../util/Log';
import JID from '../../JID';
import * as ConnectHelper from './ConnectHelper';
import StorageConnection from '../storage/Connection';
import XMPPConnection from './Connection';
import { Strophe } from '../../vendor/Strophe';
import BaseError from '../../errors/BaseError';
export enum TYPE {
BOSH,
WEBSOCKET,
}
export default class Connector {
private connectionParameters;
private connectionArgs: string[];
constructor(account: Account, url: string, jid: string, sid: string, rid: string);
constructor(account: Account, url: string, jid: string, password: string);
constructor(account: Account);
constructor(private account: Account, ...connectionArgs: string[]) {
let storage = account.getStorage();
this.connectionParameters = new PersistentMap(storage, 'connection');
connectionArgs = connectionArgs.filter(arg => typeof arg === 'string');
if (connectionArgs.length < 3) {
let type = this.connectionParameters.get('type');
if (type === TYPE.WEBSOCKET) {
throw new Error('Cannt attach to websocket connection.');
}
this.connectionArgs = [
this.connectionParameters.get('url'),
this.connectionParameters.get('jid'),
this.connectionParameters.get('sid'),
this.connectionParameters.get('rid'),
];
} else if (connectionArgs.length === 3 || connectionArgs.length === 4) {
this.connectionArgs = connectionArgs;
let type = /^wss?:/.test(connectionArgs[1]) ? TYPE.WEBSOCKET : TYPE.BOSH;
this.connectionParameters.set('type', type);
this.connectionParameters.remove('inactivity');
this.connectionParameters.remove('timestamp');
} else {
throw new BaseError('Unsupported number of arguments');
}
}
public connect() {
let inactivity = this.connectionParameters.get('inactivity');
let timestamp = this.connectionParameters.get('timestamp');
let isConnectionExpired = inactivity && timestamp && new Date().getTime() - timestamp > inactivity;
if (isConnectionExpired) {
Log.debug(
`Inactivity: ${inactivity}, Last timestamp: ${timestamp}, Time diff: ${new Date().getTime() - timestamp}`
);
Log.warn('Credentials expired');
this.account.triggerConnectionHook(Strophe.Status.CONNTIMEOUT);
this.account.triggerConnectionHook(Strophe.Status.DISCONNECTED, 'timeout');
throw new BaseError('Credentials expired');
}
return ConnectHelper.login.apply(this, this.connectionArgs).then(this.successfulConnected);
}
public getJID(): JID {
return new JID(this.connectionParameters.get('jid'));
}
public getUrl(): string {
return this.connectionParameters.get('url');
}
public getPassword(): string {
if (this.connectionArgs.length === 3) {
return this.connectionArgs[2];
}
}
public clearPassword() {
if (this.connectionArgs.length === 3) {
delete this.connectionArgs[2];
}
}
private successfulConnected = data => {
let stropheConnection = data.connection;
let status = data.status;
let condition = data.condition;
this.storeConnectionParameters(stropheConnection);
this.replaceConnectionHandler(stropheConnection);
this.addRidHandler(stropheConnection);
this.addRidUnloadHandler(stropheConnection);
let accountConnection = this.replaceStorageConnectionWithXMPPConnection(stropheConnection);
if (stropheConnection.features) {
this.storeConnectionFeatures(stropheConnection);
}
Log.debug('XMPP connection ready');
this.account.triggerConnectionHook(status, condition);
return [status, accountConnection];
};
private storeConnectionParameters(connection) {
this.connectionParameters.set({
url: connection.service,
jid: connection.jid,
sid: connection._proto.sid,
rid: connection._proto.rid,
timestamp: new Date().getTime(),
});
if (connection._proto.inactivity) {
let inactivity = connection._proto.inactivity * 1000;
this.connectionParameters.set('inactivity', inactivity);
}
}
private replaceConnectionHandler(connection) {
connection.connect_callback = (status, condition) => {
this.account.triggerConnectionHook(status, condition);
if (status === Strophe.Status.DISCONNECTED) {
this.account.connectionDisconnected();
}
};
}
private addRidHandler(connection) {
connection.nextValidRid = rid => {
let timestamp = new Date().getTime();
this.connectionParameters.set('timestamp', timestamp);
this.connectionParameters.set('rid', rid);
};
}
private addRidUnloadHandler(connection) {
$(window).on('unload', () => {
connection.nextValidRid(connection._proto.rid);
});
}
private replaceStorageConnectionWithXMPPConnection(stropheConnection) {
let accountConnection = this.account.getConnection();
let handlers = (<StorageConnection>accountConnection).getHandlers();
accountConnection.close();
accountConnection = new XMPPConnection(this.account, stropheConnection);
for (let handler of handlers) {
accountConnection.registerHandler.apply(accountConnection, handler);
}
return accountConnection;
}
private storeConnectionFeatures(connection) {
let from = new JID('', connection.domain, '');
let stanza = connection.features;
if (!stanza) {
return;
}
let capsElement = stanza.querySelector('c');
if (!capsElement) {
return;
}
let ver = capsElement.getAttribute('ver');
let discoInfoRepository = this.account.getDiscoInfoRepository();
discoInfoRepository.addRelation(from, ver);
}
}