diff --git a/ts/interfaces/connection.ts b/ts/interfaces/connection.ts new file mode 100644 index 0000000..c8ffbf7 --- /dev/null +++ b/ts/interfaces/connection.ts @@ -0,0 +1,3 @@ +export interface IRequestAuthPayload { + serverShortId: string; +} \ No newline at end of file diff --git a/ts/interfaces/index.ts b/ts/interfaces/index.ts new file mode 100644 index 0000000..0fb3261 --- /dev/null +++ b/ts/interfaces/index.ts @@ -0,0 +1 @@ +export * from './connection'; diff --git a/ts/smartsocket.classes.smartsocket.ts b/ts/smartsocket.classes.smartsocket.ts index e9f418c..11158d2 100644 --- a/ts/smartsocket.classes.smartsocket.ts +++ b/ts/smartsocket.classes.smartsocket.ts @@ -19,7 +19,7 @@ export class Smartsocket { /** * a unique id to detect server restarts */ - public id = plugins.smartunique.shortId(); + public shortId = plugins.smartunique.shortId(); public options: ISmartsocketConstructorOptions; public io: SocketIO.Server; public socketConnections = new Objectmap(); diff --git a/ts/smartsocket.classes.smartsocketclient.ts b/ts/smartsocket.classes.smartsocketclient.ts index 7d2ba04..f182328 100644 --- a/ts/smartsocket.classes.smartsocketclient.ts +++ b/ts/smartsocket.classes.smartsocketclient.ts @@ -1,9 +1,11 @@ import * as plugins from './smartsocket.plugins'; +import * as interfaces from './interfaces'; import { SocketConnection } from './smartsocket.classes.socketconnection'; import { ISocketFunctionCallDataRequest, SocketFunction } from './smartsocket.classes.socketfunction'; import { ISocketRequestDataObject, SocketRequest } from './smartsocket.classes.socketrequest'; import { SocketRole } from './smartsocket.classes.socketrole'; +import { defaultLogger } from '@pushrocks/smartlog'; /** * interface for class SmartsocketClient @@ -14,16 +16,24 @@ export interface ISmartsocketClientOptions { alias: string; // an alias makes it easier to identify this client in a multo client environment role: string; password: string; // by setting a password access to functions can be limited + autoReconnect?: boolean; } export class SmartsocketClient { + // a unique id + public shortId = plugins.smartunique.shortId(); + + // the shortId of the remote we connect to + public remoteShortId: string = null; + public alias: string; public socketRole: SocketRole; public socketConnection: SocketConnection; public serverUrl: string; public serverPort: number; + public autoReconnect: boolean; - // public eventSubject = new plugins.smart + public eventSubject = new plugins.smartrx.rxjs.Subject(); public socketFunctions = new plugins.lik.Objectmap>(); public socketRequests = new plugins.lik.Objectmap>(); @@ -37,6 +47,7 @@ export class SmartsocketClient { name: optionsArg.role, passwordHash: optionsArg.password }); + this.autoReconnect = optionsArg.autoReconnect; } public addSocketFunction(socketFunction: SocketFunction) { @@ -62,27 +73,53 @@ export class SmartsocketClient { reconnectionAttempts: 5, }) }); - this.socketConnection.socket.on('requestAuth', () => { + + const timer = new plugins.smarttime.Timer(5000); + timer.start(); + timer.completed.then(() => { + defaultLogger.log('warn', 'connection to server timed out.'); + this.disconnect(); + }); + + // authentication flow + this.socketConnection.socket.on('requestAuth', (requestAuthPayload: interfaces.IRequestAuthPayload) => { + timer.reset(); plugins.smartlog.defaultLogger.log('info', 'server requested authentication'); - this.socketConnection.socket.emit('dataAuth', { - role: this.socketRole.name, - password: this.socketRole.passwordHash, - alias: this.alias - }); + + // lets register the authenticated event this.socketConnection.socket.on('authenticated', () => { + this.remoteShortId = requestAuthPayload.serverShortId; plugins.smartlog.defaultLogger.log('info', 'client is authenticated'); this.socketConnection.authenticated = true; this.socketConnection.listenToFunctionRequests(); done.resolve(); }); - // handle errors - this.socketConnection.socket.on('reconnect_failed', async () => { - this.disconnect(); + // lets register the forbidden event + this.socketConnection.socket.on('forbidden', async () => { + defaultLogger.log('warn', `disconnecting due to being forbidden to use the ressource`); + await this.disconnect(); }); - this.socketConnection.socket.on('connect_error', async () => { - this.disconnect(); + + // lets provide the actual auth data + this.socketConnection.socket.emit('dataAuth', { + role: this.socketRole.name, + password: this.socketRole.passwordHash, + alias: this.alias }); + + }); + + // handle disconnection and errors + this.socketConnection.socket.on('disconnect', async () => { + await this.disconnect(); + }); + + this.socketConnection.socket.on('reconnect_failed', async () => { + await this.disconnect(); + }); + this.socketConnection.socket.on('connect_error', async () => { + await this.disconnect(); }); return done.promise; } @@ -92,10 +129,16 @@ export class SmartsocketClient { */ public async disconnect() { if (this.socketConnection) { - this.socketConnection.socket.disconnect(true); + this.socketConnection.disconnect(); this.socketConnection = undefined; plugins.smartlog.defaultLogger.log('ok', 'disconnected!'); } + defaultLogger.log('warn', `disconnected from server ${this.remoteShortId}`); + this.remoteShortId = null; + + if (this.autoReconnect) { + this.tryDebouncedReconnect(); + } } /** diff --git a/ts/smartsocket.classes.socketconnection.ts b/ts/smartsocket.classes.socketconnection.ts index 739ae44..28820f1 100644 --- a/ts/smartsocket.classes.socketconnection.ts +++ b/ts/smartsocket.classes.socketconnection.ts @@ -1,4 +1,5 @@ import * as plugins from './smartsocket.plugins'; +import * as interfaces from './interfaces'; import { Objectmap } from '@pushrocks/lik'; @@ -53,6 +54,9 @@ export class SocketConnection { public role: SocketRole; public smartsocketRef: Smartsocket | SmartsocketClient; public socket: SocketIO.Socket | SocketIOClient.Socket; + + public eventSubject = new plugins.smartrx.rxjs.Subject(); + constructor(optionsArg: ISocketConnectionConstructorOptions) { this.alias = optionsArg.alias; this.authenticated = optionsArg.authenticated; @@ -80,7 +84,7 @@ export class SocketConnection { */ public authenticate() { const done = plugins.smartpromise.defer(); - this.socket.on('dataAuth', (dataArg: ISocketConnectionAuthenticationObject) => { + this.socket.on('dataAuth', async (dataArg: ISocketConnectionAuthenticationObject) => { plugins.smartlog.defaultLogger.log( 'info', 'received authentication data. now hashing and comparing...' @@ -99,11 +103,14 @@ export class SocketConnection { done.resolve(this); } else { this.authenticated = false; - this.socket.disconnect(); + await this.disconnect(); done.reject('not authenticated'); } }); - this.socket.emit('requestAuth'); + const requestAuthPayload: interfaces.IRequestAuthPayload = { + serverShortId: this.smartsocketRef.shortId + }; + this.socket.emit('requestAuth', requestAuthPayload); return done.promise; } @@ -163,5 +170,8 @@ export class SocketConnection { return done.promise; } - // sending ---------------------- + // disconnecting ---------------------- + public async disconnect() { + this.socket.disconnect(true); + } } diff --git a/ts/smartsocket.plugins.ts b/ts/smartsocket.plugins.ts index 6027df4..f7ee5aa 100644 --- a/ts/smartsocket.plugins.ts +++ b/ts/smartsocket.plugins.ts @@ -10,7 +10,9 @@ import * as smarthash from '@pushrocks/smarthash'; import * as smartdelay from '@pushrocks/smartdelay'; import * as smartexpress from '@pushrocks/smartexpress'; import * as smartpromise from '@pushrocks/smartpromise'; +import * as smarttime from '@pushrocks/smarttime'; import * as smartunique from '@pushrocks/smartunique'; +import * as smartrx from '@pushrocks/smartrx'; export { @@ -20,7 +22,9 @@ export { smartdelay, smartexpress, smartpromise, + smarttime, smartunique, + smartrx }; // third party scope