Starting with webpack
parent
a6f0fcdda9
commit
13b65a1f35
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
|
@ -17,29 +17,41 @@
|
|||
"csso": "csso",
|
||||
"rebuild-structure-web-dev": "php files.php generate web dev",
|
||||
"minify-web-rel-file": "terser --compress --mangle --ecma 6 --keep_classnames --keep_fnames --output",
|
||||
"start": "npm run compile-file-helper && node file.js ndevelop"
|
||||
"start": "npm run compile-file-helper && node file.js ndevelop",
|
||||
"build": "webpack --config webpack.config.js",
|
||||
"watch": "webpack --watch"
|
||||
},
|
||||
"author": "TeaSpeak (WolverinDEV)",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/emscripten": "^1.38.0",
|
||||
"@types/jquery": "^3.3.31",
|
||||
"@types/jquery": "^3.3.34",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"@types/moment": "^2.13.0",
|
||||
"@types/node": "^12.7.2",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/sha256": "^0.2.0",
|
||||
"@types/websocket": "0.0.40",
|
||||
"clean-css": "^4.2.1",
|
||||
"css-loader": "^3.4.2",
|
||||
"csso-cli": "^2.0.2",
|
||||
"fs-extra": "latest",
|
||||
"gulp": "^4.0.2",
|
||||
"mime-types": "^2.1.24",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"node-sass": "^4.13.1",
|
||||
"sass": "1.22.10",
|
||||
"sass-loader": "^8.0.2",
|
||||
"sha256": "^0.2.0",
|
||||
"style-loader": "^1.1.3",
|
||||
"terser": "^4.2.1",
|
||||
"ts-loader": "^6.2.2",
|
||||
"ttypescript": "^1.5.10",
|
||||
"typescript": "3.6.5",
|
||||
"wat2wasm": "^1.0.2",
|
||||
"fs-extra": "latest"
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -50,6 +62,8 @@
|
|||
},
|
||||
"homepage": "https://www.teaspeak.de",
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "^8.0.1"
|
||||
"@types/fs-extra": "^8.0.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,96 +94,7 @@
|
|||
}
|
||||
|
||||
&.channel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.container-channel {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
width: 100%;
|
||||
min-height: 16px;
|
||||
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
.channel-type {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.container-channel-name {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
justify-content: left;
|
||||
|
||||
max-width: 100%; /* important for the repetitive channel name! */
|
||||
overflow-x: hidden;
|
||||
height: 16px;
|
||||
|
||||
&.align-right {
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
&.align-center, &.align-repetitive {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.channel-name {
|
||||
align-self: center;
|
||||
color: $channel_tree_entry_text_color;
|
||||
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&.align-repetitive {
|
||||
.channel-name {
|
||||
text-overflow: clip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&.move-selected {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
.show-channel-normal-only {
|
||||
display: none;
|
||||
|
||||
&.channel-normal {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.icon_no_sound {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.container-clients {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
&.client {
|
||||
|
@ -272,40 +183,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.channel .container-channel, &.client, &.server {
|
||||
.marker-text-unread {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
width: 1px;
|
||||
background-color: #a814147F;
|
||||
|
||||
opacity: 1;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
width: 24px;
|
||||
|
||||
background: -moz-linear-gradient(left, rgba(168,20,20,.18) 0%, rgba(168,20,20,0) 100%); /* FF3.6-15 */
|
||||
background: -webkit-linear-gradient(left, rgba(168,20,20,.18) 0%,rgba(168,20,20,0) 100%); /* Chrome10-25,Safari5.1-6 */
|
||||
background: linear-gradient(to right, rgba(168,20,20,.18) 0%,rgba(168,20,20,0) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@include transition(opacity $button_hover_animation_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
interface Window {
|
||||
export interface Window {
|
||||
BroadcastChannel: BroadcastChannel;
|
||||
}
|
||||
|
||||
namespace bipc {
|
||||
export namespace bipc {
|
||||
export interface BroadcastMessage {
|
||||
timestamp: number;
|
||||
receiver: string;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/// <reference path="log.ts" />
|
||||
/// <reference path="proto.ts" />
|
||||
/// <reference path="ui/view.ts" />
|
||||
/// <reference path="channel-tree/view.ts" />
|
||||
/// <reference path="settings.ts" />
|
||||
/// <reference path="FileManager.ts" />
|
||||
/// <reference path="permission/PermissionManager.ts" />
|
||||
|
@ -8,7 +8,17 @@
|
|||
/// <reference path="ui/frames/ControlBar.ts" />
|
||||
/// <reference path="connection/ConnectionBase.ts" />
|
||||
|
||||
enum DisconnectReason {
|
||||
import {ChannelTree} from "./channel-tree/view";
|
||||
import {LocalClientEntry} from "./channel-tree/client";
|
||||
import {ServerAddress} from "./channel-tree/server";
|
||||
import {ChannelEntry} from "./channel-tree/channel";
|
||||
import {AbstractServerConnection} from "./connection/ConnectionBase";
|
||||
import {PermissionManager} from "./permission/PermissionManager";
|
||||
import {GroupManager} from "./permission/GroupManager";
|
||||
import {ServerSettings} from "./settings";
|
||||
import {Hostbanner} from "./ui/frames/hostbanner";
|
||||
|
||||
export enum DisconnectReason {
|
||||
HANDLER_DESTROYED,
|
||||
REQUESTED,
|
||||
DNS_FAILED,
|
||||
|
@ -28,7 +38,7 @@ enum DisconnectReason {
|
|||
UNKNOWN
|
||||
}
|
||||
|
||||
enum ConnectionState {
|
||||
export enum ConnectionState {
|
||||
UNCONNECTED,
|
||||
CONNECTING,
|
||||
INITIALISING,
|
||||
|
@ -36,7 +46,7 @@ enum ConnectionState {
|
|||
DISCONNECTING
|
||||
}
|
||||
|
||||
enum ViewReasonId {
|
||||
export enum ViewReasonId {
|
||||
VREASON_USER_ACTION = 0,
|
||||
VREASON_MOVED = 1,
|
||||
VREASON_SYSTEM = 2,
|
||||
|
@ -51,7 +61,7 @@ enum ViewReasonId {
|
|||
VREASON_SERVER_SHUTDOWN = 11
|
||||
}
|
||||
|
||||
interface VoiceStatus {
|
||||
export interface VoiceStatus {
|
||||
input_hardware: boolean;
|
||||
input_muted: boolean;
|
||||
output_muted: boolean;
|
||||
|
@ -68,7 +78,7 @@ interface VoiceStatus {
|
|||
queries_visible: boolean;
|
||||
}
|
||||
|
||||
interface ConnectParameters {
|
||||
export interface ConnectParameters {
|
||||
nickname?: string;
|
||||
channel?: {
|
||||
target: string | number;
|
||||
|
@ -79,10 +89,10 @@ interface ConnectParameters {
|
|||
auto_reconnect_attempt?: boolean;
|
||||
}
|
||||
|
||||
class ConnectionHandler {
|
||||
export class ConnectionHandler {
|
||||
channelTree: ChannelTree;
|
||||
|
||||
serverConnection: connection.AbstractServerConnection;
|
||||
serverConnection: AbstractServerConnection;
|
||||
|
||||
fileManager: FileManager;
|
||||
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
/// <reference path="connection/CommandHandler.ts" />
|
||||
/// <reference path="connection/ConnectionBase.ts" />
|
||||
import {ChannelEntry} from "./channel-tree/channel";
|
||||
import {AbstractCommandHandler, ServerCommand} from "./connection/ConnectionBase";
|
||||
import {ConnectionHandler} from "./ConnectionHandler";
|
||||
import {CommandResult} from "./connection/ServerConnectionDeclaration";
|
||||
import {log, LogCategory} from "./log";
|
||||
import {ClientEntry} from "./channel-tree/client";
|
||||
import {hex} from "./crypto/hex";
|
||||
|
||||
class FileEntry {
|
||||
export class FileEntry {
|
||||
name: string;
|
||||
datetime: number;
|
||||
type: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
class FileListRequest {
|
||||
export class FileListRequest {
|
||||
path: string;
|
||||
entries: FileEntry[];
|
||||
|
||||
callback: (entries: FileEntry[]) => void;
|
||||
}
|
||||
|
||||
namespace transfer {
|
||||
export namespace transfer {
|
||||
export interface TransferKey {
|
||||
client_transfer_id: number;
|
||||
server_transfer_id: number;
|
||||
|
@ -152,7 +157,7 @@ class RequestFileUpload implements transfer.UploadTransfer {
|
|||
}
|
||||
}
|
||||
|
||||
class FileManager extends connection.AbstractCommandHandler {
|
||||
class FileManager extends AbstractCommandHandler {
|
||||
handle: ConnectionHandler;
|
||||
icons: IconManager;
|
||||
avatars: AvatarManager;
|
||||
|
@ -191,7 +196,7 @@ class FileManager extends connection.AbstractCommandHandler {
|
|||
this.avatars = undefined;
|
||||
}
|
||||
|
||||
handle_command(command: connection.ServerCommand): boolean {
|
||||
handle_command(command: ServerCommand): boolean {
|
||||
switch (command.command) {
|
||||
case "notifyfilelist":
|
||||
this.notifyFileList(command.arguments);
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
namespace messages.formatter {
|
||||
import {Settings, settings} from "./settings";
|
||||
import {contextmenu} from "./ui/elements/context_menu";
|
||||
import {image_preview} from "./ui/frames/image_preview";
|
||||
import {guid} from "./crypto/uid";
|
||||
|
||||
declare const xbbcode;
|
||||
export namespace messages.formatter {
|
||||
export namespace bbcode {
|
||||
const sanitizer_escaped = (key: string) => "[-- sescaped: " + key + " --]";
|
||||
const sanitizer_escaped_regex = /\[-- sescaped: ([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}) --]/;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
enum KeyCode {
|
||||
export enum KeyCode {
|
||||
KEY_CANCEL = 3,
|
||||
KEY_HELP = 6,
|
||||
KEY_BACK_SPACE = 8,
|
||||
|
@ -118,7 +118,7 @@ enum KeyCode {
|
|||
KEY_META = 224
|
||||
}
|
||||
|
||||
namespace ppt {
|
||||
export namespace ppt {
|
||||
export enum EventType {
|
||||
KEY_PRESS,
|
||||
KEY_RELEASE,
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
namespace bookmarks {
|
||||
function guid() {
|
||||
function s4() {
|
||||
return Math
|
||||
.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
}
|
||||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
|
||||
}
|
||||
import {profiles} from "./profiles/ConnectionProfile";
|
||||
|
||||
export namespace bookmarks {
|
||||
export const boorkmak_connect = (mark: Bookmark, new_tab?: boolean) => {
|
||||
const profile = profiles.find_profile(mark.connect_profile) || profiles.default_profile();
|
||||
if(profile.valid()) {
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
/// <reference path="view.ts" />
|
||||
/// <reference path="../utils/helpers.ts" />
|
||||
|
||||
enum ChannelType {
|
||||
import {ChannelTree} from "./view";
|
||||
import {ClientEntry} from "./client";
|
||||
|
||||
export enum ChannelType {
|
||||
PERMANENT,
|
||||
SEMI_PERMANENT,
|
||||
TEMPORARY
|
||||
}
|
||||
namespace ChannelType {
|
||||
export namespace ChannelType {
|
||||
export function normalize(mode: ChannelType) {
|
||||
let value: string = ChannelType[mode];
|
||||
value = value.toLowerCase();
|
||||
|
@ -14,13 +17,13 @@ namespace ChannelType {
|
|||
}
|
||||
}
|
||||
|
||||
enum ChannelSubscribeMode {
|
||||
export enum ChannelSubscribeMode {
|
||||
SUBSCRIBED,
|
||||
UNSUBSCRIBED,
|
||||
INHERITED
|
||||
}
|
||||
|
||||
class ChannelProperties {
|
||||
export class ChannelProperties {
|
||||
channel_order: number = 0;
|
||||
channel_name: string = "";
|
||||
channel_name_phonetic: string = "";
|
||||
|
@ -55,7 +58,7 @@ class ChannelProperties {
|
|||
channel_conversation_history_length: number = -1;
|
||||
}
|
||||
|
||||
class ChannelEntry {
|
||||
export class ChannelEntry {
|
||||
channelTree: ChannelTree;
|
||||
channelId: number;
|
||||
parent?: ChannelEntry;
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
/// <reference path="channel.ts" />
|
||||
/// <reference path="modal/ModalChangeVolume.ts" />
|
||||
/// <reference path="client_move.ts" />
|
||||
/// <reference path="../ui/modal/ModalChangeVolume.ts" />
|
||||
/// <reference path="../ui/client_move.ts" />
|
||||
|
||||
enum ClientType {
|
||||
import {ChannelEntry} from "./channel";
|
||||
import {ChannelTree} from "./view";
|
||||
|
||||
export enum ClientType {
|
||||
CLIENT_VOICE,
|
||||
CLIENT_QUERY,
|
||||
CLIENT_INTERNAL,
|
||||
|
@ -11,7 +14,7 @@ enum ClientType {
|
|||
CLIENT_UNDEFINED
|
||||
}
|
||||
|
||||
class ClientProperties {
|
||||
export class ClientProperties {
|
||||
client_type: ClientType = ClientType.CLIENT_VOICE; //TeamSpeaks type
|
||||
client_type_exact: ClientType = ClientType.CLIENT_VOICE;
|
||||
|
||||
|
@ -57,7 +60,7 @@ class ClientProperties {
|
|||
client_is_priority_speaker: boolean = false;
|
||||
}
|
||||
|
||||
class ClientConnectionInfo {
|
||||
export class ClientConnectionInfo {
|
||||
connection_bandwidth_received_last_minute_control: number = -1;
|
||||
connection_bandwidth_received_last_minute_keepalive: number = -1;
|
||||
connection_bandwidth_received_last_minute_speech: number = -1;
|
||||
|
@ -109,7 +112,7 @@ class ClientConnectionInfo {
|
|||
connection_client_port: number = -1;
|
||||
}
|
||||
|
||||
class ClientEntry {
|
||||
export class ClientEntry {
|
||||
readonly events: events.Registry<events.channel_tree.client>;
|
||||
|
||||
protected _clientId: number;
|
||||
|
@ -1123,7 +1126,7 @@ class ClientEntry {
|
|||
}
|
||||
}
|
||||
|
||||
class LocalClientEntry extends ClientEntry {
|
||||
export class LocalClientEntry extends ClientEntry {
|
||||
handle: ConnectionHandler;
|
||||
|
||||
private renaming: boolean;
|
||||
|
@ -1232,7 +1235,7 @@ class LocalClientEntry extends ClientEntry {
|
|||
}
|
||||
}
|
||||
|
||||
class MusicClientProperties extends ClientProperties {
|
||||
export class MusicClientProperties extends ClientProperties {
|
||||
player_state: number = 0;
|
||||
player_volume: number = 0;
|
||||
|
||||
|
@ -1264,7 +1267,7 @@ class MusicClientProperties extends ClientProperties {
|
|||
}
|
||||
*/
|
||||
|
||||
class SongInfo {
|
||||
export class SongInfo {
|
||||
song_id: number = 0;
|
||||
song_url: string = "";
|
||||
song_invoker: number = 0;
|
||||
|
@ -1277,7 +1280,7 @@ class SongInfo {
|
|||
song_length: number = 0;
|
||||
}
|
||||
|
||||
class MusicClientPlayerInfo extends SongInfo {
|
||||
export class MusicClientPlayerInfo extends SongInfo {
|
||||
bot_id: number = 0;
|
||||
player_state: number = 0;
|
||||
|
||||
|
@ -1290,7 +1293,7 @@ class MusicClientPlayerInfo extends SongInfo {
|
|||
player_description: string = "";
|
||||
}
|
||||
|
||||
class MusicClientEntry extends ClientEntry {
|
||||
export class MusicClientEntry extends ClientEntry {
|
||||
private _info_promise: Promise<MusicClientPlayerInfo>;
|
||||
private _info_promise_age: number = 0;
|
||||
private _info_promise_resolve: any;
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
/// <reference path="channel.ts" />
|
||||
/// <reference path="modal/ModalServerEdit.ts" />
|
||||
/// <reference path="../ui/modal/ModalServerEdit.ts" />
|
||||
|
||||
class ServerProperties {
|
||||
import {ChannelTree} from "./view";
|
||||
|
||||
export class ServerProperties {
|
||||
virtualserver_host: string = "";
|
||||
virtualserver_port: number = 0;
|
||||
|
||||
|
@ -78,7 +80,7 @@ class ServerProperties {
|
|||
virtualserver_total_bytes_uploaded: number = 0;
|
||||
}
|
||||
|
||||
interface ServerConnectionInfo {
|
||||
export interface ServerConnectionInfo {
|
||||
connection_filetransfer_bandwidth_sent: number;
|
||||
connection_filetransfer_bandwidth_received: number;
|
||||
|
||||
|
@ -103,12 +105,12 @@ interface ServerConnectionInfo {
|
|||
connection_ping: number;
|
||||
}
|
||||
|
||||
interface ServerAddress {
|
||||
export interface ServerAddress {
|
||||
host: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
class ServerEntry {
|
||||
export class ServerEntry {
|
||||
remote_address: ServerAddress;
|
||||
channelTree: ChannelTree;
|
||||
properties: ServerProperties;
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
/// <reference path="client.ts" />
|
||||
/// <reference path="server.ts" />
|
||||
/// <reference path="../bookmarks.ts" />
|
||||
/// <reference path="elements/context_menu.ts" />
|
||||
/// <reference path="modal/ModalCreateChannel.ts" />
|
||||
/// <reference path="../ui/elements/context_menu.ts" />
|
||||
/// <reference path="../ui/modal/ModalCreateChannel.ts" />
|
||||
/// <reference path="../../backend/ppt.d.ts" />
|
||||
|
||||
|
||||
class ChannelTree {
|
||||
export class ChannelTree {
|
||||
client: ConnectionHandler;
|
||||
server: ServerEntry;
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,448 +1,460 @@
|
|||
namespace connection {
|
||||
export class CommandHelper extends AbstractCommandHandler {
|
||||
private _who_am_i: any;
|
||||
private _awaiters_unique_ids: {[unique_id: string]:((resolved: ClientNameInfo) => any)[]} = {};
|
||||
private _awaiters_unique_dbid: {[database_id: number]:((resolved: ClientNameInfo) => any)[]} = {};
|
||||
import {
|
||||
ClientNameInfo,
|
||||
CommandResult,
|
||||
ErrorID,
|
||||
Playlist, PlaylistInfo, PlaylistSong,
|
||||
QueryList,
|
||||
QueryListEntry, ServerGroupClient
|
||||
} from "./ServerConnectionDeclaration";
|
||||
import {ChannelEntry} from "../channel-tree/channel";
|
||||
import {ChatType} from "../ui/frames/chat";
|
||||
import {ClientEntry} from "../channel-tree/client";
|
||||
import {AbstractCommandHandler, ServerCommand, SingleCommandHandler} from "./ConnectionBase";
|
||||
import {log, LogCategory} from "../log";
|
||||
|
||||
constructor(connection) {
|
||||
super(connection);
|
||||
export class CommandHelper extends AbstractCommandHandler {
|
||||
private _who_am_i: any;
|
||||
private _awaiters_unique_ids: {[unique_id: string]:((resolved: ClientNameInfo) => any)[]} = {};
|
||||
private _awaiters_unique_dbid: {[database_id: number]:((resolved: ClientNameInfo) => any)[]} = {};
|
||||
|
||||
this.volatile_handler_boss = false;
|
||||
this.ignore_consumed = true;
|
||||
constructor(connection) {
|
||||
super(connection);
|
||||
|
||||
this.volatile_handler_boss = false;
|
||||
this.ignore_consumed = true;
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.connection.command_handler_boss().register_handler(this);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if(this.connection) {
|
||||
const hboss = this.connection.command_handler_boss();
|
||||
hboss && hboss.unregister_handler(this);
|
||||
}
|
||||
this._awaiters_unique_ids = undefined;
|
||||
}
|
||||
|
||||
handle_command(command: ServerCommand): boolean {
|
||||
if(command.command == "notifyclientnamefromuid")
|
||||
this.handle_notifyclientnamefromuid(command.arguments);
|
||||
if(command.command == "notifyclientgetnamefromdbid")
|
||||
this.handle_notifyclientgetnamefromdbid(command.arguments);
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
joinChannel(channel: ChannelEntry, password?: string) : Promise<CommandResult> {
|
||||
return this.connection.send_command("clientmove", {
|
||||
"clid": this.connection.client.getClientId(),
|
||||
"cid": channel.getChannelId(),
|
||||
"cpw": password || ""
|
||||
});
|
||||
}
|
||||
|
||||
sendMessage(message: string, type: ChatType, target?: ChannelEntry | ClientEntry) : Promise<CommandResult> {
|
||||
if(type == ChatType.SERVER)
|
||||
return this.connection.send_command("sendtextmessage", {"targetmode": 3, "target": 0, "msg": message});
|
||||
else if(type == ChatType.CHANNEL)
|
||||
return this.connection.send_command("sendtextmessage", {"targetmode": 2, "target": (target as ChannelEntry).getChannelId(), "msg": message});
|
||||
else if(type == ChatType.CLIENT)
|
||||
return this.connection.send_command("sendtextmessage", {"targetmode": 1, "target": (target as ClientEntry).clientId(), "msg": message});
|
||||
}
|
||||
|
||||
updateClient(key: string, value: string) : Promise<CommandResult> {
|
||||
let data = {};
|
||||
data[key] = value;
|
||||
return this.connection.send_command("clientupdate", data);
|
||||
}
|
||||
|
||||
async info_from_uid(..._unique_ids: string[]) : Promise<ClientNameInfo[]> {
|
||||
const response: ClientNameInfo[] = [];
|
||||
const request = [];
|
||||
const unique_ids = new Set(_unique_ids);
|
||||
if(!unique_ids.size) return [];
|
||||
|
||||
const unique_id_resolvers: {[unique_id: string]: (resolved: ClientNameInfo) => any} = {};
|
||||
|
||||
|
||||
for(const unique_id of unique_ids) {
|
||||
request.push({'cluid': unique_id});
|
||||
(this._awaiters_unique_ids[unique_id] || (this._awaiters_unique_ids[unique_id] = []))
|
||||
.push(unique_id_resolvers[unique_id] = info => response.push(info));
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.connection.command_handler_boss().register_handler(this);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if(this.connection) {
|
||||
const hboss = this.connection.command_handler_boss();
|
||||
hboss && hboss.unregister_handler(this);
|
||||
try {
|
||||
await this.connection.send_command("clientgetnamefromuid", request);
|
||||
} catch(error) {
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
||||
/* nothing */
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
this._awaiters_unique_ids = undefined;
|
||||
} finally {
|
||||
/* cleanup */
|
||||
for(const unique_id of Object.keys(unique_id_resolvers))
|
||||
(this._awaiters_unique_ids[unique_id] || []).remove(unique_id_resolvers[unique_id]);
|
||||
}
|
||||
|
||||
handle_command(command: connection.ServerCommand): boolean {
|
||||
if(command.command == "notifyclientnamefromuid")
|
||||
this.handle_notifyclientnamefromuid(command.arguments);
|
||||
if(command.command == "notifyclientgetnamefromdbid")
|
||||
this.handle_notifyclientgetnamefromdbid(command.arguments);
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
return response;
|
||||
}
|
||||
|
||||
private handle_notifyclientgetnamefromdbid(json: any[]) {
|
||||
for(const entry of json) {
|
||||
const info: ClientNameInfo = {
|
||||
client_unique_id: entry["cluid"],
|
||||
client_nickname: entry["clname"],
|
||||
client_database_id: parseInt(entry["cldbid"])
|
||||
};
|
||||
|
||||
const functions = this._awaiters_unique_dbid[info.client_database_id] || [];
|
||||
delete this._awaiters_unique_dbid[info.client_database_id];
|
||||
|
||||
for(const fn of functions)
|
||||
fn(info);
|
||||
}
|
||||
}
|
||||
|
||||
async info_from_cldbid(..._cldbid: number[]) : Promise<ClientNameInfo[]> {
|
||||
const response: ClientNameInfo[] = [];
|
||||
const request = [];
|
||||
const unique_cldbid = new Set(_cldbid);
|
||||
if(!unique_cldbid.size) return [];
|
||||
|
||||
const unique_cldbid_resolvers: {[dbid: number]: (resolved: ClientNameInfo) => any} = {};
|
||||
|
||||
|
||||
for(const cldbid of unique_cldbid) {
|
||||
request.push({'cldbid': cldbid});
|
||||
(this._awaiters_unique_dbid[cldbid] || (this._awaiters_unique_dbid[cldbid] = []))
|
||||
.push(unique_cldbid_resolvers[cldbid] = info => response.push(info));
|
||||
}
|
||||
|
||||
joinChannel(channel: ChannelEntry, password?: string) : Promise<CommandResult> {
|
||||
return this.connection.send_command("clientmove", {
|
||||
"clid": this.connection.client.getClientId(),
|
||||
"cid": channel.getChannelId(),
|
||||
"cpw": password || ""
|
||||
});
|
||||
try {
|
||||
await this.connection.send_command("clientgetnamefromdbid", request);
|
||||
} catch(error) {
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
||||
/* nothing */
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
} finally {
|
||||
/* cleanup */
|
||||
for(const cldbid of Object.keys(unique_cldbid_resolvers))
|
||||
(this._awaiters_unique_dbid[cldbid] || []).remove(unique_cldbid_resolvers[cldbid]);
|
||||
}
|
||||
|
||||
sendMessage(message: string, type: ChatType, target?: ChannelEntry | ClientEntry) : Promise<CommandResult> {
|
||||
if(type == ChatType.SERVER)
|
||||
return this.connection.send_command("sendtextmessage", {"targetmode": 3, "target": 0, "msg": message});
|
||||
else if(type == ChatType.CHANNEL)
|
||||
return this.connection.send_command("sendtextmessage", {"targetmode": 2, "target": (target as ChannelEntry).getChannelId(), "msg": message});
|
||||
else if(type == ChatType.CLIENT)
|
||||
return this.connection.send_command("sendtextmessage", {"targetmode": 1, "target": (target as ClientEntry).clientId(), "msg": message});
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private handle_notifyclientnamefromuid(json: any[]) {
|
||||
for(const entry of json) {
|
||||
const info: ClientNameInfo = {
|
||||
client_unique_id: entry["cluid"],
|
||||
client_nickname: entry["clname"],
|
||||
client_database_id: parseInt(entry["cldbid"])
|
||||
};
|
||||
|
||||
const functions = this._awaiters_unique_ids[entry["cluid"]] || [];
|
||||
delete this._awaiters_unique_ids[entry["cluid"]];
|
||||
|
||||
for(const fn of functions)
|
||||
fn(info);
|
||||
}
|
||||
}
|
||||
|
||||
request_query_list(server_id: number = undefined) : Promise<QueryList> {
|
||||
return new Promise<QueryList>((resolve, reject) => {
|
||||
const single_handler = {
|
||||
command: "notifyquerylist",
|
||||
function: command => {
|
||||
const json = command.arguments;
|
||||
|
||||
const result = {} as QueryList;
|
||||
|
||||
result.flag_all = json[0]["flag_all"];
|
||||
result.flag_own = json[0]["flag_own"];
|
||||
result.queries = [];
|
||||
|
||||
for(const entry of json) {
|
||||
const rentry = {} as QueryListEntry;
|
||||
rentry.bounded_server = parseInt(entry["client_bound_server"]);
|
||||
rentry.username = entry["client_login_name"];
|
||||
rentry.unique_id = entry["client_unique_identifier"];
|
||||
|
||||
result.queries.push(rentry);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
|
||||
updateClient(key: string, value: string) : Promise<CommandResult> {
|
||||
let data = {};
|
||||
data[key] = value;
|
||||
return this.connection.send_command("clientupdate", data);
|
||||
}
|
||||
if(server_id !== undefined)
|
||||
data["server_id"] = server_id;
|
||||
|
||||
async info_from_uid(..._unique_ids: string[]) : Promise<ClientNameInfo[]> {
|
||||
const response: ClientNameInfo[] = [];
|
||||
const request = [];
|
||||
const unique_ids = new Set(_unique_ids);
|
||||
if(!unique_ids.size) return [];
|
||||
this.connection.send_command("querylist", data).catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
|
||||
const unique_id_resolvers: {[unique_id: string]: (resolved: ClientNameInfo) => any} = {};
|
||||
|
||||
|
||||
for(const unique_id of unique_ids) {
|
||||
request.push({'cluid': unique_id});
|
||||
(this._awaiters_unique_ids[unique_id] || (this._awaiters_unique_ids[unique_id] = []))
|
||||
.push(unique_id_resolvers[unique_id] = info => response.push(info));
|
||||
}
|
||||
|
||||
try {
|
||||
await this.connection.send_command("clientgetnamefromuid", request);
|
||||
} catch(error) {
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
||||
/* nothing */
|
||||
} else {
|
||||
throw error;
|
||||
if(error instanceof CommandResult) {
|
||||
if(error.id == ErrorID.EMPTY_RESULT) {
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
/* cleanup */
|
||||
for(const unique_id of Object.keys(unique_id_resolvers))
|
||||
(this._awaiters_unique_ids[unique_id] || []).remove(unique_id_resolvers[unique_id]);
|
||||
}
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
request_playlist_list() : Promise<Playlist[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const single_handler: SingleCommandHandler = {
|
||||
command: "notifyplaylistlist",
|
||||
function: command => {
|
||||
const json = command.arguments;
|
||||
const result: Playlist[] = [];
|
||||
|
||||
private handle_notifyclientgetnamefromdbid(json: any[]) {
|
||||
for(const entry of json) {
|
||||
const info: ClientNameInfo = {
|
||||
client_unique_id: entry["cluid"],
|
||||
client_nickname: entry["clname"],
|
||||
client_database_id: parseInt(entry["cldbid"])
|
||||
};
|
||||
for(const entry of json) {
|
||||
try {
|
||||
result.push({
|
||||
playlist_id: parseInt(entry["playlist_id"]),
|
||||
playlist_bot_id: parseInt(entry["playlist_bot_id"]),
|
||||
playlist_title: entry["playlist_title"],
|
||||
playlist_type: parseInt(entry["playlist_type"]),
|
||||
playlist_owner_dbid: parseInt(entry["playlist_owner_dbid"]),
|
||||
playlist_owner_name: entry["playlist_owner_name"],
|
||||
|
||||
const functions = this._awaiters_unique_dbid[info.client_database_id] || [];
|
||||
delete this._awaiters_unique_dbid[info.client_database_id];
|
||||
needed_power_modify: parseInt(entry["needed_power_modify"]),
|
||||
needed_power_permission_modify: parseInt(entry["needed_power_permission_modify"]),
|
||||
needed_power_delete: parseInt(entry["needed_power_delete"]),
|
||||
needed_power_song_add: parseInt(entry["needed_power_song_add"]),
|
||||
needed_power_song_move: parseInt(entry["needed_power_song_move"]),
|
||||
needed_power_song_remove: parseInt(entry["needed_power_song_remove"])
|
||||
});
|
||||
} catch(error) {
|
||||
log.error(LogCategory.NETWORKING, tr("Failed to parse playlist entry: %o"), error);
|
||||
}
|
||||
}
|
||||
|
||||
for(const fn of functions)
|
||||
fn(info);
|
||||
}
|
||||
}
|
||||
|
||||
async info_from_cldbid(..._cldbid: number[]) : Promise<ClientNameInfo[]> {
|
||||
const response: ClientNameInfo[] = [];
|
||||
const request = [];
|
||||
const unique_cldbid = new Set(_cldbid);
|
||||
if(!unique_cldbid.size) return [];
|
||||
|
||||
const unique_cldbid_resolvers: {[dbid: number]: (resolved: ClientNameInfo) => any} = {};
|
||||
|
||||
|
||||
for(const cldbid of unique_cldbid) {
|
||||
request.push({'cldbid': cldbid});
|
||||
(this._awaiters_unique_dbid[cldbid] || (this._awaiters_unique_dbid[cldbid] = []))
|
||||
.push(unique_cldbid_resolvers[cldbid] = info => response.push(info));
|
||||
}
|
||||
|
||||
try {
|
||||
await this.connection.send_command("clientgetnamefromdbid", request);
|
||||
} catch(error) {
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
||||
/* nothing */
|
||||
} else {
|
||||
throw error;
|
||||
resolve(result);
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
/* cleanup */
|
||||
for(const cldbid of Object.keys(unique_cldbid_resolvers))
|
||||
(this._awaiters_unique_dbid[cldbid] || []).remove(unique_cldbid_resolvers[cldbid]);
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
|
||||
return response;
|
||||
}
|
||||
this.connection.send_command("playlistlist").catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
|
||||
private handle_notifyclientnamefromuid(json: any[]) {
|
||||
for(const entry of json) {
|
||||
const info: ClientNameInfo = {
|
||||
client_unique_id: entry["cluid"],
|
||||
client_nickname: entry["clname"],
|
||||
client_database_id: parseInt(entry["cldbid"])
|
||||
};
|
||||
|
||||
const functions = this._awaiters_unique_ids[entry["cluid"]] || [];
|
||||
delete this._awaiters_unique_ids[entry["cluid"]];
|
||||
|
||||
for(const fn of functions)
|
||||
fn(info);
|
||||
}
|
||||
}
|
||||
|
||||
request_query_list(server_id: number = undefined) : Promise<QueryList> {
|
||||
return new Promise<QueryList>((resolve, reject) => {
|
||||
const single_handler = {
|
||||
command: "notifyquerylist",
|
||||
function: command => {
|
||||
const json = command.arguments;
|
||||
|
||||
const result = {} as QueryList;
|
||||
|
||||
result.flag_all = json[0]["flag_all"];
|
||||
result.flag_own = json[0]["flag_own"];
|
||||
result.queries = [];
|
||||
|
||||
for(const entry of json) {
|
||||
const rentry = {} as QueryListEntry;
|
||||
rentry.bounded_server = parseInt(entry["client_bound_server"]);
|
||||
rentry.username = entry["client_login_name"];
|
||||
rentry.unique_id = entry["client_unique_identifier"];
|
||||
|
||||
result.queries.push(rentry);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
|
||||
let data = {};
|
||||
if(server_id !== undefined)
|
||||
data["server_id"] = server_id;
|
||||
|
||||
this.connection.send_command("querylist", data).catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
|
||||
if(error instanceof CommandResult) {
|
||||
if(error.id == ErrorID.EMPTY_RESULT) {
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
}
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
request_playlist_list() : Promise<Playlist[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const single_handler: SingleCommandHandler = {
|
||||
command: "notifyplaylistlist",
|
||||
function: command => {
|
||||
const json = command.arguments;
|
||||
const result: Playlist[] = [];
|
||||
|
||||
for(const entry of json) {
|
||||
try {
|
||||
result.push({
|
||||
playlist_id: parseInt(entry["playlist_id"]),
|
||||
playlist_bot_id: parseInt(entry["playlist_bot_id"]),
|
||||
playlist_title: entry["playlist_title"],
|
||||
playlist_type: parseInt(entry["playlist_type"]),
|
||||
playlist_owner_dbid: parseInt(entry["playlist_owner_dbid"]),
|
||||
playlist_owner_name: entry["playlist_owner_name"],
|
||||
|
||||
needed_power_modify: parseInt(entry["needed_power_modify"]),
|
||||
needed_power_permission_modify: parseInt(entry["needed_power_permission_modify"]),
|
||||
needed_power_delete: parseInt(entry["needed_power_delete"]),
|
||||
needed_power_song_add: parseInt(entry["needed_power_song_add"]),
|
||||
needed_power_song_move: parseInt(entry["needed_power_song_move"]),
|
||||
needed_power_song_remove: parseInt(entry["needed_power_song_remove"])
|
||||
});
|
||||
} catch(error) {
|
||||
log.error(LogCategory.NETWORKING, tr("Failed to parse playlist entry: %o"), error);
|
||||
}
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
|
||||
this.connection.send_command("playlistlist").catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
|
||||
if(error instanceof CommandResult) {
|
||||
if(error.id == ErrorID.EMPTY_RESULT) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
reject(error);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
request_playlist_songs(playlist_id: number) : Promise<PlaylistSong[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const single_handler: SingleCommandHandler = {
|
||||
command: "notifyplaylistsonglist",
|
||||
function: command => {
|
||||
const json = command.arguments;
|
||||
|
||||
if(json[0]["playlist_id"] != playlist_id) {
|
||||
log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist songs"));
|
||||
return false;
|
||||
}
|
||||
|
||||
const result: PlaylistSong[] = [];
|
||||
|
||||
for(const entry of json) {
|
||||
try {
|
||||
result.push({
|
||||
song_id: parseInt(entry["song_id"]),
|
||||
song_invoker: entry["song_invoker"],
|
||||
song_previous_song_id: parseInt(entry["song_previous_song_id"]),
|
||||
song_url: entry["song_url"],
|
||||
song_url_loader: entry["song_url_loader"],
|
||||
|
||||
song_loaded: entry["song_loaded"] == true || entry["song_loaded"] == "1",
|
||||
song_metadata: entry["song_metadata"]
|
||||
});
|
||||
} catch(error) {
|
||||
log.error(LogCategory.NETWORKING, tr("Failed to parse playlist song entry: %o"), error);
|
||||
}
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
|
||||
this.connection.send_command("playlistsonglist", {playlist_id: playlist_id}).catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
if(error instanceof CommandResult) {
|
||||
if(error.id == ErrorID.EMPTY_RESULT) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
reject(error);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
request_playlist_client_list(playlist_id: number) : Promise<number[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const single_handler: SingleCommandHandler = {
|
||||
command: "notifyplaylistclientlist",
|
||||
function: command => {
|
||||
const json = command.arguments;
|
||||
|
||||
if(json[0]["playlist_id"] != playlist_id) {
|
||||
log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist clients"));
|
||||
return false;
|
||||
}
|
||||
|
||||
const result: number[] = [];
|
||||
|
||||
for(const entry of json)
|
||||
result.push(parseInt(entry["cldbid"]));
|
||||
|
||||
resolve(result.filter(e => !isNaN(e)));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
|
||||
this.connection.send_command("playlistclientlist", {playlist_id: playlist_id}).catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
||||
if(error instanceof CommandResult) {
|
||||
if(error.id == ErrorID.EMPTY_RESULT) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
reject(error);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
reject(error);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async request_clients_by_server_group(group_id: number) : Promise<ServerGroupClient[]> {
|
||||
//servergroupclientlist sgid=2
|
||||
//notifyservergroupclientlist sgid=6 cldbid=2 client_nickname=WolverinDEV client_unique_identifier=xxjnc14LmvTk+Lyrm8OOeo4tOqw=
|
||||
return new Promise<ServerGroupClient[]>((resolve, reject) => {
|
||||
const single_handler: SingleCommandHandler = {
|
||||
command: "notifyservergroupclientlist",
|
||||
function: command => {
|
||||
if (command.arguments[0]["sgid"] != group_id) {
|
||||
log.error(LogCategory.NETWORKING, tr("Received invalid notification for server group client list"));
|
||||
return false;
|
||||
}
|
||||
request_playlist_songs(playlist_id: number) : Promise<PlaylistSong[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const single_handler: SingleCommandHandler = {
|
||||
command: "notifyplaylistsonglist",
|
||||
function: command => {
|
||||
const json = command.arguments;
|
||||
|
||||
try {
|
||||
const result: ServerGroupClient[] = [];
|
||||
for(const entry of command.arguments)
|
||||
result.push({
|
||||
client_database_id: parseInt(entry["cldbid"]),
|
||||
client_nickname: entry["client_nickname"],
|
||||
client_unique_identifier: entry["client_unique_identifier"]
|
||||
});
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
log.error(LogCategory.NETWORKING, tr("Failed to parse server group client list: %o"), error);
|
||||
reject("failed to parse info");
|
||||
}
|
||||
|
||||
return true;
|
||||
if(json[0]["playlist_id"] != playlist_id) {
|
||||
log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist songs"));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
|
||||
this.connection.send_command("servergroupclientlist", {sgid: group_id}).catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
reject(error);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
request_playlist_info(playlist_id: number) : Promise<PlaylistInfo> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const single_handler: SingleCommandHandler = {
|
||||
command: "notifyplaylistinfo",
|
||||
function: command => {
|
||||
const json = command.arguments[0];
|
||||
if (json["playlist_id"] != playlist_id) {
|
||||
log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist info"));
|
||||
return;
|
||||
}
|
||||
const result: PlaylistSong[] = [];
|
||||
|
||||
for(const entry of json) {
|
||||
try {
|
||||
//resolve
|
||||
resolve({
|
||||
playlist_id: parseInt(json["playlist_id"]),
|
||||
playlist_title: json["playlist_title"],
|
||||
playlist_description: json["playlist_description"],
|
||||
playlist_type: parseInt(json["playlist_type"]),
|
||||
result.push({
|
||||
song_id: parseInt(entry["song_id"]),
|
||||
song_invoker: entry["song_invoker"],
|
||||
song_previous_song_id: parseInt(entry["song_previous_song_id"]),
|
||||
song_url: entry["song_url"],
|
||||
song_url_loader: entry["song_url_loader"],
|
||||
|
||||
playlist_owner_dbid: parseInt(json["playlist_owner_dbid"]),
|
||||
playlist_owner_name: json["playlist_owner_name"],
|
||||
|
||||
playlist_flag_delete_played: json["playlist_flag_delete_played"] == true || json["playlist_flag_delete_played"] == "1",
|
||||
playlist_flag_finished: json["playlist_flag_finished"] == true || json["playlist_flag_finished"] == "1",
|
||||
playlist_replay_mode: parseInt(json["playlist_replay_mode"]),
|
||||
playlist_current_song_id: parseInt(json["playlist_current_song_id"]),
|
||||
|
||||
playlist_max_songs: parseInt(json["playlist_max_songs"])
|
||||
song_loaded: entry["song_loaded"] == true || entry["song_loaded"] == "1",
|
||||
song_metadata: entry["song_metadata"]
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(LogCategory.NETWORKING, tr("Failed to parse playlist info: %o"), error);
|
||||
reject("failed to parse info");
|
||||
} catch(error) {
|
||||
log.error(LogCategory.NETWORKING, tr("Failed to parse playlist song entry: %o"), error);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
|
||||
this.connection.send_command("playlistinfo", {playlist_id: playlist_id}).catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
reject(error);
|
||||
})
|
||||
});
|
||||
}
|
||||
resolve(result);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Its just a workaround for the query management.
|
||||
* There is no garante that the whoami trick will work forever
|
||||
*/
|
||||
current_virtual_server_id() : Promise<number> {
|
||||
if(this._who_am_i)
|
||||
return Promise.resolve(parseInt(this._who_am_i["virtualserver_id"]));
|
||||
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
const single_handler: SingleCommandHandler = {
|
||||
function: command => {
|
||||
if(command.command != "" && command.command.indexOf("=") == -1)
|
||||
return false;
|
||||
|
||||
this._who_am_i = command.arguments[0];
|
||||
resolve(parseInt(this._who_am_i["virtualserver_id"]));
|
||||
return true;
|
||||
this.connection.send_command("playlistsonglist", {playlist_id: playlist_id}).catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
if(error instanceof CommandResult) {
|
||||
if(error.id == ErrorID.EMPTY_RESULT) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
}
|
||||
reject(error);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
this.connection.send_command("whoami").catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
reject(error);
|
||||
});
|
||||
request_playlist_client_list(playlist_id: number) : Promise<number[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const single_handler: SingleCommandHandler = {
|
||||
command: "notifyplaylistclientlist",
|
||||
function: command => {
|
||||
const json = command.arguments;
|
||||
|
||||
if(json[0]["playlist_id"] != playlist_id) {
|
||||
log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist clients"));
|
||||
return false;
|
||||
}
|
||||
|
||||
const result: number[] = [];
|
||||
|
||||
for(const entry of json)
|
||||
result.push(parseInt(entry["cldbid"]));
|
||||
|
||||
resolve(result.filter(e => !isNaN(e)));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
|
||||
this.connection.send_command("playlistclientlist", {playlist_id: playlist_id}).catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
reject(error);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async request_clients_by_server_group(group_id: number) : Promise<ServerGroupClient[]> {
|
||||
//servergroupclientlist sgid=2
|
||||
//notifyservergroupclientlist sgid=6 cldbid=2 client_nickname=WolverinDEV client_unique_identifier=xxjnc14LmvTk+Lyrm8OOeo4tOqw=
|
||||
return new Promise<ServerGroupClient[]>((resolve, reject) => {
|
||||
const single_handler: SingleCommandHandler = {
|
||||
command: "notifyservergroupclientlist",
|
||||
function: command => {
|
||||
if (command.arguments[0]["sgid"] != group_id) {
|
||||
log.error(LogCategory.NETWORKING, tr("Received invalid notification for server group client list"));
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const result: ServerGroupClient[] = [];
|
||||
for(const entry of command.arguments)
|
||||
result.push({
|
||||
client_database_id: parseInt(entry["cldbid"]),
|
||||
client_nickname: entry["client_nickname"],
|
||||
client_unique_identifier: entry["client_unique_identifier"]
|
||||
});
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
log.error(LogCategory.NETWORKING, tr("Failed to parse server group client list: %o"), error);
|
||||
reject("failed to parse info");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
|
||||
this.connection.send_command("servergroupclientlist", {sgid: group_id}).catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
reject(error);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
request_playlist_info(playlist_id: number) : Promise<PlaylistInfo> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const single_handler: SingleCommandHandler = {
|
||||
command: "notifyplaylistinfo",
|
||||
function: command => {
|
||||
const json = command.arguments[0];
|
||||
if (json["playlist_id"] != playlist_id) {
|
||||
log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist info"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//resolve
|
||||
resolve({
|
||||
playlist_id: parseInt(json["playlist_id"]),
|
||||
playlist_title: json["playlist_title"],
|
||||
playlist_description: json["playlist_description"],
|
||||
playlist_type: parseInt(json["playlist_type"]),
|
||||
|
||||
playlist_owner_dbid: parseInt(json["playlist_owner_dbid"]),
|
||||
playlist_owner_name: json["playlist_owner_name"],
|
||||
|
||||
playlist_flag_delete_played: json["playlist_flag_delete_played"] == true || json["playlist_flag_delete_played"] == "1",
|
||||
playlist_flag_finished: json["playlist_flag_finished"] == true || json["playlist_flag_finished"] == "1",
|
||||
playlist_replay_mode: parseInt(json["playlist_replay_mode"]),
|
||||
playlist_current_song_id: parseInt(json["playlist_current_song_id"]),
|
||||
|
||||
playlist_max_songs: parseInt(json["playlist_max_songs"])
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(LogCategory.NETWORKING, tr("Failed to parse playlist info: %o"), error);
|
||||
reject("failed to parse info");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
|
||||
this.connection.send_command("playlistinfo", {playlist_id: playlist_id}).catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
reject(error);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Its just a workaround for the query management.
|
||||
* There is no garante that the whoami trick will work forever
|
||||
*/
|
||||
current_virtual_server_id() : Promise<number> {
|
||||
if(this._who_am_i)
|
||||
return Promise.resolve(parseInt(this._who_am_i["virtualserver_id"]));
|
||||
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
const single_handler: SingleCommandHandler = {
|
||||
function: command => {
|
||||
if(command.command != "" && command.command.indexOf("=") == -1)
|
||||
return false;
|
||||
|
||||
this._who_am_i = command.arguments[0];
|
||||
resolve(parseInt(this._who_am_i["virtualserver_id"]));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
|
||||
this.connection.send_command("whoami").catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,216 +1,222 @@
|
|||
namespace connection {
|
||||
export interface CommandOptions {
|
||||
flagset?: string[]; /* default: [] */
|
||||
process_result?: boolean; /* default: true */
|
||||
import {ConnectionHandler, ConnectionState} from "../ConnectionHandler";
|
||||
import {ServerAddress} from "../channel-tree/server";
|
||||
import {CommandResult} from "./ServerConnectionDeclaration";
|
||||
import {RecorderProfile} from "../voice/RecorderProfile";
|
||||
import {CommandHelper} from "./CommandHelper";
|
||||
import {connection} from "./HandshakeHandler";
|
||||
import HandshakeHandler = connection.HandshakeHandler;
|
||||
|
||||
timeout?: number /* default: 1000 */;
|
||||
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
|
||||
};
|
||||
|
||||
export type ConnectionStateListener = (old_state: ConnectionState, new_state: ConnectionState) => any;
|
||||
export abstract class AbstractServerConnection {
|
||||
readonly client: ConnectionHandler;
|
||||
readonly command_helper: CommandHelper;
|
||||
|
||||
protected constructor(client: ConnectionHandler) {
|
||||
this.client = client;
|
||||
|
||||
this.command_helper = new CommandHelper(this);
|
||||
}
|
||||
export const CommandOptionDefaults: CommandOptions = {
|
||||
flagset: [],
|
||||
process_result: true,
|
||||
timeout: 1000
|
||||
|
||||
/* 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;
|
||||
abstract voice_connection() : voice.AbstractVoiceConnection | undefined;
|
||||
|
||||
abstract command_handler_boss() : AbstractCommandHandlerBoss;
|
||||
abstract send_command(command: string, data?: any | any[], options?: CommandOptions) : Promise<CommandResult>;
|
||||
|
||||
abstract get onconnectionstatechanged() : ConnectionStateListener;
|
||||
abstract set onconnectionstatechanged(listener: ConnectionStateListener);
|
||||
|
||||
abstract remote_address() : ServerAddress; /* only valid when connected */
|
||||
abstract handshake_handler() : HandshakeHandler; /* only valid when connected */
|
||||
|
||||
abstract ping() : {
|
||||
native: number,
|
||||
javascript?: number
|
||||
};
|
||||
}
|
||||
|
||||
export type ConnectionStateListener = (old_state: ConnectionState, new_state: ConnectionState) => any;
|
||||
export abstract class AbstractServerConnection {
|
||||
readonly client: ConnectionHandler;
|
||||
readonly command_helper: CommandHelper;
|
||||
export namespace voice {
|
||||
export enum PlayerState {
|
||||
PREBUFFERING,
|
||||
PLAYING,
|
||||
BUFFERING,
|
||||
STOPPING,
|
||||
STOPPED
|
||||
}
|
||||
|
||||
protected constructor(client: ConnectionHandler) {
|
||||
this.client = client;
|
||||
export type LatencySettings = {
|
||||
min_buffer: number; /* milliseconds */
|
||||
max_buffer: number; /* milliseconds */
|
||||
}
|
||||
|
||||
this.command_helper = new CommandHelper(this);
|
||||
export interface VoiceClient {
|
||||
client_id: number;
|
||||
|
||||
callback_playback: () => any;
|
||||
callback_stopped: () => any;
|
||||
|
||||
callback_state_changed: (new_state: PlayerState) => any;
|
||||
|
||||
get_state() : PlayerState;
|
||||
|
||||
get_volume() : number;
|
||||
set_volume(volume: number) : void;
|
||||
|
||||
abort_replay();
|
||||
|
||||
support_latency_settings() : boolean;
|
||||
|
||||
reset_latency_settings();
|
||||
latency_settings(settings?: LatencySettings) : LatencySettings;
|
||||
|
||||
support_flush() : boolean;
|
||||
flush();
|
||||
}
|
||||
|
||||
export abstract class AbstractVoiceConnection {
|
||||
readonly connection: AbstractServerConnection;
|
||||
|
||||
protected constructor(connection: AbstractServerConnection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
/* 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 encoding_supported(codec: number) : boolean;
|
||||
abstract decoding_supported(codec: number) : boolean;
|
||||
|
||||
abstract support_voice() : boolean;
|
||||
abstract voice_connection() : voice.AbstractVoiceConnection | undefined;
|
||||
abstract register_client(client_id: number) : VoiceClient;
|
||||
abstract available_clients() : VoiceClient[];
|
||||
abstract unregister_client(client: VoiceClient) : Promise<void>;
|
||||
|
||||
abstract command_handler_boss() : AbstractCommandHandlerBoss;
|
||||
abstract send_command(command: string, data?: any | any[], options?: CommandOptions) : Promise<CommandResult>;
|
||||
abstract voice_recorder() : RecorderProfile;
|
||||
abstract acquire_voice_recorder(recorder: RecorderProfile | undefined) : Promise<void>;
|
||||
|
||||
abstract get onconnectionstatechanged() : ConnectionStateListener;
|
||||
abstract set onconnectionstatechanged(listener: ConnectionStateListener);
|
||||
abstract get_encoder_codec() : number;
|
||||
abstract set_encoder_codec(codec: number);
|
||||
}
|
||||
}
|
||||
|
||||
abstract remote_address() : ServerAddress; /* only valid when connected */
|
||||
abstract handshake_handler() : HandshakeHandler; /* only valid when connected */
|
||||
export class ServerCommand {
|
||||
command: string;
|
||||
arguments: any[];
|
||||
}
|
||||
|
||||
abstract ping() : {
|
||||
native: number,
|
||||
javascript?: number
|
||||
};
|
||||
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;
|
||||
}
|
||||
|
||||
export namespace voice {
|
||||
export enum PlayerState {
|
||||
PREBUFFERING,
|
||||
PLAYING,
|
||||
BUFFERING,
|
||||
STOPPING,
|
||||
STOPPED
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.command_handlers = undefined;
|
||||
this.single_command_handler = undefined;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export type LatencySettings = {
|
||||
min_buffer: number; /* milliseconds */
|
||||
max_buffer: number; /* milliseconds */
|
||||
}
|
||||
this.command_handlers.remove(handler);
|
||||
handler.handler_boss = undefined;
|
||||
}
|
||||
|
||||
export interface VoiceClient {
|
||||
client_id: number;
|
||||
|
||||
callback_playback: () => any;
|
||||
callback_stopped: () => any;
|
||||
register_single_handler(handler: SingleCommandHandler) {
|
||||
this.single_command_handler.push(handler);
|
||||
}
|
||||
|
||||
callback_state_changed: (new_state: PlayerState) => any;
|
||||
remove_single_handler(handler: SingleCommandHandler) {
|
||||
this.single_command_handler.remove(handler);
|
||||
}
|
||||
|
||||
get_state() : PlayerState;
|
||||
handlers() : AbstractCommandHandler[] {
|
||||
return this.command_handlers;
|
||||
}
|
||||
|
||||
get_volume() : number;
|
||||
set_volume(volume: number) : void;
|
||||
invoke_handle(command: ServerCommand) : boolean {
|
||||
let flag_consumed = false;
|
||||
|
||||
abort_replay();
|
||||
|
||||
support_latency_settings() : boolean;
|
||||
|
||||
reset_latency_settings();
|
||||
latency_settings(settings?: LatencySettings) : LatencySettings;
|
||||
|
||||
support_flush() : boolean;
|
||||
flush();
|
||||
}
|
||||
|
||||
export abstract class AbstractVoiceConnection {
|
||||
readonly connection: AbstractServerConnection;
|
||||
|
||||
protected constructor(connection: AbstractServerConnection) {
|
||||
this.connection = connection;
|
||||
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);
|
||||
}
|
||||
|
||||
abstract connected() : boolean;
|
||||
abstract encoding_supported(codec: number) : boolean;
|
||||
abstract decoding_supported(codec: number) : boolean;
|
||||
|
||||
abstract register_client(client_id: number) : VoiceClient;
|
||||
abstract available_clients() : VoiceClient[];
|
||||
abstract unregister_client(client: VoiceClient) : Promise<void>;
|
||||
|
||||
abstract voice_recorder() : RecorderProfile;
|
||||
abstract acquire_voice_recorder(recorder: RecorderProfile | undefined) : Promise<void>;
|
||||
|
||||
abstract get_encoder_codec() : number;
|
||||
abstract set_encoder_codec(codec: number);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
for(const handler of [...this.single_command_handler]) {
|
||||
if(handler.command && handler.command != command.command)
|
||||
continue;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.command_handlers = undefined;
|
||||
this.single_command_handler = undefined;
|
||||
}
|
||||
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return flag_consumed;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,15 @@
|
|||
namespace connection {
|
||||
import {AbstractServerConnection} from "./ConnectionBase";
|
||||
import {ConnectParameters, DisconnectReason} from "../ConnectionHandler";
|
||||
import {profiles} from "../profiles/ConnectionProfile";
|
||||
import {profiles as iprofiles} from "../profiles/Identity";
|
||||
import {profiles as tiprofiles} from "../profiles/identities/TeamSpeakIdentity";
|
||||
import {native_client} from "../main";
|
||||
import {settings} from "../settings";
|
||||
import {CommandResult} from "./ServerConnectionDeclaration";
|
||||
|
||||
export namespace connection {
|
||||
import identities = iprofiles.identities;
|
||||
|
||||
export interface HandshakeIdentityHandler {
|
||||
connection: AbstractServerConnection;
|
||||
|
||||
|
@ -48,7 +59,7 @@ namespace connection {
|
|||
|
||||
on_teamspeak() {
|
||||
const type = this.profile.selected_type();
|
||||
if(type == profiles.identities.IdentitifyType.TEAMSPEAK)
|
||||
if(type == identities.IdentitifyType.TEAMSPEAK)
|
||||
this.handshake_finished();
|
||||
else {
|
||||
|
||||
|
@ -122,8 +133,8 @@ namespace connection {
|
|||
}
|
||||
|
||||
/* required to keep compatibility */
|
||||
if(this.profile.selected_type() === profiles.identities.IdentitifyType.TEAMSPEAK) {
|
||||
data["client_key_offset"] = (this.profile.selected_identity() as profiles.identities.TeaSpeakIdentity).hash_number;
|
||||
if(this.profile.selected_type() === identities.IdentitifyType.TEAMSPEAK) {
|
||||
data["client_key_offset"] = (this.profile.selected_identity() as tiprofiles.identities.TeaSpeakIdentity).hash_number;
|
||||
}
|
||||
|
||||
this.connection.send_command("clientinit", data).catch(error => {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
enum ErrorID {
|
||||
import {LaterPromise} from "../utils/helpers";
|
||||
|
||||
export enum ErrorID {
|
||||
NOT_IMPLEMENTED = 0x2,
|
||||
COMMAND_NOT_FOUND = 0x100,
|
||||
|
||||
|
@ -15,7 +17,7 @@ enum ErrorID {
|
|||
CONVERSATION_IS_PRIVATE = 0x2202
|
||||
}
|
||||
|
||||
class CommandResult {
|
||||
export class CommandResult {
|
||||
success: boolean;
|
||||
id: number;
|
||||
message: string;
|
||||
|
@ -35,39 +37,39 @@ class CommandResult {
|
|||
}
|
||||
}
|
||||
|
||||
interface ClientNameInfo {
|
||||
export interface ClientNameInfo {
|
||||
//cluid=tYzKUryn\/\/Y8VBMf8PHUT6B1eiE= name=Exp clname=Exp cldbid=9
|
||||
client_unique_id: string;
|
||||
client_nickname: string;
|
||||
client_database_id: number;
|
||||
}
|
||||
|
||||
interface ClientNameFromUid {
|
||||
export interface ClientNameFromUid {
|
||||
promise: LaterPromise<ClientNameInfo[]>,
|
||||
keys: string[],
|
||||
response: ClientNameInfo[]
|
||||
}
|
||||
|
||||
interface ServerGroupClient {
|
||||
export interface ServerGroupClient {
|
||||
client_nickname: string;
|
||||
client_unique_identifier: string;
|
||||
client_database_id: number;
|
||||
}
|
||||
|
||||
interface QueryListEntry {
|
||||
export interface QueryListEntry {
|
||||
username: string;
|
||||
unique_id: string;
|
||||
bounded_server: number;
|
||||
}
|
||||
|
||||
interface QueryList {
|
||||
export interface QueryList {
|
||||
flag_own: boolean;
|
||||
flag_all: boolean;
|
||||
|
||||
queries: QueryListEntry[];
|
||||
}
|
||||
|
||||
interface Playlist {
|
||||
export interface Playlist {
|
||||
playlist_id: number;
|
||||
playlist_bot_id: number;
|
||||
playlist_title: string;
|
||||
|
@ -83,7 +85,7 @@ interface Playlist {
|
|||
needed_power_song_remove: number;
|
||||
}
|
||||
|
||||
interface PlaylistInfo {
|
||||
export interface PlaylistInfo {
|
||||
playlist_id: number,
|
||||
playlist_title: string,
|
||||
playlist_description: string,
|
||||
|
@ -100,7 +102,7 @@ interface PlaylistInfo {
|
|||
playlist_max_songs: number
|
||||
}
|
||||
|
||||
interface PlaylistSong {
|
||||
export interface PlaylistSong {
|
||||
song_id: number;
|
||||
song_previous_song_id: number;
|
||||
song_invoker: string;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
namespace asn1 {
|
||||
export namespace asn1 {
|
||||
declare class Int10 {
|
||||
constructor(value?: any);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class Crc32 {
|
||||
export class Crc32 {
|
||||
private static readonly lookup = [
|
||||
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
|
||||
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace hex {
|
||||
export namespace hex {
|
||||
export function encode(buffer) {
|
||||
let hexCodes = [];
|
||||
let view = new DataView(buffer);
|
||||
|
|
|
@ -10,7 +10,7 @@ interface Window {
|
|||
}
|
||||
*/
|
||||
|
||||
namespace sha {
|
||||
export namespace sha {
|
||||
/*
|
||||
* [js-sha1]{@link https://github.com/emn178/js-sha1}
|
||||
*
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
export function guid() {
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
}
|
||||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace dns {
|
||||
export namespace dns {
|
||||
export interface AddressTarget {
|
||||
target_ip: string;
|
||||
target_port?: number;
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
namespace events {
|
||||
import {guid} from "./crypto/uid";
|
||||
import {PlaylistSong} from "./connection/ServerConnectionDeclaration";
|
||||
import {MusicClientEntry, SongInfo} from "./channel-tree/client";
|
||||
|
||||
export namespace events {
|
||||
export interface EventConvert<All> {
|
||||
as<T extends keyof All>() : All[T];
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
namespace i18n {
|
||||
export namespace i18n {
|
||||
interface CountryInfo {
|
||||
name: string;
|
||||
alpha_2: string;
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
function guid() {
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
}
|
||||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
|
||||
}
|
||||
import {guid} from "../crypto/uid";
|
||||
import {log, LogCategory} from "../log";
|
||||
import {MessageHelper} from "../ui/frames/chat";
|
||||
import {StaticSettings} from "../settings";
|
||||
import {createErrorModal} from "../ui/elements/modal";
|
||||
|
||||
namespace i18n {
|
||||
export namespace i18n {
|
||||
export interface TranslationKey {
|
||||
message: string;
|
||||
line?: number;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
//Used by CertAccept popup
|
||||
|
||||
enum LogCategory {
|
||||
import {settings} from "./settings";
|
||||
|
||||
export enum LogCategory {
|
||||
CHANNEL,
|
||||
CHANNEL_PROPERTIES, /* separating channel and channel properties because on channel init logging is a big bottleneck */
|
||||
CLIENT,
|
||||
|
@ -19,7 +21,7 @@ enum LogCategory {
|
|||
DNS
|
||||
}
|
||||
|
||||
namespace log {
|
||||
export namespace log {
|
||||
export enum LogType {
|
||||
TRACE,
|
||||
DEBUG,
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
/// <reference path="ui/frames/chat.ts" />
|
||||
/// <reference path="ui/modal/ModalConnect.ts" />
|
||||
/// <reference path="ui/modal/ModalCreateChannel.ts" />
|
||||
/// <reference path="ui/modal/ModalBanClient.ts" />
|
||||
/// <reference path="ui/modal/ModalYesNo.ts" />
|
||||
/// <reference path="ui/modal/ModalBanList.ts" />
|
||||
/// <reference path="settings.ts" />
|
||||
/// <reference path="log.ts" />
|
||||
/// <reference path="PPTListener.ts" />
|
||||
|
||||
import spawnYesNo = Modals.spawnYesNo;
|
||||
import {ConnectionHandler} from "./ConnectionHandler";
|
||||
import {bipc} from "./BrowserIPC";
|
||||
import {log, LogCategory} from "./log";
|
||||
import {profiles} from "./profiles/ConnectionProfile";
|
||||
import {Modals} from "./ui/modal/ModalConnect";
|
||||
import {settings, Settings} from "./settings";
|
||||
import {i18n} from "./i18n/localize";
|
||||
import {createInfoModal} from "./ui/elements/modal";
|
||||
import {MessageHelper} from "./ui/frames/chat";
|
||||
|
||||
const js_render = window.jsrender || $;
|
||||
const native_client = window.require !== undefined;
|
||||
export const js_render = window.jsrender || $;
|
||||
export const native_client = window.require !== undefined;
|
||||
|
||||
function getUserMediaFunctionPromise() : (constraints: MediaStreamConstraints) => Promise<MediaStream> {
|
||||
export function getUserMediaFunctionPromise() : (constraints: MediaStreamConstraints) => Promise<MediaStream> {
|
||||
if('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices)
|
||||
return constraints => navigator.mediaDevices.getUserMedia(constraints);
|
||||
|
||||
|
@ -24,11 +23,12 @@ function getUserMediaFunctionPromise() : (constraints: MediaStreamConstraints) =
|
|||
return constraints => new Promise<MediaStream>((resolve, reject) => _callbacked_function(constraints, resolve, reject));
|
||||
}
|
||||
|
||||
interface Window {
|
||||
export interface Window {
|
||||
open_connected_question: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
function setup_close() {
|
||||
export declare const nodeRequire: typeof require;
|
||||
export function setup_close() {
|
||||
window.onbeforeunload = event => {
|
||||
if(profiles.requires_save())
|
||||
profiles.save();
|
||||
|
@ -50,7 +50,7 @@ function setup_close() {
|
|||
}));
|
||||
|
||||
const exit = () => {
|
||||
const {remote} = require('electron');
|
||||
const {remote} = nodeRequire('electron');
|
||||
remote.getCurrentWindow().close();
|
||||
};
|
||||
|
||||
|
@ -80,8 +80,8 @@ function setup_close() {
|
|||
};
|
||||
}
|
||||
|
||||
declare function moment(...arguments) : any;
|
||||
function setup_jsrender() : boolean {
|
||||
export declare function moment(...arguments) : any;
|
||||
export function setup_jsrender() : boolean {
|
||||
if(!js_render) {
|
||||
loader.critical_error("Missing jsrender extension!");
|
||||
return false;
|
||||
|
@ -115,7 +115,7 @@ function setup_jsrender() : boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
async function initialize() {
|
||||
export async function initialize() {
|
||||
Settings.initialize();
|
||||
|
||||
try {
|
||||
|
@ -129,7 +129,7 @@ async function initialize() {
|
|||
bipc.setup();
|
||||
}
|
||||
|
||||
async function initialize_app() {
|
||||
export async function initialize_app() {
|
||||
try { //Initialize main template
|
||||
const main = $("#tmpl_main").renderTag({
|
||||
multi_session: !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION),
|
||||
|
@ -180,7 +180,7 @@ async function initialize_app() {
|
|||
setup_close();
|
||||
}
|
||||
|
||||
function str2ab8(str) {
|
||||
export function str2ab8(str) {
|
||||
const buf = new ArrayBuffer(str.length);
|
||||
const bufView = new Uint8Array(buf);
|
||||
for (let i = 0, strLen = str.length; i < strLen; i++) {
|
||||
|
@ -190,7 +190,7 @@ function str2ab8(str) {
|
|||
}
|
||||
|
||||
/* FIXME Dont use atob, because it sucks for non UTF-8 tings */
|
||||
function arrayBufferBase64(base64: string) {
|
||||
export function arrayBufferBase64(base64: string) {
|
||||
base64 = atob(base64);
|
||||
const buf = new ArrayBuffer(base64.length);
|
||||
const bufView = new Uint8Array(buf);
|
||||
|
@ -200,7 +200,7 @@ function arrayBufferBase64(base64: string) {
|
|||
return buf;
|
||||
}
|
||||
|
||||
function base64_encode_ab(source: ArrayBufferLike) {
|
||||
export function base64_encode_ab(source: ArrayBufferLike) {
|
||||
const encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
let base64 = "";
|
||||
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
/// <reference path="../connection/ConnectionBase.ts" />
|
||||
|
||||
enum GroupType {
|
||||
import {LaterPromise} from "../utils/helpers";
|
||||
import {PermissionManager, PermissionValue} from "./PermissionManager";
|
||||
import {AbstractCommandHandler, ServerCommand} from "../connection/ConnectionBase";
|
||||
import {ConnectionHandler} from "../ConnectionHandler";
|
||||
import {log, LogCategory} from "../log";
|
||||
import {CommandResult} from "../connection/ServerConnectionDeclaration";
|
||||
|
||||
export enum GroupType {
|
||||
QUERY,
|
||||
TEMPLATE,
|
||||
NORMAL
|
||||
}
|
||||
|
||||
enum GroupTarget {
|
||||
export enum GroupTarget {
|
||||
SERVER,
|
||||
CHANNEL
|
||||
}
|
||||
|
||||
class GroupProperties {
|
||||
export class GroupProperties {
|
||||
iconid: number = 0;
|
||||
|
||||
sortid: number = 0;
|
||||
|
@ -19,12 +26,12 @@ class GroupProperties {
|
|||
namemode: number = 0;
|
||||
}
|
||||
|
||||
class GroupPermissionRequest {
|
||||
export class GroupPermissionRequest {
|
||||
group_id: number;
|
||||
promise: LaterPromise<PermissionValue[]>;
|
||||
}
|
||||
|
||||
class Group {
|
||||
export class Group {
|
||||
properties: GroupProperties = new GroupProperties();
|
||||
|
||||
readonly handle: GroupManager;
|
||||
|
@ -63,7 +70,7 @@ class Group {
|
|||
}
|
||||
}
|
||||
|
||||
class GroupManager extends connection.AbstractCommandHandler {
|
||||
export class GroupManager extends AbstractCommandHandler {
|
||||
readonly handle: ConnectionHandler;
|
||||
|
||||
serverGroups: Group[] = [];
|
||||
|
@ -83,7 +90,7 @@ class GroupManager extends connection.AbstractCommandHandler {
|
|||
this.channelGroups = undefined;
|
||||
}
|
||||
|
||||
handle_command(command: connection.ServerCommand): boolean {
|
||||
handle_command(command: ServerCommand): boolean {
|
||||
switch (command.command) {
|
||||
case "notifyservergrouplist":
|
||||
case "notifychannelgrouplist":
|
||||
|
|
|
@ -2,7 +2,14 @@
|
|||
/// <reference path="../connection/ConnectionBase.ts" />
|
||||
/// <reference path="../i18n/localize.ts" />
|
||||
|
||||
enum PermissionType {
|
||||
import {ConnectionHandler} from "../ConnectionHandler";
|
||||
import {log, LogCategory} from "../log";
|
||||
import {LaterPromise} from "../utils/helpers";
|
||||
import {AbstractCommandHandler, ServerCommand} from "../connection/ConnectionBase";
|
||||
import LogType = log.LogType;
|
||||
import {CommandResult, ErrorID} from "../connection/ServerConnectionDeclaration";
|
||||
|
||||
export enum PermissionType {
|
||||
B_SERVERINSTANCE_HELP_VIEW = "b_serverinstance_help_view",
|
||||
B_SERVERINSTANCE_VERSION_VIEW = "b_serverinstance_version_view",
|
||||
B_SERVERINSTANCE_INFO_VIEW = "b_serverinstance_info_view",
|
||||
|
@ -352,7 +359,7 @@ enum PermissionType {
|
|||
I_FT_QUOTA_MB_UPLOAD_PER_CLIENT = "i_ft_quota_mb_upload_per_client"
|
||||
}
|
||||
|
||||
class PermissionInfo {
|
||||
export class PermissionInfo {
|
||||
name: string;
|
||||
id: number;
|
||||
description: string;
|
||||
|
@ -363,21 +370,21 @@ class PermissionInfo {
|
|||
}
|
||||
}
|
||||
|
||||
class PermissionGroup {
|
||||
export class PermissionGroup {
|
||||
begin: number;
|
||||
end: number;
|
||||
deep: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
class GroupedPermissions {
|
||||
export class GroupedPermissions {
|
||||
group: PermissionGroup;
|
||||
permissions: PermissionInfo[];
|
||||
children: GroupedPermissions[];
|
||||
parent: GroupedPermissions;
|
||||
}
|
||||
|
||||
class PermissionValue {
|
||||
export class PermissionValue {
|
||||
readonly type: PermissionInfo;
|
||||
value: number;
|
||||
flag_skip: boolean;
|
||||
|
@ -411,13 +418,13 @@ class PermissionValue {
|
|||
}
|
||||
}
|
||||
|
||||
class NeededPermissionValue extends PermissionValue {
|
||||
export class NeededPermissionValue extends PermissionValue {
|
||||
constructor(type, value) {
|
||||
super(type, value);
|
||||
}
|
||||
}
|
||||
|
||||
namespace permissions {
|
||||
export namespace permissions {
|
||||
export type PermissionRequestKeys = {
|
||||
client_id?: number;
|
||||
channel_id?: number;
|
||||
|
@ -473,13 +480,13 @@ namespace permissions {
|
|||
}
|
||||
}
|
||||
|
||||
type RequestLists =
|
||||
export type RequestLists =
|
||||
"requests_channel_permissions" |
|
||||
"requests_client_permissions" |
|
||||
"requests_client_channel_permissions" |
|
||||
"requests_playlist_permissions" |
|
||||
"requests_playlist_client_permissions";
|
||||
class PermissionManager extends connection.AbstractCommandHandler {
|
||||
export class PermissionManager extends AbstractCommandHandler {
|
||||
readonly handle: ConnectionHandler;
|
||||
|
||||
permissionList: PermissionInfo[] = [];
|
||||
|
@ -603,7 +610,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
|||
this._cacheNeededPermissions = undefined;
|
||||
}
|
||||
|
||||
handle_command(command: connection.ServerCommand): boolean {
|
||||
handle_command(command: ServerCommand): boolean {
|
||||
switch (command.command) {
|
||||
case "notifyclientneededpermissions":
|
||||
this.onNeededPermissions(command.arguments);
|
||||
|
|
|
@ -1,251 +1,260 @@
|
|||
namespace profiles {
|
||||
export class ConnectionProfile {
|
||||
id: string;
|
||||
import {MessageHelper} from "../ui/frames/chat";
|
||||
import {createErrorModal} from "../ui/elements/modal";
|
||||
import {guid} from "../crypto/uid";
|
||||
import {decode_identity, IdentitifyType, Identity} from "./Identity";
|
||||
import {static_forum_identity} from "./identities/TeaForumIdentity";
|
||||
import {TeaSpeakIdentity} from "./identities/TeamSpeakIdentity";
|
||||
import {AbstractServerConnection} from "../connection/ConnectionBase";
|
||||
import {connection} from "../connection/HandshakeHandler";
|
||||
|
||||
profile_name: string;
|
||||
default_username: string;
|
||||
default_password: string;
|
||||
import HandshakeIdentityHandler = connection.HandshakeIdentityHandler;
|
||||
|
||||
selected_identity_type: string = "unset";
|
||||
identities: { [key: string]: identities.Identity } = {};
|
||||
export class ConnectionProfile {
|
||||
id: string;
|
||||
|
||||
constructor(id: string) {
|
||||
this.id = id;
|
||||
}
|
||||
profile_name: string;
|
||||
default_username: string;
|
||||
default_password: string;
|
||||
|
||||
connect_username(): string {
|
||||
if (this.default_username && this.default_username !== "Another TeaSpeak user")
|
||||
return this.default_username;
|
||||
selected_identity_type: string = "unset";
|
||||
identities: { [key: string]: Identity } = {};
|
||||
|
||||
let selected = this.selected_identity();
|
||||
let name = selected ? selected.fallback_name() : undefined;
|
||||
return name || "Another TeaSpeak user";
|
||||
}
|
||||
constructor(id: string) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
selected_identity(current_type?: identities.IdentitifyType): identities.Identity {
|
||||
if (!current_type)
|
||||
current_type = this.selected_type();
|
||||
connect_username(): string {
|
||||
if (this.default_username && this.default_username !== "Another TeaSpeak user")
|
||||
return this.default_username;
|
||||
|
||||
if (current_type === undefined)
|
||||
return undefined;
|
||||
let selected = this.selected_identity();
|
||||
let name = selected ? selected.fallback_name() : undefined;
|
||||
return name || "Another TeaSpeak user";
|
||||
}
|
||||
|
||||
if (current_type == identities.IdentitifyType.TEAFORO) {
|
||||
return identities.static_forum_identity();
|
||||
} else if (current_type == identities.IdentitifyType.TEAMSPEAK || current_type == identities.IdentitifyType.NICKNAME) {
|
||||
return this.identities[identities.IdentitifyType[current_type].toLowerCase()];
|
||||
}
|
||||
selected_identity(current_type?: IdentitifyType): Identity {
|
||||
if (!current_type)
|
||||
current_type = this.selected_type();
|
||||
|
||||
if (current_type === undefined)
|
||||
return undefined;
|
||||
|
||||
if (current_type == IdentitifyType.TEAFORO) {
|
||||
return static_forum_identity();
|
||||
} else if (current_type == IdentitifyType.TEAMSPEAK || current_type == IdentitifyType.NICKNAME) {
|
||||
return this.identities[IdentitifyType[current_type].toLowerCase()];
|
||||
}
|
||||
|
||||
selected_type?(): identities.IdentitifyType {
|
||||
return this.selected_identity_type ? identities.IdentitifyType[this.selected_identity_type.toUpperCase()] : undefined;
|
||||
}
|
||||
|
||||
set_identity(type: identities.IdentitifyType, identity: identities.Identity) {
|
||||
this.identities[identities.IdentitifyType[type].toLowerCase()] = identity;
|
||||
}
|
||||
|
||||
spawn_identity_handshake_handler?(connection: connection.AbstractServerConnection): connection.HandshakeIdentityHandler {
|
||||
const identity = this.selected_identity();
|
||||
if (!identity)
|
||||
return undefined;
|
||||
return identity.spawn_identity_handshake_handler(connection);
|
||||
}
|
||||
|
||||
encode?(): string {
|
||||
const identity_data = {};
|
||||
for (const key in this.identities)
|
||||
if (this.identities[key])
|
||||
identity_data[key] = this.identities[key].encode();
|
||||
|
||||
return JSON.stringify({
|
||||
version: 1,
|
||||
username: this.default_username,
|
||||
password: this.default_password,
|
||||
profile_name: this.profile_name,
|
||||
identity_type: this.selected_identity_type,
|
||||
identity_data: identity_data,
|
||||
id: this.id
|
||||
});
|
||||
}
|
||||
|
||||
valid(): boolean {
|
||||
const identity = this.selected_identity();
|
||||
if (!identity || !identity.valid()) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function decode_profile(data): Promise<ConnectionProfile | string> {
|
||||
data = JSON.parse(data);
|
||||
if (data.version !== 1)
|
||||
return "invalid version";
|
||||
|
||||
const result: ConnectionProfile = new ConnectionProfile(data.id);
|
||||
result.default_username = data.username;
|
||||
result.default_password = data.password;
|
||||
result.profile_name = data.profile_name;
|
||||
result.selected_identity_type = (data.identity_type || "").toLowerCase();
|
||||
|
||||
if (data.identity_data) {
|
||||
for (const key in data.identity_data) {
|
||||
const type = identities.IdentitifyType[key.toUpperCase() as string];
|
||||
const _data = data.identity_data[key];
|
||||
if (type == undefined) continue;
|
||||
|
||||
const identity = await identities.decode_identity(type, _data);
|
||||
if (identity == undefined) continue;
|
||||
|
||||
result.identities[key.toLowerCase()] = identity;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
selected_type?(): IdentitifyType {
|
||||
return this.selected_identity_type ? IdentitifyType[this.selected_identity_type.toUpperCase()] : undefined;
|
||||
}
|
||||
|
||||
interface ProfilesData {
|
||||
version: number;
|
||||
profiles: string[];
|
||||
set_identity(type: IdentitifyType, identity: Identity) {
|
||||
this.identities[IdentitifyType[type].toLowerCase()] = identity;
|
||||
}
|
||||
|
||||
let available_profiles: ConnectionProfile[] = [];
|
||||
|
||||
export async function load() {
|
||||
available_profiles = [];
|
||||
|
||||
const profiles_json = localStorage.getItem("profiles");
|
||||
let profiles_data: ProfilesData = (() => {
|
||||
try {
|
||||
return profiles_json ? JSON.parse(profiles_json) : {version: 0} as any;
|
||||
} catch (error) {
|
||||
debugger;
|
||||
console.error(tr("Invalid profile json! Resetting profiles :( (%o)"), profiles_json);
|
||||
createErrorModal(tr("Profile data invalid"), MessageHelper.formatMessage(tr("The profile data is invalid.{:br:}This might cause data loss."))).open();
|
||||
return {version: 0};
|
||||
}
|
||||
})();
|
||||
|
||||
if (profiles_data.version === 0) {
|
||||
profiles_data = {
|
||||
version: 1,
|
||||
profiles: []
|
||||
};
|
||||
}
|
||||
if (profiles_data.version == 1) {
|
||||
for (const profile_data of profiles_data.profiles) {
|
||||
const profile = await decode_profile(profile_data);
|
||||
if (typeof (profile) === 'string') {
|
||||
console.error(tr("Failed to load profile. Reason: %s, Profile data: %s"), profile, profiles_data);
|
||||
continue;
|
||||
}
|
||||
available_profiles.push(profile);
|
||||
}
|
||||
}
|
||||
|
||||
if (!find_profile("default")) { //Create a default profile and teaforo profile
|
||||
{
|
||||
const profile = create_new_profile("default", "default");
|
||||
profile.default_password = "";
|
||||
profile.default_username = "";
|
||||
profile.profile_name = "Default Profile";
|
||||
|
||||
/* generate default identity */
|
||||
try {
|
||||
const identity = await identities.TeaSpeakIdentity.generate_new();
|
||||
let active = true;
|
||||
setTimeout(() => {
|
||||
active = false;
|
||||
}, 1000);
|
||||
await identity.improve_level(8, 1, () => active);
|
||||
profile.set_identity(identities.IdentitifyType.TEAMSPEAK, identity);
|
||||
profile.selected_identity_type = identities.IdentitifyType[identities.IdentitifyType.TEAMSPEAK];
|
||||
} catch (error) {
|
||||
createErrorModal(tr("Failed to generate default identity"), tr("Failed to generate default identity!<br>Please manually generate the identity within your settings => profiles")).open();
|
||||
}
|
||||
}
|
||||
|
||||
{ /* forum identity (works only when connected to the forum) */
|
||||
const profile = create_new_profile("TeaSpeak Forum", "teaforo");
|
||||
profile.default_password = "";
|
||||
profile.default_username = "";
|
||||
profile.profile_name = "TeaSpeak Forum profile";
|
||||
|
||||
profile.set_identity(identities.IdentitifyType.TEAFORO, identities.static_forum_identity());
|
||||
profile.selected_identity_type = identities.IdentitifyType[identities.IdentitifyType.TEAFORO];
|
||||
}
|
||||
|
||||
save();
|
||||
}
|
||||
spawn_identity_handshake_handler?(connection: AbstractServerConnection): HandshakeIdentityHandler {
|
||||
const identity = this.selected_identity();
|
||||
if (!identity)
|
||||
return undefined;
|
||||
return identity.spawn_identity_handshake_handler(connection);
|
||||
}
|
||||
|
||||
export function create_new_profile(name: string, id?: string): ConnectionProfile {
|
||||
const profile = new ConnectionProfile(id || guid());
|
||||
profile.profile_name = name;
|
||||
profile.default_username = "";
|
||||
available_profiles.push(profile);
|
||||
return profile;
|
||||
}
|
||||
encode?(): string {
|
||||
const identity_data = {};
|
||||
for (const key in this.identities)
|
||||
if (this.identities[key])
|
||||
identity_data[key] = this.identities[key].encode();
|
||||
|
||||
let _requires_save = false;
|
||||
|
||||
export function save() {
|
||||
const profiles: string[] = [];
|
||||
for (const profile of available_profiles)
|
||||
profiles.push(profile.encode());
|
||||
|
||||
const data = JSON.stringify({
|
||||
return JSON.stringify({
|
||||
version: 1,
|
||||
profiles: profiles
|
||||
username: this.default_username,
|
||||
password: this.default_password,
|
||||
profile_name: this.profile_name,
|
||||
identity_type: this.selected_identity_type,
|
||||
identity_data: identity_data,
|
||||
id: this.id
|
||||
});
|
||||
localStorage.setItem("profiles", data);
|
||||
}
|
||||
|
||||
export function mark_need_save() {
|
||||
_requires_save = true;
|
||||
valid(): boolean {
|
||||
const identity = this.selected_identity();
|
||||
if (!identity || !identity.valid()) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function requires_save(): boolean {
|
||||
return _requires_save;
|
||||
}
|
||||
async function decode_profile(data): Promise<ConnectionProfile | string> {
|
||||
data = JSON.parse(data);
|
||||
if (data.version !== 1)
|
||||
return "invalid version";
|
||||
|
||||
export function profiles(): ConnectionProfile[] {
|
||||
return available_profiles;
|
||||
}
|
||||
const result: ConnectionProfile = new ConnectionProfile(data.id);
|
||||
result.default_username = data.username;
|
||||
result.default_password = data.password;
|
||||
result.profile_name = data.profile_name;
|
||||
result.selected_identity_type = (data.identity_type || "").toLowerCase();
|
||||
|
||||
export function find_profile(id: string): ConnectionProfile | undefined {
|
||||
for (const profile of profiles())
|
||||
if (profile.id == id)
|
||||
return profile;
|
||||
if (data.identity_data) {
|
||||
for (const key in data.identity_data) {
|
||||
const type = IdentitifyType[key.toUpperCase() as string];
|
||||
const _data = data.identity_data[key];
|
||||
if (type == undefined) continue;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
const identity = await decode_identity(type, _data);
|
||||
if (identity == undefined) continue;
|
||||
|
||||
export function find_profile_by_name(name: string): ConnectionProfile | undefined {
|
||||
name = name.toLowerCase();
|
||||
for (const profile of profiles())
|
||||
if ((profile.profile_name || "").toLowerCase() == name)
|
||||
return profile;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
export function default_profile(): ConnectionProfile {
|
||||
return find_profile("default");
|
||||
}
|
||||
|
||||
export function set_default_profile(profile: ConnectionProfile) {
|
||||
const old_default = default_profile();
|
||||
if (old_default && old_default != profile) {
|
||||
old_default.id = guid();
|
||||
result.identities[key.toLowerCase()] = identity;
|
||||
}
|
||||
profile.id = "default";
|
||||
return old_default;
|
||||
}
|
||||
|
||||
export function delete_profile(profile: ConnectionProfile) {
|
||||
available_profiles.remove(profile);
|
||||
return result;
|
||||
}
|
||||
|
||||
interface ProfilesData {
|
||||
version: number;
|
||||
profiles: string[];
|
||||
}
|
||||
|
||||
let available_profiles: ConnectionProfile[] = [];
|
||||
|
||||
export async function load() {
|
||||
available_profiles = [];
|
||||
|
||||
const profiles_json = localStorage.getItem("profiles");
|
||||
let profiles_data: ProfilesData = (() => {
|
||||
try {
|
||||
return profiles_json ? JSON.parse(profiles_json) : {version: 0} as any;
|
||||
} catch (error) {
|
||||
debugger;
|
||||
console.error(tr("Invalid profile json! Resetting profiles :( (%o)"), profiles_json);
|
||||
createErrorModal(tr("Profile data invalid"), MessageHelper.formatMessage(tr("The profile data is invalid.{:br:}This might cause data loss."))).open();
|
||||
return {version: 0};
|
||||
}
|
||||
})();
|
||||
|
||||
if (profiles_data.version === 0) {
|
||||
profiles_data = {
|
||||
version: 1,
|
||||
profiles: []
|
||||
};
|
||||
}
|
||||
if (profiles_data.version == 1) {
|
||||
for (const profile_data of profiles_data.profiles) {
|
||||
const profile = await decode_profile(profile_data);
|
||||
if (typeof (profile) === 'string') {
|
||||
console.error(tr("Failed to load profile. Reason: %s, Profile data: %s"), profile, profiles_data);
|
||||
continue;
|
||||
}
|
||||
available_profiles.push(profile);
|
||||
}
|
||||
}
|
||||
|
||||
if (!find_profile("default")) { //Create a default profile and teaforo profile
|
||||
{
|
||||
const profile = create_new_profile("default", "default");
|
||||
profile.default_password = "";
|
||||
profile.default_username = "";
|
||||
profile.profile_name = "Default Profile";
|
||||
|
||||
/* generate default identity */
|
||||
try {
|
||||
const identity = await TeaSpeakIdentity.generate_new();
|
||||
let active = true;
|
||||
setTimeout(() => {
|
||||
active = false;
|
||||
}, 1000);
|
||||
await identity.improve_level(8, 1, () => active);
|
||||
profile.set_identity(IdentitifyType.TEAMSPEAK, identity);
|
||||
profile.selected_identity_type = IdentitifyType[IdentitifyType.TEAMSPEAK];
|
||||
} catch (error) {
|
||||
createErrorModal(tr("Failed to generate default identity"), tr("Failed to generate default identity!<br>Please manually generate the identity within your settings => profiles")).open();
|
||||
}
|
||||
}
|
||||
|
||||
{ /* forum identity (works only when connected to the forum) */
|
||||
const profile = create_new_profile("TeaSpeak Forum", "teaforo");
|
||||
profile.default_password = "";
|
||||
profile.default_username = "";
|
||||
profile.profile_name = "TeaSpeak Forum profile";
|
||||
|
||||
profile.set_identity(IdentitifyType.TEAFORO, static_forum_identity());
|
||||
profile.selected_identity_type = IdentitifyType[IdentitifyType.TEAFORO];
|
||||
}
|
||||
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
export function create_new_profile(name: string, id?: string): ConnectionProfile {
|
||||
const profile = new ConnectionProfile(id || guid());
|
||||
profile.profile_name = name;
|
||||
profile.default_username = "";
|
||||
available_profiles.push(profile);
|
||||
return profile;
|
||||
}
|
||||
|
||||
let _requires_save = false;
|
||||
|
||||
export function save() {
|
||||
const profiles: string[] = [];
|
||||
for (const profile of available_profiles)
|
||||
profiles.push(profile.encode());
|
||||
|
||||
const data = JSON.stringify({
|
||||
version: 1,
|
||||
profiles: profiles
|
||||
});
|
||||
localStorage.setItem("profiles", data);
|
||||
}
|
||||
|
||||
export function mark_need_save() {
|
||||
_requires_save = true;
|
||||
}
|
||||
|
||||
export function requires_save(): boolean {
|
||||
return _requires_save;
|
||||
}
|
||||
|
||||
export function profiles(): ConnectionProfile[] {
|
||||
return available_profiles;
|
||||
}
|
||||
|
||||
export function find_profile(id: string): ConnectionProfile | undefined {
|
||||
for (const profile of profiles())
|
||||
if (profile.id == id)
|
||||
return profile;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function find_profile_by_name(name: string): ConnectionProfile | undefined {
|
||||
name = name.toLowerCase();
|
||||
for (const profile of profiles())
|
||||
if ((profile.profile_name || "").toLowerCase() == name)
|
||||
return profile;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
export function default_profile(): ConnectionProfile {
|
||||
return find_profile("default");
|
||||
}
|
||||
|
||||
export function set_default_profile(profile: ConnectionProfile) {
|
||||
const old_default = default_profile();
|
||||
if (old_default && old_default != profile) {
|
||||
old_default.id = guid();
|
||||
}
|
||||
profile.id = "default";
|
||||
return old_default;
|
||||
}
|
||||
|
||||
export function delete_profile(profile: ConnectionProfile) {
|
||||
available_profiles.remove(profile);
|
||||
}
|
|
@ -1,110 +1,116 @@
|
|||
namespace profiles.identities {
|
||||
export enum IdentitifyType {
|
||||
TEAFORO,
|
||||
TEAMSPEAK,
|
||||
NICKNAME
|
||||
import {AbstractCommandHandler, AbstractServerConnection, ServerCommand} from "../connection/ConnectionBase";
|
||||
import {connection} from "../connection/HandshakeHandler";
|
||||
|
||||
import HandshakeIdentityHandler = connection.HandshakeIdentityHandler;
|
||||
import {NameIdentity} from "./identities/NameIdentity";
|
||||
import {TeaForumIdentity} from "./identities/TeaForumIdentity";
|
||||
import {TeaSpeakIdentity} from "./identities/TeamSpeakIdentity";
|
||||
|
||||
export enum IdentitifyType {
|
||||
TEAFORO,
|
||||
TEAMSPEAK,
|
||||
NICKNAME
|
||||
}
|
||||
|
||||
export interface Identity {
|
||||
fallback_name(): string | undefined ;
|
||||
uid() : string;
|
||||
type() : IdentitifyType;
|
||||
|
||||
valid() : boolean;
|
||||
|
||||
encode?() : string;
|
||||
decode(data: string) : Promise<void>;
|
||||
|
||||
spawn_identity_handshake_handler(connection: AbstractServerConnection) : HandshakeIdentityHandler;
|
||||
}
|
||||
|
||||
export async function decode_identity(type: IdentitifyType, data: string) : Promise<Identity> {
|
||||
let identity: Identity;
|
||||
switch (type) {
|
||||
case IdentitifyType.NICKNAME:
|
||||
identity = new NameIdentity();
|
||||
break;
|
||||
case IdentitifyType.TEAFORO:
|
||||
identity = new TeaForumIdentity(undefined);
|
||||
break;
|
||||
case IdentitifyType.TEAMSPEAK:
|
||||
identity = new TeaSpeakIdentity(undefined, undefined);
|
||||
break;
|
||||
}
|
||||
if(!identity)
|
||||
return undefined;
|
||||
|
||||
try {
|
||||
await identity.decode(data)
|
||||
} catch(error) {
|
||||
/* todo better error handling! */
|
||||
console.error(error);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export interface Identity {
|
||||
fallback_name(): string | undefined ;
|
||||
uid() : string;
|
||||
type() : IdentitifyType;
|
||||
return identity;
|
||||
}
|
||||
|
||||
valid() : boolean;
|
||||
export function create_identity(type: IdentitifyType) {
|
||||
let identity: Identity;
|
||||
switch (type) {
|
||||
case IdentitifyType.NICKNAME:
|
||||
identity = new NameIdentity();
|
||||
break;
|
||||
case IdentitifyType.TEAFORO:
|
||||
identity = new TeaForumIdentity(undefined);
|
||||
break;
|
||||
case IdentitifyType.TEAMSPEAK:
|
||||
identity = new TeaSpeakIdentity(undefined, undefined);
|
||||
break;
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
encode?() : string;
|
||||
decode(data: string) : Promise<void>;
|
||||
export class HandshakeCommandHandler<T extends AbstractHandshakeIdentityHandler> extends AbstractCommandHandler {
|
||||
readonly handle: T;
|
||||
|
||||
spawn_identity_handshake_handler(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler;
|
||||
constructor(connection: AbstractServerConnection, handle: T) {
|
||||
super(connection);
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
export async function decode_identity(type: IdentitifyType, data: string) : Promise<Identity> {
|
||||
let identity: Identity;
|
||||
switch (type) {
|
||||
case IdentitifyType.NICKNAME:
|
||||
identity = new NameIdentity();
|
||||
break;
|
||||
case IdentitifyType.TEAFORO:
|
||||
identity = new TeaForumIdentity(undefined);
|
||||
break;
|
||||
case IdentitifyType.TEAMSPEAK:
|
||||
identity = new TeaSpeakIdentity(undefined, undefined);
|
||||
break;
|
||||
}
|
||||
if(!identity)
|
||||
return undefined;
|
||||
|
||||
try {
|
||||
await identity.decode(data)
|
||||
} catch(error) {
|
||||
/* todo better error handling! */
|
||||
console.error(error);
|
||||
return undefined;
|
||||
handle_command(command: ServerCommand): boolean {
|
||||
if($.isFunction(this[command.command]))
|
||||
this[command.command](command.arguments);
|
||||
else if(command.command == "error") {
|
||||
return false;
|
||||
} else {
|
||||
console.warn(tr("Received unknown command while handshaking (%o)"), command);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return identity;
|
||||
export abstract class AbstractHandshakeIdentityHandler implements connection.HandshakeIdentityHandler {
|
||||
connection: AbstractServerConnection;
|
||||
|
||||
protected callbacks: ((success: boolean, message?: string) => any)[] = [];
|
||||
|
||||
protected constructor(connection: AbstractServerConnection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
export function create_identity(type: IdentitifyType) {
|
||||
let identity: Identity;
|
||||
switch (type) {
|
||||
case IdentitifyType.NICKNAME:
|
||||
identity = new NameIdentity();
|
||||
break;
|
||||
case IdentitifyType.TEAFORO:
|
||||
identity = new TeaForumIdentity(undefined);
|
||||
break;
|
||||
case IdentitifyType.TEAMSPEAK:
|
||||
identity = new TeaSpeakIdentity(undefined, undefined);
|
||||
break;
|
||||
}
|
||||
return identity;
|
||||
register_callback(callback: (success: boolean, message?: string) => any) {
|
||||
this.callbacks.push(callback);
|
||||
}
|
||||
|
||||
export class HandshakeCommandHandler<T extends AbstractHandshakeIdentityHandler> extends connection.AbstractCommandHandler {
|
||||
readonly handle: T;
|
||||
abstract start_handshake();
|
||||
|
||||
constructor(connection: connection.AbstractServerConnection, handle: T) {
|
||||
super(connection);
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
|
||||
handle_command(command: connection.ServerCommand): boolean {
|
||||
if($.isFunction(this[command.command]))
|
||||
this[command.command](command.arguments);
|
||||
else if(command.command == "error") {
|
||||
return false;
|
||||
} else {
|
||||
console.warn(tr("Received unknown command while handshaking (%o)"), command);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected trigger_success() {
|
||||
for(const callback of this.callbacks)
|
||||
callback(true);
|
||||
}
|
||||
|
||||
export abstract class AbstractHandshakeIdentityHandler implements connection.HandshakeIdentityHandler {
|
||||
connection: connection.AbstractServerConnection;
|
||||
|
||||
protected callbacks: ((success: boolean, message?: string) => any)[] = [];
|
||||
|
||||
protected constructor(connection: connection.AbstractServerConnection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
register_callback(callback: (success: boolean, message?: string) => any) {
|
||||
this.callbacks.push(callback);
|
||||
}
|
||||
|
||||
abstract start_handshake();
|
||||
|
||||
protected trigger_success() {
|
||||
for(const callback of this.callbacks)
|
||||
callback(true);
|
||||
}
|
||||
|
||||
protected trigger_fail(message: string) {
|
||||
for(const callback of this.callbacks)
|
||||
callback(false, message);
|
||||
}
|
||||
protected trigger_fail(message: string) {
|
||||
for(const callback of this.callbacks)
|
||||
callback(false, message);
|
||||
}
|
||||
}
|
|
@ -1,88 +1,92 @@
|
|||
/// <reference path="../Identity.ts" />
|
||||
import {CommandResult} from "../../connection/ServerConnectionDeclaration";
|
||||
import {log, LogCategory} from "../../log";
|
||||
import {AbstractServerConnection} from "../../connection/ConnectionBase";
|
||||
import {connection} from "../../connection/HandshakeHandler";
|
||||
|
||||
namespace profiles.identities {
|
||||
class NameHandshakeHandler extends AbstractHandshakeIdentityHandler {
|
||||
readonly identity: NameIdentity;
|
||||
handler: HandshakeCommandHandler<NameHandshakeHandler>;
|
||||
import HandshakeIdentityHandler = connection.HandshakeIdentityHandler;
|
||||
import {AbstractHandshakeIdentityHandler, HandshakeCommandHandler, IdentitifyType, Identity} from "../Identity";
|
||||
|
||||
constructor(connection: connection.AbstractServerConnection, identity: profiles.identities.NameIdentity) {
|
||||
super(connection);
|
||||
this.identity = identity;
|
||||
class NameHandshakeHandler extends AbstractHandshakeIdentityHandler {
|
||||
readonly identity: NameIdentity;
|
||||
handler: HandshakeCommandHandler<NameHandshakeHandler>;
|
||||
|
||||
this.handler = new HandshakeCommandHandler(connection, this);
|
||||
this.handler["handshakeidentityproof"] = () => this.trigger_fail("server requested unexpected proof");
|
||||
}
|
||||
constructor(connection: AbstractServerConnection, identity: NameIdentity) {
|
||||
super(connection);
|
||||
this.identity = identity;
|
||||
|
||||
start_handshake() {
|
||||
this.connection.command_handler_boss().register_handler(this.handler);
|
||||
this.connection.send_command("handshakebegin", {
|
||||
intention: 0,
|
||||
authentication_method: this.identity.type(),
|
||||
client_nickname: this.identity.name()
|
||||
}).catch(error => {
|
||||
log.error(LogCategory.IDENTITIES, tr("Failed to initialize name based handshake. Error: %o"), error);
|
||||
if(error instanceof CommandResult)
|
||||
error = error.extra_message || error.message;
|
||||
this.trigger_fail("failed to execute begin (" + error + ")");
|
||||
}).then(() => this.trigger_success());
|
||||
}
|
||||
|
||||
protected trigger_fail(message: string) {
|
||||
this.connection.command_handler_boss().unregister_handler(this.handler);
|
||||
super.trigger_fail(message);
|
||||
}
|
||||
|
||||
protected trigger_success() {
|
||||
this.connection.command_handler_boss().unregister_handler(this.handler);
|
||||
super.trigger_success();
|
||||
}
|
||||
this.handler = new HandshakeCommandHandler(connection, this);
|
||||
this.handler["handshakeidentityproof"] = () => this.trigger_fail("server requested unexpected proof");
|
||||
}
|
||||
|
||||
export class NameIdentity implements Identity {
|
||||
private _name: string;
|
||||
start_handshake() {
|
||||
this.connection.command_handler_boss().register_handler(this.handler);
|
||||
this.connection.send_command("handshakebegin", {
|
||||
intention: 0,
|
||||
authentication_method: this.identity.type(),
|
||||
client_nickname: this.identity.name()
|
||||
}).catch(error => {
|
||||
log.error(LogCategory.IDENTITIES, tr("Failed to initialize name based handshake. Error: %o"), error);
|
||||
if(error instanceof CommandResult)
|
||||
error = error.extra_message || error.message;
|
||||
this.trigger_fail("failed to execute begin (" + error + ")");
|
||||
}).then(() => this.trigger_success());
|
||||
}
|
||||
|
||||
constructor(name?: string) {
|
||||
this._name = name;
|
||||
}
|
||||
protected trigger_fail(message: string) {
|
||||
this.connection.command_handler_boss().unregister_handler(this.handler);
|
||||
super.trigger_fail(message);
|
||||
}
|
||||
|
||||
set_name(name: string) { this._name = name; }
|
||||
protected trigger_success() {
|
||||
this.connection.command_handler_boss().unregister_handler(this.handler);
|
||||
super.trigger_success();
|
||||
}
|
||||
}
|
||||
|
||||
name() : string { return this._name; }
|
||||
export class NameIdentity implements Identity {
|
||||
private _name: string;
|
||||
|
||||
fallback_name(): string | undefined {
|
||||
return this._name;
|
||||
}
|
||||
constructor(name?: string) {
|
||||
this._name = name;
|
||||
}
|
||||
|
||||
uid(): string {
|
||||
return btoa(this._name); //FIXME hash!
|
||||
}
|
||||
set_name(name: string) { this._name = name; }
|
||||
|
||||
type(): IdentitifyType {
|
||||
return IdentitifyType.NICKNAME;
|
||||
}
|
||||
name() : string { return this._name; }
|
||||
|
||||
valid(): boolean {
|
||||
return this._name != undefined && this._name.length >= 5;
|
||||
}
|
||||
fallback_name(): string | undefined {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
decode(data) : Promise<void> {
|
||||
data = JSON.parse(data);
|
||||
if(data.version !== 1)
|
||||
throw "invalid version";
|
||||
uid(): string {
|
||||
return btoa(this._name); //FIXME hash!
|
||||
}
|
||||
|
||||
this._name = data["name"];
|
||||
return;
|
||||
}
|
||||
type(): IdentitifyType {
|
||||
return IdentitifyType.NICKNAME;
|
||||
}
|
||||
|
||||
encode?() : string {
|
||||
return JSON.stringify({
|
||||
version: 1,
|
||||
name: this._name
|
||||
});
|
||||
}
|
||||
valid(): boolean {
|
||||
return this._name != undefined && this._name.length >= 5;
|
||||
}
|
||||
|
||||
spawn_identity_handshake_handler(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler {
|
||||
return new NameHandshakeHandler(connection, this);
|
||||
}
|
||||
decode(data) : Promise<void> {
|
||||
data = JSON.parse(data);
|
||||
if(data.version !== 1)
|
||||
throw "invalid version";
|
||||
|
||||
this._name = data["name"];
|
||||
return;
|
||||
}
|
||||
|
||||
encode?() : string {
|
||||
return JSON.stringify({
|
||||
version: 1,
|
||||
name: this._name
|
||||
});
|
||||
}
|
||||
|
||||
spawn_identity_handshake_handler(connection: AbstractServerConnection) : HandshakeIdentityHandler {
|
||||
return new NameHandshakeHandler(connection, this);
|
||||
}
|
||||
}
|
|
@ -1,122 +1,126 @@
|
|||
/// <reference path="../Identity.ts" />
|
||||
import {AbstractServerConnection} from "../../connection/ConnectionBase";
|
||||
import {log, LogCategory} from "../../log";
|
||||
import {CommandResult} from "../../connection/ServerConnectionDeclaration";
|
||||
import {forum} from "./teaspeak-forum";
|
||||
import {connection} from "../../connection/HandshakeHandler";
|
||||
import HandshakeIdentityHandler = connection.HandshakeIdentityHandler;
|
||||
import {AbstractHandshakeIdentityHandler, HandshakeCommandHandler, IdentitifyType, Identity} from "../Identity";
|
||||
|
||||
namespace profiles.identities {
|
||||
class TeaForumHandshakeHandler extends AbstractHandshakeIdentityHandler {
|
||||
readonly identity: TeaForumIdentity;
|
||||
handler: HandshakeCommandHandler<TeaForumHandshakeHandler>;
|
||||
class TeaForumHandshakeHandler extends AbstractHandshakeIdentityHandler {
|
||||
readonly identity: TeaForumIdentity;
|
||||
handler: HandshakeCommandHandler<TeaForumHandshakeHandler>;
|
||||
|
||||
constructor(connection: connection.AbstractServerConnection, identity: profiles.identities.TeaForumIdentity) {
|
||||
super(connection);
|
||||
this.identity = identity;
|
||||
this.handler = new HandshakeCommandHandler(connection, this);
|
||||
this.handler["handshakeidentityproof"] = this.handle_proof.bind(this);
|
||||
}
|
||||
|
||||
start_handshake() {
|
||||
this.connection.command_handler_boss().register_handler(this.handler);
|
||||
this.connection.send_command("handshakebegin", {
|
||||
intention: 0,
|
||||
authentication_method: this.identity.type(),
|
||||
data: this.identity.data().data_json()
|
||||
}).catch(error => {
|
||||
log.error(LogCategory.IDENTITIES, tr("Failed to initialize TeaForum based handshake. Error: %o"), error);
|
||||
|
||||
if(error instanceof CommandResult)
|
||||
error = error.extra_message || error.message;
|
||||
this.trigger_fail("failed to execute begin (" + error + ")");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private handle_proof(json) {
|
||||
this.connection.send_command("handshakeindentityproof", {
|
||||
proof: this.identity.data().data_sign()
|
||||
}).catch(error => {
|
||||
log.error(LogCategory.IDENTITIES, tr("Failed to proof the identity. Error: %o"), error);
|
||||
|
||||
if(error instanceof CommandResult)
|
||||
error = error.extra_message || error.message;
|
||||
this.trigger_fail("failed to execute proof (" + error + ")");
|
||||
}).then(() => this.trigger_success());
|
||||
}
|
||||
|
||||
protected trigger_fail(message: string) {
|
||||
this.connection.command_handler_boss().unregister_handler(this.handler);
|
||||
super.trigger_fail(message);
|
||||
}
|
||||
|
||||
protected trigger_success() {
|
||||
this.connection.command_handler_boss().unregister_handler(this.handler);
|
||||
super.trigger_success();
|
||||
}
|
||||
constructor(connection: AbstractServerConnection, identity: TeaForumIdentity) {
|
||||
super(connection);
|
||||
this.identity = identity;
|
||||
this.handler = new HandshakeCommandHandler(connection, this);
|
||||
this.handler["handshakeidentityproof"] = this.handle_proof.bind(this);
|
||||
}
|
||||
|
||||
export class TeaForumIdentity implements Identity {
|
||||
private readonly identity_data: forum.Data;
|
||||
start_handshake() {
|
||||
this.connection.command_handler_boss().register_handler(this.handler);
|
||||
this.connection.send_command("handshakebegin", {
|
||||
intention: 0,
|
||||
authentication_method: this.identity.type(),
|
||||
data: this.identity.data().data_json()
|
||||
}).catch(error => {
|
||||
log.error(LogCategory.IDENTITIES, tr("Failed to initialize TeaForum based handshake. Error: %o"), error);
|
||||
|
||||
valid() : boolean {
|
||||
return !!this.identity_data && !this.identity_data.is_expired();
|
||||
}
|
||||
|
||||
constructor(data: forum.Data) {
|
||||
this.identity_data = data;
|
||||
}
|
||||
|
||||
data() : forum.Data {
|
||||
return this.identity_data;
|
||||
}
|
||||
|
||||
decode(data) : Promise<void> {
|
||||
data = JSON.parse(data);
|
||||
if(data.version !== 1)
|
||||
throw "invalid version";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
encode() : string {
|
||||
return JSON.stringify({
|
||||
version: 1
|
||||
});
|
||||
}
|
||||
|
||||
spawn_identity_handshake_handler(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler {
|
||||
return new TeaForumHandshakeHandler(connection, this);
|
||||
}
|
||||
|
||||
fallback_name(): string | undefined {
|
||||
return this.identity_data ? this.identity_data.name() : undefined;
|
||||
}
|
||||
|
||||
type(): profiles.identities.IdentitifyType {
|
||||
return IdentitifyType.TEAFORO;
|
||||
}
|
||||
|
||||
uid(): string {
|
||||
//FIXME: Real UID!
|
||||
return "TeaForo#" + ((this.identity_data ? this.identity_data.name() : "Another TeaSpeak user"));
|
||||
}
|
||||
if(error instanceof CommandResult)
|
||||
error = error.extra_message || error.message;
|
||||
this.trigger_fail("failed to execute begin (" + error + ")");
|
||||
});
|
||||
}
|
||||
|
||||
let static_identity: TeaForumIdentity;
|
||||
|
||||
export function set_static_identity(identity: TeaForumIdentity) {
|
||||
static_identity = identity;
|
||||
private handle_proof(json) {
|
||||
this.connection.send_command("handshakeindentityproof", {
|
||||
proof: this.identity.data().data_sign()
|
||||
}).catch(error => {
|
||||
log.error(LogCategory.IDENTITIES, tr("Failed to proof the identity. Error: %o"), error);
|
||||
|
||||
if(error instanceof CommandResult)
|
||||
error = error.extra_message || error.message;
|
||||
this.trigger_fail("failed to execute proof (" + error + ")");
|
||||
}).then(() => this.trigger_success());
|
||||
}
|
||||
|
||||
export function update_forum() {
|
||||
if(forum.logged_in() && (!static_identity || static_identity.data() !== forum.data())) {
|
||||
static_identity = new TeaForumIdentity(forum.data());
|
||||
} else {
|
||||
static_identity = undefined;
|
||||
}
|
||||
protected trigger_fail(message: string) {
|
||||
this.connection.command_handler_boss().unregister_handler(this.handler);
|
||||
super.trigger_fail(message);
|
||||
}
|
||||
|
||||
export function valid_static_forum_identity() : boolean {
|
||||
return static_identity && static_identity.valid();
|
||||
protected trigger_success() {
|
||||
this.connection.command_handler_boss().unregister_handler(this.handler);
|
||||
super.trigger_success();
|
||||
}
|
||||
}
|
||||
|
||||
export class TeaForumIdentity implements Identity {
|
||||
private readonly identity_data: forum.Data;
|
||||
|
||||
valid() : boolean {
|
||||
return !!this.identity_data && !this.identity_data.is_expired();
|
||||
}
|
||||
|
||||
export function static_forum_identity() : TeaForumIdentity | undefined {
|
||||
return static_identity;
|
||||
constructor(data: forum.Data) {
|
||||
this.identity_data = data;
|
||||
}
|
||||
|
||||
data() : forum.Data {
|
||||
return this.identity_data;
|
||||
}
|
||||
|
||||
decode(data) : Promise<void> {
|
||||
data = JSON.parse(data);
|
||||
if(data.version !== 1)
|
||||
throw "invalid version";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
encode() : string {
|
||||
return JSON.stringify({
|
||||
version: 1
|
||||
});
|
||||
}
|
||||
|
||||
spawn_identity_handshake_handler(connection: AbstractServerConnection) : HandshakeIdentityHandler {
|
||||
return new TeaForumHandshakeHandler(connection, this);
|
||||
}
|
||||
|
||||
fallback_name(): string | undefined {
|
||||
return this.identity_data ? this.identity_data.name() : undefined;
|
||||
}
|
||||
|
||||
type(): IdentitifyType {
|
||||
return IdentitifyType.TEAFORO;
|
||||
}
|
||||
|
||||
uid(): string {
|
||||
//FIXME: Real UID!
|
||||
return "TeaForo#" + ((this.identity_data ? this.identity_data.name() : "Another TeaSpeak user"));
|
||||
}
|
||||
}
|
||||
|
||||
let static_identity: TeaForumIdentity;
|
||||
|
||||
export function set_static_identity(identity: TeaForumIdentity) {
|
||||
static_identity = identity;
|
||||
}
|
||||
|
||||
export function update_forum() {
|
||||
if(forum.logged_in() && (!static_identity || static_identity.data() !== forum.data())) {
|
||||
static_identity = new TeaForumIdentity(forum.data());
|
||||
} else {
|
||||
static_identity = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function valid_static_forum_identity() : boolean {
|
||||
return static_identity && static_identity.valid();
|
||||
}
|
||||
|
||||
export function static_forum_identity() : TeaForumIdentity | undefined {
|
||||
return static_identity;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,11 @@
|
|||
interface Window {
|
||||
import {Settings, settings} from "../../settings";
|
||||
import {update_forum} from "./TeaForumIdentity";
|
||||
|
||||
declare interface Window {
|
||||
grecaptcha: GReCaptcha;
|
||||
}
|
||||
|
||||
interface GReCaptcha {
|
||||
export interface GReCaptcha {
|
||||
render(container: string | HTMLElement, parameters: {
|
||||
sitekey: string;
|
||||
theme?: "dark" | "light";
|
||||
|
@ -18,10 +21,10 @@ interface GReCaptcha {
|
|||
reset(widget_id?: string);
|
||||
}
|
||||
|
||||
namespace forum {
|
||||
export namespace forum {
|
||||
export namespace gcaptcha {
|
||||
export async function initialize() {
|
||||
if(typeof(window.grecaptcha) === "undefined") {
|
||||
if(typeof((window as any).grecaptcha) === "undefined") {
|
||||
let script = document.createElement("script");
|
||||
script.async = true;
|
||||
|
||||
|
@ -50,7 +53,7 @@ namespace forum {
|
|||
}
|
||||
}
|
||||
|
||||
if(typeof(window.grecaptcha) === "undefined")
|
||||
if(typeof((window as any).grecaptcha) === "undefined")
|
||||
throw tr("failed to load recaptcha");
|
||||
}
|
||||
|
||||
|
@ -62,9 +65,9 @@ namespace forum {
|
|||
throw tr("initialisation failed");
|
||||
}
|
||||
if(container.attr("captcha-uuid"))
|
||||
window.grecaptcha.reset(container.attr("captcha-uuid"));
|
||||
(window as any).grecaptcha.reset(container.attr("captcha-uuid"));
|
||||
else {
|
||||
container.attr("captcha-uuid", window.grecaptcha.render(container[0], {
|
||||
container.attr("captcha-uuid", (window as any).grecaptcha.render(container[0], {
|
||||
"sitekey": key,
|
||||
callback: callback_data
|
||||
}));
|
||||
|
@ -206,7 +209,7 @@ namespace forum {
|
|||
localStorage.setItem("teaspeak-forum-data", response["data"]);
|
||||
localStorage.setItem("teaspeak-forum-sign", response["sign"]);
|
||||
localStorage.setItem("teaspeak-forum-auth", response["auth-key"]);
|
||||
profiles.identities.update_forum();
|
||||
update_forum();
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to parse forum given data: %o"), error);
|
||||
return {
|
||||
|
@ -266,7 +269,7 @@ namespace forum {
|
|||
_data = new Data(_data.auth_key, response["data"], response["sign"]);
|
||||
localStorage.setItem("teaspeak-forum-data", response["data"]);
|
||||
localStorage.setItem("teaspeak-forum-sign", response["sign"]);
|
||||
profiles.identities.update_forum();
|
||||
update_forum();
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to parse forum given data: %o"), error);
|
||||
throw tr("failed to parse data");
|
||||
|
@ -320,7 +323,7 @@ namespace forum {
|
|||
localStorage.removeItem("teaspeak-forum-data");
|
||||
localStorage.removeItem("teaspeak-forum-sign");
|
||||
localStorage.removeItem("teaspeak-forum-auth");
|
||||
profiles.identities.update_forum();
|
||||
update_forum();
|
||||
}
|
||||
|
||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
//Used by CertAccept popup
|
||||
|
||||
interface Array<T> {
|
||||
remove(elem?: T): boolean;
|
||||
last?(): T;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
/// <reference path="ui/elements/modal.ts" />
|
||||
//Used by CertAccept popup
|
||||
|
||||
import {log, LogCategory} from "./log";
|
||||
import {createErrorModal} from "./ui/elements/modal";
|
||||
|
||||
if(typeof(customElements) !== "undefined") {
|
||||
try {
|
||||
class X_Properties extends HTMLElement {}
|
||||
|
@ -14,7 +17,7 @@ if(typeof(customElements) !== "undefined") {
|
|||
}
|
||||
|
||||
/* T = value type */
|
||||
interface SettingsKey<T> {
|
||||
export interface SettingsKey<T> {
|
||||
key: string;
|
||||
|
||||
fallback_keys?: string | string[];
|
||||
|
@ -25,7 +28,7 @@ interface SettingsKey<T> {
|
|||
require_restart?: boolean;
|
||||
}
|
||||
|
||||
class SettingsBase {
|
||||
export class SettingsBase {
|
||||
protected static readonly UPDATE_DIRECT: boolean = true;
|
||||
|
||||
protected static transformStO?<T>(input?: string, _default?: T, default_type?: string) : T {
|
||||
|
@ -77,7 +80,7 @@ class SettingsBase {
|
|||
}
|
||||
}
|
||||
|
||||
class StaticSettings extends SettingsBase {
|
||||
export class StaticSettings extends SettingsBase {
|
||||
private static _instance: StaticSettings;
|
||||
static get instance() : StaticSettings {
|
||||
if(!this._instance)
|
||||
|
@ -139,7 +142,7 @@ class StaticSettings extends SettingsBase {
|
|||
}
|
||||
}
|
||||
|
||||
class Settings extends StaticSettings {
|
||||
export class Settings extends StaticSettings {
|
||||
static readonly KEY_USER_IS_NEW: SettingsKey<boolean> = {
|
||||
key: 'user_is_new_user',
|
||||
default_value: true
|
||||
|
@ -433,7 +436,7 @@ class Settings extends StaticSettings {
|
|||
}
|
||||
}
|
||||
|
||||
class ServerSettings extends SettingsBase {
|
||||
export class ServerSettings extends SettingsBase {
|
||||
private cacheServer = {};
|
||||
private _server_unique_id: string;
|
||||
private _server_save_worker: NodeJS.Timer;
|
||||
|
@ -511,4 +514,4 @@ class ServerSettings extends SettingsBase {
|
|||
}
|
||||
}
|
||||
|
||||
let settings: Settings;
|
||||
export let settings: Settings;
|
|
@ -1,4 +1,8 @@
|
|||
enum Sound {
|
||||
import {settings} from "../settings";
|
||||
import {log, LogCategory} from "../log";
|
||||
import {ConnectionHandler} from "../ConnectionHandler";
|
||||
|
||||
export enum Sound {
|
||||
SOUND_TEST = "sound.test",
|
||||
SOUND_EGG = "sound.egg",
|
||||
|
||||
|
@ -61,7 +65,7 @@ enum Sound {
|
|||
GROUP_CHANNEL_CHANGED_SELF = "group.channel.changed.self"
|
||||
}
|
||||
|
||||
namespace sound {
|
||||
export namespace sound {
|
||||
export interface SoundHandle {
|
||||
key: string;
|
||||
filename: string;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
namespace stats {
|
||||
import {log, LogCategory} from "./log";
|
||||
|
||||
export namespace stats {
|
||||
const LOG_PREFIX = "[Statistics] ";
|
||||
|
||||
export enum CloseCodes {
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
.channel-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.channel-container .container-channel {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
width: 100%;
|
||||
min-height: 16px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.channel-container .container-channel .channel-type {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
margin-right: 2px;
|
||||
}
|
||||
.channel-container .container-channel .container-channel-name {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
justify-content: left;
|
||||
max-width: 100%;
|
||||
/* important for the repetitive channel name! */
|
||||
overflow-x: hidden;
|
||||
height: 16px;
|
||||
}
|
||||
.channel-container .container-channel .container-channel-name.align-right {
|
||||
justify-content: right;
|
||||
}
|
||||
.channel-container .container-channel .container-channel-name.align-center, .channel-container .container-channel .container-channel-name.align-repetitive {
|
||||
justify-content: center;
|
||||
}
|
||||
.channel-container .container-channel .container-channel-name .channel-name {
|
||||
align-self: center;
|
||||
color: #828282;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.channel-container .container-channel .container-channel-name.align-repetitive .channel-name {
|
||||
text-overflow: clip;
|
||||
}
|
||||
.channel-container .container-channel .icons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.channel-container .container-channel.move-selected {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
.channel-container .container-channel .show-channel-normal-only {
|
||||
display: none;
|
||||
}
|
||||
.channel-container .container-channel .show-channel-normal-only.channel-normal {
|
||||
display: block;
|
||||
}
|
||||
.channel-container .container-channel .icon_no_sound {
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
.channel-container .container-channel .icon_no_sound .background {
|
||||
width: 10px;
|
||||
height: 14px;
|
||||
background: red;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 3px;
|
||||
z-index: -1;
|
||||
}
|
||||
.channel-container .container-clients {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=channel.css.map */
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"sourceRoot":"","sources":["channel.scss","colors.scss"],"names":[],"mappings":"AAEA;EACI;EACA;;AAEA;EACI;EAEA;EACA;EACA;EAEA;EACA;EAEA;EACA;;AAEA;EACI;EACA;EAEA;;AAGJ;EACI;EACA;EAEA;EACA;EAEA;EAEA;AAAiB;EACjB;EACA;;AAEA;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA,OC/CW;EDiDX;EACA;EACA;EACA;;AAIA;EACI;;AAKZ;EACI;EACA;EAEA;EACA;;AAGJ;EACI;;AAGJ;EACI;;AAEA;EACI;;AAIR;EACI;EACA;;AAEA;EACI;EACA;EAEA;EACA;EACA;EACA;EACA;;AAKZ;EACI;EACA","file":"channel.css"}
|
|
@ -0,0 +1,106 @@
|
|||
@import "colors";
|
||||
|
||||
.channel-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.container-channel {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
width: 100%;
|
||||
min-height: 16px;
|
||||
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
.channel-type {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.container-channel-name {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
justify-content: left;
|
||||
|
||||
max-width: 100%; /* important for the repetitive channel name! */
|
||||
overflow-x: hidden;
|
||||
height: 16px;
|
||||
|
||||
&.align-right {
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
&.align-center, &.align-repetitive {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.channel-name {
|
||||
align-self: center;
|
||||
color: $channel-tree-entry-color;
|
||||
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&.align-repetitive {
|
||||
.channel-name {
|
||||
text-overflow: clip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&.move-selected {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
.show-channel-normal-only {
|
||||
display: none;
|
||||
|
||||
&.channel-normal {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.icon_no_sound {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
.background {
|
||||
width: 10px;
|
||||
height: 14px;
|
||||
|
||||
background: red;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 3px;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-clients {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
import * as React from "react";
|
||||
import {ChannelEntry} from "../../channel-tree/channel";
|
||||
|
||||
const cssChannel = require("./channel.scss");
|
||||
const cssTree = require("./tree.scss");
|
||||
|
||||
class ChannelIcon extends React.Component<{ channel: ChannelEntry }, {}> {
|
||||
channel_icon_classes() {
|
||||
const class_list = [];
|
||||
const channel = this.props.channel;
|
||||
|
||||
if(channel.formattedChannelName() !== channel.channelName())
|
||||
return "channel-spacer";
|
||||
|
||||
let icon_color;
|
||||
if(channel.properties.channel_flag_password && !channel.cached_password())
|
||||
icon_color = "yellow";
|
||||
else if(channel.properties.channel_flag_maxclients_unlimited && channel.clients().length >= channel.properties.channel_maxclients)
|
||||
icon_color = "red";
|
||||
else if(channel.properties.channel_flag_maxfamilyclients_unlimited && channel.clients(true).length >= channel.properties.channel_maxfamilyclients)
|
||||
icon_color = "red";
|
||||
else
|
||||
icon_color = "green";
|
||||
|
||||
|
||||
return "channel-normal client-channel_" + icon_color + (channel.flag_subscribed ? "_subscribed" : "");
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className={this.channel_icon_classes()} />;
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelIcons extends React.Component<{channel: ChannelEntry}, {}> {
|
||||
render_icon(target: HTMLDivElement) {
|
||||
const props = this.props.channel.properties;
|
||||
if(!props.channel_icon_id) return;
|
||||
|
||||
const tag = this.props.channel.channelTree.client.fileManager.icons.generateTag(props.channel_icon_id);
|
||||
tag.appendTo($(target));
|
||||
}
|
||||
|
||||
render() {
|
||||
const icons = [];
|
||||
|
||||
const props = this.props.channel.properties;
|
||||
if(props.channel_flag_default) {
|
||||
icons.push(<div className={"show-channel-normal-only icon_entry icon_default icon client-channel_default"} title={tr("Default channel")} />);
|
||||
}
|
||||
if(props.channel_flag_password) {
|
||||
icons.push(<div className={"show-channel-normal-only icon_entry icon_password icon client-register"} title={tr("The channel is password protected")} />);
|
||||
}
|
||||
if(props.channel_codec_quality > 4) {
|
||||
icons.push(<div className={"show-channel-normal-only icon_entry icon_moderated icon client-moderated"} title={tr("Music quality")} />);
|
||||
}
|
||||
if(props.channel_needed_talk_power > 0) {
|
||||
icons.push(<div className={"show-channel-normal-only icon_entry icon_moderated icon client-moderated"} title={tr("Channel is moderated")} />);
|
||||
}
|
||||
if(props.channel_icon_id != 0) {
|
||||
icons.push(<div className={"show-channel-normal-only icon_entry channel_icon"} title={tr("Channel icon")} ref={e => this.render_icon(e) }/>)
|
||||
}
|
||||
if(!audio.codec.supported(props.channel_codec)) {
|
||||
icons.push(<div className={cssChannel.icon_no_sound}>
|
||||
<div className={cssChannel.background} />
|
||||
<div className={"icon_entry icon client-conflict-icon"} title={tr("You don't support the channel codec")} />
|
||||
</div>)
|
||||
}
|
||||
|
||||
return (<div className={cssChannel.icons}>{icons}</div>);
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelLine extends React.Component<{ channel: ChannelEntry }, {}> {
|
||||
|
||||
render() {
|
||||
let depth = 1;
|
||||
let parent = this.props.channel;
|
||||
while((parent = parent.parent))
|
||||
depth++;
|
||||
|
||||
//TODO: On handle spacer alignments in channel name!
|
||||
return (
|
||||
<div className={cssChannel.containerChannel}>
|
||||
<div className={cssTree.markerTextUnread} />
|
||||
<div className={cssTree.depthFiller} style={{ width: depth + "em" }} />
|
||||
<ChannelIcon channel={this.props.channel} />
|
||||
<div className={cssChannel.containerChannelName}>
|
||||
<a className={cssChannel.channelName}>{this.props.channel.formattedChannelName()}</a>
|
||||
</div>
|
||||
<ChannelIcons channel={this.props.channel} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelClientsView extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
class SubChannelView extends React.Component<{ channels: ChannelEntry[] }, {}> {
|
||||
|
||||
render() {
|
||||
return this.props.channels.map(e => <ChannelLine channel={e} />);
|
||||
}
|
||||
}
|
||||
|
||||
export class Channel extends React.Component<{ channel: ChannelEntry }, {}> {
|
||||
children: ChannelEntry[];
|
||||
|
||||
channel_entry() : ChannelEntry { return this.props.channel; }
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={[cssTree.entry, cssChannel.channelContainer].join(" ")}>
|
||||
<ChannelLine channel={this.props.channel} />
|
||||
<ChannelClientsView />
|
||||
<SubChannelView channels={[]} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
|
||||
/*# sourceMappingURL=colors.css.map */
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"sourceRoot":"","sources":[],"names":[],"mappings":"","file":"colors.css"}
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
$channel-tree-new-message-color: #a814147F;
|
||||
$channel-tree-entry-color: #828282;
|
|
@ -0,0 +1,38 @@
|
|||
/* Some general browser helpers */
|
||||
.tree-container .tree .entry .depth-filler {
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
}
|
||||
.tree-container .marker-text-unread {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
background-color: #a814147F;
|
||||
opacity: 1;
|
||||
-moz-transition: opacity 0.25s;
|
||||
-o-transition: opacity 0.25s;
|
||||
-webkit-transition: opacity 0.25s;
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
.tree-container .marker-text-unread:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 24px;
|
||||
background: -moz-linear-gradient(left, rgba(168, 20, 20, 0.18) 0%, rgba(168, 20, 20, 0) 100%);
|
||||
/* FF3.6-15 */
|
||||
background: -webkit-linear-gradient(left, rgba(168, 20, 20, 0.18) 0%, rgba(168, 20, 20, 0) 100%);
|
||||
/* Chrome10-25,Safari5.1-6 */
|
||||
background: linear-gradient(to right, rgba(168, 20, 20, 0.18) 0%, rgba(168, 20, 20, 0) 100%);
|
||||
/* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
|
||||
}
|
||||
.tree-container .marker-text-unread.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=tree.css.map */
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"sourceRoot":"","sources":["../../../css/static/mixin.scss","tree.scss"],"names":[],"mappings":"AAAA;ACQY;EACI;EAEA;EACA;;AAKZ;EACI;EACA;EACA;EACA;EAEA;EACA;EAEA;EDvBP,iBC4CO;ED3CP,eC2CO;ED1CP,oBC0CO;EDzCP,YCyCO;;AAnBA;EACI;EACA;EAEA;EACA;EACA;EAEA;EAEA;AAAyF;EACzF;AAA2F;EAC3F;AAAuF;;AAG3F;EACI","file":"tree.css"}
|
|
@ -0,0 +1,50 @@
|
|||
@import "../../../css/static/mixin";
|
||||
@import "../../../css/static/properties";
|
||||
|
||||
.tree-container {
|
||||
.tree {
|
||||
|
||||
.entry {
|
||||
|
||||
.depth-filler {
|
||||
font-size: 16px;
|
||||
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.marker-text-unread {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
width: 1px;
|
||||
background-color: #a814147F;
|
||||
|
||||
opacity: 1;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
width: 24px;
|
||||
|
||||
background: -moz-linear-gradient(left, rgba(168,20,20,.18) 0%, rgba(168,20,20,0) 100%); /* FF3.6-15 */
|
||||
background: -webkit-linear-gradient(left, rgba(168,20,20,.18) 0%,rgba(168,20,20,0) 100%); /* Chrome10-25,Safari5.1-6 */
|
||||
background: linear-gradient(to right, rgba(168,20,20,.18) 0%,rgba(168,20,20,0) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@include transition(opacity $button_hover_animation_time);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import * as React from "react";
|
||||
|
||||
export class TreeView {
|
||||
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
/// <reference path="client.ts" />
|
||||
|
||||
class ClientMover {
|
||||
export class ClientMover {
|
||||
static readonly listener_root = $(document);
|
||||
static readonly move_element = $("#mouse-move");
|
||||
readonly channel_tree: ChannelTree;
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import {settings} from "../../settings";
|
||||
import {log, LogCategory} from "../../log";
|
||||
|
||||
declare const $: any;
|
||||
interface JQuery<TElement = HTMLElement> {
|
||||
dividerfy() : this;
|
||||
}
|
||||
|
||||
if(!$.fn.dividerfy) {
|
||||
$.fn.dividerfy = function<T extends HTMLElement>(this: JQuery<T>) : JQuery<T> {
|
||||
this.find(".container-seperator").each(function (this: T) {
|
||||
(this as any).find(".container-seperator").each(function (this: T) {
|
||||
if(!this.previousElementSibling) return;
|
||||
if(!this.nextElementSibling) return;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace contextmenu {
|
||||
export namespace contextmenu {
|
||||
export interface MenuEntry {
|
||||
callback?: () => void;
|
||||
type: MenuEntryType;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
/// <reference path="../../PPTListener.ts" />
|
||||
import {KeyCode} from "../../PPTListener";
|
||||
|
||||
enum ElementType {
|
||||
export enum ElementType {
|
||||
HEADER,
|
||||
BODY,
|
||||
FOOTER
|
||||
}
|
||||
|
||||
type BodyCreator = (() => JQuery | JQuery[] | string) | string | JQuery | JQuery[];
|
||||
const ModalFunctions = {
|
||||
export type BodyCreator = (() => JQuery | JQuery[] | string) | string | JQuery | JQuery[];
|
||||
export const ModalFunctions = {
|
||||
divify: function (val: JQuery) {
|
||||
if(val.length > 1)
|
||||
return $.spawn("div").append(val);
|
||||
|
@ -54,7 +54,7 @@ const ModalFunctions = {
|
|||
}
|
||||
};
|
||||
|
||||
class ModalProperties {
|
||||
export class ModalProperties {
|
||||
template?: string;
|
||||
header: BodyCreator = () => "HEADER";
|
||||
body: BodyCreator = () => "BODY";
|
||||
|
@ -89,7 +89,7 @@ class ModalProperties {
|
|||
full_size?: boolean = false;
|
||||
}
|
||||
|
||||
namespace modal {
|
||||
export namespace modal {
|
||||
export function initialize_modals() {
|
||||
register_global_events();
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ let _global_modal_count = 0;
|
|||
let _global_modal_last: HTMLElement;
|
||||
let _global_modal_last_time: number;
|
||||
|
||||
class Modal {
|
||||
export class Modal {
|
||||
private _htmlTag: JQuery;
|
||||
properties: ModalProperties;
|
||||
shown: boolean;
|
||||
|
@ -296,11 +296,11 @@ class Modal {
|
|||
}
|
||||
}
|
||||
|
||||
function createModal(data: ModalProperties | any) : Modal {
|
||||
export function createModal(data: ModalProperties | any) : Modal {
|
||||
return new Modal(ModalFunctions.warpProperties(data));
|
||||
}
|
||||
|
||||
class InputModalProperties extends ModalProperties {
|
||||
export class InputModalProperties extends ModalProperties {
|
||||
maxLength?: number;
|
||||
|
||||
field_title?: string;
|
||||
|
@ -310,7 +310,7 @@ class InputModalProperties extends ModalProperties {
|
|||
error_message?: string;
|
||||
}
|
||||
|
||||
function createInputModal(headMessage: BodyCreator, question: BodyCreator, validator: (input: string) => boolean, callback: (flag: boolean | string) => void, props: InputModalProperties | any = {}) : Modal {
|
||||
export function createInputModal(headMessage: BodyCreator, question: BodyCreator, validator: (input: string) => boolean, callback: (flag: boolean | string) => void, props: InputModalProperties | any = {}) : Modal {
|
||||
props = ModalFunctions.warpProperties(props);
|
||||
props.template_properties || (props.template_properties = {});
|
||||
props.template_properties.field_title = props.field_title;
|
||||
|
@ -370,7 +370,7 @@ function createInputModal(headMessage: BodyCreator, question: BodyCreator, valid
|
|||
return modal;
|
||||
}
|
||||
|
||||
function createErrorModal(header: BodyCreator, message: BodyCreator, props: ModalProperties | any = { footer: undefined }) {
|
||||
export function createErrorModal(header: BodyCreator, message: BodyCreator, props: ModalProperties | any = { footer: undefined }) {
|
||||
props = ModalFunctions.warpProperties(props);
|
||||
(props.template_properties || (props.template_properties = {})).header_class = "modal-header-error";
|
||||
|
||||
|
@ -382,7 +382,7 @@ function createErrorModal(header: BodyCreator, message: BodyCreator, props: Moda
|
|||
return modal;
|
||||
}
|
||||
|
||||
function createInfoModal(header: BodyCreator, message: BodyCreator, props: ModalProperties | any = { footer: undefined }) {
|
||||
export function createInfoModal(header: BodyCreator, message: BodyCreator, props: ModalProperties | any = { footer: undefined }) {
|
||||
props = ModalFunctions.warpProperties(props);
|
||||
(props.template_properties || (props.template_properties = {})).header_class = "modal-header-info";
|
||||
|
||||
|
@ -393,73 +393,3 @@ function createInfoModal(header: BodyCreator, message: BodyCreator, props: Modal
|
|||
modal.htmlTag.find(".modal-body").addClass("modal-info");
|
||||
return modal;
|
||||
}
|
||||
|
||||
/* extend jquery */
|
||||
|
||||
interface ModalElements {
|
||||
header?: BodyCreator;
|
||||
body?: BodyCreator;
|
||||
footer?: BodyCreator;
|
||||
}
|
||||
|
||||
interface JQuery<TElement = HTMLElement> {
|
||||
modalize(entry_callback?: (header: JQuery, body: JQuery, footer: JQuery) => ModalElements | void, properties?: ModalProperties | any) : Modal;
|
||||
}
|
||||
|
||||
$.fn.modalize = function (this: JQuery, entry_callback?: (header: JQuery, body: JQuery, footer: JQuery) => ModalElements | void, properties?: ModalProperties | any) : Modal {
|
||||
properties = properties || {} as ModalProperties;
|
||||
entry_callback = entry_callback || ((a,b,c) => undefined);
|
||||
|
||||
let tag_modal = this[0].tagName.toLowerCase() == "modal" ? this : undefined; /* TODO may throw exception? */
|
||||
|
||||
let tag_head = tag_modal ? tag_modal.find("modal-header") : ModalFunctions.jqueriefy(properties.header);
|
||||
let tag_body = tag_modal ? tag_modal.find("modal-body") : this;
|
||||
let tag_footer = tag_modal ? tag_modal.find("modal-footer") : ModalFunctions.jqueriefy(properties.footer);
|
||||
|
||||
const result = entry_callback(tag_head as any, tag_body, tag_footer as any) || {};
|
||||
properties.header = result.header || tag_head;
|
||||
properties.body = result.body || tag_body;
|
||||
properties.footer = result.footer || tag_footer;
|
||||
return createModal(properties);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace net.graph {
|
||||
export namespace net.graph {
|
||||
export type Entry = {
|
||||
timestamp: number;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
interface SliderOptions {
|
||||
export interface SliderOptions {
|
||||
min_value?: number;
|
||||
max_value?: number;
|
||||
initial_value?: number;
|
||||
|
@ -8,11 +8,11 @@ interface SliderOptions {
|
|||
value_field?: JQuery | JQuery[];
|
||||
}
|
||||
|
||||
interface Slider {
|
||||
export interface Slider {
|
||||
value(value?: number) : number;
|
||||
}
|
||||
|
||||
function sliderfy(slider: JQuery, options?: SliderOptions) : Slider {
|
||||
export function sliderfy(slider: JQuery, options?: SliderOptions) : Slider {
|
||||
options = Object.assign( {
|
||||
initial_value: 0,
|
||||
min_value: 0,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
function tooltip(entry: JQuery) {
|
||||
export function tooltip(entry: JQuery) {
|
||||
return tooltip.initialize(entry);
|
||||
}
|
||||
|
||||
namespace tooltip {
|
||||
export namespace tooltip {
|
||||
let _global_tooltip: JQuery;
|
||||
export type Handle = {
|
||||
show();
|
||||
|
|
|
@ -13,12 +13,27 @@
|
|||
client_away_message Value: ''
|
||||
*/
|
||||
|
||||
let control_bar: ControlBar; /* global variable to access the control bar */
|
||||
import {ConnectionHandler, DisconnectReason} from "../../ConnectionHandler";
|
||||
import {top_menu} from "./MenuBar";
|
||||
import {Settings, settings} from "../../settings";
|
||||
import {createErrorModal, createInfoModal, createInputModal} from "../elements/modal";
|
||||
import {default_recorder} from "../../voice/RecorderProfile";
|
||||
import {sound, Sound} from "../../sound/Sounds";
|
||||
import {Modals} from "../modal/ModalConnect";
|
||||
import {Modal as ModalsS} from "../modal/ModalSettings";
|
||||
import {log} from "./server_log";
|
||||
import {MessageHelper} from "./chat";
|
||||
import {CommandResult} from "../../connection/ServerConnectionDeclaration";
|
||||
import {PermissionType} from "../../permission/PermissionManager";
|
||||
import {bookmarks} from "../../bookmarks";
|
||||
import {contextmenu} from "../elements/context_menu";
|
||||
|
||||
type MicrophoneState = "disabled" | "muted" | "enabled";
|
||||
type HeadphoneState = "muted" | "enabled";
|
||||
type AwayState = "away-global" | "away" | "online";
|
||||
class ControlBar {
|
||||
export let control_bar: ControlBar; /* global variable to access the control bar */
|
||||
|
||||
export type MicrophoneState = "disabled" | "muted" | "enabled";
|
||||
export type HeadphoneState = "muted" | "enabled";
|
||||
export type AwayState = "away-global" | "away" | "online";
|
||||
export class ControlBar {
|
||||
private _button_away_active: AwayState;
|
||||
private _button_microphone: MicrophoneState;
|
||||
private _button_speakers: HeadphoneState;
|
||||
|
@ -421,7 +436,7 @@ class ControlBar {
|
|||
}
|
||||
|
||||
private on_open_settings() {
|
||||
Modals.spawnSettingsModal();
|
||||
ModalsS.spawnSettingsModal();
|
||||
}
|
||||
|
||||
private on_open_connect() {
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
namespace top_menu {
|
||||
import {bookmarks} from "../../bookmarks";
|
||||
import {Modals} from "../modal/ModalBookmarks";
|
||||
import {DisconnectReason} from "../../ConnectionHandler";
|
||||
import {control_bar} from "./ControlBar";
|
||||
import {createErrorModal} from "../elements/modal";
|
||||
import {PermissionType} from "../../permission/PermissionManager";
|
||||
import {Sound} from "../../sound/Sounds";
|
||||
|
||||
export namespace top_menu {
|
||||
export interface HRItem { }
|
||||
|
||||
export interface MenuItem {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
enum ChatType {
|
||||
import {log, LogCategory} from "../../log";
|
||||
|
||||
export enum ChatType {
|
||||
GENERAL,
|
||||
SERVER,
|
||||
CHANNEL,
|
||||
|
@ -6,7 +8,7 @@ enum ChatType {
|
|||
}
|
||||
|
||||
declare const xbbcode: any;
|
||||
namespace MessageHelper {
|
||||
export namespace MessageHelper {
|
||||
export function htmlEscape(message: string) : string[] {
|
||||
const div = document.createElement('div');
|
||||
div.innerText = message;
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
/* the bar on the right with the chats (Channel & Client) */
|
||||
namespace chat {
|
||||
import {ChannelEntry} from "../../channel-tree/channel";
|
||||
import {Modals} from "../modal/ModalMusicManage";
|
||||
import {MessageHelper} from "./chat";
|
||||
import {ServerEntry} from "../../channel-tree/server";
|
||||
import {ConnectionHandler} from "../../ConnectionHandler";
|
||||
|
||||
export namespace chat {
|
||||
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
||||
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import {ConnectionHandler, DisconnectReason} from "../../ConnectionHandler";
|
||||
import {Settings, settings} from "../../settings";
|
||||
import {control_bar} from "./ControlBar";
|
||||
import {top_menu} from "./MenuBar";
|
||||
|
||||
let server_connections: ServerConnectionManager;
|
||||
export let server_connections: ServerConnectionManager;
|
||||
|
||||
class ServerConnectionManager {
|
||||
export class ServerConnectionManager {
|
||||
private connection_handlers: ConnectionHandler[] = [];
|
||||
private active_handler: ConnectionHandler | undefined;
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
class Hostbanner {
|
||||
import {ConnectionHandler} from "../../ConnectionHandler";
|
||||
import {Settings, settings} from "../../settings";
|
||||
import {log, LogCategory} from "../../log";
|
||||
|
||||
export class Hostbanner {
|
||||
readonly html_tag: JQuery<HTMLElement>;
|
||||
readonly client: ConnectionHandler;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace image_preview {
|
||||
export namespace image_preview {
|
||||
let preview_overlay: JQuery<HTMLDivElement>;
|
||||
let container_image: JQuery<HTMLDivElement>;
|
||||
let button_open_in_browser: JQuery;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
|||
namespace chat {
|
||||
export namespace chat {
|
||||
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
||||
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace chat {
|
||||
export namespace chat {
|
||||
export namespace helpers {
|
||||
//https://regex101.com/r/YQbfcX/2
|
||||
//static readonly URL_REGEX = /^(?<hostname>([a-zA-Z0-9-]+\.)+[a-zA-Z0-9-]{2,63})(?:\/(?<path>(?:[^\s?]+)?)(?:\?(?<query>\S+))?)?$/gm;
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
namespace chat {
|
||||
import {ClientEntry, LocalClientEntry} from "../../../channel-tree/client";
|
||||
import {Modals} from "../../modal/ModalClientInfo";
|
||||
import {htmltags} from "../../htmltags";
|
||||
import {image_preview} from "../image_preview";
|
||||
import {i18n} from "../../../i18n/country";
|
||||
import {GroupManager} from "../../../permission/GroupManager";
|
||||
|
||||
export namespace chat {
|
||||
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
||||
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
||||
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
namespace chat {
|
||||
import {htmltags} from "../../htmltags";
|
||||
import {MessageHelper} from "../chat";
|
||||
import {CommandResult, ErrorID} from "../../../connection/ServerConnectionDeclaration";
|
||||
import {log, LogCategory} from "../../../log";
|
||||
import {createErrorModal} from "../../elements/modal";
|
||||
|
||||
export namespace chat {
|
||||
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
||||
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
namespace chat {
|
||||
import PlayerState = connection.voice.PlayerState;
|
||||
import {MusicClientEntry} from "../../../channel-tree/client";
|
||||
import {image_preview} from "../image_preview";
|
||||
|
||||
export namespace chat {
|
||||
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
||||
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
/* the bar on the right with the chats (Channel & Client) */
|
||||
namespace chat {
|
||||
import {log, LogCategory} from "../../../log";
|
||||
import {htmltags} from "../../htmltags";
|
||||
import {MessageHelper} from "../chat";
|
||||
|
||||
export namespace chat {
|
||||
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
||||
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace htmltags {
|
||||
export namespace htmltags {
|
||||
let mouse_coordinates: {x: number, y: number} = {x: 0, y: 0};
|
||||
|
||||
function initialize() {
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
function format_date(date: number) {
|
||||
const d = new Date(date);
|
||||
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
//TODO: Test if we could render this image and not only the browser by knowing the type.
|
||||
export function spawnAvatarUpload(callback_data: (data: ArrayBuffer | undefined | null) => any) {
|
||||
const modal = createModal({
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
const avatar_to_uid = (id: string) => {
|
||||
const buffer = new Uint8Array(id.length / 2);
|
||||
for(let index = 0; index < id.length; index += 2) {
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export type BanEntry = {
|
||||
name?: string;
|
||||
unique_id: string;
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../i18n/localize.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
export function openBanList(client: ConnectionHandler) {
|
||||
let modal: Modal;
|
||||
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
export function spawnBookmarkModal() {
|
||||
let modal: Modal;
|
||||
modal = createModal({
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
let modal: Modal;
|
||||
export function spawnChangeLatency(client: ClientEntry, current: connection.voice.LatencySettings, reset: () => connection.voice.LatencySettings, apply: (settings: connection.voice.LatencySettings) => any, callback_flush?: () => any) {
|
||||
if(modal) modal.close();
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
//TODO: Use the max limit!
|
||||
|
||||
let modal: Modal;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace Modals {
|
||||
export namespace Modals {
|
||||
export function openChannelInfo(channel: ChannelEntry) {
|
||||
let modal: Modal;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace Modals {
|
||||
export namespace Modals {
|
||||
type InfoUpdateCallback = (info: ClientConnectionInfo) => any;
|
||||
export function openClientInfo(client: ClientEntry) {
|
||||
let modal: Modal;
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
|
||||
//FIXME: Move this shit out of this file!
|
||||
namespace connection_log {
|
||||
export namespace connection_log {
|
||||
//TODO: Save password data
|
||||
export type ConnectionData = {
|
||||
name: string;
|
||||
|
@ -91,7 +89,7 @@ namespace connection_log {
|
|||
});
|
||||
}
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
export function spawnConnectModal(options: {
|
||||
default_connect_new_tab?: boolean /* default false */
|
||||
}, defaultHost: { url: string, enforce: boolean} = { url: "ts.TeaSpeak.de", enforce: false}, connect_profile?: { profile: profiles.ConnectionProfile, enforce: boolean}) {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
export function createChannelModal(connection: ConnectionHandler, channel: ChannelEntry | undefined, parent: ChannelEntry | undefined, permissions: PermissionManager, callback: (properties?: ChannelProperties, permissions?: PermissionValue[]) => any) {
|
||||
let properties: ChannelProperties = { } as ChannelProperties; //The changes properties
|
||||
const modal = createModal({
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace Modals {
|
||||
export namespace Modals {
|
||||
let current_modal: Modal;
|
||||
export function createServerGroupAssignmentModal(client: ClientEntry, callback: (groups: number[], flag: boolean) => Promise<boolean>) {
|
||||
if(current_modal)
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
export function spawnIconSelect(client: ConnectionHandler, callback_icon?: (id: number) => any, selected_icon?: number) {
|
||||
selected_icon = selected_icon || 0;
|
||||
let allow_manage = client.permissions.neededPermission(PermissionType.B_ICON_MANAGE).granted(1);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace Modals {
|
||||
export namespace Modals {
|
||||
export function spawnTeamSpeakIdentityImprove(identity: profiles.identities.TeaSpeakIdentity, name: string): Modal {
|
||||
let modal: Modal;
|
||||
let elapsed_timer: NodeJS.Timer;
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
type URLGeneratorSettings = {
|
||||
flag_direct: boolean,
|
||||
flag_resolved: boolean
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace Modals {
|
||||
export namespace Modals {
|
||||
export function spawnKeySelect(callback: (key?: ppt.KeyEvent) => void) {
|
||||
let modal = createModal({
|
||||
header: tr("Select a key"),
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
export function openMusicManage(client: ConnectionHandler, bot: MusicClientEntry) {
|
||||
const ev_registry = new events.Registry<events.modal.music_manage>();
|
||||
ev_registry.enable_debug("music-manage");
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
const next_step: {[key: string]:string} = {
|
||||
"welcome": "microphone",
|
||||
//"microphone": app.is_web() ? "identity" : "speaker", /* speaker setup only for the native client! */
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
export function spawnPlaylistSongInfo(song: PlaylistSong) {
|
||||
let modal: Modal;
|
||||
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../i18n/localize.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
export function spawnPlaylistManage(client: ConnectionHandler) {
|
||||
{
|
||||
createErrorModal(tr("Not implemented"), tr("Playlist management hasn't yet been implemented")).open();
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
let global_modal: PokeModal;
|
||||
|
||||
interface ServerEntry {
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
export function spawnQueryCreate(connection: ConnectionHandler, callback_created?: (user, pass) => any) {
|
||||
let modal;
|
||||
modal = createModal({
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export namespace Modals {
|
||||
/*
|
||||
export function spawnQueryManage(client: ConnectionHandler) {
|
||||
let modal: Modal;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace Modals {
|
||||
export namespace Modals {
|
||||
export function createServerModal(server: ServerEntry, callback: (properties?: ServerProperties) => Promise<void>) {
|
||||
const properties = Object.assign({}, server.properties);
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue