mirror of
https://github.com/django/daphne.git
synced 2025-07-31 09:19:45 +03:00
add Channels WebSocket javascript wrapper (#544)
Adds a WebSocket wrapper which is both publishable to npm and importable directly for use with staticfiles/etc. Also has a new build process to make the latter file.
This commit is contained in:
parent
463d16f3f9
commit
63dc5f6651
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,6 +9,7 @@ __pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
.coverage.*
|
.coverage.*
|
||||||
TODO
|
TODO
|
||||||
|
node_modules
|
||||||
|
|
||||||
# IDE and Tooling files
|
# IDE and Tooling files
|
||||||
.idea/*
|
.idea/*
|
||||||
|
|
|
@ -17,11 +17,13 @@ cache:
|
||||||
- $HOME/.cache/pip/wheels
|
- $HOME/.cache/pip/wheels
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
- nvm install 7
|
||||||
- pip install -U pip wheel setuptools
|
- pip install -U pip wheel setuptools
|
||||||
- pip install $DJANGO -e .[tests]
|
- pip install $DJANGO -e .[tests]
|
||||||
- pip freeze
|
- pip freeze
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- python runtests.py
|
- python runtests.py
|
||||||
|
- cd js_client && npm install --progress=false && npm test && cd ..
|
||||||
- flake8
|
- flake8
|
||||||
- isort --check-only --recursive channels
|
- isort --check-only --recursive channels
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
all:
|
all:
|
||||||
|
|
||||||
|
build_assets:
|
||||||
|
cd js_client && npm run browserify && cd ..
|
||||||
|
|
||||||
release:
|
release:
|
||||||
ifndef version
|
ifndef version
|
||||||
$(error Please supply a version)
|
$(error Please supply a version)
|
||||||
|
@ -14,3 +17,4 @@ endif
|
||||||
git push
|
git push
|
||||||
git push --tags
|
git push --tags
|
||||||
python setup.py sdist bdist_wheel upload
|
python setup.py sdist bdist_wheel upload
|
||||||
|
cd js_client && npm publish && cd ..
|
||||||
|
|
395
channels/static/channels/js/websocketbridge.js
Normal file
395
channels/static/channels/js/websocketbridge.js
Normal file
|
@ -0,0 +1,395 @@
|
||||||
|
/*!
|
||||||
|
* Do not edit!. This file is autogenerated by running `npm run browserify`.
|
||||||
|
*/
|
||||||
|
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.channels = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||||
|
"use strict";
|
||||||
|
var isWebSocket = function (constructor) {
|
||||||
|
return constructor && constructor.CLOSING === 2;
|
||||||
|
};
|
||||||
|
var isGlobalWebSocket = function () {
|
||||||
|
return typeof WebSocket !== 'undefined' && isWebSocket(WebSocket);
|
||||||
|
};
|
||||||
|
var getDefaultOptions = function () { return ({
|
||||||
|
constructor: isGlobalWebSocket() ? WebSocket : null,
|
||||||
|
maxReconnectionDelay: 10000,
|
||||||
|
minReconnectionDelay: 1500,
|
||||||
|
reconnectionDelayGrowFactor: 1.3,
|
||||||
|
connectionTimeout: 4000,
|
||||||
|
maxRetries: Infinity,
|
||||||
|
debug: false,
|
||||||
|
}); };
|
||||||
|
var bypassProperty = function (src, dst, name) {
|
||||||
|
Object.defineProperty(dst, name, {
|
||||||
|
get: function () { return src[name]; },
|
||||||
|
set: function (value) { src[name] = value; },
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var initReconnectionDelay = function (config) {
|
||||||
|
return (config.minReconnectionDelay + Math.random() * config.minReconnectionDelay);
|
||||||
|
};
|
||||||
|
var updateReconnectionDelay = function (config, previousDelay) {
|
||||||
|
var newDelay = previousDelay * config.reconnectionDelayGrowFactor;
|
||||||
|
return (newDelay > config.maxReconnectionDelay)
|
||||||
|
? config.maxReconnectionDelay
|
||||||
|
: newDelay;
|
||||||
|
};
|
||||||
|
var LEVEL_0_EVENTS = ['onopen', 'onclose', 'onmessage', 'onerror'];
|
||||||
|
var reassignEventListeners = function (ws, oldWs, listeners) {
|
||||||
|
Object.keys(listeners).forEach(function (type) {
|
||||||
|
listeners[type].forEach(function (_a) {
|
||||||
|
var listener = _a[0], options = _a[1];
|
||||||
|
ws.addEventListener(type, listener, options);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (oldWs) {
|
||||||
|
LEVEL_0_EVENTS.forEach(function (name) { ws[name] = oldWs[name]; });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var ReconnectingWebsocket = function (url, protocols, options) {
|
||||||
|
var _this = this;
|
||||||
|
if (options === void 0) { options = {}; }
|
||||||
|
var ws;
|
||||||
|
var connectingTimeout;
|
||||||
|
var reconnectDelay = 0;
|
||||||
|
var retriesCount = 0;
|
||||||
|
var shouldRetry = true;
|
||||||
|
var savedOnClose = null;
|
||||||
|
var listeners = {};
|
||||||
|
// require new to construct
|
||||||
|
if (!(this instanceof ReconnectingWebsocket)) {
|
||||||
|
throw new TypeError("Failed to construct 'ReconnectingWebSocket': Please use the 'new' operator");
|
||||||
|
}
|
||||||
|
// Set config. Not using `Object.assign` because of IE11
|
||||||
|
var config = getDefaultOptions();
|
||||||
|
Object.keys(config)
|
||||||
|
.filter(function (key) { return options.hasOwnProperty(key); })
|
||||||
|
.forEach(function (key) { return config[key] = options[key]; });
|
||||||
|
if (!isWebSocket(config.constructor)) {
|
||||||
|
throw new TypeError('Invalid WebSocket constructor. Set `options.constructor`');
|
||||||
|
}
|
||||||
|
var log = config.debug ? function () {
|
||||||
|
var params = [];
|
||||||
|
for (var _i = 0; _i < arguments.length; _i++) {
|
||||||
|
params[_i - 0] = arguments[_i];
|
||||||
|
}
|
||||||
|
return console.log.apply(console, ['RWS:'].concat(params));
|
||||||
|
} : function () { };
|
||||||
|
/**
|
||||||
|
* Not using dispatchEvent, otherwise we must use a DOM Event object
|
||||||
|
* Deferred because we want to handle the close event before this
|
||||||
|
*/
|
||||||
|
var emitError = function (code, msg) { return setTimeout(function () {
|
||||||
|
var err = new Error(msg);
|
||||||
|
err.code = code;
|
||||||
|
if (Array.isArray(listeners.error)) {
|
||||||
|
listeners.error.forEach(function (_a) {
|
||||||
|
var fn = _a[0];
|
||||||
|
return fn(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (ws.onerror) {
|
||||||
|
ws.onerror(err);
|
||||||
|
}
|
||||||
|
}, 0); };
|
||||||
|
var handleClose = function () {
|
||||||
|
log('close');
|
||||||
|
retriesCount++;
|
||||||
|
log('retries count:', retriesCount);
|
||||||
|
if (retriesCount > config.maxRetries) {
|
||||||
|
emitError('EHOSTDOWN', 'Too many failed connection attempts');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!reconnectDelay) {
|
||||||
|
reconnectDelay = initReconnectionDelay(config);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reconnectDelay = updateReconnectionDelay(config, reconnectDelay);
|
||||||
|
}
|
||||||
|
log('reconnectDelay:', reconnectDelay);
|
||||||
|
if (shouldRetry) {
|
||||||
|
setTimeout(connect, reconnectDelay);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var connect = function () {
|
||||||
|
log('connect');
|
||||||
|
var oldWs = ws;
|
||||||
|
ws = new config.constructor(url, protocols);
|
||||||
|
connectingTimeout = setTimeout(function () {
|
||||||
|
log('timeout');
|
||||||
|
ws.close();
|
||||||
|
emitError('ETIMEDOUT', 'Connection timeout');
|
||||||
|
}, config.connectionTimeout);
|
||||||
|
log('bypass properties');
|
||||||
|
for (var key in ws) {
|
||||||
|
// @todo move to constant
|
||||||
|
if (['addEventListener', 'removeEventListener', 'close', 'send'].indexOf(key) < 0) {
|
||||||
|
bypassProperty(ws, _this, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ws.addEventListener('open', function () {
|
||||||
|
clearTimeout(connectingTimeout);
|
||||||
|
log('open');
|
||||||
|
reconnectDelay = initReconnectionDelay(config);
|
||||||
|
log('reconnectDelay:', reconnectDelay);
|
||||||
|
retriesCount = 0;
|
||||||
|
});
|
||||||
|
ws.addEventListener('close', handleClose);
|
||||||
|
reassignEventListeners(ws, oldWs, listeners);
|
||||||
|
// because when closing with fastClose=true, it is saved and set to null to avoid double calls
|
||||||
|
ws.onclose = ws.onclose || savedOnClose;
|
||||||
|
savedOnClose = null;
|
||||||
|
};
|
||||||
|
log('init');
|
||||||
|
connect();
|
||||||
|
this.close = function (code, reason, _a) {
|
||||||
|
if (code === void 0) { code = 1000; }
|
||||||
|
if (reason === void 0) { reason = ''; }
|
||||||
|
var _b = _a === void 0 ? {} : _a, _c = _b.keepClosed, keepClosed = _c === void 0 ? false : _c, _d = _b.fastClose, fastClose = _d === void 0 ? true : _d, _e = _b.delay, delay = _e === void 0 ? 0 : _e;
|
||||||
|
if (delay) {
|
||||||
|
reconnectDelay = delay;
|
||||||
|
}
|
||||||
|
shouldRetry = !keepClosed;
|
||||||
|
ws.close(code, reason);
|
||||||
|
if (fastClose) {
|
||||||
|
var fakeCloseEvent_1 = {
|
||||||
|
code: code,
|
||||||
|
reason: reason,
|
||||||
|
wasClean: true,
|
||||||
|
};
|
||||||
|
// execute close listeners soon with a fake closeEvent
|
||||||
|
// and remove them from the WS instance so they
|
||||||
|
// don't get fired on the real close.
|
||||||
|
handleClose();
|
||||||
|
ws.removeEventListener('close', handleClose);
|
||||||
|
// run and remove level2
|
||||||
|
if (Array.isArray(listeners.close)) {
|
||||||
|
listeners.close.forEach(function (_a) {
|
||||||
|
var listener = _a[0], options = _a[1];
|
||||||
|
listener(fakeCloseEvent_1);
|
||||||
|
ws.removeEventListener('close', listener, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// run and remove level0
|
||||||
|
if (ws.onclose) {
|
||||||
|
savedOnClose = ws.onclose;
|
||||||
|
ws.onclose(fakeCloseEvent_1);
|
||||||
|
ws.onclose = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.send = function (data) {
|
||||||
|
ws.send(data);
|
||||||
|
};
|
||||||
|
this.addEventListener = function (type, listener, options) {
|
||||||
|
if (Array.isArray(listeners[type])) {
|
||||||
|
if (!listeners[type].some(function (_a) {
|
||||||
|
var l = _a[0];
|
||||||
|
return l === listener;
|
||||||
|
})) {
|
||||||
|
listeners[type].push([listener, options]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
listeners[type] = [[listener, options]];
|
||||||
|
}
|
||||||
|
ws.addEventListener(type, listener, options);
|
||||||
|
};
|
||||||
|
this.removeEventListener = function (type, listener, options) {
|
||||||
|
if (Array.isArray(listeners[type])) {
|
||||||
|
listeners[type] = listeners[type].filter(function (_a) {
|
||||||
|
var l = _a[0];
|
||||||
|
return l !== listener;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ws.removeEventListener(type, listener, options);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
module.exports = ReconnectingWebsocket;
|
||||||
|
|
||||||
|
},{}],2:[function(require,module,exports){
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.WebSocketBridge = undefined;
|
||||||
|
|
||||||
|
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
||||||
|
|
||||||
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||||||
|
|
||||||
|
var _reconnectingWebsocket = require('reconnecting-websocket');
|
||||||
|
|
||||||
|
var _reconnectingWebsocket2 = _interopRequireDefault(_reconnectingWebsocket);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||||
|
|
||||||
|
var noop = function noop() {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bridge between Channels and plain javascript.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const webSocketBridge = new WebSocketBridge();
|
||||||
|
* webSocketBridge.connect();
|
||||||
|
* webSocketBridge.listen(function(action, stream) {
|
||||||
|
* console.log(action, stream);
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
|
||||||
|
var WebSocketBridge = function () {
|
||||||
|
function WebSocketBridge(options) {
|
||||||
|
_classCallCheck(this, WebSocketBridge);
|
||||||
|
|
||||||
|
this._socket = null;
|
||||||
|
this.streams = {};
|
||||||
|
this.default_cb = null;
|
||||||
|
this.options = _extends({}, {
|
||||||
|
onopen: noop
|
||||||
|
}, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the websocket server
|
||||||
|
*
|
||||||
|
* @param {String} [url] The url of the websocket. Defaults to
|
||||||
|
* `window.location.host`
|
||||||
|
* @param {String[]|String} [protocols] Optional string or array of protocols.
|
||||||
|
* @param {Object} options Object of options for [`reconnecting-websocket`](https://github.com/joewalnes/reconnecting-websocket#options-1).
|
||||||
|
* @example
|
||||||
|
* const webSocketBridge = new WebSocketBridge();
|
||||||
|
* webSocketBridge.connect();
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
_createClass(WebSocketBridge, [{
|
||||||
|
key: 'connect',
|
||||||
|
value: function connect(url, protocols, options) {
|
||||||
|
var _url = void 0;
|
||||||
|
if (url === undefined) {
|
||||||
|
// Use wss:// if running on https://
|
||||||
|
var scheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
|
_url = scheme + '://' + window.location.host + '/ws';
|
||||||
|
} else {
|
||||||
|
_url = url;
|
||||||
|
}
|
||||||
|
this._socket = new _reconnectingWebsocket2.default(_url, protocols, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts listening for messages on the websocket, demultiplexing if necessary.
|
||||||
|
*
|
||||||
|
* @param {Function} [cb] Callback to be execute when a message
|
||||||
|
* arrives. The callback will receive `action` and `stream` parameters
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const webSocketBridge = new WebSocketBridge();
|
||||||
|
* webSocketBridge.connect();
|
||||||
|
* webSocketBridge.listen(function(action, stream) {
|
||||||
|
* console.log(action, stream);
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'listen',
|
||||||
|
value: function listen(cb) {
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
this.default_cb = cb;
|
||||||
|
this._socket.onmessage = function (event) {
|
||||||
|
var msg = JSON.parse(event.data);
|
||||||
|
var action = void 0;
|
||||||
|
var stream = void 0;
|
||||||
|
|
||||||
|
if (msg.stream !== undefined) {
|
||||||
|
action = msg.payload;
|
||||||
|
stream = msg.stream;
|
||||||
|
var stream_cb = _this.streams[stream];
|
||||||
|
stream_cb ? stream_cb(action, stream) : null;
|
||||||
|
} else {
|
||||||
|
action = msg;
|
||||||
|
stream = null;
|
||||||
|
_this.default_cb ? _this.default_cb(action, stream) : null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this._socket.onopen = this.options.onopen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a 'stream handler' callback. Messages coming from the specified stream
|
||||||
|
* will call the specified callback.
|
||||||
|
*
|
||||||
|
* @param {String} stream The stream name
|
||||||
|
* @param {Function} cb Callback to be execute when a message
|
||||||
|
* arrives. The callback will receive `action` and `stream` parameters.
|
||||||
|
* @example
|
||||||
|
* const webSocketBridge = new WebSocketBridge();
|
||||||
|
* webSocketBridge.connect();
|
||||||
|
* webSocketBridge.listen();
|
||||||
|
* webSocketBridge.demultiplex('mystream', function(action, stream) {
|
||||||
|
* console.log(action, stream);
|
||||||
|
* });
|
||||||
|
* webSocketBridge.demultiplex('myotherstream', function(action, stream) {
|
||||||
|
* console.info(action, stream);
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'demultiplex',
|
||||||
|
value: function demultiplex(stream, cb) {
|
||||||
|
this.streams[stream] = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message to the reply channel.
|
||||||
|
*
|
||||||
|
* @param {Object} msg The message
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* webSocketBridge.send({prop1: 'value1', prop2: 'value1'});
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'send',
|
||||||
|
value: function send(msg) {
|
||||||
|
this._socket.send(JSON.stringify(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object to send messages to a specific stream
|
||||||
|
*
|
||||||
|
* @param {String} stream The stream name
|
||||||
|
* @return {Object} convenience object to send messages to `stream`.
|
||||||
|
* @example
|
||||||
|
* webSocketBridge.stream('mystream').send({prop1: 'value1', prop2: 'value1'})
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'stream',
|
||||||
|
value: function stream(_stream) {
|
||||||
|
var _this2 = this;
|
||||||
|
|
||||||
|
return {
|
||||||
|
send: function send(action) {
|
||||||
|
var msg = {
|
||||||
|
stream: _stream,
|
||||||
|
payload: action
|
||||||
|
};
|
||||||
|
_this2._socket.send(JSON.stringify(msg));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
|
||||||
|
return WebSocketBridge;
|
||||||
|
}();
|
||||||
|
|
||||||
|
exports.WebSocketBridge = WebSocketBridge;
|
||||||
|
|
||||||
|
},{"reconnecting-websocket":1}]},{},[2])(2)
|
||||||
|
});
|
|
@ -136,9 +136,9 @@ Tie that into your routing, and you're ready to go::
|
||||||
Frontend Considerations
|
Frontend Considerations
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
You can use the standard Channels WebSocket wrapper **(not yet available)**
|
You can use the standard :doc:`Channels WebSocket wrapper <javascript>` to
|
||||||
to automatically run demultiplexing, and then tie the events you receive into
|
automatically run demultiplexing, and then tie the events you receive into your
|
||||||
your frontend framework of choice based on ``action``, ``pk`` and ``data``.
|
frontend framework of choice based on ``action``, ``pk`` and ``data``.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ Topics
|
||||||
generics
|
generics
|
||||||
routing
|
routing
|
||||||
binding
|
binding
|
||||||
|
javascript
|
||||||
backends
|
backends
|
||||||
delay
|
delay
|
||||||
testing
|
testing
|
||||||
|
|
45
docs/javascript.rst
Normal file
45
docs/javascript.rst
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
Channels WebSocket wrapper
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Channels ships with a javascript WebSocket wrapper to help you connect to your websocket
|
||||||
|
and send/receive messages.
|
||||||
|
|
||||||
|
First, you must include the javascript library in your template::
|
||||||
|
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% static "channels/js/websocketbridge.js" %}
|
||||||
|
|
||||||
|
To process messages::
|
||||||
|
|
||||||
|
const webSocketBridge = new channels.WebSocketBridge();
|
||||||
|
webSocketBridge.connect();
|
||||||
|
webSocketBridge.listen(function(action, stream) {
|
||||||
|
console.log(action, stream);
|
||||||
|
});
|
||||||
|
|
||||||
|
To send messages, use the `send` method::
|
||||||
|
|
||||||
|
```
|
||||||
|
webSocketBridge.send({prop1: 'value1', prop2: 'value1'});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
To demultiplex specific streams::
|
||||||
|
|
||||||
|
webSocketBridge.connect();
|
||||||
|
webSocketBridge.listen();
|
||||||
|
webSocketBridge.demultiplex('mystream', function(action, stream) {
|
||||||
|
console.log(action, stream);
|
||||||
|
});
|
||||||
|
webSocketBridge.demultiplex('myotherstream', function(action, stream) {
|
||||||
|
console.info(action, stream);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
To send a message to a specific stream::
|
||||||
|
|
||||||
|
webSocketBridge.stream('mystream').send({prop1: 'value1', prop2: 'value1'})
|
||||||
|
|
||||||
|
The library is also available as npm module, under the name
|
||||||
|
`django-channels <https://www.npmjs.com/package/django-channels>`_
|
10
js_client/.babelrc
Normal file
10
js_client/.babelrc
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
"es2015",
|
||||||
|
"stage-1",
|
||||||
|
"react"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"transform-object-assign",
|
||||||
|
]
|
||||||
|
}
|
9
js_client/.eslintrc.js
Normal file
9
js_client/.eslintrc.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
module.exports = {
|
||||||
|
"extends": "airbnb",
|
||||||
|
"plugins": [
|
||||||
|
"react"
|
||||||
|
],
|
||||||
|
env: {
|
||||||
|
jest: true
|
||||||
|
}
|
||||||
|
};
|
8
js_client/.npmignore
Normal file
8
js_client/.npmignore
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
npm-debug.log
|
||||||
|
node_modules
|
||||||
|
.*.swp
|
||||||
|
.lock-*
|
||||||
|
build
|
||||||
|
.babelrc
|
||||||
|
webpack.*
|
||||||
|
/src/
|
42
js_client/README.md
Normal file
42
js_client/README.md
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Channels WebSocket wrapper.
|
||||||
|
|
||||||
|
To process messages:
|
||||||
|
|
||||||
|
```
|
||||||
|
import { WebSocketBridge } from 'django-channels'
|
||||||
|
|
||||||
|
const webSocketBridge = new WebSocketBridge();
|
||||||
|
webSocketBridge.connect();
|
||||||
|
webSocketBridge.listen(function(action, stream) {
|
||||||
|
console.log(action, stream);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
To send messages:
|
||||||
|
|
||||||
|
```
|
||||||
|
webSocketBridge.send({prop1: 'value1', prop2: 'value1'});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
To demultiplex specific streams:
|
||||||
|
|
||||||
|
```
|
||||||
|
const webSocketBridge = new WebSocketBridge();
|
||||||
|
webSocketBridge.connect();
|
||||||
|
webSocketBridge.listen();
|
||||||
|
webSocketBridge.demultiplex('mystream', function(action, stream) {
|
||||||
|
console.log(action, stream);
|
||||||
|
});
|
||||||
|
webSocketBridge.demultiplex('myotherstream', function(action, stream) {
|
||||||
|
console.info(action, stream);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
To send a message to a specific stream:
|
||||||
|
|
||||||
|
```
|
||||||
|
webSocketBridge.stream('mystream').send({prop1: 'value1', prop2: 'value1'})
|
||||||
|
```
|
1
js_client/banner.txt
Normal file
1
js_client/banner.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Do not edit!. This file is autogenerated by running `npm run browserify`.
|
21
js_client/esdoc.json
Normal file
21
js_client/esdoc.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"source": "./src",
|
||||||
|
"destination": "./docs",
|
||||||
|
"undocumentIdentifier": false,
|
||||||
|
"title": "django-channels",
|
||||||
|
"experimentalProposal": {
|
||||||
|
"classProperties": true,
|
||||||
|
"objectRestSpread": true
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "esdoc-importpath-plugin",
|
||||||
|
"option": {
|
||||||
|
"replaces": [
|
||||||
|
{"from": "^src/", "to": "lib/"},
|
||||||
|
{"from": ".js$", "to": ""}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
181
js_client/lib/index.js
Normal file
181
js_client/lib/index.js
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.WebSocketBridge = undefined;
|
||||||
|
|
||||||
|
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
||||||
|
|
||||||
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||||||
|
|
||||||
|
var _reconnectingWebsocket = require('reconnecting-websocket');
|
||||||
|
|
||||||
|
var _reconnectingWebsocket2 = _interopRequireDefault(_reconnectingWebsocket);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||||
|
|
||||||
|
var noop = function noop() {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bridge between Channels and plain javascript.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const webSocketBridge = new WebSocketBridge();
|
||||||
|
* webSocketBridge.connect();
|
||||||
|
* webSocketBridge.listen(function(action, stream) {
|
||||||
|
* console.log(action, stream);
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
|
||||||
|
var WebSocketBridge = function () {
|
||||||
|
function WebSocketBridge(options) {
|
||||||
|
_classCallCheck(this, WebSocketBridge);
|
||||||
|
|
||||||
|
this._socket = null;
|
||||||
|
this.streams = {};
|
||||||
|
this.default_cb = null;
|
||||||
|
this.options = _extends({}, {
|
||||||
|
onopen: noop
|
||||||
|
}, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the websocket server
|
||||||
|
*
|
||||||
|
* @param {String} [url] The url of the websocket. Defaults to
|
||||||
|
* `window.location.host`
|
||||||
|
* @param {String[]|String} [protocols] Optional string or array of protocols.
|
||||||
|
* @param {Object} options Object of options for [`reconnecting-websocket`](https://github.com/joewalnes/reconnecting-websocket#options-1).
|
||||||
|
* @example
|
||||||
|
* const webSocketBridge = new WebSocketBridge();
|
||||||
|
* webSocketBridge.connect();
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
_createClass(WebSocketBridge, [{
|
||||||
|
key: 'connect',
|
||||||
|
value: function connect(url, protocols, options) {
|
||||||
|
var _url = void 0;
|
||||||
|
if (url === undefined) {
|
||||||
|
// Use wss:// if running on https://
|
||||||
|
var scheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
|
_url = scheme + '://' + window.location.host + '/ws';
|
||||||
|
} else {
|
||||||
|
_url = url;
|
||||||
|
}
|
||||||
|
this._socket = new _reconnectingWebsocket2.default(_url, protocols, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts listening for messages on the websocket, demultiplexing if necessary.
|
||||||
|
*
|
||||||
|
* @param {Function} [cb] Callback to be execute when a message
|
||||||
|
* arrives. The callback will receive `action` and `stream` parameters
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const webSocketBridge = new WebSocketBridge();
|
||||||
|
* webSocketBridge.connect();
|
||||||
|
* webSocketBridge.listen(function(action, stream) {
|
||||||
|
* console.log(action, stream);
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'listen',
|
||||||
|
value: function listen(cb) {
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
this.default_cb = cb;
|
||||||
|
this._socket.onmessage = function (event) {
|
||||||
|
var msg = JSON.parse(event.data);
|
||||||
|
var action = void 0;
|
||||||
|
var stream = void 0;
|
||||||
|
|
||||||
|
if (msg.stream !== undefined) {
|
||||||
|
action = msg.payload;
|
||||||
|
stream = msg.stream;
|
||||||
|
var stream_cb = _this.streams[stream];
|
||||||
|
stream_cb ? stream_cb(action, stream) : null;
|
||||||
|
} else {
|
||||||
|
action = msg;
|
||||||
|
stream = null;
|
||||||
|
_this.default_cb ? _this.default_cb(action, stream) : null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this._socket.onopen = this.options.onopen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a 'stream handler' callback. Messages coming from the specified stream
|
||||||
|
* will call the specified callback.
|
||||||
|
*
|
||||||
|
* @param {String} stream The stream name
|
||||||
|
* @param {Function} cb Callback to be execute when a message
|
||||||
|
* arrives. The callback will receive `action` and `stream` parameters.
|
||||||
|
* @example
|
||||||
|
* const webSocketBridge = new WebSocketBridge();
|
||||||
|
* webSocketBridge.connect();
|
||||||
|
* webSocketBridge.listen();
|
||||||
|
* webSocketBridge.demultiplex('mystream', function(action, stream) {
|
||||||
|
* console.log(action, stream);
|
||||||
|
* });
|
||||||
|
* webSocketBridge.demultiplex('myotherstream', function(action, stream) {
|
||||||
|
* console.info(action, stream);
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'demultiplex',
|
||||||
|
value: function demultiplex(stream, cb) {
|
||||||
|
this.streams[stream] = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message to the reply channel.
|
||||||
|
*
|
||||||
|
* @param {Object} msg The message
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* webSocketBridge.send({prop1: 'value1', prop2: 'value1'});
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'send',
|
||||||
|
value: function send(msg) {
|
||||||
|
this._socket.send(JSON.stringify(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object to send messages to a specific stream
|
||||||
|
*
|
||||||
|
* @param {String} stream The stream name
|
||||||
|
* @return {Object} convenience object to send messages to `stream`.
|
||||||
|
* @example
|
||||||
|
* webSocketBridge.stream('mystream').send({prop1: 'value1', prop2: 'value1'})
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'stream',
|
||||||
|
value: function stream(_stream) {
|
||||||
|
var _this2 = this;
|
||||||
|
|
||||||
|
return {
|
||||||
|
send: function send(action) {
|
||||||
|
var msg = {
|
||||||
|
stream: _stream,
|
||||||
|
payload: action
|
||||||
|
};
|
||||||
|
_this2._socket.send(JSON.stringify(msg));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
|
||||||
|
return WebSocketBridge;
|
||||||
|
}();
|
||||||
|
|
||||||
|
exports.WebSocketBridge = WebSocketBridge;
|
72
js_client/package.json
Normal file
72
js_client/package.json
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"name": "django-channels",
|
||||||
|
"version": "0.0.2",
|
||||||
|
"description": "",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/django/channels.git"
|
||||||
|
},
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"transpile": "rm -rf lib && babel src --out-dir lib",
|
||||||
|
"docs": "rm -rf docs && esdoc -c esdoc.json",
|
||||||
|
"test": "jest",
|
||||||
|
"browserify": "browserify src/index.js -p browserify-banner -s channels -o ../channels/static/channels/js/websocketbridge.js",
|
||||||
|
"prepublish": "npm run transpile",
|
||||||
|
"compile": "npm run transpile && npm run browserify"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib/index.js"
|
||||||
|
],
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"reconnecting-websocket": "^3.0.3"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"roots": [
|
||||||
|
"tests"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserify": {
|
||||||
|
"transform": [
|
||||||
|
[
|
||||||
|
"babelify"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel": "^6.5.2",
|
||||||
|
"babel-cli": "^6.16.0",
|
||||||
|
"babel-core": "^6.16.0",
|
||||||
|
"babel-plugin-transform-inline-environment-variables": "^6.8.0",
|
||||||
|
"babel-plugin-transform-object-assign": "^6.8.0",
|
||||||
|
"babel-plugin-transform-runtime": "^6.15.0",
|
||||||
|
"babel-polyfill": "^6.16.0",
|
||||||
|
"babel-preset-es2015": "^6.16.0",
|
||||||
|
"babel-preset-react": "^6.16.0",
|
||||||
|
"babel-preset-stage-0": "^6.16.0",
|
||||||
|
"babel-register": "^6.9.0",
|
||||||
|
"babel-runtime": "^6.11.6",
|
||||||
|
"babelify": "^7.3.0",
|
||||||
|
"browserify": "^14.1.0",
|
||||||
|
"browserify-banner": "^1.0.3",
|
||||||
|
"esdoc": "^0.5.2",
|
||||||
|
"esdoc-es7-plugin": "0.0.3",
|
||||||
|
"esdoc-importpath-plugin": "^0.1.1",
|
||||||
|
"eslint": "^2.13.1",
|
||||||
|
"eslint-config-airbnb": "^9.0.1",
|
||||||
|
"eslint-plugin-import": "^1.9.2",
|
||||||
|
"eslint-plugin-jsx-a11y": "^1.5.3",
|
||||||
|
"eslint-plugin-react": "^5.2.2",
|
||||||
|
"jest": "^19.0.1",
|
||||||
|
"mock-socket": "^6.0.4",
|
||||||
|
"react": "^15.4.0",
|
||||||
|
"react-cookie": "^0.4.8",
|
||||||
|
"react-dom": "^15.4.0",
|
||||||
|
"react-redux": "^4.4.6",
|
||||||
|
"redux": "^3.6.0",
|
||||||
|
"redux-actions": "^1.0.0",
|
||||||
|
"redux-logger": "^2.7.4",
|
||||||
|
"redux-thunk": "^2.1.0"
|
||||||
|
}
|
||||||
|
}
|
139
js_client/src/index.js
Normal file
139
js_client/src/index.js
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
import ReconnectingWebSocket from 'reconnecting-websocket';
|
||||||
|
|
||||||
|
|
||||||
|
const noop = (...args) => {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bridge between Channels and plain javascript.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const webSocketBridge = new WebSocketBridge();
|
||||||
|
* webSocketBridge.connect();
|
||||||
|
* webSocketBridge.listen(function(action, stream) {
|
||||||
|
* console.log(action, stream);
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export class WebSocketBridge {
|
||||||
|
constructor(options) {
|
||||||
|
this._socket = null;
|
||||||
|
this.streams = {};
|
||||||
|
this.default_cb = null;
|
||||||
|
this.options = Object.assign({}, {
|
||||||
|
onopen: noop,
|
||||||
|
}, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the websocket server
|
||||||
|
*
|
||||||
|
* @param {String} [url] The url of the websocket. Defaults to
|
||||||
|
* `window.location.host`
|
||||||
|
* @param {String[]|String} [protocols] Optional string or array of protocols.
|
||||||
|
* @param {Object} options Object of options for [`reconnecting-websocket`](https://github.com/joewalnes/reconnecting-websocket#options-1).
|
||||||
|
* @example
|
||||||
|
* const webSocketBridge = new WebSocketBridge();
|
||||||
|
* webSocketBridge.connect();
|
||||||
|
*/
|
||||||
|
connect(url, protocols, options) {
|
||||||
|
let _url;
|
||||||
|
if (url === undefined) {
|
||||||
|
// Use wss:// if running on https://
|
||||||
|
const scheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
|
_url = `${scheme}://${window.location.host}/ws`;
|
||||||
|
} else {
|
||||||
|
_url = url;
|
||||||
|
}
|
||||||
|
this._socket = new ReconnectingWebSocket(_url, protocols, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts listening for messages on the websocket, demultiplexing if necessary.
|
||||||
|
*
|
||||||
|
* @param {Function} [cb] Callback to be execute when a message
|
||||||
|
* arrives. The callback will receive `action` and `stream` parameters
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const webSocketBridge = new WebSocketBridge();
|
||||||
|
* webSocketBridge.connect();
|
||||||
|
* webSocketBridge.listen(function(action, stream) {
|
||||||
|
* console.log(action, stream);
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
listen(cb) {
|
||||||
|
this.default_cb = cb;
|
||||||
|
this._socket.onmessage = (event) => {
|
||||||
|
const msg = JSON.parse(event.data);
|
||||||
|
let action;
|
||||||
|
let stream;
|
||||||
|
|
||||||
|
if (msg.stream !== undefined) {
|
||||||
|
action = msg.payload;
|
||||||
|
stream = msg.stream;
|
||||||
|
const stream_cb = this.streams[stream];
|
||||||
|
stream_cb ? stream_cb(action, stream) : null;
|
||||||
|
} else {
|
||||||
|
action = msg;
|
||||||
|
stream = null;
|
||||||
|
this.default_cb ? this.default_cb(action, stream) : null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this._socket.onopen = this.options.onopen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a 'stream handler' callback. Messages coming from the specified stream
|
||||||
|
* will call the specified callback.
|
||||||
|
*
|
||||||
|
* @param {String} stream The stream name
|
||||||
|
* @param {Function} cb Callback to be execute when a message
|
||||||
|
* arrives. The callback will receive `action` and `stream` parameters.
|
||||||
|
|
||||||
|
* @example
|
||||||
|
* const webSocketBridge = new WebSocketBridge();
|
||||||
|
* webSocketBridge.connect();
|
||||||
|
* webSocketBridge.listen();
|
||||||
|
* webSocketBridge.demultiplex('mystream', function(action, stream) {
|
||||||
|
* console.log(action, stream);
|
||||||
|
* });
|
||||||
|
* webSocketBridge.demultiplex('myotherstream', function(action, stream) {
|
||||||
|
* console.info(action, stream);
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
demultiplex(stream, cb) {
|
||||||
|
this.streams[stream] = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message to the reply channel.
|
||||||
|
*
|
||||||
|
* @param {Object} msg The message
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* webSocketBridge.send({prop1: 'value1', prop2: 'value1'});
|
||||||
|
*/
|
||||||
|
send(msg) {
|
||||||
|
this._socket.send(JSON.stringify(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object to send messages to a specific stream
|
||||||
|
*
|
||||||
|
* @param {String} stream The stream name
|
||||||
|
* @return {Object} convenience object to send messages to `stream`.
|
||||||
|
* @example
|
||||||
|
* webSocketBridge.stream('mystream').send({prop1: 'value1', prop2: 'value1'})
|
||||||
|
*/
|
||||||
|
stream(stream) {
|
||||||
|
return {
|
||||||
|
send: (action) => {
|
||||||
|
const msg = {
|
||||||
|
stream,
|
||||||
|
payload: action
|
||||||
|
}
|
||||||
|
this._socket.send(JSON.stringify(msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
137
js_client/tests/websocketbridge.test.js
Normal file
137
js_client/tests/websocketbridge.test.js
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
import { WebSocket, Server } from 'mock-socket';
|
||||||
|
import { WebSocketBridge } from '../src/';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
describe('WebSocketBridge', () => {
|
||||||
|
const mockServer = new Server('ws://localhost');
|
||||||
|
const serverReceivedMessage = jest.fn();
|
||||||
|
mockServer.on('message', serverReceivedMessage);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
serverReceivedMessage.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Connects', () => {
|
||||||
|
const webSocketBridge = new WebSocketBridge();
|
||||||
|
webSocketBridge.connect('ws://localhost');
|
||||||
|
});
|
||||||
|
it('Processes messages', () => {
|
||||||
|
const webSocketBridge = new WebSocketBridge();
|
||||||
|
const myMock = jest.fn();
|
||||||
|
|
||||||
|
webSocketBridge.connect('ws://localhost');
|
||||||
|
webSocketBridge.listen(myMock);
|
||||||
|
|
||||||
|
mockServer.send('{"type": "test", "payload": "message 1"}');
|
||||||
|
mockServer.send('{"type": "test", "payload": "message 2"}');
|
||||||
|
|
||||||
|
expect(myMock.mock.calls.length).toBe(2);
|
||||||
|
expect(myMock.mock.calls[0][0]).toEqual({"type": "test", "payload": "message 1"});
|
||||||
|
expect(myMock.mock.calls[0][1]).toBe(null);
|
||||||
|
});
|
||||||
|
it('Ignores multiplexed messages for unregistered streams', () => {
|
||||||
|
const webSocketBridge = new WebSocketBridge();
|
||||||
|
const myMock = jest.fn();
|
||||||
|
|
||||||
|
webSocketBridge.connect('ws://localhost');
|
||||||
|
webSocketBridge.listen(myMock);
|
||||||
|
|
||||||
|
mockServer.send('{"stream": "stream1", "payload": {"type": "test", "payload": "message 1"}}');
|
||||||
|
expect(myMock.mock.calls.length).toBe(0);
|
||||||
|
|
||||||
|
});
|
||||||
|
it('Demultiplexes messages only when they have a stream', () => {
|
||||||
|
const webSocketBridge = new WebSocketBridge();
|
||||||
|
const myMock = jest.fn();
|
||||||
|
const myMock2 = jest.fn();
|
||||||
|
const myMock3 = jest.fn();
|
||||||
|
|
||||||
|
webSocketBridge.connect('ws://localhost');
|
||||||
|
webSocketBridge.listen(myMock);
|
||||||
|
webSocketBridge.demultiplex('stream1', myMock2);
|
||||||
|
webSocketBridge.demultiplex('stream2', myMock3);
|
||||||
|
|
||||||
|
mockServer.send('{"type": "test", "payload": "message 1"}');
|
||||||
|
expect(myMock.mock.calls.length).toBe(1);
|
||||||
|
expect(myMock2.mock.calls.length).toBe(0);
|
||||||
|
expect(myMock3.mock.calls.length).toBe(0);
|
||||||
|
|
||||||
|
mockServer.send('{"stream": "stream1", "payload": {"type": "test", "payload": "message 1"}}');
|
||||||
|
|
||||||
|
expect(myMock.mock.calls.length).toBe(1);
|
||||||
|
expect(myMock2.mock.calls.length).toBe(1);
|
||||||
|
expect(myMock3.mock.calls.length).toBe(0);
|
||||||
|
|
||||||
|
expect(myMock2.mock.calls[0][0]).toEqual({"type": "test", "payload": "message 1"});
|
||||||
|
expect(myMock2.mock.calls[0][1]).toBe("stream1");
|
||||||
|
|
||||||
|
mockServer.send('{"stream": "stream2", "payload": {"type": "test", "payload": "message 2"}}');
|
||||||
|
|
||||||
|
expect(myMock.mock.calls.length).toBe(1);
|
||||||
|
expect(myMock2.mock.calls.length).toBe(1);
|
||||||
|
expect(myMock3.mock.calls.length).toBe(1);
|
||||||
|
|
||||||
|
expect(myMock3.mock.calls[0][0]).toEqual({"type": "test", "payload": "message 2"});
|
||||||
|
expect(myMock3.mock.calls[0][1]).toBe("stream2");
|
||||||
|
});
|
||||||
|
it('Demultiplexes messages', () => {
|
||||||
|
const webSocketBridge = new WebSocketBridge();
|
||||||
|
const myMock = jest.fn();
|
||||||
|
const myMock2 = jest.fn();
|
||||||
|
|
||||||
|
webSocketBridge.connect('ws://localhost');
|
||||||
|
webSocketBridge.listen();
|
||||||
|
|
||||||
|
webSocketBridge.demultiplex('stream1', myMock);
|
||||||
|
webSocketBridge.demultiplex('stream2', myMock2);
|
||||||
|
|
||||||
|
mockServer.send('{"type": "test", "payload": "message 1"}');
|
||||||
|
mockServer.send('{"type": "test", "payload": "message 2"}');
|
||||||
|
|
||||||
|
expect(myMock.mock.calls.length).toBe(0);
|
||||||
|
expect(myMock2.mock.calls.length).toBe(0);
|
||||||
|
|
||||||
|
mockServer.send('{"stream": "stream1", "payload": {"type": "test", "payload": "message 1"}}');
|
||||||
|
|
||||||
|
expect(myMock.mock.calls.length).toBe(1);
|
||||||
|
|
||||||
|
expect(myMock2.mock.calls.length).toBe(0);
|
||||||
|
|
||||||
|
expect(myMock.mock.calls[0][0]).toEqual({"type": "test", "payload": "message 1"});
|
||||||
|
expect(myMock.mock.calls[0][1]).toBe("stream1");
|
||||||
|
|
||||||
|
mockServer.send('{"stream": "stream2", "payload": {"type": "test", "payload": "message 2"}}');
|
||||||
|
|
||||||
|
expect(myMock.mock.calls.length).toBe(1);
|
||||||
|
expect(myMock2.mock.calls.length).toBe(1);
|
||||||
|
|
||||||
|
|
||||||
|
expect(myMock2.mock.calls[0][0]).toEqual({"type": "test", "payload": "message 2"});
|
||||||
|
expect(myMock2.mock.calls[0][1]).toBe("stream2");
|
||||||
|
|
||||||
|
});
|
||||||
|
it('Sends messages', () => {
|
||||||
|
const webSocketBridge = new WebSocketBridge();
|
||||||
|
|
||||||
|
webSocketBridge.connect('ws://localhost');
|
||||||
|
webSocketBridge.send({"type": "test", "payload": "message 1"});
|
||||||
|
|
||||||
|
expect(serverReceivedMessage.mock.calls.length).toBe(1);
|
||||||
|
expect(serverReceivedMessage.mock.calls[0][0]).toEqual(JSON.stringify({"type": "test", "payload": "message 1"}));
|
||||||
|
});
|
||||||
|
it('Multiplexes messages', () => {
|
||||||
|
const webSocketBridge = new WebSocketBridge();
|
||||||
|
|
||||||
|
webSocketBridge.connect('ws://localhost');
|
||||||
|
webSocketBridge.stream('stream1').send({"type": "test", "payload": "message 1"});
|
||||||
|
|
||||||
|
expect(serverReceivedMessage.mock.calls.length).toBe(1);
|
||||||
|
expect(serverReceivedMessage.mock.calls[0][0]).toEqual(JSON.stringify({
|
||||||
|
"stream": "stream1",
|
||||||
|
"payload": {
|
||||||
|
"type": "test", "payload": "message 1",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user