var selectTags = []; var ab = { tags: [], peers: [] }; var abLoading; var abError; var current_menu_peer_id = ''; var current_menu_tag = ''; class AddressBook: Reactor.Component { this var style; this var selectedTags = function() { var tags = handler.get_local_option("selected-tags"); if (tags) return tags.split(","); return []; }(); function render() { if (!handler.get_local_option("access_token")) { return
{translate("Login")}
; } if (abLoading) { return
; } else if (abError) { return
{abError}
{translate("Retry")}
; } var peers = this.getPeers(); var me = this; return
  • {translate('Add ID')}
  • {translate('Add Tag')}
  • {translate('Unselect all tags')}
  • {translate('Remove')}
  • {translate('Tags')}{svg_menu}
    {ab.tags.map(function(t) { return = 0 ? "active" : "inactive"}>{t}; })}
    ; } event mouseup $(#tags span) (evt, me) { if(evt.propButton) { current_menu_tag = me.text; me.popup($(#tag-context)); return true; } } event click $(#retry) (_, __) { refreshCurrentUser(); } event click $(#login-link) (_, __) { login(); } event click $(#tags-label svg#menu) (_, me) { me.popup($(#ab-context)); } event click $(#add-id) (_, __) { var me = this; msgbox( "custom-add-id", translate("Add ID"),
    {translate("whitelist_sep")}
    , "", function(res=null) { if (!res) return; var value = (res.text || "").trim(); var values = value.split(/[\s,;\n]+/g); if (values.length == 0) return; for (var v in values) { var found; for (var i = 0; i < ab.peers.length; ++i) { if (ab.peers[i].id == v) { found = true; break; } } if (found) continue; ab.peers.push({ id: v }); } updateAb(); me.update(); }, 300); } event click $(#add-tag) (_, __) { var me = this; msgbox("custom-add-tag", translate("Add Tag"),
    {translate("whitelist_sep")}
    , "", function(res=null) { if (!res) return; var value = (res.text || "").trim(); var values = value.split(/[\s,;\n]+/g); if (values.length == 0) return; for (var v in values) { if (ab.tags.indexOf(v) < 0) { ab.tags.push(v); } } updateAb(); me.update(); }, 300); } event click $(#remove-tag) (_, me) { var tag = current_menu_tag; var i = ab.tags.indexOf(tag); ab.tags.splice(i, 1); for (var p in ab.peers) { if (p.tags) { i = p.tags.indexOf(tag); if (i >= 0) p.tags.splice(i, 1); } } updateAb(); this.update(); } event click $(#unselect-tags) (_, me) { this.selectedTags = []; handler.set_local_option("selected-tags", ""); this.update(); } event click $(#tags span) (_, me) { me.attributes.toggleClass('active'); if (me.attributes.hasClass('active')) { this.selectedTags.push(me.text); } else { this.selectedTags.splice(this.selectedTags.indexOf(me.text), 1); } handler.set_local_option("selected-tags", this.selectedTags.join(',')); this.update(); } function getPeers() { var tags = []; for (var t in this.selectedTags) { if (ab.tags.indexOf(t) >= 0) tags.push(t); } if (tags.length != this.selectedTags.length) { this.selectedTags = tags; handler.set_local_option("selected-tags", tags.join(",")); stdout.println("updated selected tags"); } if (tags.length == 0) return ab.peers; var peers = []; if (tags.length > 0) { for (var p in ab.peers) { for (var t in (p.tags || [])) { if (tags.indexOf(t) >= 0) { peers.push(p); break; } } } } else { peers = ab.peers; } return peers; } } class SelectTags: Reactor.Component { function this(params) { selectTags = this; this.tags = params.tags; } function render() { var me = this; return
    {ab.tags.map(function(t) { return = 0 ? "active" : "inactive"}>{t}; })}
    ; } event click $(#tags span) (_, me) { me.attributes.toggleClass('active'); var i = this.tags.indexOf(me.text); if (i < 0) { this.tags.push(me.text); } else { this.tags.splice(i, 1); } } } var svg_tile = ; var svg_list = ; var search_icon = ; var clear_icon = ; function getSessionsStyleOption(type) { return (type || "recent") + "-sessions-style"; } function getSessionsStyle(type) { var v = handler.get_local_option(getSessionsStyleOption(type)); if (!v) v = type == "ab" ? "list" : "tile"; return v; } var searchPatterns = {}; class SearchBar: Reactor.Component { this var type = ""; function this(params) { this.type = (params || {}).type || ""; } function render() { var value = searchPatterns[this.type] || ""; var me = this; self.timer(1ms, function() { (me.search_id || {}).value = value; }); return
    {search_icon} {value && {clear_icon}}
    ; } event click $(span.clear-input) { this.onChange(''); } event change $(input) (_, el) { this.onChange(el.value.trim().toLowerCase()); } function onChange(v) { searchPatterns[this.type] = v; app.multipleSessions.update(); } } class SessionStyle: Reactor.Component { this var type = ""; function this(params) { this.type = (params || {}).type || ""; } function render() { var sessionsStyle = getSessionsStyle(this.type); return
    {svg_tile} {svg_list}
    ; } event click $(span.inactive) { var option = getSessionsStyleOption(this.type); var sessionsStyle = getSessionsStyle(this.type); handler.set_local_option(option, sessionsStyle == "tile" ? "list" : "tile"); if (is_linux) { app.multipleSessions.stupidUpdate(); } else { app.multipleSessions.update(); } } } class SessionList: Reactor.Component { this var sessions = []; this var type = ""; this var style; function this(params) { this.sessions = params.sessions; this.type = params.type || ""; this.style = getSessionsStyle(this.type); } function getSessions() { var p = searchPatterns[this.type]; if (!p) return this.sessions; var tmp = []; this.sessions.map(function(s) { var name = (s[4] || s.alias || "").toLowerCase(); var id = (s[0] || s.id || "").toLowerCase(); var user = (s[1] || "").toLowerCase(); var hostname = (s[2] || "").toLowerCase(); if (name.indexOf(p) >= 0 || id.indexOf(p) >= 0 || user.indexOf(p) >= 0 || hostname.indexOf(p) >= 0) { tmp.push(s); } }); return tmp; } function render() { var sessions = this.getSessions(); if (sessions.length == 0) { return
    {translate("Empty")}
    ; } var me = this; sessions = sessions.map(function(x) { return me.getSession(x); }); return
  • {translate('Connect')}
  • {translate('Transfer File')}
  • {translate('TCP Tunneling')}
  • {svg_checkmark}{translate('Always connect via relay')}
  • RDP
  • {translate('WOL')}
  • {this.type != "lan" &&
  • {translate('Rename')}
  • } {this.type != "fav" &&
  • {translate('Remove')}
  • } {is_win &&
  • {translate('Create Desktop Shortcut')}
  • }
  • {translate('Unremember Password')}
  • {(!this.type || this.type == "fav") &&
  • {translate('Add to Favorites')}
  • } {(!this.type || this.type == "fav") &&
  • {translate('Remove from Favorites')}
  • } {this.type == "ab" &&
  • {translate('Edit Tag')}
  • } {sessions}
    ; } function getSession(s) { var id = s[0] || s.id || ""; var username = s[1] || s.username || ""; var hostname = s[2] || s.hostname || ""; var platform = s[3] || s.platform || ""; var alias = s[4] || s.alias || ""; if (this.style == "list") { return
    {platform && platformSvg(platform, "white")}
    {alias ? alias : formatId(id)}
    {username}@{hostname}
    {svg_menu}
    ; } return
    {platform && platformSvg(platform, "white")}
    {username}@{hostname}
    {alias ? alias : formatId(id)}
    {svg_menu}
    ; } event dblclick $(div.remote-session-link) (evt, me) { createNewConnect(me.id, "connect"); } event click $(#menu) (_, me) { var id = me.parent.parent.id; var platform = me.parent.parent.attributes["platform"]; this.$(#rdp).style.set{ display: (platform == "Windows" && is_win) ? "block" : "none", }; this.$(#forget-password).style.set{ display: handler.peer_has_password(id) ? "block" : "none", }; if (!this.type || this.type == "fav") { var in_fav = handler.get_fav().indexOf(id) >= 0; this.$(#add-fav).style.set{ display: in_fav ? "none" : "block", }; this.$(#remove-fav).style.set{ display: in_fav ? "block" : "none", }; } // https://sciter.com/forums/topic/replacecustomize-context-menu/ var menu = this.$(menu#remote-context); current_menu_peer_id = id; var el = this.$(li#force-always-relay); if (el) { var force = handler.get_peer_option(id, "force-always-relay"); el.attributes.toggleClass("selected", force == "Y"); } var conn = this.$(menu #connect); if (conn) { var alias = me.parent.parent.$(#alias); if (alias) { alias = alias.text.replace(' ', ''); if (alias != id) { conn.text = translate('Connect') + ' ' + id; } else { conn.text = translate('Connect'); } } } me.popup(menu); } event click $(menu#remote-context li) (evt, me) { var action = me.id; var id = current_menu_peer_id; if (action == "connect") { createNewConnect(id, "connect"); } else if (action == "transfer") { createNewConnect(id, "file-transfer"); } else if (action == "wol") { handler.send_wol(id); } else if (action == "remove") { if (this.type == "ab") { for (var i = 0; i < ab.peers.length; ++i) { if (ab.peers[i].id == id) { ab.peers.splice(i, 1); app.update(); updateAb(); break; } } } else if (this.type == "lan") { handler.remove_discovered(id); app.update(); } else { handler.remove_peer(id); app.update(); } } else if (action == "forget-password") { handler.forget_password(id); } else if (action == "shortcut") { handler.create_shortcut(id); } else if (action == "rdp") { if (is_edit_rdp_port) { is_edit_rdp_port = false; return; } createNewConnect(id, "rdp"); } else if (action == "add-fav") { var favs = handler.get_fav(); if (favs.indexOf(id) < 0) { favs = [id].concat(favs); handler.store_fav(favs); } app.multipleSessions.update(); app.update(); } else if (action == "remove-fav") { var favs = handler.get_fav(); var i = favs.indexOf(id); favs.splice(i, 1); handler.store_fav(favs); app.multipleSessions.update(); } else if (action == "tunnel") { createNewConnect(id, "port-forward"); } else if (action == "rename") { var old_name = handler.get_peer_option(id, "alias"); var abPeer; if (this.type == "ab") { for (var v in ab.peers) { if (v.id == id) { abPeer = v; old_name = v.alias || ""; } } } msgbox("custom-rename", "Rename", "
    \
    \
    \ ", "", function(res=null) { if (!res) return; var name = (res.name || "").trim(); if (name != old_name) { if (abPeer) { abPeer.alias = name; updateAb(); } handler.set_peer_option(id, "alias", name); } app.update(); }); } else if (action == "force-always-relay") { var force = handler.get_peer_option(id, "force-always-relay"); handler.set_peer_option(id, "force-always-relay", force == "Y" ? "" : "Y"); } else if (action == "edit-tag") { var peer; for (var v in ab.peers) { if (v.id == id) { peer = v; } } if (!peer) return; msgbox("custom-edit-tag", "Edit Tag", , "", function(res=null) { if (!res) return; peer.tags = selectTags.tags; updateAb(); }, 260, 500, 0, "size: *; margin: 2em 0;"); } } } function getSessionsType() { return handler.get_local_option("show-sessions-type"); } class Favorites: Reactor.Component { function render() { var sessions = handler.get_fav().map(function(f) { return handler.get_peer(f); }); return ; } } class MultipleSessions: Reactor.Component { function render() { var type = getSessionsType(); return
    {translate('Recent Sessions')} {translate('Favorites')} {handler.is_installed() && {translate('Discovered')}} {translate('Address Book')}
    {!this.hidden && } {!this.hidden && }
    {!this.hidden && ((type == "fav" && ) || (type == "lan" && handler.is_installed() && ) || (type == "ab" && ) || )}
    ; } function stupidUpdate() { /* hidden is workaround of stupid sciter bug */ this.hidden = true; this.update(); var me = this; self.timer(60ms, function() { me.hidden = false; me.update(); self.timer(30ms, function() { me.onSize(); }); }); } event click $(div#sessions-type span.inactive) (_, el) { if (el.id == "lan") { discover(); } handler.set_local_option('show-sessions-type', el.id || ""); this.stupidUpdate(); } function onSize() { var w = this.$(.sessions-bar .sessions-tab).box(#width); var len = translate('Recent Sessions').length; var totalChars = 0; var nEle = 0; for (var el in this.$$(#sessions-type span)) { nEle += 1; totalChars += el.text.length; } for (var el in this.$$(#sessions-type span)) { var maxWidth = (w - nEle * 2 * 8) * el.text.length / totalChars; if (maxWidth < 0) maxWidth = 36; el.style.set{ "max-width": maxWidth + "px", }; } } } function discover() { handler.discover(); var tries = 15; function update() { self.timer(300ms, function() { tries -= 1; if (tries == 0) return; update(); var p = (app || {}).multipleSessions; if (p) { p.update(); } }); } update(); } if (getSessionsType() == "lan" && handler.is_installed()) { discover(); } class LanPeers: Reactor.Component { function render() { var sessions = []; try { sessions = JSON.parse(handler.get_lan_peers()); } catch (_) {} return ; } } view.on("size", function() { if (app && app.multipleSessions) app.multipleSessions.onSize(); }); /* { peers: [{id: "abcd", username: "", hostname: "", platform: "", alias: "", tags: ["", "", ...]}, ...], tags: [], } */ function handleAbError(err) { abLoading = false; err = translate(err); stderr.println(err); abError = err; app.update(); } function getAb() { abLoading = true; abError = ""; app.update(); httpRequest(handler.get_api_server() + "/api/ab/get", #post, {}, function(data) { if (data) { if (data.error) { handleAbError(data.error); return; } var tm = data.updated_at; ab = JSON.parse(data.data); if (!ab.tags) ab.tags = []; if (!ab.peers) ab.peers = []; } abLoading = false; app.update(); }, function(err, status) { handleAbError(err); }, getHttpHeaders()); } function updateAb() { httpRequest(handler.get_api_server() + "/api/ab", #post, { data: JSON.stringify(ab) }, function(data) { }, function(err, status) { }, getHttpHeaders()); } function resetAb() { ab = { tags: [], peers: [] }; app.update(); } function updateAbPeer() { if (ab.peers.length == 0) return; // to-do: inefficient var sessions = handler.get_recent_sessions(); if (sessions.length == 0) return; var s = sessions[0]; var id = s[0] || ""; var p; for (var tmp in ab.peers) { if (tmp.id == id) p = tmp; } if (!p) return; var username = s[1] || ""; var hostname = s[2] || ""; var platform = s[3] || ""; var alias = s[4] || ""; var updated; if (username != (p.username || "")) { p.username = username; updated = true; } if (hostname != (p.hostname || "")) { p.hostname = hostname; updated = true; } if (platform != (p.platform || "")) { p.platform = platform; updated = true; } if (alias != (p.alias || "")) { if (alias) { p.alias = alias; } else if (p.alias) { handler.set_peer_option(id, "alias", p.alias); } updated = true; } if (updated) { updateAb(); stdout.println("Ab peer updated"); } } var is_edit_rdp_port; class EditRdpPort: Reactor.Component { function render() { return {svg_edit}; } function onMouse(evt) { if (evt.type == Event.MOUSE_DOWN) { is_edit_rdp_port = true; editRdpPort(); } } } function editRdpPort() { var id = current_menu_peer_id; var p0 = handler.get_peer_option(id, "rdp_port"); var port = p0 ? : ; var name0 = handler.get_peer_option(id, "rdp_username"); var pass0 = handler.get_peer_option(id, "rdp_password"); msgbox("custom-rdp-port", 'RDP ' + translate('Settings'),
    {translate('Port')}:{port}
    {translate('Username')}:
    {translate('Password')}:
    , "", function(res=null) { if (!res) return; var p = (res.port || '').trim(); if (p != p0) { if (!p) p = '0'; p = p.toNumber(); if (p < 0 || p != p.toInteger()) { return translate("Invalid port"); } if (p == 0) p = ""; else p = p.toInteger() + ''; handler.set_peer_option(id, "rdp_port", p); } var name = (res.username || '').trim(); if (name != name0) { handler.set_peer_option(id, "rdp_username", name); } var pass = (res.password || '').trim(); if (pass != pass0) { handler.set_peer_option(id, "rdp_password", pass); } }, 240); }