@oada/oada-cache
Version:
node library for interacting with and locally caching data served on an oada-compliant server
206 lines (192 loc) • 6.18 kB
JavaScript
const Promise = require('bluebird')
const urlLib = require('url')
const uuid = require('uuid/v4')
const WebSocket = require('isomorphic-ws')
Promise.config({ warnings: false })
function websocket (url) {
//Create the message queue
var messages = []
//Create the socket
url = url.replace('https://', 'wss://').replace('http://', 'ws://')
var socket = new WebSocket(url)
var connected = false
var httpCallbacks = {}
var watchCallbacks = {}
function sendMessages () {
if (!connected) {
return
}
messages.forEach(message => {
socket.send(JSON.stringify(message))
})
messages = []
}
return new Promise((resolve, reject) => {
socket.onopen = function (event) {
connected = true
sendMessages()
resolve(socket)
}
socket.oncloconsose = function (event) {}
socket.onmessage = function (event) {
const response = JSON.parse(event.data)
if (Array.isArray(response.requestId)) {
// Handle muxed repsonses
Promise.map(response.requestId, (requestId, i) => {
// Create separate responses
return {
...response,
requestId,
path_leftover: response.path_leftover[i]
}
}).map(handleResponse)
} else {
handleResponse(response)
}
function handleResponse (response) {
//Look for id in httpCallbacks
if (response.requestId) {
if (httpCallbacks[response.requestId]) {
//Resolve Promise
if (response.status >= 200 && response.status < 300) {
httpCallbacks[response.requestId].resolve(response)
} else {
//Create error like axios
let err = new Error(
'Request failed with status code ' + response.status
)
err.request = httpCallbacks[response.requestId].request
err.response = {
status: response.status,
statusText: response.statusText,
headers: response.headers,
data: response.data
}
err.originalStack =
httpCallbacks[response.requestId].request.requestStack
httpCallbacks[response.requestId].reject(err)
}
delete httpCallbacks[response.requestId]
} else if (watchCallbacks[response.requestId]) {
if (watchCallbacks[response.requestId].resolve) {
if (response.status === 200) {
//Successfully setup websocket, resolve promise
watchCallbacks[response.requestId].resolve(response)
} else {
//error(watchCallbacks[response.requestId].request, response);
let err = new Error(
'Request failed with status code ' + response.status
)
err.response = response
err.request = watchCallbacks[response.requestId].request
watchCallbacks[response.requestId].reject(err)
}
//Remove resolve and reject so we process change as a signal next time
delete watchCallbacks[response.requestId]['resolve']
delete watchCallbacks[response.requestId]['reject']
} else {
if (watchCallbacks[response.requestId].callback == null) {
throw new Error(
'The given watch function has an undefined callback:',
watchCallbacks[response.requestId]
)
}
watchCallbacks[response.requestId].callback(response)
}
}
}
}
}
}).then(() => {
function _http (request) {
//Do a HTTP request
return new Promise((resolve, reject) => {
let urlObj = urlLib.parse(request.url)
let message = {
requestId: uuid(),
method: request.method.toLowerCase(),
path: urlObj.path,
data: request.data,
headers: Object.entries(request.headers)
.map(([key, value]) => {
return { [key.toLowerCase()]: value }
})
.reduce((a, b) => {
return { ...a, ...b }
})
}
messages.push(message)
httpCallbacks[message.requestId] = {
request: request,
resolve: resolve,
reject: reject
}
sendMessages()
})
}
function _unwatch (handler) {
let requestId
for (requestId in watchCallbacks) {
if (watchCallbacks[requestId] === handler) {
break
}
}
//Watch for changes on requested resource and trigger provided signal
return new Promise((resolve, reject) => {
const message = {
requestId,
method: 'unwatch'
}
delete watchCallbacks[message.requestId]
messages.push(message)
httpCallbacks[message.requestId] = {
resolve,
reject
}
sendMessages()
})
}
function _watch (request, callback) {
//Watch for changes on requested resource and trigger provided signal
return new Promise((resolve, reject) => {
let message = {
requestId: uuid(),
method: 'watch',
path: request.path,
headers: Object.entries(request.headers)
.map(([key, value]) => {
return { [key.toLowerCase()]: value }
})
.reduce((a, b) => {
return { ...a, ...b }
})
}
messages.push(message)
watchCallbacks[message.requestId] = {
request,
resolve,
reject,
callback
}
sendMessages()
})
}
function _close () {
//TODO reject all callbacks that have not resolved
//Clear everything
messages = []
httpCallbacks = {}
watchCallbacks = {}
//Close socket
socket.close()
}
return {
url,
http: _http,
close: _close,
watch: _watch,
unwatch: _unwatch
}
})
}
module.exports = websocket