Comparing version 0.1.3 to 0.2.0
{ | ||
"name": "koa-ws", | ||
"version": "0.1.3", | ||
"version": "0.2.0", | ||
"keywords": [ | ||
@@ -5,0 +5,0 @@ "koa", |
@@ -43,3 +43,3 @@ [![Build Status](https://secure.travis-ci.org/mekwall/koa-ws.png)](http://travis-ci.org/mekwall/koa-ws) [![Coverage Status](https://img.shields.io/coveralls/mekwall/koa-ws.svg)](https://coveralls.io/r/mekwall/koa-ws) [![Dependency Status](https://david-dm.org/mekwall/koa-ws.png)](https://david-dm.org/mekwall/koa-ws) | ||
Make sure the client library is loaded in the browser. The path is `/koaws.js` per default but can easily be changed. | ||
Load the client library in the browser with require (`var koaws = require('koa-ws/client')`) or use the hosted version at `/koaws.js`. | ||
@@ -46,0 +46,0 @@ Call the method from the client: |
@@ -5,7 +5,15 @@ (function(){ | ||
var debug, hostname, port; | ||
// Utils | ||
var util = require('util'); | ||
// EventEmitter | ||
var EventEmitter = require('events').EventEmitter; | ||
// Request object | ||
var Request = require('./request'); | ||
// Debug output | ||
var debug; | ||
try { | ||
debug = require('debug')('koa:ws'); | ||
debug = require('debug')('koa-ws'); | ||
} catch (e) { | ||
@@ -15,16 +23,2 @@ debug = console.log.bind(console); | ||
if (typeof WS_HOSTNAME !== 'undefined') { | ||
hostname = WS_HOSTNAME; | ||
} else if (typeof location !== 'undefined') { | ||
hostname = location.hostname; | ||
} else { | ||
hostname = 'localhost'; | ||
} | ||
if (typeof WS_HOSTNAME !== 'undefined') { | ||
port = WS_PORT; | ||
} else { | ||
port = 3000; | ||
} | ||
if (typeof WebSocket === 'undefined') { | ||
@@ -34,67 +28,128 @@ var WebSocket = require('ws'); | ||
// Initialize WebSocket client | ||
debug('Connecting to server: ws://%s:%s', hostname, port); | ||
var client = new WebSocket('ws://' + hostname + ':' + port); | ||
var scriptElements = document.getElementsByTagName('script'); | ||
var guessedAddress = typeof __resourceQuery === "string" && __resourceQuery ? | ||
__resourceQuery.substr(1) : | ||
scriptElements[scriptElements.length-1].getAttribute("src").replace(/\/[^\/]+$/, ""); | ||
// Queue list for messages | ||
var messageQueue = []; | ||
function Client() { | ||
// Init EventEmitter | ||
EventEmitter.call(this); | ||
// Callback container for results | ||
var awaitingResults = {}; | ||
// Queue list for messages | ||
this._messageQueue = []; | ||
// Simple support for events | ||
var callbacks = client._events = { | ||
open: [], | ||
close: [], | ||
connection: [], | ||
message: [] | ||
// Callback container for results | ||
this._awaitingResults = {}; | ||
// Client-side methods | ||
this._methods = {}; | ||
// Event handler containers | ||
this._events = { | ||
open: [], | ||
close: [], | ||
connection: [], | ||
message: [] | ||
}; | ||
// On WebSocket open | ||
this.on('open', this.onOpen); | ||
// On WebSocket close | ||
this.on('close', this.onClose); | ||
// On WebSocket message | ||
this.on('message', this.onMessage); | ||
}; | ||
if (!client.on) | ||
client.addListener = client.on = function (type, cb) { | ||
if (callbacks[type]) { | ||
callbacks[type].push(cb); | ||
} else { | ||
callbacks[type] = [cb]; | ||
// Inherit prototype from EventEmitter | ||
util.inherits(Client, EventEmitter); | ||
Client.prototype.onOpen = function (e) { | ||
debug('WebSocket opened'); | ||
if (this._messageQueue.length) { | ||
var payload; | ||
while (this._messageQueue.length) { | ||
payload = this._messageQueue.shift(); | ||
debug('→ %o', payload); | ||
this.socket.send(JSON.stringify(payload)); | ||
} | ||
}; | ||
} | ||
}; | ||
if (!client.once) | ||
client.once = function (type, cb) { | ||
client.on(type, function onceFn () { | ||
client.off(type, onceFn); | ||
cb.apply(cb, arguments); | ||
}); | ||
}; | ||
Client.prototype.onClose = function (e) { | ||
debug('WebSocket closed'); | ||
}; | ||
if (!client.off) | ||
client.removeListener = client.off = function (type, cb) { | ||
if (Array.isArray(callbacks[type])) { | ||
var idx = callbacks[type].indexOf(cb); | ||
if (idx !== -1) { | ||
if (callbacks[type].length === 1) { | ||
delete callbacks[type]; | ||
} else { | ||
callbacks[type].splice(idx, 1); | ||
} | ||
} | ||
Client.prototype.onMessage = function (e) { | ||
var payload = JSON.parse(e.data || e); | ||
if (payload.method) { | ||
debug('← %s: %o', payload.method, payload.params); | ||
var request = new Request(this, payload); | ||
if (payload.error) { | ||
debug('Got error for method %s, code %s: %s', | ||
payload.method, payload.error.code, payload.error.message); | ||
this._methods[payload.method].apply( | ||
request, | ||
payload.params | ||
); | ||
} | ||
}; | ||
} else { | ||
// Add helper handlers for the folowing events | ||
if (typeof client._socket === 'undefined') { | ||
if (payload.error && payload.id && this._awaitingResults[payload.id]) { | ||
debug('← (%s) Error %s: %o', payload.id, payload.error.code, payload.error.message); | ||
this._awaitingResults[payload.id].apply( | ||
this, | ||
[payload.error] | ||
); | ||
} else if (payload.error) { | ||
debug('← Error %s: %o', payload.error.code, payload.error.message); | ||
//client.emit('error', payload.error); | ||
//console.error('Error %s: %s', payload.error.code, payload.error.message); | ||
} else if (payload.id && this._awaitingResults[payload.id]) { | ||
debug('← (%s) %o', payload.id, payload.result); | ||
this._awaitingResults[payload.id].apply( | ||
this, | ||
[null, payload.result] | ||
); | ||
} | ||
} | ||
}; | ||
Client.prototype.connect = function (address) { | ||
address = address || guessedAddress; | ||
address = address.replace('ws://', ''); | ||
// Initialize WebSocket client | ||
debug('Connecting to server: ws://%s', address); | ||
this.socket = new WebSocket('ws://' + address); | ||
// Add helper handlers for the folowing events | ||
['open', 'close', 'message'] | ||
.forEach(function (type, i) { | ||
if (!client['on' + type]) { | ||
client['on' + type] = function () { | ||
for (var i = 0, l = callbacks[type].length; i < l; i++) { | ||
callbacks[type][i].apply(client, arguments); | ||
} | ||
}; | ||
var handler = function (e) { | ||
this.emit.apply(this, [type].concat(Array.prototype.slice.call(arguments))); | ||
}.bind(this); | ||
if (this.socket.on) { | ||
this.socket.on(type, handler) | ||
} else if (!this.socket['on' + type]) { | ||
this.socket['on' + type] = handler; | ||
} | ||
}); | ||
} | ||
}.bind(this)); | ||
}; | ||
// Call a method | ||
client.method = function () { | ||
// Register a client-side method | ||
Client.prototype.register = function (method, handler) { | ||
if (typeof method === 'object') { | ||
for (var name in methods) { | ||
this.register(name, method[name]); | ||
} | ||
} else { | ||
debug('Registering method: %s', method); | ||
this._methods[method] = handler; | ||
} | ||
}; | ||
// Call a server-side method | ||
Client.prototype.method = function () { | ||
var cb = null; | ||
@@ -117,19 +172,18 @@ var payload = { | ||
if (cb) { | ||
debug('Registering callback for id %s', payload.id); | ||
awaitingResults[payload.id] = function () { | ||
this._awaitingResults[payload.id] = function () { | ||
cb.apply(this, arguments); | ||
delete awaitingResults[payload.id]; | ||
delete this._awaitingResults[payload.id]; | ||
}; | ||
} | ||
if (this.readyState !== 1) { | ||
// Webclient is not ready, push payload to messsage queue | ||
messageQueue.push(payload); | ||
if (this.socket.readyState !== 1) { | ||
// WebSocket is not ready yet, push payload to messsage queue | ||
this._messageQueue.push(payload); | ||
} else { | ||
try { | ||
debug('Sending message: %o', payload); | ||
client.send(JSON.stringify(payload)); | ||
debug('→ %o', payload); | ||
this.socket.send(JSON.stringify(payload)); | ||
} catch (e) { | ||
if (cb) { | ||
cb.call(client, e); | ||
cb.call(this, e); | ||
} | ||
@@ -140,43 +194,11 @@ } | ||
client.on('open', function (e) { | ||
debug('WebSocket open'); | ||
var clientInstance = new Client(); | ||
if (messageQueue.length) { | ||
var payload; | ||
while (messageQueue.length) { | ||
payload = messageQueue.shift(); | ||
debug('Sending message: %o', payload); | ||
client.send(JSON.stringify(payload)); | ||
} | ||
} | ||
}); | ||
client.on('message', function (e) { | ||
var payload = JSON.parse(e.data || e); | ||
debug('Incoming message: %o', payload); | ||
if (payload.error && payload.id && awaitingResults[payload.id]) { | ||
debug('Got error for id %s, code %s: %s', payload.id, payload.error.code, payload.error.message); | ||
awaitingResults[payload.id].apply(client, [payload.error]); | ||
} else if (payload.error) { | ||
debug('Error %s: %s', payload.error.code, payload.error.message); | ||
//client.emit('error', payload.error); | ||
//console.error('Error %s: %s', payload.error.code, payload.error.message); | ||
} else if (payload.id && awaitingResults[payload.id]) { | ||
debug('Got result for id %s: %s', payload.id, payload.results); | ||
awaitingResults[payload.id].apply(client, [null, payload.result]); | ||
} else if (payload.method && Array.isArray(callbacks[payload.method])) { | ||
for (var i = 0, l = callbacks[payload.method].length; i < l; i++) { | ||
callbacks[payload.method][i].apply( | ||
client, [null, payload.params]); | ||
} | ||
} | ||
}); | ||
// Expose the client | ||
if (typeof module !== 'undefined' && module.exports) { | ||
module.exports = client; | ||
module.exports = clientInstance; | ||
} else { | ||
window.koaws = client; | ||
window.koaws = clientInstance; | ||
} | ||
}.call(this)); |
@@ -1,2 +0,1 @@ | ||
var co = require('co'); | ||
var fs = require('fs'); | ||
@@ -6,211 +5,6 @@ var path = require('path'); | ||
var replaceStream = require('replacestream'); | ||
var WebSocketServer = require('ws').Server; | ||
var cookieHelper = require('koa-ws-cookie-helper'); | ||
var debug = require('debug')('koa:ws'); | ||
var debug = require('debug')('koa-ws:middleware'); | ||
/** | ||
* KoaWebSocketServer object | ||
* @param app | ||
* @param options | ||
*/ | ||
function KoaWebSocketServer (app, options) { | ||
// Save ref to app | ||
this.app = app; | ||
var KoaWebSocketServer = require('./server'); | ||
// Container for methods | ||
this.methods = {}; | ||
// Container for sockets | ||
this.sockets = {}; | ||
// Session to socket mapping | ||
this.sessions = {}; | ||
} | ||
KoaWebSocketServer.prototype.listen = function (server) { | ||
// Create WebSocketServer | ||
this.server = new WebSocketServer({ | ||
server: server | ||
}); | ||
// Listen to connection | ||
this.server.on('connection', this.onConnection.bind(this)); | ||
} | ||
/** | ||
* On new connection | ||
* @param socket | ||
*/ | ||
KoaWebSocketServer.prototype.onConnection = function (socket) { | ||
var server = this.server; | ||
var methods = this.methods; | ||
var sockets = this.sockets; | ||
var sessions = this.sessions; | ||
socket.respond = function (method, params) { | ||
try { | ||
var payload = { | ||
jsonrpc: '2.0', | ||
method: method | ||
}; | ||
if (params) { | ||
payload.params = params; | ||
} | ||
debug('→ %s: %o', payload.method, payload.params); | ||
socket.send(JSON.stringify(payload)); | ||
} catch (e) { | ||
console.error('Something went wrong: ', e.stack); | ||
} | ||
}; | ||
socket.result = function (result) { | ||
try { | ||
var payload = { | ||
jsonrpc: '2.0', | ||
result: result, | ||
id: this.currentId | ||
}; | ||
debug('→ result for id %s: %o', payload.id, payload.result); | ||
socket.send(JSON.stringify(payload)); | ||
} catch (e) { | ||
console.error('Something went wrong: ', e.stack); | ||
} | ||
} | ||
socket.error = function (code, message) { | ||
try { | ||
var data = { | ||
jsonrpc: '2.0', | ||
error: { | ||
code: code, | ||
message: message | ||
}, | ||
id: this.currentId | ||
}; | ||
debug('→', data); | ||
socket.send(JSON.stringify(data)); | ||
} catch (e) { | ||
console.error('Something went wrong: ', e.stack); | ||
} | ||
}; | ||
socket.on('close', function () { | ||
debug('Client disconnected'); | ||
if (socket.session && Array.isArray(sockets[socket.session.id])) { | ||
sockets[socket.session.id].splice( | ||
sockets[socket.session.id].indexOf(socket), | ||
1 | ||
); | ||
} | ||
}); | ||
socket.on('error', function (err) { | ||
debug('Error occurred:', err); | ||
}); | ||
socket.on('message', function (message) { | ||
try { | ||
var payload = JSON.parse(message); | ||
} catch (e) { | ||
debug('Parse error: %s', e.stack); | ||
socket.error(-32700, 'Parse error'); | ||
return; | ||
} | ||
var request = { | ||
currentId: payload.id, | ||
method: payload.method, | ||
params: payload.params, | ||
session: socket.session | ||
}; | ||
request.error = socket.error.bind(request); | ||
request.result = socket.result.bind(request); | ||
request.respond = socket.result.bind(request); | ||
if (!payload.jsonrpc && payload.jsonrpc !== '2.0') { | ||
debug('Wrong protocol: %s', payload.jsonrpc); | ||
socket.error.apply(request, [-32600, 'Invalid Request']); | ||
return; | ||
} | ||
if (!payload.method) { | ||
debug('Missing method: %o', payload); | ||
socket.error.apply(request, [-32600, 'Invalid Request']); | ||
return; | ||
} | ||
if (typeof payload.params !== 'undefined' && typeof payload.params !== 'object' && !Array.isArray(payload.params)) { | ||
debug('Invalid params: %o', payload.params); | ||
socket.error.apply(request, [-32602, 'Invalid params']); | ||
return; | ||
} | ||
debug('← %s: %o', payload.method, payload.params); | ||
if (typeof methods[payload.method] === 'function') { | ||
try { | ||
methods[payload.method].apply(request); | ||
} catch (e) { | ||
debug('Internal error: %s', e.stack); | ||
socket.error.apply(request, [-32603, 'Internal error']); | ||
} | ||
} else { | ||
debug('Method not found: %s', payload.method, payload.params); | ||
socket.error.apply(request, [-32601, 'Method not found']); | ||
} | ||
}); | ||
// Let's try and connect the socket to session | ||
var sessionId = cookieHelper.get(socket, 'koa.sid', this.app.keys); | ||
if (sessionId) { | ||
if (typeof this.sockets[sessionId] === 'undefined') { | ||
this.sockets[sessionId] = []; | ||
} | ||
this.sockets[sessionId].push(socket); | ||
if (this.app.sessionStore) { | ||
var _this = this; | ||
(co(function* () { | ||
socket.session = yield _this.app.sessionStore.get('koa:sess:' + sessionId); | ||
socket.respond('session', socket.session); | ||
})()); | ||
} | ||
} | ||
} | ||
/** | ||
* Register a handler generator for method | ||
* @param method | ||
* @param generator | ||
* @param expose | ||
*/ | ||
KoaWebSocketServer.prototype.register = function (method, generator, expose) { | ||
if (typeof method === 'object') { | ||
for (var m in method) { | ||
this.register(m, method[m]); | ||
} | ||
} else if (typeof generator === 'object') { | ||
for (var m in generator) { | ||
this.register(method + ':' + m, generator[m]); | ||
} | ||
} else if (typeof method === 'string') { | ||
debug('Registering method: %s', method); | ||
generator.expose = expose || false; | ||
this.methods[method] = co(generator); | ||
} | ||
}; | ||
/** | ||
* Broadcast to all connected sockets | ||
* @param method string | ||
* @param params object | ||
*/ | ||
KoaWebSocketServer.prototype.broadcast = function (method, params) { | ||
for (var i in this.server.clients) { | ||
this.server.clients[i].respond(method, params, function (err) { | ||
debug('Could not send message', data, err); | ||
}); | ||
} | ||
} | ||
module.exports = function (app, passedOptions) { | ||
@@ -254,5 +48,4 @@ // Default options | ||
} | ||
yield next; | ||
}; | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
23784
14
542
2