swtc-lib
Version:
websocket access for jingtum blockchain
239 lines (221 loc) • 5.56 kB
text/typescript
import { EventEmitter } from "events"
import url from "url"
import WS from "ws"
/**
*
* @param remote
* @param opts
* @constructor
*/
class Server extends EventEmitter {
public static domainRE = /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|[-_]){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|[-_]){0,61}[0-9A-Za-z])?)*\.?$/
public static onlineStates = [
"syncing",
"tracking",
"proposing",
"validating",
"full",
"connected"
]
public opts: any
public opts_host: any
public port: any
private _opts
private _url
private _remote
private _ws
private _connected
private _opened
private _state
private _id
private _timer
constructor(remote, opts) {
super()
this.setMaxListeners(0)
if (typeof opts === "string") {
const parsed = url.parse(opts)
const secure = parsed.protocol === "wss:"
opts = {
host: parsed.hostname,
port: parsed.port ? Number(parsed.port) : secure ? 443 : 80,
secure,
path: parsed.path
}
}
if (opts === null || typeof opts !== "object") {
this.opts = new TypeError("server options not supplied")
return this
}
if (!Server.domainRE.test(opts.host)) {
this.opts_host = new TypeError("server host incorrect")
return this
}
if (Number.isNaN(opts.port) || !Number.isFinite(opts.port)) {
this.port = new TypeError("server port not a number")
return this
}
if (opts.port < 1 || opts.port > 65535) {
this.port = new TypeError("server port out of range")
return this
}
if (typeof opts.secure !== "boolean") {
opts.secure = false
}
this._opts = opts
this._url =
(this._opts.secure ? "wss://" : "ws://") +
this._opts.host +
":" +
this._opts.port +
(this._opts.path ? this._opts.path : "")
this._remote = remote
this._ws = null
this._connected = false
this._opened = false
this._state = "offline"
this._id = 0
this._timer = 0
}
public connect(callback) {
if (this._connected) return
if (this._ws) this._ws.close()
try {
this._ws = new WS(this._url)
if ("open" in this._ws) {
this._ws.open()
}
} catch (e) {
return callback(e)
}
this._ws.on("open", () => {
this._opened = true
const req = this._remote.subscribe(["ledger", "server", "transactions"])
req.submit(callback)
})
if ("open" in this._ws) {
this._ws.on("message", (socket, data) => {
this._remote._handleMessage(data)
socket === null
})
} else {
this._ws.on("message", data => {
this._remote._handleMessage(data)
})
}
this._ws.on("close", () => {
this._handleClose()
})
if ("open" in this._ws) {
this._ws.on("error", (socket, err) => {
callback(err)
socket === null
})
} else {
this._ws.on("error", err => callback(err))
}
}
public async connectPromise() {
return new Promise((resolve, reject) => {
if (this._connected) {
resolve(`this._server is connected already`)
}
if (this._ws) this._ws.close()
try {
this._ws = new WS(this._url)
if ("open" in this._ws) {
this._ws.open()
}
} catch (e) {
reject(e)
}
this._ws.on("open", () => {
this._opened = true
const req = this._remote.subscribe(["ledger", "server", "transactions"])
req
.submitPromise()
.then(result => resolve(result))
.catch(error => reject(error))
})
if ("open" in this._ws) {
this._ws.on("message", (socket, data) => {
this._remote._handleMessage(data)
socket === null
})
} else {
this._ws.on("message", data => {
this._remote._handleMessage(data)
})
}
this._ws.on("close", () => {
this._handleClose()
})
if ("open" in this._ws) {
this._ws.on("error", (socket, err) => {
reject(err)
socket === null
})
} else {
this._ws.on("error", err => reject(err))
}
})
}
/**
* close manual, not close connection until new connection
*/
public disconnect() {
this._ws.close()
this._ws = null
this._setState("offline")
}
public isConnected() {
return this._connected
}
/**
* refuse to send msg if connection blows out
* @param message
*/
public sendMessage(command, data) {
if (!this._opened) return
const req_id = this._id++
const msg = Object.assign(
{},
{
id: req_id,
command
},
data
)
this._ws.send(JSON.stringify(msg))
return req_id
}
public _setState(state) {
if (state === this._state) return
this._state = state
this._connected = state === "online"
if (!this._connected) {
this._opened = false
}
}
/**
* handle close and error exception
* and should re-connect server after 3 seconds
*/
private _handleClose() {
if (this._state === "offline") return
this._setState("offline")
if (this._timer !== 0) return
this._remote.emit("disconnect")
this._timer = setInterval(() => {
this.connect((err, ret) => {
if (err) {
ret === 0
} else {
clearInterval(this._timer)
this._timer = 0
this._remote.emit("reconnect")
}
})
}, 3000)
}
}
export { Server }