draft: refactor project so it sits on top of a standalone app
This commit is contained in:
+3
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
tidelift: npm/sockjs-client
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
# This is the list of sockjs-client authors for copyright purposes.
|
||||
#
|
||||
# This does not necessarily list everyone who has contributed code, since in
|
||||
# some cases, their employer may be the copyright holder. To see the full list
|
||||
# of contributors, see the revision history in source control.
|
||||
Bryce Kahle
|
||||
Marek Majkowski
|
||||
VMWare
|
||||
LearnBoost
|
||||
Julian Aubourg
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
1.4.0
|
||||
==
|
||||
* Add `timeout` option to set a minimum transport timeout. Fixes #403
|
||||
* Update dev deps to fix security warnings from `npm audit`
|
||||
* Guard against null `this._transport` in `debug` statement. Fixes #448
|
||||
|
||||
1.3.0
|
||||
==
|
||||
* Revert `debug` to `^3` because v4 starts using ES6. Fixes #457
|
||||
|
||||
1.2.0
|
||||
==
|
||||
* Update all outdated dependencies
|
||||
* Switch to karma and browserstack for running automated browser tests
|
||||
|
||||
1.1.5
|
||||
==
|
||||
* Wrap the the contentWindow access in a try/catch block when in setTimeout #363
|
||||
* Revised example in README #356
|
||||
* Fix connection close when Transport timeout #358
|
||||
* Fixed crash with react-native@0.45.1 on Android #386
|
||||
* Update jsDelivr link #404, #405
|
||||
* Remove Sauce Labs unsupported browsers
|
||||
* Add link to rust server implementation #411
|
||||
* location.protocol should include final `:` #396
|
||||
|
||||
1.1.4
|
||||
==
|
||||
* Upgrade `debug` and fix object key literal mangling, fixes regression in Opera 11.10 #359
|
||||
* Trim descriptions in package.json and bower.json - #372
|
||||
|
||||
1.1.3
|
||||
==
|
||||
* Bad publish to NPM (removed)
|
||||
|
||||
1.1.2
|
||||
==
|
||||
|
||||
* Ensure both sender and receiver are cleaned upon close - #342
|
||||
* Remove event listeners before calling `close` - #344
|
||||
* Update documentation links - #351, #339, #316
|
||||
* Explicitly export `undefined` when `WebSocket` does not exist. Fixes Webpack. #321
|
||||
* Include `dist` folder on npm - #265
|
||||
* Simplify build setup
|
||||
* Update to Node.js 6.9
|
||||
* Add sourcemap for minified version
|
||||
* Remove unused String.trim shim
|
||||
|
||||
1.1.1
|
||||
==
|
||||
|
||||
* Do not pass `protocols` or `options` arguments to browser WebSocket constructor - #309
|
||||
|
||||
1.1.0
|
||||
==
|
||||
|
||||
* Fix IE7/8 usage of `console.log` which does not have `apply` - #279
|
||||
* Remove `dbg` global variable - #282
|
||||
* Bump `faye-websocket` version to `0.11.0` - #267
|
||||
* Optimize `arguments` usage - #263
|
||||
* Add sourcemap file to dist folder - #237
|
||||
* Add way to transparently pass transport-specific options - #272
|
||||
|
||||
1.0.3
|
||||
==
|
||||
|
||||
* Use `https` module for xhr requests in node when url uses https - #254
|
||||
|
||||
1.0.2
|
||||
==
|
||||
|
||||
* Fix iframe info receiver url
|
||||
* Move iframe.contentWindow check inside setTimeout - #246
|
||||
|
||||
1.0.1
|
||||
==
|
||||
|
||||
* Use proper base url for iframe-based info receiver - #249
|
||||
* Don't register unload event in chrome packaged app - #223
|
||||
* Allow custom session ids - #250
|
||||
* Remove version property from bower.json - #247
|
||||
* Update example CDN url - #244
|
||||
|
||||
1.0.0
|
||||
===
|
||||
|
||||
* Simplify url handling by delegating to `url-parse` - #242
|
||||
* Upgrade to `url-parse` 1.0.1 to fix colon issue if auth has no password
|
||||
|
||||
1.0.0-beta.13
|
||||
===
|
||||
|
||||
* Transport timeout on connection should fallback - #238
|
||||
|
||||
1.0.0-beta.12
|
||||
====
|
||||
|
||||
* Upgrade `url-parse` to 1.0.0 to fix #218 again
|
||||
|
||||
1.0.0-beta.10
|
||||
====
|
||||
|
||||
* Upgrade `url-parse` to 0.2.3 to fix #222
|
||||
|
||||
1.0.0-beta.9
|
||||
====
|
||||
|
||||
* Upgrade `url-parse` to 0.2.1 to fix 'too much recursion' errors
|
||||
|
||||
1.0.0-beta.8
|
||||
====
|
||||
|
||||
* Upgrade `url-parse` to 0.2.0 to fix inheritance issues
|
||||
|
||||
1.0.0-beta.7
|
||||
====
|
||||
|
||||
* Upgrade `url-parse` to 0.1.5 to fix #218
|
||||
* Don't strip basic auth from url - #219
|
||||
|
||||
1.0.0-beta.6
|
||||
====
|
||||
|
||||
* Upgrade `url-parse` to 0.1.3 to avoid CSP issues
|
||||
|
||||
1.0.0-beta.5
|
||||
=====
|
||||
|
||||
* Upgrade `url-parse` to 0.1.1 to fix #214
|
||||
|
||||
1.0.0-beta.4
|
||||
=====
|
||||
|
||||
* Upgrade `url-parse` to 0.1.0 and `sockjs` to 0.3.11
|
||||
* Update .npmignore
|
||||
|
||||
1.0.0-beta.3
|
||||
=====
|
||||
|
||||
* Move `debug` from devDependencies to dependencies
|
||||
|
||||
1.0.0-beta.2
|
||||
=====
|
||||
|
||||
* Relax requirements when using same origin XHR - #80
|
||||
* Upgrade to JSON3 from JSON2 - #123
|
||||
* Package library with browserify supporting the UMD pattern - #184
|
||||
* Move tests to JavaScript
|
||||
* Add Gulp.js build script
|
||||
* Fix getOrigin for file:/// urls and standard ports - #173
|
||||
* Add onerror event handlers to Websockets - #169
|
||||
* Increase RTO lower bound to prevent spurious timeouts on IE8/9 - #161
|
||||
* Use window.crypto for random values when available - #128
|
||||
* Fix handling of listeners added and removed mid-dispatch - #127
|
||||
* Fix XHR Streaming for IE8 - #83
|
||||
* Remove explicit AMD name - #107
|
||||
* Check for an empty response from /info request - #143
|
||||
* Add Content-Type to XHR requests to fix issue over HTTPS on Galaxy S4 - #164
|
||||
* Fix iframe fallback when message is sent from a popup in IE7/8 - #166
|
||||
* Add support for query strings on the url - #72
|
||||
* Now works inside of Web Workers - #181
|
||||
* Support EventSource / Server Sent Events outside of iframes - #201
|
||||
* Rename protocols to transports - #65
|
||||
* Allow transports which need the body to trigger on 'interactive' readyState - #175
|
||||
* try/catch access to document.domain - #187
|
||||
* Use `window.location` instead of `document.location` - #195
|
||||
* Allow usage from node.js with same API
|
||||
|
||||
0.3.4
|
||||
=====
|
||||
|
||||
* Mentioned njoyce's fork of sockjs-gevent.
|
||||
* #90 - Don't catch onbeforeunload event - it breaks javascript://
|
||||
links in IE.
|
||||
* IE mangles 204 response code for 1223 on ajax, see:
|
||||
http://bugs.jquery.com/ticket/1450
|
||||
* Make `new` optional for SockJS constructor (via substack).
|
||||
* It is impossible to cancel JSONP polling request - compensate for that.
|
||||
* Refactored EventEmitter prototype (used only internally)
|
||||
* #66 - Failure to post data to /xhr_send should kill the session
|
||||
|
||||
|
||||
0.3.2
|
||||
=====
|
||||
|
||||
* #77 - Getting /info on modern browsers when html is served from
|
||||
file:// urls was broken.
|
||||
|
||||
0.3.1
|
||||
=====
|
||||
|
||||
* #61 - Meteor guys found that we unintentionally catch "onopen" errors.
|
||||
* #63 - Meteorjs guys found that xhr-streaming on Safari sometimes
|
||||
left busy cursor running.
|
||||
* Increased allowed time for websocket transport (from 1 rtt to 2),
|
||||
this should make ws transport more reliable over SSL, at the cost
|
||||
of slightly longer connection time for users with blocked ws.
|
||||
* #57 - previous fix didn't really work, sockjs-client still left
|
||||
a mess in browsers history when using iframe transports. This
|
||||
is fixed now.
|
||||
* #60 - Opera 12 (next) claims to do AJAX2 / CORS, but can't
|
||||
do xhr-streaming.
|
||||
* #58 - onunload test sometimes failed on Safari on windows
|
||||
* Updated readme WRT websocket protocols
|
||||
* Updated readme WRT deployments on heroku
|
||||
* Add minimalistic license block to every source file.
|
||||
|
||||
|
||||
0.3.0
|
||||
=====
|
||||
|
||||
* Temporarily disabled iframe tests - they are failing unpredictably.
|
||||
* #57 - pointing an iframe to "about:blank" during cleanup caused
|
||||
Opera to messup history.
|
||||
* #55 - Improved iframe abstraction (reduced a possible mem leak)
|
||||
* Refactored AJAX abstractions, for better CORS handing - again.
|
||||
* Add additional parent origin security check to an iframe.
|
||||
* Urls with hashes or query strings can't be passed to SockJS.
|
||||
* #18 - Mention workaround for Firefox ESC key issue
|
||||
* #53 - AMD compliance
|
||||
* sockjs/sockjs-protocol#28 - always use square brackets for
|
||||
websocket frames
|
||||
* #51 - initial support for IE10 - try XHR before XDR
|
||||
* #28 - handle onunload / onbeforeunload in a more robust fashion
|
||||
* #49 - support SockJS-client being used from files served from
|
||||
file:// urls.
|
||||
|
||||
|
||||
0.2.1
|
||||
=====
|
||||
|
||||
* "smoke-latency.html" test was unnecesairly sending too much data.
|
||||
* Bumped core dependencies (coffee-script and uglify-js)
|
||||
* Minor updates to the README, few cosmetic changes in the code.
|
||||
|
||||
|
||||
0.2.0
|
||||
=====
|
||||
|
||||
* The API had changed - use `protocols_whitelist` option instead of
|
||||
passing an array of protocols as a second argument to SockJS constructor.
|
||||
* Dropped 'chunking-test' functionality and replace it with 'info'.
|
||||
* Rewritten protocol-choosing alogirthm, see "utils.detectProtocols" method.
|
||||
* Use dynamic protocol timeouts based on RTT, not hardcoded 5 seconds
|
||||
* #34 - Don't ever reuse `session_id`, especially when trying
|
||||
fallback protocols.
|
||||
* The test server got moved from SockJS-client to SockJS-node.
|
||||
* Don't test unicode surrogates - it can't work in some environments.
|
||||
* XHR/XDR helpers were rewritten, ajax transports were simplified.
|
||||
* Added a domain check in the iframe to improve security.
|
||||
* SockJS will now trigger 1002 error if there is a problem during handshake
|
||||
instead of 2000 error.
|
||||
* Smoke-throughput test is renamed to smoke-latency.
|
||||
|
||||
0.1.2
|
||||
=====
|
||||
|
||||
* #29 - Allow all unicode characters to be send over SockJS.
|
||||
* #15 - SockJS should now work fine even if the connection is started
|
||||
in HEAD, before BODY is loaded.
|
||||
* #28 - In rare circumstances WebSocket connection can be left intact
|
||||
after the page is unloaded in FireFox.
|
||||
* Updated scripts to work with Node 0.6.
|
||||
* Initial work to do better QUnit testing.
|
||||
* Updated the minifying script (always escape unicode chars, remove
|
||||
trailing comment).
|
||||
* Use string instead of array of chars (utils.js:random_number_string).
|
||||
|
||||
|
||||
0.1.1
|
||||
=====
|
||||
|
||||
* #21 Get JsonP transport working on IE9 (Vladimir Dronnikov).
|
||||
* #26 Emit heartbeat event.
|
||||
* #27 Include license inline.
|
||||
|
||||
|
||||
0.1.0
|
||||
=====
|
||||
|
||||
* SockJS-client can only send UTF-8 encodable strings. Previously we
|
||||
took advantage of rich data structures and automatically
|
||||
json-encoded them, but this got removed. Now, all data passed to
|
||||
`send` will be converted to string. This is also how native
|
||||
* `status` property on `EventClose` is renamed to `code`
|
||||
as per Websocket API
|
||||
WebSockets behave.
|
||||
* The test server was updated to new `sockjs-node` API
|
||||
* Fixed problem with Jsonp-polling transport on IE9
|
||||
* Repository was moved - updated links.
|
||||
|
||||
|
||||
0.0.4
|
||||
=====
|
||||
|
||||
* All transports were refactored, some transports were introduced:
|
||||
htmlfile and separate xhr-streaming.
|
||||
* Added logic to detect support for http chunking, and thus a
|
||||
possibility to rule out streaming transports before running them.
|
||||
* Added 'cookie' option, useful for cookie-based load balancing
|
||||
(currently, it make a difference only for IE).
|
||||
* Added hack to prevent EventSource from crashing Safari and Chrome.
|
||||
* Loads and loads of other small and medium changes.
|
||||
|
||||
|
||||
0.0.2
|
||||
=====
|
||||
|
||||
* Initial support for JSESSIONID based load balancing. Currently
|
||||
doesn't play nicely with IE XDomainRequest transport.
|
||||
|
||||
|
||||
0.0.1
|
||||
=====
|
||||
|
||||
* Initial release.
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011-2018 The sockjs-client Authors.
|
||||
|
||||
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.
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
|
||||
# SockJS-client
|
||||
|
||||
[](https://www.npmjs.com/package/sockjs-client)[](https://travis-ci.org/sockjs/sockjs-client)[](https://david-dm.org/sockjs/sockjs-client)[](https://gitter.im/sockjs/sockjs-client)
|
||||
[](https://www.browserstack.com/automate/public-build/dW9YdlFsSEI5VzNBVVk5ZS9XT0xaTjJVQkhQMkRkNlZBQURiSWNWMC9jaz0tLXRJM05RbW1tTCt5TlhHaVgycFJUYmc9PQ==--e3ef9b9a9fa071084e6d87874b5fc65b71273821)
|
||||
|
||||
# Supporting SockJS
|
||||
|
||||
Tidelift gives software development teams a single source for purchasing and maintaining their software, with professional grade assurances from the experts who know it best, while seamlessly integrating with existing tools.
|
||||
|
||||
[Get supported sockjs-client with the Tidelift Subscription](https://tidelift.com/subscription/pkg/npm-sockjs-client?utm_source=npm-sockjs-client&utm_medium=referral&utm_campaign=readme)
|
||||
|
||||
# Summary
|
||||
|
||||
SockJS is a browser JavaScript library that provides a WebSocket-like
|
||||
object. SockJS gives you a coherent, cross-browser, Javascript API
|
||||
which creates a low latency, full duplex, cross-domain communication
|
||||
channel between the browser and the web server.
|
||||
|
||||
Under the hood SockJS tries to use native WebSockets first. If that
|
||||
fails it can use a variety of browser-specific transport protocols and
|
||||
presents them through WebSocket-like abstractions.
|
||||
|
||||
SockJS is intended to work for all modern browsers and in environments
|
||||
which don't support the WebSocket protocol -- for example, behind restrictive
|
||||
corporate proxies.
|
||||
|
||||
SockJS-client does require a server counterpart:
|
||||
|
||||
* [SockJS-node](https://github.com/sockjs/sockjs-node) is a SockJS
|
||||
server for Node.js.
|
||||
|
||||
|
||||
Philosophy:
|
||||
|
||||
* The API should follow
|
||||
[HTML5 Websockets API](https://www.w3.org/TR/websockets/) as
|
||||
closely as possible.
|
||||
* All the transports must support cross domain connections out of the
|
||||
box. It's possible and recommended to host a SockJS server on a
|
||||
different server than your main web site.
|
||||
* There is support for at least one streaming protocol for every
|
||||
major browser.
|
||||
* Streaming transports should work cross-domain and
|
||||
should support cookies (for cookie-based sticky sessions).
|
||||
* Polling transports are used as a fallback for old browsers and
|
||||
hosts behind restrictive proxies.
|
||||
* Connection establishment should be fast and lightweight.
|
||||
* No Flash inside (no need to open port 843 - which doesn't work
|
||||
through proxies, no need to host 'crossdomain.xml', no need
|
||||
[to wait for 3 seconds](https://github.com/gimite/web-socket-js/issues/49)
|
||||
in order to detect problems)
|
||||
|
||||
|
||||
Subscribe to
|
||||
[SockJS mailing list](https://groups.google.com/forum/#!forum/sockjs) for
|
||||
discussions and support.
|
||||
|
||||
# SockJS family
|
||||
|
||||
* [SockJS-client](https://github.com/sockjs/sockjs-client) JavaScript client library
|
||||
* [SockJS-node](https://github.com/sockjs/sockjs-node) Node.js server
|
||||
* [SockJS-erlang](https://github.com/sockjs/sockjs-erlang) Erlang server
|
||||
* [SockJS-cyclone](https://github.com/flaviogrossi/sockjs-cyclone) Python/Cyclone/Twisted server
|
||||
* [SockJS-tornado](https://github.com/MrJoes/sockjs-tornado) Python/Tornado server
|
||||
* [SockJS-twisted](https://github.com/DesertBus/sockjs-twisted/) Python/Twisted server
|
||||
* [SockJS-aiohttp](https://github.com/aio-libs/sockjs/) Python/Aiohttp server
|
||||
* [Spring Framework](https://projects.spring.io/spring-framework) Java [client](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web.html#websocket-fallback-sockjs-client) & server
|
||||
* [vert.x](https://github.com/vert-x/vert.x) Java/vert.x server
|
||||
* [Xitrum](https://xitrum-framework.github.io/) Scala server
|
||||
* [Atmosphere Framework](https://github.com/Atmosphere/atmosphere) JavaEE Server, Play Framework, Netty, Vert.x
|
||||
* [Actix SockJS](https://github.com/fafhrd91/actix-sockjs) Rust Server, Actix Framework
|
||||
|
||||
Work in progress:
|
||||
|
||||
* [SockJS-ruby](https://github.com/nyarly/sockjs-ruby)
|
||||
* [SockJS-netty](https://github.com/cgbystrom/sockjs-netty)
|
||||
* [SockJS-gevent](https://github.com/ksava/sockjs-gevent) ([SockJS-gevent fork](https://github.com/njoyce/sockjs-gevent))
|
||||
* [pyramid-SockJS](https://github.com/fafhrd91/pyramid_sockjs)
|
||||
* [wildcloud-websockets](https://github.com/wildcloud/wildcloud-websockets)
|
||||
* [wai-SockJS](https://github.com/Palmik/wai-sockjs)
|
||||
* [SockJS-perl](https://github.com/vti/sockjs-perl)
|
||||
* [SockJS-go](https://github.com/igm/sockjs-go/)
|
||||
* [syp.biz.SockJS.NET](https://github.com/sypbiz/SockJS.NET) - .NET port of the SockJS client
|
||||
|
||||
# Getting Started
|
||||
|
||||
SockJS mimics the [WebSockets API](https://www.w3.org/TR/websockets/),
|
||||
but instead of `WebSocket` there is a `SockJS` Javascript object.
|
||||
|
||||
First, you need to load the SockJS JavaScript library. For example, you can
|
||||
put that in your HTML head:
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
|
||||
```
|
||||
|
||||
After the script is loaded you can establish a connection with the
|
||||
SockJS server. Here's a simple example:
|
||||
|
||||
```javascript
|
||||
var sock = new SockJS('https://mydomain.com/my_prefix');
|
||||
sock.onopen = function() {
|
||||
console.log('open');
|
||||
sock.send('test');
|
||||
};
|
||||
|
||||
sock.onmessage = function(e) {
|
||||
console.log('message', e.data);
|
||||
sock.close();
|
||||
};
|
||||
|
||||
sock.onclose = function() {
|
||||
console.log('close');
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
# SockJS-client API
|
||||
|
||||
## SockJS class
|
||||
|
||||
Similar to the 'WebSocket' API, the 'SockJS' constructor takes one, or more arguments:
|
||||
|
||||
```javascript
|
||||
var sockjs = new SockJS(url, _reserved, options);
|
||||
```
|
||||
|
||||
`url` may contain a query string, if one is desired.
|
||||
|
||||
Where `options` is a hash which can contain:
|
||||
|
||||
* **server (string)**
|
||||
|
||||
String to append to url for actual data connection. Defaults to a random 4 digit number.
|
||||
|
||||
* **transports (string OR array of strings)**
|
||||
|
||||
Sometimes it is useful to disable some fallback transports. This
|
||||
option allows you to supply a list transports that may be used by
|
||||
SockJS. By default all available transports will be used.
|
||||
|
||||
* **sessionId (number OR function)**
|
||||
|
||||
Both client and server use session identifiers to distinguish connections.
|
||||
If you specify this option as a number, SockJS will use its random string
|
||||
generator function to generate session ids that are N-character long
|
||||
(where N corresponds to the number specified by **sessionId**).
|
||||
When you specify this option as a function, the function must return a
|
||||
randomly generated string. Every time SockJS needs to generate a session
|
||||
id it will call this function and use the returned string directly.
|
||||
If you don't specify this option, the default is to use the default random
|
||||
string generator to generate 8-character long session ids.
|
||||
|
||||
* **timeout (number)**
|
||||
|
||||
Specify a minimum timeout in milliseconds to use for the transport connections.
|
||||
By default this is dynamically calculated based on the measured RTT and
|
||||
the number of expected round trips. This setting will establish a minimum,
|
||||
but if the calculated timeout is higher, that will be used.
|
||||
|
||||
Although the 'SockJS' object tries to emulate the 'WebSocket'
|
||||
behaviour, it's impossible to support all of its features. An
|
||||
important SockJS limitation is the fact that you're not allowed to
|
||||
open more than one SockJS connection to a single domain at a time.
|
||||
This limitation is caused by an in-browser limit of outgoing
|
||||
connections - usually [browsers don't allow opening more than two
|
||||
outgoing connections to a single domain](https://stackoverflow.com/questions/985431/max-parallel-http-connections-in-a-browser). A single SockJS session
|
||||
requires those two connections - one for downloading data, the other for
|
||||
sending messages. Opening a second SockJS session at the same time
|
||||
would most likely block, and can result in both sessions timing out.
|
||||
|
||||
Opening more than one SockJS connection at a time is generally a
|
||||
bad practice. If you absolutely must do it, you can use
|
||||
multiple subdomains, using a different subdomain for every
|
||||
SockJS connection.
|
||||
|
||||
# Supported transports, by browser (html served from http:// or https://)
|
||||
|
||||
_Browser_ | _Websockets_ | _Streaming_ | _Polling_
|
||||
----------------|------------------|-------------|-------------------
|
||||
IE 6, 7 | no | no | jsonp-polling
|
||||
IE 8, 9 (cookies=no) | no | xdr-streaming † | xdr-polling †
|
||||
IE 8, 9 (cookies=yes)| no | iframe-htmlfile | iframe-xhr-polling
|
||||
IE 10 | rfc6455 | xhr-streaming | xhr-polling
|
||||
Chrome 6-13 | hixie-76 | xhr-streaming | xhr-polling
|
||||
Chrome 14+ | hybi-10 / rfc6455| xhr-streaming | xhr-polling
|
||||
Firefox <10 | no ‡ | xhr-streaming | xhr-polling
|
||||
Firefox 10+ | hybi-10 / rfc6455| xhr-streaming | xhr-polling
|
||||
Safari 5.x | hixie-76 | xhr-streaming | xhr-polling
|
||||
Safari 6+ | rfc6455 | xhr-streaming | xhr-polling
|
||||
Opera 10.70+ | no ‡ | iframe-eventsource | iframe-xhr-polling
|
||||
Opera 12.10+ | rfc6455 | xhr-streaming | xhr-polling
|
||||
Konqueror | no | no | jsonp-polling
|
||||
|
||||
|
||||
* **†**: IE 8+ supports [XDomainRequest][^9], which is
|
||||
essentially a modified AJAX/XHR that can do requests across
|
||||
domains. But unfortunately it doesn't send any cookies, which
|
||||
makes it inappropriate for deployments when the load balancer uses
|
||||
JSESSIONID cookie to do sticky sessions.
|
||||
|
||||
* **‡**: Firefox 4.0 and Opera 11.00 and shipped with disabled
|
||||
Websockets "hixie-76". They can still be enabled by manually
|
||||
changing a browser setting.
|
||||
|
||||
# Supported transports, by browser (html served from file://)
|
||||
|
||||
Sometimes you may want to serve your html from "file://" address - for
|
||||
development or if you're using PhoneGap or similar technologies. But
|
||||
due to the Cross Origin Policy files served from "file://" have no
|
||||
Origin, and that means some of SockJS transports won't work. For this
|
||||
reason the SockJS transport table is different than usually, major
|
||||
differences are:
|
||||
|
||||
_Browser_ | _Websockets_ | _Streaming_ | _Polling_
|
||||
----------------|---------------|--------------------|-------------------
|
||||
IE 8, 9 | same as above | iframe-htmlfile | iframe-xhr-polling
|
||||
Other | same as above | iframe-eventsource | iframe-xhr-polling
|
||||
|
||||
# Supported transports, by name
|
||||
|
||||
_Transport_ | _References_
|
||||
---------------------|---------------
|
||||
websocket (rfc6455) | [rfc 6455][^10]
|
||||
websocket (hixie-76) | [draft-hixie-thewebsocketprotocol-76][^1]
|
||||
websocket (hybi-10) | [draft-ietf-hybi-thewebsocketprotocol-10][^2]
|
||||
xhr-streaming | Transport using [Cross domain XHR][^5] [streaming][^7] capability (readyState=3).
|
||||
xdr-streaming | Transport using [XDomainRequest][^9] [streaming][^7] capability (readyState=3).
|
||||
eventsource | [EventSource/Server-sent events][^4].
|
||||
iframe-eventsource | [EventSource/Server-sent events][^4] used from an [iframe via postMessage][^3].
|
||||
htmlfile | [HtmlFile][^8].
|
||||
iframe-htmlfile | [HtmlFile][^8] used from an [iframe via postMessage][^3].
|
||||
xhr-polling | Long-polling using [cross domain XHR][^5].
|
||||
xdr-polling | Long-polling using [XDomainRequest][^9].
|
||||
iframe-xhr-polling | Long-polling using normal AJAX from an [iframe via postMessage][^3].
|
||||
jsonp-polling | Slow and old fashioned [JSONP polling][^6]. This transport will show "busy indicator" (aka: "spinning wheel") when sending data.
|
||||
|
||||
|
||||
[^1]: https://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
|
||||
[^2]: https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
|
||||
[^3]: https://developer.mozilla.org/en/DOM/window.postMessage
|
||||
[^4]: https://html.spec.whatwg.org/multipage/comms.html#server-sent-events
|
||||
[^5]: https://secure.wikimedia.org/wikipedia/en/wiki/XMLHttpRequest#Cross-domain_requests
|
||||
[^6]: https://secure.wikimedia.org/wikipedia/en/wiki/JSONP
|
||||
[^7]: http://www.debugtheweb.com/test/teststreaming.aspx
|
||||
[^8]: http://cometdaily.com/2007/11/18/ie-activexhtmlfile-transport-part-ii/
|
||||
[^9]: https://blogs.msdn.microsoft.com/ieinternals/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds/
|
||||
[^10]: https://www.rfc-editor.org/rfc/rfc6455.txt
|
||||
|
||||
|
||||
# Connecting to SockJS without the client
|
||||
|
||||
Although the main point of SockJS is to enable browser-to-server
|
||||
connectivity, it is possible to connect to SockJS from an external
|
||||
application. Any SockJS server complying with 0.3 protocol does
|
||||
support a raw WebSocket url. The raw WebSocket url for the test server
|
||||
looks like:
|
||||
|
||||
* ws://localhost:8081/echo/websocket
|
||||
|
||||
You can connect any WebSocket RFC 6455 compliant WebSocket client to
|
||||
this url. This can be a command line client, external application,
|
||||
third party code or even a browser (though I don't know why you would
|
||||
want to do so).
|
||||
|
||||
|
||||
# Deployment
|
||||
|
||||
You should use a version of sockjs-client
|
||||
that supports the protocol used by your server. For example:
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
|
||||
```
|
||||
|
||||
|
||||
For server-side deployment tricks, especially about load balancing and
|
||||
session stickiness, take a look at the
|
||||
[SockJS-node readme](https://github.com/sockjs/sockjs-node#readme).
|
||||
|
||||
|
||||
# Development and testing
|
||||
|
||||
SockJS-client needs [node.js](https://nodejs.org/) for running a test
|
||||
server and JavaScript minification. If you want to work on
|
||||
SockJS-client source code, checkout the git repo and follow these
|
||||
steps:
|
||||
|
||||
cd sockjs-client
|
||||
npm install
|
||||
|
||||
To generate JavaScript, run:
|
||||
|
||||
gulp browserify
|
||||
|
||||
To generate minified JavaScript, run:
|
||||
|
||||
gulp browserify:min
|
||||
|
||||
Both commands output into the `build` directory.
|
||||
|
||||
## Testing
|
||||
|
||||
Automated testing provided by:
|
||||
|
||||
<a href="https://browserstack.com"><img src="img/Browserstack-logo@2x.png" height="50"></a>
|
||||
|
||||
Once you've compiled the SockJS-client you may want to check if your changes
|
||||
pass all the tests.
|
||||
|
||||
npm run test:browser_local
|
||||
|
||||
This will start [karma](https://karma-runner.github.io) and a test support server.
|
||||
|
||||
# Browser Quirks
|
||||
|
||||
There are various browser quirks which we don't intend to address:
|
||||
|
||||
* Pressing ESC in Firefox, before Firefox 20, closes the SockJS connection. For a workaround
|
||||
and discussion see [#18](https://github.com/sockjs/sockjs-client/issues/18).
|
||||
* `jsonp-polling` transport will show a "spinning wheel" (aka. "busy indicator")
|
||||
when sending data.
|
||||
* You can't open more than one SockJS connection to one domain at the
|
||||
same time due to [the browser's limit of concurrent connections](https://stackoverflow.com/questions/985431/max-parallel-http-connections-in-a-browser)
|
||||
(this limit is not counting native WebSocket connections).
|
||||
* Although SockJS is trying to escape any strange Unicode characters
|
||||
(even invalid ones - [like surrogates \xD800-\xDBFF](https://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Surrogates) or [\xFFFE and \xFFFF](https://en.wikipedia.org/wiki/Unicode#Character_General_Category))
|
||||
it's advisable to use only valid characters. Using invalid
|
||||
characters is a bit slower, and may not work with SockJS servers
|
||||
that have proper Unicode support.
|
||||
* Having a global function called `onmessage` or such is probably a
|
||||
bad idea, as it could be called by the built-in `postMessage` API.
|
||||
* From SockJS' point of view there is nothing special about
|
||||
SSL/HTTPS. Connecting between unencrypted and encrypted sites
|
||||
should work just fine.
|
||||
* Although SockJS does its best to support both prefix and cookie based
|
||||
sticky sessions, the latter may not work well cross-domain with
|
||||
browsers that don't accept third-party cookies by default (Safari).
|
||||
In order to get around this make sure you're connecting to SockJS
|
||||
from the same parent domain as the main site. For example
|
||||
'sockjs.a.com' is able to set cookies if you're connecting from
|
||||
'www.a.com' or 'a.com'.
|
||||
* Trying to connect from secure "https://" to insecure "http://" is
|
||||
not a good idea. The other way around should be fine.
|
||||
* Long polling is known to cause problems on Heroku, but a
|
||||
[workaround for SockJS is available](https://github.com/sockjs/sockjs-node/issues/57#issuecomment-5242187).
|
||||
* SockJS [websocket transport is more stable over SSL](https://github.com/sockjs/sockjs-client/issues/94). If
|
||||
you're a serious SockJS user then consider using SSL
|
||||
([more info](https://www.ietf.org/mail-archive/web/hybi/current/msg01605.html)).
|
||||
+5818
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+3
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+10
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
var transportList = require('./transport-list');
|
||||
|
||||
module.exports = require('./main')(transportList);
|
||||
|
||||
// TODO can't get rid of this until all servers do
|
||||
if ('_sockjs_onload' in global) {
|
||||
setTimeout(global._sockjs_onload, 1);
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, Event = require('./event')
|
||||
;
|
||||
|
||||
function CloseEvent() {
|
||||
Event.call(this);
|
||||
this.initEvent('close', false, false);
|
||||
this.wasClean = false;
|
||||
this.code = 0;
|
||||
this.reason = '';
|
||||
}
|
||||
|
||||
inherits(CloseEvent, Event);
|
||||
|
||||
module.exports = CloseEvent;
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, EventTarget = require('./eventtarget')
|
||||
;
|
||||
|
||||
function EventEmitter() {
|
||||
EventTarget.call(this);
|
||||
}
|
||||
|
||||
inherits(EventEmitter, EventTarget);
|
||||
|
||||
EventEmitter.prototype.removeAllListeners = function(type) {
|
||||
if (type) {
|
||||
delete this._listeners[type];
|
||||
} else {
|
||||
this._listeners = {};
|
||||
}
|
||||
};
|
||||
|
||||
EventEmitter.prototype.once = function(type, listener) {
|
||||
var self = this
|
||||
, fired = false;
|
||||
|
||||
function g() {
|
||||
self.removeListener(type, g);
|
||||
|
||||
if (!fired) {
|
||||
fired = true;
|
||||
listener.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
this.on(type, g);
|
||||
};
|
||||
|
||||
EventEmitter.prototype.emit = function() {
|
||||
var type = arguments[0];
|
||||
var listeners = this._listeners[type];
|
||||
if (!listeners) {
|
||||
return;
|
||||
}
|
||||
// equivalent of Array.prototype.slice.call(arguments, 1);
|
||||
var l = arguments.length;
|
||||
var args = new Array(l - 1);
|
||||
for (var ai = 1; ai < l; ai++) {
|
||||
args[ai - 1] = arguments[ai];
|
||||
}
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
listeners[i].apply(this, args);
|
||||
}
|
||||
};
|
||||
|
||||
EventEmitter.prototype.on = EventEmitter.prototype.addListener = EventTarget.prototype.addEventListener;
|
||||
EventEmitter.prototype.removeListener = EventTarget.prototype.removeEventListener;
|
||||
|
||||
module.exports.EventEmitter = EventEmitter;
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
function Event(eventType) {
|
||||
this.type = eventType;
|
||||
}
|
||||
|
||||
Event.prototype.initEvent = function(eventType, canBubble, cancelable) {
|
||||
this.type = eventType;
|
||||
this.bubbles = canBubble;
|
||||
this.cancelable = cancelable;
|
||||
this.timeStamp = +new Date();
|
||||
return this;
|
||||
};
|
||||
|
||||
Event.prototype.stopPropagation = function() {};
|
||||
Event.prototype.preventDefault = function() {};
|
||||
|
||||
Event.CAPTURING_PHASE = 1;
|
||||
Event.AT_TARGET = 2;
|
||||
Event.BUBBLING_PHASE = 3;
|
||||
|
||||
module.exports = Event;
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
'use strict';
|
||||
|
||||
/* Simplified implementation of DOM2 EventTarget.
|
||||
* http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget
|
||||
*/
|
||||
|
||||
function EventTarget() {
|
||||
this._listeners = {};
|
||||
}
|
||||
|
||||
EventTarget.prototype.addEventListener = function(eventType, listener) {
|
||||
if (!(eventType in this._listeners)) {
|
||||
this._listeners[eventType] = [];
|
||||
}
|
||||
var arr = this._listeners[eventType];
|
||||
// #4
|
||||
if (arr.indexOf(listener) === -1) {
|
||||
// Make a copy so as not to interfere with a current dispatchEvent.
|
||||
arr = arr.concat([listener]);
|
||||
}
|
||||
this._listeners[eventType] = arr;
|
||||
};
|
||||
|
||||
EventTarget.prototype.removeEventListener = function(eventType, listener) {
|
||||
var arr = this._listeners[eventType];
|
||||
if (!arr) {
|
||||
return;
|
||||
}
|
||||
var idx = arr.indexOf(listener);
|
||||
if (idx !== -1) {
|
||||
if (arr.length > 1) {
|
||||
// Make a copy so as not to interfere with a current dispatchEvent.
|
||||
this._listeners[eventType] = arr.slice(0, idx).concat(arr.slice(idx + 1));
|
||||
} else {
|
||||
delete this._listeners[eventType];
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
EventTarget.prototype.dispatchEvent = function() {
|
||||
var event = arguments[0];
|
||||
var t = event.type;
|
||||
// equivalent of Array.prototype.slice.call(arguments, 0);
|
||||
var args = arguments.length === 1 ? [event] : Array.apply(null, arguments);
|
||||
// TODO: This doesn't match the real behavior; per spec, onfoo get
|
||||
// their place in line from the /first/ time they're set from
|
||||
// non-null. Although WebKit bumps it to the end every time it's
|
||||
// set.
|
||||
if (this['on' + t]) {
|
||||
this['on' + t].apply(this, args);
|
||||
}
|
||||
if (t in this._listeners) {
|
||||
// Grab a reference to the listeners list. removeEventListener may alter the list.
|
||||
var listeners = this._listeners[t];
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
listeners[i].apply(this, args);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = EventTarget;
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, Event = require('./event')
|
||||
;
|
||||
|
||||
function TransportMessageEvent(data) {
|
||||
Event.call(this);
|
||||
this.initEvent('message', false, false);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
inherits(TransportMessageEvent, Event);
|
||||
|
||||
module.exports = TransportMessageEvent;
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
var JSON3 = require('json3')
|
||||
, iframeUtils = require('./utils/iframe')
|
||||
;
|
||||
|
||||
function FacadeJS(transport) {
|
||||
this._transport = transport;
|
||||
transport.on('message', this._transportMessage.bind(this));
|
||||
transport.on('close', this._transportClose.bind(this));
|
||||
}
|
||||
|
||||
FacadeJS.prototype._transportClose = function(code, reason) {
|
||||
iframeUtils.postMessage('c', JSON3.stringify([code, reason]));
|
||||
};
|
||||
FacadeJS.prototype._transportMessage = function(frame) {
|
||||
iframeUtils.postMessage('t', frame);
|
||||
};
|
||||
FacadeJS.prototype._send = function(data) {
|
||||
this._transport.send(data);
|
||||
};
|
||||
FacadeJS.prototype._close = function() {
|
||||
this._transport.close();
|
||||
this._transport.removeAllListeners();
|
||||
};
|
||||
|
||||
module.exports = FacadeJS;
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
|
||||
var urlUtils = require('./utils/url')
|
||||
, eventUtils = require('./utils/event')
|
||||
, JSON3 = require('json3')
|
||||
, FacadeJS = require('./facade')
|
||||
, InfoIframeReceiver = require('./info-iframe-receiver')
|
||||
, iframeUtils = require('./utils/iframe')
|
||||
, loc = require('./location')
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:iframe-bootstrap');
|
||||
}
|
||||
|
||||
module.exports = function(SockJS, availableTransports) {
|
||||
var transportMap = {};
|
||||
availableTransports.forEach(function(at) {
|
||||
if (at.facadeTransport) {
|
||||
transportMap[at.facadeTransport.transportName] = at.facadeTransport;
|
||||
}
|
||||
});
|
||||
|
||||
// hard-coded for the info iframe
|
||||
// TODO see if we can make this more dynamic
|
||||
transportMap[InfoIframeReceiver.transportName] = InfoIframeReceiver;
|
||||
var parentOrigin;
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
SockJS.bootstrap_iframe = function() {
|
||||
/* eslint-enable camelcase */
|
||||
var facade;
|
||||
iframeUtils.currentWindowId = loc.hash.slice(1);
|
||||
var onMessage = function(e) {
|
||||
if (e.source !== parent) {
|
||||
return;
|
||||
}
|
||||
if (typeof parentOrigin === 'undefined') {
|
||||
parentOrigin = e.origin;
|
||||
}
|
||||
if (e.origin !== parentOrigin) {
|
||||
return;
|
||||
}
|
||||
|
||||
var iframeMessage;
|
||||
try {
|
||||
iframeMessage = JSON3.parse(e.data);
|
||||
} catch (ignored) {
|
||||
debug('bad json', e.data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (iframeMessage.windowId !== iframeUtils.currentWindowId) {
|
||||
return;
|
||||
}
|
||||
switch (iframeMessage.type) {
|
||||
case 's':
|
||||
var p;
|
||||
try {
|
||||
p = JSON3.parse(iframeMessage.data);
|
||||
} catch (ignored) {
|
||||
debug('bad json', iframeMessage.data);
|
||||
break;
|
||||
}
|
||||
var version = p[0];
|
||||
var transport = p[1];
|
||||
var transUrl = p[2];
|
||||
var baseUrl = p[3];
|
||||
debug(version, transport, transUrl, baseUrl);
|
||||
// change this to semver logic
|
||||
if (version !== SockJS.version) {
|
||||
throw new Error('Incompatible SockJS! Main site uses:' +
|
||||
' "' + version + '", the iframe:' +
|
||||
' "' + SockJS.version + '".');
|
||||
}
|
||||
|
||||
if (!urlUtils.isOriginEqual(transUrl, loc.href) ||
|
||||
!urlUtils.isOriginEqual(baseUrl, loc.href)) {
|
||||
throw new Error('Can\'t connect to different domain from within an ' +
|
||||
'iframe. (' + loc.href + ', ' + transUrl + ', ' + baseUrl + ')');
|
||||
}
|
||||
facade = new FacadeJS(new transportMap[transport](transUrl, baseUrl));
|
||||
break;
|
||||
case 'm':
|
||||
facade._send(iframeMessage.data);
|
||||
break;
|
||||
case 'c':
|
||||
if (facade) {
|
||||
facade._close();
|
||||
}
|
||||
facade = null;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
eventUtils.attachEvent('message', onMessage);
|
||||
|
||||
// Start
|
||||
iframeUtils.postMessage('s');
|
||||
};
|
||||
};
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
var EventEmitter = require('events').EventEmitter
|
||||
, inherits = require('inherits')
|
||||
, JSON3 = require('json3')
|
||||
, objectUtils = require('./utils/object')
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:info-ajax');
|
||||
}
|
||||
|
||||
function InfoAjax(url, AjaxObject) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
var t0 = +new Date();
|
||||
this.xo = new AjaxObject('GET', url);
|
||||
|
||||
this.xo.once('finish', function(status, text) {
|
||||
var info, rtt;
|
||||
if (status === 200) {
|
||||
rtt = (+new Date()) - t0;
|
||||
if (text) {
|
||||
try {
|
||||
info = JSON3.parse(text);
|
||||
} catch (e) {
|
||||
debug('bad json', text);
|
||||
}
|
||||
}
|
||||
|
||||
if (!objectUtils.isObject(info)) {
|
||||
info = {};
|
||||
}
|
||||
}
|
||||
self.emit('finish', info, rtt);
|
||||
self.removeAllListeners();
|
||||
});
|
||||
}
|
||||
|
||||
inherits(InfoAjax, EventEmitter);
|
||||
|
||||
InfoAjax.prototype.close = function() {
|
||||
this.removeAllListeners();
|
||||
this.xo.close();
|
||||
};
|
||||
|
||||
module.exports = InfoAjax;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, EventEmitter = require('events').EventEmitter
|
||||
, JSON3 = require('json3')
|
||||
, XHRLocalObject = require('./transport/sender/xhr-local')
|
||||
, InfoAjax = require('./info-ajax')
|
||||
;
|
||||
|
||||
function InfoReceiverIframe(transUrl) {
|
||||
var self = this;
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.ir = new InfoAjax(transUrl, XHRLocalObject);
|
||||
this.ir.once('finish', function(info, rtt) {
|
||||
self.ir = null;
|
||||
self.emit('message', JSON3.stringify([info, rtt]));
|
||||
});
|
||||
}
|
||||
|
||||
inherits(InfoReceiverIframe, EventEmitter);
|
||||
|
||||
InfoReceiverIframe.transportName = 'iframe-info-receiver';
|
||||
|
||||
InfoReceiverIframe.prototype.close = function() {
|
||||
if (this.ir) {
|
||||
this.ir.close();
|
||||
this.ir = null;
|
||||
}
|
||||
this.removeAllListeners();
|
||||
};
|
||||
|
||||
module.exports = InfoReceiverIframe;
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
'use strict';
|
||||
|
||||
var EventEmitter = require('events').EventEmitter
|
||||
, inherits = require('inherits')
|
||||
, JSON3 = require('json3')
|
||||
, utils = require('./utils/event')
|
||||
, IframeTransport = require('./transport/iframe')
|
||||
, InfoReceiverIframe = require('./info-iframe-receiver')
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:info-iframe');
|
||||
}
|
||||
|
||||
function InfoIframe(baseUrl, url) {
|
||||
var self = this;
|
||||
EventEmitter.call(this);
|
||||
|
||||
var go = function() {
|
||||
var ifr = self.ifr = new IframeTransport(InfoReceiverIframe.transportName, url, baseUrl);
|
||||
|
||||
ifr.once('message', function(msg) {
|
||||
if (msg) {
|
||||
var d;
|
||||
try {
|
||||
d = JSON3.parse(msg);
|
||||
} catch (e) {
|
||||
debug('bad json', msg);
|
||||
self.emit('finish');
|
||||
self.close();
|
||||
return;
|
||||
}
|
||||
|
||||
var info = d[0], rtt = d[1];
|
||||
self.emit('finish', info, rtt);
|
||||
}
|
||||
self.close();
|
||||
});
|
||||
|
||||
ifr.once('close', function() {
|
||||
self.emit('finish');
|
||||
self.close();
|
||||
});
|
||||
};
|
||||
|
||||
// TODO this seems the same as the 'needBody' from transports
|
||||
if (!global.document.body) {
|
||||
utils.attachEvent('load', go);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
}
|
||||
|
||||
inherits(InfoIframe, EventEmitter);
|
||||
|
||||
InfoIframe.enabled = function() {
|
||||
return IframeTransport.enabled();
|
||||
};
|
||||
|
||||
InfoIframe.prototype.close = function() {
|
||||
if (this.ifr) {
|
||||
this.ifr.close();
|
||||
}
|
||||
this.removeAllListeners();
|
||||
this.ifr = null;
|
||||
};
|
||||
|
||||
module.exports = InfoIframe;
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
'use strict';
|
||||
|
||||
var EventEmitter = require('events').EventEmitter
|
||||
, inherits = require('inherits')
|
||||
, urlUtils = require('./utils/url')
|
||||
, XDR = require('./transport/sender/xdr')
|
||||
, XHRCors = require('./transport/sender/xhr-cors')
|
||||
, XHRLocal = require('./transport/sender/xhr-local')
|
||||
, XHRFake = require('./transport/sender/xhr-fake')
|
||||
, InfoIframe = require('./info-iframe')
|
||||
, InfoAjax = require('./info-ajax')
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:info-receiver');
|
||||
}
|
||||
|
||||
function InfoReceiver(baseUrl, urlInfo) {
|
||||
debug(baseUrl);
|
||||
var self = this;
|
||||
EventEmitter.call(this);
|
||||
|
||||
setTimeout(function() {
|
||||
self.doXhr(baseUrl, urlInfo);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
inherits(InfoReceiver, EventEmitter);
|
||||
|
||||
// TODO this is currently ignoring the list of available transports and the whitelist
|
||||
|
||||
InfoReceiver._getReceiver = function(baseUrl, url, urlInfo) {
|
||||
// determine method of CORS support (if needed)
|
||||
if (urlInfo.sameOrigin) {
|
||||
return new InfoAjax(url, XHRLocal);
|
||||
}
|
||||
if (XHRCors.enabled) {
|
||||
return new InfoAjax(url, XHRCors);
|
||||
}
|
||||
if (XDR.enabled && urlInfo.sameScheme) {
|
||||
return new InfoAjax(url, XDR);
|
||||
}
|
||||
if (InfoIframe.enabled()) {
|
||||
return new InfoIframe(baseUrl, url);
|
||||
}
|
||||
return new InfoAjax(url, XHRFake);
|
||||
};
|
||||
|
||||
InfoReceiver.prototype.doXhr = function(baseUrl, urlInfo) {
|
||||
var self = this
|
||||
, url = urlUtils.addPath(baseUrl, '/info')
|
||||
;
|
||||
debug('doXhr', url);
|
||||
|
||||
this.xo = InfoReceiver._getReceiver(baseUrl, url, urlInfo);
|
||||
|
||||
this.timeoutRef = setTimeout(function() {
|
||||
debug('timeout');
|
||||
self._cleanup(false);
|
||||
self.emit('finish');
|
||||
}, InfoReceiver.timeout);
|
||||
|
||||
this.xo.once('finish', function(info, rtt) {
|
||||
debug('finish', info, rtt);
|
||||
self._cleanup(true);
|
||||
self.emit('finish', info, rtt);
|
||||
});
|
||||
};
|
||||
|
||||
InfoReceiver.prototype._cleanup = function(wasClean) {
|
||||
debug('_cleanup');
|
||||
clearTimeout(this.timeoutRef);
|
||||
this.timeoutRef = null;
|
||||
if (!wasClean && this.xo) {
|
||||
this.xo.close();
|
||||
}
|
||||
this.xo = null;
|
||||
};
|
||||
|
||||
InfoReceiver.prototype.close = function() {
|
||||
debug('close');
|
||||
this.removeAllListeners();
|
||||
this._cleanup(false);
|
||||
};
|
||||
|
||||
InfoReceiver.timeout = 8000;
|
||||
|
||||
module.exports = InfoReceiver;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = global.location || {
|
||||
origin: 'http://localhost:80'
|
||||
, protocol: 'http:'
|
||||
, host: 'localhost'
|
||||
, port: 80
|
||||
, href: 'http://localhost/'
|
||||
, hash: ''
|
||||
};
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
'use strict';
|
||||
|
||||
require('./shims');
|
||||
|
||||
var URL = require('url-parse')
|
||||
, inherits = require('inherits')
|
||||
, JSON3 = require('json3')
|
||||
, random = require('./utils/random')
|
||||
, escape = require('./utils/escape')
|
||||
, urlUtils = require('./utils/url')
|
||||
, eventUtils = require('./utils/event')
|
||||
, transport = require('./utils/transport')
|
||||
, objectUtils = require('./utils/object')
|
||||
, browser = require('./utils/browser')
|
||||
, log = require('./utils/log')
|
||||
, Event = require('./event/event')
|
||||
, EventTarget = require('./event/eventtarget')
|
||||
, loc = require('./location')
|
||||
, CloseEvent = require('./event/close')
|
||||
, TransportMessageEvent = require('./event/trans-message')
|
||||
, InfoReceiver = require('./info-receiver')
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:main');
|
||||
}
|
||||
|
||||
var transports;
|
||||
|
||||
// follow constructor steps defined at http://dev.w3.org/html5/websockets/#the-websocket-interface
|
||||
function SockJS(url, protocols, options) {
|
||||
if (!(this instanceof SockJS)) {
|
||||
return new SockJS(url, protocols, options);
|
||||
}
|
||||
if (arguments.length < 1) {
|
||||
throw new TypeError("Failed to construct 'SockJS: 1 argument required, but only 0 present");
|
||||
}
|
||||
EventTarget.call(this);
|
||||
|
||||
this.readyState = SockJS.CONNECTING;
|
||||
this.extensions = '';
|
||||
this.protocol = '';
|
||||
|
||||
// non-standard extension
|
||||
options = options || {};
|
||||
if (options.protocols_whitelist) {
|
||||
log.warn("'protocols_whitelist' is DEPRECATED. Use 'transports' instead.");
|
||||
}
|
||||
this._transportsWhitelist = options.transports;
|
||||
this._transportOptions = options.transportOptions || {};
|
||||
this._timeout = options.timeout || 0;
|
||||
|
||||
var sessionId = options.sessionId || 8;
|
||||
if (typeof sessionId === 'function') {
|
||||
this._generateSessionId = sessionId;
|
||||
} else if (typeof sessionId === 'number') {
|
||||
this._generateSessionId = function() {
|
||||
return random.string(sessionId);
|
||||
};
|
||||
} else {
|
||||
throw new TypeError('If sessionId is used in the options, it needs to be a number or a function.');
|
||||
}
|
||||
|
||||
this._server = options.server || random.numberString(1000);
|
||||
|
||||
// Step 1 of WS spec - parse and validate the url. Issue #8
|
||||
var parsedUrl = new URL(url);
|
||||
if (!parsedUrl.host || !parsedUrl.protocol) {
|
||||
throw new SyntaxError("The URL '" + url + "' is invalid");
|
||||
} else if (parsedUrl.hash) {
|
||||
throw new SyntaxError('The URL must not contain a fragment');
|
||||
} else if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
|
||||
throw new SyntaxError("The URL's scheme must be either 'http:' or 'https:'. '" + parsedUrl.protocol + "' is not allowed.");
|
||||
}
|
||||
|
||||
var secure = parsedUrl.protocol === 'https:';
|
||||
// Step 2 - don't allow secure origin with an insecure protocol
|
||||
if (loc.protocol === 'https:' && !secure) {
|
||||
throw new Error('SecurityError: An insecure SockJS connection may not be initiated from a page loaded over HTTPS');
|
||||
}
|
||||
|
||||
// Step 3 - check port access - no need here
|
||||
// Step 4 - parse protocols argument
|
||||
if (!protocols) {
|
||||
protocols = [];
|
||||
} else if (!Array.isArray(protocols)) {
|
||||
protocols = [protocols];
|
||||
}
|
||||
|
||||
// Step 5 - check protocols argument
|
||||
var sortedProtocols = protocols.sort();
|
||||
sortedProtocols.forEach(function(proto, i) {
|
||||
if (!proto) {
|
||||
throw new SyntaxError("The protocols entry '" + proto + "' is invalid.");
|
||||
}
|
||||
if (i < (sortedProtocols.length - 1) && proto === sortedProtocols[i + 1]) {
|
||||
throw new SyntaxError("The protocols entry '" + proto + "' is duplicated.");
|
||||
}
|
||||
});
|
||||
|
||||
// Step 6 - convert origin
|
||||
var o = urlUtils.getOrigin(loc.href);
|
||||
this._origin = o ? o.toLowerCase() : null;
|
||||
|
||||
// remove the trailing slash
|
||||
parsedUrl.set('pathname', parsedUrl.pathname.replace(/\/+$/, ''));
|
||||
|
||||
// store the sanitized url
|
||||
this.url = parsedUrl.href;
|
||||
debug('using url', this.url);
|
||||
|
||||
// Step 7 - start connection in background
|
||||
// obtain server info
|
||||
// http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-26
|
||||
this._urlInfo = {
|
||||
nullOrigin: !browser.hasDomain()
|
||||
, sameOrigin: urlUtils.isOriginEqual(this.url, loc.href)
|
||||
, sameScheme: urlUtils.isSchemeEqual(this.url, loc.href)
|
||||
};
|
||||
|
||||
this._ir = new InfoReceiver(this.url, this._urlInfo);
|
||||
this._ir.once('finish', this._receiveInfo.bind(this));
|
||||
}
|
||||
|
||||
inherits(SockJS, EventTarget);
|
||||
|
||||
function userSetCode(code) {
|
||||
return code === 1000 || (code >= 3000 && code <= 4999);
|
||||
}
|
||||
|
||||
SockJS.prototype.close = function(code, reason) {
|
||||
// Step 1
|
||||
if (code && !userSetCode(code)) {
|
||||
throw new Error('InvalidAccessError: Invalid code');
|
||||
}
|
||||
// Step 2.4 states the max is 123 bytes, but we are just checking length
|
||||
if (reason && reason.length > 123) {
|
||||
throw new SyntaxError('reason argument has an invalid length');
|
||||
}
|
||||
|
||||
// Step 3.1
|
||||
if (this.readyState === SockJS.CLOSING || this.readyState === SockJS.CLOSED) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO look at docs to determine how to set this
|
||||
var wasClean = true;
|
||||
this._close(code || 1000, reason || 'Normal closure', wasClean);
|
||||
};
|
||||
|
||||
SockJS.prototype.send = function(data) {
|
||||
// #13 - convert anything non-string to string
|
||||
// TODO this currently turns objects into [object Object]
|
||||
if (typeof data !== 'string') {
|
||||
data = '' + data;
|
||||
}
|
||||
if (this.readyState === SockJS.CONNECTING) {
|
||||
throw new Error('InvalidStateError: The connection has not been established yet');
|
||||
}
|
||||
if (this.readyState !== SockJS.OPEN) {
|
||||
return;
|
||||
}
|
||||
this._transport.send(escape.quote(data));
|
||||
};
|
||||
|
||||
SockJS.version = require('./version');
|
||||
|
||||
SockJS.CONNECTING = 0;
|
||||
SockJS.OPEN = 1;
|
||||
SockJS.CLOSING = 2;
|
||||
SockJS.CLOSED = 3;
|
||||
|
||||
SockJS.prototype._receiveInfo = function(info, rtt) {
|
||||
debug('_receiveInfo', rtt);
|
||||
this._ir = null;
|
||||
if (!info) {
|
||||
this._close(1002, 'Cannot connect to server');
|
||||
return;
|
||||
}
|
||||
|
||||
// establish a round-trip timeout (RTO) based on the
|
||||
// round-trip time (RTT)
|
||||
this._rto = this.countRTO(rtt);
|
||||
// allow server to override url used for the actual transport
|
||||
this._transUrl = info.base_url ? info.base_url : this.url;
|
||||
info = objectUtils.extend(info, this._urlInfo);
|
||||
debug('info', info);
|
||||
// determine list of desired and supported transports
|
||||
var enabledTransports = transports.filterToEnabled(this._transportsWhitelist, info);
|
||||
this._transports = enabledTransports.main;
|
||||
debug(this._transports.length + ' enabled transports');
|
||||
|
||||
this._connect();
|
||||
};
|
||||
|
||||
SockJS.prototype._connect = function() {
|
||||
for (var Transport = this._transports.shift(); Transport; Transport = this._transports.shift()) {
|
||||
debug('attempt', Transport.transportName);
|
||||
if (Transport.needBody) {
|
||||
if (!global.document.body ||
|
||||
(typeof global.document.readyState !== 'undefined' &&
|
||||
global.document.readyState !== 'complete' &&
|
||||
global.document.readyState !== 'interactive')) {
|
||||
debug('waiting for body');
|
||||
this._transports.unshift(Transport);
|
||||
eventUtils.attachEvent('load', this._connect.bind(this));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// calculate timeout based on RTO and round trips. Default to 5s
|
||||
var timeoutMs = Math.max(this._timeout, (this._rto * Transport.roundTrips) || 5000);
|
||||
this._transportTimeoutId = setTimeout(this._transportTimeout.bind(this), timeoutMs);
|
||||
debug('using timeout', timeoutMs);
|
||||
|
||||
var transportUrl = urlUtils.addPath(this._transUrl, '/' + this._server + '/' + this._generateSessionId());
|
||||
var options = this._transportOptions[Transport.transportName];
|
||||
debug('transport url', transportUrl);
|
||||
var transportObj = new Transport(transportUrl, this._transUrl, options);
|
||||
transportObj.on('message', this._transportMessage.bind(this));
|
||||
transportObj.once('close', this._transportClose.bind(this));
|
||||
transportObj.transportName = Transport.transportName;
|
||||
this._transport = transportObj;
|
||||
|
||||
return;
|
||||
}
|
||||
this._close(2000, 'All transports failed', false);
|
||||
};
|
||||
|
||||
SockJS.prototype._transportTimeout = function() {
|
||||
debug('_transportTimeout');
|
||||
if (this.readyState === SockJS.CONNECTING) {
|
||||
if (this._transport) {
|
||||
this._transport.close();
|
||||
}
|
||||
|
||||
this._transportClose(2007, 'Transport timed out');
|
||||
}
|
||||
};
|
||||
|
||||
SockJS.prototype._transportMessage = function(msg) {
|
||||
debug('_transportMessage', msg);
|
||||
var self = this
|
||||
, type = msg.slice(0, 1)
|
||||
, content = msg.slice(1)
|
||||
, payload
|
||||
;
|
||||
|
||||
// first check for messages that don't need a payload
|
||||
switch (type) {
|
||||
case 'o':
|
||||
this._open();
|
||||
return;
|
||||
case 'h':
|
||||
this.dispatchEvent(new Event('heartbeat'));
|
||||
debug('heartbeat', this.transport);
|
||||
return;
|
||||
}
|
||||
|
||||
if (content) {
|
||||
try {
|
||||
payload = JSON3.parse(content);
|
||||
} catch (e) {
|
||||
debug('bad json', content);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof payload === 'undefined') {
|
||||
debug('empty payload', content);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'a':
|
||||
if (Array.isArray(payload)) {
|
||||
payload.forEach(function(p) {
|
||||
debug('message', self.transport, p);
|
||||
self.dispatchEvent(new TransportMessageEvent(p));
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
debug('message', this.transport, payload);
|
||||
this.dispatchEvent(new TransportMessageEvent(payload));
|
||||
break;
|
||||
case 'c':
|
||||
if (Array.isArray(payload) && payload.length === 2) {
|
||||
this._close(payload[0], payload[1], true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
SockJS.prototype._transportClose = function(code, reason) {
|
||||
debug('_transportClose', this.transport, code, reason);
|
||||
if (this._transport) {
|
||||
this._transport.removeAllListeners();
|
||||
this._transport = null;
|
||||
this.transport = null;
|
||||
}
|
||||
|
||||
if (!userSetCode(code) && code !== 2000 && this.readyState === SockJS.CONNECTING) {
|
||||
this._connect();
|
||||
return;
|
||||
}
|
||||
|
||||
this._close(code, reason);
|
||||
};
|
||||
|
||||
SockJS.prototype._open = function() {
|
||||
debug('_open', this._transport && this._transport.transportName, this.readyState);
|
||||
if (this.readyState === SockJS.CONNECTING) {
|
||||
if (this._transportTimeoutId) {
|
||||
clearTimeout(this._transportTimeoutId);
|
||||
this._transportTimeoutId = null;
|
||||
}
|
||||
this.readyState = SockJS.OPEN;
|
||||
this.transport = this._transport.transportName;
|
||||
this.dispatchEvent(new Event('open'));
|
||||
debug('connected', this.transport);
|
||||
} else {
|
||||
// The server might have been restarted, and lost track of our
|
||||
// connection.
|
||||
this._close(1006, 'Server lost session');
|
||||
}
|
||||
};
|
||||
|
||||
SockJS.prototype._close = function(code, reason, wasClean) {
|
||||
debug('_close', this.transport, code, reason, wasClean, this.readyState);
|
||||
var forceFail = false;
|
||||
|
||||
if (this._ir) {
|
||||
forceFail = true;
|
||||
this._ir.close();
|
||||
this._ir = null;
|
||||
}
|
||||
if (this._transport) {
|
||||
this._transport.close();
|
||||
this._transport = null;
|
||||
this.transport = null;
|
||||
}
|
||||
|
||||
if (this.readyState === SockJS.CLOSED) {
|
||||
throw new Error('InvalidStateError: SockJS has already been closed');
|
||||
}
|
||||
|
||||
this.readyState = SockJS.CLOSING;
|
||||
setTimeout(function() {
|
||||
this.readyState = SockJS.CLOSED;
|
||||
|
||||
if (forceFail) {
|
||||
this.dispatchEvent(new Event('error'));
|
||||
}
|
||||
|
||||
var e = new CloseEvent('close');
|
||||
e.wasClean = wasClean || false;
|
||||
e.code = code || 1000;
|
||||
e.reason = reason;
|
||||
|
||||
this.dispatchEvent(e);
|
||||
this.onmessage = this.onclose = this.onerror = null;
|
||||
debug('disconnected');
|
||||
}.bind(this), 0);
|
||||
};
|
||||
|
||||
// See: http://www.erg.abdn.ac.uk/~gerrit/dccp/notes/ccid2/rto_estimator/
|
||||
// and RFC 2988.
|
||||
SockJS.prototype.countRTO = function(rtt) {
|
||||
// In a local environment, when using IE8/9 and the `jsonp-polling`
|
||||
// transport the time needed to establish a connection (the time that pass
|
||||
// from the opening of the transport to the call of `_dispatchOpen`) is
|
||||
// around 200msec (the lower bound used in the article above) and this
|
||||
// causes spurious timeouts. For this reason we calculate a value slightly
|
||||
// larger than that used in the article.
|
||||
if (rtt > 100) {
|
||||
return 4 * rtt; // rto > 400msec
|
||||
}
|
||||
return 300 + rtt; // 300msec < rto <= 400msec
|
||||
};
|
||||
|
||||
module.exports = function(availableTransports) {
|
||||
transports = transport(availableTransports);
|
||||
require('./iframe-bootstrap')(SockJS, availableTransports);
|
||||
return SockJS;
|
||||
};
|
||||
+452
@@ -0,0 +1,452 @@
|
||||
/* eslint-disable */
|
||||
/* jscs: disable */
|
||||
'use strict';
|
||||
|
||||
// pulled specific shims from https://github.com/es-shims/es5-shim
|
||||
|
||||
var ArrayPrototype = Array.prototype;
|
||||
var ObjectPrototype = Object.prototype;
|
||||
var FunctionPrototype = Function.prototype;
|
||||
var StringPrototype = String.prototype;
|
||||
var array_slice = ArrayPrototype.slice;
|
||||
|
||||
var _toString = ObjectPrototype.toString;
|
||||
var isFunction = function (val) {
|
||||
return ObjectPrototype.toString.call(val) === '[object Function]';
|
||||
};
|
||||
var isArray = function isArray(obj) {
|
||||
return _toString.call(obj) === '[object Array]';
|
||||
};
|
||||
var isString = function isString(obj) {
|
||||
return _toString.call(obj) === '[object String]';
|
||||
};
|
||||
|
||||
var supportsDescriptors = Object.defineProperty && (function () {
|
||||
try {
|
||||
Object.defineProperty({}, 'x', {});
|
||||
return true;
|
||||
} catch (e) { /* this is ES3 */
|
||||
return false;
|
||||
}
|
||||
}());
|
||||
|
||||
// Define configurable, writable and non-enumerable props
|
||||
// if they don't exist.
|
||||
var defineProperty;
|
||||
if (supportsDescriptors) {
|
||||
defineProperty = function (object, name, method, forceAssign) {
|
||||
if (!forceAssign && (name in object)) { return; }
|
||||
Object.defineProperty(object, name, {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: method
|
||||
});
|
||||
};
|
||||
} else {
|
||||
defineProperty = function (object, name, method, forceAssign) {
|
||||
if (!forceAssign && (name in object)) { return; }
|
||||
object[name] = method;
|
||||
};
|
||||
}
|
||||
var defineProperties = function (object, map, forceAssign) {
|
||||
for (var name in map) {
|
||||
if (ObjectPrototype.hasOwnProperty.call(map, name)) {
|
||||
defineProperty(object, name, map[name], forceAssign);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var toObject = function (o) {
|
||||
if (o == null) { // this matches both null and undefined
|
||||
throw new TypeError("can't convert " + o + ' to object');
|
||||
}
|
||||
return Object(o);
|
||||
};
|
||||
|
||||
//
|
||||
// Util
|
||||
// ======
|
||||
//
|
||||
|
||||
// ES5 9.4
|
||||
// http://es5.github.com/#x9.4
|
||||
// http://jsperf.com/to-integer
|
||||
|
||||
function toInteger(num) {
|
||||
var n = +num;
|
||||
if (n !== n) { // isNaN
|
||||
n = 0;
|
||||
} else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
|
||||
n = (n > 0 || -1) * Math.floor(Math.abs(n));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function ToUint32(x) {
|
||||
return x >>> 0;
|
||||
}
|
||||
|
||||
//
|
||||
// Function
|
||||
// ========
|
||||
//
|
||||
|
||||
// ES-5 15.3.4.5
|
||||
// http://es5.github.com/#x15.3.4.5
|
||||
|
||||
function Empty() {}
|
||||
|
||||
defineProperties(FunctionPrototype, {
|
||||
bind: function bind(that) { // .length is 1
|
||||
// 1. Let Target be the this value.
|
||||
var target = this;
|
||||
// 2. If IsCallable(Target) is false, throw a TypeError exception.
|
||||
if (!isFunction(target)) {
|
||||
throw new TypeError('Function.prototype.bind called on incompatible ' + target);
|
||||
}
|
||||
// 3. Let A be a new (possibly empty) internal list of all of the
|
||||
// argument values provided after thisArg (arg1, arg2 etc), in order.
|
||||
// XXX slicedArgs will stand in for "A" if used
|
||||
var args = array_slice.call(arguments, 1); // for normal call
|
||||
// 4. Let F be a new native ECMAScript object.
|
||||
// 11. Set the [[Prototype]] internal property of F to the standard
|
||||
// built-in Function prototype object as specified in 15.3.3.1.
|
||||
// 12. Set the [[Call]] internal property of F as described in
|
||||
// 15.3.4.5.1.
|
||||
// 13. Set the [[Construct]] internal property of F as described in
|
||||
// 15.3.4.5.2.
|
||||
// 14. Set the [[HasInstance]] internal property of F as described in
|
||||
// 15.3.4.5.3.
|
||||
var binder = function () {
|
||||
|
||||
if (this instanceof bound) {
|
||||
// 15.3.4.5.2 [[Construct]]
|
||||
// When the [[Construct]] internal method of a function object,
|
||||
// F that was created using the bind function is called with a
|
||||
// list of arguments ExtraArgs, the following steps are taken:
|
||||
// 1. Let target be the value of F's [[TargetFunction]]
|
||||
// internal property.
|
||||
// 2. If target has no [[Construct]] internal method, a
|
||||
// TypeError exception is thrown.
|
||||
// 3. Let boundArgs be the value of F's [[BoundArgs]] internal
|
||||
// property.
|
||||
// 4. Let args be a new list containing the same values as the
|
||||
// list boundArgs in the same order followed by the same
|
||||
// values as the list ExtraArgs in the same order.
|
||||
// 5. Return the result of calling the [[Construct]] internal
|
||||
// method of target providing args as the arguments.
|
||||
|
||||
var result = target.apply(
|
||||
this,
|
||||
args.concat(array_slice.call(arguments))
|
||||
);
|
||||
if (Object(result) === result) {
|
||||
return result;
|
||||
}
|
||||
return this;
|
||||
|
||||
} else {
|
||||
// 15.3.4.5.1 [[Call]]
|
||||
// When the [[Call]] internal method of a function object, F,
|
||||
// which was created using the bind function is called with a
|
||||
// this value and a list of arguments ExtraArgs, the following
|
||||
// steps are taken:
|
||||
// 1. Let boundArgs be the value of F's [[BoundArgs]] internal
|
||||
// property.
|
||||
// 2. Let boundThis be the value of F's [[BoundThis]] internal
|
||||
// property.
|
||||
// 3. Let target be the value of F's [[TargetFunction]] internal
|
||||
// property.
|
||||
// 4. Let args be a new list containing the same values as the
|
||||
// list boundArgs in the same order followed by the same
|
||||
// values as the list ExtraArgs in the same order.
|
||||
// 5. Return the result of calling the [[Call]] internal method
|
||||
// of target providing boundThis as the this value and
|
||||
// providing args as the arguments.
|
||||
|
||||
// equiv: target.call(this, ...boundArgs, ...args)
|
||||
return target.apply(
|
||||
that,
|
||||
args.concat(array_slice.call(arguments))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// 15. If the [[Class]] internal property of Target is "Function", then
|
||||
// a. Let L be the length property of Target minus the length of A.
|
||||
// b. Set the length own property of F to either 0 or L, whichever is
|
||||
// larger.
|
||||
// 16. Else set the length own property of F to 0.
|
||||
|
||||
var boundLength = Math.max(0, target.length - args.length);
|
||||
|
||||
// 17. Set the attributes of the length own property of F to the values
|
||||
// specified in 15.3.5.1.
|
||||
var boundArgs = [];
|
||||
for (var i = 0; i < boundLength; i++) {
|
||||
boundArgs.push('$' + i);
|
||||
}
|
||||
|
||||
// XXX Build a dynamic function with desired amount of arguments is the only
|
||||
// way to set the length property of a function.
|
||||
// In environments where Content Security Policies enabled (Chrome extensions,
|
||||
// for ex.) all use of eval or Function costructor throws an exception.
|
||||
// However in all of these environments Function.prototype.bind exists
|
||||
// and so this code will never be executed.
|
||||
var bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);
|
||||
|
||||
if (target.prototype) {
|
||||
Empty.prototype = target.prototype;
|
||||
bound.prototype = new Empty();
|
||||
// Clean up dangling references.
|
||||
Empty.prototype = null;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// 18. Set the [[Extensible]] internal property of F to true.
|
||||
|
||||
// TODO
|
||||
// 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).
|
||||
// 20. Call the [[DefineOwnProperty]] internal method of F with
|
||||
// arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]:
|
||||
// thrower, [[Enumerable]]: false, [[Configurable]]: false}, and
|
||||
// false.
|
||||
// 21. Call the [[DefineOwnProperty]] internal method of F with
|
||||
// arguments "arguments", PropertyDescriptor {[[Get]]: thrower,
|
||||
// [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},
|
||||
// and false.
|
||||
|
||||
// TODO
|
||||
// NOTE Function objects created using Function.prototype.bind do not
|
||||
// have a prototype property or the [[Code]], [[FormalParameters]], and
|
||||
// [[Scope]] internal properties.
|
||||
// XXX can't delete prototype in pure-js.
|
||||
|
||||
// 22. Return F.
|
||||
return bound;
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Array
|
||||
// =====
|
||||
//
|
||||
|
||||
// ES5 15.4.3.2
|
||||
// http://es5.github.com/#x15.4.3.2
|
||||
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
|
||||
defineProperties(Array, { isArray: isArray });
|
||||
|
||||
|
||||
var boxedString = Object('a');
|
||||
var splitString = boxedString[0] !== 'a' || !(0 in boxedString);
|
||||
|
||||
var properlyBoxesContext = function properlyBoxed(method) {
|
||||
// Check node 0.6.21 bug where third parameter is not boxed
|
||||
var properlyBoxesNonStrict = true;
|
||||
var properlyBoxesStrict = true;
|
||||
if (method) {
|
||||
method.call('foo', function (_, __, context) {
|
||||
if (typeof context !== 'object') { properlyBoxesNonStrict = false; }
|
||||
});
|
||||
|
||||
method.call([1], function () {
|
||||
'use strict';
|
||||
properlyBoxesStrict = typeof this === 'string';
|
||||
}, 'x');
|
||||
}
|
||||
return !!method && properlyBoxesNonStrict && properlyBoxesStrict;
|
||||
};
|
||||
|
||||
defineProperties(ArrayPrototype, {
|
||||
forEach: function forEach(fun /*, thisp*/) {
|
||||
var object = toObject(this),
|
||||
self = splitString && isString(this) ? this.split('') : object,
|
||||
thisp = arguments[1],
|
||||
i = -1,
|
||||
length = self.length >>> 0;
|
||||
|
||||
// If no callback function or if callback is not a callable function
|
||||
if (!isFunction(fun)) {
|
||||
throw new TypeError(); // TODO message
|
||||
}
|
||||
|
||||
while (++i < length) {
|
||||
if (i in self) {
|
||||
// Invoke the callback function with call, passing arguments:
|
||||
// context, property value, property key, thisArg object
|
||||
// context
|
||||
fun.call(thisp, self[i], i, object);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, !properlyBoxesContext(ArrayPrototype.forEach));
|
||||
|
||||
// ES5 15.4.4.14
|
||||
// http://es5.github.com/#x15.4.4.14
|
||||
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
|
||||
var hasFirefox2IndexOfBug = Array.prototype.indexOf && [0, 1].indexOf(1, 2) !== -1;
|
||||
defineProperties(ArrayPrototype, {
|
||||
indexOf: function indexOf(sought /*, fromIndex */ ) {
|
||||
var self = splitString && isString(this) ? this.split('') : toObject(this),
|
||||
length = self.length >>> 0;
|
||||
|
||||
if (!length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
if (arguments.length > 1) {
|
||||
i = toInteger(arguments[1]);
|
||||
}
|
||||
|
||||
// handle negative indices
|
||||
i = i >= 0 ? i : Math.max(0, length + i);
|
||||
for (; i < length; i++) {
|
||||
if (i in self && self[i] === sought) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}, hasFirefox2IndexOfBug);
|
||||
|
||||
//
|
||||
// String
|
||||
// ======
|
||||
//
|
||||
|
||||
// ES5 15.5.4.14
|
||||
// http://es5.github.com/#x15.5.4.14
|
||||
|
||||
// [bugfix, IE lt 9, firefox 4, Konqueror, Opera, obscure browsers]
|
||||
// Many browsers do not split properly with regular expressions or they
|
||||
// do not perform the split correctly under obscure conditions.
|
||||
// See http://blog.stevenlevithan.com/archives/cross-browser-split
|
||||
// I've tested in many browsers and this seems to cover the deviant ones:
|
||||
// 'ab'.split(/(?:ab)*/) should be ["", ""], not [""]
|
||||
// '.'.split(/(.?)(.?)/) should be ["", ".", "", ""], not ["", ""]
|
||||
// 'tesst'.split(/(s)*/) should be ["t", undefined, "e", "s", "t"], not
|
||||
// [undefined, "t", undefined, "e", ...]
|
||||
// ''.split(/.?/) should be [], not [""]
|
||||
// '.'.split(/()()/) should be ["."], not ["", "", "."]
|
||||
|
||||
var string_split = StringPrototype.split;
|
||||
if (
|
||||
'ab'.split(/(?:ab)*/).length !== 2 ||
|
||||
'.'.split(/(.?)(.?)/).length !== 4 ||
|
||||
'tesst'.split(/(s)*/)[1] === 't' ||
|
||||
'test'.split(/(?:)/, -1).length !== 4 ||
|
||||
''.split(/.?/).length ||
|
||||
'.'.split(/()()/).length > 1
|
||||
) {
|
||||
(function () {
|
||||
var compliantExecNpcg = /()??/.exec('')[1] === void 0; // NPCG: nonparticipating capturing group
|
||||
|
||||
StringPrototype.split = function (separator, limit) {
|
||||
var string = this;
|
||||
if (separator === void 0 && limit === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// If `separator` is not a regex, use native split
|
||||
if (_toString.call(separator) !== '[object RegExp]') {
|
||||
return string_split.call(this, separator, limit);
|
||||
}
|
||||
|
||||
var output = [],
|
||||
flags = (separator.ignoreCase ? 'i' : '') +
|
||||
(separator.multiline ? 'm' : '') +
|
||||
(separator.extended ? 'x' : '') + // Proposed for ES6
|
||||
(separator.sticky ? 'y' : ''), // Firefox 3+
|
||||
lastLastIndex = 0,
|
||||
// Make `global` and avoid `lastIndex` issues by working with a copy
|
||||
separator2, match, lastIndex, lastLength;
|
||||
separator = new RegExp(separator.source, flags + 'g');
|
||||
string += ''; // Type-convert
|
||||
if (!compliantExecNpcg) {
|
||||
// Doesn't need flags gy, but they don't hurt
|
||||
separator2 = new RegExp('^' + separator.source + '$(?!\\s)', flags);
|
||||
}
|
||||
/* Values for `limit`, per the spec:
|
||||
* If undefined: 4294967295 // Math.pow(2, 32) - 1
|
||||
* If 0, Infinity, or NaN: 0
|
||||
* If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
|
||||
* If negative number: 4294967296 - Math.floor(Math.abs(limit))
|
||||
* If other: Type-convert, then use the above rules
|
||||
*/
|
||||
limit = limit === void 0 ?
|
||||
-1 >>> 0 : // Math.pow(2, 32) - 1
|
||||
ToUint32(limit);
|
||||
while (match = separator.exec(string)) {
|
||||
// `separator.lastIndex` is not reliable cross-browser
|
||||
lastIndex = match.index + match[0].length;
|
||||
if (lastIndex > lastLastIndex) {
|
||||
output.push(string.slice(lastLastIndex, match.index));
|
||||
// Fix browsers whose `exec` methods don't consistently return `undefined` for
|
||||
// nonparticipating capturing groups
|
||||
if (!compliantExecNpcg && match.length > 1) {
|
||||
match[0].replace(separator2, function () {
|
||||
for (var i = 1; i < arguments.length - 2; i++) {
|
||||
if (arguments[i] === void 0) {
|
||||
match[i] = void 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (match.length > 1 && match.index < string.length) {
|
||||
ArrayPrototype.push.apply(output, match.slice(1));
|
||||
}
|
||||
lastLength = match[0].length;
|
||||
lastLastIndex = lastIndex;
|
||||
if (output.length >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (separator.lastIndex === match.index) {
|
||||
separator.lastIndex++; // Avoid an infinite loop
|
||||
}
|
||||
}
|
||||
if (lastLastIndex === string.length) {
|
||||
if (lastLength || !separator.test('')) {
|
||||
output.push('');
|
||||
}
|
||||
} else {
|
||||
output.push(string.slice(lastLastIndex));
|
||||
}
|
||||
return output.length > limit ? output.slice(0, limit) : output;
|
||||
};
|
||||
}());
|
||||
|
||||
// [bugfix, chrome]
|
||||
// If separator is undefined, then the result array contains just one String,
|
||||
// which is the this value (converted to a String). If limit is not undefined,
|
||||
// then the output array is truncated so that it contains no more than limit
|
||||
// elements.
|
||||
// "0".split(undefined, 0) -> []
|
||||
} else if ('0'.split(void 0, 0).length) {
|
||||
StringPrototype.split = function split(separator, limit) {
|
||||
if (separator === void 0 && limit === 0) { return []; }
|
||||
return string_split.call(this, separator, limit);
|
||||
};
|
||||
}
|
||||
|
||||
// ECMA-262, 3rd B.2.3
|
||||
// Not an ECMAScript standard, although ECMAScript 3rd Edition has a
|
||||
// non-normative section suggesting uniform semantics and it should be
|
||||
// normalized across all browsers
|
||||
// [bugfix, IE lt 9] IE < 9 substr() with negative value not working in IE
|
||||
var string_substr = StringPrototype.substr;
|
||||
var hasNegativeSubstrBug = ''.substr && '0b'.substr(-1) !== 'b';
|
||||
defineProperties(StringPrototype, {
|
||||
substr: function substr(start, length) {
|
||||
return string_substr.call(
|
||||
this,
|
||||
start < 0 ? ((start = this.length + start) < 0 ? 0 : start) : start,
|
||||
length
|
||||
);
|
||||
}
|
||||
}, hasNegativeSubstrBug);
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = [
|
||||
// streaming transports
|
||||
require('./transport/websocket')
|
||||
, require('./transport/xhr-streaming')
|
||||
, require('./transport/xdr-streaming')
|
||||
, require('./transport/eventsource')
|
||||
, require('./transport/lib/iframe-wrap')(require('./transport/eventsource'))
|
||||
|
||||
// polling transports
|
||||
, require('./transport/htmlfile')
|
||||
, require('./transport/lib/iframe-wrap')(require('./transport/htmlfile'))
|
||||
, require('./transport/xhr-polling')
|
||||
, require('./transport/xdr-polling')
|
||||
, require('./transport/lib/iframe-wrap')(require('./transport/xhr-polling'))
|
||||
, require('./transport/jsonp-polling')
|
||||
];
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
'use strict';
|
||||
|
||||
var EventEmitter = require('events').EventEmitter
|
||||
, inherits = require('inherits')
|
||||
, utils = require('../../utils/event')
|
||||
, urlUtils = require('../../utils/url')
|
||||
, XHR = global.XMLHttpRequest
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:browser:xhr');
|
||||
}
|
||||
|
||||
function AbstractXHRObject(method, url, payload, opts) {
|
||||
debug(method, url);
|
||||
var self = this;
|
||||
EventEmitter.call(this);
|
||||
|
||||
setTimeout(function () {
|
||||
self._start(method, url, payload, opts);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
inherits(AbstractXHRObject, EventEmitter);
|
||||
|
||||
AbstractXHRObject.prototype._start = function(method, url, payload, opts) {
|
||||
var self = this;
|
||||
|
||||
try {
|
||||
this.xhr = new XHR();
|
||||
} catch (x) {
|
||||
// intentionally empty
|
||||
}
|
||||
|
||||
if (!this.xhr) {
|
||||
debug('no xhr');
|
||||
this.emit('finish', 0, 'no xhr support');
|
||||
this._cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
// several browsers cache POSTs
|
||||
url = urlUtils.addQuery(url, 't=' + (+new Date()));
|
||||
|
||||
// Explorer tends to keep connection open, even after the
|
||||
// tab gets closed: http://bugs.jquery.com/ticket/5280
|
||||
this.unloadRef = utils.unloadAdd(function() {
|
||||
debug('unload cleanup');
|
||||
self._cleanup(true);
|
||||
});
|
||||
try {
|
||||
this.xhr.open(method, url, true);
|
||||
if (this.timeout && 'timeout' in this.xhr) {
|
||||
this.xhr.timeout = this.timeout;
|
||||
this.xhr.ontimeout = function() {
|
||||
debug('xhr timeout');
|
||||
self.emit('finish', 0, '');
|
||||
self._cleanup(false);
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
debug('exception', e);
|
||||
// IE raises an exception on wrong port.
|
||||
this.emit('finish', 0, '');
|
||||
this._cleanup(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((!opts || !opts.noCredentials) && AbstractXHRObject.supportsCORS) {
|
||||
debug('withCredentials');
|
||||
// Mozilla docs says https://developer.mozilla.org/en/XMLHttpRequest :
|
||||
// "This never affects same-site requests."
|
||||
|
||||
this.xhr.withCredentials = true;
|
||||
}
|
||||
if (opts && opts.headers) {
|
||||
for (var key in opts.headers) {
|
||||
this.xhr.setRequestHeader(key, opts.headers[key]);
|
||||
}
|
||||
}
|
||||
|
||||
this.xhr.onreadystatechange = function() {
|
||||
if (self.xhr) {
|
||||
var x = self.xhr;
|
||||
var text, status;
|
||||
debug('readyState', x.readyState);
|
||||
switch (x.readyState) {
|
||||
case 3:
|
||||
// IE doesn't like peeking into responseText or status
|
||||
// on Microsoft.XMLHTTP and readystate=3
|
||||
try {
|
||||
status = x.status;
|
||||
text = x.responseText;
|
||||
} catch (e) {
|
||||
// intentionally empty
|
||||
}
|
||||
debug('status', status);
|
||||
// IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450
|
||||
if (status === 1223) {
|
||||
status = 204;
|
||||
}
|
||||
|
||||
// IE does return readystate == 3 for 404 answers.
|
||||
if (status === 200 && text && text.length > 0) {
|
||||
debug('chunk');
|
||||
self.emit('chunk', status, text);
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
status = x.status;
|
||||
debug('status', status);
|
||||
// IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450
|
||||
if (status === 1223) {
|
||||
status = 204;
|
||||
}
|
||||
// IE returns this for a bad port
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa383770(v=vs.85).aspx
|
||||
if (status === 12005 || status === 12029) {
|
||||
status = 0;
|
||||
}
|
||||
|
||||
debug('finish', status, x.responseText);
|
||||
self.emit('finish', status, x.responseText);
|
||||
self._cleanup(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
self.xhr.send(payload);
|
||||
} catch (e) {
|
||||
self.emit('finish', 0, '');
|
||||
self._cleanup(false);
|
||||
}
|
||||
};
|
||||
|
||||
AbstractXHRObject.prototype._cleanup = function(abort) {
|
||||
debug('cleanup');
|
||||
if (!this.xhr) {
|
||||
return;
|
||||
}
|
||||
this.removeAllListeners();
|
||||
utils.unloadDel(this.unloadRef);
|
||||
|
||||
// IE needs this field to be a function
|
||||
this.xhr.onreadystatechange = function() {};
|
||||
if (this.xhr.ontimeout) {
|
||||
this.xhr.ontimeout = null;
|
||||
}
|
||||
|
||||
if (abort) {
|
||||
try {
|
||||
this.xhr.abort();
|
||||
} catch (x) {
|
||||
// intentionally empty
|
||||
}
|
||||
}
|
||||
this.unloadRef = this.xhr = null;
|
||||
};
|
||||
|
||||
AbstractXHRObject.prototype.close = function() {
|
||||
debug('close');
|
||||
this._cleanup(true);
|
||||
};
|
||||
|
||||
AbstractXHRObject.enabled = !!XHR;
|
||||
// override XMLHttpRequest for IE6/7
|
||||
// obfuscate to avoid firewalls
|
||||
var axo = ['Active'].concat('Object').join('X');
|
||||
if (!AbstractXHRObject.enabled && (axo in global)) {
|
||||
debug('overriding xmlhttprequest');
|
||||
XHR = function() {
|
||||
try {
|
||||
return new global[axo]('Microsoft.XMLHTTP');
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
AbstractXHRObject.enabled = !!new XHR();
|
||||
}
|
||||
|
||||
var cors = false;
|
||||
try {
|
||||
cors = 'withCredentials' in new XHR();
|
||||
} catch (ignored) {
|
||||
// intentionally empty
|
||||
}
|
||||
|
||||
AbstractXHRObject.supportsCORS = cors;
|
||||
|
||||
module.exports = AbstractXHRObject;
|
||||
+1
@@ -0,0 +1 @@
|
||||
module.exports = global.EventSource;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
var Driver = global.WebSocket || global.MozWebSocket;
|
||||
if (Driver) {
|
||||
module.exports = function WebSocketBrowserDriver(url) {
|
||||
return new Driver(url);
|
||||
};
|
||||
} else {
|
||||
module.exports = undefined;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
module.exports = require('faye-websocket').Client;
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
var EventEmitter = require('events').EventEmitter
|
||||
, inherits = require('inherits')
|
||||
, http = require('http')
|
||||
, https = require('https')
|
||||
, URL = require('url-parse')
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:driver:xhr');
|
||||
}
|
||||
|
||||
function XhrDriver(method, url, payload, opts) {
|
||||
debug(method, url, payload);
|
||||
var self = this;
|
||||
EventEmitter.call(this);
|
||||
|
||||
var parsedUrl = new URL(url);
|
||||
var options = {
|
||||
method: method
|
||||
, hostname: parsedUrl.hostname.replace(/\[|\]/g, '')
|
||||
, port: parsedUrl.port
|
||||
, path: parsedUrl.pathname + (parsedUrl.query || '')
|
||||
, headers: opts && opts.headers
|
||||
, agent: false
|
||||
};
|
||||
|
||||
var protocol = parsedUrl.protocol === 'https:' ? https : http;
|
||||
this.req = protocol.request(options, function(res) {
|
||||
res.setEncoding('utf8');
|
||||
var responseText = '';
|
||||
|
||||
res.on('data', function(chunk) {
|
||||
debug('data', chunk);
|
||||
responseText += chunk;
|
||||
self.emit('chunk', 200, responseText);
|
||||
});
|
||||
res.once('end', function() {
|
||||
debug('end');
|
||||
self.emit('finish', res.statusCode, responseText);
|
||||
self.req = null;
|
||||
});
|
||||
});
|
||||
|
||||
this.req.on('error', function(e) {
|
||||
debug('error', e);
|
||||
self.emit('finish', 0, e.message);
|
||||
});
|
||||
|
||||
if (payload) {
|
||||
this.req.write(payload);
|
||||
}
|
||||
this.req.end();
|
||||
}
|
||||
|
||||
inherits(XhrDriver, EventEmitter);
|
||||
|
||||
XhrDriver.prototype.close = function() {
|
||||
debug('close');
|
||||
this.removeAllListeners();
|
||||
if (this.req) {
|
||||
this.req.abort();
|
||||
this.req = null;
|
||||
}
|
||||
};
|
||||
|
||||
XhrDriver.enabled = true;
|
||||
XhrDriver.supportsCORS = true;
|
||||
|
||||
module.exports = XhrDriver;
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, AjaxBasedTransport = require('./lib/ajax-based')
|
||||
, EventSourceReceiver = require('./receiver/eventsource')
|
||||
, XHRCorsObject = require('./sender/xhr-cors')
|
||||
, EventSourceDriver = require('eventsource')
|
||||
;
|
||||
|
||||
function EventSourceTransport(transUrl) {
|
||||
if (!EventSourceTransport.enabled()) {
|
||||
throw new Error('Transport created when disabled');
|
||||
}
|
||||
|
||||
AjaxBasedTransport.call(this, transUrl, '/eventsource', EventSourceReceiver, XHRCorsObject);
|
||||
}
|
||||
|
||||
inherits(EventSourceTransport, AjaxBasedTransport);
|
||||
|
||||
EventSourceTransport.enabled = function() {
|
||||
return !!EventSourceDriver;
|
||||
};
|
||||
|
||||
EventSourceTransport.transportName = 'eventsource';
|
||||
EventSourceTransport.roundTrips = 2;
|
||||
|
||||
module.exports = EventSourceTransport;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, HtmlfileReceiver = require('./receiver/htmlfile')
|
||||
, XHRLocalObject = require('./sender/xhr-local')
|
||||
, AjaxBasedTransport = require('./lib/ajax-based')
|
||||
;
|
||||
|
||||
function HtmlFileTransport(transUrl) {
|
||||
if (!HtmlfileReceiver.enabled) {
|
||||
throw new Error('Transport created when disabled');
|
||||
}
|
||||
AjaxBasedTransport.call(this, transUrl, '/htmlfile', HtmlfileReceiver, XHRLocalObject);
|
||||
}
|
||||
|
||||
inherits(HtmlFileTransport, AjaxBasedTransport);
|
||||
|
||||
HtmlFileTransport.enabled = function(info) {
|
||||
return HtmlfileReceiver.enabled && info.sameOrigin;
|
||||
};
|
||||
|
||||
HtmlFileTransport.transportName = 'htmlfile';
|
||||
HtmlFileTransport.roundTrips = 2;
|
||||
|
||||
module.exports = HtmlFileTransport;
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
'use strict';
|
||||
|
||||
// Few cool transports do work only for same-origin. In order to make
|
||||
// them work cross-domain we shall use iframe, served from the
|
||||
// remote domain. New browsers have capabilities to communicate with
|
||||
// cross domain iframe using postMessage(). In IE it was implemented
|
||||
// from IE 8+, but of course, IE got some details wrong:
|
||||
// http://msdn.microsoft.com/en-us/library/cc197015(v=VS.85).aspx
|
||||
// http://stevesouders.com/misc/test-postmessage.php
|
||||
|
||||
var inherits = require('inherits')
|
||||
, JSON3 = require('json3')
|
||||
, EventEmitter = require('events').EventEmitter
|
||||
, version = require('../version')
|
||||
, urlUtils = require('../utils/url')
|
||||
, iframeUtils = require('../utils/iframe')
|
||||
, eventUtils = require('../utils/event')
|
||||
, random = require('../utils/random')
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:transport:iframe');
|
||||
}
|
||||
|
||||
function IframeTransport(transport, transUrl, baseUrl) {
|
||||
if (!IframeTransport.enabled()) {
|
||||
throw new Error('Transport created when disabled');
|
||||
}
|
||||
EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
this.origin = urlUtils.getOrigin(baseUrl);
|
||||
this.baseUrl = baseUrl;
|
||||
this.transUrl = transUrl;
|
||||
this.transport = transport;
|
||||
this.windowId = random.string(8);
|
||||
|
||||
var iframeUrl = urlUtils.addPath(baseUrl, '/iframe.html') + '#' + this.windowId;
|
||||
debug(transport, transUrl, iframeUrl);
|
||||
|
||||
this.iframeObj = iframeUtils.createIframe(iframeUrl, function(r) {
|
||||
debug('err callback');
|
||||
self.emit('close', 1006, 'Unable to load an iframe (' + r + ')');
|
||||
self.close();
|
||||
});
|
||||
|
||||
this.onmessageCallback = this._message.bind(this);
|
||||
eventUtils.attachEvent('message', this.onmessageCallback);
|
||||
}
|
||||
|
||||
inherits(IframeTransport, EventEmitter);
|
||||
|
||||
IframeTransport.prototype.close = function() {
|
||||
debug('close');
|
||||
this.removeAllListeners();
|
||||
if (this.iframeObj) {
|
||||
eventUtils.detachEvent('message', this.onmessageCallback);
|
||||
try {
|
||||
// When the iframe is not loaded, IE raises an exception
|
||||
// on 'contentWindow'.
|
||||
this.postMessage('c');
|
||||
} catch (x) {
|
||||
// intentionally empty
|
||||
}
|
||||
this.iframeObj.cleanup();
|
||||
this.iframeObj = null;
|
||||
this.onmessageCallback = this.iframeObj = null;
|
||||
}
|
||||
};
|
||||
|
||||
IframeTransport.prototype._message = function(e) {
|
||||
debug('message', e.data);
|
||||
if (!urlUtils.isOriginEqual(e.origin, this.origin)) {
|
||||
debug('not same origin', e.origin, this.origin);
|
||||
return;
|
||||
}
|
||||
|
||||
var iframeMessage;
|
||||
try {
|
||||
iframeMessage = JSON3.parse(e.data);
|
||||
} catch (ignored) {
|
||||
debug('bad json', e.data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (iframeMessage.windowId !== this.windowId) {
|
||||
debug('mismatched window id', iframeMessage.windowId, this.windowId);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (iframeMessage.type) {
|
||||
case 's':
|
||||
this.iframeObj.loaded();
|
||||
// window global dependency
|
||||
this.postMessage('s', JSON3.stringify([
|
||||
version
|
||||
, this.transport
|
||||
, this.transUrl
|
||||
, this.baseUrl
|
||||
]));
|
||||
break;
|
||||
case 't':
|
||||
this.emit('message', iframeMessage.data);
|
||||
break;
|
||||
case 'c':
|
||||
var cdata;
|
||||
try {
|
||||
cdata = JSON3.parse(iframeMessage.data);
|
||||
} catch (ignored) {
|
||||
debug('bad json', iframeMessage.data);
|
||||
return;
|
||||
}
|
||||
this.emit('close', cdata[0], cdata[1]);
|
||||
this.close();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
IframeTransport.prototype.postMessage = function(type, data) {
|
||||
debug('postMessage', type, data);
|
||||
this.iframeObj.post(JSON3.stringify({
|
||||
windowId: this.windowId
|
||||
, type: type
|
||||
, data: data || ''
|
||||
}), this.origin);
|
||||
};
|
||||
|
||||
IframeTransport.prototype.send = function(message) {
|
||||
debug('send', message);
|
||||
this.postMessage('m', message);
|
||||
};
|
||||
|
||||
IframeTransport.enabled = function() {
|
||||
return iframeUtils.iframeEnabled;
|
||||
};
|
||||
|
||||
IframeTransport.transportName = 'iframe';
|
||||
IframeTransport.roundTrips = 2;
|
||||
|
||||
module.exports = IframeTransport;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
// The simplest and most robust transport, using the well-know cross
|
||||
// domain hack - JSONP. This transport is quite inefficient - one
|
||||
// message could use up to one http request. But at least it works almost
|
||||
// everywhere.
|
||||
// Known limitations:
|
||||
// o you will get a spinning cursor
|
||||
// o for Konqueror a dumb timer is needed to detect errors
|
||||
|
||||
var inherits = require('inherits')
|
||||
, SenderReceiver = require('./lib/sender-receiver')
|
||||
, JsonpReceiver = require('./receiver/jsonp')
|
||||
, jsonpSender = require('./sender/jsonp')
|
||||
;
|
||||
|
||||
function JsonPTransport(transUrl) {
|
||||
if (!JsonPTransport.enabled()) {
|
||||
throw new Error('Transport created when disabled');
|
||||
}
|
||||
SenderReceiver.call(this, transUrl, '/jsonp', jsonpSender, JsonpReceiver);
|
||||
}
|
||||
|
||||
inherits(JsonPTransport, SenderReceiver);
|
||||
|
||||
JsonPTransport.enabled = function() {
|
||||
return !!global.document;
|
||||
};
|
||||
|
||||
JsonPTransport.transportName = 'jsonp-polling';
|
||||
JsonPTransport.roundTrips = 1;
|
||||
JsonPTransport.needBody = true;
|
||||
|
||||
module.exports = JsonPTransport;
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, urlUtils = require('../../utils/url')
|
||||
, SenderReceiver = require('./sender-receiver')
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:ajax-based');
|
||||
}
|
||||
|
||||
function createAjaxSender(AjaxObject) {
|
||||
return function(url, payload, callback) {
|
||||
debug('create ajax sender', url, payload);
|
||||
var opt = {};
|
||||
if (typeof payload === 'string') {
|
||||
opt.headers = {'Content-type': 'text/plain'};
|
||||
}
|
||||
var ajaxUrl = urlUtils.addPath(url, '/xhr_send');
|
||||
var xo = new AjaxObject('POST', ajaxUrl, payload, opt);
|
||||
xo.once('finish', function(status) {
|
||||
debug('finish', status);
|
||||
xo = null;
|
||||
|
||||
if (status !== 200 && status !== 204) {
|
||||
return callback(new Error('http status ' + status));
|
||||
}
|
||||
callback();
|
||||
});
|
||||
return function() {
|
||||
debug('abort');
|
||||
xo.close();
|
||||
xo = null;
|
||||
|
||||
var err = new Error('Aborted');
|
||||
err.code = 1000;
|
||||
callback(err);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function AjaxBasedTransport(transUrl, urlSuffix, Receiver, AjaxObject) {
|
||||
SenderReceiver.call(this, transUrl, urlSuffix, createAjaxSender(AjaxObject), Receiver, AjaxObject);
|
||||
}
|
||||
|
||||
inherits(AjaxBasedTransport, SenderReceiver);
|
||||
|
||||
module.exports = AjaxBasedTransport;
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, EventEmitter = require('events').EventEmitter
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:buffered-sender');
|
||||
}
|
||||
|
||||
function BufferedSender(url, sender) {
|
||||
debug(url);
|
||||
EventEmitter.call(this);
|
||||
this.sendBuffer = [];
|
||||
this.sender = sender;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
inherits(BufferedSender, EventEmitter);
|
||||
|
||||
BufferedSender.prototype.send = function(message) {
|
||||
debug('send', message);
|
||||
this.sendBuffer.push(message);
|
||||
if (!this.sendStop) {
|
||||
this.sendSchedule();
|
||||
}
|
||||
};
|
||||
|
||||
// For polling transports in a situation when in the message callback,
|
||||
// new message is being send. If the sending connection was started
|
||||
// before receiving one, it is possible to saturate the network and
|
||||
// timeout due to the lack of receiving socket. To avoid that we delay
|
||||
// sending messages by some small time, in order to let receiving
|
||||
// connection be started beforehand. This is only a halfmeasure and
|
||||
// does not fix the big problem, but it does make the tests go more
|
||||
// stable on slow networks.
|
||||
BufferedSender.prototype.sendScheduleWait = function() {
|
||||
debug('sendScheduleWait');
|
||||
var self = this;
|
||||
var tref;
|
||||
this.sendStop = function() {
|
||||
debug('sendStop');
|
||||
self.sendStop = null;
|
||||
clearTimeout(tref);
|
||||
};
|
||||
tref = setTimeout(function() {
|
||||
debug('timeout');
|
||||
self.sendStop = null;
|
||||
self.sendSchedule();
|
||||
}, 25);
|
||||
};
|
||||
|
||||
BufferedSender.prototype.sendSchedule = function() {
|
||||
debug('sendSchedule', this.sendBuffer.length);
|
||||
var self = this;
|
||||
if (this.sendBuffer.length > 0) {
|
||||
var payload = '[' + this.sendBuffer.join(',') + ']';
|
||||
this.sendStop = this.sender(this.url, payload, function(err) {
|
||||
self.sendStop = null;
|
||||
if (err) {
|
||||
debug('error', err);
|
||||
self.emit('close', err.code || 1006, 'Sending error: ' + err);
|
||||
self.close();
|
||||
} else {
|
||||
self.sendScheduleWait();
|
||||
}
|
||||
});
|
||||
this.sendBuffer = [];
|
||||
}
|
||||
};
|
||||
|
||||
BufferedSender.prototype._cleanup = function() {
|
||||
debug('_cleanup');
|
||||
this.removeAllListeners();
|
||||
};
|
||||
|
||||
BufferedSender.prototype.close = function() {
|
||||
debug('close');
|
||||
this._cleanup();
|
||||
if (this.sendStop) {
|
||||
this.sendStop();
|
||||
this.sendStop = null;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = BufferedSender;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, IframeTransport = require('../iframe')
|
||||
, objectUtils = require('../../utils/object')
|
||||
;
|
||||
|
||||
module.exports = function(transport) {
|
||||
|
||||
function IframeWrapTransport(transUrl, baseUrl) {
|
||||
IframeTransport.call(this, transport.transportName, transUrl, baseUrl);
|
||||
}
|
||||
|
||||
inherits(IframeWrapTransport, IframeTransport);
|
||||
|
||||
IframeWrapTransport.enabled = function(url, info) {
|
||||
if (!global.document) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var iframeInfo = objectUtils.extend({}, info);
|
||||
iframeInfo.sameOrigin = true;
|
||||
return transport.enabled(iframeInfo) && IframeTransport.enabled();
|
||||
};
|
||||
|
||||
IframeWrapTransport.transportName = 'iframe-' + transport.transportName;
|
||||
IframeWrapTransport.needBody = true;
|
||||
IframeWrapTransport.roundTrips = IframeTransport.roundTrips + transport.roundTrips - 1; // html, javascript (2) + transport - no CORS (1)
|
||||
|
||||
IframeWrapTransport.facadeTransport = transport;
|
||||
|
||||
return IframeWrapTransport;
|
||||
};
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, EventEmitter = require('events').EventEmitter
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:polling');
|
||||
}
|
||||
|
||||
function Polling(Receiver, receiveUrl, AjaxObject) {
|
||||
debug(receiveUrl);
|
||||
EventEmitter.call(this);
|
||||
this.Receiver = Receiver;
|
||||
this.receiveUrl = receiveUrl;
|
||||
this.AjaxObject = AjaxObject;
|
||||
this._scheduleReceiver();
|
||||
}
|
||||
|
||||
inherits(Polling, EventEmitter);
|
||||
|
||||
Polling.prototype._scheduleReceiver = function() {
|
||||
debug('_scheduleReceiver');
|
||||
var self = this;
|
||||
var poll = this.poll = new this.Receiver(this.receiveUrl, this.AjaxObject);
|
||||
|
||||
poll.on('message', function(msg) {
|
||||
debug('message', msg);
|
||||
self.emit('message', msg);
|
||||
});
|
||||
|
||||
poll.once('close', function(code, reason) {
|
||||
debug('close', code, reason, self.pollIsClosing);
|
||||
self.poll = poll = null;
|
||||
|
||||
if (!self.pollIsClosing) {
|
||||
if (reason === 'network') {
|
||||
self._scheduleReceiver();
|
||||
} else {
|
||||
self.emit('close', code || 1006, reason);
|
||||
self.removeAllListeners();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Polling.prototype.abort = function() {
|
||||
debug('abort');
|
||||
this.removeAllListeners();
|
||||
this.pollIsClosing = true;
|
||||
if (this.poll) {
|
||||
this.poll.abort();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Polling;
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, urlUtils = require('../../utils/url')
|
||||
, BufferedSender = require('./buffered-sender')
|
||||
, Polling = require('./polling')
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:sender-receiver');
|
||||
}
|
||||
|
||||
function SenderReceiver(transUrl, urlSuffix, senderFunc, Receiver, AjaxObject) {
|
||||
var pollUrl = urlUtils.addPath(transUrl, urlSuffix);
|
||||
debug(pollUrl);
|
||||
var self = this;
|
||||
BufferedSender.call(this, transUrl, senderFunc);
|
||||
|
||||
this.poll = new Polling(Receiver, pollUrl, AjaxObject);
|
||||
this.poll.on('message', function(msg) {
|
||||
debug('poll message', msg);
|
||||
self.emit('message', msg);
|
||||
});
|
||||
this.poll.once('close', function(code, reason) {
|
||||
debug('poll close', code, reason);
|
||||
self.poll = null;
|
||||
self.emit('close', code, reason);
|
||||
self.close();
|
||||
});
|
||||
}
|
||||
|
||||
inherits(SenderReceiver, BufferedSender);
|
||||
|
||||
SenderReceiver.prototype.close = function() {
|
||||
BufferedSender.prototype.close.call(this);
|
||||
debug('close');
|
||||
this.removeAllListeners();
|
||||
if (this.poll) {
|
||||
this.poll.abort();
|
||||
this.poll = null;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = SenderReceiver;
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, EventEmitter = require('events').EventEmitter
|
||||
, EventSourceDriver = require('eventsource')
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:receiver:eventsource');
|
||||
}
|
||||
|
||||
function EventSourceReceiver(url) {
|
||||
debug(url);
|
||||
EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
var es = this.es = new EventSourceDriver(url);
|
||||
es.onmessage = function(e) {
|
||||
debug('message', e.data);
|
||||
self.emit('message', decodeURI(e.data));
|
||||
};
|
||||
es.onerror = function(e) {
|
||||
debug('error', es.readyState, e);
|
||||
// ES on reconnection has readyState = 0 or 1.
|
||||
// on network error it's CLOSED = 2
|
||||
var reason = (es.readyState !== 2 ? 'network' : 'permanent');
|
||||
self._cleanup();
|
||||
self._close(reason);
|
||||
};
|
||||
}
|
||||
|
||||
inherits(EventSourceReceiver, EventEmitter);
|
||||
|
||||
EventSourceReceiver.prototype.abort = function() {
|
||||
debug('abort');
|
||||
this._cleanup();
|
||||
this._close('user');
|
||||
};
|
||||
|
||||
EventSourceReceiver.prototype._cleanup = function() {
|
||||
debug('cleanup');
|
||||
var es = this.es;
|
||||
if (es) {
|
||||
es.onmessage = es.onerror = null;
|
||||
es.close();
|
||||
this.es = null;
|
||||
}
|
||||
};
|
||||
|
||||
EventSourceReceiver.prototype._close = function(reason) {
|
||||
debug('close', reason);
|
||||
var self = this;
|
||||
// Safari and chrome < 15 crash if we close window before
|
||||
// waiting for ES cleanup. See:
|
||||
// https://code.google.com/p/chromium/issues/detail?id=89155
|
||||
setTimeout(function() {
|
||||
self.emit('close', null, reason);
|
||||
self.removeAllListeners();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
module.exports = EventSourceReceiver;
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, iframeUtils = require('../../utils/iframe')
|
||||
, urlUtils = require('../../utils/url')
|
||||
, EventEmitter = require('events').EventEmitter
|
||||
, random = require('../../utils/random')
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:receiver:htmlfile');
|
||||
}
|
||||
|
||||
function HtmlfileReceiver(url) {
|
||||
debug(url);
|
||||
EventEmitter.call(this);
|
||||
var self = this;
|
||||
iframeUtils.polluteGlobalNamespace();
|
||||
|
||||
this.id = 'a' + random.string(6);
|
||||
url = urlUtils.addQuery(url, 'c=' + decodeURIComponent(iframeUtils.WPrefix + '.' + this.id));
|
||||
|
||||
debug('using htmlfile', HtmlfileReceiver.htmlfileEnabled);
|
||||
var constructFunc = HtmlfileReceiver.htmlfileEnabled ?
|
||||
iframeUtils.createHtmlfile : iframeUtils.createIframe;
|
||||
|
||||
global[iframeUtils.WPrefix][this.id] = {
|
||||
start: function() {
|
||||
debug('start');
|
||||
self.iframeObj.loaded();
|
||||
}
|
||||
, message: function(data) {
|
||||
debug('message', data);
|
||||
self.emit('message', data);
|
||||
}
|
||||
, stop: function() {
|
||||
debug('stop');
|
||||
self._cleanup();
|
||||
self._close('network');
|
||||
}
|
||||
};
|
||||
this.iframeObj = constructFunc(url, function() {
|
||||
debug('callback');
|
||||
self._cleanup();
|
||||
self._close('permanent');
|
||||
});
|
||||
}
|
||||
|
||||
inherits(HtmlfileReceiver, EventEmitter);
|
||||
|
||||
HtmlfileReceiver.prototype.abort = function() {
|
||||
debug('abort');
|
||||
this._cleanup();
|
||||
this._close('user');
|
||||
};
|
||||
|
||||
HtmlfileReceiver.prototype._cleanup = function() {
|
||||
debug('_cleanup');
|
||||
if (this.iframeObj) {
|
||||
this.iframeObj.cleanup();
|
||||
this.iframeObj = null;
|
||||
}
|
||||
delete global[iframeUtils.WPrefix][this.id];
|
||||
};
|
||||
|
||||
HtmlfileReceiver.prototype._close = function(reason) {
|
||||
debug('_close', reason);
|
||||
this.emit('close', null, reason);
|
||||
this.removeAllListeners();
|
||||
};
|
||||
|
||||
HtmlfileReceiver.htmlfileEnabled = false;
|
||||
|
||||
// obfuscate to avoid firewalls
|
||||
var axo = ['Active'].concat('Object').join('X');
|
||||
if (axo in global) {
|
||||
try {
|
||||
HtmlfileReceiver.htmlfileEnabled = !!new global[axo]('htmlfile');
|
||||
} catch (x) {
|
||||
// intentionally empty
|
||||
}
|
||||
}
|
||||
|
||||
HtmlfileReceiver.enabled = HtmlfileReceiver.htmlfileEnabled || iframeUtils.iframeEnabled;
|
||||
|
||||
module.exports = HtmlfileReceiver;
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
'use strict';
|
||||
|
||||
var utils = require('../../utils/iframe')
|
||||
, random = require('../../utils/random')
|
||||
, browser = require('../../utils/browser')
|
||||
, urlUtils = require('../../utils/url')
|
||||
, inherits = require('inherits')
|
||||
, EventEmitter = require('events').EventEmitter
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:receiver:jsonp');
|
||||
}
|
||||
|
||||
function JsonpReceiver(url) {
|
||||
debug(url);
|
||||
var self = this;
|
||||
EventEmitter.call(this);
|
||||
|
||||
utils.polluteGlobalNamespace();
|
||||
|
||||
this.id = 'a' + random.string(6);
|
||||
var urlWithId = urlUtils.addQuery(url, 'c=' + encodeURIComponent(utils.WPrefix + '.' + this.id));
|
||||
|
||||
global[utils.WPrefix][this.id] = this._callback.bind(this);
|
||||
this._createScript(urlWithId);
|
||||
|
||||
// Fallback mostly for Konqueror - stupid timer, 35 seconds shall be plenty.
|
||||
this.timeoutId = setTimeout(function() {
|
||||
debug('timeout');
|
||||
self._abort(new Error('JSONP script loaded abnormally (timeout)'));
|
||||
}, JsonpReceiver.timeout);
|
||||
}
|
||||
|
||||
inherits(JsonpReceiver, EventEmitter);
|
||||
|
||||
JsonpReceiver.prototype.abort = function() {
|
||||
debug('abort');
|
||||
if (global[utils.WPrefix][this.id]) {
|
||||
var err = new Error('JSONP user aborted read');
|
||||
err.code = 1000;
|
||||
this._abort(err);
|
||||
}
|
||||
};
|
||||
|
||||
JsonpReceiver.timeout = 35000;
|
||||
JsonpReceiver.scriptErrorTimeout = 1000;
|
||||
|
||||
JsonpReceiver.prototype._callback = function(data) {
|
||||
debug('_callback', data);
|
||||
this._cleanup();
|
||||
|
||||
if (this.aborting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
debug('message', data);
|
||||
this.emit('message', data);
|
||||
}
|
||||
this.emit('close', null, 'network');
|
||||
this.removeAllListeners();
|
||||
};
|
||||
|
||||
JsonpReceiver.prototype._abort = function(err) {
|
||||
debug('_abort', err);
|
||||
this._cleanup();
|
||||
this.aborting = true;
|
||||
this.emit('close', err.code, err.message);
|
||||
this.removeAllListeners();
|
||||
};
|
||||
|
||||
JsonpReceiver.prototype._cleanup = function() {
|
||||
debug('_cleanup');
|
||||
clearTimeout(this.timeoutId);
|
||||
if (this.script2) {
|
||||
this.script2.parentNode.removeChild(this.script2);
|
||||
this.script2 = null;
|
||||
}
|
||||
if (this.script) {
|
||||
var script = this.script;
|
||||
// Unfortunately, you can't really abort script loading of
|
||||
// the script.
|
||||
script.parentNode.removeChild(script);
|
||||
script.onreadystatechange = script.onerror =
|
||||
script.onload = script.onclick = null;
|
||||
this.script = null;
|
||||
}
|
||||
delete global[utils.WPrefix][this.id];
|
||||
};
|
||||
|
||||
JsonpReceiver.prototype._scriptError = function() {
|
||||
debug('_scriptError');
|
||||
var self = this;
|
||||
if (this.errorTimer) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.errorTimer = setTimeout(function() {
|
||||
if (!self.loadedOkay) {
|
||||
self._abort(new Error('JSONP script loaded abnormally (onerror)'));
|
||||
}
|
||||
}, JsonpReceiver.scriptErrorTimeout);
|
||||
};
|
||||
|
||||
JsonpReceiver.prototype._createScript = function(url) {
|
||||
debug('_createScript', url);
|
||||
var self = this;
|
||||
var script = this.script = global.document.createElement('script');
|
||||
var script2; // Opera synchronous load trick.
|
||||
|
||||
script.id = 'a' + random.string(8);
|
||||
script.src = url;
|
||||
script.type = 'text/javascript';
|
||||
script.charset = 'UTF-8';
|
||||
script.onerror = this._scriptError.bind(this);
|
||||
script.onload = function() {
|
||||
debug('onload');
|
||||
self._abort(new Error('JSONP script loaded abnormally (onload)'));
|
||||
};
|
||||
|
||||
// IE9 fires 'error' event after onreadystatechange or before, in random order.
|
||||
// Use loadedOkay to determine if actually errored
|
||||
script.onreadystatechange = function() {
|
||||
debug('onreadystatechange', script.readyState);
|
||||
if (/loaded|closed/.test(script.readyState)) {
|
||||
if (script && script.htmlFor && script.onclick) {
|
||||
self.loadedOkay = true;
|
||||
try {
|
||||
// In IE, actually execute the script.
|
||||
script.onclick();
|
||||
} catch (x) {
|
||||
// intentionally empty
|
||||
}
|
||||
}
|
||||
if (script) {
|
||||
self._abort(new Error('JSONP script loaded abnormally (onreadystatechange)'));
|
||||
}
|
||||
}
|
||||
};
|
||||
// IE: event/htmlFor/onclick trick.
|
||||
// One can't rely on proper order for onreadystatechange. In order to
|
||||
// make sure, set a 'htmlFor' and 'event' properties, so that
|
||||
// script code will be installed as 'onclick' handler for the
|
||||
// script object. Later, onreadystatechange, manually execute this
|
||||
// code. FF and Chrome doesn't work with 'event' and 'htmlFor'
|
||||
// set. For reference see:
|
||||
// http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
|
||||
// Also, read on that about script ordering:
|
||||
// http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
|
||||
if (typeof script.async === 'undefined' && global.document.attachEvent) {
|
||||
// According to mozilla docs, in recent browsers script.async defaults
|
||||
// to 'true', so we may use it to detect a good browser:
|
||||
// https://developer.mozilla.org/en/HTML/Element/script
|
||||
if (!browser.isOpera()) {
|
||||
// Naively assume we're in IE
|
||||
try {
|
||||
script.htmlFor = script.id;
|
||||
script.event = 'onclick';
|
||||
} catch (x) {
|
||||
// intentionally empty
|
||||
}
|
||||
script.async = true;
|
||||
} else {
|
||||
// Opera, second sync script hack
|
||||
script2 = this.script2 = global.document.createElement('script');
|
||||
script2.text = "try{var a = document.getElementById('" + script.id + "'); if(a)a.onerror();}catch(x){};";
|
||||
script.async = script2.async = false;
|
||||
}
|
||||
}
|
||||
if (typeof script.async !== 'undefined') {
|
||||
script.async = true;
|
||||
}
|
||||
|
||||
var head = global.document.getElementsByTagName('head')[0];
|
||||
head.insertBefore(script, head.firstChild);
|
||||
if (script2) {
|
||||
head.insertBefore(script2, head.firstChild);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = JsonpReceiver;
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, EventEmitter = require('events').EventEmitter
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:receiver:xhr');
|
||||
}
|
||||
|
||||
function XhrReceiver(url, AjaxObject) {
|
||||
debug(url);
|
||||
EventEmitter.call(this);
|
||||
var self = this;
|
||||
|
||||
this.bufferPosition = 0;
|
||||
|
||||
this.xo = new AjaxObject('POST', url, null);
|
||||
this.xo.on('chunk', this._chunkHandler.bind(this));
|
||||
this.xo.once('finish', function(status, text) {
|
||||
debug('finish', status, text);
|
||||
self._chunkHandler(status, text);
|
||||
self.xo = null;
|
||||
var reason = status === 200 ? 'network' : 'permanent';
|
||||
debug('close', reason);
|
||||
self.emit('close', null, reason);
|
||||
self._cleanup();
|
||||
});
|
||||
}
|
||||
|
||||
inherits(XhrReceiver, EventEmitter);
|
||||
|
||||
XhrReceiver.prototype._chunkHandler = function(status, text) {
|
||||
debug('_chunkHandler', status);
|
||||
if (status !== 200 || !text) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var idx = -1; ; this.bufferPosition += idx + 1) {
|
||||
var buf = text.slice(this.bufferPosition);
|
||||
idx = buf.indexOf('\n');
|
||||
if (idx === -1) {
|
||||
break;
|
||||
}
|
||||
var msg = buf.slice(0, idx);
|
||||
if (msg) {
|
||||
debug('message', msg);
|
||||
this.emit('message', msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XhrReceiver.prototype._cleanup = function() {
|
||||
debug('_cleanup');
|
||||
this.removeAllListeners();
|
||||
};
|
||||
|
||||
XhrReceiver.prototype.abort = function() {
|
||||
debug('abort');
|
||||
if (this.xo) {
|
||||
this.xo.close();
|
||||
debug('close');
|
||||
this.emit('close', null, 'user');
|
||||
this.xo = null;
|
||||
}
|
||||
this._cleanup();
|
||||
};
|
||||
|
||||
module.exports = XhrReceiver;
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
'use strict';
|
||||
|
||||
var random = require('../../utils/random')
|
||||
, urlUtils = require('../../utils/url')
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:sender:jsonp');
|
||||
}
|
||||
|
||||
var form, area;
|
||||
|
||||
function createIframe(id) {
|
||||
debug('createIframe', id);
|
||||
try {
|
||||
// ie6 dynamic iframes with target="" support (thanks Chris Lambacher)
|
||||
return global.document.createElement('<iframe name="' + id + '">');
|
||||
} catch (x) {
|
||||
var iframe = global.document.createElement('iframe');
|
||||
iframe.name = id;
|
||||
return iframe;
|
||||
}
|
||||
}
|
||||
|
||||
function createForm() {
|
||||
debug('createForm');
|
||||
form = global.document.createElement('form');
|
||||
form.style.display = 'none';
|
||||
form.style.position = 'absolute';
|
||||
form.method = 'POST';
|
||||
form.enctype = 'application/x-www-form-urlencoded';
|
||||
form.acceptCharset = 'UTF-8';
|
||||
|
||||
area = global.document.createElement('textarea');
|
||||
area.name = 'd';
|
||||
form.appendChild(area);
|
||||
|
||||
global.document.body.appendChild(form);
|
||||
}
|
||||
|
||||
module.exports = function(url, payload, callback) {
|
||||
debug(url, payload);
|
||||
if (!form) {
|
||||
createForm();
|
||||
}
|
||||
var id = 'a' + random.string(8);
|
||||
form.target = id;
|
||||
form.action = urlUtils.addQuery(urlUtils.addPath(url, '/jsonp_send'), 'i=' + id);
|
||||
|
||||
var iframe = createIframe(id);
|
||||
iframe.id = id;
|
||||
iframe.style.display = 'none';
|
||||
form.appendChild(iframe);
|
||||
|
||||
try {
|
||||
area.value = payload;
|
||||
} catch (e) {
|
||||
// seriously broken browsers get here
|
||||
}
|
||||
form.submit();
|
||||
|
||||
var completed = function(err) {
|
||||
debug('completed', id, err);
|
||||
if (!iframe.onerror) {
|
||||
return;
|
||||
}
|
||||
iframe.onreadystatechange = iframe.onerror = iframe.onload = null;
|
||||
// Opera mini doesn't like if we GC iframe
|
||||
// immediately, thus this timeout.
|
||||
setTimeout(function() {
|
||||
debug('cleaning up', id);
|
||||
iframe.parentNode.removeChild(iframe);
|
||||
iframe = null;
|
||||
}, 500);
|
||||
area.value = '';
|
||||
// It is not possible to detect if the iframe succeeded or
|
||||
// failed to submit our form.
|
||||
callback(err);
|
||||
};
|
||||
iframe.onerror = function() {
|
||||
debug('onerror', id);
|
||||
completed();
|
||||
};
|
||||
iframe.onload = function() {
|
||||
debug('onload', id);
|
||||
completed();
|
||||
};
|
||||
iframe.onreadystatechange = function(e) {
|
||||
debug('onreadystatechange', id, iframe.readyState, e);
|
||||
if (iframe.readyState === 'complete') {
|
||||
completed();
|
||||
}
|
||||
};
|
||||
return function() {
|
||||
debug('aborted', id);
|
||||
completed(new Error('Aborted'));
|
||||
};
|
||||
};
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
'use strict';
|
||||
|
||||
var EventEmitter = require('events').EventEmitter
|
||||
, inherits = require('inherits')
|
||||
, eventUtils = require('../../utils/event')
|
||||
, browser = require('../../utils/browser')
|
||||
, urlUtils = require('../../utils/url')
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:sender:xdr');
|
||||
}
|
||||
|
||||
// References:
|
||||
// http://ajaxian.com/archives/100-line-ajax-wrapper
|
||||
// http://msdn.microsoft.com/en-us/library/cc288060(v=VS.85).aspx
|
||||
|
||||
function XDRObject(method, url, payload) {
|
||||
debug(method, url);
|
||||
var self = this;
|
||||
EventEmitter.call(this);
|
||||
|
||||
setTimeout(function() {
|
||||
self._start(method, url, payload);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
inherits(XDRObject, EventEmitter);
|
||||
|
||||
XDRObject.prototype._start = function(method, url, payload) {
|
||||
debug('_start');
|
||||
var self = this;
|
||||
var xdr = new global.XDomainRequest();
|
||||
// IE caches even POSTs
|
||||
url = urlUtils.addQuery(url, 't=' + (+new Date()));
|
||||
|
||||
xdr.onerror = function() {
|
||||
debug('onerror');
|
||||
self._error();
|
||||
};
|
||||
xdr.ontimeout = function() {
|
||||
debug('ontimeout');
|
||||
self._error();
|
||||
};
|
||||
xdr.onprogress = function() {
|
||||
debug('progress', xdr.responseText);
|
||||
self.emit('chunk', 200, xdr.responseText);
|
||||
};
|
||||
xdr.onload = function() {
|
||||
debug('load');
|
||||
self.emit('finish', 200, xdr.responseText);
|
||||
self._cleanup(false);
|
||||
};
|
||||
this.xdr = xdr;
|
||||
this.unloadRef = eventUtils.unloadAdd(function() {
|
||||
self._cleanup(true);
|
||||
});
|
||||
try {
|
||||
// Fails with AccessDenied if port number is bogus
|
||||
this.xdr.open(method, url);
|
||||
if (this.timeout) {
|
||||
this.xdr.timeout = this.timeout;
|
||||
}
|
||||
this.xdr.send(payload);
|
||||
} catch (x) {
|
||||
this._error();
|
||||
}
|
||||
};
|
||||
|
||||
XDRObject.prototype._error = function() {
|
||||
this.emit('finish', 0, '');
|
||||
this._cleanup(false);
|
||||
};
|
||||
|
||||
XDRObject.prototype._cleanup = function(abort) {
|
||||
debug('cleanup', abort);
|
||||
if (!this.xdr) {
|
||||
return;
|
||||
}
|
||||
this.removeAllListeners();
|
||||
eventUtils.unloadDel(this.unloadRef);
|
||||
|
||||
this.xdr.ontimeout = this.xdr.onerror = this.xdr.onprogress = this.xdr.onload = null;
|
||||
if (abort) {
|
||||
try {
|
||||
this.xdr.abort();
|
||||
} catch (x) {
|
||||
// intentionally empty
|
||||
}
|
||||
}
|
||||
this.unloadRef = this.xdr = null;
|
||||
};
|
||||
|
||||
XDRObject.prototype.close = function() {
|
||||
debug('close');
|
||||
this._cleanup(true);
|
||||
};
|
||||
|
||||
// IE 8/9 if the request target uses the same scheme - #79
|
||||
XDRObject.enabled = !!(global.XDomainRequest && browser.hasDomain());
|
||||
|
||||
module.exports = XDRObject;
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, XhrDriver = require('../driver/xhr')
|
||||
;
|
||||
|
||||
function XHRCorsObject(method, url, payload, opts) {
|
||||
XhrDriver.call(this, method, url, payload, opts);
|
||||
}
|
||||
|
||||
inherits(XHRCorsObject, XhrDriver);
|
||||
|
||||
XHRCorsObject.enabled = XhrDriver.enabled && XhrDriver.supportsCORS;
|
||||
|
||||
module.exports = XHRCorsObject;
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
var EventEmitter = require('events').EventEmitter
|
||||
, inherits = require('inherits')
|
||||
;
|
||||
|
||||
function XHRFake(/* method, url, payload, opts */) {
|
||||
var self = this;
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.to = setTimeout(function() {
|
||||
self.emit('finish', 200, '{}');
|
||||
}, XHRFake.timeout);
|
||||
}
|
||||
|
||||
inherits(XHRFake, EventEmitter);
|
||||
|
||||
XHRFake.prototype.close = function() {
|
||||
clearTimeout(this.to);
|
||||
};
|
||||
|
||||
XHRFake.timeout = 2000;
|
||||
|
||||
module.exports = XHRFake;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, XhrDriver = require('../driver/xhr')
|
||||
;
|
||||
|
||||
function XHRLocalObject(method, url, payload /*, opts */) {
|
||||
XhrDriver.call(this, method, url, payload, {
|
||||
noCredentials: true
|
||||
});
|
||||
}
|
||||
|
||||
inherits(XHRLocalObject, XhrDriver);
|
||||
|
||||
XHRLocalObject.enabled = XhrDriver.enabled;
|
||||
|
||||
module.exports = XHRLocalObject;
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
'use strict';
|
||||
|
||||
var utils = require('../utils/event')
|
||||
, urlUtils = require('../utils/url')
|
||||
, inherits = require('inherits')
|
||||
, EventEmitter = require('events').EventEmitter
|
||||
, WebsocketDriver = require('./driver/websocket')
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:websocket');
|
||||
}
|
||||
|
||||
function WebSocketTransport(transUrl, ignore, options) {
|
||||
if (!WebSocketTransport.enabled()) {
|
||||
throw new Error('Transport created when disabled');
|
||||
}
|
||||
|
||||
EventEmitter.call(this);
|
||||
debug('constructor', transUrl);
|
||||
|
||||
var self = this;
|
||||
var url = urlUtils.addPath(transUrl, '/websocket');
|
||||
if (url.slice(0, 5) === 'https') {
|
||||
url = 'wss' + url.slice(5);
|
||||
} else {
|
||||
url = 'ws' + url.slice(4);
|
||||
}
|
||||
this.url = url;
|
||||
|
||||
this.ws = new WebsocketDriver(this.url, [], options);
|
||||
this.ws.onmessage = function(e) {
|
||||
debug('message event', e.data);
|
||||
self.emit('message', e.data);
|
||||
};
|
||||
// Firefox has an interesting bug. If a websocket connection is
|
||||
// created after onunload, it stays alive even when user
|
||||
// navigates away from the page. In such situation let's lie -
|
||||
// let's not open the ws connection at all. See:
|
||||
// https://github.com/sockjs/sockjs-client/issues/28
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=696085
|
||||
this.unloadRef = utils.unloadAdd(function() {
|
||||
debug('unload');
|
||||
self.ws.close();
|
||||
});
|
||||
this.ws.onclose = function(e) {
|
||||
debug('close event', e.code, e.reason);
|
||||
self.emit('close', e.code, e.reason);
|
||||
self._cleanup();
|
||||
};
|
||||
this.ws.onerror = function(e) {
|
||||
debug('error event', e);
|
||||
self.emit('close', 1006, 'WebSocket connection broken');
|
||||
self._cleanup();
|
||||
};
|
||||
}
|
||||
|
||||
inherits(WebSocketTransport, EventEmitter);
|
||||
|
||||
WebSocketTransport.prototype.send = function(data) {
|
||||
var msg = '[' + data + ']';
|
||||
debug('send', msg);
|
||||
this.ws.send(msg);
|
||||
};
|
||||
|
||||
WebSocketTransport.prototype.close = function() {
|
||||
debug('close');
|
||||
var ws = this.ws;
|
||||
this._cleanup();
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
};
|
||||
|
||||
WebSocketTransport.prototype._cleanup = function() {
|
||||
debug('_cleanup');
|
||||
var ws = this.ws;
|
||||
if (ws) {
|
||||
ws.onmessage = ws.onclose = ws.onerror = null;
|
||||
}
|
||||
utils.unloadDel(this.unloadRef);
|
||||
this.unloadRef = this.ws = null;
|
||||
this.removeAllListeners();
|
||||
};
|
||||
|
||||
WebSocketTransport.enabled = function() {
|
||||
debug('enabled');
|
||||
return !!WebsocketDriver;
|
||||
};
|
||||
WebSocketTransport.transportName = 'websocket';
|
||||
|
||||
// In theory, ws should require 1 round trip. But in chrome, this is
|
||||
// not very stable over SSL. Most likely a ws connection requires a
|
||||
// separate SSL connection, in which case 2 round trips are an
|
||||
// absolute minumum.
|
||||
WebSocketTransport.roundTrips = 2;
|
||||
|
||||
module.exports = WebSocketTransport;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, AjaxBasedTransport = require('./lib/ajax-based')
|
||||
, XdrStreamingTransport = require('./xdr-streaming')
|
||||
, XhrReceiver = require('./receiver/xhr')
|
||||
, XDRObject = require('./sender/xdr')
|
||||
;
|
||||
|
||||
function XdrPollingTransport(transUrl) {
|
||||
if (!XDRObject.enabled) {
|
||||
throw new Error('Transport created when disabled');
|
||||
}
|
||||
AjaxBasedTransport.call(this, transUrl, '/xhr', XhrReceiver, XDRObject);
|
||||
}
|
||||
|
||||
inherits(XdrPollingTransport, AjaxBasedTransport);
|
||||
|
||||
XdrPollingTransport.enabled = XdrStreamingTransport.enabled;
|
||||
XdrPollingTransport.transportName = 'xdr-polling';
|
||||
XdrPollingTransport.roundTrips = 2; // preflight, ajax
|
||||
|
||||
module.exports = XdrPollingTransport;
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, AjaxBasedTransport = require('./lib/ajax-based')
|
||||
, XhrReceiver = require('./receiver/xhr')
|
||||
, XDRObject = require('./sender/xdr')
|
||||
;
|
||||
|
||||
// According to:
|
||||
// http://stackoverflow.com/questions/1641507/detect-browser-support-for-cross-domain-xmlhttprequests
|
||||
// http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/
|
||||
|
||||
function XdrStreamingTransport(transUrl) {
|
||||
if (!XDRObject.enabled) {
|
||||
throw new Error('Transport created when disabled');
|
||||
}
|
||||
AjaxBasedTransport.call(this, transUrl, '/xhr_streaming', XhrReceiver, XDRObject);
|
||||
}
|
||||
|
||||
inherits(XdrStreamingTransport, AjaxBasedTransport);
|
||||
|
||||
XdrStreamingTransport.enabled = function(info) {
|
||||
if (info.cookie_needed || info.nullOrigin) {
|
||||
return false;
|
||||
}
|
||||
return XDRObject.enabled && info.sameScheme;
|
||||
};
|
||||
|
||||
XdrStreamingTransport.transportName = 'xdr-streaming';
|
||||
XdrStreamingTransport.roundTrips = 2; // preflight, ajax
|
||||
|
||||
module.exports = XdrStreamingTransport;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, AjaxBasedTransport = require('./lib/ajax-based')
|
||||
, XhrReceiver = require('./receiver/xhr')
|
||||
, XHRCorsObject = require('./sender/xhr-cors')
|
||||
, XHRLocalObject = require('./sender/xhr-local')
|
||||
;
|
||||
|
||||
function XhrPollingTransport(transUrl) {
|
||||
if (!XHRLocalObject.enabled && !XHRCorsObject.enabled) {
|
||||
throw new Error('Transport created when disabled');
|
||||
}
|
||||
AjaxBasedTransport.call(this, transUrl, '/xhr', XhrReceiver, XHRCorsObject);
|
||||
}
|
||||
|
||||
inherits(XhrPollingTransport, AjaxBasedTransport);
|
||||
|
||||
XhrPollingTransport.enabled = function(info) {
|
||||
if (info.nullOrigin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (XHRLocalObject.enabled && info.sameOrigin) {
|
||||
return true;
|
||||
}
|
||||
return XHRCorsObject.enabled;
|
||||
};
|
||||
|
||||
XhrPollingTransport.transportName = 'xhr-polling';
|
||||
XhrPollingTransport.roundTrips = 2; // preflight, ajax
|
||||
|
||||
module.exports = XhrPollingTransport;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
, AjaxBasedTransport = require('./lib/ajax-based')
|
||||
, XhrReceiver = require('./receiver/xhr')
|
||||
, XHRCorsObject = require('./sender/xhr-cors')
|
||||
, XHRLocalObject = require('./sender/xhr-local')
|
||||
, browser = require('../utils/browser')
|
||||
;
|
||||
|
||||
function XhrStreamingTransport(transUrl) {
|
||||
if (!XHRLocalObject.enabled && !XHRCorsObject.enabled) {
|
||||
throw new Error('Transport created when disabled');
|
||||
}
|
||||
AjaxBasedTransport.call(this, transUrl, '/xhr_streaming', XhrReceiver, XHRCorsObject);
|
||||
}
|
||||
|
||||
inherits(XhrStreamingTransport, AjaxBasedTransport);
|
||||
|
||||
XhrStreamingTransport.enabled = function(info) {
|
||||
if (info.nullOrigin) {
|
||||
return false;
|
||||
}
|
||||
// Opera doesn't support xhr-streaming #60
|
||||
// But it might be able to #92
|
||||
if (browser.isOpera()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return XHRCorsObject.enabled;
|
||||
};
|
||||
|
||||
XhrStreamingTransport.transportName = 'xhr-streaming';
|
||||
XhrStreamingTransport.roundTrips = 2; // preflight, ajax
|
||||
|
||||
// Safari gets confused when a streaming ajax request is started
|
||||
// before onload. This causes the load indicator to spin indefinetely.
|
||||
// Only require body when used in a browser
|
||||
XhrStreamingTransport.needBody = !!global.document;
|
||||
|
||||
module.exports = XhrStreamingTransport;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
if (global.crypto && global.crypto.getRandomValues) {
|
||||
module.exports.randomBytes = function(length) {
|
||||
var bytes = new Uint8Array(length);
|
||||
global.crypto.getRandomValues(bytes);
|
||||
return bytes;
|
||||
};
|
||||
} else {
|
||||
module.exports.randomBytes = function(length) {
|
||||
var bytes = new Array(length);
|
||||
for (var i = 0; i < length; i++) {
|
||||
bytes[i] = Math.floor(Math.random() * 256);
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
isOpera: function() {
|
||||
return global.navigator &&
|
||||
/opera/i.test(global.navigator.userAgent);
|
||||
}
|
||||
|
||||
, isKonqueror: function() {
|
||||
return global.navigator &&
|
||||
/konqueror/i.test(global.navigator.userAgent);
|
||||
}
|
||||
|
||||
// #187 wrap document.domain in try/catch because of WP8 from file:///
|
||||
, hasDomain: function () {
|
||||
// non-browser client always has a domain
|
||||
if (!global.document) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
return !!global.document.domain;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
|
||||
var JSON3 = require('json3');
|
||||
|
||||
// Some extra characters that Chrome gets wrong, and substitutes with
|
||||
// something else on the wire.
|
||||
// eslint-disable-next-line no-control-regex
|
||||
var extraEscapable = /[\x00-\x1f\ud800-\udfff\ufffe\uffff\u0300-\u0333\u033d-\u0346\u034a-\u034c\u0350-\u0352\u0357-\u0358\u035c-\u0362\u0374\u037e\u0387\u0591-\u05af\u05c4\u0610-\u0617\u0653-\u0654\u0657-\u065b\u065d-\u065e\u06df-\u06e2\u06eb-\u06ec\u0730\u0732-\u0733\u0735-\u0736\u073a\u073d\u073f-\u0741\u0743\u0745\u0747\u07eb-\u07f1\u0951\u0958-\u095f\u09dc-\u09dd\u09df\u0a33\u0a36\u0a59-\u0a5b\u0a5e\u0b5c-\u0b5d\u0e38-\u0e39\u0f43\u0f4d\u0f52\u0f57\u0f5c\u0f69\u0f72-\u0f76\u0f78\u0f80-\u0f83\u0f93\u0f9d\u0fa2\u0fa7\u0fac\u0fb9\u1939-\u193a\u1a17\u1b6b\u1cda-\u1cdb\u1dc0-\u1dcf\u1dfc\u1dfe\u1f71\u1f73\u1f75\u1f77\u1f79\u1f7b\u1f7d\u1fbb\u1fbe\u1fc9\u1fcb\u1fd3\u1fdb\u1fe3\u1feb\u1fee-\u1fef\u1ff9\u1ffb\u1ffd\u2000-\u2001\u20d0-\u20d1\u20d4-\u20d7\u20e7-\u20e9\u2126\u212a-\u212b\u2329-\u232a\u2adc\u302b-\u302c\uaab2-\uaab3\uf900-\ufa0d\ufa10\ufa12\ufa15-\ufa1e\ufa20\ufa22\ufa25-\ufa26\ufa2a-\ufa2d\ufa30-\ufa6d\ufa70-\ufad9\ufb1d\ufb1f\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4e\ufff0-\uffff]/g
|
||||
, extraLookup;
|
||||
|
||||
// This may be quite slow, so let's delay until user actually uses bad
|
||||
// characters.
|
||||
var unrollLookup = function(escapable) {
|
||||
var i;
|
||||
var unrolled = {};
|
||||
var c = [];
|
||||
for (i = 0; i < 65536; i++) {
|
||||
c.push( String.fromCharCode(i) );
|
||||
}
|
||||
escapable.lastIndex = 0;
|
||||
c.join('').replace(escapable, function(a) {
|
||||
unrolled[ a ] = '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
||||
return '';
|
||||
});
|
||||
escapable.lastIndex = 0;
|
||||
return unrolled;
|
||||
};
|
||||
|
||||
// Quote string, also taking care of unicode characters that browsers
|
||||
// often break. Especially, take care of unicode surrogates:
|
||||
// http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Surrogates
|
||||
module.exports = {
|
||||
quote: function(string) {
|
||||
var quoted = JSON3.stringify(string);
|
||||
|
||||
// In most cases this should be very fast and good enough.
|
||||
extraEscapable.lastIndex = 0;
|
||||
if (!extraEscapable.test(quoted)) {
|
||||
return quoted;
|
||||
}
|
||||
|
||||
if (!extraLookup) {
|
||||
extraLookup = unrollLookup(extraEscapable);
|
||||
}
|
||||
|
||||
return quoted.replace(extraEscapable, function(a) {
|
||||
return extraLookup[a];
|
||||
});
|
||||
}
|
||||
};
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
'use strict';
|
||||
|
||||
var random = require('./random');
|
||||
|
||||
var onUnload = {}
|
||||
, afterUnload = false
|
||||
// detect google chrome packaged apps because they don't allow the 'unload' event
|
||||
, isChromePackagedApp = global.chrome && global.chrome.app && global.chrome.app.runtime
|
||||
;
|
||||
|
||||
module.exports = {
|
||||
attachEvent: function(event, listener) {
|
||||
if (typeof global.addEventListener !== 'undefined') {
|
||||
global.addEventListener(event, listener, false);
|
||||
} else if (global.document && global.attachEvent) {
|
||||
// IE quirks.
|
||||
// According to: http://stevesouders.com/misc/test-postmessage.php
|
||||
// the message gets delivered only to 'document', not 'window'.
|
||||
global.document.attachEvent('on' + event, listener);
|
||||
// I get 'window' for ie8.
|
||||
global.attachEvent('on' + event, listener);
|
||||
}
|
||||
}
|
||||
|
||||
, detachEvent: function(event, listener) {
|
||||
if (typeof global.addEventListener !== 'undefined') {
|
||||
global.removeEventListener(event, listener, false);
|
||||
} else if (global.document && global.detachEvent) {
|
||||
global.document.detachEvent('on' + event, listener);
|
||||
global.detachEvent('on' + event, listener);
|
||||
}
|
||||
}
|
||||
|
||||
, unloadAdd: function(listener) {
|
||||
if (isChromePackagedApp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var ref = random.string(8);
|
||||
onUnload[ref] = listener;
|
||||
if (afterUnload) {
|
||||
setTimeout(this.triggerUnloadCallbacks, 0);
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
, unloadDel: function(ref) {
|
||||
if (ref in onUnload) {
|
||||
delete onUnload[ref];
|
||||
}
|
||||
}
|
||||
|
||||
, triggerUnloadCallbacks: function() {
|
||||
for (var ref in onUnload) {
|
||||
onUnload[ref]();
|
||||
delete onUnload[ref];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var unloadTriggered = function() {
|
||||
if (afterUnload) {
|
||||
return;
|
||||
}
|
||||
afterUnload = true;
|
||||
module.exports.triggerUnloadCallbacks();
|
||||
};
|
||||
|
||||
// 'unload' alone is not reliable in opera within an iframe, but we
|
||||
// can't use `beforeunload` as IE fires it on javascript: links.
|
||||
if (!isChromePackagedApp) {
|
||||
module.exports.attachEvent('unload', unloadTriggered);
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
'use strict';
|
||||
|
||||
var eventUtils = require('./event')
|
||||
, JSON3 = require('json3')
|
||||
, browser = require('./browser')
|
||||
;
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:utils:iframe');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
WPrefix: '_jp'
|
||||
, currentWindowId: null
|
||||
|
||||
, polluteGlobalNamespace: function() {
|
||||
if (!(module.exports.WPrefix in global)) {
|
||||
global[module.exports.WPrefix] = {};
|
||||
}
|
||||
}
|
||||
|
||||
, postMessage: function(type, data) {
|
||||
if (global.parent !== global) {
|
||||
global.parent.postMessage(JSON3.stringify({
|
||||
windowId: module.exports.currentWindowId
|
||||
, type: type
|
||||
, data: data || ''
|
||||
}), '*');
|
||||
} else {
|
||||
debug('Cannot postMessage, no parent window.', type, data);
|
||||
}
|
||||
}
|
||||
|
||||
, createIframe: function(iframeUrl, errorCallback) {
|
||||
var iframe = global.document.createElement('iframe');
|
||||
var tref, unloadRef;
|
||||
var unattach = function() {
|
||||
debug('unattach');
|
||||
clearTimeout(tref);
|
||||
// Explorer had problems with that.
|
||||
try {
|
||||
iframe.onload = null;
|
||||
} catch (x) {
|
||||
// intentionally empty
|
||||
}
|
||||
iframe.onerror = null;
|
||||
};
|
||||
var cleanup = function() {
|
||||
debug('cleanup');
|
||||
if (iframe) {
|
||||
unattach();
|
||||
// This timeout makes chrome fire onbeforeunload event
|
||||
// within iframe. Without the timeout it goes straight to
|
||||
// onunload.
|
||||
setTimeout(function() {
|
||||
if (iframe) {
|
||||
iframe.parentNode.removeChild(iframe);
|
||||
}
|
||||
iframe = null;
|
||||
}, 0);
|
||||
eventUtils.unloadDel(unloadRef);
|
||||
}
|
||||
};
|
||||
var onerror = function(err) {
|
||||
debug('onerror', err);
|
||||
if (iframe) {
|
||||
cleanup();
|
||||
errorCallback(err);
|
||||
}
|
||||
};
|
||||
var post = function(msg, origin) {
|
||||
debug('post', msg, origin);
|
||||
setTimeout(function() {
|
||||
try {
|
||||
// When the iframe is not loaded, IE raises an exception
|
||||
// on 'contentWindow'.
|
||||
if (iframe && iframe.contentWindow) {
|
||||
iframe.contentWindow.postMessage(msg, origin);
|
||||
}
|
||||
} catch (x) {
|
||||
// intentionally empty
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
iframe.src = iframeUrl;
|
||||
iframe.style.display = 'none';
|
||||
iframe.style.position = 'absolute';
|
||||
iframe.onerror = function() {
|
||||
onerror('onerror');
|
||||
};
|
||||
iframe.onload = function() {
|
||||
debug('onload');
|
||||
// `onload` is triggered before scripts on the iframe are
|
||||
// executed. Give it few seconds to actually load stuff.
|
||||
clearTimeout(tref);
|
||||
tref = setTimeout(function() {
|
||||
onerror('onload timeout');
|
||||
}, 2000);
|
||||
};
|
||||
global.document.body.appendChild(iframe);
|
||||
tref = setTimeout(function() {
|
||||
onerror('timeout');
|
||||
}, 15000);
|
||||
unloadRef = eventUtils.unloadAdd(cleanup);
|
||||
return {
|
||||
post: post
|
||||
, cleanup: cleanup
|
||||
, loaded: unattach
|
||||
};
|
||||
}
|
||||
|
||||
/* eslint no-undef: "off", new-cap: "off" */
|
||||
, createHtmlfile: function(iframeUrl, errorCallback) {
|
||||
var axo = ['Active'].concat('Object').join('X');
|
||||
var doc = new global[axo]('htmlfile');
|
||||
var tref, unloadRef;
|
||||
var iframe;
|
||||
var unattach = function() {
|
||||
clearTimeout(tref);
|
||||
iframe.onerror = null;
|
||||
};
|
||||
var cleanup = function() {
|
||||
if (doc) {
|
||||
unattach();
|
||||
eventUtils.unloadDel(unloadRef);
|
||||
iframe.parentNode.removeChild(iframe);
|
||||
iframe = doc = null;
|
||||
CollectGarbage();
|
||||
}
|
||||
};
|
||||
var onerror = function(r) {
|
||||
debug('onerror', r);
|
||||
if (doc) {
|
||||
cleanup();
|
||||
errorCallback(r);
|
||||
}
|
||||
};
|
||||
var post = function(msg, origin) {
|
||||
try {
|
||||
// When the iframe is not loaded, IE raises an exception
|
||||
// on 'contentWindow'.
|
||||
setTimeout(function() {
|
||||
if (iframe && iframe.contentWindow) {
|
||||
iframe.contentWindow.postMessage(msg, origin);
|
||||
}
|
||||
}, 0);
|
||||
} catch (x) {
|
||||
// intentionally empty
|
||||
}
|
||||
};
|
||||
|
||||
doc.open();
|
||||
doc.write('<html><s' + 'cript>' +
|
||||
'document.domain="' + global.document.domain + '";' +
|
||||
'</s' + 'cript></html>');
|
||||
doc.close();
|
||||
doc.parentWindow[module.exports.WPrefix] = global[module.exports.WPrefix];
|
||||
var c = doc.createElement('div');
|
||||
doc.body.appendChild(c);
|
||||
iframe = doc.createElement('iframe');
|
||||
c.appendChild(iframe);
|
||||
iframe.src = iframeUrl;
|
||||
iframe.onerror = function() {
|
||||
onerror('onerror');
|
||||
};
|
||||
tref = setTimeout(function() {
|
||||
onerror('timeout');
|
||||
}, 15000);
|
||||
unloadRef = eventUtils.unloadAdd(cleanup);
|
||||
return {
|
||||
post: post
|
||||
, cleanup: cleanup
|
||||
, loaded: unattach
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.iframeEnabled = false;
|
||||
if (global.document) {
|
||||
// postMessage misbehaves in konqueror 4.6.5 - the messages are delivered with
|
||||
// huge delay, or not at all.
|
||||
module.exports.iframeEnabled = (typeof global.postMessage === 'function' ||
|
||||
typeof global.postMessage === 'object') && (!browser.isKonqueror());
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
var logObject = {};
|
||||
['log', 'debug', 'warn'].forEach(function (level) {
|
||||
var levelExists;
|
||||
|
||||
try {
|
||||
levelExists = global.console && global.console[level] && global.console[level].apply;
|
||||
} catch(e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
logObject[level] = levelExists ? function () {
|
||||
return global.console[level].apply(global.console, arguments);
|
||||
} : (level === 'log' ? function () {} : logObject.log);
|
||||
});
|
||||
|
||||
module.exports = logObject;
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
isObject: function(obj) {
|
||||
var type = typeof obj;
|
||||
return type === 'function' || type === 'object' && !!obj;
|
||||
}
|
||||
|
||||
, extend: function(obj) {
|
||||
if (!this.isObject(obj)) {
|
||||
return obj;
|
||||
}
|
||||
var source, prop;
|
||||
for (var i = 1, length = arguments.length; i < length; i++) {
|
||||
source = arguments[i];
|
||||
for (prop in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, prop)) {
|
||||
obj[prop] = source[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
/* global crypto:true */
|
||||
var crypto = require('crypto');
|
||||
|
||||
// This string has length 32, a power of 2, so the modulus doesn't introduce a
|
||||
// bias.
|
||||
var _randomStringChars = 'abcdefghijklmnopqrstuvwxyz012345';
|
||||
module.exports = {
|
||||
string: function(length) {
|
||||
var max = _randomStringChars.length;
|
||||
var bytes = crypto.randomBytes(length);
|
||||
var ret = [];
|
||||
for (var i = 0; i < length; i++) {
|
||||
ret.push(_randomStringChars.substr(bytes[i] % max, 1));
|
||||
}
|
||||
return ret.join('');
|
||||
}
|
||||
|
||||
, number: function(max) {
|
||||
return Math.floor(Math.random() * max);
|
||||
}
|
||||
|
||||
, numberString: function(max) {
|
||||
var t = ('' + (max - 1)).length;
|
||||
var p = new Array(t + 1).join('0');
|
||||
return (p + this.number(max)).slice(-t);
|
||||
}
|
||||
};
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:utils:transport');
|
||||
}
|
||||
|
||||
module.exports = function(availableTransports) {
|
||||
return {
|
||||
filterToEnabled: function(transportsWhitelist, info) {
|
||||
var transports = {
|
||||
main: []
|
||||
, facade: []
|
||||
};
|
||||
if (!transportsWhitelist) {
|
||||
transportsWhitelist = [];
|
||||
} else if (typeof transportsWhitelist === 'string') {
|
||||
transportsWhitelist = [transportsWhitelist];
|
||||
}
|
||||
|
||||
availableTransports.forEach(function(trans) {
|
||||
if (!trans) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (trans.transportName === 'websocket' && info.websocket === false) {
|
||||
debug('disabled from server', 'websocket');
|
||||
return;
|
||||
}
|
||||
|
||||
if (transportsWhitelist.length &&
|
||||
transportsWhitelist.indexOf(trans.transportName) === -1) {
|
||||
debug('not in whitelist', trans.transportName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (trans.enabled(info)) {
|
||||
debug('enabled', trans.transportName);
|
||||
transports.main.push(trans);
|
||||
if (trans.facadeTransport) {
|
||||
transports.facade.push(trans.facadeTransport);
|
||||
}
|
||||
} else {
|
||||
debug('disabled', trans.transportName);
|
||||
}
|
||||
});
|
||||
return transports;
|
||||
}
|
||||
};
|
||||
};
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
'use strict';
|
||||
|
||||
var URL = require('url-parse');
|
||||
|
||||
var debug = function() {};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug = require('debug')('sockjs-client:utils:url');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getOrigin: function(url) {
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var p = new URL(url);
|
||||
if (p.protocol === 'file:') {
|
||||
return null;
|
||||
}
|
||||
|
||||
var port = p.port;
|
||||
if (!port) {
|
||||
port = (p.protocol === 'https:') ? '443' : '80';
|
||||
}
|
||||
|
||||
return p.protocol + '//' + p.hostname + ':' + port;
|
||||
}
|
||||
|
||||
, isOriginEqual: function(a, b) {
|
||||
var res = this.getOrigin(a) === this.getOrigin(b);
|
||||
debug('same', a, b, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
, isSchemeEqual: function(a, b) {
|
||||
return (a.split(':')[0] === b.split(':')[0]);
|
||||
}
|
||||
|
||||
, addPath: function (url, path) {
|
||||
var qs = url.split('?');
|
||||
return qs[0] + path + (qs[1] ? '?' + qs[1] : '');
|
||||
}
|
||||
|
||||
, addQuery: function (url, q) {
|
||||
return url + (url.indexOf('?') === -1 ? ('?' + q) : ('&' + q));
|
||||
}
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
module.exports = '1.4.0';
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
BrowserStackLocal v7.5
|
||||
|
||||
You can now access your local server(s) in our remote browser.
|
||||
|
||||
Press Ctrl-C to exit
|
||||
|
||||
|
||||
*** WARNING: Your testing experience with webserver at localhost:9876 may be suboptimal, as the server is sending an HTTP response header containing: "Connection: close".
|
||||
To improve the experience, please change it to "Connection: keep-alive"
|
||||
*** WARNING: Your testing experience with webserver at 0.0.0.0:9876 may be suboptimal, as the server is sending an HTTP response header containing: "Connection: close".
|
||||
To improve the experience, please change it to "Connection: keep-alive"
|
||||
+395
@@ -0,0 +1,395 @@
|
||||
|
||||
3.1.0 / 2017-09-26
|
||||
==================
|
||||
|
||||
* Add `DEBUG_HIDE_DATE` env var (#486)
|
||||
* Remove ReDoS regexp in %o formatter (#504)
|
||||
* Remove "component" from package.json
|
||||
* Remove `component.json`
|
||||
* Ignore package-lock.json
|
||||
* Examples: fix colors printout
|
||||
* Fix: browser detection
|
||||
* Fix: spelling mistake (#496, @EdwardBetts)
|
||||
|
||||
3.0.1 / 2017-08-24
|
||||
==================
|
||||
|
||||
* Fix: Disable colors in Edge and Internet Explorer (#489)
|
||||
|
||||
3.0.0 / 2017-08-08
|
||||
==================
|
||||
|
||||
* Breaking: Remove DEBUG_FD (#406)
|
||||
* Breaking: Use `Date#toISOString()` instead to `Date#toUTCString()` when output is not a TTY (#418)
|
||||
* Breaking: Make millisecond timer namespace specific and allow 'always enabled' output (#408)
|
||||
* Addition: document `enabled` flag (#465)
|
||||
* Addition: add 256 colors mode (#481)
|
||||
* Addition: `enabled()` updates existing debug instances, add `destroy()` function (#440)
|
||||
* Update: component: update "ms" to v2.0.0
|
||||
* Update: separate the Node and Browser tests in Travis-CI
|
||||
* Update: refactor Readme, fixed documentation, added "Namespace Colors" section, redid screenshots
|
||||
* Update: separate Node.js and web browser examples for organization
|
||||
* Update: update "browserify" to v14.4.0
|
||||
* Fix: fix Readme typo (#473)
|
||||
|
||||
2.6.9 / 2017-09-22
|
||||
==================
|
||||
|
||||
* remove ReDoS regexp in %o formatter (#504)
|
||||
|
||||
2.6.8 / 2017-05-18
|
||||
==================
|
||||
|
||||
* Fix: Check for undefined on browser globals (#462, @marbemac)
|
||||
|
||||
2.6.7 / 2017-05-16
|
||||
==================
|
||||
|
||||
* Fix: Update ms to 2.0.0 to fix regular expression denial of service vulnerability (#458, @hubdotcom)
|
||||
* Fix: Inline extend function in node implementation (#452, @dougwilson)
|
||||
* Docs: Fix typo (#455, @msasad)
|
||||
|
||||
2.6.5 / 2017-04-27
|
||||
==================
|
||||
|
||||
* Fix: null reference check on window.documentElement.style.WebkitAppearance (#447, @thebigredgeek)
|
||||
* Misc: clean up browser reference checks (#447, @thebigredgeek)
|
||||
* Misc: add npm-debug.log to .gitignore (@thebigredgeek)
|
||||
|
||||
|
||||
2.6.4 / 2017-04-20
|
||||
==================
|
||||
|
||||
* Fix: bug that would occur if process.env.DEBUG is a non-string value. (#444, @LucianBuzzo)
|
||||
* Chore: ignore bower.json in npm installations. (#437, @joaovieira)
|
||||
* Misc: update "ms" to v0.7.3 (@tootallnate)
|
||||
|
||||
2.6.3 / 2017-03-13
|
||||
==================
|
||||
|
||||
* Fix: Electron reference to `process.env.DEBUG` (#431, @paulcbetts)
|
||||
* Docs: Changelog fix (@thebigredgeek)
|
||||
|
||||
2.6.2 / 2017-03-10
|
||||
==================
|
||||
|
||||
* Fix: DEBUG_MAX_ARRAY_LENGTH (#420, @slavaGanzin)
|
||||
* Docs: Add backers and sponsors from Open Collective (#422, @piamancini)
|
||||
* Docs: Add Slackin invite badge (@tootallnate)
|
||||
|
||||
2.6.1 / 2017-02-10
|
||||
==================
|
||||
|
||||
* Fix: Module's `export default` syntax fix for IE8 `Expected identifier` error
|
||||
* Fix: Whitelist DEBUG_FD for values 1 and 2 only (#415, @pi0)
|
||||
* Fix: IE8 "Expected identifier" error (#414, @vgoma)
|
||||
* Fix: Namespaces would not disable once enabled (#409, @musikov)
|
||||
|
||||
2.6.0 / 2016-12-28
|
||||
==================
|
||||
|
||||
* Fix: added better null pointer checks for browser useColors (@thebigredgeek)
|
||||
* Improvement: removed explicit `window.debug` export (#404, @tootallnate)
|
||||
* Improvement: deprecated `DEBUG_FD` environment variable (#405, @tootallnate)
|
||||
|
||||
2.5.2 / 2016-12-25
|
||||
==================
|
||||
|
||||
* Fix: reference error on window within webworkers (#393, @KlausTrainer)
|
||||
* Docs: fixed README typo (#391, @lurch)
|
||||
* Docs: added notice about v3 api discussion (@thebigredgeek)
|
||||
|
||||
2.5.1 / 2016-12-20
|
||||
==================
|
||||
|
||||
* Fix: babel-core compatibility
|
||||
|
||||
2.5.0 / 2016-12-20
|
||||
==================
|
||||
|
||||
* Fix: wrong reference in bower file (@thebigredgeek)
|
||||
* Fix: webworker compatibility (@thebigredgeek)
|
||||
* Fix: output formatting issue (#388, @kribblo)
|
||||
* Fix: babel-loader compatibility (#383, @escwald)
|
||||
* Misc: removed built asset from repo and publications (@thebigredgeek)
|
||||
* Misc: moved source files to /src (#378, @yamikuronue)
|
||||
* Test: added karma integration and replaced babel with browserify for browser tests (#378, @yamikuronue)
|
||||
* Test: coveralls integration (#378, @yamikuronue)
|
||||
* Docs: simplified language in the opening paragraph (#373, @yamikuronue)
|
||||
|
||||
2.4.5 / 2016-12-17
|
||||
==================
|
||||
|
||||
* Fix: `navigator` undefined in Rhino (#376, @jochenberger)
|
||||
* Fix: custom log function (#379, @hsiliev)
|
||||
* Improvement: bit of cleanup + linting fixes (@thebigredgeek)
|
||||
* Improvement: rm non-maintainted `dist/` dir (#375, @freewil)
|
||||
* Docs: simplified language in the opening paragraph. (#373, @yamikuronue)
|
||||
|
||||
2.4.4 / 2016-12-14
|
||||
==================
|
||||
|
||||
* Fix: work around debug being loaded in preload scripts for electron (#368, @paulcbetts)
|
||||
|
||||
2.4.3 / 2016-12-14
|
||||
==================
|
||||
|
||||
* Fix: navigation.userAgent error for react native (#364, @escwald)
|
||||
|
||||
2.4.2 / 2016-12-14
|
||||
==================
|
||||
|
||||
* Fix: browser colors (#367, @tootallnate)
|
||||
* Misc: travis ci integration (@thebigredgeek)
|
||||
* Misc: added linting and testing boilerplate with sanity check (@thebigredgeek)
|
||||
|
||||
2.4.1 / 2016-12-13
|
||||
==================
|
||||
|
||||
* Fix: typo that broke the package (#356)
|
||||
|
||||
2.4.0 / 2016-12-13
|
||||
==================
|
||||
|
||||
* Fix: bower.json references unbuilt src entry point (#342, @justmatt)
|
||||
* Fix: revert "handle regex special characters" (@tootallnate)
|
||||
* Feature: configurable util.inspect()`options for NodeJS (#327, @tootallnate)
|
||||
* Feature: %O`(big O) pretty-prints objects (#322, @tootallnate)
|
||||
* Improvement: allow colors in workers (#335, @botverse)
|
||||
* Improvement: use same color for same namespace. (#338, @lchenay)
|
||||
|
||||
2.3.3 / 2016-11-09
|
||||
==================
|
||||
|
||||
* Fix: Catch `JSON.stringify()` errors (#195, Jovan Alleyne)
|
||||
* Fix: Returning `localStorage` saved values (#331, Levi Thomason)
|
||||
* Improvement: Don't create an empty object when no `process` (Nathan Rajlich)
|
||||
|
||||
2.3.2 / 2016-11-09
|
||||
==================
|
||||
|
||||
* Fix: be super-safe in index.js as well (@TooTallNate)
|
||||
* Fix: should check whether process exists (Tom Newby)
|
||||
|
||||
2.3.1 / 2016-11-09
|
||||
==================
|
||||
|
||||
* Fix: Added electron compatibility (#324, @paulcbetts)
|
||||
* Improvement: Added performance optimizations (@tootallnate)
|
||||
* Readme: Corrected PowerShell environment variable example (#252, @gimre)
|
||||
* Misc: Removed yarn lock file from source control (#321, @fengmk2)
|
||||
|
||||
2.3.0 / 2016-11-07
|
||||
==================
|
||||
|
||||
* Fix: Consistent placement of ms diff at end of output (#215, @gorangajic)
|
||||
* Fix: Escaping of regex special characters in namespace strings (#250, @zacronos)
|
||||
* Fix: Fixed bug causing crash on react-native (#282, @vkarpov15)
|
||||
* Feature: Enabled ES6+ compatible import via default export (#212 @bucaran)
|
||||
* Feature: Added %O formatter to reflect Chrome's console.log capability (#279, @oncletom)
|
||||
* Package: Update "ms" to 0.7.2 (#315, @DevSide)
|
||||
* Package: removed superfluous version property from bower.json (#207 @kkirsche)
|
||||
* Readme: fix USE_COLORS to DEBUG_COLORS
|
||||
* Readme: Doc fixes for format string sugar (#269, @mlucool)
|
||||
* Readme: Updated docs for DEBUG_FD and DEBUG_COLORS environment variables (#232, @mattlyons0)
|
||||
* Readme: doc fixes for PowerShell (#271 #243, @exoticknight @unreadable)
|
||||
* Readme: better docs for browser support (#224, @matthewmueller)
|
||||
* Tooling: Added yarn integration for development (#317, @thebigredgeek)
|
||||
* Misc: Renamed History.md to CHANGELOG.md (@thebigredgeek)
|
||||
* Misc: Added license file (#226 #274, @CantemoInternal @sdaitzman)
|
||||
* Misc: Updated contributors (@thebigredgeek)
|
||||
|
||||
2.2.0 / 2015-05-09
|
||||
==================
|
||||
|
||||
* package: update "ms" to v0.7.1 (#202, @dougwilson)
|
||||
* README: add logging to file example (#193, @DanielOchoa)
|
||||
* README: fixed a typo (#191, @amir-s)
|
||||
* browser: expose `storage` (#190, @stephenmathieson)
|
||||
* Makefile: add a `distclean` target (#189, @stephenmathieson)
|
||||
|
||||
2.1.3 / 2015-03-13
|
||||
==================
|
||||
|
||||
* Updated stdout/stderr example (#186)
|
||||
* Updated example/stdout.js to match debug current behaviour
|
||||
* Renamed example/stderr.js to stdout.js
|
||||
* Update Readme.md (#184)
|
||||
* replace high intensity foreground color for bold (#182, #183)
|
||||
|
||||
2.1.2 / 2015-03-01
|
||||
==================
|
||||
|
||||
* dist: recompile
|
||||
* update "ms" to v0.7.0
|
||||
* package: update "browserify" to v9.0.3
|
||||
* component: fix "ms.js" repo location
|
||||
* changed bower package name
|
||||
* updated documentation about using debug in a browser
|
||||
* fix: security error on safari (#167, #168, @yields)
|
||||
|
||||
2.1.1 / 2014-12-29
|
||||
==================
|
||||
|
||||
* browser: use `typeof` to check for `console` existence
|
||||
* browser: check for `console.log` truthiness (fix IE 8/9)
|
||||
* browser: add support for Chrome apps
|
||||
* Readme: added Windows usage remarks
|
||||
* Add `bower.json` to properly support bower install
|
||||
|
||||
2.1.0 / 2014-10-15
|
||||
==================
|
||||
|
||||
* node: implement `DEBUG_FD` env variable support
|
||||
* package: update "browserify" to v6.1.0
|
||||
* package: add "license" field to package.json (#135, @panuhorsmalahti)
|
||||
|
||||
2.0.0 / 2014-09-01
|
||||
==================
|
||||
|
||||
* package: update "browserify" to v5.11.0
|
||||
* node: use stderr rather than stdout for logging (#29, @stephenmathieson)
|
||||
|
||||
1.0.4 / 2014-07-15
|
||||
==================
|
||||
|
||||
* dist: recompile
|
||||
* example: remove `console.info()` log usage
|
||||
* example: add "Content-Type" UTF-8 header to browser example
|
||||
* browser: place %c marker after the space character
|
||||
* browser: reset the "content" color via `color: inherit`
|
||||
* browser: add colors support for Firefox >= v31
|
||||
* debug: prefer an instance `log()` function over the global one (#119)
|
||||
* Readme: update documentation about styled console logs for FF v31 (#116, @wryk)
|
||||
|
||||
1.0.3 / 2014-07-09
|
||||
==================
|
||||
|
||||
* Add support for multiple wildcards in namespaces (#122, @seegno)
|
||||
* browser: fix lint
|
||||
|
||||
1.0.2 / 2014-06-10
|
||||
==================
|
||||
|
||||
* browser: update color palette (#113, @gscottolson)
|
||||
* common: make console logging function configurable (#108, @timoxley)
|
||||
* node: fix %o colors on old node <= 0.8.x
|
||||
* Makefile: find node path using shell/which (#109, @timoxley)
|
||||
|
||||
1.0.1 / 2014-06-06
|
||||
==================
|
||||
|
||||
* browser: use `removeItem()` to clear localStorage
|
||||
* browser, node: don't set DEBUG if namespaces is undefined (#107, @leedm777)
|
||||
* package: add "contributors" section
|
||||
* node: fix comment typo
|
||||
* README: list authors
|
||||
|
||||
1.0.0 / 2014-06-04
|
||||
==================
|
||||
|
||||
* make ms diff be global, not be scope
|
||||
* debug: ignore empty strings in enable()
|
||||
* node: make DEBUG_COLORS able to disable coloring
|
||||
* *: export the `colors` array
|
||||
* npmignore: don't publish the `dist` dir
|
||||
* Makefile: refactor to use browserify
|
||||
* package: add "browserify" as a dev dependency
|
||||
* Readme: add Web Inspector Colors section
|
||||
* node: reset terminal color for the debug content
|
||||
* node: map "%o" to `util.inspect()`
|
||||
* browser: map "%j" to `JSON.stringify()`
|
||||
* debug: add custom "formatters"
|
||||
* debug: use "ms" module for humanizing the diff
|
||||
* Readme: add "bash" syntax highlighting
|
||||
* browser: add Firebug color support
|
||||
* browser: add colors for WebKit browsers
|
||||
* node: apply log to `console`
|
||||
* rewrite: abstract common logic for Node & browsers
|
||||
* add .jshintrc file
|
||||
|
||||
0.8.1 / 2014-04-14
|
||||
==================
|
||||
|
||||
* package: re-add the "component" section
|
||||
|
||||
0.8.0 / 2014-03-30
|
||||
==================
|
||||
|
||||
* add `enable()` method for nodejs. Closes #27
|
||||
* change from stderr to stdout
|
||||
* remove unnecessary index.js file
|
||||
|
||||
0.7.4 / 2013-11-13
|
||||
==================
|
||||
|
||||
* remove "browserify" key from package.json (fixes something in browserify)
|
||||
|
||||
0.7.3 / 2013-10-30
|
||||
==================
|
||||
|
||||
* fix: catch localStorage security error when cookies are blocked (Chrome)
|
||||
* add debug(err) support. Closes #46
|
||||
* add .browser prop to package.json. Closes #42
|
||||
|
||||
0.7.2 / 2013-02-06
|
||||
==================
|
||||
|
||||
* fix package.json
|
||||
* fix: Mobile Safari (private mode) is broken with debug
|
||||
* fix: Use unicode to send escape character to shell instead of octal to work with strict mode javascript
|
||||
|
||||
0.7.1 / 2013-02-05
|
||||
==================
|
||||
|
||||
* add repository URL to package.json
|
||||
* add DEBUG_COLORED to force colored output
|
||||
* add browserify support
|
||||
* fix component. Closes #24
|
||||
|
||||
0.7.0 / 2012-05-04
|
||||
==================
|
||||
|
||||
* Added .component to package.json
|
||||
* Added debug.component.js build
|
||||
|
||||
0.6.0 / 2012-03-16
|
||||
==================
|
||||
|
||||
* Added support for "-" prefix in DEBUG [Vinay Pulim]
|
||||
* Added `.enabled` flag to the node version [TooTallNate]
|
||||
|
||||
0.5.0 / 2012-02-02
|
||||
==================
|
||||
|
||||
* Added: humanize diffs. Closes #8
|
||||
* Added `debug.disable()` to the CS variant
|
||||
* Removed padding. Closes #10
|
||||
* Fixed: persist client-side variant again. Closes #9
|
||||
|
||||
0.4.0 / 2012-02-01
|
||||
==================
|
||||
|
||||
* Added browser variant support for older browsers [TooTallNate]
|
||||
* Added `debug.enable('project:*')` to browser variant [TooTallNate]
|
||||
* Added padding to diff (moved it to the right)
|
||||
|
||||
0.3.0 / 2012-01-26
|
||||
==================
|
||||
|
||||
* Added millisecond diff when isatty, otherwise UTC string
|
||||
|
||||
0.2.0 / 2012-01-22
|
||||
==================
|
||||
|
||||
* Added wildcard support
|
||||
|
||||
0.1.0 / 2011-12-02
|
||||
==================
|
||||
|
||||
* Added: remove colors unless stderr isatty [TooTallNate]
|
||||
|
||||
0.0.1 / 2010-01-03
|
||||
==================
|
||||
|
||||
* Initial release
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014 TJ Holowaychuk <tj@vision-media.ca>
|
||||
|
||||
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.
|
||||
|
||||
+437
@@ -0,0 +1,437 @@
|
||||
# debug
|
||||
[](https://travis-ci.org/visionmedia/debug) [](https://coveralls.io/github/visionmedia/debug?branch=master) [](https://visionmedia-community-slackin.now.sh/) [](#backers)
|
||||
[](#sponsors)
|
||||
|
||||
<img width="647" src="https://user-images.githubusercontent.com/71256/29091486-fa38524c-7c37-11e7-895f-e7ec8e1039b6.png">
|
||||
|
||||
A tiny JavaScript debugging utility modelled after Node.js core's debugging
|
||||
technique. Works in Node.js and web browsers.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ npm install debug
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
`debug` exposes a function; simply pass this function the name of your module, and it will return a decorated version of `console.error` for you to pass debug statements to. This will allow you to toggle the debug output for different parts of your module as well as the module as a whole.
|
||||
|
||||
Example [_app.js_](./examples/node/app.js):
|
||||
|
||||
```js
|
||||
var debug = require('debug')('http')
|
||||
, http = require('http')
|
||||
, name = 'My App';
|
||||
|
||||
// fake app
|
||||
|
||||
debug('booting %o', name);
|
||||
|
||||
http.createServer(function(req, res){
|
||||
debug(req.method + ' ' + req.url);
|
||||
res.end('hello\n');
|
||||
}).listen(3000, function(){
|
||||
debug('listening');
|
||||
});
|
||||
|
||||
// fake worker of some kind
|
||||
|
||||
require('./worker');
|
||||
```
|
||||
|
||||
Example [_worker.js_](./examples/node/worker.js):
|
||||
|
||||
```js
|
||||
var a = require('debug')('worker:a')
|
||||
, b = require('debug')('worker:b');
|
||||
|
||||
function work() {
|
||||
a('doing lots of uninteresting work');
|
||||
setTimeout(work, Math.random() * 1000);
|
||||
}
|
||||
|
||||
work();
|
||||
|
||||
function workb() {
|
||||
b('doing some work');
|
||||
setTimeout(workb, Math.random() * 2000);
|
||||
}
|
||||
|
||||
workb();
|
||||
```
|
||||
|
||||
The `DEBUG` environment variable is then used to enable these based on space or
|
||||
comma-delimited names.
|
||||
|
||||
Here are some examples:
|
||||
|
||||
<img width="647" alt="screen shot 2017-08-08 at 12 53 04 pm" src="https://user-images.githubusercontent.com/71256/29091703-a6302cdc-7c38-11e7-8304-7c0b3bc600cd.png">
|
||||
<img width="647" alt="screen shot 2017-08-08 at 12 53 38 pm" src="https://user-images.githubusercontent.com/71256/29091700-a62a6888-7c38-11e7-800b-db911291ca2b.png">
|
||||
<img width="647" alt="screen shot 2017-08-08 at 12 53 25 pm" src="https://user-images.githubusercontent.com/71256/29091701-a62ea114-7c38-11e7-826a-2692bedca740.png">
|
||||
|
||||
#### Windows command prompt notes
|
||||
|
||||
##### CMD
|
||||
|
||||
On Windows the environment variable is set using the `set` command.
|
||||
|
||||
```cmd
|
||||
set DEBUG=*,-not_this
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```cmd
|
||||
set DEBUG=* & node app.js
|
||||
```
|
||||
|
||||
##### PowerShell (VS Code default)
|
||||
|
||||
PowerShell uses different syntax to set environment variables.
|
||||
|
||||
```cmd
|
||||
$env:DEBUG = "*,-not_this"
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```cmd
|
||||
$env:DEBUG='app';node app.js
|
||||
```
|
||||
|
||||
Then, run the program to be debugged as usual.
|
||||
|
||||
npm script example:
|
||||
```js
|
||||
"windowsDebug": "@powershell -Command $env:DEBUG='*';node app.js",
|
||||
```
|
||||
|
||||
## Namespace Colors
|
||||
|
||||
Every debug instance has a color generated for it based on its namespace name.
|
||||
This helps when visually parsing the debug output to identify which debug instance
|
||||
a debug line belongs to.
|
||||
|
||||
#### Node.js
|
||||
|
||||
In Node.js, colors are enabled when stderr is a TTY. You also _should_ install
|
||||
the [`supports-color`](https://npmjs.org/supports-color) module alongside debug,
|
||||
otherwise debug will only use a small handful of basic colors.
|
||||
|
||||
<img width="521" src="https://user-images.githubusercontent.com/71256/29092181-47f6a9e6-7c3a-11e7-9a14-1928d8a711cd.png">
|
||||
|
||||
#### Web Browser
|
||||
|
||||
Colors are also enabled on "Web Inspectors" that understand the `%c` formatting
|
||||
option. These are WebKit web inspectors, Firefox ([since version
|
||||
31](https://hacks.mozilla.org/2014/05/editable-box-model-multiple-selection-sublime-text-keys-much-more-firefox-developer-tools-episode-31/))
|
||||
and the Firebug plugin for Firefox (any version).
|
||||
|
||||
<img width="524" src="https://user-images.githubusercontent.com/71256/29092033-b65f9f2e-7c39-11e7-8e32-f6f0d8e865c1.png">
|
||||
|
||||
|
||||
## Millisecond diff
|
||||
|
||||
When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls.
|
||||
|
||||
<img width="647" src="https://user-images.githubusercontent.com/71256/29091486-fa38524c-7c37-11e7-895f-e7ec8e1039b6.png">
|
||||
|
||||
When stdout is not a TTY, `Date#toISOString()` is used, making it more useful for logging the debug information as shown below:
|
||||
|
||||
<img width="647" src="https://user-images.githubusercontent.com/71256/29091956-6bd78372-7c39-11e7-8c55-c948396d6edd.png">
|
||||
|
||||
|
||||
## Conventions
|
||||
|
||||
If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". If you append a "*" to the end of your name, it will always be enabled regardless of the setting of the DEBUG environment variable. You can then use it for normal output as well as debug output.
|
||||
|
||||
## Wildcards
|
||||
|
||||
The `*` character may be used as a wildcard. Suppose for example your library has
|
||||
debuggers named "connect:bodyParser", "connect:compress", "connect:session",
|
||||
instead of listing all three with
|
||||
`DEBUG=connect:bodyParser,connect:compress,connect:session`, you may simply do
|
||||
`DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.
|
||||
|
||||
You can also exclude specific debuggers by prefixing them with a "-" character.
|
||||
For example, `DEBUG=*,-connect:*` would include all debuggers except those
|
||||
starting with "connect:".
|
||||
|
||||
## Environment Variables
|
||||
|
||||
When running through Node.js, you can set a few environment variables that will
|
||||
change the behavior of the debug logging:
|
||||
|
||||
| Name | Purpose |
|
||||
|-----------|-------------------------------------------------|
|
||||
| `DEBUG` | Enables/disables specific debugging namespaces. |
|
||||
| `DEBUG_HIDE_DATE` | Hide date from debug output (non-TTY). |
|
||||
| `DEBUG_COLORS`| Whether or not to use colors in the debug output. |
|
||||
| `DEBUG_DEPTH` | Object inspection depth. |
|
||||
| `DEBUG_SHOW_HIDDEN` | Shows hidden properties on inspected objects. |
|
||||
|
||||
|
||||
__Note:__ The environment variables beginning with `DEBUG_` end up being
|
||||
converted into an Options object that gets used with `%o`/`%O` formatters.
|
||||
See the Node.js documentation for
|
||||
[`util.inspect()`](https://nodejs.org/api/util.html#util_util_inspect_object_options)
|
||||
for the complete list.
|
||||
|
||||
## Formatters
|
||||
|
||||
Debug uses [printf-style](https://wikipedia.org/wiki/Printf_format_string) formatting.
|
||||
Below are the officially supported formatters:
|
||||
|
||||
| Formatter | Representation |
|
||||
|-----------|----------------|
|
||||
| `%O` | Pretty-print an Object on multiple lines. |
|
||||
| `%o` | Pretty-print an Object all on a single line. |
|
||||
| `%s` | String. |
|
||||
| `%d` | Number (both integer and float). |
|
||||
| `%j` | JSON. Replaced with the string '[Circular]' if the argument contains circular references. |
|
||||
| `%%` | Single percent sign ('%'). This does not consume an argument. |
|
||||
|
||||
|
||||
### Custom formatters
|
||||
|
||||
You can add custom formatters by extending the `debug.formatters` object.
|
||||
For example, if you wanted to add support for rendering a Buffer as hex with
|
||||
`%h`, you could do something like:
|
||||
|
||||
```js
|
||||
const createDebug = require('debug')
|
||||
createDebug.formatters.h = (v) => {
|
||||
return v.toString('hex')
|
||||
}
|
||||
|
||||
// …elsewhere
|
||||
const debug = createDebug('foo')
|
||||
debug('this is hex: %h', new Buffer('hello world'))
|
||||
// foo this is hex: 68656c6c6f20776f726c6421 +0ms
|
||||
```
|
||||
|
||||
|
||||
## Browser Support
|
||||
|
||||
You can build a browser-ready script using [browserify](https://github.com/substack/node-browserify),
|
||||
or just use the [browserify-as-a-service](https://wzrd.in/) [build](https://wzrd.in/standalone/debug@latest),
|
||||
if you don't want to build it yourself.
|
||||
|
||||
Debug's enable state is currently persisted by `localStorage`.
|
||||
Consider the situation shown below where you have `worker:a` and `worker:b`,
|
||||
and wish to debug both. You can enable this using `localStorage.debug`:
|
||||
|
||||
```js
|
||||
localStorage.debug = 'worker:*'
|
||||
```
|
||||
|
||||
And then refresh the page.
|
||||
|
||||
```js
|
||||
a = debug('worker:a');
|
||||
b = debug('worker:b');
|
||||
|
||||
setInterval(function(){
|
||||
a('doing some work');
|
||||
}, 1000);
|
||||
|
||||
setInterval(function(){
|
||||
b('doing some work');
|
||||
}, 1200);
|
||||
```
|
||||
|
||||
|
||||
## Output streams
|
||||
|
||||
By default `debug` will log to stderr, however this can be configured per-namespace by overriding the `log` method:
|
||||
|
||||
Example [_stdout.js_](./examples/node/stdout.js):
|
||||
|
||||
```js
|
||||
var debug = require('debug');
|
||||
var error = debug('app:error');
|
||||
|
||||
// by default stderr is used
|
||||
error('goes to stderr!');
|
||||
|
||||
var log = debug('app:log');
|
||||
// set this namespace to log via console.log
|
||||
log.log = console.log.bind(console); // don't forget to bind to console!
|
||||
log('goes to stdout');
|
||||
error('still goes to stderr!');
|
||||
|
||||
// set all output to go via console.info
|
||||
// overrides all per-namespace log settings
|
||||
debug.log = console.info.bind(console);
|
||||
error('now goes to stdout via console.info');
|
||||
log('still goes to stdout, but via console.info now');
|
||||
```
|
||||
|
||||
## Extend
|
||||
You can simply extend debugger
|
||||
```js
|
||||
const log = require('debug')('auth');
|
||||
|
||||
//creates new debug instance with extended namespace
|
||||
const logSign = log.extend('sign');
|
||||
const logLogin = log.extend('login');
|
||||
|
||||
log('hello'); // auth hello
|
||||
logSign('hello'); //auth:sign hello
|
||||
logLogin('hello'); //auth:login hello
|
||||
```
|
||||
|
||||
## Set dynamically
|
||||
|
||||
You can also enable debug dynamically by calling the `enable()` method :
|
||||
|
||||
```js
|
||||
let debug = require('debug');
|
||||
|
||||
console.log(1, debug.enabled('test'));
|
||||
|
||||
debug.enable('test');
|
||||
console.log(2, debug.enabled('test'));
|
||||
|
||||
debug.disable();
|
||||
console.log(3, debug.enabled('test'));
|
||||
|
||||
```
|
||||
|
||||
print :
|
||||
```
|
||||
1 false
|
||||
2 true
|
||||
3 false
|
||||
```
|
||||
|
||||
Usage :
|
||||
`enable(namespaces)`
|
||||
`namespaces` can include modes separated by a colon and wildcards.
|
||||
|
||||
Note that calling `enable()` completely overrides previously set DEBUG variable :
|
||||
|
||||
```
|
||||
$ DEBUG=foo node -e 'var dbg = require("debug"); dbg.enable("bar"); console.log(dbg.enabled("foo"))'
|
||||
=> false
|
||||
```
|
||||
|
||||
## Checking whether a debug target is enabled
|
||||
|
||||
After you've created a debug instance, you can determine whether or not it is
|
||||
enabled by checking the `enabled` property:
|
||||
|
||||
```javascript
|
||||
const debug = require('debug')('http');
|
||||
|
||||
if (debug.enabled) {
|
||||
// do stuff...
|
||||
}
|
||||
```
|
||||
|
||||
You can also manually toggle this property to force the debug instance to be
|
||||
enabled or disabled.
|
||||
|
||||
|
||||
## Authors
|
||||
|
||||
- TJ Holowaychuk
|
||||
- Nathan Rajlich
|
||||
- Andrew Rhyne
|
||||
|
||||
## Backers
|
||||
|
||||
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/debug#backer)]
|
||||
|
||||
<a href="https://opencollective.com/debug/backer/0/website" target="_blank"><img src="https://opencollective.com/debug/backer/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/1/website" target="_blank"><img src="https://opencollective.com/debug/backer/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/2/website" target="_blank"><img src="https://opencollective.com/debug/backer/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/3/website" target="_blank"><img src="https://opencollective.com/debug/backer/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/4/website" target="_blank"><img src="https://opencollective.com/debug/backer/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/5/website" target="_blank"><img src="https://opencollective.com/debug/backer/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/6/website" target="_blank"><img src="https://opencollective.com/debug/backer/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/7/website" target="_blank"><img src="https://opencollective.com/debug/backer/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/8/website" target="_blank"><img src="https://opencollective.com/debug/backer/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/9/website" target="_blank"><img src="https://opencollective.com/debug/backer/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/10/website" target="_blank"><img src="https://opencollective.com/debug/backer/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/11/website" target="_blank"><img src="https://opencollective.com/debug/backer/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/12/website" target="_blank"><img src="https://opencollective.com/debug/backer/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/13/website" target="_blank"><img src="https://opencollective.com/debug/backer/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/14/website" target="_blank"><img src="https://opencollective.com/debug/backer/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/15/website" target="_blank"><img src="https://opencollective.com/debug/backer/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/16/website" target="_blank"><img src="https://opencollective.com/debug/backer/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/17/website" target="_blank"><img src="https://opencollective.com/debug/backer/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/18/website" target="_blank"><img src="https://opencollective.com/debug/backer/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/19/website" target="_blank"><img src="https://opencollective.com/debug/backer/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/20/website" target="_blank"><img src="https://opencollective.com/debug/backer/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/21/website" target="_blank"><img src="https://opencollective.com/debug/backer/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/22/website" target="_blank"><img src="https://opencollective.com/debug/backer/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/23/website" target="_blank"><img src="https://opencollective.com/debug/backer/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/24/website" target="_blank"><img src="https://opencollective.com/debug/backer/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/25/website" target="_blank"><img src="https://opencollective.com/debug/backer/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/26/website" target="_blank"><img src="https://opencollective.com/debug/backer/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/27/website" target="_blank"><img src="https://opencollective.com/debug/backer/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/28/website" target="_blank"><img src="https://opencollective.com/debug/backer/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/backer/29/website" target="_blank"><img src="https://opencollective.com/debug/backer/29/avatar.svg"></a>
|
||||
|
||||
|
||||
## Sponsors
|
||||
|
||||
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/debug#sponsor)]
|
||||
|
||||
<a href="https://opencollective.com/debug/sponsor/0/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/1/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/2/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/3/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/4/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/5/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/6/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/7/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/8/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/9/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/10/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/11/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/12/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/13/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/14/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/15/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/16/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/17/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/18/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/19/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/20/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/21/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/22/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/23/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/24/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/25/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/26/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/27/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/28/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/debug/sponsor/29/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/29/avatar.svg"></a>
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014-2017 TJ Holowaychuk <tj@vision-media.ca>
|
||||
|
||||
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.
|
||||
+886
@@ -0,0 +1,886 @@
|
||||
"use strict";
|
||||
|
||||
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
|
||||
|
||||
(function (f) {
|
||||
if ((typeof exports === "undefined" ? "undefined" : _typeof(exports)) === "object" && typeof module !== "undefined") {
|
||||
module.exports = f();
|
||||
} else if (typeof define === "function" && define.amd) {
|
||||
define([], f);
|
||||
} else {
|
||||
var g;
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
g = window;
|
||||
} else if (typeof global !== "undefined") {
|
||||
g = global;
|
||||
} else if (typeof self !== "undefined") {
|
||||
g = self;
|
||||
} else {
|
||||
g = this;
|
||||
}
|
||||
|
||||
g.debug = f();
|
||||
}
|
||||
})(function () {
|
||||
var define, module, exports;
|
||||
return function () {
|
||||
function r(e, n, t) {
|
||||
function o(i, f) {
|
||||
if (!n[i]) {
|
||||
if (!e[i]) {
|
||||
var c = "function" == typeof require && require;
|
||||
if (!f && c) return c(i, !0);
|
||||
if (u) return u(i, !0);
|
||||
var a = new Error("Cannot find module '" + i + "'");
|
||||
throw a.code = "MODULE_NOT_FOUND", a;
|
||||
}
|
||||
|
||||
var p = n[i] = {
|
||||
exports: {}
|
||||
};
|
||||
e[i][0].call(p.exports, function (r) {
|
||||
var n = e[i][1][r];
|
||||
return o(n || r);
|
||||
}, p, p.exports, r, e, n, t);
|
||||
}
|
||||
|
||||
return n[i].exports;
|
||||
}
|
||||
|
||||
for (var u = "function" == typeof require && require, i = 0; i < t.length; i++) {
|
||||
o(t[i]);
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
return r;
|
||||
}()({
|
||||
1: [function (require, module, exports) {
|
||||
/**
|
||||
* Helpers.
|
||||
*/
|
||||
var s = 1000;
|
||||
var m = s * 60;
|
||||
var h = m * 60;
|
||||
var d = h * 24;
|
||||
var w = d * 7;
|
||||
var y = d * 365.25;
|
||||
/**
|
||||
* Parse or format the given `val`.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `long` verbose formatting [false]
|
||||
*
|
||||
* @param {String|Number} val
|
||||
* @param {Object} [options]
|
||||
* @throws {Error} throw an error if val is not a non-empty string or a number
|
||||
* @return {String|Number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
module.exports = function (val, options) {
|
||||
options = options || {};
|
||||
|
||||
var type = _typeof(val);
|
||||
|
||||
if (type === 'string' && val.length > 0) {
|
||||
return parse(val);
|
||||
} else if (type === 'number' && isNaN(val) === false) {
|
||||
return options.long ? fmtLong(val) : fmtShort(val);
|
||||
}
|
||||
|
||||
throw new Error('val is not a non-empty string or a valid number. val=' + JSON.stringify(val));
|
||||
};
|
||||
/**
|
||||
* Parse the given `str` and return milliseconds.
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {Number}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
|
||||
function parse(str) {
|
||||
str = String(str);
|
||||
|
||||
if (str.length > 100) {
|
||||
return;
|
||||
}
|
||||
|
||||
var match = /^((?:\d+)?\-?\d?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(str);
|
||||
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
|
||||
var n = parseFloat(match[1]);
|
||||
var type = (match[2] || 'ms').toLowerCase();
|
||||
|
||||
switch (type) {
|
||||
case 'years':
|
||||
case 'year':
|
||||
case 'yrs':
|
||||
case 'yr':
|
||||
case 'y':
|
||||
return n * y;
|
||||
|
||||
case 'weeks':
|
||||
case 'week':
|
||||
case 'w':
|
||||
return n * w;
|
||||
|
||||
case 'days':
|
||||
case 'day':
|
||||
case 'd':
|
||||
return n * d;
|
||||
|
||||
case 'hours':
|
||||
case 'hour':
|
||||
case 'hrs':
|
||||
case 'hr':
|
||||
case 'h':
|
||||
return n * h;
|
||||
|
||||
case 'minutes':
|
||||
case 'minute':
|
||||
case 'mins':
|
||||
case 'min':
|
||||
case 'm':
|
||||
return n * m;
|
||||
|
||||
case 'seconds':
|
||||
case 'second':
|
||||
case 'secs':
|
||||
case 'sec':
|
||||
case 's':
|
||||
return n * s;
|
||||
|
||||
case 'milliseconds':
|
||||
case 'millisecond':
|
||||
case 'msecs':
|
||||
case 'msec':
|
||||
case 'ms':
|
||||
return n;
|
||||
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Short format for `ms`.
|
||||
*
|
||||
* @param {Number} ms
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
|
||||
function fmtShort(ms) {
|
||||
var msAbs = Math.abs(ms);
|
||||
|
||||
if (msAbs >= d) {
|
||||
return Math.round(ms / d) + 'd';
|
||||
}
|
||||
|
||||
if (msAbs >= h) {
|
||||
return Math.round(ms / h) + 'h';
|
||||
}
|
||||
|
||||
if (msAbs >= m) {
|
||||
return Math.round(ms / m) + 'm';
|
||||
}
|
||||
|
||||
if (msAbs >= s) {
|
||||
return Math.round(ms / s) + 's';
|
||||
}
|
||||
|
||||
return ms + 'ms';
|
||||
}
|
||||
/**
|
||||
* Long format for `ms`.
|
||||
*
|
||||
* @param {Number} ms
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
|
||||
function fmtLong(ms) {
|
||||
var msAbs = Math.abs(ms);
|
||||
|
||||
if (msAbs >= d) {
|
||||
return plural(ms, msAbs, d, 'day');
|
||||
}
|
||||
|
||||
if (msAbs >= h) {
|
||||
return plural(ms, msAbs, h, 'hour');
|
||||
}
|
||||
|
||||
if (msAbs >= m) {
|
||||
return plural(ms, msAbs, m, 'minute');
|
||||
}
|
||||
|
||||
if (msAbs >= s) {
|
||||
return plural(ms, msAbs, s, 'second');
|
||||
}
|
||||
|
||||
return ms + ' ms';
|
||||
}
|
||||
/**
|
||||
* Pluralization helper.
|
||||
*/
|
||||
|
||||
|
||||
function plural(ms, msAbs, n, name) {
|
||||
var isPlural = msAbs >= n * 1.5;
|
||||
return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : '');
|
||||
}
|
||||
}, {}],
|
||||
2: [function (require, module, exports) {
|
||||
// shim for using process in browser
|
||||
var process = module.exports = {}; // cached from whatever global is present so that test runners that stub it
|
||||
// don't break things. But we need to wrap it in a try catch in case it is
|
||||
// wrapped in strict mode code which doesn't define any globals. It's inside a
|
||||
// function because try/catches deoptimize in certain engines.
|
||||
|
||||
var cachedSetTimeout;
|
||||
var cachedClearTimeout;
|
||||
|
||||
function defaultSetTimout() {
|
||||
throw new Error('setTimeout has not been defined');
|
||||
}
|
||||
|
||||
function defaultClearTimeout() {
|
||||
throw new Error('clearTimeout has not been defined');
|
||||
}
|
||||
|
||||
(function () {
|
||||
try {
|
||||
if (typeof setTimeout === 'function') {
|
||||
cachedSetTimeout = setTimeout;
|
||||
} else {
|
||||
cachedSetTimeout = defaultSetTimout;
|
||||
}
|
||||
} catch (e) {
|
||||
cachedSetTimeout = defaultSetTimout;
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof clearTimeout === 'function') {
|
||||
cachedClearTimeout = clearTimeout;
|
||||
} else {
|
||||
cachedClearTimeout = defaultClearTimeout;
|
||||
}
|
||||
} catch (e) {
|
||||
cachedClearTimeout = defaultClearTimeout;
|
||||
}
|
||||
})();
|
||||
|
||||
function runTimeout(fun) {
|
||||
if (cachedSetTimeout === setTimeout) {
|
||||
//normal enviroments in sane situations
|
||||
return setTimeout(fun, 0);
|
||||
} // if setTimeout wasn't available but was latter defined
|
||||
|
||||
|
||||
if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
|
||||
cachedSetTimeout = setTimeout;
|
||||
return setTimeout(fun, 0);
|
||||
}
|
||||
|
||||
try {
|
||||
// when when somebody has screwed with setTimeout but no I.E. maddness
|
||||
return cachedSetTimeout(fun, 0);
|
||||
} catch (e) {
|
||||
try {
|
||||
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
|
||||
return cachedSetTimeout.call(null, fun, 0);
|
||||
} catch (e) {
|
||||
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
|
||||
return cachedSetTimeout.call(this, fun, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runClearTimeout(marker) {
|
||||
if (cachedClearTimeout === clearTimeout) {
|
||||
//normal enviroments in sane situations
|
||||
return clearTimeout(marker);
|
||||
} // if clearTimeout wasn't available but was latter defined
|
||||
|
||||
|
||||
if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
|
||||
cachedClearTimeout = clearTimeout;
|
||||
return clearTimeout(marker);
|
||||
}
|
||||
|
||||
try {
|
||||
// when when somebody has screwed with setTimeout but no I.E. maddness
|
||||
return cachedClearTimeout(marker);
|
||||
} catch (e) {
|
||||
try {
|
||||
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
|
||||
return cachedClearTimeout.call(null, marker);
|
||||
} catch (e) {
|
||||
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
|
||||
// Some versions of I.E. have different rules for clearTimeout vs setTimeout
|
||||
return cachedClearTimeout.call(this, marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var queue = [];
|
||||
var draining = false;
|
||||
var currentQueue;
|
||||
var queueIndex = -1;
|
||||
|
||||
function cleanUpNextTick() {
|
||||
if (!draining || !currentQueue) {
|
||||
return;
|
||||
}
|
||||
|
||||
draining = false;
|
||||
|
||||
if (currentQueue.length) {
|
||||
queue = currentQueue.concat(queue);
|
||||
} else {
|
||||
queueIndex = -1;
|
||||
}
|
||||
|
||||
if (queue.length) {
|
||||
drainQueue();
|
||||
}
|
||||
}
|
||||
|
||||
function drainQueue() {
|
||||
if (draining) {
|
||||
return;
|
||||
}
|
||||
|
||||
var timeout = runTimeout(cleanUpNextTick);
|
||||
draining = true;
|
||||
var len = queue.length;
|
||||
|
||||
while (len) {
|
||||
currentQueue = queue;
|
||||
queue = [];
|
||||
|
||||
while (++queueIndex < len) {
|
||||
if (currentQueue) {
|
||||
currentQueue[queueIndex].run();
|
||||
}
|
||||
}
|
||||
|
||||
queueIndex = -1;
|
||||
len = queue.length;
|
||||
}
|
||||
|
||||
currentQueue = null;
|
||||
draining = false;
|
||||
runClearTimeout(timeout);
|
||||
}
|
||||
|
||||
process.nextTick = function (fun) {
|
||||
var args = new Array(arguments.length - 1);
|
||||
|
||||
if (arguments.length > 1) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
args[i - 1] = arguments[i];
|
||||
}
|
||||
}
|
||||
|
||||
queue.push(new Item(fun, args));
|
||||
|
||||
if (queue.length === 1 && !draining) {
|
||||
runTimeout(drainQueue);
|
||||
}
|
||||
}; // v8 likes predictible objects
|
||||
|
||||
|
||||
function Item(fun, array) {
|
||||
this.fun = fun;
|
||||
this.array = array;
|
||||
}
|
||||
|
||||
Item.prototype.run = function () {
|
||||
this.fun.apply(null, this.array);
|
||||
};
|
||||
|
||||
process.title = 'browser';
|
||||
process.browser = true;
|
||||
process.env = {};
|
||||
process.argv = [];
|
||||
process.version = ''; // empty string to avoid regexp issues
|
||||
|
||||
process.versions = {};
|
||||
|
||||
function noop() {}
|
||||
|
||||
process.on = noop;
|
||||
process.addListener = noop;
|
||||
process.once = noop;
|
||||
process.off = noop;
|
||||
process.removeListener = noop;
|
||||
process.removeAllListeners = noop;
|
||||
process.emit = noop;
|
||||
process.prependListener = noop;
|
||||
process.prependOnceListener = noop;
|
||||
|
||||
process.listeners = function (name) {
|
||||
return [];
|
||||
};
|
||||
|
||||
process.binding = function (name) {
|
||||
throw new Error('process.binding is not supported');
|
||||
};
|
||||
|
||||
process.cwd = function () {
|
||||
return '/';
|
||||
};
|
||||
|
||||
process.chdir = function (dir) {
|
||||
throw new Error('process.chdir is not supported');
|
||||
};
|
||||
|
||||
process.umask = function () {
|
||||
return 0;
|
||||
};
|
||||
}, {}],
|
||||
3: [function (require, module, exports) {
|
||||
/**
|
||||
* This is the common logic for both the Node.js and web browser
|
||||
* implementations of `debug()`.
|
||||
*/
|
||||
function setup(env) {
|
||||
createDebug.debug = createDebug;
|
||||
createDebug.default = createDebug;
|
||||
createDebug.coerce = coerce;
|
||||
createDebug.disable = disable;
|
||||
createDebug.enable = enable;
|
||||
createDebug.enabled = enabled;
|
||||
createDebug.humanize = require('ms');
|
||||
Object.keys(env).forEach(function (key) {
|
||||
createDebug[key] = env[key];
|
||||
});
|
||||
/**
|
||||
* Active `debug` instances.
|
||||
*/
|
||||
|
||||
createDebug.instances = [];
|
||||
/**
|
||||
* The currently active debug mode names, and names to skip.
|
||||
*/
|
||||
|
||||
createDebug.names = [];
|
||||
createDebug.skips = [];
|
||||
/**
|
||||
* Map of special "%n" handling functions, for the debug "format" argument.
|
||||
*
|
||||
* Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
|
||||
*/
|
||||
|
||||
createDebug.formatters = {};
|
||||
/**
|
||||
* Selects a color for a debug namespace
|
||||
* @param {String} namespace The namespace string for the for the debug instance to be colored
|
||||
* @return {Number|String} An ANSI color code for the given namespace
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function selectColor(namespace) {
|
||||
var hash = 0;
|
||||
|
||||
for (var i = 0; i < namespace.length; i++) {
|
||||
hash = (hash << 5) - hash + namespace.charCodeAt(i);
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
|
||||
return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
|
||||
}
|
||||
|
||||
createDebug.selectColor = selectColor;
|
||||
/**
|
||||
* Create a debugger with the given `namespace`.
|
||||
*
|
||||
* @param {String} namespace
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function createDebug(namespace) {
|
||||
var prevTime;
|
||||
|
||||
function debug() {
|
||||
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
||||
args[_key] = arguments[_key];
|
||||
}
|
||||
|
||||
// Disabled?
|
||||
if (!debug.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = debug; // Set `diff` timestamp
|
||||
|
||||
var curr = Number(new Date());
|
||||
var ms = curr - (prevTime || curr);
|
||||
self.diff = ms;
|
||||
self.prev = prevTime;
|
||||
self.curr = curr;
|
||||
prevTime = curr;
|
||||
args[0] = createDebug.coerce(args[0]);
|
||||
|
||||
if (typeof args[0] !== 'string') {
|
||||
// Anything else let's inspect with %O
|
||||
args.unshift('%O');
|
||||
} // Apply any `formatters` transformations
|
||||
|
||||
|
||||
var index = 0;
|
||||
args[0] = args[0].replace(/%([a-zA-Z%])/g, function (match, format) {
|
||||
// If we encounter an escaped % then don't increase the array index
|
||||
if (match === '%%') {
|
||||
return match;
|
||||
}
|
||||
|
||||
index++;
|
||||
var formatter = createDebug.formatters[format];
|
||||
|
||||
if (typeof formatter === 'function') {
|
||||
var val = args[index];
|
||||
match = formatter.call(self, val); // Now we need to remove `args[index]` since it's inlined in the `format`
|
||||
|
||||
args.splice(index, 1);
|
||||
index--;
|
||||
}
|
||||
|
||||
return match;
|
||||
}); // Apply env-specific formatting (colors, etc.)
|
||||
|
||||
createDebug.formatArgs.call(self, args);
|
||||
var logFn = self.log || createDebug.log;
|
||||
logFn.apply(self, args);
|
||||
}
|
||||
|
||||
debug.namespace = namespace;
|
||||
debug.enabled = createDebug.enabled(namespace);
|
||||
debug.useColors = createDebug.useColors();
|
||||
debug.color = selectColor(namespace);
|
||||
debug.destroy = destroy;
|
||||
debug.extend = extend; // Debug.formatArgs = formatArgs;
|
||||
// debug.rawLog = rawLog;
|
||||
// env-specific initialization logic for debug instances
|
||||
|
||||
if (typeof createDebug.init === 'function') {
|
||||
createDebug.init(debug);
|
||||
}
|
||||
|
||||
createDebug.instances.push(debug);
|
||||
return debug;
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
var index = createDebug.instances.indexOf(this);
|
||||
|
||||
if (index !== -1) {
|
||||
createDebug.instances.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function extend(namespace, delimiter) {
|
||||
return createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);
|
||||
}
|
||||
/**
|
||||
* Enables a debug mode by namespaces. This can include modes
|
||||
* separated by a colon and wildcards.
|
||||
*
|
||||
* @param {String} namespaces
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
function enable(namespaces) {
|
||||
createDebug.save(namespaces);
|
||||
createDebug.names = [];
|
||||
createDebug.skips = [];
|
||||
var i;
|
||||
var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/);
|
||||
var len = split.length;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (!split[i]) {
|
||||
// ignore empty strings
|
||||
continue;
|
||||
}
|
||||
|
||||
namespaces = split[i].replace(/\*/g, '.*?');
|
||||
|
||||
if (namespaces[0] === '-') {
|
||||
createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
|
||||
} else {
|
||||
createDebug.names.push(new RegExp('^' + namespaces + '$'));
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < createDebug.instances.length; i++) {
|
||||
var instance = createDebug.instances[i];
|
||||
instance.enabled = createDebug.enabled(instance.namespace);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Disable debug output.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
function disable() {
|
||||
createDebug.enable('');
|
||||
}
|
||||
/**
|
||||
* Returns true if the given mode name is enabled, false otherwise.
|
||||
*
|
||||
* @param {String} name
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
function enabled(name) {
|
||||
if (name[name.length - 1] === '*') {
|
||||
return true;
|
||||
}
|
||||
|
||||
var i;
|
||||
var len;
|
||||
|
||||
for (i = 0, len = createDebug.skips.length; i < len; i++) {
|
||||
if (createDebug.skips[i].test(name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0, len = createDebug.names.length; i < len; i++) {
|
||||
if (createDebug.names[i].test(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Coerce `val`.
|
||||
*
|
||||
* @param {Mixed} val
|
||||
* @return {Mixed}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
|
||||
function coerce(val) {
|
||||
if (val instanceof Error) {
|
||||
return val.stack || val.message;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
createDebug.enable(createDebug.load());
|
||||
return createDebug;
|
||||
}
|
||||
|
||||
module.exports = setup;
|
||||
}, {
|
||||
"ms": 1
|
||||
}],
|
||||
4: [function (require, module, exports) {
|
||||
(function (process) {
|
||||
/* eslint-env browser */
|
||||
|
||||
/**
|
||||
* This is the web browser implementation of `debug()`.
|
||||
*/
|
||||
exports.log = log;
|
||||
exports.formatArgs = formatArgs;
|
||||
exports.save = save;
|
||||
exports.load = load;
|
||||
exports.useColors = useColors;
|
||||
exports.storage = localstorage();
|
||||
/**
|
||||
* Colors.
|
||||
*/
|
||||
|
||||
exports.colors = ['#0000CC', '#0000FF', '#0033CC', '#0033FF', '#0066CC', '#0066FF', '#0099CC', '#0099FF', '#00CC00', '#00CC33', '#00CC66', '#00CC99', '#00CCCC', '#00CCFF', '#3300CC', '#3300FF', '#3333CC', '#3333FF', '#3366CC', '#3366FF', '#3399CC', '#3399FF', '#33CC00', '#33CC33', '#33CC66', '#33CC99', '#33CCCC', '#33CCFF', '#6600CC', '#6600FF', '#6633CC', '#6633FF', '#66CC00', '#66CC33', '#9900CC', '#9900FF', '#9933CC', '#9933FF', '#99CC00', '#99CC33', '#CC0000', '#CC0033', '#CC0066', '#CC0099', '#CC00CC', '#CC00FF', '#CC3300', '#CC3333', '#CC3366', '#CC3399', '#CC33CC', '#CC33FF', '#CC6600', '#CC6633', '#CC9900', '#CC9933', '#CCCC00', '#CCCC33', '#FF0000', '#FF0033', '#FF0066', '#FF0099', '#FF00CC', '#FF00FF', '#FF3300', '#FF3333', '#FF3366', '#FF3399', '#FF33CC', '#FF33FF', '#FF6600', '#FF6633', '#FF9900', '#FF9933', '#FFCC00', '#FFCC33'];
|
||||
/**
|
||||
* Currently only WebKit-based Web Inspectors, Firefox >= v31,
|
||||
* and the Firebug extension (any Firefox version) are known
|
||||
* to support "%c" CSS customizations.
|
||||
*
|
||||
* TODO: add a `localStorage` variable to explicitly enable/disable colors
|
||||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
|
||||
function useColors() {
|
||||
// NB: In an Electron preload script, document will be defined but not fully
|
||||
// initialized. Since we know we're in Chrome, we'll just detect this case
|
||||
// explicitly
|
||||
if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) {
|
||||
return true;
|
||||
} // Internet Explorer and Edge do not support colors.
|
||||
|
||||
|
||||
if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
|
||||
return false;
|
||||
} // Is webkit? http://stackoverflow.com/a/16459606/376773
|
||||
// document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
|
||||
|
||||
|
||||
return typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // Is firebug? http://stackoverflow.com/a/398120/376773
|
||||
typeof window !== 'undefined' && window.console && (window.console.firebug || window.console.exception && window.console.table) || // Is firefox >= v31?
|
||||
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
|
||||
typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker
|
||||
typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/);
|
||||
}
|
||||
/**
|
||||
* Colorize log arguments if enabled.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
function formatArgs(args) {
|
||||
args[0] = (this.useColors ? '%c' : '') + this.namespace + (this.useColors ? ' %c' : ' ') + args[0] + (this.useColors ? '%c ' : ' ') + '+' + module.exports.humanize(this.diff);
|
||||
|
||||
if (!this.useColors) {
|
||||
return;
|
||||
}
|
||||
|
||||
var c = 'color: ' + this.color;
|
||||
args.splice(1, 0, c, 'color: inherit'); // The final "%c" is somewhat tricky, because there could be other
|
||||
// arguments passed either before or after the %c, so we need to
|
||||
// figure out the correct index to insert the CSS into
|
||||
|
||||
var index = 0;
|
||||
var lastC = 0;
|
||||
args[0].replace(/%[a-zA-Z%]/g, function (match) {
|
||||
if (match === '%%') {
|
||||
return;
|
||||
}
|
||||
|
||||
index++;
|
||||
|
||||
if (match === '%c') {
|
||||
// We only are interested in the *last* %c
|
||||
// (the user may have provided their own)
|
||||
lastC = index;
|
||||
}
|
||||
});
|
||||
args.splice(lastC, 0, c);
|
||||
}
|
||||
/**
|
||||
* Invokes `console.log()` when available.
|
||||
* No-op when `console.log` is not a "function".
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
function log() {
|
||||
var _console;
|
||||
|
||||
// This hackery is required for IE8/9, where
|
||||
// the `console.log` function doesn't have 'apply'
|
||||
return (typeof console === "undefined" ? "undefined" : _typeof(console)) === 'object' && console.log && (_console = console).log.apply(_console, arguments);
|
||||
}
|
||||
/**
|
||||
* Save `namespaces`.
|
||||
*
|
||||
* @param {String} namespaces
|
||||
* @api private
|
||||
*/
|
||||
|
||||
|
||||
function save(namespaces) {
|
||||
try {
|
||||
if (namespaces) {
|
||||
exports.storage.setItem('debug', namespaces);
|
||||
} else {
|
||||
exports.storage.removeItem('debug');
|
||||
}
|
||||
} catch (error) {// Swallow
|
||||
// XXX (@Qix-) should we be logging these?
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Load `namespaces`.
|
||||
*
|
||||
* @return {String} returns the previously persisted debug modes
|
||||
* @api private
|
||||
*/
|
||||
|
||||
|
||||
function load() {
|
||||
var r;
|
||||
|
||||
try {
|
||||
r = exports.storage.getItem('debug');
|
||||
} catch (error) {} // Swallow
|
||||
// XXX (@Qix-) should we be logging these?
|
||||
// If debug isn't set in LS, and we're in Electron, try to load $DEBUG
|
||||
|
||||
|
||||
if (!r && typeof process !== 'undefined' && 'env' in process) {
|
||||
r = process.env.DEBUG;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
/**
|
||||
* Localstorage attempts to return the localstorage.
|
||||
*
|
||||
* This is necessary because safari throws
|
||||
* when a user disables cookies/localstorage
|
||||
* and you attempt to access it.
|
||||
*
|
||||
* @return {LocalStorage}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
|
||||
function localstorage() {
|
||||
try {
|
||||
// TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context
|
||||
// The Browser also has localStorage in the global context.
|
||||
return localStorage;
|
||||
} catch (error) {// Swallow
|
||||
// XXX (@Qix-) should we be logging these?
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = require('./common')(exports);
|
||||
var formatters = module.exports.formatters;
|
||||
/**
|
||||
* Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
|
||||
*/
|
||||
|
||||
formatters.j = function (v) {
|
||||
try {
|
||||
return JSON.stringify(v);
|
||||
} catch (error) {
|
||||
return '[UnexpectedJSONParseError]: ' + error.message;
|
||||
}
|
||||
};
|
||||
}).call(this, require('_process'));
|
||||
}, {
|
||||
"./common": 3,
|
||||
"_process": 2
|
||||
}]
|
||||
}, {}, [4])(4);
|
||||
});
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
module.exports = require('./src/node');
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"_from": "debug@^3.2.5",
|
||||
"_id": "debug@3.2.6",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"_location": "/sockjs-client/debug",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "debug@^3.2.5",
|
||||
"name": "debug",
|
||||
"escapedName": "debug",
|
||||
"rawSpec": "^3.2.5",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^3.2.5"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/sockjs-client"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"_shasum": "e83d17de16d8a7efb7717edbe5fb10135eee629b",
|
||||
"_spec": "debug@^3.2.5",
|
||||
"_where": "O:\\zero\\zero.Web\\node_modules\\sockjs-client",
|
||||
"author": {
|
||||
"name": "TJ Holowaychuk",
|
||||
"email": "tj@vision-media.ca"
|
||||
},
|
||||
"browser": "./src/browser.js",
|
||||
"bugs": {
|
||||
"url": "https://github.com/visionmedia/debug/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Nathan Rajlich",
|
||||
"email": "nathan@tootallnate.net",
|
||||
"url": "http://n8.io"
|
||||
},
|
||||
{
|
||||
"name": "Andrew Rhyne",
|
||||
"email": "rhyneandrew@gmail.com"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "small debugging utility",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.0.0",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"browserify": "14.4.0",
|
||||
"chai": "^3.5.0",
|
||||
"concurrently": "^3.1.0",
|
||||
"coveralls": "^3.0.2",
|
||||
"istanbul": "^0.4.5",
|
||||
"karma": "^3.0.0",
|
||||
"karma-chai": "^0.1.0",
|
||||
"karma-mocha": "^1.3.0",
|
||||
"karma-phantomjs-launcher": "^1.0.2",
|
||||
"mocha": "^5.2.0",
|
||||
"mocha-lcov-reporter": "^1.2.0",
|
||||
"rimraf": "^2.5.4",
|
||||
"xo": "^0.23.0"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"node.js",
|
||||
"dist/debug.js",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"homepage": "https://github.com/visionmedia/debug#readme",
|
||||
"keywords": [
|
||||
"debug",
|
||||
"log",
|
||||
"debugger"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "./src/index.js",
|
||||
"name": "debug",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/visionmedia/debug.git"
|
||||
},
|
||||
"unpkg": "./dist/debug.js",
|
||||
"version": "3.2.6"
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
"use strict";
|
||||
|
||||
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
/**
|
||||
* This is the web browser implementation of `debug()`.
|
||||
*/
|
||||
exports.log = log;
|
||||
exports.formatArgs = formatArgs;
|
||||
exports.save = save;
|
||||
exports.load = load;
|
||||
exports.useColors = useColors;
|
||||
exports.storage = localstorage();
|
||||
/**
|
||||
* Colors.
|
||||
*/
|
||||
|
||||
exports.colors = ['#0000CC', '#0000FF', '#0033CC', '#0033FF', '#0066CC', '#0066FF', '#0099CC', '#0099FF', '#00CC00', '#00CC33', '#00CC66', '#00CC99', '#00CCCC', '#00CCFF', '#3300CC', '#3300FF', '#3333CC', '#3333FF', '#3366CC', '#3366FF', '#3399CC', '#3399FF', '#33CC00', '#33CC33', '#33CC66', '#33CC99', '#33CCCC', '#33CCFF', '#6600CC', '#6600FF', '#6633CC', '#6633FF', '#66CC00', '#66CC33', '#9900CC', '#9900FF', '#9933CC', '#9933FF', '#99CC00', '#99CC33', '#CC0000', '#CC0033', '#CC0066', '#CC0099', '#CC00CC', '#CC00FF', '#CC3300', '#CC3333', '#CC3366', '#CC3399', '#CC33CC', '#CC33FF', '#CC6600', '#CC6633', '#CC9900', '#CC9933', '#CCCC00', '#CCCC33', '#FF0000', '#FF0033', '#FF0066', '#FF0099', '#FF00CC', '#FF00FF', '#FF3300', '#FF3333', '#FF3366', '#FF3399', '#FF33CC', '#FF33FF', '#FF6600', '#FF6633', '#FF9900', '#FF9933', '#FFCC00', '#FFCC33'];
|
||||
/**
|
||||
* Currently only WebKit-based Web Inspectors, Firefox >= v31,
|
||||
* and the Firebug extension (any Firefox version) are known
|
||||
* to support "%c" CSS customizations.
|
||||
*
|
||||
* TODO: add a `localStorage` variable to explicitly enable/disable colors
|
||||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
|
||||
function useColors() {
|
||||
// NB: In an Electron preload script, document will be defined but not fully
|
||||
// initialized. Since we know we're in Chrome, we'll just detect this case
|
||||
// explicitly
|
||||
if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) {
|
||||
return true;
|
||||
} // Internet Explorer and Edge do not support colors.
|
||||
|
||||
|
||||
if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
|
||||
return false;
|
||||
} // Is webkit? http://stackoverflow.com/a/16459606/376773
|
||||
// document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
|
||||
|
||||
|
||||
return typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // Is firebug? http://stackoverflow.com/a/398120/376773
|
||||
typeof window !== 'undefined' && window.console && (window.console.firebug || window.console.exception && window.console.table) || // Is firefox >= v31?
|
||||
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
|
||||
typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker
|
||||
typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/);
|
||||
}
|
||||
/**
|
||||
* Colorize log arguments if enabled.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
function formatArgs(args) {
|
||||
args[0] = (this.useColors ? '%c' : '') + this.namespace + (this.useColors ? ' %c' : ' ') + args[0] + (this.useColors ? '%c ' : ' ') + '+' + module.exports.humanize(this.diff);
|
||||
|
||||
if (!this.useColors) {
|
||||
return;
|
||||
}
|
||||
|
||||
var c = 'color: ' + this.color;
|
||||
args.splice(1, 0, c, 'color: inherit'); // The final "%c" is somewhat tricky, because there could be other
|
||||
// arguments passed either before or after the %c, so we need to
|
||||
// figure out the correct index to insert the CSS into
|
||||
|
||||
var index = 0;
|
||||
var lastC = 0;
|
||||
args[0].replace(/%[a-zA-Z%]/g, function (match) {
|
||||
if (match === '%%') {
|
||||
return;
|
||||
}
|
||||
|
||||
index++;
|
||||
|
||||
if (match === '%c') {
|
||||
// We only are interested in the *last* %c
|
||||
// (the user may have provided their own)
|
||||
lastC = index;
|
||||
}
|
||||
});
|
||||
args.splice(lastC, 0, c);
|
||||
}
|
||||
/**
|
||||
* Invokes `console.log()` when available.
|
||||
* No-op when `console.log` is not a "function".
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
function log() {
|
||||
var _console;
|
||||
|
||||
// This hackery is required for IE8/9, where
|
||||
// the `console.log` function doesn't have 'apply'
|
||||
return (typeof console === "undefined" ? "undefined" : _typeof(console)) === 'object' && console.log && (_console = console).log.apply(_console, arguments);
|
||||
}
|
||||
/**
|
||||
* Save `namespaces`.
|
||||
*
|
||||
* @param {String} namespaces
|
||||
* @api private
|
||||
*/
|
||||
|
||||
|
||||
function save(namespaces) {
|
||||
try {
|
||||
if (namespaces) {
|
||||
exports.storage.setItem('debug', namespaces);
|
||||
} else {
|
||||
exports.storage.removeItem('debug');
|
||||
}
|
||||
} catch (error) {// Swallow
|
||||
// XXX (@Qix-) should we be logging these?
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Load `namespaces`.
|
||||
*
|
||||
* @return {String} returns the previously persisted debug modes
|
||||
* @api private
|
||||
*/
|
||||
|
||||
|
||||
function load() {
|
||||
var r;
|
||||
|
||||
try {
|
||||
r = exports.storage.getItem('debug');
|
||||
} catch (error) {} // Swallow
|
||||
// XXX (@Qix-) should we be logging these?
|
||||
// If debug isn't set in LS, and we're in Electron, try to load $DEBUG
|
||||
|
||||
|
||||
if (!r && typeof process !== 'undefined' && 'env' in process) {
|
||||
r = process.env.DEBUG;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
/**
|
||||
* Localstorage attempts to return the localstorage.
|
||||
*
|
||||
* This is necessary because safari throws
|
||||
* when a user disables cookies/localstorage
|
||||
* and you attempt to access it.
|
||||
*
|
||||
* @return {LocalStorage}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
|
||||
function localstorage() {
|
||||
try {
|
||||
// TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context
|
||||
// The Browser also has localStorage in the global context.
|
||||
return localStorage;
|
||||
} catch (error) {// Swallow
|
||||
// XXX (@Qix-) should we be logging these?
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = require('./common')(exports);
|
||||
var formatters = module.exports.formatters;
|
||||
/**
|
||||
* Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
|
||||
*/
|
||||
|
||||
formatters.j = function (v) {
|
||||
try {
|
||||
return JSON.stringify(v);
|
||||
} catch (error) {
|
||||
return '[UnexpectedJSONParseError]: ' + error.message;
|
||||
}
|
||||
};
|
||||
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This is the common logic for both the Node.js and web browser
|
||||
* implementations of `debug()`.
|
||||
*/
|
||||
function setup(env) {
|
||||
createDebug.debug = createDebug;
|
||||
createDebug.default = createDebug;
|
||||
createDebug.coerce = coerce;
|
||||
createDebug.disable = disable;
|
||||
createDebug.enable = enable;
|
||||
createDebug.enabled = enabled;
|
||||
createDebug.humanize = require('ms');
|
||||
Object.keys(env).forEach(function (key) {
|
||||
createDebug[key] = env[key];
|
||||
});
|
||||
/**
|
||||
* Active `debug` instances.
|
||||
*/
|
||||
|
||||
createDebug.instances = [];
|
||||
/**
|
||||
* The currently active debug mode names, and names to skip.
|
||||
*/
|
||||
|
||||
createDebug.names = [];
|
||||
createDebug.skips = [];
|
||||
/**
|
||||
* Map of special "%n" handling functions, for the debug "format" argument.
|
||||
*
|
||||
* Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
|
||||
*/
|
||||
|
||||
createDebug.formatters = {};
|
||||
/**
|
||||
* Selects a color for a debug namespace
|
||||
* @param {String} namespace The namespace string for the for the debug instance to be colored
|
||||
* @return {Number|String} An ANSI color code for the given namespace
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function selectColor(namespace) {
|
||||
var hash = 0;
|
||||
|
||||
for (var i = 0; i < namespace.length; i++) {
|
||||
hash = (hash << 5) - hash + namespace.charCodeAt(i);
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
|
||||
return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
|
||||
}
|
||||
|
||||
createDebug.selectColor = selectColor;
|
||||
/**
|
||||
* Create a debugger with the given `namespace`.
|
||||
*
|
||||
* @param {String} namespace
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function createDebug(namespace) {
|
||||
var prevTime;
|
||||
|
||||
function debug() {
|
||||
// Disabled?
|
||||
if (!debug.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
||||
args[_key] = arguments[_key];
|
||||
}
|
||||
|
||||
var self = debug; // Set `diff` timestamp
|
||||
|
||||
var curr = Number(new Date());
|
||||
var ms = curr - (prevTime || curr);
|
||||
self.diff = ms;
|
||||
self.prev = prevTime;
|
||||
self.curr = curr;
|
||||
prevTime = curr;
|
||||
args[0] = createDebug.coerce(args[0]);
|
||||
|
||||
if (typeof args[0] !== 'string') {
|
||||
// Anything else let's inspect with %O
|
||||
args.unshift('%O');
|
||||
} // Apply any `formatters` transformations
|
||||
|
||||
|
||||
var index = 0;
|
||||
args[0] = args[0].replace(/%([a-zA-Z%])/g, function (match, format) {
|
||||
// If we encounter an escaped % then don't increase the array index
|
||||
if (match === '%%') {
|
||||
return match;
|
||||
}
|
||||
|
||||
index++;
|
||||
var formatter = createDebug.formatters[format];
|
||||
|
||||
if (typeof formatter === 'function') {
|
||||
var val = args[index];
|
||||
match = formatter.call(self, val); // Now we need to remove `args[index]` since it's inlined in the `format`
|
||||
|
||||
args.splice(index, 1);
|
||||
index--;
|
||||
}
|
||||
|
||||
return match;
|
||||
}); // Apply env-specific formatting (colors, etc.)
|
||||
|
||||
createDebug.formatArgs.call(self, args);
|
||||
var logFn = self.log || createDebug.log;
|
||||
logFn.apply(self, args);
|
||||
}
|
||||
|
||||
debug.namespace = namespace;
|
||||
debug.enabled = createDebug.enabled(namespace);
|
||||
debug.useColors = createDebug.useColors();
|
||||
debug.color = selectColor(namespace);
|
||||
debug.destroy = destroy;
|
||||
debug.extend = extend; // Debug.formatArgs = formatArgs;
|
||||
// debug.rawLog = rawLog;
|
||||
// env-specific initialization logic for debug instances
|
||||
|
||||
if (typeof createDebug.init === 'function') {
|
||||
createDebug.init(debug);
|
||||
}
|
||||
|
||||
createDebug.instances.push(debug);
|
||||
return debug;
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
var index = createDebug.instances.indexOf(this);
|
||||
|
||||
if (index !== -1) {
|
||||
createDebug.instances.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function extend(namespace, delimiter) {
|
||||
return createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);
|
||||
}
|
||||
/**
|
||||
* Enables a debug mode by namespaces. This can include modes
|
||||
* separated by a colon and wildcards.
|
||||
*
|
||||
* @param {String} namespaces
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
function enable(namespaces) {
|
||||
createDebug.save(namespaces);
|
||||
createDebug.names = [];
|
||||
createDebug.skips = [];
|
||||
var i;
|
||||
var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/);
|
||||
var len = split.length;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (!split[i]) {
|
||||
// ignore empty strings
|
||||
continue;
|
||||
}
|
||||
|
||||
namespaces = split[i].replace(/\*/g, '.*?');
|
||||
|
||||
if (namespaces[0] === '-') {
|
||||
createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
|
||||
} else {
|
||||
createDebug.names.push(new RegExp('^' + namespaces + '$'));
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < createDebug.instances.length; i++) {
|
||||
var instance = createDebug.instances[i];
|
||||
instance.enabled = createDebug.enabled(instance.namespace);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Disable debug output.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
function disable() {
|
||||
createDebug.enable('');
|
||||
}
|
||||
/**
|
||||
* Returns true if the given mode name is enabled, false otherwise.
|
||||
*
|
||||
* @param {String} name
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
function enabled(name) {
|
||||
if (name[name.length - 1] === '*') {
|
||||
return true;
|
||||
}
|
||||
|
||||
var i;
|
||||
var len;
|
||||
|
||||
for (i = 0, len = createDebug.skips.length; i < len; i++) {
|
||||
if (createDebug.skips[i].test(name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0, len = createDebug.names.length; i < len; i++) {
|
||||
if (createDebug.names[i].test(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Coerce `val`.
|
||||
*
|
||||
* @param {Mixed} val
|
||||
* @return {Mixed}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
|
||||
function coerce(val) {
|
||||
if (val instanceof Error) {
|
||||
return val.stack || val.message;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
createDebug.enable(createDebug.load());
|
||||
return createDebug;
|
||||
}
|
||||
|
||||
module.exports = setup;
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Detect Electron renderer / nwjs process, which is node, but we should
|
||||
* treat as a browser.
|
||||
*/
|
||||
if (typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs) {
|
||||
module.exports = require('./browser.js');
|
||||
} else {
|
||||
module.exports = require('./node.js');
|
||||
}
|
||||
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var tty = require('tty');
|
||||
|
||||
var util = require('util');
|
||||
/**
|
||||
* This is the Node.js implementation of `debug()`.
|
||||
*/
|
||||
|
||||
|
||||
exports.init = init;
|
||||
exports.log = log;
|
||||
exports.formatArgs = formatArgs;
|
||||
exports.save = save;
|
||||
exports.load = load;
|
||||
exports.useColors = useColors;
|
||||
/**
|
||||
* Colors.
|
||||
*/
|
||||
|
||||
exports.colors = [6, 2, 3, 4, 5, 1];
|
||||
|
||||
try {
|
||||
// Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json)
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
var supportsColor = require('supports-color');
|
||||
|
||||
if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
|
||||
exports.colors = [20, 21, 26, 27, 32, 33, 38, 39, 40, 41, 42, 43, 44, 45, 56, 57, 62, 63, 68, 69, 74, 75, 76, 77, 78, 79, 80, 81, 92, 93, 98, 99, 112, 113, 128, 129, 134, 135, 148, 149, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 178, 179, 184, 185, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 214, 215, 220, 221];
|
||||
}
|
||||
} catch (error) {} // Swallow - we only care if `supports-color` is available; it doesn't have to be.
|
||||
|
||||
/**
|
||||
* Build up the default `inspectOpts` object from the environment variables.
|
||||
*
|
||||
* $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js
|
||||
*/
|
||||
|
||||
|
||||
exports.inspectOpts = Object.keys(process.env).filter(function (key) {
|
||||
return /^debug_/i.test(key);
|
||||
}).reduce(function (obj, key) {
|
||||
// Camel-case
|
||||
var prop = key.substring(6).toLowerCase().replace(/_([a-z])/g, function (_, k) {
|
||||
return k.toUpperCase();
|
||||
}); // Coerce string value into JS value
|
||||
|
||||
var val = process.env[key];
|
||||
|
||||
if (/^(yes|on|true|enabled)$/i.test(val)) {
|
||||
val = true;
|
||||
} else if (/^(no|off|false|disabled)$/i.test(val)) {
|
||||
val = false;
|
||||
} else if (val === 'null') {
|
||||
val = null;
|
||||
} else {
|
||||
val = Number(val);
|
||||
}
|
||||
|
||||
obj[prop] = val;
|
||||
return obj;
|
||||
}, {});
|
||||
/**
|
||||
* Is stdout a TTY? Colored output is enabled when `true`.
|
||||
*/
|
||||
|
||||
function useColors() {
|
||||
return 'colors' in exports.inspectOpts ? Boolean(exports.inspectOpts.colors) : tty.isatty(process.stderr.fd);
|
||||
}
|
||||
/**
|
||||
* Adds ANSI color escape codes if enabled.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
function formatArgs(args) {
|
||||
var name = this.namespace,
|
||||
useColors = this.useColors;
|
||||
|
||||
if (useColors) {
|
||||
var c = this.color;
|
||||
var colorCode = "\x1B[3" + (c < 8 ? c : '8;5;' + c);
|
||||
var prefix = " ".concat(colorCode, ";1m").concat(name, " \x1B[0m");
|
||||
args[0] = prefix + args[0].split('\n').join('\n' + prefix);
|
||||
args.push(colorCode + 'm+' + module.exports.humanize(this.diff) + "\x1B[0m");
|
||||
} else {
|
||||
args[0] = getDate() + name + ' ' + args[0];
|
||||
}
|
||||
}
|
||||
|
||||
function getDate() {
|
||||
if (exports.inspectOpts.hideDate) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return new Date().toISOString() + ' ';
|
||||
}
|
||||
/**
|
||||
* Invokes `util.format()` with the specified arguments and writes to stderr.
|
||||
*/
|
||||
|
||||
|
||||
function log() {
|
||||
return process.stderr.write(util.format.apply(util, arguments) + '\n');
|
||||
}
|
||||
/**
|
||||
* Save `namespaces`.
|
||||
*
|
||||
* @param {String} namespaces
|
||||
* @api private
|
||||
*/
|
||||
|
||||
|
||||
function save(namespaces) {
|
||||
if (namespaces) {
|
||||
process.env.DEBUG = namespaces;
|
||||
} else {
|
||||
// If you set a process.env field to null or undefined, it gets cast to the
|
||||
// string 'null' or 'undefined'. Just delete instead.
|
||||
delete process.env.DEBUG;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Load `namespaces`.
|
||||
*
|
||||
* @return {String} returns the previously persisted debug modes
|
||||
* @api private
|
||||
*/
|
||||
|
||||
|
||||
function load() {
|
||||
return process.env.DEBUG;
|
||||
}
|
||||
/**
|
||||
* Init logic for `debug` instances.
|
||||
*
|
||||
* Create a new `inspectOpts` object in case `useColors` is set
|
||||
* differently for a particular `debug` instance.
|
||||
*/
|
||||
|
||||
|
||||
function init(debug) {
|
||||
debug.inspectOpts = {};
|
||||
var keys = Object.keys(exports.inspectOpts);
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = require('./common')(exports);
|
||||
var formatters = module.exports.formatters;
|
||||
/**
|
||||
* Map %o to `util.inspect()`, all on a single line.
|
||||
*/
|
||||
|
||||
formatters.o = function (v) {
|
||||
this.inspectOpts.colors = this.useColors;
|
||||
return util.inspect(v, this.inspectOpts).replace(/\s*\n\s*/g, ' ');
|
||||
};
|
||||
/**
|
||||
* Map %O to `util.inspect()`, allowing multiple lines if needed.
|
||||
*/
|
||||
|
||||
|
||||
formatters.O = function (v) {
|
||||
this.inspectOpts.colors = this.useColors;
|
||||
return util.inspect(v, this.inspectOpts);
|
||||
};
|
||||
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
### 0.11.3 / 2019-06-10
|
||||
|
||||
- Fix a race condition that caused a timeout not to be cancelled immediately
|
||||
when the WebSocket is closed
|
||||
|
||||
### 0.11.2 / 2019-06-10
|
||||
|
||||
(This version was pulled due to an error when publishing)
|
||||
|
||||
### 0.11.1 / 2017-01-22
|
||||
|
||||
- Forcibly close the I/O stream after a timeout if the peer does not respond
|
||||
after calling `close()`
|
||||
|
||||
### 0.11.0 / 2016-02-24
|
||||
|
||||
- Introduce a `net` option to the `Client` class for setting things like, say,
|
||||
`servername`
|
||||
|
||||
### 0.10.0 / 2015-07-08
|
||||
|
||||
- Add the standard `code` and `reason` parameters to the `close` method
|
||||
|
||||
### 0.9.4 / 2015-03-08
|
||||
|
||||
- Don't send input to the driver before `start()` is called
|
||||
|
||||
### 0.9.3 / 2015-02-19
|
||||
|
||||
- Make sure the TCP socket is not left open when closing the connection
|
||||
|
||||
### 0.9.2 / 2014-12-21
|
||||
|
||||
- Only emit `error` once, and don't emit it after `close`
|
||||
|
||||
### 0.9.1 / 2014-12-18
|
||||
|
||||
- Check that all options to the WebSocket constructor are recognized
|
||||
|
||||
### 0.9.0 / 2014-12-13
|
||||
|
||||
- Allow protocol extensions to be passed into websocket-extensions
|
||||
|
||||
### 0.8.1 / 2014-11-12
|
||||
|
||||
- Send the correct hostname when upgrading a connection to TLS
|
||||
|
||||
### 0.8.0 / 2014-11-08
|
||||
|
||||
- Support connections via HTTP proxies
|
||||
- Close the connection cleanly if we're still waiting for a handshake response
|
||||
|
||||
### 0.7.3 / 2014-10-04
|
||||
|
||||
- Allow sockets to be closed when they are in any state other than `CLOSED`
|
||||
|
||||
### 0.7.2 / 2013-12-29
|
||||
|
||||
- Make sure the `close` event is emitted by clients on Node v0.10
|
||||
|
||||
### 0.7.1 / 2013-12-03
|
||||
|
||||
- Support the `maxLength` websocket-driver option
|
||||
- Make the client emit `error` events on network errors
|
||||
|
||||
### 0.7.0 / 2013-09-09
|
||||
|
||||
- Allow the server to send custom headers with EventSource responses
|
||||
|
||||
### 0.6.1 / 2013-07-05
|
||||
|
||||
- Add `ca` option to the client for specifying certificate authorities
|
||||
- Start the server driver asynchronously so that `onopen` handlers can be added
|
||||
|
||||
### 0.6.0 / 2013-05-12
|
||||
|
||||
- Add support for custom headers
|
||||
|
||||
### 0.5.0 / 2013-05-05
|
||||
|
||||
- Extract the protocol handlers into the `websocket-driver` library
|
||||
- Support the Node streaming API
|
||||
|
||||
### 0.4.4 / 2013-02-14
|
||||
|
||||
- Emit the `close` event if TCP is closed before CLOSE frame is acked
|
||||
|
||||
### 0.4.3 / 2012-07-09
|
||||
|
||||
- Add `Connection: close` to EventSource response
|
||||
- Handle situations where `request.socket` is undefined
|
||||
|
||||
### 0.4.2 / 2012-04-06
|
||||
|
||||
- Add WebSocket error code `1011`.
|
||||
- Handle URLs with no path correctly by sending `GET /`
|
||||
|
||||
### 0.4.1 / 2012-02-26
|
||||
|
||||
- Treat anything other than a `Buffer` as a string when calling `send()`
|
||||
|
||||
### 0.4.0 / 2012-02-13
|
||||
|
||||
- Add `ping()` method to server-side `WebSocket` and `EventSource`
|
||||
- Buffer `send()` calls until the draft-76 handshake is complete
|
||||
- Fix HTTPS problems on Node 0.7
|
||||
|
||||
### 0.3.1 / 2012-01-16
|
||||
|
||||
- Call `setNoDelay(true)` on `net.Socket` objects to reduce latency
|
||||
|
||||
### 0.3.0 / 2012-01-13
|
||||
|
||||
- Add support for `EventSource` connections
|
||||
|
||||
### 0.2.0 / 2011-12-21
|
||||
|
||||
- Add support for `Sec-WebSocket-Protocol` negotiation
|
||||
- Support `hixie-76` close frames and 75/76 ignored segments
|
||||
- Improve performance of HyBi parsing/framing functions
|
||||
- Decouple parsers from TCP and reduce write volume
|
||||
|
||||
### 0.1.2 / 2011-12-05
|
||||
|
||||
- Detect closed sockets on the server side when TCP connection breaks
|
||||
- Make `hixie-76` sockets work through HAProxy
|
||||
|
||||
### 0.1.1 / 2011-11-30
|
||||
|
||||
- Fix `addEventListener()` interface methods
|
||||
|
||||
### 0.1.0 / 2011-11-27
|
||||
|
||||
- Initial release, based on WebSocket components from Faye
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
Copyright 2010-2019 James Coglan
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
# faye-websocket [](http://travis-ci.org/faye/faye-websocket-node)
|
||||
|
||||
This is a general-purpose WebSocket implementation extracted from the
|
||||
[Faye](http://faye.jcoglan.com) project. It provides classes for easily building
|
||||
WebSocket servers and clients in Node. It does not provide a server itself, but
|
||||
rather makes it easy to handle WebSocket connections within an existing
|
||||
[Node](https://nodejs.org/) application. It does not provide any abstraction
|
||||
other than the standard [WebSocket
|
||||
API](https://html.spec.whatwg.org/multipage/comms.html#network).
|
||||
|
||||
It also provides an abstraction for handling
|
||||
[EventSource](https://html.spec.whatwg.org/multipage/comms.html#server-sent-events)
|
||||
connections, which are one-way connections that allow the server to push data to
|
||||
the client. They are based on streaming HTTP responses and can be easier to access
|
||||
via proxies than WebSockets.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ npm install faye-websocket
|
||||
```
|
||||
|
||||
|
||||
## Handling WebSocket connections in Node
|
||||
|
||||
You can handle WebSockets on the server side by listening for HTTP Upgrade
|
||||
requests, and creating a new socket for the request. This socket object exposes
|
||||
the usual WebSocket methods for receiving and sending messages. For example this
|
||||
is how you'd implement an echo server:
|
||||
|
||||
```js
|
||||
var WebSocket = require('faye-websocket'),
|
||||
http = require('http');
|
||||
|
||||
var server = http.createServer();
|
||||
|
||||
server.on('upgrade', function(request, socket, body) {
|
||||
if (WebSocket.isWebSocket(request)) {
|
||||
var ws = new WebSocket(request, socket, body);
|
||||
|
||||
ws.on('message', function(event) {
|
||||
ws.send(event.data);
|
||||
});
|
||||
|
||||
ws.on('close', function(event) {
|
||||
console.log('close', event.code, event.reason);
|
||||
ws = null;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(8000);
|
||||
```
|
||||
|
||||
`WebSocket` objects are also duplex streams, so you could replace the
|
||||
`ws.on('message', ...)` line with:
|
||||
|
||||
```js
|
||||
ws.pipe(ws);
|
||||
```
|
||||
|
||||
Note that under certain circumstances (notably a draft-76 client connecting
|
||||
through an HTTP proxy), the WebSocket handshake will not be complete after you
|
||||
call `new WebSocket()` because the server will not have received the entire
|
||||
handshake from the client yet. In this case, calls to `ws.send()` will buffer
|
||||
the message in memory until the handshake is complete, at which point any
|
||||
buffered messages will be sent to the client.
|
||||
|
||||
If you need to detect when the WebSocket handshake is complete, you can use the
|
||||
`onopen` event.
|
||||
|
||||
If the connection's protocol version supports it, you can call `ws.ping()` to
|
||||
send a ping message and wait for the client's response. This method takes a
|
||||
message string, and an optional callback that fires when a matching pong message
|
||||
is received. It returns `true` if and only if a ping message was sent. If the
|
||||
client does not support ping/pong, this method sends no data and returns
|
||||
`false`.
|
||||
|
||||
```js
|
||||
ws.ping('Mic check, one, two', function() {
|
||||
// fires when pong is received
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## Using the WebSocket client
|
||||
|
||||
The client supports both the plain-text `ws` protocol and the encrypted `wss`
|
||||
protocol, and has exactly the same interface as a socket you would use in a web
|
||||
browser. On the wire it identifies itself as `hybi-13`.
|
||||
|
||||
```js
|
||||
var WebSocket = require('faye-websocket'),
|
||||
ws = new WebSocket.Client('ws://www.example.com/');
|
||||
|
||||
ws.on('open', function(event) {
|
||||
console.log('open');
|
||||
ws.send('Hello, world!');
|
||||
});
|
||||
|
||||
ws.on('message', function(event) {
|
||||
console.log('message', event.data);
|
||||
});
|
||||
|
||||
ws.on('close', function(event) {
|
||||
console.log('close', event.code, event.reason);
|
||||
ws = null;
|
||||
});
|
||||
```
|
||||
|
||||
The WebSocket client also lets you inspect the status and headers of the
|
||||
handshake response via its `statusCode` and `headers` properties.
|
||||
|
||||
To connect via a proxy, set the `proxy` option to the HTTP origin of the proxy,
|
||||
including any authorization information, custom headers and TLS config you
|
||||
require. Only the `origin` setting is required.
|
||||
|
||||
```js
|
||||
var ws = new WebSocket.Client('ws://www.example.com/', [], {
|
||||
proxy: {
|
||||
origin: 'http://username:password@proxy.example.com',
|
||||
headers: {'User-Agent': 'node'},
|
||||
tls: {cert: fs.readFileSync('client.crt')}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The `tls` value is an object that will be passed to
|
||||
[`tls.connect()`](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback).
|
||||
|
||||
|
||||
## Subprotocol negotiation
|
||||
|
||||
The WebSocket protocol allows peers to select and identify the application
|
||||
protocol to use over the connection. On the client side, you can set which
|
||||
protocols the client accepts by passing a list of protocol names when you
|
||||
construct the socket:
|
||||
|
||||
```js
|
||||
var ws = new WebSocket.Client('ws://www.example.com/', ['irc', 'amqp']);
|
||||
```
|
||||
|
||||
On the server side, you can likewise pass in the list of protocols the server
|
||||
supports after the other constructor arguments:
|
||||
|
||||
```js
|
||||
var ws = new WebSocket(request, socket, body, ['irc', 'amqp']);
|
||||
```
|
||||
|
||||
If the client and server agree on a protocol, both the client- and server-side
|
||||
socket objects expose the selected protocol through the `ws.protocol` property.
|
||||
|
||||
|
||||
## Protocol extensions
|
||||
|
||||
faye-websocket is based on the
|
||||
[websocket-extensions](https://github.com/faye/websocket-extensions-node)
|
||||
framework that allows extensions to be negotiated via the
|
||||
`Sec-WebSocket-Extensions` header. To add extensions to a connection, pass an
|
||||
array of extensions to the `:extensions` option. For example, to add
|
||||
[permessage-deflate](https://github.com/faye/permessage-deflate-node):
|
||||
|
||||
```js
|
||||
var deflate = require('permessage-deflate');
|
||||
|
||||
var ws = new WebSocket(request, socket, body, [], {extensions: [deflate]});
|
||||
```
|
||||
|
||||
|
||||
## Initialization options
|
||||
|
||||
Both the server- and client-side classes allow an options object to be passed in
|
||||
at initialization time, for example:
|
||||
|
||||
```js
|
||||
var ws = new WebSocket(request, socket, body, protocols, options);
|
||||
var ws = new WebSocket.Client(url, protocols, options);
|
||||
```
|
||||
|
||||
`protocols` is an array of subprotocols as described above, or `null`.
|
||||
`options` is an optional object containing any of these fields:
|
||||
|
||||
- `extensions` - an array of
|
||||
[websocket-extensions](https://github.com/faye/websocket-extensions-node)
|
||||
compatible extensions, as described above
|
||||
- `headers` - an object containing key-value pairs representing HTTP headers to
|
||||
be sent during the handshake process
|
||||
- `maxLength` - the maximum allowed size of incoming message frames, in bytes.
|
||||
The default value is `2^26 - 1`, or 1 byte short of 64 MiB.
|
||||
- `ping` - an integer that sets how often the WebSocket should send ping frames,
|
||||
measured in seconds
|
||||
|
||||
The client accepts some additional options:
|
||||
|
||||
- `proxy` - settings for a proxy as described above
|
||||
- `net` - an object containing settings for the origin server that will be
|
||||
passed to
|
||||
[`net.connect()`](https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener)
|
||||
- `tls` - an object containing TLS settings for the origin server, this will be
|
||||
passed to
|
||||
[`tls.connect()`](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback)
|
||||
- `ca` - (legacy) a shorthand for passing `{tls: {ca: value}}`
|
||||
|
||||
|
||||
## WebSocket API
|
||||
|
||||
Both server- and client-side `WebSocket` objects support the following API.
|
||||
|
||||
- **`on('open', function(event) {})`** fires when the socket connection is
|
||||
established. Event has no attributes.
|
||||
- **`on('message', function(event) {})`** fires when the socket receives a
|
||||
message. Event has one attribute, **`data`**, which is either a `String` (for
|
||||
text frames) or a `Buffer` (for binary frames).
|
||||
- **`on('error', function(event) {})`** fires when there is a protocol error due
|
||||
to bad data sent by the other peer. This event is purely informational, you do
|
||||
not need to implement error recover.
|
||||
- **`on('close', function(event) {})`** fires when either the client or the
|
||||
server closes the connection. Event has two optional attributes, **`code`**
|
||||
and **`reason`**, that expose the status code and message sent by the peer
|
||||
that closed the connection.
|
||||
- **`send(message)`** accepts either a `String` or a `Buffer` and sends a text
|
||||
or binary message over the connection to the other peer.
|
||||
- **`ping(message, function() {})`** sends a ping frame with an optional message
|
||||
and fires the callback when a matching pong is received.
|
||||
- **`close(code, reason)`** closes the connection, sending the given status code
|
||||
and reason text, both of which are optional.
|
||||
- **`version`** is a string containing the version of the `WebSocket` protocol
|
||||
the connection is using.
|
||||
- **`protocol`** is a string (which may be empty) identifying the subprotocol
|
||||
the socket is using.
|
||||
|
||||
|
||||
## Handling EventSource connections in Node
|
||||
|
||||
EventSource connections provide a very similar interface, although because they
|
||||
only allow the server to send data to the client, there is no `onmessage` API.
|
||||
EventSource allows the server to push text messages to the client, where each
|
||||
message has an optional event-type and ID.
|
||||
|
||||
```js
|
||||
var WebSocket = require('faye-websocket'),
|
||||
EventSource = WebSocket.EventSource,
|
||||
http = require('http');
|
||||
|
||||
var server = http.createServer();
|
||||
|
||||
server.on('request', function(request, response) {
|
||||
if (EventSource.isEventSource(request)) {
|
||||
var es = new EventSource(request, response);
|
||||
console.log('open', es.url, es.lastEventId);
|
||||
|
||||
// Periodically send messages
|
||||
var loop = setInterval(function() { es.send('Hello') }, 1000);
|
||||
|
||||
es.on('close', function() {
|
||||
clearInterval(loop);
|
||||
es = null;
|
||||
});
|
||||
|
||||
} else {
|
||||
// Normal HTTP request
|
||||
response.writeHead(200, {'Content-Type': 'text/plain'});
|
||||
response.end('Hello');
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(8000);
|
||||
```
|
||||
|
||||
The `send` method takes two optional parameters, `event` and `id`. The default
|
||||
event-type is `'message'` with no ID. For example, to send a `notification`
|
||||
event with ID `99`:
|
||||
|
||||
```js
|
||||
es.send('Breaking News!', {event: 'notification', id: '99'});
|
||||
```
|
||||
|
||||
The `EventSource` object exposes the following properties:
|
||||
|
||||
- **`url`** is a string containing the URL the client used to create the
|
||||
EventSource.
|
||||
- **`lastEventId`** is a string containing the last event ID received by the
|
||||
client. You can use this when the client reconnects after a dropped connection
|
||||
to determine which messages need resending.
|
||||
|
||||
When you initialize an EventSource with ` new EventSource()`, you can pass
|
||||
configuration options after the `response` parameter. Available options are:
|
||||
|
||||
- **`headers`** is an object containing custom headers to be set on the
|
||||
EventSource response.
|
||||
- **`retry`** is a number that tells the client how long (in seconds) it should
|
||||
wait after a dropped connection before attempting to reconnect.
|
||||
- **`ping`** is a number that tells the server how often (in seconds) to send
|
||||
'ping' packets to the client to keep the connection open, to defeat timeouts
|
||||
set by proxies. The client will ignore these messages.
|
||||
|
||||
For example, this creates a connection that allows access from any origin, pings
|
||||
every 15 seconds and is retryable every 10 seconds if the connection is broken:
|
||||
|
||||
```js
|
||||
var es = new EventSource(request, response, {
|
||||
headers: {'Access-Control-Allow-Origin': '*'},
|
||||
ping: 15,
|
||||
retry: 10
|
||||
});
|
||||
```
|
||||
|
||||
You can send a ping message at any time by calling `es.ping()`. Unlike
|
||||
WebSocket, the client does not send a response to this; it is merely to send
|
||||
some data over the wire to keep the connection alive.
|
||||
Generated
Vendored
+133
@@ -0,0 +1,133 @@
|
||||
'use strict';
|
||||
|
||||
var Stream = require('stream').Stream,
|
||||
util = require('util'),
|
||||
driver = require('websocket-driver'),
|
||||
Headers = require('websocket-driver/lib/websocket/driver/headers'),
|
||||
API = require('./websocket/api'),
|
||||
EventTarget = require('./websocket/api/event_target'),
|
||||
Event = require('./websocket/api/event');
|
||||
|
||||
var EventSource = function(request, response, options) {
|
||||
this.writable = true;
|
||||
options = options || {};
|
||||
|
||||
this._stream = response.socket;
|
||||
this._ping = options.ping || this.DEFAULT_PING;
|
||||
this._retry = options.retry || this.DEFAULT_RETRY;
|
||||
|
||||
var scheme = driver.isSecureRequest(request) ? 'https:' : 'http:';
|
||||
this.url = scheme + '//' + request.headers.host + request.url;
|
||||
this.lastEventId = request.headers['last-event-id'] || '';
|
||||
this.readyState = API.CONNECTING;
|
||||
|
||||
var headers = new Headers(),
|
||||
self = this;
|
||||
|
||||
if (options.headers) {
|
||||
for (var key in options.headers) headers.set(key, options.headers[key]);
|
||||
}
|
||||
|
||||
if (!this._stream || !this._stream.writable) return;
|
||||
process.nextTick(function() { self._open() });
|
||||
|
||||
this._stream.setTimeout(0);
|
||||
this._stream.setNoDelay(true);
|
||||
|
||||
var handshake = 'HTTP/1.1 200 OK\r\n' +
|
||||
'Content-Type: text/event-stream\r\n' +
|
||||
'Cache-Control: no-cache, no-store\r\n' +
|
||||
'Connection: close\r\n' +
|
||||
headers.toString() +
|
||||
'\r\n' +
|
||||
'retry: ' + Math.floor(this._retry * 1000) + '\r\n\r\n';
|
||||
|
||||
this._write(handshake);
|
||||
|
||||
this._stream.on('drain', function() { self.emit('drain') });
|
||||
|
||||
if (this._ping)
|
||||
this._pingTimer = setInterval(function() { self.ping() }, this._ping * 1000);
|
||||
|
||||
['error', 'end'].forEach(function(event) {
|
||||
self._stream.on(event, function() { self.close() });
|
||||
});
|
||||
};
|
||||
util.inherits(EventSource, Stream);
|
||||
|
||||
EventSource.isEventSource = function(request) {
|
||||
if (request.method !== 'GET') return false;
|
||||
var accept = (request.headers.accept || '').split(/\s*,\s*/);
|
||||
return accept.indexOf('text/event-stream') >= 0;
|
||||
};
|
||||
|
||||
var instance = {
|
||||
DEFAULT_PING: 10,
|
||||
DEFAULT_RETRY: 5,
|
||||
|
||||
_write: function(chunk) {
|
||||
if (!this.writable) return false;
|
||||
try {
|
||||
return this._stream.write(chunk, 'utf8');
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
_open: function() {
|
||||
if (this.readyState !== API.CONNECTING) return;
|
||||
|
||||
this.readyState = API.OPEN;
|
||||
|
||||
var event = new Event('open');
|
||||
event.initEvent('open', false, false);
|
||||
this.dispatchEvent(event);
|
||||
},
|
||||
|
||||
write: function(message) {
|
||||
return this.send(message);
|
||||
},
|
||||
|
||||
end: function(message) {
|
||||
if (message !== undefined) this.write(message);
|
||||
this.close();
|
||||
},
|
||||
|
||||
send: function(message, options) {
|
||||
if (this.readyState > API.OPEN) return false;
|
||||
|
||||
message = String(message).replace(/(\r\n|\r|\n)/g, '$1data: ');
|
||||
options = options || {};
|
||||
|
||||
var frame = '';
|
||||
if (options.event) frame += 'event: ' + options.event + '\r\n';
|
||||
if (options.id) frame += 'id: ' + options.id + '\r\n';
|
||||
frame += 'data: ' + message + '\r\n\r\n';
|
||||
|
||||
return this._write(frame);
|
||||
},
|
||||
|
||||
ping: function() {
|
||||
return this._write(':\r\n\r\n');
|
||||
},
|
||||
|
||||
close: function() {
|
||||
if (this.readyState > API.OPEN) return false;
|
||||
|
||||
this.readyState = API.CLOSED;
|
||||
this.writable = false;
|
||||
if (this._pingTimer) clearInterval(this._pingTimer);
|
||||
if (this._stream) this._stream.end();
|
||||
|
||||
var event = new Event('close');
|
||||
event.initEvent('close', false, false);
|
||||
this.dispatchEvent(event);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
for (var method in instance) EventSource.prototype[method] = instance[method];
|
||||
for (var key in EventTarget) EventSource.prototype[key] = EventTarget[key];
|
||||
|
||||
module.exports = EventSource;
|
||||
Generated
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
// API references:
|
||||
//
|
||||
// * https://html.spec.whatwg.org/multipage/comms.html#network
|
||||
// * https://dom.spec.whatwg.org/#interface-eventtarget
|
||||
// * https://dom.spec.whatwg.org/#interface-event
|
||||
|
||||
'use strict';
|
||||
|
||||
var util = require('util'),
|
||||
driver = require('websocket-driver'),
|
||||
API = require('./websocket/api');
|
||||
|
||||
var WebSocket = function(request, socket, body, protocols, options) {
|
||||
options = options || {};
|
||||
|
||||
this._stream = socket;
|
||||
this._driver = driver.http(request, {maxLength: options.maxLength, protocols: protocols});
|
||||
|
||||
var self = this;
|
||||
if (!this._stream || !this._stream.writable) return;
|
||||
if (!this._stream.readable) return this._stream.end();
|
||||
|
||||
var catchup = function() { self._stream.removeListener('data', catchup) };
|
||||
this._stream.on('data', catchup);
|
||||
|
||||
API.call(this, options);
|
||||
|
||||
process.nextTick(function() {
|
||||
self._driver.start();
|
||||
self._driver.io.write(body);
|
||||
});
|
||||
};
|
||||
util.inherits(WebSocket, API);
|
||||
|
||||
WebSocket.isWebSocket = function(request) {
|
||||
return driver.isWebSocket(request);
|
||||
};
|
||||
|
||||
WebSocket.validateOptions = function(options, validKeys) {
|
||||
driver.validateOptions(options, validKeys);
|
||||
};
|
||||
|
||||
WebSocket.WebSocket = WebSocket;
|
||||
WebSocket.Client = require('./websocket/client');
|
||||
WebSocket.EventSource = require('./eventsource');
|
||||
|
||||
module.exports = WebSocket;
|
||||
Generated
Vendored
+197
@@ -0,0 +1,197 @@
|
||||
'use strict';
|
||||
|
||||
var Stream = require('stream').Stream,
|
||||
util = require('util'),
|
||||
driver = require('websocket-driver'),
|
||||
EventTarget = require('./api/event_target'),
|
||||
Event = require('./api/event');
|
||||
|
||||
var API = function(options) {
|
||||
options = options || {};
|
||||
driver.validateOptions(options, ['headers', 'extensions', 'maxLength', 'ping', 'proxy', 'tls', 'ca']);
|
||||
|
||||
this.readable = this.writable = true;
|
||||
|
||||
var headers = options.headers;
|
||||
if (headers) {
|
||||
for (var name in headers) this._driver.setHeader(name, headers[name]);
|
||||
}
|
||||
|
||||
var extensions = options.extensions;
|
||||
if (extensions) {
|
||||
[].concat(extensions).forEach(this._driver.addExtension, this._driver);
|
||||
}
|
||||
|
||||
this._ping = options.ping;
|
||||
this._pingId = 0;
|
||||
this.readyState = API.CONNECTING;
|
||||
this.bufferedAmount = 0;
|
||||
this.protocol = '';
|
||||
this.url = this._driver.url;
|
||||
this.version = this._driver.version;
|
||||
|
||||
var self = this;
|
||||
|
||||
this._driver.on('open', function(e) { self._open() });
|
||||
this._driver.on('message', function(e) { self._receiveMessage(e.data) });
|
||||
this._driver.on('close', function(e) { self._beginClose(e.reason, e.code) });
|
||||
|
||||
this._driver.on('error', function(error) {
|
||||
self._emitError(error.message);
|
||||
});
|
||||
this.on('error', function() {});
|
||||
|
||||
this._driver.messages.on('drain', function() {
|
||||
self.emit('drain');
|
||||
});
|
||||
|
||||
if (this._ping)
|
||||
this._pingTimer = setInterval(function() {
|
||||
self._pingId += 1;
|
||||
self.ping(self._pingId.toString());
|
||||
}, this._ping * 1000);
|
||||
|
||||
this._configureStream();
|
||||
|
||||
if (!this._proxy) {
|
||||
this._stream.pipe(this._driver.io);
|
||||
this._driver.io.pipe(this._stream);
|
||||
}
|
||||
};
|
||||
util.inherits(API, Stream);
|
||||
|
||||
API.CONNECTING = 0;
|
||||
API.OPEN = 1;
|
||||
API.CLOSING = 2;
|
||||
API.CLOSED = 3;
|
||||
|
||||
API.CLOSE_TIMEOUT = 30000;
|
||||
|
||||
var instance = {
|
||||
write: function(data) {
|
||||
return this.send(data);
|
||||
},
|
||||
|
||||
end: function(data) {
|
||||
if (data !== undefined) this.send(data);
|
||||
this.close();
|
||||
},
|
||||
|
||||
pause: function() {
|
||||
return this._driver.messages.pause();
|
||||
},
|
||||
|
||||
resume: function() {
|
||||
return this._driver.messages.resume();
|
||||
},
|
||||
|
||||
send: function(data) {
|
||||
if (this.readyState > API.OPEN) return false;
|
||||
if (!(data instanceof Buffer)) data = String(data);
|
||||
return this._driver.messages.write(data);
|
||||
},
|
||||
|
||||
ping: function(message, callback) {
|
||||
if (this.readyState > API.OPEN) return false;
|
||||
return this._driver.ping(message, callback);
|
||||
},
|
||||
|
||||
close: function(code, reason) {
|
||||
if (code === undefined) code = 1000;
|
||||
if (reason === undefined) reason = '';
|
||||
|
||||
if (code !== 1000 && (code < 3000 || code > 4999))
|
||||
throw new Error("Failed to execute 'close' on WebSocket: " +
|
||||
"The code must be either 1000, or between 3000 and 4999. " +
|
||||
code + " is neither.");
|
||||
|
||||
if (this.readyState !== API.CLOSED) this.readyState = API.CLOSING;
|
||||
var self = this;
|
||||
|
||||
this._closeTimer = setTimeout(function() {
|
||||
self._beginClose('', 1006);
|
||||
}, API.CLOSE_TIMEOUT);
|
||||
|
||||
this._driver.close(reason, code);
|
||||
},
|
||||
|
||||
_configureStream: function() {
|
||||
var self = this;
|
||||
|
||||
this._stream.setTimeout(0);
|
||||
this._stream.setNoDelay(true);
|
||||
|
||||
['close', 'end'].forEach(function(event) {
|
||||
this._stream.on(event, function() { self._finalizeClose() });
|
||||
}, this);
|
||||
|
||||
this._stream.on('error', function(error) {
|
||||
self._emitError('Network error: ' + self.url + ': ' + error.message);
|
||||
self._finalizeClose();
|
||||
});
|
||||
},
|
||||
|
||||
_open: function() {
|
||||
if (this.readyState !== API.CONNECTING) return;
|
||||
|
||||
this.readyState = API.OPEN;
|
||||
this.protocol = this._driver.protocol || '';
|
||||
|
||||
var event = new Event('open');
|
||||
event.initEvent('open', false, false);
|
||||
this.dispatchEvent(event);
|
||||
},
|
||||
|
||||
_receiveMessage: function(data) {
|
||||
if (this.readyState > API.OPEN) return false;
|
||||
|
||||
if (this.readable) this.emit('data', data);
|
||||
|
||||
var event = new Event('message', {data: data});
|
||||
event.initEvent('message', false, false);
|
||||
this.dispatchEvent(event);
|
||||
},
|
||||
|
||||
_emitError: function(message) {
|
||||
if (this.readyState >= API.CLOSING) return;
|
||||
|
||||
var event = new Event('error', {message: message});
|
||||
event.initEvent('error', false, false);
|
||||
this.dispatchEvent(event);
|
||||
},
|
||||
|
||||
_beginClose: function(reason, code) {
|
||||
if (this.readyState === API.CLOSED) return;
|
||||
this.readyState = API.CLOSING;
|
||||
this._closeParams = [reason, code];
|
||||
|
||||
if (this._stream) {
|
||||
this._stream.destroy();
|
||||
if (!this._stream.readable) this._finalizeClose();
|
||||
}
|
||||
},
|
||||
|
||||
_finalizeClose: function() {
|
||||
if (this.readyState === API.CLOSED) return;
|
||||
this.readyState = API.CLOSED;
|
||||
|
||||
if (this._closeTimer) clearTimeout(this._closeTimer);
|
||||
if (this._pingTimer) clearInterval(this._pingTimer);
|
||||
if (this._stream) this._stream.end();
|
||||
|
||||
if (this.readable) this.emit('end');
|
||||
this.readable = this.writable = false;
|
||||
|
||||
var reason = this._closeParams ? this._closeParams[0] : '',
|
||||
code = this._closeParams ? this._closeParams[1] : 1006;
|
||||
|
||||
var event = new Event('close', {code: code, reason: reason});
|
||||
event.initEvent('close', false, false);
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
for (var method in instance) API.prototype[method] = instance[method];
|
||||
for (var key in EventTarget) API.prototype[key] = EventTarget[key];
|
||||
|
||||
module.exports = API;
|
||||
Generated
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
var Event = function(eventType, options) {
|
||||
this.type = eventType;
|
||||
for (var key in options)
|
||||
this[key] = options[key];
|
||||
};
|
||||
|
||||
Event.prototype.initEvent = function(eventType, canBubble, cancelable) {
|
||||
this.type = eventType;
|
||||
this.bubbles = canBubble;
|
||||
this.cancelable = cancelable;
|
||||
};
|
||||
|
||||
Event.prototype.stopPropagation = function() {};
|
||||
Event.prototype.preventDefault = function() {};
|
||||
|
||||
Event.CAPTURING_PHASE = 1;
|
||||
Event.AT_TARGET = 2;
|
||||
Event.BUBBLING_PHASE = 3;
|
||||
|
||||
module.exports = Event;
|
||||
Generated
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
var Event = require('./event');
|
||||
|
||||
var EventTarget = {
|
||||
onopen: null,
|
||||
onmessage: null,
|
||||
onerror: null,
|
||||
onclose: null,
|
||||
|
||||
addEventListener: function(eventType, listener, useCapture) {
|
||||
this.on(eventType, listener);
|
||||
},
|
||||
|
||||
removeEventListener: function(eventType, listener, useCapture) {
|
||||
this.removeListener(eventType, listener);
|
||||
},
|
||||
|
||||
dispatchEvent: function(event) {
|
||||
event.target = event.currentTarget = this;
|
||||
event.eventPhase = Event.AT_TARGET;
|
||||
|
||||
if (this['on' + event.type])
|
||||
this['on' + event.type](event);
|
||||
|
||||
this.emit(event.type, event);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = EventTarget;
|
||||
Generated
Vendored
+90
@@ -0,0 +1,90 @@
|
||||
'use strict';
|
||||
|
||||
var util = require('util'),
|
||||
net = require('net'),
|
||||
tls = require('tls'),
|
||||
url = require('url'),
|
||||
driver = require('websocket-driver'),
|
||||
API = require('./api'),
|
||||
Event = require('./api/event');
|
||||
|
||||
var DEFAULT_PORTS = {'http:': 80, 'https:': 443, 'ws:':80, 'wss:': 443},
|
||||
SECURE_PROTOCOLS = ['https:', 'wss:'];
|
||||
|
||||
var Client = function(_url, protocols, options) {
|
||||
options = options || {};
|
||||
|
||||
this.url = _url;
|
||||
this._driver = driver.client(this.url, {maxLength: options.maxLength, protocols: protocols});
|
||||
|
||||
['open', 'error'].forEach(function(event) {
|
||||
this._driver.on(event, function() {
|
||||
self.headers = self._driver.headers;
|
||||
self.statusCode = self._driver.statusCode;
|
||||
});
|
||||
}, this);
|
||||
|
||||
var proxy = options.proxy || {},
|
||||
endpoint = url.parse(proxy.origin || this.url),
|
||||
port = endpoint.port || DEFAULT_PORTS[endpoint.protocol],
|
||||
secure = SECURE_PROTOCOLS.indexOf(endpoint.protocol) >= 0,
|
||||
onConnect = function() { self._onConnect() },
|
||||
netOptions = options.net || {},
|
||||
originTLS = options.tls || {},
|
||||
socketTLS = proxy.origin ? (proxy.tls || {}) : originTLS,
|
||||
self = this;
|
||||
|
||||
netOptions.host = socketTLS.host = endpoint.hostname;
|
||||
netOptions.port = socketTLS.port = port;
|
||||
|
||||
originTLS.ca = originTLS.ca || options.ca;
|
||||
socketTLS.servername = socketTLS.servername || endpoint.hostname;
|
||||
|
||||
this._stream = secure
|
||||
? tls.connect(socketTLS, onConnect)
|
||||
: net.connect(netOptions, onConnect);
|
||||
|
||||
if (proxy.origin) this._configureProxy(proxy, originTLS);
|
||||
|
||||
API.call(this, options);
|
||||
};
|
||||
util.inherits(Client, API);
|
||||
|
||||
Client.prototype._onConnect = function() {
|
||||
var worker = this._proxy || this._driver;
|
||||
worker.start();
|
||||
};
|
||||
|
||||
Client.prototype._configureProxy = function(proxy, originTLS) {
|
||||
var uri = url.parse(this.url),
|
||||
secure = SECURE_PROTOCOLS.indexOf(uri.protocol) >= 0,
|
||||
self = this,
|
||||
name;
|
||||
|
||||
this._proxy = this._driver.proxy(proxy.origin);
|
||||
|
||||
if (proxy.headers) {
|
||||
for (name in proxy.headers) this._proxy.setHeader(name, proxy.headers[name]);
|
||||
}
|
||||
|
||||
this._proxy.pipe(this._stream, {end: false});
|
||||
this._stream.pipe(this._proxy);
|
||||
|
||||
this._proxy.on('connect', function() {
|
||||
if (secure) {
|
||||
var options = {socket: self._stream, servername: uri.hostname};
|
||||
for (name in originTLS) options[name] = originTLS[name];
|
||||
self._stream = tls.connect(options);
|
||||
self._configureStream();
|
||||
}
|
||||
self._driver.io.pipe(self._stream);
|
||||
self._stream.pipe(self._driver.io);
|
||||
self._driver.start();
|
||||
});
|
||||
|
||||
this._proxy.on('error', function(error) {
|
||||
self._driver.emit('error', error);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Client;
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"_from": "faye-websocket@~0.11.1",
|
||||
"_id": "faye-websocket@0.11.3",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==",
|
||||
"_location": "/sockjs-client/faye-websocket",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "faye-websocket@~0.11.1",
|
||||
"name": "faye-websocket",
|
||||
"escapedName": "faye-websocket",
|
||||
"rawSpec": "~0.11.1",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "~0.11.1"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/sockjs-client"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz",
|
||||
"_shasum": "5c0e9a8968e8912c286639fde977a8b209f2508e",
|
||||
"_spec": "faye-websocket@~0.11.1",
|
||||
"_where": "O:\\zero\\zero.Web\\node_modules\\sockjs-client",
|
||||
"author": {
|
||||
"name": "James Coglan",
|
||||
"email": "jcoglan@gmail.com",
|
||||
"url": "http://jcoglan.com/"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/faye/faye-websocket-node/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {
|
||||
"websocket-driver": ">=0.5.1"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "Standards-compliant WebSocket server and client",
|
||||
"devDependencies": {
|
||||
"jstest": "*",
|
||||
"pace": "*",
|
||||
"permessage-deflate": "*"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"homepage": "https://github.com/faye/faye-websocket-node",
|
||||
"keywords": [
|
||||
"websocket",
|
||||
"eventsource"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/faye/websocket",
|
||||
"name": "faye-websocket",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/faye/faye-websocket-node.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jstest spec/runner.js"
|
||||
},
|
||||
"version": "0.11.3"
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Helpers.
|
||||
*/
|
||||
|
||||
var s = 1000;
|
||||
var m = s * 60;
|
||||
var h = m * 60;
|
||||
var d = h * 24;
|
||||
var w = d * 7;
|
||||
var y = d * 365.25;
|
||||
|
||||
/**
|
||||
* Parse or format the given `val`.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `long` verbose formatting [false]
|
||||
*
|
||||
* @param {String|Number} val
|
||||
* @param {Object} [options]
|
||||
* @throws {Error} throw an error if val is not a non-empty string or a number
|
||||
* @return {String|Number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
module.exports = function(val, options) {
|
||||
options = options || {};
|
||||
var type = typeof val;
|
||||
if (type === 'string' && val.length > 0) {
|
||||
return parse(val);
|
||||
} else if (type === 'number' && isFinite(val)) {
|
||||
return options.long ? fmtLong(val) : fmtShort(val);
|
||||
}
|
||||
throw new Error(
|
||||
'val is not a non-empty string or a valid number. val=' +
|
||||
JSON.stringify(val)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the given `str` and return milliseconds.
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {Number}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function parse(str) {
|
||||
str = String(str);
|
||||
if (str.length > 100) {
|
||||
return;
|
||||
}
|
||||
var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
|
||||
str
|
||||
);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
var n = parseFloat(match[1]);
|
||||
var type = (match[2] || 'ms').toLowerCase();
|
||||
switch (type) {
|
||||
case 'years':
|
||||
case 'year':
|
||||
case 'yrs':
|
||||
case 'yr':
|
||||
case 'y':
|
||||
return n * y;
|
||||
case 'weeks':
|
||||
case 'week':
|
||||
case 'w':
|
||||
return n * w;
|
||||
case 'days':
|
||||
case 'day':
|
||||
case 'd':
|
||||
return n * d;
|
||||
case 'hours':
|
||||
case 'hour':
|
||||
case 'hrs':
|
||||
case 'hr':
|
||||
case 'h':
|
||||
return n * h;
|
||||
case 'minutes':
|
||||
case 'minute':
|
||||
case 'mins':
|
||||
case 'min':
|
||||
case 'm':
|
||||
return n * m;
|
||||
case 'seconds':
|
||||
case 'second':
|
||||
case 'secs':
|
||||
case 'sec':
|
||||
case 's':
|
||||
return n * s;
|
||||
case 'milliseconds':
|
||||
case 'millisecond':
|
||||
case 'msecs':
|
||||
case 'msec':
|
||||
case 'ms':
|
||||
return n;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Short format for `ms`.
|
||||
*
|
||||
* @param {Number} ms
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function fmtShort(ms) {
|
||||
var msAbs = Math.abs(ms);
|
||||
if (msAbs >= d) {
|
||||
return Math.round(ms / d) + 'd';
|
||||
}
|
||||
if (msAbs >= h) {
|
||||
return Math.round(ms / h) + 'h';
|
||||
}
|
||||
if (msAbs >= m) {
|
||||
return Math.round(ms / m) + 'm';
|
||||
}
|
||||
if (msAbs >= s) {
|
||||
return Math.round(ms / s) + 's';
|
||||
}
|
||||
return ms + 'ms';
|
||||
}
|
||||
|
||||
/**
|
||||
* Long format for `ms`.
|
||||
*
|
||||
* @param {Number} ms
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function fmtLong(ms) {
|
||||
var msAbs = Math.abs(ms);
|
||||
if (msAbs >= d) {
|
||||
return plural(ms, msAbs, d, 'day');
|
||||
}
|
||||
if (msAbs >= h) {
|
||||
return plural(ms, msAbs, h, 'hour');
|
||||
}
|
||||
if (msAbs >= m) {
|
||||
return plural(ms, msAbs, m, 'minute');
|
||||
}
|
||||
if (msAbs >= s) {
|
||||
return plural(ms, msAbs, s, 'second');
|
||||
}
|
||||
return ms + ' ms';
|
||||
}
|
||||
|
||||
/**
|
||||
* Pluralization helper.
|
||||
*/
|
||||
|
||||
function plural(ms, msAbs, n, name) {
|
||||
var isPlural = msAbs >= n * 1.5;
|
||||
return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : '');
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Zeit, Inc.
|
||||
|
||||
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.
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"_from": "ms@^2.1.1",
|
||||
"_id": "ms@2.1.2",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"_location": "/sockjs-client/ms",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "ms@^2.1.1",
|
||||
"name": "ms",
|
||||
"escapedName": "ms",
|
||||
"rawSpec": "^2.1.1",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^2.1.1"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/sockjs-client/debug"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"_shasum": "d09d1f357b443f493382a8eb3ccd183872ae6009",
|
||||
"_spec": "ms@^2.1.1",
|
||||
"_where": "O:\\zero\\zero.Web\\node_modules\\sockjs-client\\node_modules\\debug",
|
||||
"bugs": {
|
||||
"url": "https://github.com/zeit/ms/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"deprecated": false,
|
||||
"description": "Tiny millisecond conversion utility",
|
||||
"devDependencies": {
|
||||
"eslint": "4.12.1",
|
||||
"expect.js": "0.3.1",
|
||||
"husky": "0.14.3",
|
||||
"lint-staged": "5.0.0",
|
||||
"mocha": "4.0.1"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "eslint:recommended",
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"index.js"
|
||||
],
|
||||
"homepage": "https://github.com/zeit/ms#readme",
|
||||
"license": "MIT",
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
"npm run lint",
|
||||
"prettier --single-quote --write",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"main": "./index",
|
||||
"name": "ms",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/zeit/ms.git"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint lib/* bin/*",
|
||||
"precommit": "lint-staged",
|
||||
"test": "mocha tests.js"
|
||||
},
|
||||
"version": "2.1.2"
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
# ms
|
||||
|
||||
[](https://travis-ci.org/zeit/ms)
|
||||
[](https://spectrum.chat/zeit)
|
||||
|
||||
Use this package to easily convert various time formats to milliseconds.
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
ms('2 days') // 172800000
|
||||
ms('1d') // 86400000
|
||||
ms('10h') // 36000000
|
||||
ms('2.5 hrs') // 9000000
|
||||
ms('2h') // 7200000
|
||||
ms('1m') // 60000
|
||||
ms('5s') // 5000
|
||||
ms('1y') // 31557600000
|
||||
ms('100') // 100
|
||||
ms('-3 days') // -259200000
|
||||
ms('-1h') // -3600000
|
||||
ms('-200') // -200
|
||||
```
|
||||
|
||||
### Convert from Milliseconds
|
||||
|
||||
```js
|
||||
ms(60000) // "1m"
|
||||
ms(2 * 60000) // "2m"
|
||||
ms(-3 * 60000) // "-3m"
|
||||
ms(ms('10 hours')) // "10h"
|
||||
```
|
||||
|
||||
### Time Format Written-Out
|
||||
|
||||
```js
|
||||
ms(60000, { long: true }) // "1 minute"
|
||||
ms(2 * 60000, { long: true }) // "2 minutes"
|
||||
ms(-3 * 60000, { long: true }) // "-3 minutes"
|
||||
ms(ms('10 hours'), { long: true }) // "10 hours"
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Works both in [Node.js](https://nodejs.org) and in the browser
|
||||
- If a number is supplied to `ms`, a string with a unit is returned
|
||||
- If a string that contains the number is supplied, it returns it as a number (e.g.: it returns `100` for `'100'`)
|
||||
- If you pass a string with a number and a valid unit, the number of equivalent milliseconds is returned
|
||||
|
||||
## Related Packages
|
||||
|
||||
- [ms.macro](https://github.com/knpwrs/ms.macro) - Run `ms` as a macro at build-time.
|
||||
|
||||
## Caught a Bug?
|
||||
|
||||
1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device
|
||||
2. Link the package to the global module directory: `npm link`
|
||||
3. Within the module you want to test your local development instance of ms, just link it to the dependencies: `npm link ms`. Instead of the default one from npm, Node.js will now use your clone of ms!
|
||||
|
||||
As always, you can run the tests using: `npm test`
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"_from": "sockjs-client@1.4.0",
|
||||
"_id": "sockjs-client@1.4.0",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==",
|
||||
"_location": "/sockjs-client",
|
||||
"_phantomChildren": {
|
||||
"websocket-driver": "0.7.3"
|
||||
},
|
||||
"_requested": {
|
||||
"type": "version",
|
||||
"registry": true,
|
||||
"raw": "sockjs-client@1.4.0",
|
||||
"name": "sockjs-client",
|
||||
"escapedName": "sockjs-client",
|
||||
"rawSpec": "1.4.0",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "1.4.0"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/webpack-dev-server"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz",
|
||||
"_shasum": "c9f2568e19c8fd8173b4997ea3420e0bb306c7d5",
|
||||
"_spec": "sockjs-client@1.4.0",
|
||||
"_where": "O:\\zero\\zero.Web\\node_modules\\webpack-dev-server",
|
||||
"author": {
|
||||
"name": "Bryce Kahle"
|
||||
},
|
||||
"browser": {
|
||||
"./lib/transport/driver/websocket.js": "./lib/transport/browser/websocket.js",
|
||||
"eventsource": "./lib/transport/browser/eventsource.js",
|
||||
"./lib/transport/driver/xhr.js": "./lib/transport/browser/abstract-xhr.js",
|
||||
"crypto": "./lib/utils/browser-crypto.js",
|
||||
"events": "./lib/event/emitter.js"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/sockjs/sockjs-client/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Bryce Kahle",
|
||||
"email": "bkahle@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Marek Majkowski",
|
||||
"email": "deadbeef@popcount.org"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"debug": "^3.2.5",
|
||||
"eventsource": "^1.0.7",
|
||||
"faye-websocket": "~0.11.1",
|
||||
"inherits": "^2.0.3",
|
||||
"json3": "^3.3.2",
|
||||
"url-parse": "^1.4.3"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "SockJS-client is a browser JavaScript library that provides a WebSocket-like object.",
|
||||
"devDependencies": {
|
||||
"browserify": "^16.2.3",
|
||||
"envify": "^4.0.0",
|
||||
"eslint": "^6.2.1",
|
||||
"expect.js": "~0.3.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-header": "^2.0.5",
|
||||
"gulp-rename": "^1.4.0",
|
||||
"gulp-replace": "^1.0.0",
|
||||
"gulp-sourcemaps": "^2.6.0",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"karma": "^4.2.0",
|
||||
"karma-browserify": "^6.1.0",
|
||||
"karma-browserstack-launcher": "^1.5.1",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-mocha": "^1.3.0",
|
||||
"mocha": "^5.2.0",
|
||||
"node-static": "^0.7.6",
|
||||
"proxyquire": "^2.1.0",
|
||||
"pump": "^3.0.0",
|
||||
"sockjs": "^0.3.17",
|
||||
"vinyl-buffer": "~1.0.0",
|
||||
"vinyl-source-stream": "^2.0.0"
|
||||
},
|
||||
"homepage": "http://sockjs.org",
|
||||
"jsdelivr": "dist/sockjs.min.js",
|
||||
"keywords": [
|
||||
"websockets",
|
||||
"websocket"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "./lib/entry.js",
|
||||
"name": "sockjs-client",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/sockjs/sockjs-client.git"
|
||||
},
|
||||
"scripts": {
|
||||
"gulp": "gulp",
|
||||
"lint": "eslint .",
|
||||
"postpublish": "git push origin --all && git push origin --tags",
|
||||
"postversion": "npm publish",
|
||||
"test": "mocha tests/node.js",
|
||||
"test:browser_local": "npm run test:bundle && npx karma start --browsers Chrome",
|
||||
"test:browser_remote": "npm run test:bundle && npx karma start",
|
||||
"test:bundle": "gulp testbundle",
|
||||
"version": "gulp release && git add -A dist lib/version.js Changelog.md"
|
||||
},
|
||||
"version": "1.4.0"
|
||||
}
|
||||
Reference in New Issue
Block a user