Created
August 24, 2019 23:16
-
-
Save reidstidolph/a92860351df1c5e01b77f88b127aca9f to your computer and use it in GitHub Desktop.
Print events from 128T to your console. To install, copy the script to your system, make it executable, and run it. Script is fully self-contained, just needs to be run from a system with NodeJS installed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
/******************************************************************************* | |
* * | |
* Eventsource Module * | |
* * | |
*******************************************************************************/ | |
var parse = require('url').parse | |
var events = require('events') | |
var https = require('https') | |
var http = require('http') | |
var util = require('util') | |
var httpsOptions = [ | |
'pfx', 'key', 'passphrase', 'cert', 'ca', 'ciphers', | |
'rejectUnauthorized', 'secureProtocol', 'servername', 'checkServerIdentity' | |
] | |
var bom = [239, 187, 191] | |
var colon = 58 | |
var space = 32 | |
var lineFeed = 10 | |
var carriageReturn = 13 | |
function hasBom (buf) { | |
return bom.every(function (charCode, index) { | |
return buf[index] === charCode | |
}) | |
} | |
/** | |
* Creates a new EventSource object | |
* | |
* @param {String} url the URL to which to connect | |
* @param {Object} [eventSourceInitDict] extra init params. See README for details. | |
* @api public | |
**/ | |
function EventSource (url, eventSourceInitDict) { | |
var readyState = EventSource.CONNECTING | |
Object.defineProperty(this, 'readyState', { | |
get: function () { | |
return readyState | |
} | |
}) | |
Object.defineProperty(this, 'url', { | |
get: function () { | |
return url | |
} | |
}) | |
var self = this | |
self.reconnectInterval = 1000 | |
function onConnectionClosed (message) { | |
if (readyState === EventSource.CLOSED) return | |
readyState = EventSource.CONNECTING | |
_emit('error', new Event('error', {message: message})) | |
// The url may have been changed by a temporary | |
// redirect. If that's the case, revert it now. | |
if (reconnectUrl) { | |
url = reconnectUrl | |
reconnectUrl = null | |
} | |
setTimeout(function () { | |
if (readyState !== EventSource.CONNECTING) { | |
return | |
} | |
connect() | |
}, self.reconnectInterval) | |
} | |
var req | |
var lastEventId = '' | |
if (eventSourceInitDict && eventSourceInitDict.headers && eventSourceInitDict.headers['Last-Event-ID']) { | |
lastEventId = eventSourceInitDict.headers['Last-Event-ID'] | |
delete eventSourceInitDict.headers['Last-Event-ID'] | |
} | |
var discardTrailingNewline = false | |
var data = '' | |
var eventName = '' | |
var reconnectUrl = null | |
function connect () { | |
var options = parse(url) | |
var isSecure = options.protocol === 'https:' | |
options.headers = { 'Cache-Control': 'no-cache', 'Accept': 'text/event-stream' } | |
if (lastEventId) options.headers['Last-Event-ID'] = lastEventId | |
if (eventSourceInitDict && eventSourceInitDict.headers) { | |
for (var i in eventSourceInitDict.headers) { | |
var header = eventSourceInitDict.headers[i] | |
if (header) { | |
options.headers[i] = header | |
} | |
} | |
} | |
// Legacy: this should be specified as `eventSourceInitDict.https.rejectUnauthorized`, | |
// but for now exists as a backwards-compatibility layer | |
options.rejectUnauthorized = !(eventSourceInitDict && !eventSourceInitDict.rejectUnauthorized) | |
// If specify http proxy, make the request to sent to the proxy server, | |
// and include the original url in path and Host headers | |
var useProxy = eventSourceInitDict && eventSourceInitDict.proxy | |
if (useProxy) { | |
var proxy = parse(eventSourceInitDict.proxy) | |
isSecure = proxy.protocol === 'https:' | |
options.protocol = isSecure ? 'https:' : 'http:' | |
options.path = url | |
options.headers.Host = options.host | |
options.hostname = proxy.hostname | |
options.host = proxy.host | |
options.port = proxy.port | |
} | |
// If https options are specified, merge them into the request options | |
if (eventSourceInitDict && eventSourceInitDict.https) { | |
for (var optName in eventSourceInitDict.https) { | |
if (httpsOptions.indexOf(optName) === -1) { | |
continue | |
} | |
var option = eventSourceInitDict.https[optName] | |
if (option !== undefined) { | |
options[optName] = option | |
} | |
} | |
} | |
// Pass this on to the XHR | |
if (eventSourceInitDict && eventSourceInitDict.withCredentials !== undefined) { | |
options.withCredentials = eventSourceInitDict.withCredentials | |
} | |
req = (isSecure ? https : http).request(options, function (res) { | |
// Handle HTTP errors | |
if (res.statusCode === 500 || res.statusCode === 502 || res.statusCode === 503 || res.statusCode === 504) { | |
_emit('error', new Event('error', {status: res.statusCode, message: res.statusMessage})) | |
onConnectionClosed() | |
return | |
} | |
// Handle HTTP redirects | |
if (res.statusCode === 301 || res.statusCode === 307) { | |
if (!res.headers.location) { | |
// Server sent redirect response without Location header. | |
_emit('error', new Event('error', {status: res.statusCode, message: res.statusMessage})) | |
return | |
} | |
if (res.statusCode === 307) reconnectUrl = url | |
url = res.headers.location | |
process.nextTick(connect) | |
return | |
} | |
if (res.statusCode !== 200) { | |
_emit('error', new Event('error', {status: res.statusCode, message: res.statusMessage})) | |
return self.close() | |
} | |
readyState = EventSource.OPEN | |
res.on('close', function () { | |
res.removeAllListeners('close') | |
res.removeAllListeners('end') | |
onConnectionClosed() | |
}) | |
res.on('end', function () { | |
res.removeAllListeners('close') | |
res.removeAllListeners('end') | |
onConnectionClosed() | |
}) | |
_emit('open', new Event('open')) | |
// text/event-stream parser adapted from webkit's | |
// Source/WebCore/page/EventSource.cpp | |
var isFirst = true | |
var buf | |
res.on('data', function (chunk) { | |
buf = buf ? Buffer.concat([buf, chunk]) : chunk | |
if (isFirst && hasBom(buf)) { | |
buf = buf.slice(bom.length) | |
} | |
isFirst = false | |
var pos = 0 | |
var length = buf.length | |
while (pos < length) { | |
if (discardTrailingNewline) { | |
if (buf[pos] === lineFeed) { | |
++pos | |
} | |
discardTrailingNewline = false | |
} | |
var lineLength = -1 | |
var fieldLength = -1 | |
var c | |
for (var i = pos; lineLength < 0 && i < length; ++i) { | |
c = buf[i] | |
if (c === colon) { | |
if (fieldLength < 0) { | |
fieldLength = i - pos | |
} | |
} else if (c === carriageReturn) { | |
discardTrailingNewline = true | |
lineLength = i - pos | |
} else if (c === lineFeed) { | |
lineLength = i - pos | |
} | |
} | |
if (lineLength < 0) { | |
break | |
} | |
parseEventStreamLine(buf, pos, fieldLength, lineLength) | |
pos += lineLength + 1 | |
} | |
if (pos === length) { | |
buf = void 0 | |
} else if (pos > 0) { | |
buf = buf.slice(pos) | |
} | |
}) | |
}) | |
req.on('error', function (err) { | |
onConnectionClosed(err.message) | |
}) | |
if (req.setNoDelay) req.setNoDelay(true) | |
req.end() | |
} | |
connect() | |
function _emit () { | |
if (self.listeners(arguments[0]).length > 0) { | |
self.emit.apply(self, arguments) | |
} | |
} | |
this._close = function () { | |
if (readyState === EventSource.CLOSED) return | |
readyState = EventSource.CLOSED | |
if (req.abort) req.abort() | |
if (req.xhr && req.xhr.abort) req.xhr.abort() | |
} | |
function parseEventStreamLine (buf, pos, fieldLength, lineLength) { | |
if (lineLength === 0) { | |
if (data.length > 0) { | |
//let urlObj = new URL(url) | |
let originRegex = /^[\w-]+:\/{2,}\[?[\w\.:-]+\]?(?::[0-9]*)?/ | |
let origin = url.match(originRegex)[0] | |
var type = eventName || 'message' | |
_emit(type, new MessageEvent(type, { | |
data: data.slice(0, -1), // remove trailing newline | |
lastEventId: lastEventId, | |
origin: origin | |
})) | |
data = '' | |
} | |
eventName = void 0 | |
} else if (fieldLength > 0) { | |
var noValue = fieldLength < 0 | |
var step = 0 | |
var field = buf.slice(pos, pos + (noValue ? lineLength : fieldLength)).toString() | |
if (noValue) { | |
step = lineLength | |
} else if (buf[pos + fieldLength + 1] !== space) { | |
step = fieldLength + 1 | |
} else { | |
step = fieldLength + 2 | |
} | |
pos += step | |
var valueLength = lineLength - step | |
var value = buf.slice(pos, pos + valueLength).toString() | |
if (field === 'data') { | |
data += value + '\n' | |
} else if (field === 'event') { | |
eventName = value | |
} else if (field === 'id') { | |
lastEventId = value | |
} else if (field === 'retry') { | |
var retry = parseInt(value, 10) | |
if (!Number.isNaN(retry)) { | |
self.reconnectInterval = retry | |
} | |
} | |
} | |
} | |
} | |
module.exports = EventSource | |
util.inherits(EventSource, events.EventEmitter) | |
EventSource.prototype.constructor = EventSource; // make stacktraces readable | |
['open', 'error', 'message'].forEach(function (method) { | |
Object.defineProperty(EventSource.prototype, 'on' + method, { | |
/** | |
* Returns the current listener | |
* | |
* @return {Mixed} the set function or undefined | |
* @api private | |
*/ | |
get: function get () { | |
var listener = this.listeners(method)[0] | |
return listener ? (listener._listener ? listener._listener : listener) : undefined | |
}, | |
/** | |
* Start listening for events | |
* | |
* @param {Function} listener the listener | |
* @return {Mixed} the set function or undefined | |
* @api private | |
*/ | |
set: function set (listener) { | |
this.removeAllListeners(method) | |
this.addEventListener(method, listener) | |
} | |
}) | |
}) | |
/** | |
* Ready states | |
*/ | |
Object.defineProperty(EventSource, 'CONNECTING', {enumerable: true, value: 0}) | |
Object.defineProperty(EventSource, 'OPEN', {enumerable: true, value: 1}) | |
Object.defineProperty(EventSource, 'CLOSED', {enumerable: true, value: 2}) | |
EventSource.prototype.CONNECTING = 0 | |
EventSource.prototype.OPEN = 1 | |
EventSource.prototype.CLOSED = 2 | |
/** | |
* Closes the connection, if one is made, and sets the readyState attribute to 2 (closed) | |
* | |
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventSource/close | |
* @api public | |
*/ | |
EventSource.prototype.close = function () { | |
this._close() | |
} | |
/** | |
* Emulates the W3C Browser based WebSocket interface using addEventListener. | |
* | |
* @param {String} type A string representing the event type to listen out for | |
* @param {Function} listener callback | |
* @see https://developer.mozilla.org/en/DOM/element.addEventListener | |
* @see http://dev.w3.org/html5/websockets/#the-websocket-interface | |
* @api public | |
*/ | |
EventSource.prototype.addEventListener = function addEventListener (type, listener) { | |
if (typeof listener === 'function') { | |
// store a reference so we can return the original function again | |
listener._listener = listener | |
this.on(type, listener) | |
} | |
} | |
/** | |
* Emulates the W3C Browser based WebSocket interface using dispatchEvent. | |
* | |
* @param {Event} event An event to be dispatched | |
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent | |
* @api public | |
*/ | |
EventSource.prototype.dispatchEvent = function dispatchEvent (event) { | |
if (!event.type) { | |
throw new Error('UNSPECIFIED_EVENT_TYPE_ERR') | |
} | |
// if event is instance of an CustomEvent (or has 'details' property), | |
// send the detail object as the payload for the event | |
this.emit(event.type, event.detail) | |
} | |
/** | |
* Emulates the W3C Browser based WebSocket interface using removeEventListener. | |
* | |
* @param {String} type A string representing the event type to remove | |
* @param {Function} listener callback | |
* @see https://developer.mozilla.org/en/DOM/element.removeEventListener | |
* @see http://dev.w3.org/html5/websockets/#the-websocket-interface | |
* @api public | |
*/ | |
EventSource.prototype.removeEventListener = function removeEventListener (type, listener) { | |
if (typeof listener === 'function') { | |
listener._listener = undefined | |
this.removeListener(type, listener) | |
} | |
} | |
/** | |
* W3C Event | |
* | |
* @see http://www.w3.org/TR/DOM-Level-3-Events/#interface-Event | |
* @api private | |
*/ | |
function Event (type, optionalProperties) { | |
Object.defineProperty(this, 'type', { writable: false, value: type, enumerable: true }) | |
if (optionalProperties) { | |
for (var f in optionalProperties) { | |
if (optionalProperties.hasOwnProperty(f)) { | |
Object.defineProperty(this, f, { writable: false, value: optionalProperties[f], enumerable: true }) | |
} | |
} | |
} | |
} | |
/** | |
* W3C MessageEvent | |
* | |
* @see http://www.w3.org/TR/webmessaging/#event-definitions | |
* @api private | |
*/ | |
function MessageEvent (type, eventInitDict) { | |
Object.defineProperty(this, 'type', { writable: false, value: type, enumerable: true }) | |
for (var f in eventInitDict) { | |
if (eventInitDict.hasOwnProperty(f)) { | |
Object.defineProperty(this, f, { writable: false, value: eventInitDict[f], enumerable: true }) | |
} | |
} | |
} | |
/******************************************************************************* | |
* * | |
* End Eventsource * | |
* * | |
*******************************************************************************/ | |
/******************************************************************************* | |
* * | |
* Variables and Constants * | |
* * | |
*******************************************************************************/ | |
const readline = require('readline') | |
var t128Host = '127.0.0.1' | |
var t128User = 'admin' | |
var t128Password = null | |
var t128Token = null | |
var authToken = null | |
const rl = readline.createInterface({input: process.stdin,output: process.stdout}) | |
const ipRegex=/\b(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/ | |
const fqdnRegex=/\b(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)\b/ | |
/******************************************************************************* | |
* * | |
* Functions * | |
* * | |
*******************************************************************************/ | |
function fqdnIsValid(fqdn){ | |
return fqdnRegex.test(fqdn) | |
} | |
function ipIsValid(ipAddr){ | |
return ipRegex.test(ipAddr) | |
} | |
function fetchToken() { | |
return new Promise((resolve, reject) => { | |
let reqBody = JSON.stringify({'username': t128User,'password': t128Password}) | |
let reqOptions = { | |
host : t128Host, | |
path : '/api/v1/login', | |
method : 'POST', | |
rejectUnauthorized: false, | |
headers : { | |
'content-type':'application/json', | |
'content-length': Buffer.byteLength(reqBody, "utf8") | |
} | |
} | |
let restReq = https.request(reqOptions, (res) => { | |
// handle http errors | |
if (res.statusCode < 200 || res.statusCode > 299) { | |
reject(new Error(`${res.statusCode} ${res.statusMessage}`)) | |
} | |
// temporary data holder | |
let body = [] | |
// on every content chunk, push it to the data array | |
res.on('data', (chunk) => body.push(chunk)) | |
// we are done, resolve promise with those joined chunks | |
res.on('end', () => { | |
if (res.headers['content-type'].search(/application\/json/i) === 0) { | |
authToken = JSON.parse(body.join('')).token | |
resolve() | |
} else { | |
console.log(body.join('')) | |
resolve(body.join('')) | |
} | |
}) | |
}) | |
// handle connection errors of the request | |
restReq.on('error', (err) => { | |
reject(err) | |
}) | |
// write body to request if one is provided | |
restReq.write(reqBody) | |
restReq.end() | |
}) | |
} | |
function askForHost(){ | |
return new Promise((resolve, reject) => { | |
let askQuestion = function(){ | |
rl.question(`128T host (press ENTER for '${t128Host}'): `, (host) => { | |
if (host === '') { | |
resolve() | |
} else { | |
if (ipIsValid(host) || fqdnIsValid(host)) { | |
t128Host = host | |
resolve() | |
} else { | |
console.log('\nERROR: entry invalid, lease enter a valid IP or FQDN\n') | |
askQuestion() | |
} | |
} | |
}) | |
} | |
askQuestion() | |
}) | |
} | |
function askForUser(){ | |
return new Promise((resolve, reject) => { | |
rl.question(`username (press ENTER for '${t128User}'): `, (user) => { | |
if (user != '') {t128User = user} | |
resolve() | |
}) | |
}) | |
} | |
function askForPassword(){ | |
return new Promise((resolve, reject) => { | |
rl.question(`password for '${t128User}': `, (password) => { | |
t128Password = password | |
rl.close() | |
resolve() | |
}) | |
rl.stdoutMuted = true | |
rl._writeToOutput = function _writeToOutput(stringToWrite) { | |
if (rl.stdoutMuted && stringToWrite != '' && stringToWrite != '\r' && stringToWrite != '\n' && stringToWrite != '\r\n') | |
rl.output.write("*"); | |
else | |
rl.output.write(stringToWrite); | |
} | |
}) | |
} | |
function openEventStream(){ | |
return new Promise((resolve, reject) => { | |
var es = new EventSource(`https://${t128Host}/api/v1/events?token=${authToken}`, {https: {rejectUnauthorized: false}}); | |
es.onmessage = (event)=>{ | |
console.log(JSON.stringify(JSON.parse(event.data), null, 2)); | |
} | |
es.onerror = (err)=>{ | |
reject(err) | |
} | |
es.onopen = ()=>{ | |
console.log('eventstream opened, listening for events...') | |
resolve() | |
} | |
}) | |
} | |
/******************************************************************************* | |
* * | |
* Begin Script * | |
* * | |
*******************************************************************************/ | |
askForHost() | |
.then(()=>{ | |
return askForUser() | |
}) | |
.then(()=>{ | |
return askForPassword() | |
}) | |
.then(()=>{ | |
return fetchToken() | |
}) | |
.then(()=>{ | |
return openEventStream() | |
}) | |
.catch((err)=>{ | |
console.log(`Error: ${err.message}`) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment