TeaWeb/shared/js/connection/ConnectionBase.ts

190 lines
6.6 KiB
TypeScript
Raw Normal View History

2019-02-23 13:15:22 +00:00
namespace connection {
export interface CommandOptions {
flagset?: string[]; /* default: [] */
process_result?: boolean; /* default: true */
timeout?: number /* default: 1000 */;
}
export const CommandOptionDefaults: CommandOptions = {
flagset: [],
process_result: true,
timeout: 1000
};
2019-04-04 19:47:52 +00:00
export type ConnectionStateListener = (old_state: ConnectionState, new_state: ConnectionState) => any;
2019-02-23 13:15:22 +00:00
export abstract class AbstractServerConnection {
2019-04-04 19:47:52 +00:00
readonly client: ConnectionHandler;
2019-02-23 13:15:22 +00:00
readonly command_helper: CommandHelper;
2019-04-04 19:47:52 +00:00
protected constructor(client: ConnectionHandler) {
2019-02-23 13:15:22 +00:00
this.client = client;
this.command_helper = new CommandHelper(this);
}
/* resolved as soon a connection has been established. This does not means that the authentication had yet been done! */
abstract connect(address: ServerAddress, handshake: HandshakeHandler, timeout?: number) : Promise<void>;
abstract connected() : boolean;
abstract disconnect(reason?: string) : Promise<void>;
abstract support_voice() : boolean;
2019-03-07 14:30:53 +00:00
abstract voice_connection() : voice.AbstractVoiceConnection | undefined;
2019-02-23 13:15:22 +00:00
abstract command_handler_boss() : AbstractCommandHandlerBoss;
abstract send_command(command: string, data?: any | any[], options?: CommandOptions) : Promise<CommandResult>;
2019-04-04 19:47:52 +00:00
abstract get onconnectionstatechanged() : ConnectionStateListener;
abstract set onconnectionstatechanged(listener: ConnectionStateListener);
2019-04-15 13:33:51 +00:00
abstract remote_address() : ServerAddress; /* only valid when connected */
abstract handshake_handler() : HandshakeHandler; /* only valid when connected */
2019-02-23 13:15:22 +00:00
}
2019-03-07 14:30:53 +00:00
export namespace voice {
2019-04-04 19:47:52 +00:00
export enum PlayerState {
PREBUFFERING,
PLAYING,
BUFFERING,
STOPPING,
STOPPED
}
2019-03-07 14:30:53 +00:00
export interface VoiceClient {
client_id: number;
2019-02-23 13:15:22 +00:00
2019-03-07 14:30:53 +00:00
callback_playback: () => any;
callback_stopped: () => any;
2019-04-04 19:47:52 +00:00
callback_state_changed: (new_state: PlayerState) => any;
get_state() : PlayerState;
2019-03-07 14:30:53 +00:00
get_volume() : number;
2019-04-04 19:47:52 +00:00
set_volume(volume: number) : void;
abort_replay();
2019-02-23 13:15:22 +00:00
}
2019-03-07 14:30:53 +00:00
export abstract class AbstractVoiceConnection {
readonly connection: AbstractServerConnection;
protected constructor(connection: AbstractServerConnection) {
this.connection = connection;
}
abstract connected() : boolean;
2019-04-04 19:47:52 +00:00
abstract encoding_supported(codec: number) : boolean;
abstract decoding_supported(codec: number) : boolean;
2019-03-07 14:30:53 +00:00
abstract register_client(client_id: number) : VoiceClient;
2019-04-04 19:47:52 +00:00
abstract available_clients() : VoiceClient[];
2019-03-07 14:30:53 +00:00
abstract unregister_client(client: VoiceClient) : Promise<void>;
2019-04-04 19:47:52 +00:00
abstract voice_recorder() : VoiceRecorder;
abstract acquire_voice_recorder(recorder: VoiceRecorder | undefined);
2019-03-07 14:30:53 +00:00
}
2019-02-23 13:15:22 +00:00
}
export class ServerCommand {
command: string;
arguments: any[];
}
export abstract class AbstractCommandHandler {
readonly connection: AbstractServerConnection;
handler_boss: AbstractCommandHandlerBoss | undefined;
volatile_handler_boss: boolean = false; /* if true than the command handler could be registered twice to two or more handlers */
ignore_consumed: boolean = false;
protected constructor(connection: AbstractServerConnection) {
this.connection = connection;
}
/**
* @return If the command should be consumed
*/
abstract handle_command(command: ServerCommand) : boolean;
}
export interface SingleCommandHandler {
name?: string;
command?: string;
timeout?: number;
/* if the return is true then the command handler will be removed */
function: (command: ServerCommand) => boolean;
}
export abstract class AbstractCommandHandlerBoss {
readonly connection: AbstractServerConnection;
protected command_handlers: AbstractCommandHandler[] = [];
/* TODO: Timeout */
protected single_command_handler: SingleCommandHandler[] = [];
protected constructor(connection: AbstractServerConnection) {
this.connection = connection;
}
register_handler(handler: AbstractCommandHandler) {
if(!handler.volatile_handler_boss && handler.handler_boss)
throw "handler already registered";
this.command_handlers.remove(handler); /* just to be sure */
this.command_handlers.push(handler);
handler.handler_boss = this;
}
unregister_handler(handler: AbstractCommandHandler) {
if(!handler.volatile_handler_boss && handler.handler_boss !== this) {
console.warn(tr("Tried to unregister command handler which does not belong to the handler boss"));
return;
}
this.command_handlers.remove(handler);
handler.handler_boss = undefined;
}
register_single_handler(handler: SingleCommandHandler) {
this.single_command_handler.push(handler);
}
remove_single_handler(handler: SingleCommandHandler) {
this.single_command_handler.remove(handler);
}
handlers() : AbstractCommandHandler[] {
return this.command_handlers;
}
invoke_handle(command: ServerCommand) : boolean {
let flag_consumed = false;
for(const handler of this.command_handlers) {
try {
if(!flag_consumed || handler.ignore_consumed)
flag_consumed = flag_consumed || handler.handle_command(command);
} catch(error) {
console.error(tr("Failed to invoke command handler. Invocation results in an exception: %o"), error);
}
}
for(const handler of [...this.single_command_handler]) {
if(handler.command && handler.command != command.command)
continue;
try {
if(handler.function(command))
this.single_command_handler.remove(handler);
} catch(error) {
console.error(tr("Failed to invoke single command handler. Invocation results in an exception: %o"), error);
}
}
return flag_consumed;
}
}
}