diff --git a/src/index.js b/src/index.js index 9649ef9..513fa12 100644 --- a/src/index.js +++ b/src/index.js @@ -6,13 +6,19 @@ const POW_TIME = 1; const TTL = 10; const POW_TARGET = 0.002; -function createStatusPayload(msg, isJson) { - let tag = '~#c4'; - let content = msg; - let messageType = '~:public-group-user-message'; - let clockValue = (new Date().getTime()) * 100; - let contentType = (isJson ? 'content/json' : 'text/plain'); - let timestamp = new Date().getTime(); +const GROUP_MESSAGE = "~:public-group-user-message"; +const USER_MESSAGE = "~:user-message"; + +const CONTACT_DISCOVERY_TOPIC = '0xf8946aac'; + +const CONTACT_CODE_REGEXP = /^(0x)?[0-9a-f]{130}$/i; + +function createStatusPayload(content, messageType, isJson) { + const tag = '~#c4'; + const clockValue = (new Date().getTime()) * 100; + const contentType = (isJson ? 'content/json' : 'text/plain'); + const timestamp = new Date().getTime(); + return asciiToHex( JSON.stringify([ tag, @@ -25,6 +31,8 @@ class StatusJS { constructor() { this.channels = {}; + this.contacts = {}; + this.userMessagesSubscription = null; } async connect(url) { @@ -35,6 +43,16 @@ class StatusJS { this.sig = await web3.shh.newKeyPair(); } + async getPublicKey(){ + const pubKey = await this.shh.getPublicKey(this.sig); + return pubKey; + } + + async getUserName(){ + const pubKey = await this.getPublicKey(); + return utils.generateUsernameFromSeed(pubKey); + } + async joinChat(channelName, cb) { let channelKey = await this.shh.generateSymKeyFromPassword(channelName); this.channels[channelName] = { @@ -45,19 +63,39 @@ class StatusJS { if (cb) cb(); } + async addContact(contactCode, cb) { + this.contacts[contactCode] = { + 'username': utils.generateUsernameFromSeed(contactCode) + } + if (cb) cb(); + } + leaveChat(channelName) { - this.channels[channelName].unsubscribe(); + this.channels[channelName].subscription.unsubscribe(); delete this.channels[channelName]; } + async removeContact(contactCode, cb) { + delete this.contacts[contactCode]; + } + isSubscribedTo(channelName) { return !!this.channels[channelName]; } - onMessage(channelName, cb) { + onMessage(par1, par2) { + if(typeof par1 === "function"){ + this.onUserMessage(par1); + } else { + this.onChannelMessage(par1, par2); + } + } + + onChannelMessage(channelName, cb) { if (!this.channels[channelName]) { return cb("unknown channel: " + channelName); } + this.channels[channelName].subscription = this.shh.subscribe("messages", { minPow: POW_TARGET, symKeyID: this.channels[channelName].channelKey, @@ -70,16 +108,51 @@ class StatusJS { }); } - sendMessage(channelName, msg, cb) { + onUserMessage(cb) { + this.userMessagesSubscription = this.shh.subscribe("messages", { + minPow: POW_TARGET, + privateKeyID: this.sig, + topics: [CONTACT_DISCOVERY_TOPIC] + }).on('data', (data) => { + let username = utils.generateUsernameFromSeed(data.sig); + if(!this.contacts[data.sig]) this.contacts[data.sig] = {}; + this.contacts[data.sig].username = username; + cb(null, {payload: hexToAscii(data.payload), data: data, username: username}); + }).on('error', (err) => { + cb(err); + }); + } + + sendUserMessage(contactCode, msg, cb) { + this.shh.post({ + pubKey: contactCode, + sig: this.sig, + ttl: TTL, + topic: CONTACT_DISCOVERY_TOPIC, + payload: createStatusPayload(msg, USER_MESSAGE), + powTime: POW_TIME, + powTarget: POW_TARGET + }).then(() => { + if (!cb) return; + cb(null, true); + }).catch((e) => { + if (!cb) return; + cb(e, false); + }); + } + + sendGroupMessage(channelName, msg, cb) { if (!this.channels[channelName]) { + if(!cb) return; return cb("unknown channel: " + channelName); } + this.shh.post({ symKeyID: this.channels[channelName].channelKey, sig: this.sig, ttl: TTL, topic: this.channels[channelName].channelCode, - payload: createStatusPayload(msg), + payload: createStatusPayload(msg, GROUP_MESSAGE), powTime: POW_TIME, powTarget: POW_TARGET }).then(() => { @@ -100,7 +173,7 @@ class StatusJS { sig: this.sig, ttl: TTL, topic: this.channels[channelName].channelCode, - payload: createStatusPayload(JSON.stringify(msg), true), + payload: createStatusPayload(JSON.stringify(msg), GROUP_MESSAGE, true), powTime: POW_TIME, powTarget: POW_TARGET }).then(() => { @@ -112,6 +185,14 @@ class StatusJS { }); } + sendMessage(destination, msg, cb){ + if (CONTACT_CODE_REGEXP.test(destination)) { + this.sendUserMessage(destination, msg, cb); + } else { + this.sendGroupMessage(destination, msg, cb); + } + } + } module.exports = StatusJS; diff --git a/test.js b/test.js index 2be28ac..47c1f80 100644 --- a/test.js +++ b/test.js @@ -2,7 +2,7 @@ var StatusJS = require('./src/index.js'); (async () => { var status = new StatusJS(); - status.connect("ws://localhost:8546"); + await status.connect("ws://localhost:8546"); await status.joinChat("mytest"); await status.joinChat("mytest2"); diff --git a/test2.js b/test2.js new file mode 100644 index 0000000..2085c50 --- /dev/null +++ b/test2.js @@ -0,0 +1,41 @@ +const StatusJS = require('./src/index.js'); +const Web3 = require('web3'); +const { utils: { asciiToHex, hexToAscii } } = Web3; + +(async () => { + let status1 = new StatusJS(); + await status1.connect("ws://localhost:8546"); + + let status2 = new StatusJS(); + await status2.connect("ws://localhost:8546"); + + + const user1pubKey = await status1.getPublicKey(); + const user2pubKey = await status2.getPublicKey(); + + console.log("user1 (" + await status1.getUserName() + "):\n" + user1pubKey); + console.log("user2 (" + await status2.getUserName() + "):\n" + user2pubKey); + console.log("\n") + + + const receivedMessageCb = (username) => (err, data) => { + console.log( username + " received a message from " + data.username); + console.log(data.data.sig); + console.log(data.payload) + }; + + + status1.onMessage(receivedMessageCb('user1')); + status2.onMessage(receivedMessageCb('user2')); + + status1.addContact(user2pubKey); + status1.sendMessage(user2pubKey, "hello user2!"); + + status2.sendMessage(user1pubKey, "hello user1!"); + + + // Text someone at status + //status1.sendMessage("0xcontact_code_here", "hello!"); + + +})()