abstract client functionality into its own module
This commit is contained in:
parent
d968103bbd
commit
7e54d33a73
|
@ -2,3 +2,4 @@ node_modules
|
||||||
dist
|
dist
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
TODO
|
||||||
|
|
|
@ -67,14 +67,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||||
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
|
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
|
||||||
},
|
},
|
||||||
"async": {
|
|
||||||
"version": "2.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
|
|
||||||
"integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
|
|
||||||
"requires": {
|
|
||||||
"lodash": "^4.17.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"async-limiter": {
|
"async-limiter": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
|
||||||
|
@ -1222,10 +1214,10 @@
|
||||||
"sha3": "^1.1.0"
|
"sha3": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash.isequal": {
|
||||||
"version": "4.17.11",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
|
||||||
},
|
},
|
||||||
"lowercase-keys": {
|
"lowercase-keys": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@ -1698,6 +1690,14 @@
|
||||||
"inherits": "^2.0.1"
|
"inherits": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rxjs": {
|
||||||
|
"version": "6.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz",
|
||||||
|
"integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
|
@ -2012,6 +2012,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
|
||||||
"integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0="
|
"integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0="
|
||||||
},
|
},
|
||||||
|
"tslib": {
|
||||||
|
"version": "1.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
|
||||||
|
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
|
||||||
|
},
|
||||||
"tunnel-agent": {
|
"tunnel-agent": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
|
@ -2034,14 +2039,6 @@
|
||||||
"mime-types": "~2.1.18"
|
"mime-types": "~2.1.18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typedarray-to-buffer": {
|
|
||||||
"version": "3.1.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
|
|
||||||
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
|
|
||||||
"requires": {
|
|
||||||
"is-typedarray": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ultron": {
|
"ultron": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
|
||||||
|
@ -2389,8 +2386,17 @@
|
||||||
"integrity": "sha512-wAnENuZx75T5ZSrT2De2LOaUuPf2yRjq1VfcbD7+Zd79F3DZZLBJcPyCNVQ1U0fAXt0wfgCKl7sVw5pffqR9Bw==",
|
"integrity": "sha512-wAnENuZx75T5ZSrT2De2LOaUuPf2yRjq1VfcbD7+Zd79F3DZZLBJcPyCNVQ1U0fAXt0wfgCKl7sVw5pffqR9Bw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"underscore": "1.8.3",
|
"underscore": "1.8.3",
|
||||||
"web3-core-helpers": "1.0.0-beta.36",
|
"web3-core-helpers": "1.0.0-beta.36"
|
||||||
"websocket": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2"
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"websocket": {
|
||||||
|
"version": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2",
|
||||||
|
"from": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2",
|
||||||
|
"requires": {
|
||||||
|
"debug": "^2.2.0",
|
||||||
|
"nan": "^2.3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"web3-shh": {
|
"web3-shh": {
|
||||||
|
@ -2425,16 +2431,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"websocket": {
|
|
||||||
"version": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2",
|
|
||||||
"from": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible",
|
|
||||||
"requires": {
|
|
||||||
"debug": "^2.2.0",
|
|
||||||
"nan": "^2.3.3",
|
|
||||||
"typedarray-to-buffer": "^3.1.2",
|
|
||||||
"yaeti": "^0.0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"wrappy": {
|
"wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
@ -2501,11 +2497,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
||||||
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
|
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
|
||||||
},
|
},
|
||||||
"yaeti": {
|
|
||||||
"version": "0.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
|
|
||||||
"integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc="
|
|
||||||
},
|
|
||||||
"yauzl": {
|
"yauzl": {
|
||||||
"version": "2.10.0",
|
"version": "2.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||||
|
|
|
@ -37,7 +37,9 @@
|
||||||
"homepage": "https://github.com/status-im/status-x#readme",
|
"homepage": "https://github.com/status-im/status-x#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"colors": "^1.3.2",
|
"colors": "^1.3.2",
|
||||||
|
"lodash.isequal": "^4.5.0",
|
||||||
"neo-blessed": "^0.2.0",
|
"neo-blessed": "^0.2.0",
|
||||||
|
"rxjs": "^6.3.3",
|
||||||
"status-js-api": "^1.0.5"
|
"status-js-api": "^1.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,75 +1,76 @@
|
||||||
const Events = require("events");
|
const Events = require("events");
|
||||||
import Users from "./users";
|
|
||||||
import colors from "colors";
|
import colors from "colors";
|
||||||
|
|
||||||
class ChannelManager {
|
class ChannelManager {
|
||||||
public channels: any[];
|
public channels: any[];
|
||||||
public events: any;
|
public events: any;
|
||||||
public allUsers: Users;
|
|
||||||
private currentChannel: number;
|
private currentChannel: number;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.channels = [];
|
this.channels = [];
|
||||||
this.events = new Events();
|
this.events = new Events();
|
||||||
this.currentChannel = 0;
|
this.currentChannel = 0;
|
||||||
this.allUsers = new Users();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public addChannel(channelName: string, type: string, extraData?: any) {
|
public addChannel(channelName: string, channelObject: any) {
|
||||||
if (this.getChannel(channelName)) {
|
const channel = {name: channelName, pendingMessages: [], channel: channelObject, users: [], typingUsers: []};
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel = {name: channelName, pendingMessages: [], type, ...extraData};
|
|
||||||
channel.users = new Users();
|
|
||||||
this.channels.push(channel);
|
this.channels.push(channel);
|
||||||
this.events.emit("update");
|
this.currentChannel = this.channels.length;
|
||||||
|
this.events.emit("channelSwitch");
|
||||||
|
this.events.emit("updateChannels", this.getChannelList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public getChannel(channelName: string) {
|
public getChannel(channelName: string) {
|
||||||
return this.channels.find((c) => c.name === channelName);
|
return this.channels.find((c) => c.name === channelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updateChannelUsers(channelName: string, users: any) {
|
||||||
|
this.getChannel(channelName).users = users;
|
||||||
|
this.events.emit("updateUsers", channelName, users);
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateChannelTypingUsers(channelName: string, typingUsers: any) {
|
||||||
|
this.getChannel(channelName).typingUsers = typingUsers;
|
||||||
|
this.events.emit("updateTypingUsers", channelName, typingUsers);
|
||||||
|
}
|
||||||
|
|
||||||
public getCurrentChannel() {
|
public getCurrentChannel() {
|
||||||
return this.channels[this.currentChannel];
|
return this.channels[this.currentChannel - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
public addMessage(channelName: string, message: string, pubkey: string, username: string) {
|
public addMessage(channelName: string, message: string, pubkey: string, username: string) {
|
||||||
const channel = this.getChannel(channelName);
|
const channel = this.getChannel(channelName);
|
||||||
if (channelName !== this.channels[this.currentChannel].name) {
|
if (channelName !== this.getCurrentChannel().name) {
|
||||||
channel.pendingMessages.push({pubkey, username, message});
|
channel.pendingMessages.push({pubkey, username, message});
|
||||||
} else {
|
} else {
|
||||||
this.events.emit("newMessage", channelName, username, message);
|
this.events.emit("newMessage", channelName, username, message);
|
||||||
}
|
}
|
||||||
const user = this.allUsers.addOrUpdateUserKey(pubkey, username);
|
|
||||||
channel.users.addUserOrUpdate(user);
|
|
||||||
|
|
||||||
this.events.emit("update");
|
this.events.emit("update");
|
||||||
}
|
}
|
||||||
|
|
||||||
public dumpPendingMessages() {
|
public dumpPendingMessages() {
|
||||||
const messages = this.channels[this.currentChannel].pendingMessages.slice(0);
|
const messages = this.getCurrentChannel().pendingMessages.slice(0);
|
||||||
this.channels[this.currentChannel].pendingMessages = [];
|
this.getCurrentChannel().pendingMessages = [];
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
public switchChannelIndex(index: number) {
|
public switchChannelIndex(index: number) {
|
||||||
if (index < 0) {
|
if (index <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (index >= this.channels.length) {
|
if (index > this.channels.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.currentChannel = index;
|
this.currentChannel = index;
|
||||||
this.events.emit("update");
|
|
||||||
this.events.emit("channelSwitch");
|
this.events.emit("channelSwitch");
|
||||||
|
this.events.emit("updateChannels", this.getChannelList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public getChannelList() {
|
public getChannelList() {
|
||||||
return this.channels.map((c) => {
|
return this.channels.map((c) => {
|
||||||
const prefix = c.type === "channel" ? "#" : "";
|
const prefix = c.type === "channel" ? "#" : "";
|
||||||
|
|
||||||
if (c.name === this.channels[this.currentChannel].name) {
|
if (c.name === this.getCurrentChannel().name) {
|
||||||
return colors.green(`${prefix}${c.name}`);
|
return colors.green(`${prefix}${c.name}`);
|
||||||
}
|
}
|
||||||
if (c.pendingMessages.length === 0) {
|
if (c.pendingMessages.length === 0) {
|
||||||
|
@ -78,15 +79,7 @@ class ChannelManager {
|
||||||
return `${prefix}${c.name} (${c.pendingMessages.length})`;
|
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;
|
export default ChannelManager;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import StatusJS from "status-js-api";
|
import StatusJS from "status-js-api";
|
||||||
import UI from "./ui";
|
import UI from "./ui";
|
||||||
import ChannelManager from "./channelManager";
|
import ChannelManager from "./status-js-client/channelManager";
|
||||||
|
|
||||||
|
import StatusJSClient from "./status-js-client";
|
||||||
|
|
||||||
const DEFAULT_CHANNEL = "mytest";
|
const DEFAULT_CHANNEL = "mytest";
|
||||||
const CONTACT_CODE_REGEXP = /^(0x)?[0-9a-f]{130}$/i;
|
const CONTACT_CODE_REGEXP = /^(0x)?[0-9a-f]{130}$/i;
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
import StatusJSClient from "./status-js-client/index";
|
||||||
|
import UI from "./ui";
|
||||||
|
import ChannelManager from "./channelManager";
|
||||||
|
|
||||||
|
const ui = new UI();
|
||||||
|
|
||||||
|
ui.logEntry(`
|
||||||
|
Welcome to
|
||||||
|
_________ __ __ ____ ___
|
||||||
|
/ _____// |______ _/ |_ __ __ _____\\ \\/ /
|
||||||
|
\\_____ \\\\ __\\__ \\\\ __\\ | \\/ ___/\\ /
|
||||||
|
/ \\| | / __ \\| | | | /\\___ \\ / \\
|
||||||
|
/_______ /|__| (____ /__| |____//____ >___/\\ \\
|
||||||
|
\\/ \\/ \\/ \\_/
|
||||||
|
`);
|
||||||
|
|
||||||
|
ui.logEntry(`Generating Identity....`);
|
||||||
|
ui.logEntry(`Connecting to Peers....`);
|
||||||
|
ui.logEntry(`Rejoining Channels....`);
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const status = new StatusJSClient();
|
||||||
|
await status.connectToNode("ws://localhost:8546")
|
||||||
|
const userPubKey = await status.getPublicKey();
|
||||||
|
|
||||||
|
ui.logEntry(`PK: ${userPubKey}`);
|
||||||
|
ui.logEntry(`-----------------------------------------------------------`);
|
||||||
|
|
||||||
|
const channelManager = new ChannelManager();
|
||||||
|
|
||||||
|
ui.events.on("cmd", async (cmd: string) => {
|
||||||
|
if (cmd.split(" ")[0] === "/join") {
|
||||||
|
const channelName = cmd.split(" ")[1].replace("#", "");
|
||||||
|
|
||||||
|
ui.logEntry("joining " + channelName);
|
||||||
|
|
||||||
|
if (channelManager.getChannel(channelName)) {
|
||||||
|
return ui.logEntry("you already joined this channel. you can switch channel with the /s <number> command");
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel = await status.joinChannel(channelName);
|
||||||
|
channelManager.addChannel(channelName, channel);
|
||||||
|
|
||||||
|
channel.messagesObserver.subscribe((msg) => {
|
||||||
|
channelManager.addMessage(channelName, msg.message, msg.pubkey, msg.username);
|
||||||
|
});
|
||||||
|
|
||||||
|
channel.usersObserver.subscribe((users) => {
|
||||||
|
channelManager.updateChannelUsers(channelName, users);
|
||||||
|
})
|
||||||
|
|
||||||
|
channel.usersTypingObserver.subscribe((typingUsers) => {
|
||||||
|
channelManager.updateChannelTypingUsers(channelName, typingUsers);
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.split(" ")[0] === "/s") {
|
||||||
|
const channelNumber = cmd.split(" ")[1];
|
||||||
|
channelManager.switchChannelIndex(parseInt(channelNumber, 10));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentChannel = channelManager.getCurrentChannel();
|
||||||
|
if (!currentChannel) {
|
||||||
|
ui.logEntry("not in any channel; try /join #mytest");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
channelManager.getCurrentChannel().channel.sendMessage(cmd);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.events.on("typing", (currentText: string) => {
|
||||||
|
if (currentText[0] === "/") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentChannel = channelManager.getCurrentChannel();
|
||||||
|
if (!currentChannel) return;
|
||||||
|
|
||||||
|
currentChannel.channel.typingEvent();
|
||||||
|
});
|
||||||
|
|
||||||
|
channelManager.events.on("newMessage", (channelName: string, username: string, message: string) => {
|
||||||
|
const msg = (username + ">").green + " " + message;
|
||||||
|
ui.logEntry(msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
channelManager.events.on("channelSwitch", () => {
|
||||||
|
const currentChannel = channelManager.getCurrentChannel();
|
||||||
|
|
||||||
|
ui.logEntry("-------------------");
|
||||||
|
ui.logEntry("now viewing #" + currentChannel.name);
|
||||||
|
|
||||||
|
channelManager.dumpPendingMessages().forEach((message: any) => {
|
||||||
|
const msg = (message.username + ">").green + " " + message.message;
|
||||||
|
ui.logEntry(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
channelManager.events.on("updateUsers", (channelName, users) => {
|
||||||
|
if (channelName !== channelManager.getCurrentChannel().name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.availableUsers(users.map((x: any) => {
|
||||||
|
return {name: x.username, status: (x.online ? "on" : "offline")};
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
channelManager.events.on("updateTypingUsers", (channelName, typingUsers) => {
|
||||||
|
if (channelName !== channelManager.getCurrentChannel().name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typingUsers.length === 0) {
|
||||||
|
return ui.setStatus("");
|
||||||
|
}
|
||||||
|
if (typingUsers.length === 1) {
|
||||||
|
return ui.setStatus(typingUsers[0] + " is typing");
|
||||||
|
}
|
||||||
|
return ui.setStatus(typingUsers.join(", ") + " are typing");
|
||||||
|
});
|
||||||
|
|
||||||
|
channelManager.events.on("updateChannels", ui.availableChannels.bind(this));
|
||||||
|
})();
|
|
@ -0,0 +1,151 @@
|
||||||
|
import { Observable, fromEvent, interval } from 'rxjs';
|
||||||
|
import { throttle, map, distinctUntilChanged } from 'rxjs/operators';
|
||||||
|
import Events from 'events';
|
||||||
|
import isEqual from 'lodash.isequal';
|
||||||
|
|
||||||
|
class Channel {
|
||||||
|
private channelName: string;
|
||||||
|
private status: any;
|
||||||
|
public messagesObserver: any;
|
||||||
|
public usersTypingObserver: any;
|
||||||
|
public usersObserver: any;
|
||||||
|
private typingObserver: any;
|
||||||
|
|
||||||
|
constructor(channelName: string, status: any) {
|
||||||
|
this.status = status;
|
||||||
|
this.channelName = channelName;
|
||||||
|
this.events = new Events();
|
||||||
|
this.usersTyping = {};
|
||||||
|
this.users = {};
|
||||||
|
this.typingObserver = fromEvent(this.events, 'typing');
|
||||||
|
this.usersTypingObserver = fromEvent(this.events, 'usersTyping').pipe(
|
||||||
|
throttle(val => interval(450)),
|
||||||
|
map(() => Object.values(this.usersTyping).map(x => x.username)),
|
||||||
|
distinctUntilChanged(isEqual),
|
||||||
|
);
|
||||||
|
this.usersObserver = fromEvent(this.events, 'users').pipe(
|
||||||
|
throttle(val => interval(1000)),
|
||||||
|
map(() => Object.values(this.users).map((x) => {
|
||||||
|
return {username: x.username, online: x.online}
|
||||||
|
})),
|
||||||
|
distinctUntilChanged(isEqual),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public joinChannel(cb) {
|
||||||
|
this.status.joinChat(this.channelName, () => {
|
||||||
|
this.pingChannel();
|
||||||
|
this.listenToMessages();
|
||||||
|
this.listenToTyping();
|
||||||
|
this.listenToUsers();
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private pingChannel() {
|
||||||
|
this.status.sendJsonMessage(this.channelName, {type: "ping"});
|
||||||
|
setInterval(() => {
|
||||||
|
this.status.sendJsonMessage(this.channelName, {type: "ping"});
|
||||||
|
}, 5 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private listenToMessages() {
|
||||||
|
this.messagesObserver = Observable.create((observer) => {
|
||||||
|
this.status.onMessage(this.channelName, (err: any, data: any) => {
|
||||||
|
if (err || !data) {
|
||||||
|
console.dir("---- error ")
|
||||||
|
console.dir(err)
|
||||||
|
console.dir(data)
|
||||||
|
|
||||||
|
return observer.error(err);
|
||||||
|
}
|
||||||
|
const msg = JSON.parse(data.payload)[1][0];
|
||||||
|
|
||||||
|
if (JSON.parse(data.payload)[1][1] === "content/json") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.usersTyping[data.data.sig] = {username: data.username, lastTyped: 0}; // user is likley no longer typing if a message was received
|
||||||
|
observer.next({message: msg, pubkey: data.data.sig, username: data.username});
|
||||||
|
this.events.emit('users')
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.status.onMessage(this.channelName, (err: any, data: any) => {
|
||||||
|
if (JSON.parse(data.payload)[1][1] !== "content/json") {
|
||||||
|
// usersTyping[data.data.sig] = 0; // user is likley no longer typing if a message was received
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = JSON.parse(JSON.parse(data.payload)[1][0]);
|
||||||
|
const fromUser = data.data.sig;
|
||||||
|
|
||||||
|
if (msg.type === "ping") {
|
||||||
|
this.users[fromUser] = {
|
||||||
|
username: data.username,
|
||||||
|
lastSeen: (new Date().getTime()),
|
||||||
|
online: true
|
||||||
|
}
|
||||||
|
this.events.emit('users')
|
||||||
|
|
||||||
|
// 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
|
||||||
|
//}
|
||||||
|
this.usersTyping[fromUser] = {username: data.username, lastTyped: (new Date().getTime()), online: true};
|
||||||
|
this.events.emit('usersTyping')
|
||||||
|
this.events.emit('users')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private listenToUsers() {
|
||||||
|
this.events.emit('users')
|
||||||
|
setInterval(() => {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.events.emit('users')
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public listenToTyping() {
|
||||||
|
this.typingObserver.pipe(throttle(val => interval(3000))).subscribe(() => {
|
||||||
|
this.status.sendJsonMessage(this.channelName, {type: "typing"});
|
||||||
|
})
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
const currentTime = (new Date().getTime());
|
||||||
|
for (const pubkey of Object.keys(this.usersTyping)) {
|
||||||
|
const lastTyped = this.usersTyping[pubkey].lastTyped;
|
||||||
|
|
||||||
|
if (currentTime - lastTyped > 3 * 1000 || currentTime < lastTyped) {
|
||||||
|
delete this.usersTyping[pubkey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.events.emit('usersTyping')
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
public typingEvent() {
|
||||||
|
this.events.emit('typing')
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendMessage(msg: string) {
|
||||||
|
this.status.sendMessage(this.channelName, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Channel;
|
|
@ -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;
|
|
@ -0,0 +1,57 @@
|
||||||
|
import StatusJS from "status-js-api";
|
||||||
|
import Channel from './channel';
|
||||||
|
|
||||||
|
interface ConfigOptions {
|
||||||
|
pingFrequency: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface WhisperProvider {
|
||||||
|
send: Function;
|
||||||
|
sendAasync: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatusJSClient {
|
||||||
|
private status: any;
|
||||||
|
private userPubKey: string;
|
||||||
|
private userName: string;
|
||||||
|
private channels: any;
|
||||||
|
|
||||||
|
constructor(options?: ConfigOptions) {
|
||||||
|
this.status = new StatusJS();
|
||||||
|
this.userPubKey = "";
|
||||||
|
this.userName = "";
|
||||||
|
this.channels = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async connectToNode(url: string) {
|
||||||
|
await this.status.connect(url);
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public connectToProvider(provider: WhisperProvider) {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async init() {
|
||||||
|
this.userPubKey = await this.status.getPublicKey();
|
||||||
|
this.userName = await this.status.getUserName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getPublicKey() {
|
||||||
|
const userPubKey = await this.status.getPublicKey();
|
||||||
|
return userPubKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public joinChannel(channelName: string) {
|
||||||
|
const channel = new Channel(channelName, this.status);
|
||||||
|
this.channels[channelName] = channel;
|
||||||
|
return new Promise((resolve: any, reject?: any) => {
|
||||||
|
channel.joinChannel(() => {
|
||||||
|
resolve(channel);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StatusJSClient;
|
|
@ -70,13 +70,12 @@ class UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
public availableChannels(channels: string[]) {
|
public availableChannels(channels: string[]) {
|
||||||
this.channels.setContent(channels.map((c, i) => `(${i}) ${c}`).join("\n"));
|
this.channels.setContent(channels.map((c, i) => `(${i+1}) ${c}`).join("\n"));
|
||||||
this.screen.render();
|
this.screen.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: to remove, might not be used anymore
|
|
||||||
private setStatus(status: string) {
|
private setStatus(status: string) {
|
||||||
this.operations.setContent(status);
|
this.consoleState.setContent(status);
|
||||||
this.screen.render();
|
this.screen.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -858,12 +858,6 @@ async-limiter@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
|
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
|
||||||
|
|
||||||
async@^2.6.1:
|
|
||||||
version "2.6.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
|
|
||||||
dependencies:
|
|
||||||
lodash "^4.17.10"
|
|
||||||
|
|
||||||
asynckit@^0.4.0:
|
asynckit@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
|
@ -2794,6 +2788,10 @@ lodash.debounce@^4.0.8:
|
||||||
version "4.0.8"
|
version "4.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||||
|
|
||||||
|
lodash.isequal@^4.5.0:
|
||||||
|
version "4.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||||
|
|
||||||
lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.5:
|
lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.5:
|
||||||
version "4.17.11"
|
version "4.17.11"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
|
||||||
|
@ -3686,7 +3684,7 @@ run-async@^2.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-promise "^2.1.0"
|
is-promise "^2.1.0"
|
||||||
|
|
||||||
rxjs@^6.1.0:
|
rxjs@^6.1.0, rxjs@^6.3.3:
|
||||||
version "6.3.3"
|
version "6.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz#3c6a7fa420e844a81390fb1158a9ec614f4bad55"
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz#3c6a7fa420e844a81390fb1158a9ec614f4bad55"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Reference in New Issue