Files
Nitro_Render_V3/packages/communication/src/CommunicationManager.ts
T

211 lines
7.1 KiB
TypeScript

import { ICommunicationManager, IConnection, IMessageConfiguration, IMessageEvent } from '@nitrots/api';
import { GetConfiguration } from '@nitrots/configuration';
import { GetEventDispatcher, NitroEvent, NitroEventType } from '@nitrots/events';
import { GetTickerTime, NitroLogger } from '@nitrots/utils';
import { NitroMessages } from './NitroMessages';
import { SocketConnection } from './SocketConnection';
import { AuthenticatedEvent, ClientHelloMessageComposer, ClientPingEvent, InfoRetrieveMessageComposer, PongMessageComposer, SSOTicketMessageComposer, UniqueIDMessageComposer } from './messages';
import { Thumbmark } from '@thumbmarkjs/thumbmarkjs';
export class CommunicationManager implements ICommunicationManager
{
private _connection: IConnection = new SocketConnection();
private _messages: IMessageConfiguration = new NitroMessages();
private _pongInterval: any = null;
private _messageEvents: IMessageEvent[] = [];
private _socketClosedCallback: () => void = null;
private _socketOpenedCallback: () => void = null;
private _socketErrorCallback: () => void = null;
private _socketReconnectedCallback: () => void = null;
private _machineIdPromise: Promise<string> = null;
private _initResolved: boolean = false;
private async generateMachineID(): Promise<string>
{
try
{
const result = await new Thumbmark().get();
return result.thumbmark ? `IID-${result.thumbmark}` : 'FAILED';
}
catch(error)
{
NitroLogger.warn('[CommunicationManager] Failed to generate machine ID', error);
return 'FAILED';
}
}
private async sendHandshake(): Promise<void>
{
if(!this._machineIdPromise) this._machineIdPromise = this.generateMachineID();
const machineId = await this._machineIdPromise;
this._connection.send(new ClientHelloMessageComposer(null, null, null, null));
this._connection.send(new SSOTicketMessageComposer(GetConfiguration().getValue('sso.ticket', null), GetTickerTime()));
this._connection.send(new UniqueIDMessageComposer(machineId, '', ''));
}
constructor()
{
this._connection.registerMessages(this._messages);
}
public async init(): Promise<void>
{
// Store callback for cleanup
this._socketClosedCallback = () =>
{
this.stopPong();
};
GetEventDispatcher().addEventListener(NitroEventType.SOCKET_CLOSED, this._socketClosedCallback);
// Handle reconnection - re-authenticate when socket reconnects
this._socketReconnectedCallback = () =>
{
NitroLogger.log('[CommunicationManager] Socket reconnected, re-authenticating...');
if(GetConfiguration().getValue<boolean>('system.pong.manually', false)) this.startPong();
void this.sendHandshake();
};
GetEventDispatcher().addEventListener(NitroEventType.SOCKET_RECONNECTED, this._socketReconnectedCallback);
return new Promise((resolve, reject) =>
{
// Store callback for cleanup
this._socketOpenedCallback = () =>
{
if(GetConfiguration().getValue<boolean>('system.pong.manually', false)) this.startPong();
void this.sendHandshake();
};
GetEventDispatcher().addEventListener(NitroEventType.SOCKET_OPENED, this._socketOpenedCallback);
// Store callback for cleanup
this._socketErrorCallback = () =>
{
if(!this._initResolved) reject();
};
GetEventDispatcher().addEventListener(NitroEventType.SOCKET_ERROR, this._socketErrorCallback);
// Store message events for cleanup
const pingEvent = new ClientPingEvent((event: ClientPingEvent) => this.sendPong());
const authEvent = new AuthenticatedEvent((event: AuthenticatedEvent) =>
{
const isReconnect = this._initResolved;
NitroLogger.log('[CommunicationManager] AuthenticatedEvent received (isReconnect=' + isReconnect + ')');
this._connection.authenticated();
if(!this._initResolved)
{
this._initResolved = true;
resolve();
}
if(isReconnect)
{
this._connection.ready();
}
event.connection.send(new InfoRetrieveMessageComposer());
if(isReconnect)
{
NitroLogger.log('[CommunicationManager] Dispatching SOCKET_REAUTHENTICATED');
GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOCKET_REAUTHENTICATED));
}
});
this._messageEvents.push(pingEvent, authEvent);
this._connection.addMessageEvent(pingEvent);
this._connection.addMessageEvent(authEvent);
this._connection.init(GetConfiguration().getValue<string>('socket.url'));
});
}
public dispose(): void
{
// Stop pong interval
this.stopPong();
// Remove event dispatcher listeners
if(this._socketClosedCallback)
{
GetEventDispatcher().removeEventListener(NitroEventType.SOCKET_CLOSED, this._socketClosedCallback);
this._socketClosedCallback = null;
}
if(this._socketOpenedCallback)
{
GetEventDispatcher().removeEventListener(NitroEventType.SOCKET_OPENED, this._socketOpenedCallback);
this._socketOpenedCallback = null;
}
if(this._socketErrorCallback)
{
GetEventDispatcher().removeEventListener(NitroEventType.SOCKET_ERROR, this._socketErrorCallback);
this._socketErrorCallback = null;
}
if(this._socketReconnectedCallback)
{
GetEventDispatcher().removeEventListener(NitroEventType.SOCKET_RECONNECTED, this._socketReconnectedCallback);
this._socketReconnectedCallback = null;
}
// Remove message events
for(const event of this._messageEvents)
{
this._connection.removeMessageEvent(event);
}
this._messageEvents = [];
}
protected startPong(): void
{
if(this._pongInterval) this.stopPong();
this._pongInterval = setInterval(() => this.sendPong(), GetConfiguration().getValue<number>('system.pong.interval.ms', 20000));
}
protected stopPong(): void
{
if(!this._pongInterval) return;
clearInterval(this._pongInterval);
this._pongInterval = null;
}
protected sendPong(): void
{
this._connection?.send(new PongMessageComposer());
}
public registerMessageEvent(event: IMessageEvent): IMessageEvent
{
if(this._connection) this._connection.addMessageEvent(event);
return event;
}
public removeMessageEvent(event: IMessageEvent): void
{
if(!this._connection) return;
this._connection.removeMessageEvent(event);
}
public get connection(): IConnection
{
return this._connection;
}
}