Import nodejs chat
This commit is contained in:
parent
597c1514f5
commit
f6f5d9b01e
|
@ -0,0 +1,203 @@
|
|||
const protons = require('protons')
|
||||
|
||||
const { Request, Stats } = protons(`
|
||||
message Request {
|
||||
enum Type {
|
||||
SEND_MESSAGE = 0;
|
||||
UPDATE_PEER = 1;
|
||||
STATS = 2;
|
||||
}
|
||||
|
||||
required Type type = 1;
|
||||
optional SendMessage sendMessage = 2;
|
||||
optional UpdatePeer updatePeer = 3;
|
||||
optional Stats stats = 4;
|
||||
}
|
||||
|
||||
message SendMessage {
|
||||
required bytes data = 1;
|
||||
required int64 created = 2;
|
||||
required bytes id = 3;
|
||||
}
|
||||
|
||||
message UpdatePeer {
|
||||
optional bytes userHandle = 1;
|
||||
}
|
||||
|
||||
message Stats {
|
||||
enum NodeType {
|
||||
GO = 0;
|
||||
NODEJS = 1;
|
||||
BROWSER = 2;
|
||||
}
|
||||
|
||||
repeated bytes connectedPeers = 1;
|
||||
optional NodeType nodeType = 2;
|
||||
}
|
||||
`)
|
||||
|
||||
class Chat {
|
||||
/**
|
||||
*
|
||||
* @param {Libp2p} libp2p A Libp2p node to communicate through
|
||||
* @param {string} topic The topic to subscribe to
|
||||
* @param {function(Message)} messageHandler Called with every `Message` received on `topic`
|
||||
*/
|
||||
constructor(libp2p, topic, messageHandler) {
|
||||
this.libp2p = libp2p
|
||||
this.topic = topic
|
||||
this.messageHandler = messageHandler
|
||||
this.userHandles = new Map([
|
||||
[libp2p.peerId.toB58String(), 'Me']
|
||||
])
|
||||
|
||||
this.connectedPeers = new Set()
|
||||
this.libp2p.connectionManager.on('peer:connect', (connection) => {
|
||||
if (this.connectedPeers.has(connection.remotePeer.toB58String())) return
|
||||
this.connectedPeers.add(connection.remotePeer.toB58String())
|
||||
this.sendStats(Array.from(this.connectedPeers))
|
||||
})
|
||||
this.libp2p.connectionManager.on('peer:disconnect', (connection) => {
|
||||
if (this.connectedPeers.delete(connection.remotePeer.toB58String())) {
|
||||
this.sendStats(Array.from(this.connectedPeers))
|
||||
}
|
||||
})
|
||||
|
||||
// Join if libp2p is already on
|
||||
if (this.libp2p.isStarted()) this.join()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler that is run when `this.libp2p` starts
|
||||
*/
|
||||
onStart () {
|
||||
this.join()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler that is run when `this.libp2p` stops
|
||||
*/
|
||||
onStop () {
|
||||
this.leave()
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to `Chat.topic`. All messages will be
|
||||
* forwarded to `messageHandler`
|
||||
* @private
|
||||
*/
|
||||
join () {
|
||||
this.libp2p.pubsub.subscribe(this.topic, (message) => {
|
||||
try {
|
||||
const request = Request.decode(message.data)
|
||||
switch (request.type) {
|
||||
case Request.Type.UPDATE_PEER:
|
||||
const newHandle = request.updatePeer.userHandle.toString()
|
||||
console.info(`System: ${message.from} is now ${newHandle}.`)
|
||||
this.userHandles.set(message.from, newHandle)
|
||||
break
|
||||
case Request.Type.SEND_MESSAGE:
|
||||
this.messageHandler({
|
||||
from: message.from,
|
||||
message: request.sendMessage
|
||||
})
|
||||
break
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes from `Chat.topic`
|
||||
* @private
|
||||
*/
|
||||
leave () {
|
||||
this.libp2p.pubsub.unsubscribe(this.topic)
|
||||
}
|
||||
|
||||
/**
|
||||
* Crudely checks the input for a command. If no command is
|
||||
* found `false` is returned. If the input contains a command,
|
||||
* that command will be processed and `true` will be returned.
|
||||
* @param {Buffer|string} input Text submitted by the user
|
||||
* @returns {boolean} Whether or not there was a command
|
||||
*/
|
||||
checkCommand (input) {
|
||||
const str = input.toString()
|
||||
if (str.startsWith('/')) {
|
||||
const args = str.slice(1).split(' ')
|
||||
switch (args[0]) {
|
||||
case 'name':
|
||||
this.updatePeer(args[1])
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message over pubsub to update the user handle
|
||||
* to the provided `name`.
|
||||
* @param {Buffer|string} name Username to change to
|
||||
*/
|
||||
async updatePeer (name) {
|
||||
const msg = Request.encode({
|
||||
type: Request.Type.UPDATE_PEER,
|
||||
updatePeer: {
|
||||
userHandle: Buffer.from(name)
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
await this.libp2p.pubsub.publish(this.topic, msg)
|
||||
} catch (err) {
|
||||
console.error('Could not publish name change', err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the updated stats to the pubsub network
|
||||
* @param {Array<Buffer>} connectedPeers
|
||||
*/
|
||||
async sendStats (connectedPeers) {
|
||||
const msg = Request.encode({
|
||||
type: Request.Type.STATS,
|
||||
stats: {
|
||||
connectedPeers,
|
||||
nodeType: Stats.NodeType.NODEJS
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
await this.libp2p.pubsub.publish(this.topic, msg)
|
||||
} catch (err) {
|
||||
console.error('Could not publish stats update', err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes the given `message` to pubsub peers
|
||||
* @throws
|
||||
* @param {Buffer|string} message The chat message to send
|
||||
*/
|
||||
async send (message) {
|
||||
const msg = Request.encode({
|
||||
type: Request.Type.SEND_MESSAGE,
|
||||
sendMessage: {
|
||||
id: (~~(Math.random() * 1e9)).toString(36) + Date.now(),
|
||||
data: Buffer.from(message),
|
||||
created: Date.now()
|
||||
}
|
||||
})
|
||||
|
||||
await this.libp2p.pubsub.publish(this.topic, msg)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Chat
|
||||
module.exports.TOPIC = '/libp2p/example/chat/1.0.0'
|
||||
module.exports.CLEARLINE = '\033[1A'
|
|
@ -0,0 +1,94 @@
|
|||
'use strict'
|
||||
|
||||
// Libp2p Core
|
||||
const Libp2p = require('libp2p')
|
||||
// Transports
|
||||
const TCP = require('libp2p-tcp')
|
||||
const Websockets = require('libp2p-websockets')
|
||||
const WebrtcStar = require('libp2p-webrtc-star')
|
||||
const wrtc = require('wrtc')
|
||||
// Stream Muxer
|
||||
const Mplex = require('libp2p-mplex')
|
||||
// Connection Encryption
|
||||
const { NOISE } = require('libp2p-noise')
|
||||
const Secio = require('libp2p-secio')
|
||||
// Chat over Pubsub
|
||||
const PubsubChat = require('./chat')
|
||||
// Peer Discovery
|
||||
const Bootstrap = require('libp2p-bootstrap')
|
||||
const MDNS = require('libp2p-mdns')
|
||||
const KadDHT = require('libp2p-kad-dht')
|
||||
// PubSub implementation
|
||||
const Gossipsub = require('libp2p-gossipsub')
|
||||
|
||||
;(async () => {
|
||||
// Create the Node
|
||||
const libp2p = await Libp2p.create({
|
||||
addresses: {
|
||||
listen: [
|
||||
'/ip4/0.0.0.0/tcp/0',
|
||||
'/ip4/0.0.0.0/tcp/0/ws',
|
||||
`/ip4/127.0.0.1/tcp/15555/ws/p2p-webrtc-star/`
|
||||
]
|
||||
},
|
||||
modules: {
|
||||
transport: [ TCP, Websockets, WebrtcStar ],
|
||||
streamMuxer: [ Mplex ],
|
||||
connEncryption: [ NOISE, Secio ],
|
||||
peerDiscovery: [ Bootstrap, MDNS ],
|
||||
dht: KadDHT,
|
||||
pubsub: Gossipsub
|
||||
},
|
||||
config: {
|
||||
transport : {
|
||||
[WebrtcStar.prototype[Symbol.toStringTag]]: {
|
||||
wrtc
|
||||
}
|
||||
},
|
||||
peerDiscovery: {
|
||||
bootstrap: {
|
||||
list: [ '/ip4/127.0.0.1/tcp/63785/ipfs/QmWjz6xb8v9K4KnYEwP5Yk75k5mMBCehzWFLCvvQpYxF3d' ]
|
||||
}
|
||||
},
|
||||
dht: {
|
||||
enabled: true,
|
||||
randomWalk: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Listen on libp2p for `peer:connect` and log the provided connection.remotePeer.toB58String() peer id string.
|
||||
libp2p.connectionManager.on('peer:connect', (connection) => {
|
||||
console.info(`Connected to ${connection.remotePeer.toB58String()}!`)
|
||||
})
|
||||
|
||||
// Start libp2p
|
||||
await libp2p.start()
|
||||
|
||||
// Create our PubsubChat client
|
||||
const pubsubChat = new PubsubChat(libp2p, PubsubChat.TOPIC, ({ from, message }) => {
|
||||
let fromMe = from === libp2p.peerId.toB58String()
|
||||
let user = from.substring(0, 6)
|
||||
if (pubsubChat.userHandles.has(from)) {
|
||||
user = pubsubChat.userHandles.get(from)
|
||||
}
|
||||
console.info(`${fromMe ? PubsubChat.CLEARLINE : ''}${user}(${new Date(message.created).toLocaleTimeString()}): ${message.data}`)
|
||||
})
|
||||
|
||||
// Set up our input handler
|
||||
process.stdin.on('data', async (message) => {
|
||||
// Remove trailing newline
|
||||
message = message.slice(0, -1)
|
||||
// If there was a command, exit early
|
||||
if (pubsubChat.checkCommand(message)) return
|
||||
|
||||
try {
|
||||
// Publish the message
|
||||
await pubsubChat.send(message)
|
||||
} catch (err) {
|
||||
console.error('Could not publish chat', err)
|
||||
}
|
||||
})
|
||||
})()
|
Loading…
Reference in New Issue