import { ICodec, IConnection, IMessageComposer, IMessageConfiguration, IMessageDataWrapper, IMessageEvent, WebSocketEventEnum } from '@nitrots/api'; import { GetEventDispatcher, NitroEvent, NitroEventType } from '@nitrots/events'; import { NitroLogger } from '@nitrots/utils'; import { EvaWireFormat } from './codec'; import { MessageClassManager } from './messages'; export class SocketConnection implements IConnection { private _socket: WebSocket = null; private _messages: MessageClassManager = new MessageClassManager(); private _codec: ICodec = new EvaWireFormat(); private _dataBuffer: ArrayBuffer = null; private _isReady: boolean = false; private _pendingClientMessages: IMessageComposer[] = []; private _pendingServerMessages: IMessageDataWrapper[] = []; private _isAuthenticated: boolean = false; public init(socketUrl: string): void { if(!socketUrl || !socketUrl.length) return; this._dataBuffer = new ArrayBuffer(0); this._socket = new WebSocket(socketUrl); this._socket.binaryType = 'arraybuffer'; this._socket.addEventListener(WebSocketEventEnum.CONNECTION_OPENED, event => GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOCKET_OPENED))); this._socket.addEventListener(WebSocketEventEnum.CONNECTION_CLOSED, event => GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOCKET_CLOSED))); this._socket.addEventListener(WebSocketEventEnum.CONNECTION_ERROR, event => GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOCKET_ERROR))); this._socket.addEventListener(WebSocketEventEnum.CONNECTION_MESSAGE, (event: MessageEvent) => { this._dataBuffer = this.concatArrayBuffers(this._dataBuffer, event.data); this.processReceivedData(); }); } public ready(): void { if(this._isReady) return; this._isReady = true; if(this._pendingServerMessages && this._pendingServerMessages.length) this.processWrappers(...this._pendingServerMessages); if(this._pendingClientMessages && this._pendingClientMessages.length) this.send(...this._pendingClientMessages); this._pendingServerMessages = []; this._pendingClientMessages = []; } public authenticated(): void { this._isAuthenticated = true; } public send(...composers: IMessageComposer[]): boolean { if(!composers) return false; composers = [...composers]; if(this._isAuthenticated && !this._isReady) { this._pendingClientMessages.push(...composers); return false; } for(const composer of composers) { if(!composer) continue; const header = this._messages.getComposerId(composer); if(header === -1) { NitroLogger.packets('Unknown Composer', composer.constructor.name); continue; } const message = composer.getMessageArray(); const encoded = this._codec.encode(header, message); if(!encoded) { NitroLogger.packets('Encoding Failed', composer.constructor.name); continue; } NitroLogger.packets('OutgoingComposer', header, composer.constructor.name, message); this.write(encoded.getBuffer()); } return true; } private write(buffer: ArrayBuffer): void { if(this._socket.readyState !== WebSocket.OPEN) return; this._socket.send(buffer); } public processReceivedData(): void { try { this.processData(); } catch (err) { NitroLogger.error(err); } } private processData(): void { const wrappers = this.splitReceivedMessages(); if(!wrappers || !wrappers.length) return; if(this._isAuthenticated && !this._isReady) { if(!this._pendingServerMessages) this._pendingServerMessages = []; this._pendingServerMessages.push(...wrappers); return; } this.processWrappers(...wrappers); } private processWrappers(...wrappers: IMessageDataWrapper[]): void { if(!wrappers || !wrappers.length) return; for(const wrapper of wrappers) { if(!wrapper) continue; const messages = this.getMessagesForWrapper(wrapper); if(!messages || !messages.length) continue; NitroLogger.packets('IncomingMessage', wrapper.header, messages[0].constructor.name, messages[0].parser); this.handleMessages(...messages); } } private splitReceivedMessages(): IMessageDataWrapper[] { if(!this._dataBuffer || !this._dataBuffer.byteLength) return null; return this._codec.decode(this); } private concatArrayBuffers(buffer1: ArrayBuffer, buffer2: ArrayBuffer): ArrayBuffer { const array = new Uint8Array(buffer1.byteLength + buffer2.byteLength); array.set(new Uint8Array(buffer1), 0); array.set(new Uint8Array(buffer2), buffer1.byteLength); return array.buffer; } private getMessagesForWrapper(wrapper: IMessageDataWrapper): IMessageEvent[] { if(!wrapper) return null; const events = this._messages.getEvents(wrapper.header); if(!events || !events.length) { NitroLogger.packets('IncomingMessage', wrapper.header, 'UNREGISTERED', wrapper); return null; } try { //@ts-ignore const parser = new events[0].parserClass(); if(!parser || !parser.flush() || !parser.parse(wrapper)) return null; for(const event of events) (event.parser = parser); } catch (e) { NitroLogger.error('Error parsing message', e, events[0].constructor.name); return null; } return events; } private handleMessages(...messages: IMessageEvent[]): void { messages = [...messages]; for(const message of messages) { if(!message) continue; message.connection = this; if(message.callBack) message.callBack(message); } } public registerMessages(configuration: IMessageConfiguration): void { if(!configuration) return; this._messages.registerMessages(configuration); } public addMessageEvent(event: IMessageEvent): void { if(!event || !this._messages) return; this._messages.registerMessageEvent(event); } public removeMessageEvent(event: IMessageEvent): void { if(!event || !this._messages) return; this._messages.removeMessageEvent(event); } public get isAuthenticated(): boolean { return this._isAuthenticated; } public get dataBuffer(): ArrayBuffer { return this._dataBuffer; } public set dataBuffer(buffer: ArrayBuffer) { this._dataBuffer = buffer; } }