teletype-client-modified
Version:
I have modified the library developed by Atom to make it compatible with non-browser facility. I am using it only for experimentation purposes. I will remove this package post my experiment and raise a PR at Atom to finally merge this package so that this
353 lines (298 loc) • 11.7 kB
JavaScript
const assert = require('assert')
const {CompositeDisposable, Emitter} = require('event-kit')
const {NetworkMessage, PeerIdentity} = require('./teletype-client_pb')
const Errors = require('./errors')
const convertToProtobufCompatibleBuffer = require('./convert-to-protobuf-compatible-buffer')
module.exports =
class StarOverlayNetwork {
constructor ({id, peerPool, isHub, connectionTimeout}) {
this.id = id
this.peerPool = peerPool
this.isHub = isHub
this.connectionTimeout = connectionTimeout || 5000
this.emitter = new Emitter()
this.memberIdentitiesById = new Map([
[this.getPeerId(), this.peerPool.getPeerIdentity(this.getPeerId())]
])
this.connectedMemberIds = new Set()
this.resetConnectedMembers()
if (this.isHub) {
this.spokes = new Set()
this.state = 'connected'
} else {
this.state = 'disconnected'
}
this.subscriptions = new CompositeDisposable(
peerPool.onDisconnection(this.didLoseConnectionToPeer.bind(this)),
peerPool.onReceive(this.receive.bind(this))
)
}
dispose () {
this.subscriptions.dispose()
this.disconnect()
}
async connectTo (hubId) {
assert(!this.isHub, 'The hub should only receive connections')
assert(!this.hubId, 'Can connect to hub only once')
this.state = 'connecting'
this.hubId = hubId
await this.peerPool.connectTo(this.hubId)
try {
const starJoinRequest = new NetworkMessage.StarJoinRequest()
starJoinRequest.setSenderId(this.getPeerId())
const networkMessage = new NetworkMessage()
networkMessage.setStarJoinRequest(starJoinRequest)
networkMessage.setNetworkId(this.id)
this.send(this.hubId, networkMessage.serializeBinary())
} catch (error) {
this.state = 'disconnected'
this.hubId = null
throw error
}
const timeoutError = new Errors.NetworkConnectionError('Connecting to the portal network timed out')
return new Promise((resolve, reject) => {
this.resolveConnectionPromise = resolve
setTimeout(() => {
if (this.state === 'connecting') {
this.state = 'timeout'
this.disconnect()
reject(timeoutError)
}
}, this.connectionTimeout)
})
}
disconnect () {
if (this.state === 'disconnected') return
const leaveNotificationMessage = new NetworkMessage.StarLeaveNotification()
leaveNotificationMessage.setMemberId(this.getPeerId())
leaveNotificationMessage.setConnectionLost(false)
const networkMessage = new NetworkMessage()
networkMessage.setStarLeaveNotification(leaveNotificationMessage)
networkMessage.setNetworkId(this.id)
if (this.isHub) {
this.forwardBroadcast(this.getPeerId(), networkMessage.serializeBinary())
this.spokes.clear()
} else {
this.send(this.hubId, networkMessage.serializeBinary())
this.hubId = null
}
this.resetConnectedMembers()
this.state = 'disconnected'
}
unicast (recipientId, message) {
message = convertToProtobufCompatibleBuffer(message)
const starUnicast = new NetworkMessage.StarUnicast()
starUnicast.setSenderId(this.peerPool.peerId)
starUnicast.setRecipientId(recipientId)
starUnicast.setBody(message)
const networkMessage = new NetworkMessage()
networkMessage.setStarUnicast(starUnicast)
networkMessage.setNetworkId(this.id)
const rawNetworkMessage = networkMessage.serializeBinary()
if (this.isHub) {
this.forwardUnicast(recipientId, rawNetworkMessage)
} else {
this.send(this.hubId, rawNetworkMessage)
}
}
broadcast (message) {
message = convertToProtobufCompatibleBuffer(message)
const starBroadcast = new NetworkMessage.StarBroadcast()
starBroadcast.setSenderId(this.peerPool.peerId)
starBroadcast.setBody(message)
const networkMessage = new NetworkMessage()
networkMessage.setStarBroadcast(starBroadcast)
networkMessage.setNetworkId(this.id)
const rawNetworkMessage = networkMessage.serializeBinary()
if (this.isHub) {
this.forwardBroadcast(this.peerPool.peerId, rawNetworkMessage)
} else {
this.send(this.hubId, rawNetworkMessage)
}
}
onMemberJoin (callback) {
return this.emitter.on('join', callback)
}
onMemberLeave (callback) {
return this.emitter.on('leave', callback)
}
onReceive (callback) {
return this.emitter.on('receive', callback)
}
getMemberIds () {
return Array.from(this.connectedMemberIds)
}
getMemberIdentity (peerId) {
if (!this.memberIdentitiesById.has(peerId)) {
const originalStackTrace = new Error().stack
const metadata = {
localPeerId: this.getPeerId(),
requestedPeerId: peerId,
isHub: this.isHub,
networkState: this.state,
connectedMemberIds: Array.from(this.connectedMemberIds),
memberIdsWithIdentity: Array.from(this.memberIdentitiesById.keys()),
}
process.nextTick(() => {
const error = new Error('Attempted to get identity for non-connected member')
error.metadata = metadata
error.privateMetadata = { originalStackTrace }
error.privateMetadataDescription = originalStackTrace
throw error
})
}
return this.memberIdentitiesById.get(peerId)
}
getPeerId () {
return this.peerPool.peerId
}
didLoseConnectionToPeer ({peerId}) {
if (!this.connectedMemberIds.has(peerId)) return
if (this.isHub) {
this.spokes.delete(peerId)
const leaveNotificationMessage = new NetworkMessage.StarLeaveNotification()
leaveNotificationMessage.setMemberId(peerId)
leaveNotificationMessage.setConnectionLost(true)
const networkMessage = new NetworkMessage()
networkMessage.setStarLeaveNotification(leaveNotificationMessage)
networkMessage.setNetworkId(this.id)
this.forwardBroadcast(peerId, networkMessage.serializeBinary())
}
this.memberDidLeave(peerId, true)
}
receive ({senderId, message}) {
if (this.state === 'disconnected') return
const networkMessage = NetworkMessage.deserializeBinary(message)
if (networkMessage.getNetworkId() !== this.id) return
if (networkMessage.hasStarJoinRequest()) {
this.receiveJoinRequest(message, networkMessage.getStarJoinRequest())
} else if (networkMessage.hasStarJoinResponse()) {
this.receiveJoinResponse(networkMessage.getStarJoinResponse())
} else if (networkMessage.hasStarJoinNotification()) {
this.receiveJoinNotification(networkMessage.getStarJoinNotification())
} else if (networkMessage.hasStarLeaveNotification()) {
this.receiveLeaveNotification(message, networkMessage.getStarLeaveNotification())
} else if (networkMessage.hasStarUnicast()) {
this.receiveUnicast(message, networkMessage.getStarUnicast())
} else if (networkMessage.hasStarBroadcast()) {
this.receiveBroadcast(message, networkMessage.getStarBroadcast())
}
}
receiveJoinRequest (rawMessage, connectionMessage) {
assert(this.isHub, 'Join requests should only be sent to the hub')
const senderId = connectionMessage.getSenderId()
const senderIdentity = this.peerPool.getPeerIdentity(senderId)
this.state = 'connected'
this.spokes.add(senderId)
this.memberIdentitiesById.set(senderId, senderIdentity)
this.connectedMemberIds.add(senderId)
// Respond to new member
const joinResponseMessage = new NetworkMessage.StarJoinResponse()
const memberIdentitiesByIdMessage = joinResponseMessage.getMemberIdentitiesByIdMap()
this.connectedMemberIds.forEach((peerId) => {
const identity = this.getMemberIdentity(peerId)
memberIdentitiesByIdMessage.set(peerId, serializePeerIdentity(identity))
})
const responseNetworkMessage = new NetworkMessage()
responseNetworkMessage.setNetworkId(this.id)
responseNetworkMessage.setStarJoinResponse(joinResponseMessage)
this.send(senderId, responseNetworkMessage.serializeBinary())
// Notify other spokes of new member
const joinNotificationMessage = new NetworkMessage.StarJoinNotification()
joinNotificationMessage.setMemberId(senderId)
joinNotificationMessage.setMemberIdentity(serializePeerIdentity(senderIdentity))
const notificationNetworkMessage = new NetworkMessage()
notificationNetworkMessage.setNetworkId(this.id)
notificationNetworkMessage.setStarJoinNotification(joinNotificationMessage)
this.forwardBroadcast(senderId, notificationNetworkMessage.serializeBinary())
this.emitter.emit('join', {peerId: senderId})
}
receiveJoinResponse (joinResponseMessage) {
assert(!this.isHub, 'Connection responses cannot be sent to the hub')
joinResponseMessage.getMemberIdentitiesByIdMap().forEach((identityMessage, peerId) => {
this.memberIdentitiesById.set(peerId, deserializePeerIdentity(identityMessage))
this.connectedMemberIds.add(peerId)
})
this.state = 'connected'
this.resolveConnectionPromise()
this.resolveConnectionPromise = null
}
receiveJoinNotification (joinNotificationMessage) {
const memberId = joinNotificationMessage.getMemberId()
const memberIdentity = deserializePeerIdentity(joinNotificationMessage.getMemberIdentity())
this.memberIdentitiesById.set(memberId, memberIdentity)
this.connectedMemberIds.add(memberId)
this.emitter.emit('join', {peerId: memberId})
}
receiveLeaveNotification (rawMessage, leaveNotification) {
const memberId = leaveNotification.getMemberId()
if (!this.connectedMemberIds.has(memberId)) return
if (this.isHub) {
this.spokes.delete(memberId)
this.forwardBroadcast(memberId, rawMessage)
}
this.memberDidLeave(memberId, leaveNotification.getConnectionLost())
}
memberDidLeave (peerId, connectionLost) {
if (peerId === this.hubId) {
this.hubId = null
this.state = 'disconnected'
this.resetConnectedMembers()
} else {
this.connectedMemberIds.delete(peerId)
}
this.emitter.emit('leave', {peerId, connectionLost})
}
receiveUnicast (rawMessage, unicastMessage) {
const recipientId = unicastMessage.getRecipientId()
if (recipientId === this.peerPool.peerId) {
this.emitter.emit('receive', {
senderId: unicastMessage.getSenderId(),
message: convertToProtobufCompatibleBuffer(unicastMessage.getBody())
})
} else if (this.isHub) {
this.forwardUnicast(recipientId, rawMessage)
} else {
throw new Error('Received a unicast not intended for this peer')
}
}
receiveBroadcast (rawMessage, broadcastMessage) {
const senderId = broadcastMessage.getSenderId()
if (this.isHub) this.forwardBroadcast(senderId, rawMessage)
this.emitter.emit('receive', {
senderId,
message: convertToProtobufCompatibleBuffer(broadcastMessage.getBody())
})
}
forwardUnicast (recipientId, rawMessage) {
if (this.spokes.has(recipientId)) {
this.send(recipientId, rawMessage)
}
}
forwardBroadcast (senderId, rawMessage) {
this.spokes.forEach((peerId) => {
if (peerId !== senderId) {
this.send(peerId, rawMessage)
}
})
}
send (peerId, message) {
if (this.peerPool.isConnectedToPeer(peerId) ) {
this.peerPool.send(peerId, message)
}
}
resetConnectedMembers () {
this.connectedMemberIds.clear()
this.connectedMemberIds.add(this.getPeerId())
}
}
function serializePeerIdentity (identity) {
const identityMessage = new PeerIdentity()
identityMessage.setLogin(identity.login)
return identityMessage
}
function deserializePeerIdentity (identityMessage) {
return {
login: identityMessage.getLogin()
}
}