[
  {
    "path": ".babelrc",
    "content": "{\n    \"presets\": [\"es2015\", \"stage-0\"],\n    \"env\": {\n        \"test\": {\n            \"plugins\": [\"istanbul\"]\n        }\n    }\n}\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n    \"extends\": \"eslint:recommended\",\n    \"parser\": \"babel-eslint\",\n    \"env\": {\n        \"browser\": true,\n        \"mocha\": true,\n        \"node\": true,\n        \"meteor\": true\n    },\n    \"rules\": {\n        \"brace-style\": [2, \"1tbs\"],\n        \"comma-spacing\": [2, {\"before\": false, \"after\": true}],\n        \"computed-property-spacing\": [2, \"never\"],\n        \"indent\": [2, 4],\n        \"linebreak-style\": [2, \"unix\"],\n        \"new-cap\": [2, {\"capIsNew\": false}],\n        \"no-console\": [0],\n        \"no-multi-spaces\": [0],\n        \"no-underscore-dangle\": [0],\n        \"object-curly-spacing\": [2, \"never\"],\n        \"one-var\": [2, \"never\"],\n        \"quotes\": [2, \"double\"],\n        \"semi\": [2, \"always\"],\n        \"keyword-spacing\": [2],\n        \"space-before-blocks\": [2, \"always\"],\n        \"space-before-function-paren\": [2, \"always\"],\n        \"space-in-parens\": [2, \"never\"],\n        \"strict\": [2, \"never\"]\n    }\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules/\nnpm-debug.log\ncoverage/\n.nyc_output\nlib/\n.DS_Store\n"
  },
  {
    "path": ".npmignore",
    "content": "node_modules/\nnpm-debug.log\ncoverage/\n"
  },
  {
    "path": ".npmrc",
    "content": "package-lock=false\n"
  },
  {
    "path": ".travis.yml",
    "content": "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  # - npm run start-meteor & sleep 30\n\ndeploy:\n  provider: npm\n  email: npm-bot@mondora.com\n  api_key: $NPM_TOKEN\n  on:\n    tags: true\n  skip_cleanup: true\n\nscript:\n  - npm run lint\n  - npm run coverage\n  - npm run coveralls\n  # - npm run e2e-test\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## 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 subscription id.\n\n## 2.1.0 (February 23, 2016)\n\nInternal API change: made `Socket.emit` synchronous.\n\n## 2.0.1 (February 14, 2016)\n\nFixed npm distribution (`lib/` was not published being in `.gitignore`).\n\n## 2.0.0 (February 14, 2016)\n\n### Breaking changes\n\n* Distribute as individual modules in `lib` instead of bundle in `dist`. Should\n  not break node consumers. Could break browserify and webpack consumers.\n  Certainly breaks bower consumers (bower support has been removed)\n\n### New features\n\n* Added method to disconnect\n* Added options to control auto-connect and auto-reconnect behaviour. As it\n  turns out they could indeed be useful, for instance when one wants to simulate\n  a connection scenario (e.g. in stress tests) and needs to have fine-grained\n  control on the lifecycle of the connection.\n\n## 1.1.0 (July 11, 2015)\n\nMoved the code to use ES6. In the process, I also refactored it a bit to use\nless \"exotic\" patterns, but there _should be_ no breaking changes to the public\nAPI.\n\nTwo enhancements:\n\n1.  a `status` property (`connected` / `disconnected`) is now available on the\n    instance\n1.  it's now possible to call methods `sub`, `unsub`, and `method` right after\n    creating the instance. Calls are queued and performed after the `connected`\n    event\n\n## 1.0.0 (January 11, 2015)\n\nThe library has been rewritten from scratch and its scope somewhat reduced. The\npurpose of the rewrite, other than simplification, was to implement better\nunder-the-hood APIs to allow more flexibility.\n\nThe biggest change is that the library no longer handles method and\nsubscription calls. I.e., it doesn't take anymore callbacks to the `method` and\n`sub` methods.  Rather it returns the `id` of those calls, and lets the\nconsumer handle the `result`, `updated`, `ready`, `nosub` events related to\nthose calls. My plan is to bake this functionality directly into Asteroid,\nwhich sometimes need to have lower level access to those events.\n\nSome options have been removed, namely `do_not_autoconnect`,\n`do_not_autoreconnect` and `socket_intercept_function`. The functionalities\nprovided by the first two options can be recreated, but it requires meddling\nwith the library internals (one has to re-define the `_init` method). I figured\nthis wouldn't be a problem since I've never found a use case for them. The\nthird functionality - i.e. intercepting the socket `send` method and doing\nsomething with the message that has been sent - is easily recreated by\nlistening to the `message:in`, `message:out` private events of the `_socket`\nproperty of a DDP instance. Other private events are available on the property,\nmaking it easier to monitor and gather metrics about the WebSocket.\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014-2016 mondora <open@mondora.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![npm version](https://badge.fury.io/js/ddp.js.svg)](https://badge.fury.io/js/ddp.js)\n[![Build Status](https://travis-ci.org/mondora/ddp.js.svg?branch=master)](https://travis-ci.org/mondora/ddp.js)\n[![Coverage Status](https://img.shields.io/coveralls/mondora/ddp.js.svg)](https://coveralls.io/r/mondora/ddp.js?branch=master)\n[![Dependency Status](https://david-dm.org/mondora/ddp.js.svg)](https://david-dm.org/mondora/ddp.js)\n[![devDependency Status](https://david-dm.org/mondora/ddp.js/dev-status.svg)](https://david-dm.org/mondora/ddp.js#info=devDependencies)\n\n# ddp.js\n\nA javascript isomorphic/universal ddp client.\n\n> ## Warning\n> `ddp.js@^2.0.0` is only distributed as an `npm` module instead of an UMD\n> bundle. Also, `bower` has been removed as a method of distribution. If you\n> need an UMD bundle or `bower` support, I'm open for suggestions to add back\n> those methods of distribution without polluting this repo.\n\n## What is it for?\n\nThe purpose of this library is:\n\n- to set up and maintain a ddp connection with a ddp server, freeing the\n  developer from having to do it on their own\n- to give the developer a clear, consistent API to communicate with the ddp\n  server\n\n## Install\n\nTo install ddp.js using `npm`:\n\n    npm install ddp.js\n\nor using `yarn`:\n\n    yarn add ddp.js\n\n## Example usage\n\n```js\nconst DDP = require(\"ddp.js\");\nconst options = {\n    endpoint: \"ws://localhost:3000/websocket\",\n    SocketConstructor: WebSocket\n};\nconst ddp = new DDP(options);\n\nddp.on(\"connected\", () => {\n    console.log(\"Connected\");\n});\n\nconst subId = ddp.sub(\"mySubscription\");\nddp.on(\"ready\", message => {\n    if (message.subs.includes(subId)) {\n        console.log(\"mySubscription ready\");\n    }\n});\nddp.on(\"added\", message => {\n    console.log(message.collection);\n});\n\nconst myLoginParams = {\n    user: {\n        email: \"user@example.com\"\n    },\n    password: \"hunter2\"\n};\nconst methodId = ddp.method(\"login\", [myLoginParams]);\nddp.on(\"result\", message => {\n    if (message.id === methodId && !message.error) {\n        console.log(\"Logged in!\");\n    }\n});\n```\n\n## Developing\n\nAfter cloning the repository, install `npm` dependencies with `npm install`.\nRun `npm test` to run unit tests, or `npm run dev` to have `mocha` re-run your\ntests when source or test files change.\n\nTo run e2e tests, first [install meteor](https://www.meteor.com/install). Then,\nstart the meteor server with `npm run start-meteor`. Finally, run\n`npm run e2e-test` to run the e2e test suite, or `npm run e2e-dev` to have\n`mocha` re-run the suite when source or test files change.\n\n## Public API\n\n### new DDP(options)\n\nCreates a new DDP instance. After being constructed, the instance will\nestablish a connection with the DDP server and will try to maintain it open.\n\n#### Arguments\n\n- `options` **object** *required*\n\nAvailable options are:\n\n- `endpoint` **string** *required*: the location of the websocket server. Its\n  format depends on the type of socket you are using.\n\n- `SocketConstructor` **function** *required*: the constructor function that\n  will be used to construct the socket. Meteor (currently the only DDP server\n  available) supports websockets and SockJS sockets.  So, practically speaking,\n  this means that on the browser you can use either the browser's native\n  WebSocket constructor or the SockJS constructor provided by the SockJS\n  library.  On the server you can use whichever library implements the\n  websocket protocol (e.g.  faye-websocket).\n\n- `autoConnect` **boolean** *optional* [default: `true`]: whether to establish\n  the connection to the server upon instantiation. When `false`, one can\n  manually establish the connection with the `connect` method.\n\n- `autoReconnect` **boolean** *optional* [default: `true`]: whether to try to\n  reconnect to the server when the socket connection closes, unless the closing\n  was initiated by a call to the `disconnect` method.\n\n- `reconnectInterval` **number** *optional* [default: 10000]: the interval in ms\n  between reconnection attempts.\n\n#### Returns\n\nA new DDP instance, which is also an `EventEmitter` instance.\n\n---\n\n### DDP.method(name, params)\n\nCalls a remote method.\n\n#### Arguments\n\n- `name` **string** *required*: name of the method to call.\n\n- `params` **array** *required*: array of parameters to pass to the remote\n  method. Pass an empty array if you do not wish to pass any parameters.\n\n#### Returns\n\nThe unique `id` (string) corresponding to the method call.\n\n#### Example usage\n\nServer code:\n```js\nMeteor.methods({\n    myMethod (param_0, param_1, param_2) {\n        /* ... */\n    }\n});\n```\nClient code:\n```js\nconst methodCallId = ddp.method(\"myMethod\", [param_0, param_1, param_2]);\n```\n\n---\n\n### DDP.sub(name, params)\n\nSubscribes to a server publication.\n\n#### Arguments\n\n- `name` **string** *required*: name of the server publication.\n\n- `params` **array** *required*: array of parameters to pass to the server\n  publish function. Pass an empty array if you do not wish to pass any\n  parameters.\n\n#### Returns\n\nThe unique `id` (string) corresponding to the subscription call.\n\n#### Example usage\n\nServer code:\n```js\nMeteor.publish(\"myPublication\", (param_0, param_1, param_2) {\n    /* ... */\n});\n```\nClient code:\n```js\nconst subscriptionId = ddp.sub(\"myPublication\", [param_0, param_1, param_2]);\n```\n\n---\n\n### DDP.unsub(id)\n\nUnsubscribes to a previously-subscribed server publication.\n\n#### Arguments\n\n- `id` **string** *required*: id of the subscription.\n\n#### Returns\n\nThe `id` corresponding to the subscription call (not of much use, but I return\nit for consistency).\n\n---\n\n### DDP.connect()\n\nConnects to the ddp server. The method is called automatically by the class\nconstructor if the `autoConnect` option is set to `true` (default behaviour).\nSo there generally should be no need for the developer to call the method\nthemselves.\n\n#### Arguments\n\nNone\n\n#### Returns\n\nNone\n\n---\n\n### DDP.disconnect()\n\nDisconnects from the ddp server by closing the `WebSocket` connection. You can\nlisten on the `disconnected` event to be notified of the disconnection.\n\n#### Arguments\n\nNone\n\n#### Returns\n\nNone\n\n## Public events\n\n### Connection events\n\n- `connected`: emitted with no arguments when the DDP connection is\n  established.\n\n- `disconnected`: emitted with no arguments when the DDP connection drops.\n\n### Subscription events\n\nAll the following events are emitted with one argument, the parsed DDP message.\nFurther details can be found [on the DDP spec\npage](https://github.com/meteor/meteor/blob/devel/packages/ddp/DDP.md).\n\n- `ready`\n- `nosub`\n- `added`\n- `changed`\n- `removed`\n\n### Method events\n\nAll the following events are emitted with one argument, the parsed DDP message.\nFurther details can be found [on the DDP spec\npage](https://github.com/meteor/meteor/blob/devel/packages/ddp/DDP.md).\n\n- `result`\n- `updated`\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"ddp.js\",\n  \"version\": \"2.2.1\",\n  \"description\": \"ddp javascript client\",\n  \"main\": \"lib/ddp.js\",\n  \"scripts\": {\n    \"build\": \"babel src --out-dir lib\",\n    \"clean\": \"rimraf lib coverage\",\n    \"coverage\": \"nyc --require babel-register --reporter=lcov --include src --all npm test\",\n    \"coveralls\": \"cat ./coverage/lcov.info | coveralls\",\n    \"dev\": \"npm test -- --watch\",\n    \"lint\": \"eslint src test\",\n    \"prepare\": \"npm run clean && npm run build\",\n    \"test\": \"mocha --require babel-register --recursive test/unit\",\n    \"start-meteor\": \"cd test/server/ && meteor\",\n    \"e2e-test\": \"mocha --require babel-register --recursive test/e2e\",\n    \"e2e-dev\": \"npm run e2e-test -- --watch\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/mondora/ddp.js\"\n  },\n  \"keywords\": [\n    \"ddp\",\n    \"meteor\",\n    \"asteroid\"\n  ],\n  \"author\": \"Paolo Scanferla <paolo.scanferla@mondora.com>\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mondora/ddp.js/issues\"\n  },\n  \"homepage\": \"https://github.com/mondora/ddp.js\",\n  \"devDependencies\": {\n    \"babel-cli\": \"^6.26.0\",\n    \"babel-eslint\": \"^8.0.1\",\n    \"babel-plugin-istanbul\": \"^4.1.5\",\n    \"babel-preset-es2015\": \"^6.24.1\",\n    \"babel-preset-stage-0\": \"^6.24.1\",\n    \"babel-register\": \"^6.26.0\",\n    \"chai\": \"^4.1.2\",\n    \"coveralls\": \"^3.0.0\",\n    \"eslint\": \"^4.9.0\",\n    \"faye-websocket\": \"^0.11.1\",\n    \"mocha\": \"^4.0.1\",\n    \"nyc\": \"^11.2.1\",\n    \"sinon\": \"^4.0.2\",\n    \"sinon-chai\": \"^2.14.0\"\n  },\n  \"dependencies\": {\n    \"wolfy87-eventemitter\": \"^5.2.3\"\n  }\n}\n"
  },
  {
    "path": "src/ddp.js",
    "content": "import EventEmitter from \"wolfy87-eventemitter\";\nimport Queue from \"./queue\";\nimport Socket from \"./socket\";\nimport {contains, uniqueId} from \"./utils\";\n\nconst DDP_VERSION = \"1\";\nconst PUBLIC_EVENTS = [\n    // Subscription messages\n    \"ready\", \"nosub\", \"added\", \"changed\", \"removed\",\n    // Method messages\n    \"result\", \"updated\",\n    // Error messages\n    \"error\"\n];\nconst DEFAULT_RECONNECT_INTERVAL = 10000;\n\nexport default class DDP extends EventEmitter {\n\n    emit () {\n        setTimeout(super.emit.bind(this, ...arguments), 0);\n    }\n\n    constructor (options) {\n\n        super();\n\n        this.status = \"disconnected\";\n\n        // Default `autoConnect` and `autoReconnect` to true\n        this.autoConnect = (options.autoConnect !== false);\n        this.autoReconnect = (options.autoReconnect !== false);\n        this.reconnectInterval = options.reconnectInterval || DEFAULT_RECONNECT_INTERVAL;\n\n        this.messageQueue = new Queue(message => {\n            if (this.status === \"connected\") {\n                this.socket.send(message);\n                return true;\n            } else {\n                return false;\n            }\n        });\n\n        this.socket = new Socket(options.SocketConstructor, options.endpoint);\n\n        this.socket.on(\"open\", () => {\n            // When the socket opens, send the `connect` message\n            // to establish the DDP connection\n            this.socket.send({\n                msg: \"connect\",\n                version: DDP_VERSION,\n                support: [DDP_VERSION]\n            });\n        });\n\n        this.socket.on(\"close\", () => {\n            this.status = \"disconnected\";\n            this.messageQueue.empty();\n            this.emit(\"disconnected\");\n            if (this.autoReconnect) {\n                // Schedule a reconnection\n                setTimeout(\n                    this.socket.open.bind(this.socket),\n                    this.reconnectInterval\n                );\n            }\n        });\n\n        this.socket.on(\"message:in\", message => {\n            if (message.msg === \"connected\") {\n                this.status = \"connected\";\n                this.messageQueue.process();\n                this.emit(\"connected\");\n            } else if (message.msg === \"ping\") {\n                // Reply with a `pong` message to prevent the server from\n                // closing the connection\n                this.socket.send({msg: \"pong\", id: message.id});\n            } else if (contains(PUBLIC_EVENTS, message.msg)) {\n                this.emit(message.msg, message);\n            }\n        });\n\n        if (this.autoConnect) {\n            this.connect();\n        }\n\n    }\n\n    connect () {\n        this.socket.open();\n    }\n\n    disconnect () {\n        /*\n        *   If `disconnect` is called, the caller likely doesn't want the\n        *   the instance to try to auto-reconnect. Therefore we set the\n        *   `autoReconnect` flag to false.\n        */\n        this.autoReconnect = false;\n        this.socket.close();\n    }\n\n    method (name, params) {\n        const id = uniqueId();\n        this.messageQueue.push({\n            msg: \"method\",\n            id: id,\n            method: name,\n            params: params\n        });\n        return id;\n    }\n\n    sub (name, params, id = null) {\n        id || (id = uniqueId());\n        this.messageQueue.push({\n            msg: \"sub\",\n            id: id,\n            name: name,\n            params: params\n        });\n        return id;\n    }\n\n    unsub (id) {\n        this.messageQueue.push({\n            msg: \"unsub\",\n            id: id\n        });\n        return id;\n    }\n\n}\n"
  },
  {
    "path": "src/queue.js",
    "content": "export default class Queue {\n\n    /*\n    *   As the name implies, `\u001bconsumer` is the (sole) consumer of the queue.\n    *   It gets called with each element of the queue and its return value\n    *   serves as a ack, determining whether the element is removed or not from\n    *   the queue, allowing then subsequent elements to be processed.\n    */\n\n    constructor (consumer) {\n        this.consumer = consumer;\n        this.queue = [];\n    }\n\n    push (element) {\n        this.queue.push(element);\n        this.process();\n    }\n\n    process () {\n        if (this.queue.length !== 0) {\n            const ack = this.consumer(this.queue[0]);\n            if (ack) {\n                this.queue.shift();\n                this.process();\n            }\n        }\n    }\n\n    empty () {\n        this.queue = [];\n    }\n\n}\n"
  },
  {
    "path": "src/socket.js",
    "content": "import EventEmitter from \"wolfy87-eventemitter\";\n\nexport default class Socket extends EventEmitter {\n\n    constructor (SocketConstructor, endpoint) {\n        super();\n        this.SocketConstructor = SocketConstructor;\n        this.endpoint = endpoint;\n        this.rawSocket = null;\n    }\n\n    send (object) {\n        const message = JSON.stringify(object);\n        this.rawSocket.send(message);\n        // Emit a copy of the object, as the listener might mutate it.\n        this.emit(\"message:out\", JSON.parse(message));\n    }\n\n    open () {\n\n        /*\n        *   Makes `open` a no-op if there's already a `rawSocket`. This avoids\n        *   memory / socket leaks if `open` is called twice (e.g. by a user\n        *   calling `ddp.connect` twice) without properly disposing of the\n        *   socket connection. `rawSocket` gets automatically set to `null` only\n        *   when it goes into a closed or error state. This way `rawSocket` is\n        *   disposed of correctly: the socket connection is closed, and the\n        *   object can be garbage collected.\n        */\n        if (this.rawSocket) {\n            return;\n        }\n        this.rawSocket = new this.SocketConstructor(this.endpoint);\n\n        /*\n        *   Calls to `onopen` and `onclose` directly trigger the `open` and\n        *   `close` events on the `Socket` instance.\n        */\n        this.rawSocket.onopen = () => this.emit(\"open\");\n        this.rawSocket.onclose = () => {\n            this.rawSocket = null;\n            this.emit(\"close\");\n        };\n        /*\n        *   Calls to `onerror` trigger the `close` event on the `Socket`\n        *   instance, and cause the `rawSocket` object to be disposed of.\n        *   Since it's not clear what conditions could cause the error and if\n        *   it's possible to recover from it, we prefer to always close the\n        *   connection (if it isn't already) and dispose of the socket object.\n        */\n        this.rawSocket.onerror = () => {\n            // It's not clear what the socket lifecycle is when errors occurr.\n            // Hence, to avoid the `close` event to be emitted twice, before\n            // manually closing the socket we de-register the `onclose`\n            // callback.\n            delete this.rawSocket.onclose;\n            // Safe to perform even if the socket is already closed\n            this.rawSocket.close();\n            this.rawSocket = null;\n            this.emit(\"close\");\n        };\n        /*\n        *   Calls to `onmessage` trigger a `message:in` event on the `Socket`\n        *   instance only once the message (first parameter to `onmessage`) has\n        *   been successfully parsed into a javascript object.\n        */\n        this.rawSocket.onmessage = message => {\n            var object;\n            try {\n                object = JSON.parse(message.data);\n            } catch (ignore) {\n                // Simply ignore the malformed message and return\n                return;\n            }\n            // Outside the try-catch block as it must only catch JSON parsing\n            // errors, not errors that may occur inside a \"message:in\" event\n            // handler\n            this.emit(\"message:in\", object);\n        };\n\n    }\n\n    close () {\n        /*\n        *   Avoid throwing an error if `rawSocket === null`\n        */\n        if (this.rawSocket) {\n            this.rawSocket.close();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/utils.js",
    "content": "var i = 0;\nexport function uniqueId () {\n    return (i++).toString();\n}\n\nexport function contains (array, element) {\n    return array.indexOf(element) !== -1;\n}\n"
  },
  {
    "path": "test/e2e/connection.js",
    "content": "import chai, {expect} from \"chai\";\nimport {Client} from \"faye-websocket\";\nimport sinon from \"sinon\";\nimport sinonChai from \"sinon-chai\";\n\nimport DDP from \"../../src/ddp\";\n\nchai.use(sinonChai);\n\nconst options = {\n    endpoint: \"ws://localhost:3000/websocket\",\n    SocketConstructor: Client\n};\n\ndescribe(\"connection\", () => {\n\n    var ddp = null;\n    afterEach(done => {\n        if (ddp) {\n            ddp.on(\"disconnected\", () => done());\n            ddp.disconnect();\n            ddp = null;\n        } else {\n            done();\n        }\n    });\n\n    describe(\"connecting\", () => {\n\n        it(\"a connection is established on instantiation unless `autoConnect === false`\", done => {\n            /*\n            *   The test suceeds when the `connected` event is fired, signaling\n            *   the establishment of the connection.\n            *   If the event is never fired, the test times out and fails.\n            */\n            ddp = new DDP(options);\n            ddp.on(\"connected\", () => {\n                done();\n            });\n        });\n\n        it(\"a connection is not established on instantiation when `autoConnect === false`\", done => {\n            /*\n            *   The test succeeds if, 1s after the creation of the DDP instance,\n            *   a `connected` event has not been fired.\n            */\n            const ddp = new DDP({\n                ...options,\n                autoConnect: false\n            });\n            const connectedHandler = sinon.spy();\n            ddp.on(\"connected\", connectedHandler);\n            setTimeout(() => {\n                try {\n                    expect(connectedHandler).to.have.callCount(0);\n                    done();\n                } catch (e) {\n                    done(e);\n                }\n            }, 1000);\n        });\n\n        it(\"a connection can be established manually when `autoConnect === false`\", done => {\n            /*\n            *   The test suceeds when the `connected` event is fired, signaling\n            *   the establishment of the connection.\n            *   If the event is never fired, the test times out and fails.\n            */\n            ddp = new DDP({\n                ...options,\n                autoConnect: false\n            });\n            ddp.connect();\n            ddp.on(\"connected\", () => {\n                done();\n            });\n        });\n\n        it(\"manually connecting several times doesn't causes multiple simultaneous connections [CASE: `autoConnect === true`]\", done => {\n            /*\n            *   The test suceeds if 1s after having called `connect` several\n            *   times only one connection has been established.\n            */\n            ddp = new DDP(options);\n            const connectedSpy = sinon.spy();\n            ddp.connect();\n            ddp.connect();\n            ddp.connect();\n            ddp.connect();\n            ddp.on(\"connected\", connectedSpy);\n            setTimeout(() => {\n                try {\n                    expect(connectedSpy).to.have.callCount(1);\n                    done();\n                } catch (e) {\n                    done(e);\n                }\n            }, 1000);\n        });\n\n        it(\"manually connecting several times doesn't causes multiple simultaneous connections [CASE: `autoConnect === false`]\", done => {\n            /*\n            *   The test suceeds if 1s after having called `connect` several\n            *   times only one connection has been established.\n            */\n            ddp = new DDP({\n                ...options,\n                autoConnect: false\n            });\n            const connectedSpy = sinon.spy();\n            ddp.connect();\n            ddp.connect();\n            ddp.connect();\n            ddp.connect();\n            ddp.on(\"connected\", connectedSpy);\n            setTimeout(() => {\n                try {\n                    expect(connectedSpy).to.have.callCount(1);\n                    done();\n                } catch (e) {\n                    done(e);\n                }\n            }, 1000);\n        });\n\n    });\n\n    describe(\"disconnecting\", () => {\n\n        it(\"the connection is closed when calling `disconnect`\", done => {\n            /*\n            *   The test suceeds when the `disconnected` event is fired,\n            *   signaling the termination of the connection.\n            *   If the event is never fired, the test times out and fails.\n            */\n            const ddp = new DDP(options);\n            ddp.on(\"connected\", () => {\n                ddp.disconnect();\n            });\n            ddp.on(\"disconnected\", () => {\n                done();\n            });\n        });\n\n        it(\"calling `disconnect` several times causes no issues\", done => {\n            /*\n            *   The test suceeds if:\n            *   - calling `disconnect` several times doesn't throw, both before\n            *     and after the `disconnected` event has been received\n            *   - one and only one `disconnected` event is fired (check after\n            *     1s)\n            */\n            const ddp = new DDP(options);\n            const disconnectSpy = sinon.spy(() => {\n                try {\n                    ddp.disconnect();\n                    ddp.disconnect();\n                    ddp.disconnect();\n                    ddp.disconnect();\n                } catch (e) {\n                    done(e);\n                }\n            });\n            ddp.on(\"connected\", () => {\n                try {\n                    ddp.disconnect();\n                    ddp.disconnect();\n                    ddp.disconnect();\n                    ddp.disconnect();\n                } catch (e) {\n                    done(e);\n                }\n            });\n            ddp.on(\"disconnected\", disconnectSpy);\n            setTimeout(() => {\n                try {\n                    expect(disconnectSpy).to.have.callCount(1);\n                    done();\n                } catch (e) {\n                    done(e);\n                }\n            }, 1000);\n        });\n\n        it(\"the connection is closed when calling `disconnect` and it's not re-established\", done => {\n            /*\n            *   The test suceeds if, 1s after the `disconnected` event has been\n            *   fired, there hasn't been any reconnection.\n            */\n            const ddp = new DDP({\n                ...options,\n                reconnectInterval: 10\n            });\n            const disconnectOnConnection = sinon.spy(() => {\n                ddp.disconnect();\n            });\n            ddp.on(\"connected\", disconnectOnConnection);\n            ddp.on(\"disconnected\", () => {\n                setTimeout(() => {\n                    try {\n                        expect(disconnectOnConnection).to.have.callCount(1);\n                        done();\n                    } catch (e) {\n                        done(e);\n                    }\n                }, 1000);\n            });\n        });\n\n        it(\"the connection is closed and re-established when the server closes the connection, unless `autoReconnect === true`\", done => {\n            /*\n            *   The test suceeds when the `connect` event is fired a second time\n            *   after the client gets disconnected from the server (occurrence\n            *   marked by the `disconnected` event).\n            *   If the event is never fired a second time, the test times out\n            *   and fails.\n            */\n            ddp = new DDP({\n                ...options,\n                reconnectInterval: 10\n            });\n            var callCount = 0;\n            ddp.on(\"connected\", () => {\n                callCount += 1;\n                if (callCount === 1) {\n                    ddp.method(\"disconnectMe\", []);\n                }\n                if (callCount === 2) {\n                    done();\n                }\n            });\n        });\n\n        it(\"the connection is closed and _not_ re-established when the server closes the connection and `autoReconnect === false`\", done => {\n            /*\n            *   The test suceeds if, 1s after the `disconnected` event has been\n            *   fired, there hasn't been any reconnection.\n            */\n            const ddp = new DDP({\n                ...options,\n                reconnectInterval: 10,\n                autoReconnect: false\n            });\n            const disconnectMe = sinon.spy(() => {\n                ddp.method(\"disconnectMe\", []);\n            });\n            ddp.on(\"connected\", disconnectMe);\n            ddp.on(\"disconnected\", () => {\n                setTimeout(() => {\n                    try {\n                        expect(disconnectMe).to.have.callCount(1);\n                        done();\n                    } catch (e) {\n                        done(e);\n                    }\n                }, 1000);\n            });\n        });\n\n        describe(\"ddp.js issue #22\", () => {\n\n            /*\n            *   We need to test that no `uncaughtException`-s are raised. Since\n            *   mocha adds a listener to the `uncaughtException` event which\n            *   causes tests to fail in an unexpected manner, we first remove\n            *   that listener, and then we restore it. Since it's not clear\n            *   _what_ mocha does with that listener, we try to lower the\n            *   meddling impact by doing all of our work inside the `it` block.\n            */\n\n            it(\"no issues when sending messages while disconnected / while disconnecting\", done => {\n                /*\n                *   The test suceeds if, 100ms after the `disconnected` event\n                *   has been fired, there haven't been any uncaught exceptions.\n                */\n                const catcher = sinon.spy();\n                const listeners = process.listeners(\"uncaughtException\");\n                process.removeAllListeners(\"uncaughtException\");\n                process.on(\"uncaughtException\", catcher);\n                const ddp = new DDP({\n                    ...options,\n                    autoReconnect: false\n                });\n                ddp.on(\"connected\", () => {\n                    ddp.disconnect();\n                });\n                const interval = setInterval(() => {\n                    ddp.method(\"echo\", []);\n                }, 1);\n                ddp.on(\"disconnected\", () => {\n                    setTimeout(runAssertions, 100);\n                });\n                const runAssertions = () => {\n                    clearInterval(interval);\n                    process.removeAllListeners(\"uncaughtException\");\n                    listeners.forEach(listener => {\n                        process.on(\"uncaughtException\", listener);\n                    });\n                    try {\n                        expect(catcher).to.have.callCount(0);\n                        done();\n                    } catch (e) {\n                        done(e);\n                    }\n                };\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/e2e/methods.js",
    "content": "import {expect} from \"chai\";\nimport {Client} from \"faye-websocket\";\n\nimport DDP from \"../../src/ddp\";\n\ndescribe(\"methods\", () => {\n\n    describe(\"calling a method\", () => {\n\n        const ddp = new DDP({\n            endpoint: \"ws://localhost:3000/websocket\",\n            SocketConstructor: Client\n        });\n\n        after(done => {\n            ddp.on(\"disconnected\", () => done());\n            ddp.disconnect();\n        });\n\n        it(\"invokes the method on the server and gets a `result` message with the response\", done => {\n            /*\n            *   The test suceeds when the `result` message for the echo method\n            *   call is received, and assertions all succeed.\n            *   If the `result` message is never received, the test times out\n            *   and fails. Naturally, the test also fails if assertions fail.\n            */\n            const methodId = ddp.method(\"echo\", [0, 1, 2, 3, 4]);\n            ddp.on(\"result\", message => {\n                if (message.id !== methodId || message.error) {\n                    return;\n                }\n                try {\n                    expect(message.result).to.deep.equal([0, 1, 2, 3, 4]);\n                    done();\n                } catch (e) {\n                    done(e);\n                }\n            });\n        });\n\n        it(\"gets an `updated` message\", done => {\n            /*\n            *   The test suceeds when the `updated` message for the echo method\n            *   call is received.\n            *   If the `updated` message is never received, the test times out\n            *   and fails.\n            */\n            const methodId = ddp.method(\"echo\", [0, 1, 2, 3, 4]);\n            ddp.on(\"updated\", message => {\n                if (message.methods.indexOf(methodId) !== -1) {\n                    done();\n                }\n            });\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/e2e/subscriptions.js",
    "content": "import {expect} from \"chai\";\nimport {Client} from \"faye-websocket\";\n\nimport DDP from \"../../src/ddp\";\n\ndescribe(\"subscriptions\", () => {\n\n    describe(\"subscribing to a publication\", () => {\n\n        const ddp = new DDP({\n            endpoint: \"ws://localhost:3000/websocket\",\n            SocketConstructor: Client\n        });\n\n        after(done => {\n            ddp.on(\"disconnected\", () => done());\n            ddp.disconnect();\n        });\n\n        it(\"sends a sub call to the server and receives server-sent scubscription events\", done => {\n            /*\n            *   The test suceeds when the `ready` message for the echo\n            *   subscription is received, and assertions all succeed.\n            *   If the `ready` message is never received, the test times out\n            *   and fails. Naturally, the test also fails if assertions fail.\n            */\n            const subId = ddp.sub(\"echo\", [0, 1, 2, 3, 4]);\n            const collections = {};\n            ddp.on(\"added\", message => {\n                collections[message.collection] = {\n                    ...collections[message.collection],\n                    [message.id]: {\n                        _id: message.id,\n                        ...message.fields\n                    }\n                };\n            });\n            ddp.on(\"ready\", message => {\n                if (message.subs.indexOf(subId) === -1) {\n                    return;\n                }\n                try {\n                    expect(collections).to.deep.equal({\n                        echoParameters: {\n                            \"id_0\": {_id: \"id_0\", param: 0},\n                            \"id_1\": {_id: \"id_1\", param: 1},\n                            \"id_2\": {_id: \"id_2\", param: 2},\n                            \"id_3\": {_id: \"id_3\", param: 3},\n                            \"id_4\": {_id: \"id_4\", param: 4}\n                        }\n                    });\n                    done();\n                } catch (e) {\n                    done(e);\n                }\n            });\n        });\n\n    });\n\n    describe(\"unsubscribing from a publication\", () => {\n\n        const ddp = new DDP({\n            endpoint: \"ws://localhost:3000/websocket\",\n            SocketConstructor: Client\n        });\n\n        after(done => {\n            ddp.on(\"disconnected\", () => done());\n            ddp.disconnect();\n        });\n\n        it(\"sends an unsub call to the server and receives a nosub unsubscriptions confirmation event\", done => {\n            /*\n            *   The test suceeds when the `nosub` message for the echo\n            *   subscription is received. If the `nosub` message is never\n            *   received, the test times out and fails.\n            */\n            const subId = ddp.sub(\"echo\", [0, 1, 2, 3, 4]);\n            ddp.on(\"ready\", message => {\n                if (message.subs.indexOf(subId) === -1) {\n                    return;\n                }\n                ddp.unsub(subId);\n            });\n            ddp.on(\"nosub\", message => {\n                if (message.id !== subId) {\n                    return;\n                }\n                done();\n            });\n        });\n\n    });\n\n    describe(\"getting unsubscribed from a publication\", () => {\n\n        const ddp = new DDP({\n            endpoint: \"ws://localhost:3000/websocket\",\n            SocketConstructor: Client\n        });\n\n        after(done => {\n            ddp.on(\"disconnected\", () => done());\n            ddp.disconnect();\n        });\n\n        it(\"receives a nosub unsubscriptions event\", function (done) {\n            /*\n            *   The test suceeds when the `nosub` message for the echo\n            *   subscription is received. If the `nosub` message is never\n            *   received, the test times out and fails.\n            *   The server will stop the subscription after about 1s, so there\n            *   is no need to terminate it with an `unsub`. We will however\n            *   increase the test timeout to 3s to account for the delay.\n            */\n            this.timeout(3000);\n            const subId = ddp.sub(\"autoTerminating\");\n            var subReady = false;\n            ddp.on(\"ready\", message => {\n                if (message.subs.indexOf(subId) !== -1) {\n                    subReady = true;\n                }\n            });\n            ddp.on(\"nosub\", message => {\n                if (message.id !== subId) {\n                    return;\n                }\n                try {\n                    // Ensure the subscription got marked as ready.\n                    expect(subReady).to.equal(true);\n                    done();\n                } catch (e) {\n                    done(e);\n                }\n            });\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/server/.meteor/.finished-upgraders",
    "content": "# This file contains information which helps Meteor properly upgrade your\n# app when you run 'meteor update'. You should check it into version control\n# with your project.\n\nnotices-for-0.9.0\nnotices-for-0.9.1\n0.9.4-platform-file\nnotices-for-facebook-graph-api-2\n1.2.0-standard-minifiers-package\n1.2.0-meteor-platform-split\n1.2.0-cordova-changes\n1.2.0-breaking-changes\n1.3.0-split-minifiers-package\n1.4.0-remove-old-dev-bundle-link\n1.4.1-add-shell-server-package\n1.4.3-split-account-service-packages\n1.5-add-dynamic-import-package\n"
  },
  {
    "path": "test/server/.meteor/.gitignore",
    "content": "local\n"
  },
  {
    "path": "test/server/.meteor/.id",
    "content": "# This file contains a token that is unique to your project.\n# Check it into your repository along with the rest of this directory.\n# It can be used for purposes such as:\n#   - ensuring you don't accidentally deploy one app on top of another\n#   - providing package authors with aggregated statistics\n\nydh6g2121jjwyqq7agh\n"
  },
  {
    "path": "test/server/.meteor/packages",
    "content": "# Meteor packages used by this project, one per line.\n# Check this file (and the other files in this directory) into your repository.\n#\n# 'meteor add' and 'meteor remove' will edit this file for you,\n# but you can also edit it by hand.\n\nmeteor-base@1.1.0             # Packages every Meteor app needs to have\nmobile-experience@1.0.5       # Packages for a great mobile UX\nmongo@1.2.2                   # The database Meteor supports right now\nblaze-html-templates    # Compile .html files into Meteor Blaze views\nsession@1.1.7                 # Client-side reactive dictionary for your app\njquery@1.11.10                  # Helpful client-side library\ntracker@1.1.3                 # Meteor's client-side reactive programming library\n\nes5-shim@4.6.15                # ECMAScript 5 compatibility for older browsers.\n\nstandard-minifier-css\nstandard-minifier-js\nshell-server\ndynamic-import\n"
  },
  {
    "path": "test/server/.meteor/platforms",
    "content": "server\nbrowser\n"
  },
  {
    "path": "test/server/.meteor/release",
    "content": "METEOR@1.5.2.2\n"
  },
  {
    "path": "test/server/.meteor/versions",
    "content": "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.2\nblaze-html-templates@1.1.2\nblaze-tools@1.0.10\nboilerplate-generator@1.2.0\ncaching-compiler@1.1.9\ncaching-html-compiler@1.1.2\ncallback-hook@1.0.10\ncheck@1.2.5\nddp@1.3.1\nddp-client@2.1.3\nddp-common@1.2.9\nddp-server@2.0.2\ndeps@1.0.12\ndiff-sequence@1.0.7\ndynamic-import@0.1.3\necmascript@0.8.3\necmascript-runtime@0.4.1\necmascript-runtime-client@0.4.3\necmascript-runtime-server@0.4.1\nejson@1.0.14\nes5-shim@4.6.15\ngeojson-utils@1.0.10\nhot-code-push@1.0.4\nhtml-tools@1.0.11\nhtmljs@1.0.11\nhttp@1.2.12\nid-map@1.0.9\njquery@1.11.10\nlaunch-screen@1.1.1\nlivedata@1.0.18\nlogging@1.1.19\nmeteor@1.7.2\nmeteor-base@1.1.0\nminifier-css@1.2.16\nminifier-js@2.1.4\nminimongo@1.3.3\nmobile-experience@1.0.5\nmobile-status-bar@1.0.14\nmodules@0.10.0\nmodules-runtime@0.8.0\nmongo@1.2.2\nmongo-dev-server@1.0.1\nmongo-id@1.0.6\nnpm-mongo@2.2.33\nobserve-sequence@1.0.16\nordered-dict@1.0.9\npromise@0.9.0\nrandom@1.0.10\nreactive-dict@1.1.9\nreactive-var@1.0.11\nreload@1.1.11\nretry@1.0.9\nroutepolicy@1.0.12\nsession@1.1.7\nshell-server@0.2.4\nspacebars@1.0.15\nspacebars-compiler@1.1.3\nstandard-minifier-css@1.3.5\nstandard-minifier-js@2.1.2\ntemplating@1.3.2\ntemplating-compiler@1.3.3\ntemplating-runtime@1.3.2\ntemplating-tools@1.1.2\ntracker@1.1.3\nui@1.0.13\nunderscore@1.0.10\nurl@1.1.0\nwebapp@1.3.19\nwebapp-hashing@1.0.9\n"
  },
  {
    "path": "test/server/methods.js",
    "content": "Meteor.methods({\n    echo: function () {\n        return _.toArray(arguments);\n    },\n    disconnectMe: function () {\n        this.connection.close();\n    }\n});\n"
  },
  {
    "path": "test/server/publications.js",
    "content": "Meteor.publish(\"echo\", function () {\n    var self = this;\n    _.each(arguments, function (param, index) {\n        self.added(\"echoParameters\", \"id_\" + index, {param: param});\n    });\n    self.ready();\n});\n\nMeteor.publish(\"autoTerminating\", function () {\n    var self = this;\n    self.added(\"autoTerminating\", \"id\", {});\n    self.ready();\n    setTimeout(function () {\n        self.stop();\n    }, 1000);\n});\n"
  },
  {
    "path": "test/unit/ddp.js",
    "content": "import chai, {expect} from \"chai\";\nimport sinon from \"sinon\";\nimport sinonChai from \"sinon-chai\";\n\nchai.use(sinonChai);\n\nimport DDP from \"../../src/ddp\";\nimport Socket from \"../../src/socket\";\n\nclass SocketConstructorMock {\n    send () {}\n    close () {}\n}\nconst options = {\n    SocketConstructor: SocketConstructorMock\n};\n\ndescribe(\"`DDP` class\", () => {\n\n    describe(\"`constructor` method\", () => {\n\n        beforeEach(() => {\n            sinon.stub(Socket.prototype, \"on\");\n            sinon.stub(Socket.prototype, \"open\");\n        });\n        afterEach(() => {\n            Socket.prototype.on.restore();\n            Socket.prototype.open.restore();\n        });\n\n        it(\"instantiates a `Socket`\", () => {\n            const ddp = new DDP(options);\n            expect(ddp.socket).to.be.an.instanceOf(Socket);\n        });\n\n        it(\"registers handlers for `socket` events\", () => {\n            const ddp = new DDP(options);\n            expect(ddp.socket.on).to.have.always.been.calledWithMatch(\n                sinon.match.string,\n                sinon.match.func\n            );\n        });\n\n        it(\"opens a connection to the server (by calling `socket.open`) unless `options.autoConnect === false`\", () => {\n            const ddp = new DDP(options);\n            expect(ddp.socket.open).to.have.callCount(1);\n        });\n\n        it(\"does not open a connection when `options.autoConnect === false`\", () => {\n            const ddp = new DDP({\n                ...options,\n                autoConnect: false\n            });\n            expect(ddp.socket.open).to.have.callCount(0);\n        });\n\n        it(\"sets the instance `reconnectInterval` to `options.reconnectInterval` if specified\", () => {\n            const ddp = new DDP({\n                ...options,\n                reconnectInterval: 1,\n                autoConnect: false\n            });\n            expect(ddp.reconnectInterval).to.equal(1);\n        });\n\n        it(\"sets the instance `reconnectInterval` to a default value if `options.reconnectInterval` is not specified\", () => {\n            const ddp = new DDP({\n                ...options,\n                autoConnect: false\n            });\n            expect(ddp.reconnectInterval).to.equal(10000);\n        });\n\n\n    });\n\n    describe(\"`method` method\", () => {\n\n        it(\"sends a DDP `method` message\", () => {\n            const ddp = new DDP(options);\n            ddp.messageQueue.push = sinon.spy();\n            const id = ddp.method(\"name\", [\"param\"]);\n            expect(ddp.messageQueue.push).to.have.been.calledWith({\n                msg: \"method\",\n                id: id,\n                method: \"name\",\n                params: [\"param\"]\n            });\n        });\n\n        it(\"returns the method's `id`\", () => {\n            const ddp = new DDP(options);\n            ddp.messageQueue.push = sinon.spy();\n            const id = ddp.method(\"name\", [\"param\"]);\n            expect(id).to.be.a(\"string\");\n        });\n\n    });\n\n    describe(\"`sub` method\", () => {\n\n        it(\"sends a DDP `sub` message\", () => {\n            const ddp = new DDP(options);\n            ddp.messageQueue.push = sinon.spy();\n            const id = ddp.sub(\"name\", [\"param\"]);\n            expect(ddp.messageQueue.push).to.have.been.calledWith({\n                msg: \"sub\",\n                id: id,\n                name: \"name\",\n                params: [\"param\"]\n            });\n        });\n\n        it(\"returns the sub's `id`\", () => {\n            const ddp = new DDP(options);\n            ddp.messageQueue.push = sinon.spy();\n            const id = ddp.sub(\"name\", [\"param\"]);\n            expect(id).to.be.a(\"string\");\n        });\n\n        it(\"generates unique id when not specified\", () => {\n            const ddp = new DDP(options);\n            var ids = [];\n            ids.push(ddp.sub(\"echo\", [ 0 ]));\n            ids.push(ddp.sub(\"echo\", [ 0 ]));\n            expect(ids[0]).to.be.a(\"string\");\n            expect(ids[1]).to.be.a(\"string\");\n            expect(ids[0]).not.to.equal(ids[1]);\n        });\n\n        it(\"allows manually specifying sub's id\", () => {\n            const ddp = new DDP(options);\n            const subId = ddp.sub(\"echo\", [ 0 ], \"12345\");\n            expect(subId).to.equal(\"12345\");\n        });\n\n    });\n\n    describe(\"`unsub` method\", () => {\n\n        it(\"sends a DDP `unsub` message\", () => {\n            const ddp = new DDP(options);\n            ddp.messageQueue.push = sinon.spy();\n            const id = ddp.unsub(\"id\");\n            expect(ddp.messageQueue.push).to.have.been.calledWith({\n                msg: \"unsub\",\n                id: id\n            });\n        });\n\n        it(\"returns the sub's `id`\", () => {\n            const ddp = new DDP(options);\n            ddp.messageQueue.push = sinon.spy();\n            const id = ddp.unsub(\"id\");\n            expect(id).to.be.a(\"string\");\n            expect(id).to.equal(\"id\");\n        });\n\n    });\n\n    describe(\"`connect` method\", () => {\n\n        beforeEach(() => {\n            sinon.stub(Socket.prototype, \"open\");\n        });\n        afterEach(() => {\n            Socket.prototype.open.restore();\n        });\n\n        it(\"opens the WebSocket connection\", () => {\n            const ddp = new DDP(options);\n            Socket.prototype.open.reset();\n            ddp.connect();\n            expect(ddp.socket.open).to.have.callCount(1);\n        });\n\n    });\n\n    describe(\"`disconnect` method\", () => {\n\n        beforeEach(() => {\n            sinon.stub(Socket.prototype, \"close\");\n        });\n        afterEach(() => {\n            Socket.prototype.close.restore();\n        });\n\n        it(\"closes the WebSocket connection\", () => {\n            const ddp = new DDP(options);\n            ddp.disconnect();\n            expect(ddp.socket.close).to.have.callCount(1);\n        });\n\n        it(\"sets the `autoReconnect` flag to false\", () => {\n            const ddp = new DDP(options);\n            ddp.disconnect();\n            expect(ddp.autoReconnect).to.equal(false);\n        });\n\n    });\n\n    describe(\"`socket` `open` handler\", () => {\n\n        beforeEach(() => {\n            sinon.stub(global, \"setTimeout\").callsFake(fn => fn());\n        });\n        afterEach(() => {\n            global.setTimeout.restore();\n        });\n\n        it(\"sends the `connect` DDP message\", () => {\n            const ddp = new DDP(options);\n            ddp.socket.send = sinon.spy();\n            ddp.socket.emit(\"open\");\n            expect(ddp.socket.send).to.have.been.calledWith({\n                msg: \"connect\",\n                version: \"1\",\n                support: [\"1\"]\n            });\n        });\n\n    });\n\n    describe(\"`socket` `close` handler\", () => {\n\n        beforeEach(() => {\n            sinon.stub(global, \"setTimeout\").callsFake(fn => fn());\n        });\n        afterEach(() => {\n            global.setTimeout.restore();\n        });\n\n        it(\"emits the `disconnected` event\", () => {\n            const ddp = new DDP(options);\n            ddp.emit = sinon.spy();\n            ddp.socket.emit(\"close\");\n            expect(ddp.emit).to.have.been.calledWith(\"disconnected\");\n        });\n\n        it(\"sets the status to `disconnected`\", () => {\n            const ddp = new DDP(options);\n            ddp.status = \"connected\";\n            ddp.emit = sinon.spy();\n            ddp.socket.emit(\"close\");\n            expect(ddp.status).to.equal(\"disconnected\");\n        });\n\n        it(\"schedules a reconnection unless `options.autoReconnect === false`\", () => {\n            const ddp = new DDP(options);\n            ddp.socket.open = sinon.spy();\n            ddp.socket.emit(\"close\");\n            expect(ddp.socket.open).to.have.callCount(1);\n        });\n\n        it(\"doesn't schedule a reconnection when `options.autoReconnect === false`\", () => {\n            const ddp = new DDP({\n                ...options,\n                autoReconnect: false\n            });\n            ddp.socket.open = sinon.spy();\n            ddp.socket.emit(\"close\");\n            expect(ddp.socket.open).to.have.callCount(0);\n        });\n\n    });\n\n    describe(\"`socket` `message:in` handler\", () => {\n\n        beforeEach(() => {\n            sinon.stub(global, \"setTimeout\").callsFake(fn => fn());\n        });\n        afterEach(() => {\n            global.setTimeout.restore();\n        });\n\n        it(\"responds to `ping` DDP messages\", () => {\n            const ddp = new DDP(options);\n            ddp.socket.send = sinon.spy();\n            ddp.socket.emit(\"message:in\", {\n                id: \"id\",\n                msg: \"ping\"\n            });\n            expect(ddp.socket.send).to.have.been.calledWith({\n                id: \"id\",\n                msg: \"pong\"\n            });\n        });\n\n        it(\"triggers `messageQueue` processing upon connection\", () => {\n            const ddp = new DDP(options);\n            ddp.emit = sinon.spy();\n            ddp.messageQueue.process = sinon.spy();\n            ddp.socket.emit(\"message:in\", {msg: \"connected\"});\n            expect(ddp.messageQueue.process).to.have.callCount(1);\n        });\n\n        it(\"sets the status to `connected` upon connection\", () => {\n            const ddp = new DDP(options);\n            ddp.emit = sinon.spy();\n            ddp.socket.emit(\"message:in\", {msg: \"connected\"});\n            expect(ddp.status).to.equal(\"connected\");\n        });\n\n        it(\"emits public DDP messages as events\", () => {\n            const ddp = new DDP(options);\n            ddp.emit = sinon.spy();\n            const message = {\n                id: \"id\",\n                msg: \"result\"\n            };\n            ddp.socket.emit(\"message:in\", message);\n            expect(ddp.emit).to.have.been.calledWith(\"result\", message);\n        });\n\n        it(\"ignores unknown (or non public) DDP messages\", () => {\n            const ddp = new DDP(options);\n            ddp.emit = sinon.spy();\n            const message = {\n                id: \"id\",\n                msg: \"not-a-ddp-message\"\n            };\n            ddp.socket.emit(\"message:in\", message);\n            expect(ddp.emit).to.have.callCount(0);\n        });\n\n    });\n\n    describe(\"`messageQueue` consumer\", () => {\n\n        it(\"acks if `status` is `connected`\", () => {\n            const ddp = new DDP(options);\n            ddp.status = \"connected\";\n            const ack = ddp.messageQueue.consumer({});\n            expect(ack).to.equal(true);\n        });\n\n        it(\"doesn't ack if `status` is `disconnected`\", () => {\n            const ddp = new DDP(options);\n            ddp.status = \"disconnected\";\n            const ack = ddp.messageQueue.consumer({});\n            expect(ack).to.equal(false);\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/unit/queue.js",
    "content": "import chai, {expect} from \"chai\";\nimport sinon from \"sinon\";\nimport sinonChai from \"sinon-chai\";\n\nchai.use(sinonChai);\n\nimport Queue from \"../../src/queue\";\n\ndescribe(\"`Queue` class\", () => {\n\n    describe(\"`push` method\", () => {\n\n        it(\"adds an element to the queue\", () => {\n            const q = new Queue();\n            q.process = sinon.spy();\n            const element = {};\n            q.push(element);\n            expect(q.queue).to.include(element);\n        });\n\n        it(\"triggers processing\", () => {\n            const q = new Queue();\n            q.process = sinon.spy();\n            const element = {};\n            q.push(element);\n            expect(q.process).to.have.callCount(1);\n        });\n\n    });\n\n    describe(\"`process` method\", () => {\n\n        it(\"calls the consumer on each element of the queue\", () => {\n            const consumer = sinon.spy(() => true);\n            const q = new Queue(consumer);\n            q.queue = [0, 1, 2];\n            q.process();\n            expect(consumer).to.have.been.calledWith(0);\n            expect(consumer).to.have.been.calledWith(1);\n            expect(consumer).to.have.been.calledWith(2);\n            expect(consumer).to.have.callCount(3);\n        });\n\n        it(\"removes elements from the queue\", () => {\n            const consumer = sinon.spy(() => true);\n            const q = new Queue(consumer);\n            q.queue = [0, 1, 2];\n            q.process();\n            expect(q.queue.length).to.equal(0);\n        });\n\n        it(\"doesn't remove elements from the queue if the consumer doesn't ack\", () => {\n            const consumer = sinon.spy(() => false);\n            const q = new Queue(consumer);\n            q.queue = [0, 1, 2];\n            q.process();\n            expect(consumer).to.have.been.calledWith(0);\n            expect(consumer).to.have.callCount(1);\n            expect(q.queue.length).to.equal(3);\n        });\n\n    });\n\n    describe(\"`empty` method\", () => {\n\n        it(\"empties the queue\", () => {\n            const q = new Queue();\n            q.process = sinon.spy();\n            const element = {};\n            q.push(element);\n            expect(q.queue.length).to.equal(1);\n            q.empty();\n            expect(q.queue.length).to.equal(0);\n        });\n\n    });\n\n\n});\n"
  },
  {
    "path": "test/unit/socket.js",
    "content": "import chai, {expect} from \"chai\";\nimport sinon from \"sinon\";\nimport sinonChai from \"sinon-chai\";\n\nchai.use(sinonChai);\n\nimport Socket from \"../../src/socket\";\n\nclass SocketConstructorMock {\n    close () {}\n    send () {}\n}\n\ndescribe(\"`Socket` class\", () => {\n\n    describe(\"`send` method\", () => {\n\n        it(\"sends a message through the `rawSocket`\", () => {\n            const socket = new Socket();\n            socket.rawSocket = {\n                send: sinon.spy()\n            };\n            socket.send({});\n            expect(socket.rawSocket.send).to.have.callCount(1);\n        });\n\n        it(\"stringifies the object to send\", () => {\n            const socket = new Socket();\n            socket.rawSocket = {\n                send: sinon.spy()\n            };\n            const object = {\n                a: \"a\"\n            };\n            const expectedMessage = JSON.stringify(object);\n            socket.send(object);\n            expect(socket.rawSocket.send).to.have.been.calledWith(expectedMessage);\n        });\n\n        it(\"emits a `message:out` event\", () => {\n            const socket = new Socket();\n            socket.rawSocket = {\n                send: sinon.spy()\n            };\n            socket.emit = sinon.spy();\n            const object = {\n                a: \"a\"\n            };\n            socket.send(object);\n            expect(socket.emit).to.have.been.calledWith(\"message:out\", object);\n        });\n\n    });\n\n    describe(\"`open` method\", () => {\n\n        it(\"no-op if `rawSocket` is already defined\", () => {\n            const socket = new Socket(SocketConstructorMock);\n            const rawSocket = {};\n            socket.rawSocket = rawSocket;\n            socket.open();\n            // Test, for instance, that `rawSocket` has not been replaced.\n            expect(socket.rawSocket).to.equal(rawSocket);\n        });\n\n        it(\"instantiates a `SocketConstructor`\", () => {\n            const socket = new Socket(SocketConstructorMock);\n            socket.open();\n            expect(socket.rawSocket).to.be.an.instanceOf(SocketConstructorMock);\n        });\n\n        it(\"registers handlers for `rawSocket` events\", () => {\n            const socket = new Socket(SocketConstructorMock);\n            socket.open();\n            expect(socket.rawSocket.onopen).to.be.a(\"function\");\n            expect(socket.rawSocket.onclose).to.be.a(\"function\");\n            expect(socket.rawSocket.onerror).to.be.a(\"function\");\n            expect(socket.rawSocket.onmessage).to.be.a(\"function\");\n        });\n\n    });\n\n    describe(\"`close` method\", () => {\n\n        it(\"closes the `rawSocket`\", () => {\n            const socket = new Socket(SocketConstructorMock);\n            socket.open();\n            socket.rawSocket.close = sinon.spy();\n            socket.close();\n            expect(socket.rawSocket.close).to.have.callCount(1);\n        });\n\n        it(\"doesn't throw if there's no `rawSocket`\", () => {\n            const socket = new Socket(SocketConstructorMock);\n            const peacemaker = () => {\n                socket.close();\n            };\n            expect(peacemaker).not.to.throw();\n        });\n\n    });\n\n    describe(\"`rawSocket` `onopen` handler\", () => {\n\n        it(\"emits an `open` event\", () => {\n            const socket = new Socket(SocketConstructorMock);\n            const handler = sinon.spy();\n            socket.on(\"open\", handler);\n            socket.open();\n            socket.rawSocket.onopen();\n            expect(handler).to.have.callCount(1);\n        });\n\n    });\n\n    describe(\"`rawSocket` `onclose` handler\", () => {\n\n        it(\"emits a `close` event\", () => {\n            const socket = new Socket(SocketConstructorMock);\n            const handler = sinon.spy();\n            socket.on(\"close\", handler);\n            socket.open();\n            socket.rawSocket.onclose();\n            expect(handler).to.have.callCount(1);\n        });\n\n        it(\"null-s the `rawSocket` property\", () => {\n            const socket = new Socket(SocketConstructorMock);\n            socket.open();\n            socket.rawSocket.onclose();\n            expect(socket.rawSocket).to.equal(null);\n        });\n\n    });\n\n    describe(\"`rawSocket` `onerror` handler\", () => {\n\n        it(\"closes `rawSocket` (by calling `rawSocket.close`)\", () => {\n            const socket = new Socket(SocketConstructorMock);\n            socket.open();\n            const rawSocket = socket.rawSocket;\n            rawSocket.close = sinon.spy();\n            socket.rawSocket.onerror();\n            expect(rawSocket.close).to.have.callCount(1);\n        });\n\n        it(\"de-registers the `rawSocket.onclose` callback\", () => {\n            const socket = new Socket(SocketConstructorMock);\n            socket.open();\n            const rawSocket = socket.rawSocket;\n            expect(rawSocket).to.have.property(\"onclose\");\n            socket.rawSocket.onerror();\n            expect(rawSocket).not.to.have.property(\"onclose\");\n        });\n\n        it(\"emits a `close` event\", () => {\n            const socket = new Socket(SocketConstructorMock);\n            const handler = sinon.spy();\n            socket.on(\"close\", handler);\n            socket.open();\n            socket.rawSocket.onerror();\n            expect(handler).to.have.callCount(1);\n        });\n\n        it(\"null-s the `rawSocket` property\", () => {\n            const socket = new Socket(SocketConstructorMock);\n            socket.open();\n            socket.rawSocket.onerror();\n            expect(socket.rawSocket).to.equal(null);\n        });\n\n    });\n\n    describe(\"`rawSocket` `onmessage` handler\", () => {\n\n        it(\"parses message data into an object\", () => {\n            const socket = new Socket(SocketConstructorMock);\n            sinon.stub(JSON, \"parse\");\n            socket.open();\n            socket.rawSocket.onmessage({data: \"message\"});\n            expect(JSON.parse).to.have.been.calledWith(\"message\");\n            JSON.parse.restore();\n        });\n\n        it(\"ignores malformed messages\", () => {\n            const socket = new Socket(SocketConstructorMock);\n            sinon.stub(JSON, \"parse\").throws();\n            socket.open();\n            expect(socket.rawSocket.onmessage).not.to.throw();\n            JSON.parse.restore();\n        });\n\n        it(\"emits `message:in` events\", () => {\n            const socket = new Socket(SocketConstructorMock);\n            const handler = sinon.spy();\n            socket.on(\"message:in\", handler);\n            socket.open();\n            socket.rawSocket.onmessage({data: JSON.stringify({a: \"a\"})});\n            expect(handler).to.have.callCount(1);\n            expect(handler).to.have.been.calledWith({a: \"a\"});\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/unit/utils.js",
    "content": "import {expect} from \"chai\";\n\nimport {contains, uniqueId} from \"../../src/utils\";\n\ndescribe(\"`utils` object\", () => {\n\n    describe(\"`contains` function\", () => {\n\n        it(\"returns true if the first parameter contains the second parameter\", () => {\n            const array = [\"element\"];\n            const element = \"element\";\n            expect(contains(array, element)).to.equal(true);\n        });\n\n        it(\"returns false if the first parameter doesn't contain the second parameter\", () => {\n            const array = [\"element\"];\n            const element = \"different-element\";\n            expect(contains(array, element)).to.equal(false);\n        });\n\n    });\n\n    describe(\"`uniqueId` function\", () => {\n\n        it(\"should return a different string each time it's called\", () => {\n            const ret1 = uniqueId();\n            const ret2 = uniqueId();\n            expect(ret1).not.to.equal(ret2);\n        });\n\n    });\n\n});\n"
  }
]