import * as loader from "tc-loader"; import {Stage} from "tc-loader"; import {settings, Settings} from "tc-shared/settings"; import * as log from "tc-shared/log"; import {LogCategory} from "tc-shared/log"; import * as bipc from "./ipc/BrowserIPC"; import * as sound from "./sound/Sounds"; import * as i18n from "./i18n/localize"; import {tra} from "./i18n/localize"; import {ConnectionHandler} from "tc-shared/ConnectionHandler"; import {createInfoModal} from "tc-shared/ui/elements/Modal"; import * as stats from "./stats"; import * as fidentity from "./profiles/identities/TeaForumIdentity"; import {defaultRecorder, RecorderProfile, setDefaultRecorder} from "tc-shared/voice/RecorderProfile"; import {spawnConnectModal} from "tc-shared/ui/modal/ModalConnect"; import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo"; import {formatMessage} from "tc-shared/ui/frames/chat"; import {openModalNewcomer} from "tc-shared/ui/modal/ModalNewcomer"; import * as aplayer from "tc-backend/audio/player"; import * as ppt from "tc-backend/ppt"; import * as keycontrol from "./KeyControl"; import * as React from "react"; import * as ReactDOM from "react-dom"; import * as global_ev_handler from "./events/ClientGlobalControlHandler"; import {global_client_actions} from "tc-shared/events/GlobalEvents"; import {FileTransferState, TransferProvider,} from "tc-shared/file/Transfer"; import {MenuEntryType, spawn_context_menu} from "tc-shared/ui/elements/ContextMenu"; import {copy_to_clipboard} from "tc-shared/utils/helpers"; import {checkForUpdatedApp} from "tc-shared/update"; import {setupJSRender} from "tc-shared/ui/jsrender"; import {ConnectRequestData} from "tc-shared/ipc/ConnectHandler"; import "svg-sprites/client-icons"; /* required import for init */ import "../css/load-css" import "./proto"; import "./ui/elements/ContextDivider"; import "./ui/elements/Tab"; import "./connection/CommandHandler"; import "./connection/ConnectionBase"; import "./video-viewer/Controller"; import "./profiles/ConnectionProfile"; import "./update/UpdaterWeb"; import "./file/LocalIcons"; import "./ui/frames/menu-bar/MainMenu"; import "./connection/rtc/Connection"; import "./connection/rtc/video/Connection"; import "./video/VideoSource"; import "./media/Video"; import {defaultConnectProfile, findConnectProfile} from "tc-shared/profiles/ConnectionProfile"; import {server_connections} from "tc-shared/ConnectionManager"; import {initializeConnectionUIList} from "tc-shared/ui/frames/connection-handler-list/Controller"; import ContextMenuEvent = JQuery.ContextMenuEvent; import {Registry} from "tc-shared/events"; import {ControlBarEvents} from "tc-shared/ui/frames/control-bar/Definitions"; import {ControlBar2} from "tc-shared/ui/frames/control-bar/Renderer"; import {initializeControlBarController} from "tc-shared/ui/frames/control-bar/Controller"; let preventWelcomeUI = false; async function initialize() { try { await i18n.initialize(); } catch(error) { console.error(tr("Failed to initialized the translation system!\nError: %o"), error); loader.critical_error("Failed to setup the translation system"); return; } bipc.setup(); } async function initialize_app() { initializeConnectionUIList(); global_ev_handler.initialize(global_client_actions); { const events = new Registry() initializeControlBarController(events, "main"); ReactDOM.render(, $(".container-control-bar")[0]); } /* loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { name: "settings init", priority: 10, function: async () => global_ev_handler.load_default_states(client_control_events) }); */ if(!aplayer.initialize()) console.warn(tr("Failed to initialize audio controller!")); aplayer.on_ready(() => { if(aplayer.set_master_volume) aplayer.on_ready(() => aplayer.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER) / 100)); else log.warn(LogCategory.GENERAL, tr("Client does not support aplayer.set_master_volume()... May client is too old?")); }); setDefaultRecorder(new RecorderProfile("default")); defaultRecorder.initialize().catch(error => { log.error(LogCategory.AUDIO, tr("Failed to initialize default recorder: %o"), error); }); sound.initialize().then(() => { log.info(LogCategory.AUDIO, tr("Sounds initialized")); }); sound.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER_SOUNDS) / 100); try { await ppt.initialize(); } catch(error) { log.error(LogCategory.GENERAL, tr("Failed to initialize ppt!\nError: %o"), error); loader.critical_error(tr("Failed to initialize ppt!")); return; } } export function handle_connect_request(properties: ConnectRequestData, connection: ConnectionHandler) { const profile_uuid = properties.profile || (defaultConnectProfile() || { id: 'default' }).id; const profile = findConnectProfile(profile_uuid) || defaultConnectProfile(); const username = properties.username || profile.connectUsername(); const password = properties.password ? properties.password.value : ""; const password_hashed = properties.password ? properties.password.hashed : false; if(profile && profile.valid()) { settings.changeGlobal(Settings.KEY_USER_IS_NEW, false); if(!aplayer.initialized()) { spawnYesNo(tra("Connect to {}", properties.address), tra("Would you like to connect to {}?", properties.address), result => { if(result) { aplayer.on_ready(() => handle_connect_request(properties, connection)); } else { /* Well... the client don't want to... */ } }).open(); return; } connection.startConnection(properties.address, profile, true, { nickname: username, password: password.length > 0 ? { password: password, hashed: password_hashed } : undefined }); server_connections.set_active_connection(connection); } else { spawnConnectModal({},{ url: properties.address, enforce: true }, { profile: profile, enforce: true }); } } function main() { /* initialize font */ { const font = settings.static_global(Settings.KEY_FONT_SIZE); $(document.body).css("font-size", font + "px"); settings.globalChangeListener(Settings.KEY_FONT_SIZE, value => { $(document.body).css("font-size", value + "px"); }) } /* context menu prevent */ $(document).on('contextmenu', (event: ContextMenuEvent) => { if(event.isDefaultPrevented()) { return; } if(event.target instanceof HTMLInputElement) { if((!!event.target.value || __build.target === "client") && !event.target.disabled && !event.target.readOnly && event.target.type !== "number") { spawn_context_menu(event.pageX, event.pageY, { type: MenuEntryType.ENTRY, name: tr("Copy"), callback: () => { copy_to_clipboard(event.target.value); }, icon_class: "client-copy", visible: !!event.target.value }, { type: MenuEntryType.ENTRY, name: tr("Paste"), callback: () => { const { clipboard } = __non_webpack_require__('electron'); event.target.value = clipboard.readText(); }, icon_class: "client-copy", visible: __build.target === "client", }); } event.preventDefault(); return; } if(settings.static_global(Settings.KEY_DISABLE_GLOBAL_CONTEXT_MENU)) { event.preventDefault(); } }); window.removeLoaderContextMenuHook(); //top_menu.initialize(); const initial_handler = server_connections.spawn_server_connection(); initial_handler.acquireInputHardware().then(() => {}); server_connections.set_active_connection(initial_handler); /** Setup the XF forum identity **/ fidentity.update_forum(); keycontrol.initialize(); stats.initialize({ verbose: true, anonymize_ip_addresses: true, volatile_collection_only: false }); stats.register_user_count_listener(status => { log.info(LogCategory.STATISTICS, tr("Received user count update: %o"), status); }); server_connections.set_active_connection(server_connections.all_connections()[0]); checkForUpdatedApp(); (window as any).test_download = async () => { const connection = server_connections.active_connection(); const download = connection.fileManager.initializeFileDownload({ targetSupplier: async () => await TransferProvider.provider().createDownloadTarget(), name: "HomeStudent2019Retail.img", path: "/", channel: 4 }); console.log("Download stated"); await download.awaitFinished(); console.log("Download finished (%s)", FileTransferState[download.transferState()]); //console.log(await (download.target as ResponseTransferTarget).getResponse().blob()); console.log("Have buffer"); }; (window as any).test_upload = async () => { const connection = server_connections.active_connection(); const download = connection.fileManager.initializeFileUpload({ source: async () => await TransferProvider.provider().createTextSource("Hello my lovely world...."), name: "test-upload.txt", path: "/", channel: 4 }); console.log("Download stated"); await download.awaitFinished(); console.log("Download finished (%s)", FileTransferState[download.transferState()]); //console.log(await (download.target as ResponseTransferTarget).getResponse().blob()); console.log("Have buffer"); }; /* schedule it a bit later then the main because the main function is still within the loader */ setTimeout(() => { //(window as any).spawnVideo(); /* Modals.createChannelModal(connection, undefined, undefined, connection.permissions, (cb, perms) => { }); */ // Modals.openServerInfo(connection.channelTree.server); //Modals.createServerModal(connection.channelTree.server, properties => Promise.resolve()); //Modals.openClientInfo(connection.getClient()); //Modals.openServerInfoBandwidth(connection.channelTree.server); //Modals.openBanList(connection); /* Modals.spawnBanClient(connection,[ {name: "WolverinDEV", unique_id: "XXXX"}, {name: "WolverinDEV", unique_id: "XXXX"}, {name: "WolverinDEV", unique_id: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}, {name: "WolverinDEV", unique_id: "YYY"} ], () => {}); */ }, 4000); //spawnSettingsModal("general-keymap"); //Modals.spawnKeySelect(console.log); //Modals.spawnBookmarkModal(); /* { const modal = createModal({ header: tr("Test Net Graph"), body: () => { const canvas = $.spawn("canvas") .css("position", "absolute") .css({ top: 0, bottom: 0, right: 0, left: 0 }); return $.spawn("div") .css("height", "5em") .css("width", "30em") .css("position", "relative") .append(canvas); }, footer: null }); const graph = new net.graph.Graph(modal.htmlTag.find("canvas")[0] as any); graph.initialize(); modal.close_listener.push(() => graph.terminate()); modal.open(); } */ //setTimeout(() => spawnPermissionEditorModal(server_connections.active_connection()), 3000); //setTimeout(() => spawnGroupCreate(server_connections.active_connection(), "server"), 3000); if(settings.static_global(Settings.KEY_USER_IS_NEW) && !preventWelcomeUI) { const modal = openModalNewcomer(); modal.close_listener.push(() => settings.changeGlobal(Settings.KEY_USER_IS_NEW, false)); } //spawnGlobalSettingsEditor(); //spawnVideoPopout(server_connections.active_connection(), "https://www.youtube.com/watch?v=9683D18fyvs"); } const task_teaweb_starter: loader.Task = { name: "voice app starter", function: async () => { try { await initialize_app(); main(); if(!aplayer.initialized()) { log.info(LogCategory.VOICE, tr("Initialize audio controller later!")); if(!aplayer.initializeFromGesture) { console.error(tr("Missing aplayer.initializeFromGesture")); } else $(document).one('click', () => aplayer.initializeFromGesture()); } loader.config.abortAnimationOnFinish = settings.static_global(Settings.KEY_LOADER_ANIMATION_ABORT); } catch (ex) { console.error(ex.stack); if(ex instanceof ReferenceError || ex instanceof TypeError) ex = ex.name + ": " + ex.message; loader.critical_error("Failed to invoke main function:
" + ex); } }, priority: 10 }; const task_connect_handler: loader.Task = { name: "Connect handler", function: async () => { const address = settings.static(Settings.KEY_CONNECT_ADDRESS, ""); const chandler = bipc.getInstanceConnectHandler(); if(settings.static(Settings.KEY_FLAG_CONNECT_DEFAULT, false) && address) { const connect_data = { address: address, profile: settings.static(Settings.KEY_CONNECT_PROFILE, ""), username: settings.static(Settings.KEY_CONNECT_USERNAME, ""), password: { value: settings.static(Settings.KEY_CONNECT_PASSWORD, ""), hashed: settings.static(Settings.KEY_FLAG_CONNECT_PASSWORD, false) } }; if(chandler && !settings.static(Settings.KEY_CONNECT_NO_SINGLE_INSTANCE)) { try { await chandler.post_connect_request(connect_data, () => new Promise(resolve => { spawnYesNo(tr("Another TeaWeb instance is already running"), tra("Another TeaWeb instance is already running.{:br:}Would you like to connect there?"), response => { resolve(response); }, { closeable: false }).open(); })); log.info(LogCategory.CLIENT, tr("Executed connect successfully in another browser window. Closing this window")); const message = "You're connecting to {0} within the other TeaWeb instance.{:br:}" + "You could now close this page."; createInfoModal( tr("Connecting successfully within other instance"), formatMessage(/* @tr-ignore */ tr(message), connect_data.address), { closeable: false, footer: undefined } ).open(); return; } catch(error) { log.info(LogCategory.CLIENT, tr("Failed to execute connect within other TeaWeb instance. Using this one. Error: %o"), error); } } preventWelcomeUI = true; loader.register_task(loader.Stage.LOADED, { priority: 0, function: async () => handle_connect_request(connect_data, server_connections.active_connection() || server_connections.spawn_server_connection()), name: tr("default url connect") }); } if(chandler) { /* no instance avail, so lets make us avail */ chandler.callback_available = data => { return !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION); }; chandler.callback_execute = data => { preventWelcomeUI = true; handle_connect_request(data, server_connections.spawn_server_connection()); return true; } } loader.register_task(loader.Stage.LOADED, task_teaweb_starter); }, priority: 10 }; const task_certificate_callback: loader.Task = { name: "certificate accept tester", function: async () => { loader.register_task(loader.Stage.LOADED, task_connect_handler); }, priority: 10 }; loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { name: "jrendere initialize", function: async () => { try { if(!setupJSRender()) throw "invalid load"; } catch (error) { loader.critical_error(tr("Failed to setup jsrender")); console.error(tr("Failed to load jsrender! %o"), error); return; } }, priority: 110 }); loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { name: "app starter", function: async () => { try { await initialize(); if(__build.target == "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; loader.critical_error("Failed to boot app function:
" + ex); } }, priority: 1000 }); loader.register_task(loader.Stage.LOADED, { name: "error task", function: async () => { if(Settings.instance.static(Settings.KEY_LOAD_DUMMY_ERROR, false)) { loader.critical_error("The tea is cold!", "Argh, this is evil! Cold tea dosn't taste good."); throw "The tea is cold!"; } }, priority: 20 }); loader.register_task(Stage.JAVASCRIPT_INITIALIZING,{ name: "app init", function: async () => { try { //Initialize main template const main = $("#tmpl_main").renderTag({ multi_session: !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION), app_version: __build.version }).dividerfy(); $("body").append(main); } catch(error) { log.error(LogCategory.GENERAL, error); loader.critical_error(tr("Failed to setup main page!")); return; } }, priority: 100 });