Repository: mondora/ddp.js
Branch: master
Commit: 70055210083f
Files: 30
Total size: 62.0 KB
Directory structure:
gitextract_ltd_5_p8/
├── .babelrc
├── .eslintrc
├── .gitignore
├── .npmignore
├── .npmrc
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── src/
│ ├── ddp.js
│ ├── queue.js
│ ├── socket.js
│ └── utils.js
└── test/
├── e2e/
│ ├── connection.js
│ ├── methods.js
│ └── subscriptions.js
├── server/
│ ├── .meteor/
│ │ ├── .finished-upgraders
│ │ ├── .gitignore
│ │ ├── .id
│ │ ├── packages
│ │ ├── platforms
│ │ ├── release
│ │ └── versions
│ ├── methods.js
│ └── publications.js
└── unit/
├── ddp.js
├── queue.js
├── socket.js
└── utils.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": ["es2015", "stage-0"],
"env": {
"test": {
"plugins": ["istanbul"]
}
}
}
================================================
FILE: .eslintrc
================================================
{
"extends": "eslint:recommended",
"parser": "babel-eslint",
"env": {
"browser": true,
"mocha": true,
"node": true,
"meteor": true
},
"rules": {
"brace-style": [2, "1tbs"],
"comma-spacing": [2, {"before": false, "after": true}],
"computed-property-spacing": [2, "never"],
"indent": [2, 4],
"linebreak-style": [2, "unix"],
"new-cap": [2, {"capIsNew": false}],
"no-console": [0],
"no-multi-spaces": [0],
"no-underscore-dangle": [0],
"object-curly-spacing": [2, "never"],
"one-var": [2, "never"],
"quotes": [2, "double"],
"semi": [2, "always"],
"keyword-spacing": [2],
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, "always"],
"space-in-parens": [2, "never"],
"strict": [2, "never"]
}
}
================================================
FILE: .gitignore
================================================
node_modules/
npm-debug.log
coverage/
.nyc_output
lib/
.DS_Store
================================================
FILE: .npmignore
================================================
node_modules/
npm-debug.log
coverage/
================================================
FILE: .npmrc
================================================
package-lock=false
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- 8
# before_install:
# - curl https://install.meteor.com | /bin/sh
# before_script:
# - npm run start-meteor & sleep 30
deploy:
provider: npm
email: npm-bot@mondora.com
api_key: $NPM_TOKEN
on:
tags: true
skip_cleanup: true
script:
- npm run lint
- npm run coverage
- npm run coveralls
# - npm run e2e-test
================================================
FILE: CHANGELOG.md
================================================
## 2.2.1 (October 26, 2017)
PR #38: update dependencies
## 2.2.0 (July 2, 2016)
PR #29: add possibility to specify a subscription id.
## 2.1.0 (February 23, 2016)
Internal API change: made `Socket.emit` synchronous.
## 2.0.1 (February 14, 2016)
Fixed npm distribution (`lib/` was not published being in `.gitignore`).
## 2.0.0 (February 14, 2016)
### Breaking changes
* Distribute as individual modules in `lib` instead of bundle in `dist`. Should
not break node consumers. Could break browserify and webpack consumers.
Certainly breaks bower consumers (bower support has been removed)
### New features
* Added method to disconnect
* Added options to control auto-connect and auto-reconnect behaviour. As it
turns out they could indeed be useful, for instance when one wants to simulate
a connection scenario (e.g. in stress tests) and needs to have fine-grained
control on the lifecycle of the connection.
## 1.1.0 (July 11, 2015)
Moved the code to use ES6. In the process, I also refactored it a bit to use
less "exotic" patterns, but there _should be_ no breaking changes to the public
API.
Two enhancements:
1. a `status` property (`connected` / `disconnected`) is now available on the
instance
1. it's now possible to call methods `sub`, `unsub`, and `method` right after
creating the instance. Calls are queued and performed after the `connected`
event
## 1.0.0 (January 11, 2015)
The library has been rewritten from scratch and its scope somewhat reduced. The
purpose of the rewrite, other than simplification, was to implement better
under-the-hood APIs to allow more flexibility.
The biggest change is that the library no longer handles method and
subscription calls. I.e., it doesn't take anymore callbacks to the `method` and
`sub` methods. Rather it returns the `id` of those calls, and lets the
consumer handle the `result`, `updated`, `ready`, `nosub` events related to
those calls. My plan is to bake this functionality directly into Asteroid,
which sometimes need to have lower level access to those events.
Some options have been removed, namely `do_not_autoconnect`,
`do_not_autoreconnect` and `socket_intercept_function`. The functionalities
provided by the first two options can be recreated, but it requires meddling
with the library internals (one has to re-define the `_init` method). I figured
this wouldn't be a problem since I've never found a use case for them. The
third functionality - i.e. intercepting the socket `send` method and doing
something with the message that has been sent - is easily recreated by
listening to the `message:in`, `message:out` private events of the `_socket`
property of a DDP instance. Other private events are available on the property,
making it easier to monitor and gather metrics about the WebSocket.
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2014-2016 mondora <open@mondora.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
[](https://badge.fury.io/js/ddp.js)
[](https://travis-ci.org/mondora/ddp.js)
[](https://coveralls.io/r/mondora/ddp.js?branch=master)
[](https://david-dm.org/mondora/ddp.js)
[](https://david-dm.org/mondora/ddp.js#info=devDependencies)
# ddp.js
A javascript isomorphic/universal ddp client.
> ## Warning
> `ddp.js@^2.0.0` is only distributed as an `npm` module instead of an UMD
> bundle. Also, `bower` has been removed as a method of distribution. If you
> need an UMD bundle or `bower` support, I'm open for suggestions to add back
> those methods of distribution without polluting this repo.
## What is it for?
The purpose of this library is:
- to set up and maintain a ddp connection with a ddp server, freeing the
developer from having to do it on their own
- to give the developer a clear, consistent API to communicate with the ddp
server
## Install
To install ddp.js using `npm`:
npm install ddp.js
or using `yarn`:
yarn add ddp.js
## Example usage
```js
const DDP = require("ddp.js");
const options = {
endpoint: "ws://localhost:3000/websocket",
SocketConstructor: WebSocket
};
const ddp = new DDP(options);
ddp.on("connected", () => {
console.log("Connected");
});
const subId = ddp.sub("mySubscription");
ddp.on("ready", message => {
if (message.subs.includes(subId)) {
console.log("mySubscription ready");
}
});
ddp.on("added", message => {
console.log(message.collection);
});
const myLoginParams = {
user: {
email: "user@example.com"
},
password: "hunter2"
};
const methodId = ddp.method("login", [myLoginParams]);
ddp.on("result", message => {
if (message.id === methodId && !message.error) {
console.log("Logged in!");
}
});
```
## Developing
After cloning the repository, install `npm` dependencies with `npm install`.
Run `npm test` to run unit tests, or `npm run dev` to have `mocha` re-run your
tests when source or test files change.
To run e2e tests, first [install meteor](https://www.meteor.com/install). Then,
start the meteor server with `npm run start-meteor`. Finally, run
`npm run e2e-test` to run the e2e test suite, or `npm run e2e-dev` to have
`mocha` re-run the suite when source or test files change.
## Public API
### new DDP(options)
Creates a new DDP instance. After being constructed, the instance will
establish a connection with the DDP server and will try to maintain it open.
#### Arguments
- `options` **object** *required*
Available options are:
- `endpoint` **string** *required*: the location of the websocket server. Its
format depends on the type of socket you are using.
- `SocketConstructor` **function** *required*: the constructor function that
will be used to construct the socket. Meteor (currently the only DDP server
available) supports websockets and SockJS sockets. So, practically speaking,
this means that on the browser you can use either the browser's native
WebSocket constructor or the SockJS constructor provided by the SockJS
library. On the server you can use whichever library implements the
websocket protocol (e.g. faye-websocket).
- `autoConnect` **boolean** *optional* [default: `true`]: whether to establish
the connection to the server upon instantiation. When `false`, one can
manually establish the connection with the `connect` method.
- `autoReconnect` **boolean** *optional* [default: `true`]: whether to try to
reconnect to the server when the socket connection closes, unless the closing
was initiated by a call to the `disconnect` method.
- `reconnectInterval` **number** *optional* [default: 10000]: the interval in ms
between reconnection attempts.
#### Returns
A new DDP instance, which is also an `EventEmitter` instance.
---
### DDP.method(name, params)
Calls a remote method.
#### Arguments
- `name` **string** *required*: name of the method to call.
- `params` **array** *required*: array of parameters to pass to the remote
method. Pass an empty array if you do not wish to pass any parameters.
#### Returns
The unique `id` (string) corresponding to the method call.
#### Example usage
Server code:
```js
Meteor.methods({
myMethod (param_0, param_1, param_2) {
/* ... */
}
});
```
Client code:
```js
const methodCallId = ddp.method("myMethod", [param_0, param_1, param_2]);
```
---
### DDP.sub(name, params)
Subscribes to a server publication.
#### Arguments
- `name` **string** *required*: name of the server publication.
- `params` **array** *required*: array of parameters to pass to the server
publish function. Pass an empty array if you do not wish to pass any
parameters.
#### Returns
The unique `id` (string) corresponding to the subscription call.
#### Example usage
Server code:
```js
Meteor.publish("myPublication", (param_0, param_1, param_2) {
/* ... */
});
```
Client code:
```js
const subscriptionId = ddp.sub("myPublication", [param_0, param_1, param_2]);
```
---
### DDP.unsub(id)
Unsubscribes to a previously-subscribed server publication.
#### Arguments
- `id` **string** *required*: id of the subscription.
#### Returns
The `id` corresponding to the subscription call (not of much use, but I return
it for consistency).
---
### DDP.connect()
Connects to the ddp server. The method is called automatically by the class
constructor if the `autoConnect` option is set to `true` (default behaviour).
So there generally should be no need for the developer to call the method
themselves.
#### Arguments
None
#### Returns
None
---
### DDP.disconnect()
Disconnects from the ddp server by closing the `WebSocket` connection. You can
listen on the `disconnected` event to be notified of the disconnection.
#### Arguments
None
#### Returns
None
## Public events
### Connection events
- `connected`: emitted with no arguments when the DDP connection is
established.
- `disconnected`: emitted with no arguments when the DDP connection drops.
### Subscription events
All the following events are emitted with one argument, the parsed DDP message.
Further details can be found [on the DDP spec
page](https://github.com/meteor/meteor/blob/devel/packages/ddp/DDP.md).
- `ready`
- `nosub`
- `added`
- `changed`
- `removed`
### Method events
All the following events are emitted with one argument, the parsed DDP message.
Further details can be found [on the DDP spec
page](https://github.com/meteor/meteor/blob/devel/packages/ddp/DDP.md).
- `result`
- `updated`
================================================
FILE: package.json
================================================
{
"name": "ddp.js",
"version": "2.2.1",
"description": "ddp javascript client",
"main": "lib/ddp.js",
"scripts": {
"build": "babel src --out-dir lib",
"clean": "rimraf lib coverage",
"coverage": "nyc --require babel-register --reporter=lcov --include src --all npm test",
"coveralls": "cat ./coverage/lcov.info | coveralls",
"dev": "npm test -- --watch",
"lint": "eslint src test",
"prepare": "npm run clean && npm run build",
"test": "mocha --require babel-register --recursive test/unit",
"start-meteor": "cd test/server/ && meteor",
"e2e-test": "mocha --require babel-register --recursive test/e2e",
"e2e-dev": "npm run e2e-test -- --watch"
},
"repository": {
"type": "git",
"url": "https://github.com/mondora/ddp.js"
},
"keywords": [
"ddp",
"meteor",
"asteroid"
],
"author": "Paolo Scanferla <paolo.scanferla@mondora.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/mondora/ddp.js/issues"
},
"homepage": "https://github.com/mondora/ddp.js",
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-eslint": "^8.0.1",
"babel-plugin-istanbul": "^4.1.5",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"babel-register": "^6.26.0",
"chai": "^4.1.2",
"coveralls": "^3.0.0",
"eslint": "^4.9.0",
"faye-websocket": "^0.11.1",
"mocha": "^4.0.1",
"nyc": "^11.2.1",
"sinon": "^4.0.2",
"sinon-chai": "^2.14.0"
},
"dependencies": {
"wolfy87-eventemitter": "^5.2.3"
}
}
================================================
FILE: src/ddp.js
================================================
import EventEmitter from "wolfy87-eventemitter";
import Queue from "./queue";
import Socket from "./socket";
import {contains, uniqueId} from "./utils";
const DDP_VERSION = "1";
const PUBLIC_EVENTS = [
// Subscription messages
"ready", "nosub", "added", "changed", "removed",
// Method messages
"result", "updated",
// Error messages
"error"
];
const DEFAULT_RECONNECT_INTERVAL = 10000;
export default class DDP extends EventEmitter {
emit () {
setTimeout(super.emit.bind(this, ...arguments), 0);
}
constructor (options) {
super();
this.status = "disconnected";
// Default `autoConnect` and `autoReconnect` to true
this.autoConnect = (options.autoConnect !== false);
this.autoReconnect = (options.autoReconnect !== false);
this.reconnectInterval = options.reconnectInterval || DEFAULT_RECONNECT_INTERVAL;
this.messageQueue = new Queue(message => {
if (this.status === "connected") {
this.socket.send(message);
return true;
} else {
return false;
}
});
this.socket = new Socket(options.SocketConstructor, options.endpoint);
this.socket.on("open", () => {
// When the socket opens, send the `connect` message
// to establish the DDP connection
this.socket.send({
msg: "connect",
version: DDP_VERSION,
support: [DDP_VERSION]
});
});
this.socket.on("close", () => {
this.status = "disconnected";
this.messageQueue.empty();
this.emit("disconnected");
if (this.autoReconnect) {
// Schedule a reconnection
setTimeout(
this.socket.open.bind(this.socket),
this.reconnectInterval
);
}
});
this.socket.on("message:in", message => {
if (message.msg === "connected") {
this.status = "connected";
this.messageQueue.process();
this.emit("connected");
} else if (message.msg === "ping") {
// Reply with a `pong` message to prevent the server from
// closing the connection
this.socket.send({msg: "pong", id: message.id});
} else if (contains(PUBLIC_EVENTS, message.msg)) {
this.emit(message.msg, message);
}
});
if (this.autoConnect) {
this.connect();
}
}
connect () {
this.socket.open();
}
disconnect () {
/*
* If `disconnect` is called, the caller likely doesn't want the
* the instance to try to auto-reconnect. Therefore we set the
* `autoReconnect` flag to false.
*/
this.autoReconnect = false;
this.socket.close();
}
method (name, params) {
const id = uniqueId();
this.messageQueue.push({
msg: "method",
id: id,
method: name,
params: params
});
return id;
}
sub (name, params, id = null) {
id || (id = uniqueId());
this.messageQueue.push({
msg: "sub",
id: id,
name: name,
params: params
});
return id;
}
unsub (id) {
this.messageQueue.push({
msg: "unsub",
id: id
});
return id;
}
}
================================================
FILE: src/queue.js
================================================
export default class Queue {
/*
* As the name implies, `consumer` is the (sole) consumer of the queue.
* It gets called with each element of the queue and its return value
* serves as a ack, determining whether the element is removed or not from
* the queue, allowing then subsequent elements to be processed.
*/
constructor (consumer) {
this.consumer = consumer;
this.queue = [];
}
push (element) {
this.queue.push(element);
this.process();
}
process () {
if (this.queue.length !== 0) {
const ack = this.consumer(this.queue[0]);
if (ack) {
this.queue.shift();
this.process();
}
}
}
empty () {
this.queue = [];
}
}
================================================
FILE: src/socket.js
================================================
import EventEmitter from "wolfy87-eventemitter";
export default class Socket extends EventEmitter {
constructor (SocketConstructor, endpoint) {
super();
this.SocketConstructor = SocketConstructor;
this.endpoint = endpoint;
this.rawSocket = null;
}
send (object) {
const message = JSON.stringify(object);
this.rawSocket.send(message);
// Emit a copy of the object, as the listener might mutate it.
this.emit("message:out", JSON.parse(message));
}
open () {
/*
* Makes `open` a no-op if there's already a `rawSocket`. This avoids
* memory / socket leaks if `open` is called twice (e.g. by a user
* calling `ddp.connect` twice) without properly disposing of the
* socket connection. `rawSocket` gets automatically set to `null` only
* when it goes into a closed or error state. This way `rawSocket` is
* disposed of correctly: the socket connection is closed, and the
* object can be garbage collected.
*/
if (this.rawSocket) {
return;
}
this.rawSocket = new this.SocketConstructor(this.endpoint);
/*
* Calls to `onopen` and `onclose` directly trigger the `open` and
* `close` events on the `Socket` instance.
*/
this.rawSocket.onopen = () => this.emit("open");
this.rawSocket.onclose = () => {
this.rawSocket = null;
this.emit("close");
};
/*
* Calls to `onerror` trigger the `close` event on the `Socket`
* instance, and cause the `rawSocket` object to be disposed of.
* Since it's not clear what conditions could cause the error and if
* it's possible to recover from it, we prefer to always close the
* connection (if it isn't already) and dispose of the socket object.
*/
this.rawSocket.onerror = () => {
// It's not clear what the socket lifecycle is when errors occurr.
// Hence, to avoid the `close` event to be emitted twice, before
// manually closing the socket we de-register the `onclose`
// callback.
delete this.rawSocket.onclose;
// Safe to perform even if the socket is already closed
this.rawSocket.close();
this.rawSocket = null;
this.emit("close");
};
/*
* Calls to `onmessage` trigger a `message:in` event on the `Socket`
* instance only once the message (first parameter to `onmessage`) has
* been successfully parsed into a javascript object.
*/
this.rawSocket.onmessage = message => {
var object;
try {
object = JSON.parse(message.data);
} catch (ignore) {
// Simply ignore the malformed message and return
return;
}
// Outside the try-catch block as it must only catch JSON parsing
// errors, not errors that may occur inside a "message:in" event
// handler
this.emit("message:in", object);
};
}
close () {
/*
* Avoid throwing an error if `rawSocket === null`
*/
if (this.rawSocket) {
this.rawSocket.close();
}
}
}
================================================
FILE: src/utils.js
================================================
var i = 0;
export function uniqueId () {
return (i++).toString();
}
export function contains (array, element) {
return array.indexOf(element) !== -1;
}
================================================
FILE: test/e2e/connection.js
================================================
import chai, {expect} from "chai";
import {Client} from "faye-websocket";
import sinon from "sinon";
import sinonChai from "sinon-chai";
import DDP from "../../src/ddp";
chai.use(sinonChai);
const options = {
endpoint: "ws://localhost:3000/websocket",
SocketConstructor: Client
};
describe("connection", () => {
var ddp = null;
afterEach(done => {
if (ddp) {
ddp.on("disconnected", () => done());
ddp.disconnect();
ddp = null;
} else {
done();
}
});
describe("connecting", () => {
it("a connection is established on instantiation unless `autoConnect === false`", done => {
/*
* The test suceeds when the `connected` event is fired, signaling
* the establishment of the connection.
* If the event is never fired, the test times out and fails.
*/
ddp = new DDP(options);
ddp.on("connected", () => {
done();
});
});
it("a connection is not established on instantiation when `autoConnect === false`", done => {
/*
* The test succeeds if, 1s after the creation of the DDP instance,
* a `connected` event has not been fired.
*/
const ddp = new DDP({
...options,
autoConnect: false
});
const connectedHandler = sinon.spy();
ddp.on("connected", connectedHandler);
setTimeout(() => {
try {
expect(connectedHandler).to.have.callCount(0);
done();
} catch (e) {
done(e);
}
}, 1000);
});
it("a connection can be established manually when `autoConnect === false`", done => {
/*
* The test suceeds when the `connected` event is fired, signaling
* the establishment of the connection.
* If the event is never fired, the test times out and fails.
*/
ddp = new DDP({
...options,
autoConnect: false
});
ddp.connect();
ddp.on("connected", () => {
done();
});
});
it("manually connecting several times doesn't causes multiple simultaneous connections [CASE: `autoConnect === true`]", done => {
/*
* The test suceeds if 1s after having called `connect` several
* times only one connection has been established.
*/
ddp = new DDP(options);
const connectedSpy = sinon.spy();
ddp.connect();
ddp.connect();
ddp.connect();
ddp.connect();
ddp.on("connected", connectedSpy);
setTimeout(() => {
try {
expect(connectedSpy).to.have.callCount(1);
done();
} catch (e) {
done(e);
}
}, 1000);
});
it("manually connecting several times doesn't causes multiple simultaneous connections [CASE: `autoConnect === false`]", done => {
/*
* The test suceeds if 1s after having called `connect` several
* times only one connection has been established.
*/
ddp = new DDP({
...options,
autoConnect: false
});
const connectedSpy = sinon.spy();
ddp.connect();
ddp.connect();
ddp.connect();
ddp.connect();
ddp.on("connected", connectedSpy);
setTimeout(() => {
try {
expect(connectedSpy).to.have.callCount(1);
done();
} catch (e) {
done(e);
}
}, 1000);
});
});
describe("disconnecting", () => {
it("the connection is closed when calling `disconnect`", done => {
/*
* The test suceeds when the `disconnected` event is fired,
* signaling the termination of the connection.
* If the event is never fired, the test times out and fails.
*/
const ddp = new DDP(options);
ddp.on("connected", () => {
ddp.disconnect();
});
ddp.on("disconnected", () => {
done();
});
});
it("calling `disconnect` several times causes no issues", done => {
/*
* The test suceeds if:
* - calling `disconnect` several times doesn't throw, both before
* and after the `disconnected` event has been received
* - one and only one `disconnected` event is fired (check after
* 1s)
*/
const ddp = new DDP(options);
const disconnectSpy = sinon.spy(() => {
try {
ddp.disconnect();
ddp.disconnect();
ddp.disconnect();
ddp.disconnect();
} catch (e) {
done(e);
}
});
ddp.on("connected", () => {
try {
ddp.disconnect();
ddp.disconnect();
ddp.disconnect();
ddp.disconnect();
} catch (e) {
done(e);
}
});
ddp.on("disconnected", disconnectSpy);
setTimeout(() => {
try {
expect(disconnectSpy).to.have.callCount(1);
done();
} catch (e) {
done(e);
}
}, 1000);
});
it("the connection is closed when calling `disconnect` and it's not re-established", done => {
/*
* The test suceeds if, 1s after the `disconnected` event has been
* fired, there hasn't been any reconnection.
*/
const ddp = new DDP({
...options,
reconnectInterval: 10
});
const disconnectOnConnection = sinon.spy(() => {
ddp.disconnect();
});
ddp.on("connected", disconnectOnConnection);
ddp.on("disconnected", () => {
setTimeout(() => {
try {
expect(disconnectOnConnection).to.have.callCount(1);
done();
} catch (e) {
done(e);
}
}, 1000);
});
});
it("the connection is closed and re-established when the server closes the connection, unless `autoReconnect === true`", done => {
/*
* The test suceeds when the `connect` event is fired a second time
* after the client gets disconnected from the server (occurrence
* marked by the `disconnected` event).
* If the event is never fired a second time, the test times out
* and fails.
*/
ddp = new DDP({
...options,
reconnectInterval: 10
});
var callCount = 0;
ddp.on("connected", () => {
callCount += 1;
if (callCount === 1) {
ddp.method("disconnectMe", []);
}
if (callCount === 2) {
done();
}
});
});
it("the connection is closed and _not_ re-established when the server closes the connection and `autoReconnect === false`", done => {
/*
* The test suceeds if, 1s after the `disconnected` event has been
* fired, there hasn't been any reconnection.
*/
const ddp = new DDP({
...options,
reconnectInterval: 10,
autoReconnect: false
});
const disconnectMe = sinon.spy(() => {
ddp.method("disconnectMe", []);
});
ddp.on("connected", disconnectMe);
ddp.on("disconnected", () => {
setTimeout(() => {
try {
expect(disconnectMe).to.have.callCount(1);
done();
} catch (e) {
done(e);
}
}, 1000);
});
});
describe("ddp.js issue #22", () => {
/*
* We need to test that no `uncaughtException`-s are raised. Since
* mocha adds a listener to the `uncaughtException` event which
* causes tests to fail in an unexpected manner, we first remove
* that listener, and then we restore it. Since it's not clear
* _what_ mocha does with that listener, we try to lower the
* meddling impact by doing all of our work inside the `it` block.
*/
it("no issues when sending messages while disconnected / while disconnecting", done => {
/*
* The test suceeds if, 100ms after the `disconnected` event
* has been fired, there haven't been any uncaught exceptions.
*/
const catcher = sinon.spy();
const listeners = process.listeners("uncaughtException");
process.removeAllListeners("uncaughtException");
process.on("uncaughtException", catcher);
const ddp = new DDP({
...options,
autoReconnect: false
});
ddp.on("connected", () => {
ddp.disconnect();
});
const interval = setInterval(() => {
ddp.method("echo", []);
}, 1);
ddp.on("disconnected", () => {
setTimeout(runAssertions, 100);
});
const runAssertions = () => {
clearInterval(interval);
process.removeAllListeners("uncaughtException");
listeners.forEach(listener => {
process.on("uncaughtException", listener);
});
try {
expect(catcher).to.have.callCount(0);
done();
} catch (e) {
done(e);
}
};
});
});
});
});
================================================
FILE: test/e2e/methods.js
================================================
import {expect} from "chai";
import {Client} from "faye-websocket";
import DDP from "../../src/ddp";
describe("methods", () => {
describe("calling a method", () => {
const ddp = new DDP({
endpoint: "ws://localhost:3000/websocket",
SocketConstructor: Client
});
after(done => {
ddp.on("disconnected", () => done());
ddp.disconnect();
});
it("invokes the method on the server and gets a `result` message with the response", done => {
/*
* The test suceeds when the `result` message for the echo method
* call is received, and assertions all succeed.
* If the `result` message is never received, the test times out
* and fails. Naturally, the test also fails if assertions fail.
*/
const methodId = ddp.method("echo", [0, 1, 2, 3, 4]);
ddp.on("result", message => {
if (message.id !== methodId || message.error) {
return;
}
try {
expect(message.result).to.deep.equal([0, 1, 2, 3, 4]);
done();
} catch (e) {
done(e);
}
});
});
it("gets an `updated` message", done => {
/*
* The test suceeds when the `updated` message for the echo method
* call is received.
* If the `updated` message is never received, the test times out
* and fails.
*/
const methodId = ddp.method("echo", [0, 1, 2, 3, 4]);
ddp.on("updated", message => {
if (message.methods.indexOf(methodId) !== -1) {
done();
}
});
});
});
});
================================================
FILE: test/e2e/subscriptions.js
================================================
import {expect} from "chai";
import {Client} from "faye-websocket";
import DDP from "../../src/ddp";
describe("subscriptions", () => {
describe("subscribing to a publication", () => {
const ddp = new DDP({
endpoint: "ws://localhost:3000/websocket",
SocketConstructor: Client
});
after(done => {
ddp.on("disconnected", () => done());
ddp.disconnect();
});
it("sends a sub call to the server and receives server-sent scubscription events", done => {
/*
* The test suceeds when the `ready` message for the echo
* subscription is received, and assertions all succeed.
* If the `ready` message is never received, the test times out
* and fails. Naturally, the test also fails if assertions fail.
*/
const subId = ddp.sub("echo", [0, 1, 2, 3, 4]);
const collections = {};
ddp.on("added", message => {
collections[message.collection] = {
...collections[message.collection],
[message.id]: {
_id: message.id,
...message.fields
}
};
});
ddp.on("ready", message => {
if (message.subs.indexOf(subId) === -1) {
return;
}
try {
expect(collections).to.deep.equal({
echoParameters: {
"id_0": {_id: "id_0", param: 0},
"id_1": {_id: "id_1", param: 1},
"id_2": {_id: "id_2", param: 2},
"id_3": {_id: "id_3", param: 3},
"id_4": {_id: "id_4", param: 4}
}
});
done();
} catch (e) {
done(e);
}
});
});
});
describe("unsubscribing from a publication", () => {
const ddp = new DDP({
endpoint: "ws://localhost:3000/websocket",
SocketConstructor: Client
});
after(done => {
ddp.on("disconnected", () => done());
ddp.disconnect();
});
it("sends an unsub call to the server and receives a nosub unsubscriptions confirmation event", done => {
/*
* The test suceeds when the `nosub` message for the echo
* subscription is received. If the `nosub` message is never
* received, the test times out and fails.
*/
const subId = ddp.sub("echo", [0, 1, 2, 3, 4]);
ddp.on("ready", message => {
if (message.subs.indexOf(subId) === -1) {
return;
}
ddp.unsub(subId);
});
ddp.on("nosub", message => {
if (message.id !== subId) {
return;
}
done();
});
});
});
describe("getting unsubscribed from a publication", () => {
const ddp = new DDP({
endpoint: "ws://localhost:3000/websocket",
SocketConstructor: Client
});
after(done => {
ddp.on("disconnected", () => done());
ddp.disconnect();
});
it("receives a nosub unsubscriptions event", function (done) {
/*
* The test suceeds when the `nosub` message for the echo
* subscription is received. If the `nosub` message is never
* received, the test times out and fails.
* The server will stop the subscription after about 1s, so there
* is no need to terminate it with an `unsub`. We will however
* increase the test timeout to 3s to account for the delay.
*/
this.timeout(3000);
const subId = ddp.sub("autoTerminating");
var subReady = false;
ddp.on("ready", message => {
if (message.subs.indexOf(subId) !== -1) {
subReady = true;
}
});
ddp.on("nosub", message => {
if (message.id !== subId) {
return;
}
try {
// Ensure the subscription got marked as ready.
expect(subReady).to.equal(true);
done();
} catch (e) {
done(e);
}
});
});
});
});
================================================
FILE: test/server/.meteor/.finished-upgraders
================================================
# This file contains information which helps Meteor properly upgrade your
# app when you run 'meteor update'. You should check it into version control
# with your project.
notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2
1.2.0-standard-minifiers-package
1.2.0-meteor-platform-split
1.2.0-cordova-changes
1.2.0-breaking-changes
1.3.0-split-minifiers-package
1.4.0-remove-old-dev-bundle-link
1.4.1-add-shell-server-package
1.4.3-split-account-service-packages
1.5-add-dynamic-import-package
================================================
FILE: test/server/.meteor/.gitignore
================================================
local
================================================
FILE: test/server/.meteor/.id
================================================
# This file contains a token that is unique to your project.
# Check it into your repository along with the rest of this directory.
# It can be used for purposes such as:
# - ensuring you don't accidentally deploy one app on top of another
# - providing package authors with aggregated statistics
ydh6g2121jjwyqq7agh
================================================
FILE: test/server/.meteor/packages
================================================
# Meteor packages used by this project, one per line.
# Check this file (and the other files in this directory) into your repository.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
meteor-base@1.1.0 # Packages every Meteor app needs to have
mobile-experience@1.0.5 # Packages for a great mobile UX
mongo@1.2.2 # The database Meteor supports right now
blaze-html-templates # Compile .html files into Meteor Blaze views
session@1.1.7 # Client-side reactive dictionary for your app
jquery@1.11.10 # Helpful client-side library
tracker@1.1.3 # Meteor's client-side reactive programming library
es5-shim@4.6.15 # ECMAScript 5 compatibility for older browsers.
standard-minifier-css
standard-minifier-js
shell-server
dynamic-import
================================================
FILE: test/server/.meteor/platforms
================================================
server
browser
================================================
FILE: test/server/.meteor/release
================================================
METEOR@1.5.2.2
================================================
FILE: test/server/.meteor/versions
================================================
allow-deny@1.0.9
autoupdate@1.3.12
babel-compiler@6.20.0
babel-runtime@1.0.1
base64@1.0.10
binary-heap@1.0.10
blaze@2.3.2
blaze-html-templates@1.1.2
blaze-tools@1.0.10
boilerplate-generator@1.2.0
caching-compiler@1.1.9
caching-html-compiler@1.1.2
callback-hook@1.0.10
check@1.2.5
ddp@1.3.1
ddp-client@2.1.3
ddp-common@1.2.9
ddp-server@2.0.2
deps@1.0.12
diff-sequence@1.0.7
dynamic-import@0.1.3
ecmascript@0.8.3
ecmascript-runtime@0.4.1
ecmascript-runtime-client@0.4.3
ecmascript-runtime-server@0.4.1
ejson@1.0.14
es5-shim@4.6.15
geojson-utils@1.0.10
hot-code-push@1.0.4
html-tools@1.0.11
htmljs@1.0.11
http@1.2.12
id-map@1.0.9
jquery@1.11.10
launch-screen@1.1.1
livedata@1.0.18
logging@1.1.19
meteor@1.7.2
meteor-base@1.1.0
minifier-css@1.2.16
minifier-js@2.1.4
minimongo@1.3.3
mobile-experience@1.0.5
mobile-status-bar@1.0.14
modules@0.10.0
modules-runtime@0.8.0
mongo@1.2.2
mongo-dev-server@1.0.1
mongo-id@1.0.6
npm-mongo@2.2.33
observe-sequence@1.0.16
ordered-dict@1.0.9
promise@0.9.0
random@1.0.10
reactive-dict@1.1.9
reactive-var@1.0.11
reload@1.1.11
retry@1.0.9
routepolicy@1.0.12
session@1.1.7
shell-server@0.2.4
spacebars@1.0.15
spacebars-compiler@1.1.3
standard-minifier-css@1.3.5
standard-minifier-js@2.1.2
templating@1.3.2
templating-compiler@1.3.3
templating-runtime@1.3.2
templating-tools@1.1.2
tracker@1.1.3
ui@1.0.13
underscore@1.0.10
url@1.1.0
webapp@1.3.19
webapp-hashing@1.0.9
================================================
FILE: test/server/methods.js
================================================
Meteor.methods({
echo: function () {
return _.toArray(arguments);
},
disconnectMe: function () {
this.connection.close();
}
});
================================================
FILE: test/server/publications.js
================================================
Meteor.publish("echo", function () {
var self = this;
_.each(arguments, function (param, index) {
self.added("echoParameters", "id_" + index, {param: param});
});
self.ready();
});
Meteor.publish("autoTerminating", function () {
var self = this;
self.added("autoTerminating", "id", {});
self.ready();
setTimeout(function () {
self.stop();
}, 1000);
});
================================================
FILE: test/unit/ddp.js
================================================
import chai, {expect} from "chai";
import sinon from "sinon";
import sinonChai from "sinon-chai";
chai.use(sinonChai);
import DDP from "../../src/ddp";
import Socket from "../../src/socket";
class SocketConstructorMock {
send () {}
close () {}
}
const options = {
SocketConstructor: SocketConstructorMock
};
describe("`DDP` class", () => {
describe("`constructor` method", () => {
beforeEach(() => {
sinon.stub(Socket.prototype, "on");
sinon.stub(Socket.prototype, "open");
});
afterEach(() => {
Socket.prototype.on.restore();
Socket.prototype.open.restore();
});
it("instantiates a `Socket`", () => {
const ddp = new DDP(options);
expect(ddp.socket).to.be.an.instanceOf(Socket);
});
it("registers handlers for `socket` events", () => {
const ddp = new DDP(options);
expect(ddp.socket.on).to.have.always.been.calledWithMatch(
sinon.match.string,
sinon.match.func
);
});
it("opens a connection to the server (by calling `socket.open`) unless `options.autoConnect === false`", () => {
const ddp = new DDP(options);
expect(ddp.socket.open).to.have.callCount(1);
});
it("does not open a connection when `options.autoConnect === false`", () => {
const ddp = new DDP({
...options,
autoConnect: false
});
expect(ddp.socket.open).to.have.callCount(0);
});
it("sets the instance `reconnectInterval` to `options.reconnectInterval` if specified", () => {
const ddp = new DDP({
...options,
reconnectInterval: 1,
autoConnect: false
});
expect(ddp.reconnectInterval).to.equal(1);
});
it("sets the instance `reconnectInterval` to a default value if `options.reconnectInterval` is not specified", () => {
const ddp = new DDP({
...options,
autoConnect: false
});
expect(ddp.reconnectInterval).to.equal(10000);
});
});
describe("`method` method", () => {
it("sends a DDP `method` message", () => {
const ddp = new DDP(options);
ddp.messageQueue.push = sinon.spy();
const id = ddp.method("name", ["param"]);
expect(ddp.messageQueue.push).to.have.been.calledWith({
msg: "method",
id: id,
method: "name",
params: ["param"]
});
});
it("returns the method's `id`", () => {
const ddp = new DDP(options);
ddp.messageQueue.push = sinon.spy();
const id = ddp.method("name", ["param"]);
expect(id).to.be.a("string");
});
});
describe("`sub` method", () => {
it("sends a DDP `sub` message", () => {
const ddp = new DDP(options);
ddp.messageQueue.push = sinon.spy();
const id = ddp.sub("name", ["param"]);
expect(ddp.messageQueue.push).to.have.been.calledWith({
msg: "sub",
id: id,
name: "name",
params: ["param"]
});
});
it("returns the sub's `id`", () => {
const ddp = new DDP(options);
ddp.messageQueue.push = sinon.spy();
const id = ddp.sub("name", ["param"]);
expect(id).to.be.a("string");
});
it("generates unique id when not specified", () => {
const ddp = new DDP(options);
var ids = [];
ids.push(ddp.sub("echo", [ 0 ]));
ids.push(ddp.sub("echo", [ 0 ]));
expect(ids[0]).to.be.a("string");
expect(ids[1]).to.be.a("string");
expect(ids[0]).not.to.equal(ids[1]);
});
it("allows manually specifying sub's id", () => {
const ddp = new DDP(options);
const subId = ddp.sub("echo", [ 0 ], "12345");
expect(subId).to.equal("12345");
});
});
describe("`unsub` method", () => {
it("sends a DDP `unsub` message", () => {
const ddp = new DDP(options);
ddp.messageQueue.push = sinon.spy();
const id = ddp.unsub("id");
expect(ddp.messageQueue.push).to.have.been.calledWith({
msg: "unsub",
id: id
});
});
it("returns the sub's `id`", () => {
const ddp = new DDP(options);
ddp.messageQueue.push = sinon.spy();
const id = ddp.unsub("id");
expect(id).to.be.a("string");
expect(id).to.equal("id");
});
});
describe("`connect` method", () => {
beforeEach(() => {
sinon.stub(Socket.prototype, "open");
});
afterEach(() => {
Socket.prototype.open.restore();
});
it("opens the WebSocket connection", () => {
const ddp = new DDP(options);
Socket.prototype.open.reset();
ddp.connect();
expect(ddp.socket.open).to.have.callCount(1);
});
});
describe("`disconnect` method", () => {
beforeEach(() => {
sinon.stub(Socket.prototype, "close");
});
afterEach(() => {
Socket.prototype.close.restore();
});
it("closes the WebSocket connection", () => {
const ddp = new DDP(options);
ddp.disconnect();
expect(ddp.socket.close).to.have.callCount(1);
});
it("sets the `autoReconnect` flag to false", () => {
const ddp = new DDP(options);
ddp.disconnect();
expect(ddp.autoReconnect).to.equal(false);
});
});
describe("`socket` `open` handler", () => {
beforeEach(() => {
sinon.stub(global, "setTimeout").callsFake(fn => fn());
});
afterEach(() => {
global.setTimeout.restore();
});
it("sends the `connect` DDP message", () => {
const ddp = new DDP(options);
ddp.socket.send = sinon.spy();
ddp.socket.emit("open");
expect(ddp.socket.send).to.have.been.calledWith({
msg: "connect",
version: "1",
support: ["1"]
});
});
});
describe("`socket` `close` handler", () => {
beforeEach(() => {
sinon.stub(global, "setTimeout").callsFake(fn => fn());
});
afterEach(() => {
global.setTimeout.restore();
});
it("emits the `disconnected` event", () => {
const ddp = new DDP(options);
ddp.emit = sinon.spy();
ddp.socket.emit("close");
expect(ddp.emit).to.have.been.calledWith("disconnected");
});
it("sets the status to `disconnected`", () => {
const ddp = new DDP(options);
ddp.status = "connected";
ddp.emit = sinon.spy();
ddp.socket.emit("close");
expect(ddp.status).to.equal("disconnected");
});
it("schedules a reconnection unless `options.autoReconnect === false`", () => {
const ddp = new DDP(options);
ddp.socket.open = sinon.spy();
ddp.socket.emit("close");
expect(ddp.socket.open).to.have.callCount(1);
});
it("doesn't schedule a reconnection when `options.autoReconnect === false`", () => {
const ddp = new DDP({
...options,
autoReconnect: false
});
ddp.socket.open = sinon.spy();
ddp.socket.emit("close");
expect(ddp.socket.open).to.have.callCount(0);
});
});
describe("`socket` `message:in` handler", () => {
beforeEach(() => {
sinon.stub(global, "setTimeout").callsFake(fn => fn());
});
afterEach(() => {
global.setTimeout.restore();
});
it("responds to `ping` DDP messages", () => {
const ddp = new DDP(options);
ddp.socket.send = sinon.spy();
ddp.socket.emit("message:in", {
id: "id",
msg: "ping"
});
expect(ddp.socket.send).to.have.been.calledWith({
id: "id",
msg: "pong"
});
});
it("triggers `messageQueue` processing upon connection", () => {
const ddp = new DDP(options);
ddp.emit = sinon.spy();
ddp.messageQueue.process = sinon.spy();
ddp.socket.emit("message:in", {msg: "connected"});
expect(ddp.messageQueue.process).to.have.callCount(1);
});
it("sets the status to `connected` upon connection", () => {
const ddp = new DDP(options);
ddp.emit = sinon.spy();
ddp.socket.emit("message:in", {msg: "connected"});
expect(ddp.status).to.equal("connected");
});
it("emits public DDP messages as events", () => {
const ddp = new DDP(options);
ddp.emit = sinon.spy();
const message = {
id: "id",
msg: "result"
};
ddp.socket.emit("message:in", message);
expect(ddp.emit).to.have.been.calledWith("result", message);
});
it("ignores unknown (or non public) DDP messages", () => {
const ddp = new DDP(options);
ddp.emit = sinon.spy();
const message = {
id: "id",
msg: "not-a-ddp-message"
};
ddp.socket.emit("message:in", message);
expect(ddp.emit).to.have.callCount(0);
});
});
describe("`messageQueue` consumer", () => {
it("acks if `status` is `connected`", () => {
const ddp = new DDP(options);
ddp.status = "connected";
const ack = ddp.messageQueue.consumer({});
expect(ack).to.equal(true);
});
it("doesn't ack if `status` is `disconnected`", () => {
const ddp = new DDP(options);
ddp.status = "disconnected";
const ack = ddp.messageQueue.consumer({});
expect(ack).to.equal(false);
});
});
});
================================================
FILE: test/unit/queue.js
================================================
import chai, {expect} from "chai";
import sinon from "sinon";
import sinonChai from "sinon-chai";
chai.use(sinonChai);
import Queue from "../../src/queue";
describe("`Queue` class", () => {
describe("`push` method", () => {
it("adds an element to the queue", () => {
const q = new Queue();
q.process = sinon.spy();
const element = {};
q.push(element);
expect(q.queue).to.include(element);
});
it("triggers processing", () => {
const q = new Queue();
q.process = sinon.spy();
const element = {};
q.push(element);
expect(q.process).to.have.callCount(1);
});
});
describe("`process` method", () => {
it("calls the consumer on each element of the queue", () => {
const consumer = sinon.spy(() => true);
const q = new Queue(consumer);
q.queue = [0, 1, 2];
q.process();
expect(consumer).to.have.been.calledWith(0);
expect(consumer).to.have.been.calledWith(1);
expect(consumer).to.have.been.calledWith(2);
expect(consumer).to.have.callCount(3);
});
it("removes elements from the queue", () => {
const consumer = sinon.spy(() => true);
const q = new Queue(consumer);
q.queue = [0, 1, 2];
q.process();
expect(q.queue.length).to.equal(0);
});
it("doesn't remove elements from the queue if the consumer doesn't ack", () => {
const consumer = sinon.spy(() => false);
const q = new Queue(consumer);
q.queue = [0, 1, 2];
q.process();
expect(consumer).to.have.been.calledWith(0);
expect(consumer).to.have.callCount(1);
expect(q.queue.length).to.equal(3);
});
});
describe("`empty` method", () => {
it("empties the queue", () => {
const q = new Queue();
q.process = sinon.spy();
const element = {};
q.push(element);
expect(q.queue.length).to.equal(1);
q.empty();
expect(q.queue.length).to.equal(0);
});
});
});
================================================
FILE: test/unit/socket.js
================================================
import chai, {expect} from "chai";
import sinon from "sinon";
import sinonChai from "sinon-chai";
chai.use(sinonChai);
import Socket from "../../src/socket";
class SocketConstructorMock {
close () {}
send () {}
}
describe("`Socket` class", () => {
describe("`send` method", () => {
it("sends a message through the `rawSocket`", () => {
const socket = new Socket();
socket.rawSocket = {
send: sinon.spy()
};
socket.send({});
expect(socket.rawSocket.send).to.have.callCount(1);
});
it("stringifies the object to send", () => {
const socket = new Socket();
socket.rawSocket = {
send: sinon.spy()
};
const object = {
a: "a"
};
const expectedMessage = JSON.stringify(object);
socket.send(object);
expect(socket.rawSocket.send).to.have.been.calledWith(expectedMessage);
});
it("emits a `message:out` event", () => {
const socket = new Socket();
socket.rawSocket = {
send: sinon.spy()
};
socket.emit = sinon.spy();
const object = {
a: "a"
};
socket.send(object);
expect(socket.emit).to.have.been.calledWith("message:out", object);
});
});
describe("`open` method", () => {
it("no-op if `rawSocket` is already defined", () => {
const socket = new Socket(SocketConstructorMock);
const rawSocket = {};
socket.rawSocket = rawSocket;
socket.open();
// Test, for instance, that `rawSocket` has not been replaced.
expect(socket.rawSocket).to.equal(rawSocket);
});
it("instantiates a `SocketConstructor`", () => {
const socket = new Socket(SocketConstructorMock);
socket.open();
expect(socket.rawSocket).to.be.an.instanceOf(SocketConstructorMock);
});
it("registers handlers for `rawSocket` events", () => {
const socket = new Socket(SocketConstructorMock);
socket.open();
expect(socket.rawSocket.onopen).to.be.a("function");
expect(socket.rawSocket.onclose).to.be.a("function");
expect(socket.rawSocket.onerror).to.be.a("function");
expect(socket.rawSocket.onmessage).to.be.a("function");
});
});
describe("`close` method", () => {
it("closes the `rawSocket`", () => {
const socket = new Socket(SocketConstructorMock);
socket.open();
socket.rawSocket.close = sinon.spy();
socket.close();
expect(socket.rawSocket.close).to.have.callCount(1);
});
it("doesn't throw if there's no `rawSocket`", () => {
const socket = new Socket(SocketConstructorMock);
const peacemaker = () => {
socket.close();
};
expect(peacemaker).not.to.throw();
});
});
describe("`rawSocket` `onopen` handler", () => {
it("emits an `open` event", () => {
const socket = new Socket(SocketConstructorMock);
const handler = sinon.spy();
socket.on("open", handler);
socket.open();
socket.rawSocket.onopen();
expect(handler).to.have.callCount(1);
});
});
describe("`rawSocket` `onclose` handler", () => {
it("emits a `close` event", () => {
const socket = new Socket(SocketConstructorMock);
const handler = sinon.spy();
socket.on("close", handler);
socket.open();
socket.rawSocket.onclose();
expect(handler).to.have.callCount(1);
});
it("null-s the `rawSocket` property", () => {
const socket = new Socket(SocketConstructorMock);
socket.open();
socket.rawSocket.onclose();
expect(socket.rawSocket).to.equal(null);
});
});
describe("`rawSocket` `onerror` handler", () => {
it("closes `rawSocket` (by calling `rawSocket.close`)", () => {
const socket = new Socket(SocketConstructorMock);
socket.open();
const rawSocket = socket.rawSocket;
rawSocket.close = sinon.spy();
socket.rawSocket.onerror();
expect(rawSocket.close).to.have.callCount(1);
});
it("de-registers the `rawSocket.onclose` callback", () => {
const socket = new Socket(SocketConstructorMock);
socket.open();
const rawSocket = socket.rawSocket;
expect(rawSocket).to.have.property("onclose");
socket.rawSocket.onerror();
expect(rawSocket).not.to.have.property("onclose");
});
it("emits a `close` event", () => {
const socket = new Socket(SocketConstructorMock);
const handler = sinon.spy();
socket.on("close", handler);
socket.open();
socket.rawSocket.onerror();
expect(handler).to.have.callCount(1);
});
it("null-s the `rawSocket` property", () => {
const socket = new Socket(SocketConstructorMock);
socket.open();
socket.rawSocket.onerror();
expect(socket.rawSocket).to.equal(null);
});
});
describe("`rawSocket` `onmessage` handler", () => {
it("parses message data into an object", () => {
const socket = new Socket(SocketConstructorMock);
sinon.stub(JSON, "parse");
socket.open();
socket.rawSocket.onmessage({data: "message"});
expect(JSON.parse).to.have.been.calledWith("message");
JSON.parse.restore();
});
it("ignores malformed messages", () => {
const socket = new Socket(SocketConstructorMock);
sinon.stub(JSON, "parse").throws();
socket.open();
expect(socket.rawSocket.onmessage).not.to.throw();
JSON.parse.restore();
});
it("emits `message:in` events", () => {
const socket = new Socket(SocketConstructorMock);
const handler = sinon.spy();
socket.on("message:in", handler);
socket.open();
socket.rawSocket.onmessage({data: JSON.stringify({a: "a"})});
expect(handler).to.have.callCount(1);
expect(handler).to.have.been.calledWith({a: "a"});
});
});
});
================================================
FILE: test/unit/utils.js
================================================
import {expect} from "chai";
import {contains, uniqueId} from "../../src/utils";
describe("`utils` object", () => {
describe("`contains` function", () => {
it("returns true if the first parameter contains the second parameter", () => {
const array = ["element"];
const element = "element";
expect(contains(array, element)).to.equal(true);
});
it("returns false if the first parameter doesn't contain the second parameter", () => {
const array = ["element"];
const element = "different-element";
expect(contains(array, element)).to.equal(false);
});
});
describe("`uniqueId` function", () => {
it("should return a different string each time it's called", () => {
const ret1 = uniqueId();
const ret2 = uniqueId();
expect(ret1).not.to.equal(ret2);
});
});
});
gitextract_ltd_5_p8/
├── .babelrc
├── .eslintrc
├── .gitignore
├── .npmignore
├── .npmrc
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── src/
│ ├── ddp.js
│ ├── queue.js
│ ├── socket.js
│ └── utils.js
└── test/
├── e2e/
│ ├── connection.js
│ ├── methods.js
│ └── subscriptions.js
├── server/
│ ├── .meteor/
│ │ ├── .finished-upgraders
│ │ ├── .gitignore
│ │ ├── .id
│ │ ├── packages
│ │ ├── platforms
│ │ ├── release
│ │ └── versions
│ ├── methods.js
│ └── publications.js
└── unit/
├── ddp.js
├── queue.js
├── socket.js
└── utils.js
SYMBOL INDEX (29 symbols across 6 files)
FILE: src/ddp.js
constant DDP_VERSION (line 6) | const DDP_VERSION = "1";
constant PUBLIC_EVENTS (line 7) | const PUBLIC_EVENTS = [
constant DEFAULT_RECONNECT_INTERVAL (line 15) | const DEFAULT_RECONNECT_INTERVAL = 10000;
class DDP (line 17) | class DDP extends EventEmitter {
method emit (line 19) | emit () {
method constructor (line 23) | constructor (options) {
method connect (line 88) | connect () {
method disconnect (line 92) | disconnect () {
method method (line 102) | method (name, params) {
method sub (line 113) | sub (name, params, id = null) {
method unsub (line 124) | unsub (id) {
FILE: src/queue.js
class Queue (line 1) | class Queue {
method constructor (line 10) | constructor (consumer) {
method push (line 15) | push (element) {
method process (line 20) | process () {
method empty (line 30) | empty () {
FILE: src/socket.js
class Socket (line 3) | class Socket extends EventEmitter {
method constructor (line 5) | constructor (SocketConstructor, endpoint) {
method send (line 12) | send (object) {
method open (line 19) | open () {
method close (line 83) | close () {
FILE: src/utils.js
function uniqueId (line 2) | function uniqueId () {
function contains (line 6) | function contains (array, element) {
FILE: test/unit/ddp.js
class SocketConstructorMock (line 10) | class SocketConstructorMock {
method send (line 11) | send () {}
method close (line 12) | close () {}
FILE: test/unit/socket.js
class SocketConstructorMock (line 9) | class SocketConstructorMock {
method close (line 10) | close () {}
method send (line 11) | send () {}
Condensed preview — 30 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (68K chars).
[
{
"path": ".babelrc",
"chars": 125,
"preview": "{\n \"presets\": [\"es2015\", \"stage-0\"],\n \"env\": {\n \"test\": {\n \"plugins\": [\"istanbul\"]\n }\n "
},
{
"path": ".eslintrc",
"chars": 910,
"preview": "{\n \"extends\": \"eslint:recommended\",\n \"parser\": \"babel-eslint\",\n \"env\": {\n \"browser\": true,\n \"moch"
},
{
"path": ".gitignore",
"chars": 65,
"preview": "node_modules/\nnpm-debug.log\ncoverage/\n.nyc_output\nlib/\n.DS_Store\n"
},
{
"path": ".npmignore",
"chars": 38,
"preview": "node_modules/\nnpm-debug.log\ncoverage/\n"
},
{
"path": ".npmrc",
"chars": 19,
"preview": "package-lock=false\n"
},
{
"path": ".travis.yml",
"chars": 367,
"preview": "language: node_js\n\nnode_js:\n - 8\n\n# before_install:\n # - curl https://install.meteor.com | /bin/sh\n\n# before_script:\n "
},
{
"path": "CHANGELOG.md",
"chars": 2809,
"preview": "## 2.2.1 (October 26, 2017)\n\nPR #38: update dependencies\n\n## 2.2.0 (July 2, 2016)\n\nPR #29: add possibility to specify a "
},
{
"path": "LICENSE",
"chars": 1098,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2014-2016 mondora <open@mondora.com>\n\nPermission is hereby granted, free of charge,"
},
{
"path": "README.md",
"chars": 6799,
"preview": "[](https://badge.fury.io/js/ddp.js)\n[ consumer of the queue.\n *"
},
{
"path": "src/socket.js",
"chars": 3398,
"preview": "import EventEmitter from \"wolfy87-eventemitter\";\n\nexport default class Socket extends EventEmitter {\n\n constructor (S"
},
{
"path": "src/utils.js",
"chars": 161,
"preview": "var i = 0;\nexport function uniqueId () {\n return (i++).toString();\n}\n\nexport function contains (array, element) {\n "
},
{
"path": "test/e2e/connection.js",
"chars": 10926,
"preview": "import chai, {expect} from \"chai\";\nimport {Client} from \"faye-websocket\";\nimport sinon from \"sinon\";\nimport sinonChai fr"
},
{
"path": "test/e2e/methods.js",
"chars": 1867,
"preview": "import {expect} from \"chai\";\nimport {Client} from \"faye-websocket\";\n\nimport DDP from \"../../src/ddp\";\n\ndescribe(\"methods"
},
{
"path": "test/e2e/subscriptions.js",
"chars": 4719,
"preview": "import {expect} from \"chai\";\nimport {Client} from \"faye-websocket\";\n\nimport DDP from \"../../src/ddp\";\n\ndescribe(\"subscri"
},
{
"path": "test/server/.meteor/.finished-upgraders",
"chars": 530,
"preview": "# This file contains information which helps Meteor properly upgrade your\n# app when you run 'meteor update'. You should"
},
{
"path": "test/server/.meteor/.gitignore",
"chars": 6,
"preview": "local\n"
},
{
"path": "test/server/.meteor/.id",
"chars": 322,
"preview": "# This file contains a token that is unique to your project.\n# Check it into your repository along with the rest of this"
},
{
"path": "test/server/.meteor/packages",
"chars": 887,
"preview": "# Meteor packages used by this project, one per line.\n# Check this file (and the other files in this directory) into you"
},
{
"path": "test/server/.meteor/platforms",
"chars": 15,
"preview": "server\nbrowser\n"
},
{
"path": "test/server/.meteor/release",
"chars": 15,
"preview": "METEOR@1.5.2.2\n"
},
{
"path": "test/server/.meteor/versions",
"chars": 1395,
"preview": "allow-deny@1.0.9\nautoupdate@1.3.12\nbabel-compiler@6.20.0\nbabel-runtime@1.0.1\nbase64@1.0.10\nbinary-heap@1.0.10\nblaze@2.3."
},
{
"path": "test/server/methods.js",
"chars": 160,
"preview": "Meteor.methods({\n echo: function () {\n return _.toArray(arguments);\n },\n disconnectMe: function () {\n "
},
{
"path": "test/server/publications.js",
"chars": 406,
"preview": "Meteor.publish(\"echo\", function () {\n var self = this;\n _.each(arguments, function (param, index) {\n self.a"
},
{
"path": "test/unit/ddp.js",
"chars": 10587,
"preview": "import chai, {expect} from \"chai\";\nimport sinon from \"sinon\";\nimport sinonChai from \"sinon-chai\";\n\nchai.use(sinonChai);\n"
},
{
"path": "test/unit/queue.js",
"chars": 2276,
"preview": "import chai, {expect} from \"chai\";\nimport sinon from \"sinon\";\nimport sinonChai from \"sinon-chai\";\n\nchai.use(sinonChai);\n"
},
{
"path": "test/unit/socket.js",
"chars": 6671,
"preview": "import chai, {expect} from \"chai\";\nimport sinon from \"sinon\";\nimport sinonChai from \"sinon-chai\";\n\nchai.use(sinonChai);\n"
},
{
"path": "test/unit/utils.js",
"chars": 939,
"preview": "import {expect} from \"chai\";\n\nimport {contains, uniqueId} from \"../../src/utils\";\n\ndescribe(\"`utils` object\", () => {\n\n "
}
]
About this extraction
This page contains the full source code of the mondora/ddp.js GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 30 files (62.0 KB), approximately 15.4k tokens, and a symbol index with 29 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.