diff --git a/package.json b/package.json index a70db5a..10b5f11 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,6 @@ "build:node": "npm run babel:node", "clean": "rimraf dist", "lint": "npm-run-all lint:*", - "lint:js": "npm-run-all lint:js:*", - "lint:js:core": "eslint babel.config.js src/", "lint:ts": "tslint -c tslint.json 'src/**/*.ts'", "prepublishOnly": "npm-run-all clean build test", "test": "npm-run-all lint test:*", diff --git a/src/channelManager.js b/src/channelManager.js deleted file mode 100644 index a18b1e9..0000000 --- a/src/channelManager.js +++ /dev/null @@ -1,127 +0,0 @@ -var Events = require('events'); - -class User { - constructor(pubkey, username) { - this.pubkey = pubkey; - this.username = username; - this.online = false; - this.lastSeen = 0; - } -} - -class Users { - constructor() { - this.users = {}; - } - - addUserOrUpdate(user) { - this.users[user.pubkey] = user; - } - - addOrUpdateUserKey(pubkey, username) { - if (!this.users[pubkey]) { - this.users[pubkey] = new User(pubkey, username); - } - this.users[pubkey].lastSeen = (new Date().getTime()); - this.users[pubkey].online = true; - return this.users[pubkey]; - } - - getUsers() { - let userList = []; - for (let pubkey in this.users) { - userList.push(pubkey); - } - return userList; - } - - updateUsersState() { - let currentTime = (new Date().getTime()); - for (let pubkey in this.users) { - let user = this.users[pubkey]; - if (currentTime - user.lastSeen > 10*1000) { - user.online = false; - } - } - } - -} - -class ChannelManager { - constructor() { - this.channels = []; - this.events = new Events(); - this.currentChannel = 0; - this.allUsers = new Users(); - } - - addChannel(channelName, type, extraData) { - if(this.getChannel(channelName)) return; - - let channel = {name: channelName, pendingMessages: [], type, ...extraData}; - channel.users = new Users(); - this.channels.push(channel); - this.events.emit("update"); - } - - getChannel(channelName) { - return this.channels.find(c => c.name === channelName); - } - - getCurrentChannel() { - return this.channels[this.currentChannel]; - } - - addMessage(channelName, message, pubkey, username) { - let channel = this.getChannel(channelName); - if (channelName !== this.channels[this.currentChannel].name) { - channel.pendingMessages.push({pubkey, username, message}); - } else { - this.events.emit("newMessage", channelName, username, message); - } - let user = this.allUsers.addOrUpdateUserKey(pubkey, username); - channel.users.addUserOrUpdate(user); - - this.events.emit("update"); - } - - dumpPendingMessages() { - let messages = this.channels[this.currentChannel].pendingMessages.slice(0); - this.channels[this.currentChannel].pendingMessages = []; - return messages; - } - - switchChannelIndex(index) { - if (index < 0) return; - if (index >= this.channels.length) return; - this.currentChannel = index; - this.events.emit("update"); - this.events.emit("channelSwitch"); - } - - getChannelList() { - return this.channels.map((c) => { - - const prefix = c.type === 'channel' ? '#' : ''; - - if (c.name === this.channels[this.currentChannel].name) { - return `${prefix}${c.name}`.green; - } - if (c.pendingMessages.length === 0) { - return `${prefix}${c.name}`; - } - return `${prefix}${c.name} (${c.pendingMessages.length})`; - }); - } - - getUsersInCurrentChannel() { - let channel = this.getCurrentChannel(); - let user_keys = channel.users.getUsers(); - let users = user_keys.map((pubkey) => { - return this.allUsers.users[pubkey]; - }); - return users; - } -} - -module.exports = ChannelManager; diff --git a/src/channelManager.ts b/src/channelManager.ts new file mode 100644 index 0000000..6747f65 --- /dev/null +++ b/src/channelManager.ts @@ -0,0 +1,92 @@ +const Events = require("events"); +import Users from "./users"; +import colors from "colors"; + +class ChannelManager { + public channels: any[]; + public events: any; + public allUsers: Users; + private currentChannel: number; + + constructor() { + this.channels = []; + this.events = new Events(); + this.currentChannel = 0; + this.allUsers = new Users(); + } + + public addChannel(channelName: string, type: string, extraData?: any) { + if (this.getChannel(channelName)) { + return; + } + + const channel = {name: channelName, pendingMessages: [], type, ...extraData}; + channel.users = new Users(); + this.channels.push(channel); + this.events.emit("update"); + } + + public getChannel(channelName: string) { + return this.channels.find((c) => c.name === channelName); + } + + public getCurrentChannel() { + return this.channels[this.currentChannel]; + } + + public addMessage(channelName: string, message: string, pubkey: string, username: string) { + const channel = this.getChannel(channelName); + if (channelName !== this.channels[this.currentChannel].name) { + channel.pendingMessages.push({pubkey, username, message}); + } else { + this.events.emit("newMessage", channelName, username, message); + } + const user = this.allUsers.addOrUpdateUserKey(pubkey, username); + channel.users.addUserOrUpdate(user); + + this.events.emit("update"); + } + + public dumpPendingMessages() { + const messages = this.channels[this.currentChannel].pendingMessages.slice(0); + this.channels[this.currentChannel].pendingMessages = []; + return messages; + } + + public switchChannelIndex(index: number) { + if (index < 0) { + return; + } + if (index >= this.channels.length) { + return; + } + this.currentChannel = index; + this.events.emit("update"); + this.events.emit("channelSwitch"); + } + + public getChannelList() { + return this.channels.map((c) => { + const prefix = c.type === "channel" ? "#" : ""; + + if (c.name === this.channels[this.currentChannel].name) { + return colors.green(`${prefix}${c.name}`); + } + if (c.pendingMessages.length === 0) { + return `${prefix}${c.name}`; + } + return `${prefix}${c.name} (${c.pendingMessages.length})`; + }); + } + + public getUsersInCurrentChannel() { + const channel = this.getCurrentChannel(); + const userKeys = channel.users.getUsers(); + const users = userKeys.map((pubkey: string) => { + return this.allUsers.users[pubkey]; + }); + return users; + } +} + +export default ChannelManager; diff --git a/src/index.js b/src/index.ts similarity index 58% rename from src/index.js rename to src/index.ts index df00bfe..5e7c3bd 100644 --- a/src/index.js +++ b/src/index.ts @@ -1,70 +1,72 @@ -var UI = require('./ui.js'); -var StatusJS = require('status-js-api'); -var ChannelManager = require('./channelManager.js'); +import StatusJS from "status-js-api"; +import UI from "./ui"; +import ChannelManager from "./channelManager"; const DEFAULT_CHANNEL = "mytest"; const CONTACT_CODE_REGEXP = /^(0x)?[0-9a-f]{130}$/i; -let userPubKey; +let userPubKey: any; -var ui = new UI(); +const ui = new UI(); -var channels = new ChannelManager(); +const channels = new ChannelManager(); -let usersTyping = {}; +const usersTyping = {}; -channels.events.on('update', () => { +channels.events.on("update", () => { ui.availableChannels(channels.getChannelList()); }); -channels.events.on('channelSwitch', () => { +channels.events.on("channelSwitch", () => { ui.logEntry("-------------------"); ui.logEntry("now viewing #" + channels.getCurrentChannel().name); - channels.dumpPendingMessages().forEach((message) => { - let msg = (message.username + ">").green + " " + message.message; + channels.dumpPendingMessages().forEach((message: any) => { + const msg = (message.username + ">").green + " " + message.message; ui.logEntry(msg); }); }); -channels.events.on('newMessage', (channelName, username, message) => { - let msg = (username + ">").green + " " + message; +channels.events.on("newMessage", (channelName: string, username: string, message: string) => { + const msg = (username + ">").green + " " + message; ui.logEntry(msg); }); -var updateUsers = function() { - let users = channels.getUsersInCurrentChannel().map((x) => { +const updateUsers = () => { + const users = channels.getUsersInCurrentChannel().map((x: any) => { return {name: x.username, status: (x.online ? "on" : "offline")}; }); ui.availableUsers(users); }; -var handleProtocolMessages = function(channelName, data) { +const handleProtocolMessages = (channelName: string, data: any) => { // TODO: yes this is ugly, can be moved to the lib level - let msg = JSON.parse(JSON.parse(data.payload)[1][0]); - let fromUser = data.data.sig; + const msg = JSON.parse(JSON.parse(data.payload)[1][0]); + const fromUser = data.data.sig; - if (msg.type === 'ping') { - let user = channels.allUsers.addOrUpdateUserKey(fromUser, data.username); - let channel = channels.getChannel(channelName); + if (msg.type === "ping") { + const user = channels.allUsers.addOrUpdateUserKey(fromUser, data.username); + const channel = channels.getChannel(channelName); channel.users.addUserOrUpdate(user); channels.events.emit("update"); - } + } - if (msg.type === 'typing') { - if (fromUser === userPubKey) return; // ignore typing events from self + if (msg.type === "typing") { + if (fromUser === userPubKey) { + return; // ignore typing events from self + } usersTyping[fromUser] = (new Date().getTime()); } }; -channels.events.on('update', updateUsers); -channels.events.on('channelSwitch', updateUsers); +channels.events.on("update", updateUsers); +channels.events.on("channelSwitch", updateUsers); -setInterval(function() { - let typingUsers = []; - let currentTime = (new Date().getTime()); - for (let pubkey in usersTyping) { - let lastTyped = usersTyping[pubkey]; - if (currentTime - lastTyped > 3*1000 || currentTime < lastTyped) { +setInterval(() => { + const typingUsers = []; + const currentTime = (new Date().getTime()); + for (const pubkey of Object.keys(usersTyping)) { + const lastTyped = usersTyping[pubkey]; + if (currentTime - lastTyped > 3 * 1000 || currentTime < lastTyped) { delete usersTyping[pubkey]; } else { if (channels.allUsers.users[pubkey]) { @@ -82,8 +84,8 @@ setInterval(function() { return; } - ui.consoleState.setContent(typingUsers.join(', ') + " are typing"); -}, 0.5*1000); + ui.consoleState.setContent(typingUsers.join(", ") + " are typing"); +}, 0.5 * 1000); ui.logEntry(` Welcome to @@ -109,32 +111,31 @@ ui.logEntry(`Rejoining Channels....`); ui.logEntry(`PK: ${userPubKey}`); ui.logEntry(`-----------------------------------------------------------`); - const fs = require('fs'); - fs.writeFile("/tmp/test", await status.getPublicKey(), function(err) { + const fs = require("fs"); + fs.writeFile("/tmp/test", await status.getPublicKey(), (err: any) => { if (err) { return console.log(err); } }); - setInterval(function() { + setInterval(() => { const channel = channels.getCurrentChannel(); - if(!channel.pubKey){ + if (!channel.pubKey) { // TODO: JSON message is being displayed in the chat box of status status.sendJsonMessage(channel.name, {type: "ping"}); channels.allUsers.updateUsersState(); } }, 5 * 1000); - status.joinChat(DEFAULT_CHANNEL, () => { ui.logEntry(("Joined #" + DEFAULT_CHANNEL).green.underline); - channels.addChannel(DEFAULT_CHANNEL, 'channel'); + channels.addChannel(DEFAULT_CHANNEL, "channel"); - status.onMessage(DEFAULT_CHANNEL, (err, data) => { - let msg = JSON.parse(data.payload)[1][0]; + status.onMessage(DEFAULT_CHANNEL, (err: any, data: any) => { + const msg = JSON.parse(data.payload)[1][0]; - if (JSON.parse(data.payload)[1][1] === 'content/json') { + if (JSON.parse(data.payload)[1][1] === "content/json") { handleProtocolMessages(DEFAULT_CHANNEL, data); } else { usersTyping[data.data.sig] = 0; // user is likley no longer typing if a message was received @@ -143,30 +144,29 @@ ui.logEntry(`Rejoining Channels....`); }); }); - - status.onMessage((err, data) => { - channels.addChannel(data.username, 'contact', {pubKey: data.data.sig}); - let msg = JSON.parse(data.payload)[1][0]; - if (JSON.parse(data.payload)[1][1] === 'content/json') { + status.onMessage((err: any, data: any) => { + channels.addChannel(data.username, "contact", {pubKey: data.data.sig}); + const msg = JSON.parse(data.payload)[1][0]; + if (JSON.parse(data.payload)[1][1] === "content/json") { handleProtocolMessages(data.username, data); } else { channels.addMessage(data.username, msg, data.data.sig, data.username); } }); - ui.events.on('cmd', (cmd) => { - if (cmd.split(' ')[0] === '/join') { - let channelName = cmd.split(' ')[1].replace('#',''); + ui.events.on("cmd", (cmd: string) => { + if (cmd.split(" ")[0] === "/join") { + const channelName = cmd.split(" ")[1].replace("#", ""); ui.logEntry("joining " + channelName); status.joinChat(channelName).then(() => { ui.logEntry("joined #" + channelName); - channels.addChannel(channelName, 'channel'); + channels.addChannel(channelName, "channel"); - status.onMessage(channelName, (err, data) => { - let msg = JSON.parse(data.payload)[1][0]; + status.onMessage(channelName, (err: any, data: any) => { + const msg = JSON.parse(data.payload)[1][0]; - if (JSON.parse(data.payload)[1][1] === 'content/json') { + if (JSON.parse(data.payload)[1][1] === "content/json") { handleProtocolMessages(channelName, data); } else { channels.addMessage(channelName, msg, data.data.sig, data.username); @@ -176,14 +176,14 @@ ui.logEntry(`Rejoining Channels....`); }); return; } - if (cmd.split(' ')[0] === '/s') { - let channelNumber = cmd.split(' ')[1]; + if (cmd.split(" ")[0] === "/s") { + const channelNumber = cmd.split(" ")[1]; channels.switchChannelIndex(parseInt(channelNumber, 10)); return; } - if(cmd.split(' ')[0] === '/msg') { - let destination = cmd.substr(5); + if (cmd.split(" ")[0] === "/msg") { + const destination = cmd.substr(5); if (!(CONTACT_CODE_REGEXP.test(destination) || (/^[a-z0-9A-Z\s]{4,}$/).test(destination))) { ui.logEntry(`Invalid account`.red); @@ -191,13 +191,13 @@ ui.logEntry(`Rejoining Channels....`); } // TODO:resolve ens username - const user = Object.values(channels.allUsers.users).find(x => x.username === destination); - if(user){ - channels.addChannel(user.username, 'contact', {pubKey: user.pubkey}); + const user: any = Object.values(channels.allUsers.users).find((x: any) => x.username === destination); + if (user) { + channels.addChannel(user.username, "contact", {pubKey: user.pubkey}); channels.switchChannelIndex(channels.channels.length - 1); } else { - status.getUserName(destination).then(username => { - channels.addChannel(username, 'contact', {pubKey: destination}); + status.getUserName(destination).then((username: string) => { + channels.addChannel(username, "contact", {pubKey: destination}); channels.switchChannelIndex(channels.channels.length - 1); }); } @@ -206,7 +206,7 @@ ui.logEntry(`Rejoining Channels....`); } const channel = channels.getCurrentChannel(); - if(channel.pubKey){ + if (channel.pubKey) { status.sendMessage(channel.pubKey, cmd); channels.addMessage(channel.name, cmd, channel.pubKey, userName); } else { @@ -215,35 +215,35 @@ ui.logEntry(`Rejoining Channels....`); }); // keep track of each channel typing sent for throttling purposes - let typingNotificationsTimestamp = { - }; + const typingNotificationsTimestamp = {}; - ui.events.on('typing', (currentText) => { + ui.events.on("typing", (currentText: string) => { // TODO: use async.cargo instead and/or a to avoid unnecessary requests - if (currentText[0] === '/') return; + if (currentText[0] === "/") { + return; + } const channel = channels.getCurrentChannel(); - if(!channel.pubKey){ - let channelName = channels.getCurrentChannel().name; + if (!channel.pubKey) { + const channelName = channels.getCurrentChannel().name; if (!typingNotificationsTimestamp[channelName]) { typingNotificationsTimestamp[channelName] = { + lastEvent: 0, timeout: 0, - lastEvent: 0 }; } - let now = (new Date().getTime()); + const now = (new Date().getTime()); clearTimeout(typingNotificationsTimestamp[channelName].timeout); - if (typingNotificationsTimestamp[channelName].lastEvent === 0 || now - typingNotificationsTimestamp[channelName].lastEvent > 3*1000) { + if (typingNotificationsTimestamp[channelName].lastEvent === 0 || now - typingNotificationsTimestamp[channelName].lastEvent > 3 * 1000) { typingNotificationsTimestamp[channelName].lastEvent = (new Date().getTime()); status.sendJsonMessage(channelName, {type: "typing"}); } - typingNotificationsTimestamp[channelName].timeout = setTimeout(function() { + typingNotificationsTimestamp[channelName].timeout = setTimeout(() => { typingNotificationsTimestamp[channelName].lastEvent = (new Date().getTime()); status.sendJsonMessage(channelName, {type: "typing"}); - }, 3*1000); + }, 3 * 1000); } }); })(); - diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 0000000..0de652e --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1 @@ +declare module "status-js-api"; diff --git a/src/ui.js b/src/ui.ts similarity index 59% rename from src/ui.js rename to src/ui.ts index f0e920d..3f9e5ea 100644 --- a/src/ui.js +++ b/src/ui.ts @@ -1,21 +1,35 @@ -require('colors'); +require("colors"); const blessed = require("neo-blessed"); const Events = require("events"); class UI { - constructor(_options) { - let options = _options || {}; + public events: any; + private color: string; + private minimal: boolean; + private screen: any; + private input: any; + private consoleBox: any; + private consoleStateContainer: any; + public consoleState: any; + private wrapper: any; + private users: any; + private channels: any; + private log: any; + private logText: any; + private operations: any; + + constructor(options: any = {}) { this.events = new Events(); this.color = options.color || "green"; this.minimal = options.minimal || false; this.screen = blessed.screen({ - smartCSR: true, - title: options.title || ("StatusX"), + autoPadding: true, dockBorders: false, fullUnicode: true, - autoPadding: true + smartCSR: true, + title: options.title || ("StatusX"), }); this.layoutLog(); @@ -24,7 +38,7 @@ class UI { this.layoutCmd(); this.layoutState(); - this.screen.key(["C-c"], function () { + this.screen.key(["C-c"], () => { process.exit(0); }); @@ -36,257 +50,254 @@ class UI { this.input.focus(); } - availableUsers(users) { - let stateColors = { - 'on': 'green', - 'off': 'grey' + public availableUsers(users: any) { + const stateColors = { + off: "grey", + on: "green", }; - let user_list = Object.keys(users).map((user) => { - let userObj = users[user]; + const userList = Object.keys(users).map((user) => { + const userObj = users[user]; if (userObj.status in stateColors) { - let color = stateColors[userObj.status]; + const color = stateColors[userObj.status]; return userObj.name[color]; } return userObj.name; }); - this.users.setContent(user_list.join('\n')); + this.users.setContent(userList.join("\n")); this.screen.render(); } - availableChannels(channels) { - this.channels.setContent(channels.map((c, i) => `(${i}) ${c}`).join('\n')); + public availableChannels(channels: string[]) { + this.channels.setContent(channels.map((c, i) => `(${i}) ${c}`).join("\n")); this.screen.render(); } - setStatus(status) { + // TODO: to remove, might not be used anymore + private setStatus(status: string) { this.operations.setContent(status); this.screen.render(); } - logEntry() { - this.logText.log(...arguments); + public logEntry(...args: any[]) { + this.logText.log(...args); this.screen.render(); } - layoutLog() { + private layoutLog() { this.log = blessed.box({ - label: "Logs", - padding: 1, - width: "68%", - height: "92%", - left: "12%", - top: "0%", border: { - type: "line" + type: "line", }, + height: "92%", + label: "Logs", + left: "12%", + padding: 1, style: { - fg: -1, border: { - fg: this.color - } - } + fg: this.color, + }, + fg: -1, + }, + top: "0%", + width: "68%", }); this.logText = blessed.log({ - parent: this.log, - tags: true, - width: "100%-5", - //height: '90%', - scrollable: true, - input: false, alwaysScroll: true, + // height: "90%", + input: false, + keys: false, + mouse: true, + parent: this.log, + scrollable: true, scrollbar: { ch: " ", - inverse: true + inverse: true, }, - keys: false, + tags: true, vi: false, - mouse: true + width: "100%-5", }); this.screen.append(this.log); } - layoutUsers() { - + private layoutUsers() { this.wrapper = blessed.layout({ - width: "20%", height: "100%", - top: "0%", + layout: "grid", left: "80%", - layout: "grid" + top: "0%", + width: "20%", }); this.users = blessed.box({ - parent: this.wrapper, - label: "Users", - tags: true, - padding: this.minimal ? { - left: 1 - } : 1, - width: "100%", - height: "95%", - valign: "top", - border: { - type: "line" - }, - scrollable: true, alwaysScroll: true, + border: { + type: "line", + }, + height: "95%", + label: "Users", + padding: this.minimal ? { + left: 1, + } : 1, + parent: this.wrapper, + scrollable: true, scrollbar: { ch: " ", - inverse: true + inverse: true, }, style: { - fg: -1, border: { - fg: this.color - } - } + fg: this.color, + }, + fg: -1, + }, + tags: true, + valign: "top", + width: "100%", }); this.screen.append(this.wrapper); } - layoutChannels() { + private layoutChannels() { this.wrapper = blessed.layout({ - width: "12%", height: "100%", - top: "0%", + layout: "grid", left: "0%", - layout: "grid" + top: "0%", + width: "12%", }); this.channels = blessed.box({ - parent: this.wrapper, - label: "Channels", - tags: true, - padding: this.minimal ? { - left: 1 - } : 1, - width: "100%", - height: "95%", - valign: "top", - border: { - type: "line" - }, - scrollable: true, alwaysScroll: true, + border: { + type: "line", + }, + height: "95%", + label: "Channels", + padding: this.minimal ? { + left: 1, + } : 1, + parent: this.wrapper, + scrollable: true, scrollbar: { ch: " ", - inverse: true + inverse: true, }, style: { - fg: -1, border: { - fg: this.color - } - } + fg: this.color, + }, + fg: -1, + }, + tags: true, + valign: "top", + width: "100%", }); this.screen.append(this.wrapper); } - - layoutCmd() { + private layoutCmd() { this.consoleBox = blessed.box({ - label: 'Messages', - tags: true, - padding: 0, - width: '100%', - height: '6%', - left: '0%', - top: '95%', border: { - type: 'line' + type: "line", }, + height: "6%", + label: "Messages", + left: "0%", + padding: 0, style: { - fg: 'black', border: { - fg: this.color - } - } + fg: this.color, + }, + fg: "black", + }, + tags: true, + top: "95%", + width: "100%", }); this.input = blessed.textbox({ - parent: this.consoleBox, - name: 'input', + height: "50%", input: true, - keys: false, - top: 0, - left: 1, - height: '50%', - width: '100%-2', inputOnFocus: true, + keys: false, + left: 1, + name: "input", + parent: this.consoleBox, style: { - fg: 'green', - bg: 'black', + bg: "black", + fg: "green", focus: { - bg: 'black', - fg: 'green' - } - } + bg: "black", + fg: "green", + }, + }, + top: 0, + width: "100%-2", }); - let self = this; - - this.input.key(["C-c"], function () { - self.events.emit('exit'); + this.input.key(["C-c"], () => { + this.events.emit("exit"); process.exit(0); }); - this.input.key(["C-w"], function () { - self.input.clearValue(); - self.input.focus(); + this.input.key(["C-w"], () => { + this.input.clearValue(); + this.input.focus(); }); - this.input.key('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''), function () { - self.events.emit("typing", self.input.value); + this.input.key("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""), () => { + this.events.emit("typing", this.input.value); }); - this.input.on('submit', this.submitCmd.bind(this)); + this.input.on("submit", this.submitCmd.bind(this)); this.screen.append(this.consoleBox); } - layoutState() { + private layoutState() { this.consoleStateContainer = blessed.layout({ - width: '68%', - height: '5%', - left: '12%', - top: '92%', - layout: "grid" + height: "5%", + layout: "grid", + left: "12%", + top: "92%", + width: "68%", }); this.consoleState = blessed.box({ - parent: this.consoleStateContainer, - label: "", - tags: true, - padding: { - left: 1 - }, - width: "100%", - height: "100%", - valign: "middle", border: { - type: "line" + type: "line", }, + height: "100%", + label: "", + padding: { + left: 1, + }, + parent: this.consoleStateContainer, style: { - fg: -1, border: { - fg: this.color - } - } + fg: this.color, + }, + fg: -1, + }, + tags: true, + valign: "middle", + width: "100%", }); this.screen.append(this.consoleStateContainer); } - submitCmd(cmd) { - if (cmd !== '') { - this.events.emit('cmd', cmd); + private submitCmd(cmd: string) { + if (cmd !== "") { + this.events.emit("cmd", cmd); } this.input.clearValue(); this.input.focus(); @@ -294,4 +305,4 @@ class UI { } -module.exports = UI; +export default UI; diff --git a/src/user.ts b/src/user.ts new file mode 100644 index 0000000..e9084ea --- /dev/null +++ b/src/user.ts @@ -0,0 +1,16 @@ + +class User { + public pubkey: string; + public username: string; + public online: boolean; + public lastSeen: number; + + constructor(pubkey: string, username: string) { + this.pubkey = pubkey; + this.username = username; + this.online = false; + this.lastSeen = 0; + } +} + +export default User; diff --git a/src/users.ts b/src/users.ts new file mode 100644 index 0000000..6816220 --- /dev/null +++ b/src/users.ts @@ -0,0 +1,43 @@ +import User from "./user"; + +class Users { + public users: any; + + constructor() { + this.users = {}; + } + + public addUserOrUpdate(user: User) { + this.users[user.pubkey] = user; + } + + public addOrUpdateUserKey(pubkey: string, username: string) { + if (!this.users[pubkey]) { + this.users[pubkey] = new User(pubkey, username); + } + this.users[pubkey].lastSeen = (new Date().getTime()); + this.users[pubkey].online = true; + return this.users[pubkey]; + } + + public getUsers() { + const userList = []; + for (const pubkey of Object.keys(this.users)) { + userList.push(pubkey); + } + return userList; + } + + public updateUsersState() { + const currentTime = (new Date().getTime()); + for (const pubkey of Object.keys(this.users)) { + const user = this.users[pubkey]; + if (currentTime - user.lastSeen > 10 * 1000) { + user.online = false; + } + } + } + +} + +export default Users;