/// /// /// /// /// /// /// /// /// /// let settings: Settings; const js_render = window.jsrender || $; const native_client = window.require !== undefined; function getUserMediaFunctionPromise() : (constraints: MediaStreamConstraints) => Promise { if('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices) return constraints => navigator.mediaDevices.getUserMedia(constraints); const _callbacked_function = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; if(!_callbacked_function) return undefined; return constraints => new Promise((resolve, reject) => _callbacked_function(constraints, resolve, reject)); } interface Window { open_connected_question: () => Promise; } function setup_close() { window.onbeforeunload = event => { if(profiles.requires_save()) profiles.save(); if(!settings.static(Settings.KEY_DISABLE_UNLOAD_DIALOG, false)) { const active_connections = server_connections.server_connection_handlers().filter(e => e.connected); if(active_connections.length == 0) return; if(!native_client) { event.returnValue = "Are you really sure?
You're still connected!"; } else { const do_exit = () => { const dp = server_connections.server_connection_handlers().map(e => { if(e.serverConnection.connected()) return e.serverConnection.disconnect(tr("client closed")); return Promise.resolve(); }).map(e => e.catch(error => { console.warn(tr("Failed to disconnect from server on client close: %o"), e); })); const exit = () => { const {remote} = require('electron'); remote.getCurrentWindow().close(); }; Promise.all(dp).then(exit); /* force exit after 2500ms */ setTimeout(exit, 2500); }; if(window.open_connected_question) { event.preventDefault(); event.returnValue = "question"; window.open_connected_question().then(result => { if(result) { /* prevent quitting because we try to disconnect */ window.onbeforeunload = e => e.preventDefault(); /* allow a force quit after 5 seconds */ setTimeout(() => window.onbeforeunload, 5000); do_exit(); } }); } else { /* we're in debugging mode */ do_exit(); } } } }; } declare function moment(...arguments) : any; function setup_jsrender() : boolean { if(!js_render) { displayCriticalError("Missing jsrender extension!"); return false; } if(!js_render.views) { displayCriticalError("Missing jsrender viewer extension!"); return false; } js_render.views.settings.allowCode(true); js_render.views.tags("rnd", (argument) => { let min = parseInt(argument.substr(0, argument.indexOf('~'))); let max = parseInt(argument.substr(argument.indexOf('~') + 1)); return (Math.round(Math.random() * (min + max + 1) - min)).toString(); }); js_render.views.tags("fmt_date", (...args) => { return moment(args[0]).format(args[1]); }); js_render.views.tags("tr", (...args) => { return tr(args[0]); }); $(".jsrender-template").each((idx, _entry) => { if(!js_render.templates(_entry.id, _entry.innerHTML)) { //, _entry.innerHTML console.error("Failed to cache template " + _entry.id + " for js render!"); } else console.debug("Successfully loaded jsrender template " + _entry.id); }); return true; } async function initialize() { settings = new Settings(); try { await i18n.initialize(); } catch(error) { console.error(tr("Failed to initialized the translation system!\nError: %o"), error); displayCriticalError("Failed to setup the translation system"); return; } bipc.setup(); } async function initialize_app() { const display_load_error = message => { if(typeof(display_critical_load) !== "undefined") display_critical_load(message); else displayCriticalError(message); }; try { //Initialize main template const main = $("#tmpl_main").renderTag({ multi_session: !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION), app_version: app.ui_version() }).dividerfy(); $("body").append(main); } catch(error) { console.error(error); display_load_error(tr("Failed to setup main page!")); return; } control_bar = new ControlBar($("#control_bar")); /* setup the control bar */ if(!audio.player.initialize()) console.warn(tr("Failed to initialize audio controller!")); if(audio.player.set_master_volume) audio.player.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER) / 100); else console.warn("Client does not support audio.player.set_master_volume()... May client is too old?"); if(audio.recorder.device_refresh_available()) await audio.recorder.refresh_devices(); default_recorder = new RecorderProfile("default"); await default_recorder.initialize(); sound.initialize().then(() => { console.log(tr("Sounds initialitzed")); }); sound.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER_SOUNDS) / 100); await profiles.load(); try { await ppt.initialize(); } catch(error) { console.error(tr("Failed to initialize ppt!\nError: %o"), error); displayCriticalError(tr("Failed to initialize ppt!")); return; } setup_close(); } function str2ab8(str) { const buf = new ArrayBuffer(str.length); const bufView = new Uint8Array(buf); for (let i = 0, strLen = str.length; i < strLen; i++) { bufView[i] = str.charCodeAt(i); } return buf; } /* FIXME Dont use atob, because it sucks for non UTF-8 tings */ function arrayBufferBase64(base64: string) { base64 = atob(base64); const buf = new ArrayBuffer(base64.length); const bufView = new Uint8Array(buf); for (let i = 0, strLen = base64.length; i < strLen; i++) { bufView[i] = base64.charCodeAt(i); } return buf; } function base64_encode_ab(source: ArrayBufferLike) { const encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; let base64 = ""; const bytes = new Uint8Array(source); const byte_length = bytes.byteLength; const byte_reminder = byte_length % 3; const main_length = byte_length - byte_reminder; let a, b, c, d; let chunk; // Main loop deals with bytes in chunks of 3 for (let i = 0; i < main_length; i = i + 3) { // Combine the three bytes into a single integer chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]; // Use bitmasks to extract 6-bit segments from the triplet a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18 b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12 c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6 d = (chunk & 63) >> 0; // 63 = (2^6 - 1) << 0 // Convert the raw binary segments to the appropriate ASCII encoding base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]; } // Deal with the remaining bytes and padding if (byte_reminder == 1) { chunk = bytes[main_length]; a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2 // Set the 4 least significant bits to zero b = (chunk & 3) << 4; // 3 = 2^2 - 1 base64 += encodings[a] + encodings[b] + '=='; } else if (byte_reminder == 2) { chunk = (bytes[main_length] << 8) | bytes[main_length + 1]; a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10 b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4 // Set the 2 least significant bits to zero c = (chunk & 15) << 2; // 15 = 2^4 - 1 base64 += encodings[a] + encodings[b] + encodings[c] + '='; } return base64 } /* class TestProxy extends bipc.MethodProxy { constructor(params: bipc.MethodProxyConnectParameters) { super(bipc.get_handler(), params.channel_id && params.client_id ? params : undefined); if(!this.is_slave()) { this.register_method(this.add_slave); } if(!this.is_master()) { this.register_method(this.say_hello); this.register_method(this.add_master); } } setup() { super.setup(); } protected on_connected() { console.log("Test proxy connected"); } protected on_disconnected() { console.log("Test proxy disconnected"); } private async say_hello() : Promise { console.log("Hello World"); } private async add_slave(a: number, b: number) : Promise { return a + b; } private async add_master(a: number, b: number) : Promise { return a * b; } } interface Window { proxy_instance: TestProxy & {url: () => string}; } */ function main() { /* window.proxy_instance = new TestProxy({ client_id: settings.static_global("proxy_client_id", undefined), channel_id: settings.static_global("proxy_channel_id", undefined) }) as any; if(window.proxy_instance.is_master()) { window.proxy_instance.setup(); window.proxy_instance.url = () => { const data = window.proxy_instance.generate_connect_parameters(); return "proxy_channel_id=" + data.channel_id + "&proxy_client_id=" + data.client_id; }; } */ //http://localhost:63343/Web-Client/index.php?_ijt=omcpmt8b9hnjlfguh8ajgrgolr&default_connect_url=true&default_connect_type=teamspeak&default_connect_url=localhost%3A9987&disableUnloadDialog=1&loader_ignore_age=1 /* initialize font */ { const font = settings.static_global(Settings.KEY_FONT_SIZE, parseInt(getComputedStyle(document.body).fontSize)); $(document.body).css("font-size", font + "px"); } /* context menu prevent */ $(document).on('contextmenu', event => { if(event.isDefaultPrevented()) return; if(!settings.static_global(Settings.KEY_DISABLE_GLOBAL_CONTEXT_MENU)) event.preventDefault(); }); top_menu.initialize(); server_connections = new ServerConnectionManager($("#connection-handlers")); control_bar.initialise(); /* before connection handler to allow property apply */ const initial_handler = server_connections.spawn_server_connection_handler(); initial_handler.acquire_recorder(default_recorder, false); control_bar.set_connection_handler(initial_handler); /** Setup the XF forum identity **/ profiles.identities.update_forum(); let _resize_timeout: NodeJS.Timer; $(window).on('resize', event => { if(event.target !== window) return; if(_resize_timeout) clearTimeout(_resize_timeout); _resize_timeout = setTimeout(() => { for(const connection of server_connections.server_connection_handlers()) connection.invoke_resized_on_activate = true; const active_connection = server_connections.active_connection_handler(); if(active_connection) active_connection.resize_elements(); $(".window-resize-listener").trigger('resize'); }, 1000); }); stats.initialize({ verbose: true, anonymize_ip_addresses: true, volatile_collection_only: false }); stats.register_user_count_listener(status => { console.log("Received user count update: %o", status); }); (window).test_upload = (message?: string) => { message = message || "Hello World"; const connection = server_connections.active_connection_handler(); connection.fileManager.upload_file({ size: message.length, overwrite: true, channel: connection.getClient().currentChannel(), name: '/HelloWorld.txt', path: '' }).then(key => { console.log("Got key: %o", key); const upload = new RequestFileUpload(key); const buffer = new Uint8Array(message.length); { for(let index = 0; index < message.length; index++) buffer[index] = message.charCodeAt(index); } upload.put_data(buffer).catch(error => { console.error(error); }); }) }; server_connections.set_active_connection_handler(server_connections.server_connection_handlers()[0]); if(settings.static(Settings.KEY_FLAG_CONNECT_DEFAULT, false) && settings.static(Settings.KEY_CONNECT_ADDRESS, "")) { const profile_uuid = settings.static(Settings.KEY_CONNECT_PROFILE, (profiles.default_profile() || {id: 'default'}).id); console.log("UUID: %s", profile_uuid); const profile = profiles.find_profile(profile_uuid) || profiles.default_profile(); const address = settings.static(Settings.KEY_CONNECT_ADDRESS, ""); const username = settings.static(Settings.KEY_CONNECT_USERNAME, "Another TeaSpeak user"); const password = settings.static(Settings.KEY_CONNECT_PASSWORD, ""); const password_hashed = settings.static(Settings.KEY_FLAG_CONNECT_PASSWORD, false); if(profile && profile.valid()) { const connection = server_connections.active_connection_handler() || server_connections.spawn_server_connection_handler(); connection.startConnection(address, profile, true, { nickname: username, password: password.length > 0 ? { password: password, hashed: password_hashed } : undefined }); } else { Modals.spawnConnectModal({},{ url: address, enforce: true }, { profile: profile, enforce: true }); } } setTimeout(() => { const connection = server_connections.active_connection_handler(); /* Modals.createChannelModal(connection, undefined, undefined, connection.permissions, (cb, perms) => { }); */ //Modals.createServerModal(connection.channelTree.server, properties => Promise.resolve()); }, 1000); //Modals.spawnSettingsModal("audio-sounds"); //Modals.spawnKeySelect(console.log); } const task_teaweb_starter: loader.Task = { name: "voice app starter", function: async () => { try { await initialize_app(); main(); if(!audio.player.initialized()) { log.info(LogCategory.VOICE, tr("Initialize audio controller later!")); if(!audio.player.initializeFromGesture) { console.error(tr("Missing audio.player.initializeFromGesture")); } else $(document).one('click', event => audio.player.initializeFromGesture()); } } catch (ex) { console.error(ex.stack); if(ex instanceof ReferenceError || ex instanceof TypeError) ex = ex.name + ": " + ex.message; displayCriticalError("Failed to invoke main function:
" + ex); } }, priority: 10 }; const task_certificate_callback: loader.Task = { name: "certificate accept tester", function: async () => { const certificate_accept = settings.static_global(Settings.KEY_CERTIFICATE_CALLBACK, undefined); if(certificate_accept) { log.info(LogCategory.IPC, tr("Using this instance as certificate callback. ID: %s"), certificate_accept); try { try { await bipc.get_handler().post_certificate_accpected(certificate_accept); } catch(e) {} //FIXME remove! log.info(LogCategory.IPC, tr("Other instance has acknowledged out work. Closing this window.")); const seconds_tag = $.spawn("a"); let seconds = 5; let interval_id; interval_id = setInterval(() => { seconds--; seconds_tag.text(seconds.toString()); if(seconds <= 0) { clearTimeout(interval_id); log.info(LogCategory.GENERAL, tr("Closing window")); window.close(); return; } }, 1000); const message = "You've successfully accepted the certificate.{:br:}" + "This page will close in {0} seconds."; createInfoModal( tr("Certificate acccepted successfully"), MessageHelper.formatMessage(tr(message), seconds_tag), { closeable: false, footer: undefined } ).open(); return; } catch(error) { log.warn(LogCategory.IPC, tr("Failed to successfully post certificate accept status: %o"), error); } } else { log.info(LogCategory.IPC, tr("We're not used to accept certificated. Booting app.")); } loader.register_task(loader.Stage.LOADED, task_teaweb_starter); }, priority: 10 }; loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { name: "jrendere initialize", function: async () => { try { if(!setup_jsrender()) throw "invalid load"; } catch (error) { displayCriticalError(tr("Failed to setup jsrender")); console.error(tr("Failed to load jsrender! %o"), error); return; } }, priority: 100 }); loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { name: "app starter", function: async () => { try { await initialize(); if(app.is_web()) { loader.register_task(loader.Stage.LOADED, task_certificate_callback); } else loader.register_task(loader.Stage.LOADED, task_teaweb_starter); } catch (ex) { if(ex instanceof Error || typeof(ex.stack) !== "undefined") console.error((tr || (msg => msg))("Critical error stack trace: %o"), ex.stack); if(ex instanceof ReferenceError || ex instanceof TypeError) ex = ex.name + ": " + ex.message; displayCriticalError("Failed to boot app function:
" + ex); } }, priority: 1000 });