Template Upload

This commit is contained in:
SOUTHERNCO\x2mjbyrn
2017-05-17 13:45:25 -04:00
parent 415b9c25f3
commit 7efe7605b8
11476 changed files with 2170865 additions and 34 deletions

220
node_modules/lite-server/.eslintrc generated vendored Normal file
View File

@ -0,0 +1,220 @@
{
"env": {
"node": true,
"es6": true
},
"extends": [
"eslint:recommended"
],
"rules": {
"accessor-pairs": "error",
"array-bracket-spacing": [
"error",
"never"
],
"array-callback-return": "error",
"arrow-body-style": "error",
"arrow-parens": "error",
"arrow-spacing": "error",
"block-scoped-var": "error",
"block-spacing": "error",
"brace-style": [
"error",
"1tbs"
],
"callback-return": "error",
"camelcase": "error",
"comma-spacing": [
"error",
{
"after": true,
"before": false
}
],
"comma-style": [
"error",
"last"
],
"complexity": ["error", 20],
"computed-property-spacing": "error",
"consistent-return": "error",
"consistent-this": "error",
"curly": "error",
"default-case": "error",
"dot-location": "error",
"dot-notation": "error",
"eol-last": "error",
"eqeqeq": "error",
"func-names": "error",
"func-style": "off",
"generator-star-spacing": "error",
"global-require": "off",
"guard-for-in": "error",
"handle-callback-err": "error",
"id-blacklist": "error",
"id-length": "off",
"id-match": "error",
"indent": ["error", 2],
"init-declarations": "off",
"jsx-quotes": "error",
"key-spacing": "error",
"keyword-spacing": [
"error",
{
"after": true,
"before": true
}
],
"linebreak-style": [
"error",
"unix"
],
"lines-around-comment": "off",
"max-depth": "error",
"max-len": [
2,
{
"code": 100,
"tabWidth": 2,
"ignoreUrls": true
}
],
"max-nested-callbacks": "error",
"max-params": "error",
"max-statements": "off",
"new-cap": "error",
"new-parens": "error",
"newline-after-var": "off",
"newline-before-return": "error",
"newline-per-chained-call": "error",
"no-alert": "error",
"no-array-constructor": "error",
"no-bitwise": "error",
"no-caller": "error",
"no-catch-shadow": "error",
"no-confusing-arrow": "error",
"no-continue": "error",
"no-div-regex": "error",
"no-else-return": "error",
"no-empty-function": "off",
"no-eq-null": "error",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-extra-label": "error",
"no-extra-parens": "off",
"no-floating-decimal": "error",
"no-implicit-coercion": "error",
"no-implicit-globals": "error",
"no-implied-eval": "error",
"no-inline-comments": "off",
"no-invalid-this": "error",
"no-iterator": "error",
"no-label-var": "error",
"no-labels": "error",
"no-lone-blocks": "error",
"no-lonely-if": "error",
"no-loop-func": "error",
"no-magic-numbers": "off",
"no-mixed-requires": "error",
"no-multi-spaces": "error",
"no-multi-str": "error",
"no-multiple-empty-lines": "error",
"no-native-reassign": "error",
"no-negated-condition": "error",
"no-nested-ternary": "error",
"no-new": "error",
"no-new-func": "error",
"no-new-object": "error",
"no-new-require": "error",
"no-new-wrappers": "error",
"no-octal-escape": "error",
"no-param-reassign": "off",
"no-path-concat": "error",
"no-plusplus": "error",
"no-process-env": "error",
"no-process-exit": "error",
"no-proto": "error",
"no-restricted-globals": "error",
"no-restricted-imports": "error",
"no-restricted-modules": "error",
"no-restricted-syntax": "error",
"no-return-assign": "error",
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-shadow": "error",
"no-shadow-restricted-names": "error",
"no-spaced-func": "error",
"no-sync": "error",
"no-ternary": "error",
"no-throw-literal": "error",
"no-trailing-spaces": "error",
"no-undef-init": "error",
"no-undefined": "error",
"no-underscore-dangle": "error",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": "error",
"no-unused-expressions": "error",
"no-use-before-define": "off",
"no-useless-call": "error",
"no-useless-concat": "error",
"no-useless-constructor": "error",
"no-var": "off",
"no-void": "error",
"no-warning-comments": "error",
"no-whitespace-before-property": "error",
"no-with": "error",
"object-curly-spacing": [
"error",
"always"
],
"object-shorthand": "error",
"one-var": "off",
"one-var-declaration-per-line": "error",
"operator-assignment": "error",
"operator-linebreak": "error",
"padded-blocks": "off",
"prefer-arrow-callback": "error",
"prefer-const": "error",
"prefer-reflect": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"prefer-template": "error",
"quote-props": "off",
"quotes": [
"error",
"single"
],
"radix": "error",
"require-jsdoc": "off",
"require-yield": "error",
"semi": "error",
"semi-spacing": "error",
"sort-imports": "error",
"sort-vars": "error",
"space-before-blocks": "error",
"space-before-function-paren": "off",
"space-in-parens": [
"error",
"never"
],
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": [
"error",
"always"
],
"strict": "error",
"template-curly-spacing": "error",
"valid-jsdoc": "error",
"vars-on-top": "off",
"wrap-iife": "error",
"wrap-regex": "error",
"yield-star-spacing": "error",
"yoda": [
"error",
"never"
]
}
}

4
node_modules/lite-server/.npmignore generated vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules
.idea
coverage/
*.log

5
node_modules/lite-server/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,5 @@
language: node_js
node_js: stable
script:
- npm test

19
node_modules/lite-server/ISSUE_TEMPLATE.md generated vendored Normal file
View File

@ -0,0 +1,19 @@
Before you open an issue, please check if a similar issue already exists or has been closed before.
### A descriptive title
The behavior you expect to see, and the actual behavior...
For feature requests, a description of the problem you're trying to solve, including *why* you think this is a problem.
### Bug repro steps
Please give us an isolated way to reproduce the behavior (example: GitHub repository with code that anyone can clone to observe the problem, or a Dockerfile that replicates your environment):
1.
2.
3.
### Environment
- `lite-server` version:
- `nodejs` version:
- `npm` version:
- OS type/version:

21
node_modules/lite-server/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-2016 John Papa
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.

171
node_modules/lite-server/README.md generated vendored Normal file
View File

@ -0,0 +1,171 @@
# lite-server
Lightweight *development only* node server that serves a web app, opens it in the browser, refreshes when html or javascript change, injects CSS changes using sockets, and has a fallback page when a route is not found.
[![Dependency Status](https://david-dm.org/johnpapa/lite-server.svg)](https://david-dm.org/johnpapa/lite-server)
[![npm version](https://badge.fury.io/js/lite-server.svg)](http://badge.fury.io/js/lite-server)
[![Build Status](https://travis-ci.org/johnpapa/lite-server.svg?branch=master)](https://travis-ci.org/johnpapa/lite-server)
## Why
BrowserSync does most of what we want in a super fast lightweight development server. It serves the static content, detects changes, refreshes the browser, and offers many customizations.
When creating a SPA there are routes that are only known to the browser. For example, `/customer/21` may be a client side route for an Angular app. If this route is entered manually or linked to directly as the entry point of the Angular app (aka a deep link) the static server will receive the request, because Angular is not loaded yet. The server will not find a match for the route and thus return a 404. The desired behavior in this case is to return the `index.html` (or whatever starting page of the app we have defined). BrowserSync does not automatically allow for a fallback page. But it does allow for custom middleware. This is where `lite-server` steps in.
`lite-server` is a simple customized wrapper around BrowserSync to make it easy to serve SPAs.
## Installation and Usage
The recommended installation method is a local NPM install for your project:
```bash
$ npm install lite-server --save-dev
$ yarn add lite-server --dev # or yarn
```
...and add a "script" entry within your project's `package.json` file:
```json
# Inside package.json...
"scripts": {
"dev": "lite-server"
},
```
With the above script entry, you can then start `lite-server` via:
```bash
$ npm run dev
```
Other options for running locally installed NPM binaries is discussed in this Stack Overflow question: [How to use package installed locally in node_modules](http://stackoverflow.com/q/9679932)
### Global Installation
lite-server can be also installed globally, if preferred:
```bash
$ npm install -g lite-server
# To run:
$ lite-server
```
## Custom Configuration
The default behavior serves from the current folder, opens a browser, and applies a HTML5 route fallback to `./index.html`.
lite-server uses [BrowserSync](https://www.browsersync.io/), and allows for configuration overrides via a local `bs-config.json` or `bs-config.js` file in your project.
You can provide custom path to your config file via `-c` or `--config=` run time options:
```bash
lite-server -c configs/my-bs-config.js
```
For example, to change the server port, watched file paths, and base directory for your project, create a `bs-config.json` in your project's folder:
```json
{
"port": 8000,
"files": ["./src/**/*.{html,htm,css,js}"],
"server": { "baseDir": "./src" }
}
```
You can also provide custom path to your base directory `--baseDir=` run time options:
```bash
lite-server --baseDir="dist"
```
A more complicated example with modifications to the server middleware can be done with a `bs-config.js` file, which requires the `module.exports = { ... };` syntax:
```js
module.exports = {
server: {
middleware: {
// overrides the second middleware default with new settings
1: require('connect-history-api-fallback')({index: '/index.html', verbose: true})
}
}
};
```
The `bs-config.js` file may also export a function that receives the lite-server Browsersync instance as its only argument. While not required, the return value of this function will be used to extend the default lite-server configuration.
```js
module.exports = function(bs) {
return {
server: {
middleware: {
// overrides the second middleware default with new settings
1: require('connect-history-api-fallback')({
index: '/index.html',
verbose: true
})
}
}
};
};
```
**NOTE:** Keep in mind that when using middleware overrides the specific middleware module must be installed in your project. For the above example, you'll need to do:
```bash
$ npm install connect-history-api-fallback --save-dev
```
...otherwise you'll get an error similar to:
```
Error: Cannot find module 'connect-history-api-fallback'
```
Another example: To remove one of the [default middlewares](./lib/config-defaults.js), such as `connect-logger`, you can set it's array index to `null`:
```js
module.exports = {
server: {
middleware: {
0: null // removes default `connect-logger` middleware
}
}
};
```
A list of the entire set of BrowserSync options can be found in its docs: <http://www.browsersync.io/docs/options/>
## Testing
When using `lite-server` to run end to end tests, we may not want to log verbosely. We may also want to prevent the browser from opening. These options in the `bs-config.js` will silence all logging from `lite-server`:
```js
open: false
logLevel: "silent",
server: {
middleware: {
0: null
}
}
```
## Known Issues
CSS with Angular 2 is embedded thus even though BrowserSync detects the file change to CSS, it does not inject the file via sockets. As a workaround, `injectChanges` defaults to `false`.
## Contributing
1. Fork and clone it
1. Install dependencies: `npm install`
1. Create a feature branch: `git checkout -b new-feature`
1. Commit changes: `git commit -am 'Added a feature'`
1. Run static code analysis and unit tests: `npm test`
1. Push to the remote branch: `git push origin new-feature`
1. Create a new [Pull Request](https://github.com/johnpapa/lite-server/pull/new/master)
## License
Code released under the [MIT license](./LICENSE).

7
node_modules/lite-server/bin/lite-server generated vendored Normal file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env node
'use strict';
// Provide a title to the process in `ps`
process.title = 'lite-server';
require('../lib/lite-server')();

5
node_modules/lite-server/index.js generated vendored Normal file
View File

@ -0,0 +1,5 @@
'use strict';
module.exports = {
server: require('./lib/lite-server.js'),
defaults: require('./lib/config-defaults')
};

24
node_modules/lite-server/lib/config-defaults.js generated vendored Normal file
View File

@ -0,0 +1,24 @@
'use strict';
var fallback = require('connect-history-api-fallback');
var log = require('connect-logger');
/*
| For up-to-date information about the options:
| http://www.browsersync.io/docs/options/
*/
module.exports = {
injectChanges: false, // workaround for Angular 2 styleUrls loading
files: ['./**/*.{html,htm,css,js}'],
watchOptions: {
ignored: 'node_modules'
},
server: {
baseDir: './',
middleware: [
log({ format: '%date %status %method %url' }),
fallback({
index: '/index.html',
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'] // systemjs workaround
})
]
}
};

78
node_modules/lite-server/lib/lite-server.js generated vendored Normal file
View File

@ -0,0 +1,78 @@
'use strict';
/**
* lite-server : Simple server for angular/SPA projects
*
* Simply loads some default browser-sync config that apply to SPAs,
* applies custom config overrides from user's own local `bs-config.{js|json}` file,
* and launches browser-sync.
*/
var browserSync = require('browser-sync').create('lite-server');
var path = require('path');
var _ = require('lodash');
var config = require('./config-defaults');
module.exports = function start(opts, cb) {
opts = opts || {};
opts.argv = opts.argv || process.argv;
opts.console = opts.console || console;
cb = cb || function noop() { };
// Load configuration
var argv = require('minimist')(opts.argv.slice(2));
var bsConfigName = argv.c || argv.config || 'bs-config';
// Load optional browser-sync config file from user's project dir
var bsConfigPath = path.resolve(bsConfigName);
var overrides = {};
try {
overrides = require(bsConfigPath);
} catch (err) {
if (err.code && err.code === 'MODULE_NOT_FOUND') {
logMissingConfigFile();
} else {
throw (err);
}
}
// Set optional baseDir
config.server.baseDir = argv.baseDir || config.server.baseDir;
if (typeof overrides === 'function') {
overrides = overrides(browserSync);
}
_.merge(config, overrides);
// Fixes browsersync error when overriding middleware array
if (config.server.middleware) {
config.server.middleware = _.compact(config.server.middleware);
}
logConfig();
// Run browser-sync
browserSync.init(config, cb);
return browserSync;
function logEnabled() {
return config.logLevel !== 'silent';
}
function logConfig() {
if (logEnabled()) {
opts.console.log('** browser-sync config **');
opts.console.log(config);
}
}
function logMissingConfigFile() {
if (logEnabled()) {
opts.console.info(
'Did not detect a `bs-config.json` or `bs-config.js` override file.' +
' Using lite-server defaults...'
);
}
}
};

View File

@ -0,0 +1,15 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/../browser-sync/bin/browser-sync.js" "$@"
ret=$?
else
node "$basedir/../browser-sync/bin/browser-sync.js" "$@"
ret=$?
fi
exit $ret

View File

@ -0,0 +1,7 @@
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\browser-sync\bin\browser-sync.js" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\browser-sync\bin\browser-sync.js" %*
)

View File

@ -0,0 +1,356 @@
<a name="2.8.2"></a>
## 2.8.2 (2015-07-31)
### Bug Fixes
* **https:** add newly generated ssl self-signed certs that will expire for 10 years - fixes ([45104a7](https://github.com/browsersync/browser-sync/commit/45104a7)), closes [#750](https://github.com/browsersync/browser-sync/issues/750)
<a name="2.8.1"></a>
## 2.8.1 (2015-07-28)
### Bug Fixes
* **web-sockets:** Use separate server for web sockets in proxy mode - fixes #625 ([40017b4](https://github.com/browsersync/browser-sync/commit/40017b4)), closes [#625](https://github.com/browsersync/browser-sync/issues/625)
### Features
* **serve-static:** Added option `serveStatic` to allow proxy/snippet mode to easily serve local fil ([384ef67](https://github.com/browsersync/browser-sync/commit/384ef67))
<a name="2.7.13"></a>
## 2.7.13 (2015-06-28)
### Bug Fixes
* **snippet:** Allow async attribute to be removed from snippet with snippetOptions.async = fal ([c32bec6](https://github.com/browsersync/browser-sync/commit/c32bec6)), closes [#670](https://github.com/browsersync/browser-sync/issues/670)
* **socket-options:** allow socket.domain string|fn for setting domain only on socket path - fixes #69 ([5157432](https://github.com/browsersync/browser-sync/commit/5157432)), closes [#690](https://github.com/browsersync/browser-sync/issues/690)
### Features
* **api:** expose sockets to public api ([985682c](https://github.com/browsersync/browser-sync/commit/985682c))
<a name="2.7.12"></a>
## 2.7.12 (2015-06-17)
### Bug Fixes
* **client-script:** allow proxy to also use client script middleware ([c5fdbbf](https://github.com/browsersync/browser-sync/commit/c5fdbbf))
* **client-script:** serve cached/gzipped client JS file - fixes #657 ([dbe9ffe](https://github.com/browsersync/browser-sync/commit/dbe9ffe)), closes [#657](https://github.com/browsersync/browser-sync/issues/657)
<a name"2.7.11"></a>
### 2.7.11 (2015-06-16)
#### Bug Fixes
* **client-scroll:** add scrollRestoreTechnique option as alternative to cookie for restoring scroll ([7897ea6a](https://github.com/Browsersync/browser-sync/commit/7897ea6a), closes [#630](https://github.com/Browsersync/browser-sync/issues/630))
<a name"2.7.9"></a>
### 2.7.9 (2015-06-11)
#### Bug Fixes
* **cli:** Remove --exclude flag - ([133aa1a6](https://github.com/Browsersync/browser-sync/commit/133aa1a6), closes [#667](https://github.com/Browsersync/browser-sync/issues/667))
* **proxy:** only rewrite domains within attributes (via foxy bump to 11.0.2) - ([d80d9481](https://github.com/Browsersync/browser-sync/commit/d80d9481), closes [#647](https://github.com/Browsersync/browser-sync/issues/647))
<a name"2.7.7"></a>
### 2.7.7 (2015-06-09)
#### Bug Fixes
* **plugins:** Allow plugins to register middleware via server:middleware hook when in proxy mo ([104dbb4a](https://github.com/Browsersync/browser-sync/commit/104dbb4a), closes [#663](https://github.com/Browsersync/browser-sync/issues/663))
<a name"2.7.6"></a>
### 2.7.6 (2015-05-28)
#### Bug Fixes
* **plugins:** allow module references in options.plugins array - ([aabc03c8](https://github.com/Browsersync/browser-sync/commit/aabc03c8), closes [#648](https://github.com/Browsersync/browser-sync/issues/648))
<a name"2.7.5"></a>
### 2.7.5 (2015-05-26)
#### Bug Fixes
* **file-watcher:** defer to default callback should `fn` property be absent from file watching obje ([9f826cbe](https://github.com/Browsersync/browser-sync/commit/9f826cbe), closes [#643](https://github.com/Browsersync/browser-sync/issues/643))
<a name"2.7.3"></a>
### 2.7.3 (2015-05-24)
#### Bug Fixes
* **file-watching:** bind public running instance to watch callbacks given in options - ([d7c96e4f](https://github.com/Browsersync/browser-sync/commit/d7c96e4f), closes [#631](https://github.com/Browsersync/browser-sync/issues/631))
* **snippet:** Bump resp-modifier to allow more flexible whitelist/blacklist paths for snippet ([f09c2797](https://github.com/Browsersync/browser-sync/commit/f09c2797), closes [#553](https://github.com/Browsersync/browser-sync/issues/553))
#### Features
* **rewrite-rules:** enable live updating of rewrite rules for both static server & proxy ([a4e2bf6f](https://github.com/Browsersync/browser-sync/commit/a4e2bf6f))
<a name"2.7.1"></a>
### 2.7.1 (2015-05-06)
#### Bug Fixes
* **web-sockets:** revert handling upgrade event on proxy as it causes regression fix #606 ([1c6b1c03](https://github.com/Browsersync/browser-sync/commit/1c6b1c03))
<a name"2.6.8"></a>
### 2.6.8 (2015-04-29)
#### Bug Fixes
* **cli:** Allow absolute paths for config file - ([8fcd9048](https://github.com/Browsersync/browser-sync/commit/8fcd9048), closes [#583](https://github.com/Browsersync/browser-sync/issues/583))
<a name"2.6.5"></a>
### 2.6.5 (2015-04-25)
#### Bug Fixes
* **file-watching:** use canLogFileChange() to determine whether file:reload, stream:changed & browse ([164154ea](https://github.com/Browsersync/browser-sync/commit/164154ea), closes [#479](https://github.com/Browsersync/browser-sync/issues/479))
<a name"2.6.1"></a>
### 2.6.1 (2015-04-13)
#### Bug Fixes
* **stream:** Allow deprecated .reload({stream: true}) when no instance running, closes [#573](https://github.com/Browsersync/browser-sync/issues/573)
<a name"2.6.0"></a>
## 2.6.0 (2015-04-12)
#### Bug Fixes
* **open:** Allow open: 'ui' and open: 'ui-external' when in snippet mode - ([d0333582](https://github.com/Browsersync/browser-sync/commit/d0333582), closes [#571](https://github.com/Browsersync/browser-sync/issues/571))
* **server:** set index correctly if serveStaticOptions: {index: <path>} given ([34816a76](https://github.com/Browsersync/browser-sync/commit/34816a76))
#### Features
* **cli:** allow 'browser' option from cli - ([ca517d03](https://github.com/Browsersync/browser-sync/commit/ca517d03), closes [#552](https://github.com/Browsersync/browser-sync/issues/552))
* **client:** Bump client to allow wildcards in reload method - ([1e4de8f7](https://github.com/Browsersync/browser-sync/commit/1e4de8f7), closes [#572](https://github.com/Browsersync/browser-sync/issues/572))
* **commands:** Add reload command for http-protocol comms ([c0fe70dc](https://github.com/Browsersync/browser-sync/commit/c0fe70dc))
* **file-watcher:** Add `.watch()` to public api ([6a2609f0](https://github.com/Browsersync/browser-sync/commit/6a2609f0))
* **http-protocol:**
* Add support for https comms ([efd4f39c](https://github.com/Browsersync/browser-sync/commit/efd4f39c))
* Add reload method to http protocol ([f6a3601f](https://github.com/Browsersync/browser-sync/commit/f6a3601f))
* **plugins:** Accept object literal as plugin + options ([757f492e](https://github.com/Browsersync/browser-sync/commit/757f492e))
* **reload:** Add reload-delay and reload-debounce to cli -, ([38d62c96](https://github.com/Browsersync/browser-sync/commit/38d62c96), closes [#329](https://github.com/Browsersync/browser-sync/issues/329), [#562](https://github.com/Browsersync/browser-sync/issues/562))
* **stream:** Implement dedicated `.stream()` method for better handling streams & to pave the ([2581e7a1](https://github.com/Browsersync/browser-sync/commit/2581e7a1))
* **watchers:**
* Allow per-watcher options hash. ([3c069fba](https://github.com/Browsersync/browser-sync/commit/3c069fba))
* switch to chokidar for file-watching, implement callback interface on per-patter ([14afddfc](https://github.com/Browsersync/browser-sync/commit/14afddfc))
<a name"2.5.1"></a>
### 2.5.1 (2015-03-31)
#### Bug Fixes
* **proxy:** Bump foxy dep to ensure middlewares work correctly for old IEs ([104e9dd1](https://github.com/Browsersync/browser-sync/commit/104e9dd1))
* **snippet:** Log UI access urls when in snippet mode ([c448fa0b](https://github.com/Browsersync/browser-sync/commit/c448fa0b))
<a name"2.5.0"></a>
## 2.5.0 (2015-03-29)
#### Bug Fixes
* **proxy:** Bump Foxy to stop cookies being altered when parsed ([ff3c46bd](https://github.com/Browsersync/browser-sync/commit/ff3c46bd))
#### Features
* **options:** Allow any serve-static specific configuration under new property - ([4c58541f](https://github.com/Browsersync/browser-sync/commit/4c58541f), closes [#539](https://github.com/Browsersync/browser-sync/issues/539))
* **throttle:** Bump UI for network throttle ([7e2f588e](https://github.com/Browsersync/browser-sync/commit/7e2f588e))
<a name"2.4.0"></a>
## 2.4.0 (2015-03-21)
#### Features
* **rewrite:** Allow addtional HTML rewrite rules through server + proxy modes to help with #51 ([76ae686d](https://github.com/Browsersync/browser-sync/commit/76ae686d))
<a name"2.3.2"></a>
### 2.3.2 (2015-03-21)
#### Bug Fixes
* **client:** Bump UI to fix safari deprecated error messages - fix #445 ([6bb7513c](https://github.com/Browsersync/browser-sync/commit/6bb7513c))
<a name"2.2.5"></a>
### 2.2.5 (2015-03-17)
#### Features
* **plugins:** Allow plugins to be given inline within options hash ([fd4ccd9e](https://github.com/Browsersync/browser-sync/commit/fd4ccd9e))
### 2.2.4 (2015-03-13)
#### Bug Fixes
* **reload:** Allow multiple instances to call their own `.reload()` method - ([da53dc21](https://github.com/Browsersync/browser-sync/commit/da53dc21c6f7afd801a9f00489a6df2ab46156bb), closes [#511](https://github.com/Browsersync/browser-sync/issues/511))
### 2.2.3 (2015-03-08)
#### Bug Fixes
* **socket:** Set heartbeat interval correctly - ([7621c0de](https://github.com/Browsersync/browser-sync/commit/7621c0dece1fea6c170ffdc117bd7c67be2138ed), closes [#499](https://github.com/Browsersync/browser-sync/issues/499))
### 2.2.2 (2015-03-04)
#### Bug Fixes
* **paths:** Fix regression with absolute/relative paths to scripts/sockets/https etc - ([2386fe1b](https://github.com/Browsersync/browser-sync/commit/2386fe1bbde175b18211ef9b242b6af0bf11128c), closes [#463](https://github.com/Browsersync/browser-sync/issues/463))
* **snippet:** Allow serving the client js over https when in snippet mode - ([196bafbe](https://github.com/Browsersync/browser-sync/commit/196bafbee2b09c2a1e8b09a988a2c8aa43bac2b9), closes [#459](https://github.com/Browsersync/browser-sync/issues/459))
* **socket:** Bump socket io + client to fix #477 & https://github.com/Browsersync/browser-syn ([659c281e](https://github.com/Browsersync/browser-sync/commit/659c281eac8ab8343a7ba7b13fa532560dc1bd9c))
### 2.1.4 (2015-02-18)
#### Bug Fixes
* **cli:** allow disable injection via cli - ([12ffbd79](https://github.com/Browsersync/browser-sync/commit/12ffbd793443c7ede191aad55bcd530e566f0947), closes [#444](https://github.com/Browsersync/browser-sync/issues/444))
* **snippet:**
* Allow serving the client js over https when in snippet mode - ([196bafbe](https://github.com/Browsersync/browser-sync/commit/196bafbee2b09c2a1e8b09a988a2c8aa43bac2b9), closes [#459](https://github.com/Browsersync/browser-sync/issues/459))
* Allow serving the snippet on secure server + base url - re: #437 ([96d689c0](https://github.com/Browsersync/browser-sync/commit/96d689c0830975dbf2baee5aaaaa396415052512))
* Always use full url path for scripts - ([14bd6f51](https://github.com/Browsersync/browser-sync/commit/14bd6f5126c52228a0ed306a118feac0e65c50db), closes [#437](https://github.com/Browsersync/browser-sync/issues/437))
## 2.1.0 (2015-02-16)
#### Features
* **https:** Add HTTPS proxying - re: #399 ([09dbca6e](https://github.com/Browsersync/browser-sync/commit/09dbca6e3e60fa699ca2519d56ada3cbd5a2237b))
* **proxy:** Allow user-specified proxy request headers ([0c303a7e](https://github.com/Browsersync/browser-sync/commit/0c303a7e4a8bafa554d42c6895698b7338d036f4), closes [#430](https://github.com/Browsersync/browser-sync/issues/430))
### 2.0.1 (2015-02-10)
#### Bug Fixes
* **cli:**
* Ensure server options are merged from command line ([8d677328](https://github.com/Browsersync/browser-sync/commit/8d677328a779502ba6f6e16b74f125dc2caeaf92))
* explode files args when given on command line., ([18324f0a](https://github.com/Browsersync/browser-sync/commit/18324f0a7b4d3c49bd16800a7ba77cf13ea2449a), closes [#425](https://github.com/Browsersync/browser-sync/issues/425), [#426](https://github.com/Browsersync/browser-sync/issues/426))
* Don't double-merge cli options, re: #417 ([057d97f3](https://github.com/Browsersync/browser-sync/commit/057d97f35786f120bc2057c884c80c5ce95aaf79))
* **https:** Ensure HTTPS option is used in legacy mode + top level, re: #427 ([799c0a59](https://github.com/Browsersync/browser-sync/commit/799c0a59cd152eb11e6f8e66a1d3adcf082624f7))
* **proxy:**
* use path as startPath if given as proxy option ([f4ac4c59](https://github.com/Browsersync/browser-sync/commit/f4ac4c595a479b44676824cdbdaa34cc1dc9d966))
* Bump Foxy module to fix issues with redirects, ([e5d8fe18](https://github.com/Browsersync/browser-sync/commit/e5d8fe180bfd46f1380ec1f532d81f62f2f6ab11), closes [#381](https://github.com/Browsersync/browser-sync/issues/381))
* **reload:** Bump browser-sync-client fix ##369 ([9bcf1086](https://github.com/Browsersync/browser-sync/commit/9bcf108694f0e51bafc3bd6d0a584781e2950f68))
* **stream:** Don't log file info when once: true - fixes https://github.com/google/web-starter-kit/issues/593 ([8f4d7275](https://github.com/Browsersync/browser-sync/commit/8f4d7275d4dfa6e22dec4b87d19b3be51bab8af3))
#### Features
* **core:** Use immutable data internally to enable advanced features needed in upcoming UI ([b5d6d9c1](https://github.com/Browsersync/browser-sync/commit/b5d6d9c1866cf8451cf235dc3bca674af9e6d767))
* **options:**
* Allow silent setting of options ([31e196a0](https://github.com/Browsersync/browser-sync/commit/31e196a0e900356cf5cbb9b1e8a4c3202011d01e))
* added reloadOnRestart option - https://github.com/shakyShane/browser-sync/issues ([b1bcfa81](https://github.com/Browsersync/browser-sync/commit/b1bcfa81638b1f99fed7d71ee051c00ceebaf8f9))
* **server:** add serveFile method for plugin use ([c5007871](https://github.com/Browsersync/browser-sync/commit/c50078717f291f3cb301b0bc315eac1b42f6d7b6))
* **snippet:** Add black/white lists - ([6a2a296e](https://github.com/Browsersync/browser-sync/commit/6a2a296ee05312d56de3ae47f5dfb6e04f877692), closes [#373](https://github.com/Browsersync/browser-sync/issues/373))
* **tunnel:** Switch to ngrok - re: #192 ([7359435c](https://github.com/Browsersync/browser-sync/commit/7359435ca4efd429c9421aa912a036f82d022d82))
### 1.8.2 (2014-12-22)
#### Bug Fixes
* **proxy:** Bump foxy to fix #376 ([fe6c73db](https://github.com/shakyShane/browser-sync/commit/fe6c73db47f82d10ea25b0b8c58b032e972a4663))
#### Features
* **server:** allow to inject browser-sync client.js in custom middlewares ([841c6c31](https://github.com/shakyShane/browser-sync/commit/841c6c31015955ff92cffd937f19f2c78ce27e8d))
### 1.8.1 (2014-12-19)
#### Bug Fixes
* **proxy:** Bump foxy to fix #376 ([284cf84a](https://github.com/shakyShane/browser-sync/commit/284cf84a0390a07d9824972c8ab67ec95cf8109f))
### 1.7.3 (2014-12-16)
#### Features
* **files:** pause/resume ([a3c697f6](https://github.com/shakyShane/browser-sync/commit/a3c697f66b4fcec3966ed77a841e55aafb70f69a))
### 1.6.5 (2014-11-16)
#### Bug Fixes
* **snippet:** Add snippet.ignorePaths option - ([dd9b284b](https://github.com/shakyShane/browser-sync/commit/dd9b284b47f01884996619c012f134c982639b8c), closes [#330](https://github.com/shakyShane/browser-sync/issues/330))
#### Features
* **snippet:** Allow user-provided rule for writing the snippet ([33c4586d](https://github.com/shakyShane/browser-sync/commit/33c4586dce26a4c9672b99d14d29adb064dac6ec))
### 1.6.4 (2014-11-08)
#### Bug Fixes
* **proxy:** Bump Foxy to fix issues with redirects ([e2f30be2](https://github.com/shakyShane/browser-sync/commit/e2f30be2269629a96503ea487b5248ab3b6918ab))
### 1.6.2 (2014-11-02)
#### Bug Fixes
* **options:** Ignore cli options from public api usage fix #314 ([1de4a3b0](https://github.com/shakyShane/browser-sync/commit/1de4a3b06cd888345aa5130a03cad070b1f5b466))

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2015] [Shane Osbourne]
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.

View File

@ -0,0 +1,79 @@
<p align="center">
<a href="https://ci.appveyor.com/project/shakyShane/browser-sync" title="AppVeyor branch">
<img src="https://img.shields.io/appveyor/ci/shakyshane/browser-sync/master.svg?style=flat-square&label=windows" />
</a>
<a href="https://travis-ci.org/BrowserSync/browser-sync" title="Travis branch">
<img src="https://img.shields.io/travis/BrowserSync/browser-sync/master.svg?style=flat-square&label=linux" />
</a>
<a href="https://coveralls.io/r/BrowserSync/browser-sync?branch=master" title="Coverage Status">
<img src="https://img.shields.io/coveralls/BrowserSync/browser-sync.svg?style=flat-square" />
</a>
<a href="https://www.npmjs.com/package/browser-sync">
<img src="https://img.shields.io/npm/dm/browser-sync.svg?style=flat-square" />
</a>
</p>
<p align="center">
<a href="https://www.npmjs.com/package/browser-sync" title="NPM version">
<img src="https://img.shields.io/npm/v/browser-sync.svg?style=flat-square" />
</a>
<a href="https://david-dm.org/Browsersync/browser-sync" title="Dependency Status">
<img src="https://img.shields.io/david/Browsersync/browser-sync.svg?style=flat-square&label=deps" />
</a>
<a href="https://david-dm.org/Browsersync/browser-sync#info=devDependencies" title="devDependency Status">
<img src="https://img.shields.io/david/dev/Browsersync/browser-sync.svg?style=flat-square&label=devDeps" />
</a>
</p>
<p align="center"><a href="http://www.browsersync.io"><img src="https://raw.githubusercontent.com/BrowserSync/browsersync.github.io/master/public/img/logo-gh.png" /></a></p>
<p align="center">Keep multiple browsers & devices in sync when building websites.</p>
<p align="center">Browsersync is developed and maintained internally at <a href="http://www.wearejh.com">JH</a></p>
<p align="center">Follow <a href="https://twitter.com/browsersync">@Browsersync</a> on twitter for news & updates.</p>
<p align="center">Community <a href="https://browsersync.herokuapp.com"><img src="https://browsersync.herokuapp.com/badge.svg" /></a></p>
## Features
Please visit [browsersync.io](http://browsersync.io) for a full run-down of features
## Requirements
Browsersync works by injecting an asynchronous script tag (`<script async>...</script>`) right after the `<body>` tag
during initial request. In order for this to work properly the `<body>` tag must be present. Alternatively you
can provide a custom rule for the snippet using [snippetOptions](http://www.browsersync.io/docs/options/#option-snippetOptions)
## Upgrading from 1.x to 2.x ?
Providing you haven't accessed any internal properties, everything will just work as
there are no breaking changes to the public API. Internally however, we now use an
immutable data structure for storing/retrieving options. So whereas before you could access urls like this...
```js
browserSync({server: true}, function(err, bs) {
console.log(bs.options.urls.local);
});
```
... you now access them in the following way:
```js
browserSync({server: true}, function(err, bs) {
console.log(bs.options.getIn(["urls", "local"]));
});
```
## Install and trouble shooting
[browsersync.io docs](http://browsersync.io)
## Integrations / recipes
[Browsersync recipes](https://github.com/Browsersync/recipes)
## Support
If you've found Browser-sync useful and would like to contribute to its continued development & support, please feel free to send a donation of any size - it would be greatly appreciated!
[![Support via Gittip](https://rawgithub.com/chris---/Donation-Badges/master/gittip.jpeg)](https://www.gittip.com/shakyshane)
[![Support via PayPal](https://rawgithub.com/chris---/Donation-Badges/master/paypal.jpeg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=shakyshane%40gmail%2ecom&lc=US&item_name=browser%2dsync)
Apache 2
Copyright (c) 2016 Shane Osbourne

View File

@ -0,0 +1,91 @@
#!/usr/bin/env node
var startOpts = require("../lib/cli/opts.start.json");
var reloadOpts = require("../lib/cli/opts.reload.json");
var recipeOpts = require("../lib/cli/opts.recipe.json");
var pkg = require("../package.json");
var utils = require("../lib/utils");
/**
* Handle cli input
*/
if (!module.parent) {
var yargs = require("yargs")
.command("start", "Start the server")
.command("init", "Create a configuration file")
.command("reload", "Send a reload event over HTTP protocol")
.command("recipe", "Generate the files for a recipe")
.version(function () {
return pkg.version;
})
.epilogue("For help running a certain command, type <command> --help\neg: $0 start --help");
var argv = yargs.argv;
var command = argv._[0];
var valid = ["start", "init", "reload", "recipe"];
if (valid.indexOf(command) > -1) {
handleIncoming(command, yargs.reset());
} else {
yargs.showHelp();
}
}
/**
* @param {{cli: object, [whitelist]: array, [cb]: function}} opts
* @returns {*}
*/
function handleCli(opts) {
opts.cb = opts.cb || utils.defaultCallback;
return require("../lib/cli/command." + opts.cli.input[0])(opts);
}
module.exports = handleCli;
/**
* @param {string} command
* @param {object} yargs
*/
function handleIncoming(command, yargs) {
var out;
if (command === "start") {
out = yargs
.usage("Usage: $0 start [options]")
.options(startOpts)
.example("$0 start -s app", "- Use the App directory to serve files")
.example("$0 start -p www.bbc.co.uk", "- Proxy an existing website")
.help()
.argv;
}
if (command === "init") {
out = yargs
.usage("Usage: $0 init")
.example("$0 init")
.help()
.argv;
}
if (command === "reload") {
out = yargs
.usage("Usage: $0 reload")
.options(reloadOpts)
.example("$0 reload")
.example("$0 reload --port 4000")
.help()
.argv;
}
if (command === "recipe") {
out = yargs
.usage("Usage: $0 recipe <recipe-name>")
.option(recipeOpts)
.example("$0 recipe ls", "list the recipes")
.example("$0 recipe gulp.sass", "use the gulp.sass recipe")
.help()
.argv;
}
if (out.help) {
return yargs.showHelp();
}
handleCli({cli: {flags: out, input: out._}});
}

View File

@ -0,0 +1,372 @@
#! /usr/bin/env node
"use strict";
/**
* @module BrowserSync
*/
var pjson = require("./package.json");
var BrowserSync = require("./lib/browser-sync");
var publicUtils = require("./lib/public/public-utils");
var events = require("events");
var PassThrough = require("stream").PassThrough;
var logger = require("eazy-logger").Logger({
useLevelPrefixes: true
});
var singleton = false;
var singletonPlugins = [];
var instances = [];
/**
* @type {boolean|EventEmitter}
*/
var singletonEmitter = false;
module.exports = initSingleton;
/**
* Create a Browsersync instance
* @method create
* @param {String} name an identifier that can used for retrieval later
*/
module.exports.create = create;
/**
* Get a single instance by name. This is useful if you have your
* build scripts in separate files
* @method get
* @param {String} name
* @returns {Object|Boolean}
*/
module.exports.get = function (name) {
var instance = getSingle(name);
if (instance) {
return instance;
}
throw new Error("An instance with the name `%s` was not found.".replace("%s", name));
};
/**
* Check if an instance has been created.
* @method has
* @param {String} name
* @returns {Boolean}
*/
module.exports.has = function (name) {
var instance = getSingle(name);
if (instance) {
return true;
}
return false;
};
/**
* Start the Browsersync service. This will launch a server, proxy or start the snippet
* mode depending on your use-case.
* @method init
* @param {Object} [config] This is the main configuration for your Browsersync instance and can contain any of the [available options]({{site.links.options}})
* If you do not pass a config an argument for configuration, Browsersync will still run; but it will be in the `snippet` mode
* @param {Function} [cb] If you pass a callback function, it will be called when Browsersync has completed all setup tasks and is ready to use. This
* is useful when you need to wait for information (for example: urls, port etc) or perform other tasks synchronously.
* @returns {BrowserSync}
*/
module.exports.init = initSingleton;
/**
* Register a plugin. Must implement at least a 'plugin' method that returns a
* callable function.
*
* @method use
* @param {String} name The name of the plugin
* @param {Object} module The object to be `required`.
* @param {Function} [cb] A callback function that will return any errors.
*/
module.exports.use = function () {
var args = Array.prototype.slice.call(arguments);
singletonPlugins.push({
args: args
});
};
/**
* The `reload` method will inform all browsers about changed files and will either cause the browser to refresh, or inject the files where possible.
*
* @method reload
* @param {String|Array|Object} [arg] The file or files to be reloaded.
* @returns {*}
*/
module.exports.reload = noop("reload");
/**
* The `stream` method returns a transform stream and can act once or on many files.
*
* @method stream
* @param {Object} [opts] Configuration for the stream method
* @since 2.6.0
* @returns {*}
*/
module.exports.stream = noop("stream");
/**
* Helper method for browser notifications
*
* @method notify
* @param {String|HTML} msg Can be a simple message such as 'Connected' or HTML
* @param {Number} [timeout] How long the message will remain in the browser. @since 1.3.0
*/
module.exports.notify = noop("notify");
/**
* This method will close any running server, stop file watching & exit the current process.
*
* @method exit
*/
module.exports.exit = noop("exit");
/**
* Stand alone file-watcher. Use this along with Browsersync to create your own, minimal build system
* @method watch
* @param {string} patterns Glob patterns for files to watch
* @param {object} [opts] Options to be passed to Chokidar - check what's available in [their docs](https://github.com/paulmillr/chokidar#getting-started)
* @param {function} [fn] Callback function for each event.
* @since 2.6.0
*/
module.exports.watch = noop("watch");
/**
* Method to pause file change events
*
* @method pause
*/
module.exports.pause = noop("pause");
/**
* Method to resume paused watchers
*
* @method resume
*/
module.exports.resume = noop("resume");
/**
* Add properties fo
*/
Object.defineProperties(module.exports, {
/**
* The internal Event Emitter used by the running Browsersync instance (if there is one).
* You can use this to emit your own events, such as changed files, logging etc.
*
* @property emitter
*/
"emitter": {
get: function () {
if (!singletonEmitter) {
singletonEmitter = newEmitter();
return singletonEmitter;
}
return singletonEmitter;
}
},
/**
* A simple true/false flag that you can use to determine if there's a currently-running Browsersync instance.
*
* @property active
*/
"active": {
get: getSingletonValue.bind(null, "active")
},
/**
* A simple true/false flag to determine if the current instance is paused
*
* @property paused
*/
"paused": {
get: getSingletonValue.bind(null, "paused")
}
});
/**
* Event emitter factory
* @returns {EventEmitter}
*/
function newEmitter() {
var emitter = new events.EventEmitter();
emitter.setMaxListeners(20);
return emitter;
}
/**
* Get the singleton's emitter, or a new one.
* @returns {EventEmitter}
*/
function getSingletonEmitter() {
if (singletonEmitter) {
return singletonEmitter;
}
singletonEmitter = newEmitter();
return singletonEmitter;
}
/**
* Helper to allow methods to be called on the module export
* before there's a running instance
* @param {String} name
* @returns {Function}
*/
function noop(name) {
return function () {
var args = Array.prototype.slice.call(arguments);
if (singleton) {
return singleton[name].apply(singleton, args);
} else {
if (publicUtils.isStreamArg(name, args)) {
return new PassThrough({objectMode: true});
}
}
};
}
/**
* Create a single instance when module export is used directly via browserSync({});
* This is mostly for back-compatibility, for also for the nicer api.
* This will never be removed to ensure we never break user-land, but
* we should discourage it's use.
* @returns {*}
*/
function initSingleton() {
var instance;
if (instances.length) {
instance = instances.filter(function (item) {
return item.name === "singleton";
});
if (instance.length) {
logger.error("{yellow:You tried to start Browsersync twice!} To create multiple instances, use {cyan:browserSync.create().init()");
return instance;
}
}
var args = Array.prototype.slice.call(arguments);
singleton = create("singleton", getSingletonEmitter());
if (singletonPlugins.length) {
singletonPlugins.forEach(function (obj) {
singleton.instance.registerPlugin.apply(singleton.instance, obj.args);
});
}
singleton.init.apply(null, args);
return singleton;
}
/**
* @param {String} prop
* @returns {Object|Boolean}
*/
function getSingletonValue(prop) {
var single = getSingle("singleton");
if (single) {
return single[prop];
}
return false;
}
/**
* Get a single instance by name
* @param {String} name
* @returns {Object|Boolean}
*/
function getSingle(name) {
if (instances.length) {
var match = instances.filter(function (item) {
return item.name === name;
});
if (match.length) {
return match[0];
}
}
return false;
}
/**
* Create an instance of Browsersync
* @param {String} [name]
* @param {EventEmitter} [emitter]
* @returns {{init: *, exit: (exit|exports), notify: *, reload: *, cleanup: *, emitter: (Browsersync.events|*), use: *}}
*/
function create(name, emitter) {
name = name || new Date().getTime();
emitter = emitter || newEmitter();
var browserSync = new BrowserSync(emitter);
var instance = {
name: name,
instance: browserSync,
exit: require("./lib/public/exit")(browserSync),
notify: require("./lib/public/notify")(browserSync),
pause: require("./lib/public/pause")(browserSync),
resume: require("./lib/public/resume")(browserSync),
reload: require("./lib/public/reload")(emitter),
stream: require("./lib/public/stream")(emitter),
cleanup: browserSync.cleanup.bind(browserSync),
use: browserSync.registerPlugin.bind(browserSync),
getOption: browserSync.getOption.bind(browserSync),
emitter: browserSync.events,
watch: require("./lib/file-watcher").watch
};
browserSync.publicInstance = instance;
instance.init = require("./lib/public/init")(browserSync, name, pjson);
Object.defineProperty(instance, "active", {
get: function () {
return browserSync.active;
}
});
Object.defineProperty(instance, "paused", {
get: function () {
return browserSync.paused;
}
});
/**
* Access to client-side socket for emitting events
*
* @property sockets
*/
Object.defineProperty(instance, "sockets", {
get: function () {
if (!browserSync.active) {
return {
emit: function () {},
on: function () {}
};
} else {
return browserSync.io.sockets;
}
}
});
instances.push(instance);
return instance;
}
/**
* Reset the state of the module.
* (should only be needed for test environments)
*/
module.exports.reset = function () {
instances.forEach(function (item) {
item.cleanup();
});
instances = [];
singletonPlugins = [];
singletonEmitter = false;
singleton = false;
};
/**
* @type {Array}
*/
module.exports.instances = instances;

View File

@ -0,0 +1,86 @@
"use strict";
/**
* The purpose of this function is
* to handle back-backwards compatibility
* @param {Object} args
* @returns {{config: {}, cb: *}}
*/
module.exports = function (args) {
var config = {};
var cb;
switch (args.length) {
case 1 :
if (isFilesArg(args[0])) {
config.files = args[0];
} else if (typeof args[0] === "function") {
cb = args[0];
} else {
config = args[0];
}
break;
case 2 :
// if second is a function, first MUST be config
if (typeof args[1] === "function") {
config = args[0] || {};
cb = args[1];
} else {
if (args[1] === null || args[1] === undefined) {
config = args[0];
} else {
// finally, second arg could be a plain object for config
config = args[1] || {};
if (!config.files) {
config.files = args[0];
}
}
}
break;
case 3 :
config = args[1] || {};
if (!config.files) {
config.files = args[0];
}
cb = args[2];
}
return {
config: config,
cb: cb
};
};
/**
* Files args were only ever strings or arrays
* @param arg
* @returns {*|boolean}
*/
function isFilesArg (arg) {
return Array.isArray(arg) || typeof arg === "string";
}

View File

@ -0,0 +1,60 @@
var async = require("./async");
module.exports = [
{
step: "Finding an empty port",
fn: async.getEmptyPort
},
{
step: "Getting an extra port for Proxy",
fn: async.getExtraPortForProxy
},
{
step: "Checking online status",
fn: async.getOnlineStatus
},
{
step: "Resolve user plugins from options",
fn: async.resolveInlineUserPlugins
},
{
step: "Set Urls and other options that rely on port/online status",
fn: async.setOptions
},
{
step: "Setting Internal Events",
fn: async.setInternalEvents
},
{
step: "Setting file watchers",
fn: async.setFileWatchers
},
{
step: "Merging middlewares from core + plugins",
fn: async.mergeMiddlewares
},
{
step: "Starting the Server",
fn: async.startServer
},
{
step: "Starting the HTTPS Tunnel",
fn: async.startTunnel
},
{
step: "Starting the web-socket server",
fn: async.startSockets
},
{
step: "Starting the UI",
fn: async.startUi
},
{
step: "Merge UI settings",
fn: async.mergeUiSettings
},
{
step: "Init user plugins",
fn: async.initUserPlugins
}
];

View File

@ -0,0 +1,323 @@
"use strict";
var _ = require("../lodash.custom");
var Immutable = require("immutable");
var utils = require("./utils");
var pluginUtils = require("./plugins");
var connectUtils = require("./connect-utils");
module.exports = {
/**
* BrowserSync needs at least 1 free port.
* It will check the one provided in config
* and keep incrementing until an available one is found.
* @param {BrowserSync} bs
* @param {Function} done
*/
getEmptyPort: function (bs, done) {
utils.getPorts(bs.options, function (err, port) {
if (err) {
return utils.fail(true, err, bs.cb);
}
bs.debug("Found a free port: {magenta:%s", port);
done(null, {
options: {
port: port
}
});
});
},
/**
* If the running mode is proxy, we'll use a separate port
* for the Browsersync web-socket server. This is to eliminate any issues
* with trying to proxy web sockets through to the users server.
* @param bs
* @param done
*/
getExtraPortForProxy: function (bs, done) {
/**
* An extra port is not needed in snippet/server mode
*/
if (bs.options.get("mode") !== "proxy") {
return done();
}
/**
* Web socket support is disabled by default
*/
if (!bs.options.getIn(["proxy", "ws"])) {
return done();
}
/**
* Use 1 higher than server port by default...
*/
var socketPort = bs.options.get("port") + 1;
/**
* Or use the user-defined socket.port option instead
*/
if (bs.options.hasIn(["socket", "port"])) {
socketPort = bs.options.getIn(["socket", "port"]);
}
utils.getPort(socketPort, null, function (err, port) {
if (err) {
return utils.fail(true, err, bs.cb);
}
done(null, {
optionsIn: [
{
path: ["socket", "port"],
value: port
}
]
});
});
},
/**
* Some features require an internet connection.
* If the user did not provide either `true` or `false`
* for the online option, we will attempt to resolve www.google.com
* as a way of determining network connectivity
* @param {BrowserSync} bs
* @param {Function} done
*/
getOnlineStatus: function (bs, done) {
if (_.isUndefined(bs.options.get("online")) && _.isUndefined(process.env.TESTING)) {
require("dns").resolve("www.google.com", function (err) {
var online = false;
if (err) {
bs.debug("Could not resolve www.google.com, setting {magenta:online: false}");
} else {
bs.debug("Resolved www.google.com, setting {magenta:online: true}");
online = true;
}
done(null, {
options: {
online: online
}
});
});
} else {
done();
}
},
/**
* Try to load plugins that were given in options
* @param {BrowserSync} bs
* @param {Function} done
*/
resolveInlineUserPlugins: function (bs, done) {
var plugins = bs.options
.get("plugins")
.map(pluginUtils.resolvePlugin)
.map(pluginUtils.requirePlugin);
plugins
.forEach(function (plugin) {
if (plugin.get("errors").size) {
return logPluginError(plugin);
}
var jsPlugin = plugin.toJS();
jsPlugin.options = jsPlugin.options || {};
jsPlugin.options.moduleName = jsPlugin.moduleName;
bs.registerPlugin(jsPlugin.module, jsPlugin.options);
});
function logPluginError (plugin) {
utils.fail(true, plugin.getIn(["errors", 0]), bs.cb);
}
done();
},
/**
*
* @param {BrowserSync} bs
* @param {Function} done
*/
setOptions: function (bs, done) {
done(null, {
options: {
urls: utils.getUrlOptions(bs.options),
snippet: connectUtils.scriptTags(bs.options),
scriptPaths: Immutable.fromJS(connectUtils.clientScript(bs.options, true)),
files: bs.pluginManager.hook(
"files:watch",
bs.options.get("files"),
bs.pluginManager.pluginOptions
)
}
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
setInternalEvents: function (bs, done) {
require("./internal-events")(bs);
done();
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
setFileWatchers: function (bs, done) {
done(null, {
instance: {
watchers: bs.pluginManager.get("file:watcher")(bs)
}
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
mergeMiddlewares: function (bs, done) {
done(null, {
options: {
middleware: bs.pluginManager.hook(
"server:middleware",
bs.options.get("middleware")
)
}
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
startServer: function (bs, done) {
var server = bs.pluginManager.get("server")(bs);
done(null, {
instance: {
server: server.server,
app: server.app
}
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
startTunnel: function (bs, done) {
if (bs.options.get("tunnel") && bs.options.get("online")) {
var localTunnel = require("./tunnel");
localTunnel(bs, function (err, tunnel) {
if (err) {
return done(err);
} else {
return done(null, {
optionsIn: [
{
path: ["urls", "tunnel"],
value: tunnel.url
}
],
instance: {
tunnel: tunnel
}
});
}
});
} else {
done();
}
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
startSockets: function (bs, done) {
var clientEvents = bs.pluginManager.hook(
"client:events",
bs.options.get("clientEvents").toJS()
);
// Start the socket, needs an existing server.
var io = bs.pluginManager.get("socket")(
bs.server,
clientEvents,
bs
);
done(null, {
instance: {
io: io
},
options: {
clientEvents: Immutable.fromJS(clientEvents)
}
});
},
/**
*
* @param {BrowserSync} bs
* @param {Function} done
*/
startUi: function (bs, done) {
var PLUGIN_NAME = "UI";
var userPlugins = bs.getUserPlugins();
var ui = bs.pluginManager.get(PLUGIN_NAME);
var uiOpts = bs.options.get("ui");
if (!uiOpts || uiOpts.get("enabled") === false) {
return done();
}
// if user provided a UI, use it instead
if (userPlugins.some(function (item) {
return item.name === PLUGIN_NAME;
})) {
uiOpts = bs.options.get("ui").mergeDeep(Immutable.fromJS(bs.pluginManager.pluginOptions[PLUGIN_NAME]));
}
return ui(uiOpts.toJS(), bs, function (err, ui) {
if (err) {
return done(err);
}
done(null, {
instance: {
ui: ui
}
});
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
mergeUiSettings: function (bs, done) {
if (!bs.ui) {
return done();
}
done(null, {
options: {
urls: bs.options.get("urls").merge(bs.ui.options.get("urls"))
}
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
initUserPlugins: function (bs, done) {
bs.pluginManager.initUserPlugins(bs);
done(null, {
options: {
userPlugins: bs.getUserPlugins()
}
});
}
};

View File

@ -0,0 +1,696 @@
"use strict";
var hooks = require("./hooks");
var asyncTasks = require("./async-tasks");
var config = require("./config");
var connectUtils = require("./connect-utils");
var utils = require("./utils");
var logger = require("./logger");
var eachSeries = utils.eachSeries;
var _ = require("../lodash.custom");
var EE = require("easy-extender");
/**
* Required internal plugins.
* Any of these can be overridden by deliberately
* causing a name-clash.
*/
var defaultPlugins = {
"logger": logger,
"socket": require("./sockets"),
"file:watcher": require("./file-watcher"),
"server": require("./server"),
"tunnel": require("./tunnel"),
"client:script": require("browser-sync-client"),
"UI": require("browser-sync-ui")
};
/**
* @constructor
*/
var BrowserSync = function (emitter) {
var bs = this;
bs.cwd = process.cwd();
bs.active = false;
bs.paused = false;
bs.config = config;
bs.utils = utils;
bs.events = bs.emitter = emitter;
bs._userPlugins = [];
bs._reloadQueue = [];
bs._cleanupTasks = [];
bs._browserReload = false;
// Plugin management
bs.pluginManager = new EE(defaultPlugins, hooks);
};
/**
* Call a user-options provided callback
* @param name
*/
BrowserSync.prototype.callback = function (name) {
var bs = this;
var cb = bs.options.getIn(["callbacks", name]);
if (_.isFunction(cb)) {
cb.apply(bs.publicInstance, _.toArray(arguments).slice(1));
}
};
/**
* @param {Map} options
* @param {Function} cb
* @returns {BrowserSync}
*/
BrowserSync.prototype.init = function (options, cb) {
/**
* Safer access to `this`
* @type {BrowserSync}
*/
var bs = this;
/**
* Set user-provided callback, or assign a noop
* @type {Function}
*/
bs.cb = cb || utils.defaultCallback;
/**
* Verify provided config.
* Some options are not compatible and will cause us to
* end the process.
*/
if (!utils.verifyConfig(options, bs.cb)) {
return;
}
/**
* Save a reference to the original options
* @type {Map}
* @private
*/
bs._options = options;
/**
* Set additional options that depend on what the
* user may of provided
* @type {Map}
*/
bs.options = require("./options").update(options);
/**
* Kick off default plugins.
*/
bs.pluginManager.init();
/**
* Create a base logger & debugger.
*/
bs.logger = bs.pluginManager.get("logger")(bs.events, bs);
bs.debugger = bs.logger.clone({useLevelPrefixes: true});
bs.debug = bs.debugger.debug;
/**
* Run each setup task in sequence
*/
eachSeries(
asyncTasks,
taskRunner(bs),
tasksComplete(bs)
);
return this;
};
/**
* Run 1 setup task.
* Each task is a pure function.
* They can return options or instance properties to set,
* but they cannot set them directly.
* @param {BrowserSync} bs
* @returns {Function}
*/
function taskRunner (bs) {
return function (item, cb) {
bs.debug("-> {yellow:Starting Step: " + item.step);
/**
* Execute the current task.
*/
item.fn(bs, executeTask);
function executeTask(err, out) {
/**
* Exit early if any task returned an error.
*/
if (err) {
return cb(err);
}
/**
* Act on return values (such as options to be set,
* or instance properties to be set
*/
if (out) {
handleOut(bs, out);
}
bs.debug("+ {green:Step Complete: " + item.step);
cb();
}
};
}
/**
* @param bs
* @param out
*/
function handleOut (bs, out) {
/**
* Set a single/many option.
*/
if (out.options) {
setOptions(bs, out.options);
}
/**
* Any options returned that require path access?
*/
if (out.optionsIn) {
out.optionsIn.forEach(function (item) {
bs.setOptionIn(item.path, item.value);
});
}
/**
* Any instance properties returned?
*/
if (out.instance) {
Object.keys(out.instance).forEach(function (key) {
bs[key] = out.instance[key];
});
}
}
/**
* Update the options Map
* @param bs
* @param options
*/
function setOptions (bs, options) {
/**
* If multiple options were set, act on the immutable map
* in an efficient way
*/
if (Object.keys(options).length > 1) {
bs.setMany(function (item) {
Object.keys(options).forEach(function (key) {
item.set(key, options[key]);
return item;
});
});
} else {
Object.keys(options).forEach(function (key) {
bs.setOption(key, options[key]);
});
}
}
/**
* At this point, ALL async tasks have completed
* @param {BrowserSync} bs
* @returns {Function}
*/
function tasksComplete (bs) {
return function (err) {
if (err) {
bs.logger.setOnce("useLevelPrefixes", true).error(err.message);
}
/**
* Set active flag
*/
bs.active = true;
/**
* @deprecated
*/
bs.events.emit("init", bs);
/**
* This is no-longer needed as the Callback now only resolves
* when everything (including slow things, like the tunnel) is ready.
* It's here purely for backwards compatibility.
* @deprecated
*/
bs.events.emit("service:running", {
options: bs.options,
baseDir: bs.options.getIn(["server", "baseDir"]),
type: bs.options.get("mode"),
port: bs.options.get("port"),
url: bs.options.getIn(["urls", "local"]),
urls: bs.options.get("urls").toJS(),
tunnel: bs.options.getIn(["urls", "tunnel"])
});
/**
* Call any option-provided callbacks
*/
bs.callback("ready", null, bs);
/**
* Finally, call the user-provided callback given as last arg
*/
bs.cb(null, bs);
};
}
/**
* @param module
* @param opts
* @param cb
*/
BrowserSync.prototype.registerPlugin = function (module, opts, cb) {
var bs = this;
bs.pluginManager.registerPlugin(module, opts, cb);
if (module["plugin:name"]) {
bs._userPlugins.push(module);
}
};
/**
* Get a plugin by name
* @param name
*/
BrowserSync.prototype.getUserPlugin = function (name) {
var bs = this;
var items = bs.getUserPlugins(function (item) {
return item["plugin:name"] === name;
});
if (items && items.length) {
return items[0];
}
return false;
};
/**
* @param {Function} [filter]
*/
BrowserSync.prototype.getUserPlugins = function (filter) {
var bs = this;
filter = filter || function () {
return true;
};
/**
* Transform Plugins option
*/
bs.userPlugins = bs._userPlugins.filter(filter).map(function (plugin) {
return {
name: plugin["plugin:name"],
active: plugin._enabled,
opts: bs.pluginManager.pluginOptions[plugin["plugin:name"]]
};
});
return bs.userPlugins;
};
/**
* Get middleware
* @returns {*}
*/
BrowserSync.prototype.getMiddleware = function (type) {
var types = {
"connector": connectUtils.socketConnector(this.options),
"socket-js": require("./snippet").utils.getSocketScript()
};
if (type in types) {
return function (req, res) {
res.setHeader("Content-Type", "text/javascript");
res.end(types[type]);
};
}
};
/**
* Shortcut for pushing a file-serving middleware
* onto the stack
* @param {String} path
* @param {{type: string, content: string}} props
*/
var _serveFileCount = 0;
BrowserSync.prototype.serveFile = function (path, props) {
var bs = this;
var mode = bs.options.get("mode");
var entry = {
handle: function (req, res) {
res.setHeader("Content-Type", props.type);
res.end(props.content);
},
id: "Browsersync - " + _serveFileCount++,
route: path
};
bs._addMiddlewareToStack(entry);
};
/**
* Add middlewares on the fly
* @param {{route: string, handle: function, id?: string}}
*/
BrowserSync.prototype._addMiddlewareToStack = function (entry) {
var bs = this;
if (bs.options.get("mode") === "proxy") {
bs.app.stack.splice(bs.app.stack.length-1, 0, entry);
} else {
bs.app.stack.push(entry);
}
};
var _addMiddlewareCount = 0;
BrowserSync.prototype.addMiddleware = function (route, handle, opts) {
var bs = this;
if (!bs.app) {
return;
}
opts = opts || {};
if (!opts.id) {
opts.id = "bs-mw-" + _addMiddlewareCount++;
}
if (route === "*") {
route = "";
}
var entry = {
id: opts.id,
route: route,
handle: handle
};
if (opts.override) {
entry.override = true;
}
bs.options = bs.options.update("middleware", function (mw) {
if (bs.options.get("mode") === "proxy") {
return mw.insert(mw.size-1, entry);
}
return mw.concat(entry);
});
bs.resetMiddlewareStack();
};
/**
* Remove middlewares on the fly
* @param {String} id
* @returns {Server}
*/
BrowserSync.prototype.removeMiddleware = function (id) {
var bs = this;
if (!bs.app) {
return;
}
bs.options = bs.options.update("middleware", function (mw) {
return mw.filter(function (mw) {
return mw.id !== id;
});
});
bs.resetMiddlewareStack();
};
/**
* Middleware for socket connection (external usage)
* @param opts
* @returns {*}
*/
BrowserSync.prototype.getSocketConnector = function (opts) {
var bs = this;
return function (req, res) {
res.setHeader("Content-Type", "text/javascript");
res.end(bs.getExternalSocketConnector(opts));
};
};
/**
* Socket connector as a string
* @param {Object} opts
* @returns {*}
*/
BrowserSync.prototype.getExternalSocketConnector = function (opts) {
var bs = this;
return connectUtils.socketConnector(
bs.options.withMutations(function (item) {
item.set("socket", item.get("socket").merge(opts));
if (!bs.options.getIn(["proxy", "ws"])) {
item.set("mode", "snippet");
}
})
);
};
/**
* Socket io as string (for embedding)
* @returns {*}
*/
BrowserSync.prototype.getSocketIoScript = function () {
return require("./snippet").utils.getSocketScript();
};
/**
* Callback helper
* @param name
*/
BrowserSync.prototype.getOption = function (name) {
this.debug("Getting option: {magenta:%s", name);
return this.options.get(name);
};
/**
* Callback helper
* @param path
*/
BrowserSync.prototype.getOptionIn = function (path) {
this.debug("Getting option via path: {magenta:%s", path);
return this.options.getIn(path);
};
/**
* @returns {BrowserSync.options}
*/
BrowserSync.prototype.getOptions = function () {
return this.options;
};
/**
* @returns {BrowserSync.options}
*/
BrowserSync.prototype.getLogger = logger.getLogger;
/**
* @param {String} name
* @param {*} value
* @returns {BrowserSync.options|*}
*/
BrowserSync.prototype.setOption = function (name, value, opts) {
var bs = this;
opts = opts || {};
bs.debug("Setting Option: {cyan:%s} - {magenta:%s", name, value.toString());
bs.options = bs.options.set(name, value);
if (!opts.silent) {
bs.events.emit("options:set", {path: name, value: value, options: bs.options});
}
return this.options;
};
/**
* @param path
* @param value
* @param opts
* @returns {Map|*|BrowserSync.options}
*/
BrowserSync.prototype.setOptionIn = function (path, value, opts) {
var bs = this;
opts = opts || {};
bs.debug("Setting Option: {cyan:%s} - {magenta:%s", path.join("."), value.toString());
bs.options = bs.options.setIn(path, value);
if (!opts.silent) {
bs.events.emit("options:set", {path: path, value: value, options: bs.options});
}
return bs.options;
};
/**
* Set multiple options with mutations
* @param fn
* @param opts
* @returns {Map|*}
*/
BrowserSync.prototype.setMany = function (fn, opts) {
var bs = this;
opts = opts || {};
bs.debug("Setting multiple Options");
bs.options = bs.options.withMutations(fn);
if (!opts.silent) {
bs.events.emit("options:set", {options: bs.options.toJS()});
}
return this.options;
};
BrowserSync.prototype.addRewriteRule = function (rule) {
var bs = this;
bs.options = bs.options.update("rewriteRules", function (rules) {
return rules.concat(rule);
});
bs.resetMiddlewareStack();
};
BrowserSync.prototype.removeRewriteRule = function (id) {
var bs = this;
bs.options = bs.options.update("rewriteRules", function (rules) {
return rules.filter(function (rule) {
return rule.id !== id;
});
});
bs.resetMiddlewareStack();
};
BrowserSync.prototype.setRewriteRules = function (rules) {
var bs = this;
bs.options = bs.options.update("rewriteRules", function (_) {
return rules;
});
bs.resetMiddlewareStack();
};
/**
* Add a new rewrite rule to the stack
* @param {Object} rule
*/
BrowserSync.prototype.resetMiddlewareStack = function () {
var bs = this;
var middlewares = require("./server/utils").getMiddlewares(bs, bs.options);
bs.app.stack = middlewares;
};
/**
* @param fn
*/
BrowserSync.prototype.registerCleanupTask = function (fn) {
this._cleanupTasks.push(fn);
};
/**
* Instance Cleanup
*/
BrowserSync.prototype.cleanup = function (cb) {
var bs = this;
if (!bs.active) {
return;
}
// Remove all event listeners
if (bs.events) {
bs.debug("Removing event listeners...");
bs.events.removeAllListeners();
}
// Close any core file watchers
if (bs.watchers) {
Object.keys(bs.watchers).forEach(function (key) {
bs.watchers[key].watchers.forEach(function (watcher) {
watcher.close();
});
});
}
// Run any additional clean up tasks
bs._cleanupTasks.forEach(function (fn) {
if (_.isFunction(fn)) {
fn(bs);
}
});
// Reset the flag
bs.debug("Setting {magenta:active: false");
bs.active = false;
bs.paused = false;
bs.pluginManager.plugins = {};
bs.pluginManager.pluginOptions = {};
bs.pluginManager.defaultPlugins = defaultPlugins;
bs._userPlugins = [];
bs.userPlugins = [];
bs._reloadTimer = undefined;
bs._reloadQueue = [];
bs._cleanupTasks = [];
if (_.isFunction(cb)) {
cb(null, bs);
}
};
module.exports = BrowserSync;

View File

@ -0,0 +1,59 @@
"use strict";
var config = require("../config");
var logger = require("../logger").logger;
var fs = require("fs");
var _ = require("../../lodash.custom");
var path = require("path");
var info = {
/**
* Version info
* @param {Object} pjson
* @returns {String}
*/
getVersion: function (pjson) {
console.log(pjson.version);
return pjson.version;
},
/**
* Retrieve the config file
* @returns {*}
* @private
* @param filePath
*/
getConfigFile: function (filePath) {
return require(path.resolve(filePath));
},
/**
* Generate an example Config file.
*/
makeConfig: function (cwd, cb) {
var opts = require(path.join(__dirname, "..", config.configFile));
var userOpts = {};
var ignore = ["excludedFileTypes", "injectFileTypes", "snippetOptions"];
Object.keys(opts).forEach(function (key) {
if (!_.includes(ignore, key)) {
userOpts[key] = opts[key];
}
});
var file = fs.readFileSync(path.join(__dirname, config.template), "utf8");
file = file.replace("//OPTS", JSON.stringify(userOpts, null, 4));
fs.writeFile(path.resolve(cwd, config.userFile), file, function () {
logger.info("Config file created {magenta:%s}", config.userFile);
logger.info(
"To use it, in the same directory run: " +
"{cyan:browser-sync start --config bs-config.js}"
);
cb();
});
}
};
module.exports = info;

View File

@ -0,0 +1,317 @@
"use strict";
var path = require("path");
var url = require("url");
var _ = require("../../lodash.custom");
var Immutable = require("immutable");
var isList = Immutable.List.isList;
var isMap = Immutable.Map.isMap;
var defaultConfig = require("../default-config");
var immDefs = Immutable.fromJS(defaultConfig);
var opts = exports;
/**
* @type {{wrapPattern: Function}}
*/
opts.utils = {
/**
* Transform a string arg such as "*.html, css/*.css" into array
* @param string
* @returns {Array}
*/
explodeFilesArg: function (string) {
return string.split(",").map(function (item) {
return item.trim();
});
},
/**
* @param pattern
* @returns {*|string}
* @private
*/
wrapPattern: function (pattern) {
var prefix = "!";
var suffix = "/**";
var lastChar = pattern.charAt(pattern.length - 1);
var extName = path.extname(pattern);
// If there's a file ext, don't append any suffix
if (extName.length) {
suffix = "";
} else {
if (lastChar === "/") {
suffix = "**";
}
if (lastChar === "*") {
suffix = "";
}
}
return [prefix, pattern, suffix].join("");
}
};
opts.callbacks = {
/**
* Merge server options
* @param {String|Boolean|Object} value
* @param [argv]
* @returns {*}
*/
server: function (value, argv) {
if (value === false) {
if (!argv || !argv.server) {
return false;
}
}
var obj = {
baseDir: "./"
};
if (_.isString(value) || isList(value)) {
obj.baseDir = value;
} else {
if (value && value !== true) {
if (value.get("baseDir")) {
return value;
}
}
}
if (argv) {
if (argv.index) {
obj.index = argv.index;
}
if (argv.directory) {
obj.directory = true;
}
}
return Immutable.fromJS(obj);
},
/**
* @param value
* @param argv
* @returns {*}
*/
proxy: function (value) {
var mw;
var target;
if (!value || value === true) {
return false;
}
if (typeof value !== "string") {
target = value.get("target");
mw = value.get("middleware");
} else {
target = value;
value = Immutable.Map({});
}
if (!target.match(/^(https?):\/\//)) {
target = "http://" + target;
}
var parsedUrl = url.parse(target);
if (!parsedUrl.port) {
parsedUrl.port = 80;
}
var out = {
target: parsedUrl.protocol + "//" + parsedUrl.host,
url: Immutable.Map(parsedUrl)
};
if (mw) {
out.middleware = mw;
}
return value.mergeDeep(out);
},
/**
* @param value
* @private
*/
ports: function (value) {
var segs;
var obj = {};
if (typeof value === "string") {
if (~value.indexOf(",")) {
segs = value.split(",");
obj.min = parseInt(segs[0], 10);
obj.max = parseInt(segs[1], 10);
} else {
obj.min = parseInt(value, 10);
obj.max = null;
}
} else {
obj.min = value.get("min");
obj.max = value.get("max") || null;
}
return Immutable.Map(obj);
},
/**
* @param value
* @param argv
* @returns {*}
*/
ghostMode: function (value, argv) {
var trueAll = {
clicks: true,
scroll: true,
forms: {
submit: true,
inputs: true,
toggles: true
}
};
var falseAll = {
clicks: false,
scroll: false,
forms: {
submit: false,
inputs: false,
toggles: false
}
};
if (value === false || value === "false" || argv && argv.ghost === false) {
return Immutable.fromJS(falseAll);
}
if (value === true || value === "true" || argv && argv.ghost === true) {
return Immutable.fromJS(trueAll);
}
if (value.get("forms") === false) {
return value.withMutations(function (map) {
map.set("forms", Immutable.fromJS({
submit: false,
inputs: false,
toggles: false
}));
});
}
if (value.get("forms") === true) {
return value.withMutations(function (map) {
map.set("forms", Immutable.fromJS({
submit: true,
inputs: true,
toggles: true
}));
});
}
return value;
},
/**
* @param value
* @returns {*}
*/
files: function (value) {
var namespaces = {core: {}};
namespaces.core.globs = [];
namespaces.core.objs = [];
var processed = opts.makeFilesArg(value);
if (processed.globs.length) {
namespaces.core.globs = processed.globs;
}
if (processed.objs.length) {
namespaces.core.objs = processed.objs;
}
return Immutable.fromJS(namespaces);
},
/**
* @param value
*/
extensions: function (value) {
if (_.isString(value)) {
var split = opts.utils.explodeFilesArg(value);
if (split.length) {
return Immutable.List(split);
}
}
if (Immutable.List.isList(value)) {
return value;
}
return value;
}
};
/**
* @param {Object} values
* @param {Object} [argv]
* @returns {Map}
*/
opts.merge = function (values, argv) {
return immDefs
.mergeDeep(values)
.withMutations(function (item) {
item.map(function (value, key) {
if (opts.callbacks[key]) {
item.set(key, opts.callbacks[key](value, argv));
}
});
});
};
/**
* @param value
* @returns {{globs: Array, objs: Array}}
*/
opts.makeFilesArg = function (value) {
var globs = [];
var objs = [];
if (_.isString(value)) {
globs = globs.concat(
opts.utils.explodeFilesArg(value)
);
}
if (isList(value) && value.size) {
value.forEach(function (value) {
if (_.isString(value)) {
globs.push(value);
} else {
if (isMap(value)) {
objs.push(value);
}
}
});
}
return {
globs: globs,
objs: objs
};
};

View File

@ -0,0 +1,15 @@
/*
|--------------------------------------------------------------------------
| Browser-sync config file
|--------------------------------------------------------------------------
|
| For up-to-date information about the options:
| http://www.browsersync.io/docs/options/
|
| There are more options than you see here, these are just the ones that are
| set internally. See the website for more info.
|
|
*/
module.exports = //OPTS;

View File

@ -0,0 +1,15 @@
"use strict";
var info = require("./cli-info");
/**
* $ browser-sync init
*
* This command will generate a configuration
* file in the current directory
*
* @param opts
*/
module.exports = function (opts) {
info.makeConfig(process.cwd(), opts.cb);
};

View File

@ -0,0 +1,64 @@
"use strict";
var logger = require("../logger").logger;
/**
* $ browser-sync recipe <name> <options>
*
* This command will copy a recipe into either the current directory
* or one given with the --output flag
*
* @param opts
* @returns {Function}
*/
module.exports = function (opts) {
var path = require("path");
var fs = require("fs-extra");
var input = opts.cli.input.slice(1);
var resolved = require.resolve("bs-recipes");
var dir = path.dirname(resolved);
var logRecipes = function () {
var dirs = fs.readdirSync(path.join(dir, "recipes"));
logger.info("Install one of the following with {cyan:browser-sync recipe <name>\n");
dirs.forEach(function (name) {
console.log(" " + name);
});
};
if (!input.length) {
logger.info("No recipe name provided!");
logRecipes();
return opts.cb();
}
if (opts.cli.input[1] === "ls") {
logRecipes();
return opts.cb();
}
input = input[0];
var flags = opts.cli.flags;
var output = flags.output ? path.resolve(flags.output) : path.join(process.cwd(), input);
var targetDir = path.join(dir, "recipes", input);
if (fs.existsSync(output)) {
return opts.cb(new Error("Target folder exists remove it first and then try again"));
}
if (fs.existsSync(targetDir)) {
fs.copy(targetDir, output, function (err) {
if (err) {
opts.cb(err);
} else {
logger.info("Recipe copied into {cyan:%s}", output);
logger.info("Next, inside that folder, run {cyan:npm i && npm start}");
opts.cb(null);
}
});
} else {
logger.info("Recipe {cyan:%s} not found. The following are available though", input);
logRecipes();
opts.cb();
}
};

View File

@ -0,0 +1,44 @@
"use strict";
/**
* $ browser-sync reload <options>
*
* This commands starts the Browsersync servers
* & Optionally UI.
*
* @param opts
* @returns {Function}
*/
module.exports = function (opts) {
var flags = opts.cli.flags;
if (!flags.url) {
flags.url = "http://localhost:" + (flags.port || 3000);
}
var proto = require("../http-protocol");
var scheme = flags.url.match(/^https/) ? "https" : "http";
var args = {method: "reload"};
if (flags.files) {
args.args = flags.files;
}
var url = proto.getUrl(args, flags.url);
if (scheme === "https") {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
}
require(scheme).get(url, function (res) {
res.on("data", function () {
if (res.statusCode === 200) {
opts.cb(null, res);
}
});
}).on("error", function (err) {
if (err.code === "ECONNREFUSED") {
err.message = "Browsersync not running at " + flags.url;
}
return opts.cb(err);
});
};

View File

@ -0,0 +1,88 @@
"use strict";
var path = require("path");
var fs = require("fs");
var _ = require("../../lodash.custom");
var utils = require("../utils");
var opts = require("./cli-options").utils;
/**
* $ browser-sync start <options>
*
* This commands starts the Browsersync servers
* & Optionally UI.
*
* @param opts
* @returns {Function}
*/
module.exports = function (opts) {
var flags = preprocessFlags(opts.cli.flags);
var maybepkg = path.resolve(process.cwd(), "package.json");
var input = flags;
if (flags.config) {
var maybeconf = path.resolve(process.cwd(), flags.config);
if (fs.existsSync(maybeconf)) {
var conf = require(maybeconf);
input = _.merge({}, conf, flags);
} else {
utils.fail(true, new Error("Configuration file '" + flags.config + "' not found"), opts.cb);
}
} else {
if (fs.existsSync(maybepkg)) {
var pkg = require(maybepkg);
if (pkg["browser-sync"]) {
console.log("> Configuration obtained from package.json");
input = _.merge({}, pkg["browser-sync"], flags);
}
}
}
return require("../../")
.create("cli")
.init(input, opts.cb);
};
/**
* @param flags
* @returns {*}
*/
function preprocessFlags (flags) {
return [
stripUndefined,
legacyFilesArgs
].reduce(function (flags, fn) {
return fn.call(null, flags);
}, flags);
}
/**
* Incoming undefined values are problematic as
* they interfere with Immutable.Map.mergeDeep
* @param subject
* @returns {*}
*/
function stripUndefined (subject) {
return Object.keys(subject).reduce(function (acc, key) {
var value = subject[key];
if (typeof value === "undefined") {
return acc;
}
acc[key] = value;
return acc;
}, {});
}
/**
* @param flags
* @returns {*}
*/
function legacyFilesArgs(flags) {
if (flags.files && flags.files.length) {
flags.files = flags.files.reduce(function (acc, item) {
return acc.concat(opts.explodeFilesArg(item));
}, []);
}
return flags;
}

View File

@ -0,0 +1,11 @@
{cyan:Server Example:}
{gray:---------------}
Use current directory as root & watch CSS files
{gray:$} browser-sync start --server --files="css/*.css"
{cyan:Proxy Example:}
{gray:---------------}
Proxy `localhost:8080` & watch CSS/HTML files
{gray:$} browser-sync start --proxy="localhost:8080" --files="*.html, css/*.css"

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,6 @@
{
"output": {
"alias": "o",
"desc": "Specify an output directory"
}
}

View File

@ -0,0 +1,16 @@
{
"files": {
"desc": "File paths to reload",
"type": "array",
"alias": "f"
},
"port": {
"alias": "p",
"type": "number",
"desc": "Target a running instance by port number"
},
"url": {
"alias": "u",
"desc": "Provide the full the url to the running Browsersync instance"
}
}

View File

@ -0,0 +1,120 @@
{
"server": {
"alias": "s",
"desc": "Run a Local server (uses your cwd as the web root)"
},
"serveStatic": {
"type": "array",
"alias": "ss",
"desc": "Directories to serve static files from"
},
"port": {
"type": "number",
"desc": "Specify a port to use"
},
"proxy": {
"alias": "p",
"desc": "Proxy an existing server",
"example": "$0 shane is cool"
},
"ws": {
"type": "boolean",
"desc": "Proxy mode only - enable websocket proxying"
},
"browser": {
"type": "array",
"alias": "b",
"desc": "Choose which browser should be auto-opened"
},
"files": {
"type": "array",
"alias": "f",
"desc": "File paths to watch"
},
"index": {
"type": "string",
"desc": "Specify which file should be used as the index page"
},
"plugins": {
"type": "array",
"desc": "Load Browsersync plugins"
},
"extensions": {
"type": "array",
"desc": "Specify file extension fallbacks"
},
"startPath": {
"type": "string",
"desc": "Specify the start path for the opened browser"
},
"https": {
"desc": "Enable SSL for local development"
},
"directory": {
"type": "boolean",
"desc": "Show a directory listing for the server"
},
"xip": {
"type": "boolean",
"desc": "Use xip.io domain routing"
},
"tunnel": {
"desc": "Use a public URL"
},
"open": {
"type": "string",
"desc": "Choose which URL is auto-opened (local, external or tunnel), or provide a url"
},
"cors": {
"type": "boolean",
"desc": "Add Access Control headers to every request"
},
"config": {
"type": "string",
"alias": "c",
"desc": "Specify a path to a configuration file"
},
"host": {
"desc": "Specify a hostname to use"
},
"logLevel": {
"desc": "Set the logger output level (silent, info or debug)"
},
"reload-delay": {
"type": "number",
"desc": "Time in milliseconds to delay the reload event following file changes"
},
"reload-debounce": {
"type": "number",
"desc": "Restrict the frequency in which browser:reload events can be emitted to connected clients"
},
"ui-port": {
"type": "number",
"desc": "Specify a port for the UI to use"
},
"watchEvents": {
"type": "array",
"desc": "Specify which file events to respond to"
},
"no-notify": {
"desc": "Disable the notify element in browsers"
},
"no-open": {
"desc": "Don't open a new browser window"
},
"no-online": {
"desc": "Force offline usage"
},
"no-ui": {
"desc": "Don't start the user interface"
},
"no-ghost-mode": {
"desc": "Disable Ghost Mode"
},
"no-inject-changes": {
"desc": "Reload on every file change"
},
"no-reload-on-restart": {
"desc": "Don't auto-reload all browsers following a restart"
}
}

View File

@ -0,0 +1,31 @@
"use strict";
var path = require("path");
/**
* @type {{controlPanel: {jsFile: string, baseDir: *}, socketIoScript: string, configFile: string, client: {shims: string}}}
*/
module.exports = {
controlPanel: {
jsFile: "/js/app.js",
baseDir: path.join(__dirname, "control-panel")
},
templates: {
scriptTag: path.join(__dirname, "templates/script-tags.tmpl"),
scriptTagSimple: path.join(__dirname, "templates/script-tags-simple.tmpl"),
connector: path.join(__dirname, "templates/connector.tmpl")
},
socketIoScript: "/public/socket.io.min.1.6.0.js",
configFile: "default-config.js",
userFile: "bs-config.js",
template: "cli-template.js",
httpProtocol: {
path: "/__browser_sync__"
},
client: {
shims: "/client/client-shims.js"
},
errors: {
"server+proxy": "Invalid config. You cannot specify both server & proxy options.",
"proxy+https": "Invalid config. You set https: true, but your proxy target doesn't reflect this."
}
};

View File

@ -0,0 +1,245 @@
"use strict";
var _ = require("../lodash.custom");
var fs = require("fs");
var config = require("./config");
function getPath(options, relative, port) {
if (options.get("mode") === "snippet") {
return options.get("scheme") + "://HOST:" + port + relative;
} else {
return "//HOST:" + port + relative;
}
}
var connectUtils = {
/**
* @param {Immutable.Map} options
* @returns {String}
*/
scriptTags: function (options) {
var scriptPath = this.clientScript(options);
var async = options.getIn(["snippetOptions", "async"]);
var scriptDomain = options.getIn(["script", "domain"]);
/**
* Generate the [src] attribute based on user options
*/
var scriptSrc = (function () {
if (options.get("localOnly")) {
return [
options.get("scheme"),
"://localhost:",
options.get("port"),
scriptPath
].join("");
}
/**
* First, was "scriptPath" set? if so the user wanted full control over the
* script tag output
*
*/
if (_.isFunction(options.get("scriptPath"))) {
return options.get("scriptPath").apply(null, getScriptArgs(options, scriptPath));
}
/**
* Next, if "script.domain" was given, allow that + the path to the JS file
* eg:
* script.domain=localhost:3000
* -> localhost:3000/browser-sync/browser-sync-client.js
*/
if (scriptDomain) {
if (_.isFunction(scriptDomain)) {
return scriptDomain.call(null, options) + scriptPath;
}
if (scriptDomain.match(/\{port\}/)) {
return scriptDomain.replace("{port}", options.get("port")) + scriptPath;
}
return scriptDomain + scriptPath;
}
/**
* Now if server or proxy, use dynamic script
* eg:
* browser-sync start --server
* ->
* "HOST:3000/browser-sync/browser-sync-client.js".replace("HOST", location.hostname)
*/
if (options.get("server") || options.get("proxy")) {
return scriptPath;
}
/**
* Final use case is snippet mode
* -> "http://HOST:3000/browser-sync/browser-sync-client.js".replace("HOST", location.hostname)
* -> "//HOST:3000/browser-sync/browser-sync-client.js".replace("HOST", location.hostname)"
*/
return getPath(options, scriptPath, options.get("port"));
})();
/**
* Decide which template shall be used to generate the script tags
*/
var template = (function () {
if (scriptDomain || options.get("localOnly")) {
return config.templates.scriptTagSimple;
}
return config.templates.scriptTag;
})();
/**
* Finally read the template file from disk and replace
* the dynamic values.
*/
return fs.readFileSync(template, "utf8")
.replace("%script%", scriptSrc)
.replace("%async%", async ? "async" : "");
},
/**
* @param {Map} options
* @returns {String}
*/
socketConnector: function (options) {
var socket = options.get("socket");
var template = fs.readFileSync(config.templates.connector, "utf-8");
var url = connectUtils.getConnectionUrl(options);
/**
* ***Backwards compatibility***. While `socket.path` is technically a
* socketIoClientConfig property, it's been documented previously
* as a top-level option, so must stay.
*/
var clientConfig = socket
.get("socketIoClientConfig")
.merge({
path: socket.get("path")
});
template = template
.replace("%config%", JSON.stringify(clientConfig.toJS()))
.replace("%url%", url);
return template;
},
/**
* @param {Object} socketOpts
* @param {Map} options
* @returns {String|Function}
*/
getNamespace: function (socketOpts, options) {
var namespace = socketOpts.namespace;
if (typeof namespace === "function") {
return namespace(options);
}
if (!namespace.match(/^\//)) {
namespace = "/" + namespace;
}
return namespace;
},
/**
* @param {Map} options
* @returns {string}
*/
getConnectionUrl: function (options) {
var socketOpts = options.get("socket").toJS();
var namespace = connectUtils.getNamespace(socketOpts, options);
var protocol = "";
var withHostnamePort = "'{protocol}' + location.hostname + ':{port}{ns}'";
var withHost = "'{protocol}' + location.host + '{ns}'";
var withDomain = "'{domain}{ns}'";
var port = options.get("port");
// default use-case is server/proxy
var string = withHost;
if (options.get("mode") !== "server") {
protocol = options.get("scheme") + "://";
string = withHostnamePort;
}
if (options.get("mode") === "proxy" && options.getIn(["proxy", "ws"])) {
port = options.getIn(["socket", "port"]);
}
/**
* Ensure socket.domain is always a string (for noop replacements later)
*/
socketOpts.domain = (function () {
if (options.get("localOnly")) {
string = withDomain;
return [
options.get("scheme"),
"://localhost:",
options.get("port")
].join("");
}
if (socketOpts.domain) {
string = withDomain;
/**
* User provided a function
*/
if (_.isFunction(socketOpts.domain)) {
return socketOpts.domain.call(null, options);
}
/**
* User provided a string
*/
if (_.isString(socketOpts.domain)) {
return socketOpts.domain;
}
}
return "";
})();
return string
.replace("{protocol}", protocol)
.replace("{port}", port)
.replace("{domain}", socketOpts.domain.replace("{port}", port))
.replace("{ns}", namespace);
},
/**
* @param {Object} [options]
* @param {Boolean} [both]
*/
clientScript: function (options, both) {
var prefix = options.getIn(["socket", "clientPath"]);
var script = prefix + "/browser-sync-client.js";
var versioned = prefix + "/browser-sync-client.js?v=" + options.get("version");
if (both) {
return {
path: script,
versioned: versioned
};
}
return versioned;
}
};
/**
* @param options
* @returns {*[]}
*/
function getScriptArgs (options, scriptPath) {
var abspath = options.get("scheme") + "://HOST:" + options.get("port") + scriptPath;
return [
scriptPath,
options.get("port"),
options.set("absolute", abspath)
];
}
module.exports = connectUtils;

View File

@ -0,0 +1,546 @@
"use strict";
/**
* @module BrowserSync.options
*/
module.exports = {
/**
* Browsersync includes a user-interface that is accessed via a separate port.
* The UI allows to controls all devices, push sync updates and much more.
* @property ui
* @type Object
* @param {Number} [port=3001]
* @param {Number} [weinre.port=8080]
* @since 2.0.0
* @default false
*/
ui: {
port: 3001,
weinre: {
port: 8080
}
},
/**
* Browsersync can watch your files as you work. Changes you make will either
* be injected into the page (CSS & images) or will cause all browsers to do
* a full-page refresh.
* @property files
* @type Array|String
* @default false
*/
files: false,
/**
* Specify which file events to respond to.
* Available events: `add`, `change`, `unlink`, `addDir`, `unlinkDir`
* @property watchEvents
* @type Array
* @default ["change"]
* @since 2.18.8
*/
watchEvents: ["change"],
/**
* File watching options that get passed along to [Chokidar](https://github.com/paulmillr/chokidar).
* Check their docs for available options
* @property watchOptions
* @type Object
* @default undefined
* @since 2.6.0
*/
watchOptions: {
ignoreInitial: true
/*
persistent: true,
ignored: '*.txt',
followSymlinks: true,
cwd: '.',
usePolling: true,
alwaysStat: false,
depth: undefined,
interval: 100,
ignorePermissionErrors: false,
atomic: true
*/
},
/**
* Use the built-in static server for basic HTML/JS/CSS websites.
* @property server
* @type Object|Boolean
* @default false
*/
server: false,
/**
* Proxy an EXISTING vhost. Browsersync will wrap your vhost with a proxy URL to view your site.
* @property proxy
* @type String|Object|Boolean
* @param {String} [target]
* @param {Boolean} [ws] - Enable websocket proxying
* @param {Function|Array} [middleware]
* @param {Function} [reqHeaders]
* @param {Array} [proxyReq]
* @param {Array} [proxyRes]
* @default false
*/
proxy: false,
/**
* @property port
* @type Number
* @default 3000
*/
port: 3000,
/**
* @property middleware
* @type Function|Array
* @default false
*/
middleware: false,
/**
* Add additional directories from which static
* files should be served. Should only be used in `proxy` or `snippet`
* mode.
* @property serveStatic
* @type Array
* @default []
* @since 2.8.0
*/
serveStatic: [],
/**
* Options that are passed to the serve-static middleware
* when you use the string[] syntax: eg: `serveStatic: ['./app']`. Please see
* [serve-static](https://github.com/expressjs/serve-static) for details
*
* @property serveStaticOptions
* @type Object
* @since 2.17.0
*/
/**
* Enable https for localhost development. **Note** - this is not needed for proxy
* option as it will be inferred from your target url.
* @property https
* @type Boolean
* @default undefined
* @since 1.3.0
*/
/**
* Override http module to allow using 3rd party server modules (such as http2)
* @property httpModule
* @type string
* @default undefined
* @since 2.18.0
*/
/**
* Clicks, Scrolls & Form inputs on any device will be mirrored to all others.
* @property ghostMode
* @param {Boolean} [clicks=true]
* @param {Boolean} [scroll=true]
* @param {Boolean} [location=true]
* @param {Boolean} [forms=true]
* @param {Boolean} [forms.submit=true]
* @param {Boolean} [forms.inputs=true]
* @param {Boolean} [forms.toggles=true]
* @type Object
*/
ghostMode: {
clicks: true,
scroll: true,
location: true,
forms: {
submit: true,
inputs: true,
toggles: true
}
},
/**
* Can be either "info", "debug", "warn", or "silent"
* @property logLevel
* @type String
* @default info
*/
logLevel: "info",
/**
* Change the console logging prefix. Useful if you're creating your
* own project based on Browsersync
* @property logPrefix
* @type String
* @default BS
* @since 1.5.1
*/
logPrefix: "BS",
/**
* @property logConnections
* @type Boolean
* @default false
*/
logConnections: false,
/**
* @property logFileChanges
* @type Boolean
* @default true
*/
logFileChanges: true,
/**
* Log the snippet to the console when you're in snippet mode (no proxy/server)
* @property logSnippet
* @type: Boolean
* @default true
* @since 1.5.2
*/
logSnippet: true,
/**
* You can control how the snippet is injected
* onto each page via a custom regex + function.
* You can also provide patterns for certain urls
* that should be ignored from the snippet injection.
* @property snippetOptions
* @since 2.0.0
* @param {Boolean} [async] - should the script tags have the async attribute?
* @param {Array} [blacklist]
* @param {Array} [whitelist]
* @param {RegExp} [rule.match=/$/]
* @param {Function} [rule.fn=Function]
* @type Object
*/
snippetOptions: {
async: true,
whitelist: [],
blacklist: [],
rule: {
match: /<body[^>]*>/i,
fn: function (snippet, match) {
return match + snippet;
}
}
},
/**
* Add additional HTML rewriting rules.
* @property rewriteRules
* @since 2.4.0
* @type Array
* @default false
*/
rewriteRules: [],
/**
* @property tunnel
* @type String|Boolean
* @default null
*/
/**
* Some features of Browsersync (such as `xip` & `tunnel`) require an internet connection, but if you're
* working offline, you can reduce start-up time by setting this option to `false`
* @property online
* @type Boolean
* @default undefined
*/
/**
* Decide which URL to open automatically when Browsersync starts. Defaults to "local" if none set.
* Can be `true`, `local`, `external`, `ui`, `ui-external`, `tunnel` or `false`
* @property open
* @type Boolean|String
* @default true
*/
open: "local",
/**
* @property browser
* @type String|Array
* @default default
*/
browser: "default",
/**
* Add HTTP access control (CORS) headers to assets served by Browsersync.
* @property cors
* @type boolean
* @default false
* @since 2.16.0
*/
cors: false,
/**
* Requires an internet connection - useful for services such as [Typekit](https://typekit.com/)
* as it allows you to configure domains such as `*.xip.io` in your kit settings
* @property xip
* @type Boolean
* @default false
*/
xip: false,
hostnameSuffix: false,
/**
* Reload each browser when Browsersync is restarted.
* @property reloadOnRestart
* @type Boolean
* @default false
*/
reloadOnRestart: false,
/**
* The small pop-over notifications in the browser are not always needed/wanted.
* @property notify
* @type Boolean
* @default true
*/
notify: true,
/**
* @property scrollProportionally
* @type Boolean
* @default true
*/
scrollProportionally: true,
/**
* @property scrollThrottle
* @type Number
* @default 0
*/
scrollThrottle: 0,
/**
* Decide which technique should be used to restore
* scroll position following a reload.
* Can be `window.name` or `cookie`
* @property scrollRestoreTechnique
* @type String
* @default 'window.name'
*/
scrollRestoreTechnique: "window.name",
/**
* Sync the scroll position of any element
* on the page. Add any amount of CSS selectors
* @property scrollElements
* @type Array
* @default []
* @since 2.9.0
*/
scrollElements: [],
/**
* Sync the scroll position of any element
* on the page - where any scrolled element
* will cause all others to match scroll position.
* This is helpful when a breakpoint alters which element
* is actually scrolling
* @property scrollElementMapping
* @type Array
* @default []
* @since 2.9.0
*/
scrollElementMapping: [],
/**
* Time, in milliseconds, to wait before
* instructing the browser to reload/inject following a
* file change event
* @property reloadDelay
* @type Number
* @default 0
*/
reloadDelay: 0,
/**
* Wait for a specified window of event-silence before
* sending any reload events.
* @property reloadDebounce
* @type Number
* @default 0
* @since 2.6.0
*/
reloadDebounce: 0,
/**
* Emit only the first event during sequential time windows
* of a specified duration.
* @property reloadThrottle
* @type Number
* @default 0
* @since 2.13.0
*/
reloadThrottle: 0,
/**
* User provided plugins
* @property plugins
* @type Array
* @default []
* @since 2.6.0
*/
plugins: [],
/**
* @property injectChanges
* @type Boolean
* @default true
*/
injectChanges: true,
/**
* @property startPath
* @type String|Null
* @default null
*/
startPath: null,
/**
* Whether to minify client script, or not.
* @property minify
* @type Boolean
* @default true
*/
minify: true,
/**
* @property host
* @type String
* @default null
*/
host: null,
/**
* Support environments where dynamic hostnames are not required
* (ie: electron)
* @property localOnly
* @type Boolean
* @default false
* @since 2.14.0
*/
localOnly: false,
/**
* @property codeSync
* @type Boolean
* @default true
*/
codeSync: true,
/**
* @property timestamps
* @type Boolean
* @default true
*/
timestamps: true,
clientEvents: [
"scroll",
"scroll:element",
"input:text",
"input:toggles",
"form:submit",
"form:reset",
"click"
],
/**
* Alter the script path for complete control over where the Browsersync
* Javascript is served from. Whatever you return from this function
* will be used as the script path.
* @property scriptPath
* @default undefined
* @since 1.5.0
* @type Function
*/
/**
* Configure the Socket.IO path and namespace & domain to avoid collisions.
* @property socket
* @param {String} [path="/browser-sync/socket.io"]
* @param {String} [clientPath="/browser-sync"]
* @param {String|Function} [namespace="/browser-sync"]
* @param {String|Function} [domain=undefined]
* @param {String|Function} [port=undefined]
* @param {Object} [clients.heartbeatTimeout=5000]
* @since 1.6.2
* @type Object
*/
socket: {
socketIoOptions: {
log: false
},
socketIoClientConfig: {
reconnectionAttempts: 50
},
path: "/browser-sync/socket.io",
clientPath: "/browser-sync",
namespace: "/browser-sync",
clients: {
heartbeatTimeout: 5000
}
},
/**
* Configure the script domain
* @property script
* @param {String|Function} [domain=undefined]
* @since 2.14.0
* @type Object
*/
tagNames: {
"less": "link",
"scss": "link",
"css": "link",
"jpg": "img",
"jpeg": "img",
"png": "img",
"svg": "img",
"gif": "img",
"js": "script"
},
injectFileTypes: ["css", "png", "jpg", "jpeg", "svg", "gif", "webp", "map"],
excludedFileTypes: [
"js",
"css",
"pdf",
"map",
"svg",
"ico",
"woff",
"json",
"eot",
"ttf",
"png",
"jpg",
"jpeg",
"webp",
"gif",
"mp4",
"mp3",
"3gp",
"ogg",
"ogv",
"webm",
"m4a",
"flv",
"wmv",
"avi",
"swf",
"scss"
]
};

View File

@ -0,0 +1,120 @@
var utils = require("./utils");
/**
* Apply the operators that apply to the 'file:changed' event
* @param {Rx.Observable} subject
* @param options
* @return {Rx.Observable<{type: string, files: Array<any>}>}
*/
function fileChanges(subject, options) {
var operators = [
{
option: "reloadThrottle",
fnName: "throttle"
},
{
option: "reloadDelay",
fnName: "delay"
}
];
var scheduler = options.getIn(["debug", "scheduler"]);
/**
* if the 'reloadDebounce' option was provided, create
* a stream buffered/debounced stream of events
*/
var initial = (function() {
if (options.get("reloadDebounce") > 0) {
return getAggregatedDebouncedStream(subject, options, scheduler);
}
return subject;
})();
return applyOperators(operators, initial, options, scheduler)
.map(function(xs) {
var items = [].concat(xs);
var paths = items.map(function (x) { return x.path });
if (utils.willCauseReload(paths, options.get("injectFileTypes").toJS())) {
return {
type: "reload",
files: items
}
}
return {
type: "inject",
files: items
}
});
}
module.exports.fileChanges = fileChanges;
/**
* Apply the operators that apply to the 'browser:reload' event
* @param {Rx.Observable} subject
* @param options
* @returns {Rx.Observable}
*/
function applyReloadOperators (subject, options) {
var operators = [
{
option: "reloadDebounce",
fnName: "debounce"
},
{
option: "reloadThrottle",
fnName: "throttle"
},
{
option: "reloadDelay",
fnName: "delay"
}
];
return applyOperators(operators, subject, options, options.getIn(["debug", "scheduler"]));
}
module.exports.applyReloadOperators = applyReloadOperators;
/**
* @param items
* @param subject
* @param options
* @param scheduler
*/
function applyOperators (items, subject, options, scheduler) {
return items.reduce(function(subject, item) {
var value = options.get(item.option);
if (value > 0) {
return subject[item.fnName].call(subject, value, scheduler);
}
return subject;
}, subject);
}
/**
* @param subject
* @param options
* @param scheduler
*/
function getAggregatedDebouncedStream (subject, options, scheduler) {
return subject
.filter(function(x) { return options.get("watchEvents").indexOf(x.event) > -1 })
.buffer(subject.debounce(options.get("reloadDebounce"), scheduler))
.map(function(buffered) {
return buffered.reduce(function (acc, item) {
if (!acc[item.path]) acc[item.path] = item;
if (acc[item.path]) acc[item.path] = item;
return acc;
}, {});
})
.map(function(group) {
return Object
.keys(group)
.map(function(key) {
return group[key];
});
})
.filter(function (x) { return x.length })
}

View File

@ -0,0 +1,61 @@
"use strict";
var _ = require("../lodash.custom");
var fileUtils = {
/**
* React to file-change events that occur on "core" namespace only
* @param bs
* @param data
*/
changedFile: function (bs, data) {
/**
* If the event property is undefined, infer that it's a 'change'
* event due the fact this handler is for emitter.emit("file:changed")
*/
if (_.isUndefined(data.event)) {
data.event = "change";
}
/**
* Chokidar always sends an 'event' property - which could be
* `add` `unlink` etc etc so we need to check for that and only
* respond to 'change', for now.
*/
if (bs.options.get("watchEvents").indexOf(data.event) > -1) {
if (!bs.paused && data.namespace === "core") {
bs.events.emit("file:reload", fileUtils.getFileInfo(data, bs.options));
}
}
},
/**
* @param data
* @param options
* @returns {{assetFileName: *, fileExtension: String}}
*/
getFileInfo: function (data, options) {
data.ext = require("path").extname(data.path).slice(1);
data.basename = require("path").basename(data.path);
var obj = {
ext: data.ext,
path: data.path,
basename: data.basename,
event: data.event,
type: "inject"
};
// RELOAD page
if (!_.includes(options.get("injectFileTypes").toJS(), obj.ext)) {
obj.url = obj.path;
obj.type = "reload";
}
obj.path = data.path;
obj.log = data.log;
return obj;
}
};
module.exports = fileUtils;

View File

@ -0,0 +1,86 @@
"use strict";
var _ = require("../lodash.custom");
var utils = require("./utils");
var Rx = require("rx");
/**
* Plugin interface
* @returns {*|function(this:exports)}
*/
module.exports.plugin = function (bs) {
var options = bs.options;
var emitter = bs.emitter;
var defaultWatchOptions = options.get("watchOptions").toJS();
return options.get("files").reduce(function (map, glob, namespace) {
/**
* Default CB when not given
* @param event
* @param path
*/
var fn = function (event, path) {
emitter.emit("file:changed", {
event: event,
path: path,
namespace: namespace
});
};
var jsItem = glob.toJS();
if (jsItem.globs.length) {
var watcher = watch(jsItem.globs, defaultWatchOptions, fn);
map[namespace] = {
watchers: [watcher]
};
}
if (jsItem.objs.length) {
jsItem.objs.forEach(function (item) {
if (!_.isFunction(item.fn)) {
item.fn = fn;
}
var watcher = watch(item.match, item.options || defaultWatchOptions, item.fn.bind(bs.publicInstance));
if (!map[namespace]) {
map[namespace] = {
watchers: [watcher]
};
} else {
map[namespace].watchers.push(watcher);
}
});
}
return map;
}, {});
};
/**
* @param patterns
* @param opts
* @param cb
* @returns {*}
*/
function watch (patterns, opts, cb) {
if (typeof opts === "function") {
cb = opts;
opts = {};
}
var watcher = require("chokidar")
.watch(patterns, opts);
if (_.isFunction(cb)) {
watcher.on("all", cb);
}
return watcher;
}
module.exports.watch = watch;

View File

@ -0,0 +1,96 @@
"use strict";
var _ = require("../lodash.custom");
var Immutable = require("immutable");
var snippetUtils = require("./snippet").utils;
module.exports = {
/**
*
* @this {BrowserSync}
* @returns {String}
*/
"client:js": function (hooks, data) {
var js = snippetUtils.getClientJs(data.port, data.options);
return hooks.reduce(function (joined, hook) {
return joined + hook;
}, js);
},
/**
* @this {BrowserSync}
* @returns {Array}
*/
"client:events": function (hooks, clientEvents) {
hooks.forEach(function (hook) {
var result = hook(this);
if (Array.isArray(result)) {
clientEvents = _.union(clientEvents, result);
} else {
clientEvents.push(result);
}
}, this);
return clientEvents;
},
/**
* @returns {Array}
*/
"server:middleware": function (hooks, initial) {
initial = initial || [];
_.each(hooks, function (hook) {
var result = hook(this);
if (Array.isArray(result)) {
result.forEach(function (res) {
if (_.isFunction(res)) {
initial = initial.push(res);
}
});
} else {
if (_.isFunction(result)) {
initial = initial.push(result);
}
}
}, this);
return initial;
},
/**
* @param {Array} hooks
* @param {Map|List} initial
* @param pluginOptions
* @returns {any}
*/
"files:watch": function (hooks, initial, pluginOptions) {
var opts;
if (pluginOptions) {
opts = Immutable.fromJS(pluginOptions);
opts.forEach(function (value, key) {
if (!value) {
return;
}
var files = value.get("files");
if (files) {
var fileArg = require("./cli/cli-options").makeFilesArg(files);
if (fileArg) {
initial = initial.set(key, Immutable.fromJS(fileArg));
}
}
});
}
return initial;
}
};

View File

@ -0,0 +1,69 @@
"use strict";
var queryString = require("qs");
var proto = exports;
/**
* Use BrowserSync options + querystring to create a
* full HTTP/HTTTPS url.
*
* Eg. http://localhost:3000/__browser_sync__?method=reload
* Eg. http://localhost:3000/__browser_sync__?method=reload&args=core.css
* Eg. http://localhost:3000/__browser_sync__?method=reload&args=core.css&args=core.min
*
* @param args
* @param url
* @returns {string}
*/
proto.getUrl = function (args, url) {
return [
url,
require("./config").httpProtocol.path,
"?",
queryString.stringify(args)
].join("");
};
/**
* Return a middleware for handling the requests
* @param {BrowserSync} bs
* @returns {Function}
*/
proto.middleware = function (bs) {
return function (req, res) {
var params = queryString.parse(req.url.replace(/^.*\?/, ""));
var output;
if (!Object.keys(params).length) {
output = [
"Error: No Parameters were provided.",
"Example: http://localhost:3000/__browser_sync__?method=reload&args=core.css"
];
res.writeHead(500, {"Content-Type": "text/plain"});
res.end(output.join("\n"));
return;
}
try {
require("./public/" + params.method)(bs.events).apply(null, [params.args]);
output = [
"Called public API method `.%s()`".replace("%s", params.method),
"With args: " + JSON.stringify(params.args)
];
res.end(output.join("\n"));
} catch (e) {
res.writeHead(404, {"Content-Type": "text/plain"});
res.write("Public API method `" + params.method + "` not found.");
res.end();
return;
}
};
};

View File

@ -0,0 +1,110 @@
"use strict";
var utils = require("./utils");
var fileUtils = require("./file-utils");
var Rx = require("rx");
var fromEvent = Rx.Observable.fromEvent;
var fileHandler = require("./file-event-handler");
module.exports = function (bs) {
var events = {
/**
* File reloads
* @param data
*/
"file:reload": function (data) {
bs.io.sockets.emit("file:reload", data);
},
/**
* Browser Reloads
*/
"browser:reload": function () {
bs.io.sockets.emit("browser:reload");
},
/**
* Browser Notify
* @param data
*/
"browser:notify": function (data) {
bs.io.sockets.emit("browser:notify", data);
},
/**
* Things that happened after the service is running
* @param data
*/
"service:running": function (data) {
var mode = bs.options.get("mode");
var open = bs.options.get("open");
if (mode === "proxy" || mode === "server" || open === "ui" || open === "ui-external") {
utils.openBrowser(data.url, bs.options, bs);
}
// log about any file watching
if (bs.watchers) {
bs.events.emit("file:watching", bs.watchers);
}
},
/**
* Option setting
* @param data
*/
"options:set": function (data) {
if (bs.io) {
bs.io.sockets.emit("options:set", data);
}
},
/**
* Plugin configuration setting
* @param data
*/
"plugins:configure": function (data) {
if (data.active) {
bs.pluginManager.enablePlugin(data.name);
} else {
bs.pluginManager.disablePlugin(data.name);
}
bs.setOption("userPlugins", bs.getUserPlugins());
},
"plugins:opts": function (data) {
if (bs.pluginManager.pluginOptions[data.name]) {
bs.pluginManager.pluginOptions[data.name] = data.opts;
bs.setOption("userPlugins", bs.getUserPlugins());
}
}
};
Object.keys(events).forEach(function (event) {
bs.events.on(event, events[event]);
});
var reloader = fileHandler.applyReloadOperators(fromEvent(bs.events, "_browser:reload"), bs.options)
.subscribe(function() {
bs.events.emit("browser:reload");
});
var coreNamespacedWatchers = fromEvent(bs.events, "file:changed")
.filter(function() { return bs.options.get("codeSync") })
.filter(function(x) { return x.namespace === "core" });
var handler = fileHandler.fileChanges(coreNamespacedWatchers, bs.options)
.subscribe(function (x) {
if (x.type === "reload") {
bs.events.emit("browser:reload");
}
if (x.type === "inject") {
x.files.forEach(function(data) {
if (!bs.paused && data.namespace === "core") {
bs.events.emit("file:reload", fileUtils.getFileInfo(data, bs.options));
}
});
}
});
bs.registerCleanupTask(function() {
handler.dispose();
reloader.dispose();
});
};

View File

@ -0,0 +1,288 @@
"use strict";
var messages = require("./connect-utils");
var utils = require("./utils");
var _ = require("../lodash.custom");
var template = "[{blue:%s}] ";
var logger = require("eazy-logger").Logger({
prefix: template.replace("%s", "BS"),
useLevelPrefixes: false
});
module.exports.logger = logger;
/**
* @param name
* @returns {*}
*/
module.exports.getLogger = function (name) {
return logger.clone(function (config) {
config.prefix = config.prefix + template.replace("%s", name);
return config;
});
};
/**
* Logging Callbacks
*/
module.exports.callbacks = {
/**
* Log when file-watching has started
* @param {BrowserSync} bs
* @param data
*/
"file:watching": function (bs, data) {
if (Object.keys(data).length) {
logger.info("Watching files...");
}
},
/**
* Log when a file changes
* @param {BrowserSync} bs
* @param data
*/
"file:reload": function (bs, data) {
if (canLogFileChange(bs, data)) {
if (data.path[0] === "*") {
return logger.info("{cyan:Reloading files that match: {magenta:%s", data.path);
}
logger.info("{cyan:File event [" + data.event + "] : {magenta:%s", data.path);
}
},
/**
*
*/
"service:exit": function () {
logger.debug("Exiting...");
},
/**
*
*/
"browser:reload": function (bs) {
if (canLogFileChange(bs)) {
logger.info("{cyan:Reloading Browsers...");
}
},
/**
*
*/
"browser:error": function () {
logger.error("Couldn't open browser (if you are using BrowserSync in a headless environment, you might want to set the {cyan:open} option to {cyan:false})");
},
/**
* @param {BrowserSync} bs
* @param data
*/
"stream:changed": function (bs, data) {
if (canLogFileChange(bs)) {
var changed = data.changed;
logger.info("{cyan:%s %s changed} ({magenta:%s})",
changed.length,
changed.length > 1 ? "files" : "file",
changed.join(", ")
);
}
},
/**
* Client connected logging
* @param {BrowserSync} bs
* @param data
*/
"client:connected": function (bs, data) {
var uaString = utils.getUaString(data.ua);
var msg = "{cyan:Browser Connected: {magenta:%s, version: %s}";
var method = "info";
if (!bs.options.get("logConnections")) {
method = "debug";
}
logger.log(method, msg,
uaString.name,
uaString.version
);
},
/**
* Main logging when the service is running
* @param {BrowserSync} bs
* @param data
*/
"service:running": function (bs, data) {
var type = data.type;
if (type === "server") {
var baseDir = bs.options.getIn(["server", "baseDir"]);
logUrls(bs.options.get("urls").toJS());
if (baseDir) {
if (utils.isList(baseDir)) {
baseDir.forEach(serveFiles);
} else {
serveFiles(baseDir);
}
}
}
if (type === "proxy") {
logger.info("Proxying: {cyan:%s}", bs.options.getIn(["proxy", "target"]));
logUrls(bs.options.get("urls").toJS());
}
if (type === "snippet") {
if (bs.options.get("logSnippet")) {
logger.info(
"{bold:Copy the following snippet into your website, " +
"just before the closing {cyan:</body>} tag"
);
logger.unprefixed("info",
messages.scriptTags(bs.options)
);
}
logUrls(bs.options.get("urls").filter(function (value, key) {
return key.slice(0, 2) === "ui";
}).toJS());
}
function serveFiles (base) {
logger.info("Serving files from: {magenta:%s}", base);
}
}
};
/**
* Plugin interface for BrowserSync
* @param {EventEmitter} emitter
* @param {BrowserSync} bs
* @returns {Object}
*/
module.exports.plugin = function (emitter, bs) {
var logPrefix = bs.options.get("logPrefix");
var logLevel = bs.options.get("logLevel");
// Should set logger level here!
logger.setLevel(logLevel);
if (logPrefix) {
if (_.isFunction(logPrefix)) {
logger.setPrefix(logPrefix);
} else {
logger.setPrefix(template.replace("%s", logPrefix));
}
}
_.each(exports.callbacks, function (func, event) {
emitter.on(event, func.bind(this, bs));
});
return logger;
};
/**
*
* @param urls
*/
function logUrls (urls) {
var keys = Object.keys(urls);
var longestName = 0;
var longesturl = 0;
var offset = 2;
if (!keys.length) {
return;
}
var names = keys.map(function (key) {
if (key.length > longestName) {
longestName = key.length;
}
if (urls[key].length > longesturl) {
longesturl = urls[key].length;
}
return key;
});
var underline = getChars(longestName + offset + longesturl + 1, "-");
var underlined = false;
logger.info("{bold:Access URLs:");
logger.unprefixed("info", "{grey: %s", underline);
keys.forEach(function (key, i) {
var keyname = getKeyName(key);
logger.unprefixed("info", " %s: {magenta:%s}",
getPadding(key.length, longestName + offset) + keyname,
urls[key]
);
if (!underlined && names[i + 1] && names[i + 1].indexOf("ui") > -1) {
underlined = true;
logger.unprefixed("info", "{grey: %s}", underline);
}
});
logger.unprefixed("info", "{grey: %s}", underline);
}
/**
* @param {Number} len
* @param {Number} max
* @returns {string}
*/
function getPadding (len, max) {
return new Array(max - (len + 1)).join(" ");
}
/**
* @param {Number} len
* @param {String} char
* @returns {string}
*/
function getChars (len, char) {
return new Array(len).join(char);
}
/**
* Transform url-key names into something more presentable
* @param key
* @returns {string}
*/
function getKeyName(key) {
if (key.indexOf("ui") > -1) {
if (key === "ui") {
return "UI";
}
if (key === "ui-external") {
return "UI External";
}
}
return key.substr(0, 1).toUpperCase() + key.substring(1);
}
/**
* Determine if file changes should be logged
* @param bs
* @param data
* @returns {boolean}
*/
function canLogFileChange(bs, data) {
if (data && data.log === false) {
return false;
}
return bs.options.get("logFileChanges");
}

View File

@ -0,0 +1,250 @@
"use strict";
var _ = require("../lodash.custom");
var Immutable = require("immutable");
var defaultConfig = require("./default-config");
/**
* @param {Map} options
* @returns {Map}
*/
module.exports.update = function (options) {
return options.withMutations(function (item) {
setMode(item);
setScheme(item);
setStartPath(item);
setProxyWs(item);
setServerOpts(item);
setNamespace(item);
fixSnippetOptions(item);
fixRewriteRules(item);
setMiddleware(item);
setOpen(item);
if (item.get("uiPort")) {
item.setIn(["ui", "port"], item.get("uiPort"));
}
});
};
/**
* Move top-level ws options to proxy.ws
* This is to allow it to be set from the CLI
* @param item
*/
function setProxyWs(item) {
if (item.get("ws") && item.get("mode") === "proxy") {
item.setIn(["proxy", "ws"], true);
}
}
/**
* @param item
*/
function setOpen (item) {
var open = item.get("open");
if (item.get("mode") === "snippet") {
if (open !== "ui" && open !== "ui-external") {
item.set("open", false);
}
}
}
/**
* Set the running mode
* @param item
*/
function setMode (item) {
item.set("mode", (function () {
if (item.get("server")) {
return "server";
}
if (item.get("proxy")) {
return "proxy";
}
return "snippet";
})());
}
/**
* @param item
*/
function setScheme (item) {
var scheme = "http";
if (item.getIn(["server", "https"])) {
scheme = "https";
}
if (item.get("https")) {
scheme = "https";
}
if (item.getIn(["proxy", "url", "protocol"])) {
if (item.getIn(["proxy", "url", "protocol"]) === "https:") {
scheme = "https";
}
}
item.set("scheme", scheme);
}
/**
* @param item
*/
function setStartPath (item) {
if (item.get("proxy")) {
var path = item.getIn(["proxy", "url", "path"]);
if (path !== "/") {
item.set("startPath", path);
}
}
}
/**
* @param item
*/
function setNamespace(item) {
var namespace = item.getIn(["socket", "namespace"]);
if (_.isFunction(namespace)) {
item.setIn(["socket", "namespace"], namespace(defaultConfig.socket.namespace));
}
}
/**
* @param item
*/
function setServerOpts(item) {
if (item.get("server")) {
var indexarg = item.get("index") || item.getIn(["server", "index"]) || "index.html";
var optPath = ["server", "serveStaticOptions"];
if (item.get("directory")) {
item.setIn(["server", "directory"], true);
}
if (!item.getIn(optPath)) {
item.setIn(optPath, Immutable.Map({
index: indexarg
}));
} else {
if (!item.hasIn(optPath.concat(["index"]))) {
item.setIn(optPath.concat(["index"]), indexarg);
}
}
// cli extensions
if (item.get("extensions")) {
item.setIn(optPath.concat(["extensions"]), item.get("extensions"));
}
}
}
/**
* Back-compat fixes for rewriteRules being set to a boolean
*/
function fixRewriteRules (item) {
return item.update("rewriteRules", function (rr) {
return Immutable.List([]).concat(rr).filter(Boolean)
});
}
function fixSnippetOptions (item) {
var ignorePaths = item.getIn(["snippetOptions", "ignorePaths"]);
var includePaths = item.getIn(["snippetOptions", "whitelist"]);
if (ignorePaths) {
if (_.isString(ignorePaths)) {
ignorePaths = [ignorePaths];
}
ignorePaths = ignorePaths.map(ensureSlash);
item.setIn(["snippetOptions", "blacklist"], Immutable.List(ignorePaths));
}
if (includePaths) {
includePaths = includePaths.map(ensureSlash);
item.setIn(["snippetOptions", "whitelist"], Immutable.List(includePaths));
}
}
/**
* Enforce paths to begin with a forward slash
*/
function ensureSlash (item) {
if (item[0] !== "/") {
return "/" + item;
}
return item;
}
/**
*
*/
function setMiddleware (item) {
var mw = getMiddlwares(item);
item.set("middleware", mw);
}
/**
* top-level option, or given as part of the proxy/server option
* @param item
* @returns {*}
*/
function getMiddlwares (item) {
var mw = item.get("middleware");
var serverMw = item.getIn(["server", "middleware"]);
var proxyMw = item.getIn(["proxy", "middleware"]);
var list = Immutable.List([]);
if (mw) {
return listMerge(list, mw);
}
if (serverMw) {
return listMerge(list, serverMw);
}
if (proxyMw) {
return listMerge(list, proxyMw);
}
return list;
}
/**
* @param item
* @returns {*}
*/
function isList (item) {
return Immutable.List.isList(item);
}
/**
* @param list
* @param item
* @returns {*}
*/
function listMerge(list, item) {
if (_.isFunction(item)) {
list = list.push(item);
}
if (isList(item) && item.size) {
list = list.merge(item);
}
return list;
}

View File

@ -0,0 +1,185 @@
var Immutable = require("immutable");
var Map = Immutable.Map;
var isMap = Immutable.Map.isMap;
var List = Immutable.List;
var qs = require("qs");
var path = require("path");
var fs = require("fs");
var Plugin = Immutable.Record({
moduleName: "",
name: "",
active: true,
module: undefined,
options: Map({}),
via: "inline",
dir: process.cwd(),
init: undefined,
errors: List([])
});
/**
* Accept a string/object
* and resolve it into the plugin format above
* @param item
* @returns {*}
*/
function resolvePlugin(item) {
/**
* Handle when string was given, such as plugins: ['bs-html-injector']
*/
if (typeof item === "string") {
return getFromString(item);
}
if (!isMap(item)) {
return new Plugin().mergeDeep({errors: [new Error("Plugin not supported in this format")]});
}
if (item.has("module")) {
var nameOrObj = item.get("module");
var options = item.get("options");
/**
* The 'module' key can be a string, this allows
* inline plugin references, but with options
* eg:
*
* bs.init({
* plugins: [
* {
* module: './myjs-file.js'
* options: {
* files: "*.html"
* }
* }
* ]
* });
*/
if (typeof nameOrObj === "string") {
return getFromString(nameOrObj)
.mergeDeep({
options: options
});
}
/**
* If the plugin was given completely inline (because it needs options)
* eg:
*
* bs.init({
* plugins: [
* {
* module: {
* plugin: function() {
* console.log('My plugin code')
* }
* },
* options: {
* files: "*.html"
* }
* }
* ]
* })
*/
if (Immutable.Map.isMap(nameOrObj)) {
return new Plugin({
module: nameOrObj,
options: options
});
}
}
/**
* If a module was given directly. For example, ater calling require.
*
* eg:
* var myplugin = require('./some-js');
* bs.init({plugins: [myplugin]});
*/
if (item.has("plugin")) {
return new Plugin({
module: item
})
}
/**
* If we reach here, the plugin option was used incorrectly
*/
return new Plugin().mergeDeep({errors: [new Error("Plugin was not configured correctly")]})
}
module.exports.resolvePlugin = resolvePlugin;
/**
* Load a plugin from disk
* @param item
* @returns {*}
*/
function requirePlugin (item) {
/**
* if the "module" property already exists and
* is not a string, then we bail and don't bother looking
* for the file
*/
if (item.get("module") && typeof item.get("module") !== "string") {
return item;
}
try {
/**
* Try a raw node require() call - this will be how
* regular "npm installed" plugins wil work
*/
var maybe = path.resolve(process.cwd(), "node_modules", item.get("name"));
return item.set("module", require(maybe));
} catch (e) {
/**
* If require threw an MODULE_NOT_FOUND error, try again
* by resolving from cwd. This is needed since cli
* users will not add ./ to the front of a path (which
* node requires to resolve from cwd)
*/
if (e.code === "MODULE_NOT_FOUND") {
var maybe = path.resolve(process.cwd(), item.get("name"));
if (fs.existsSync(maybe)) {
return item.set("module", require(maybe));
} else {
/**
* Finally return a plugin that contains the error
* this will be picked up later and discarded
*/
return item.update("errors", function (errors) {
return errors.concat(e);
});
}
}
throw e;
}
}
module.exports.requirePlugin = requirePlugin;
function getFromString(string) {
/**
* We allow query strings for plugins, so always split on ?
*/
var split = string.split("?");
var outGoing = new Plugin({
moduleName: split[0],
name: split[0]
});
if (split.length > 1) {
return outGoing.update("options", function (opts) {
return opts.mergeDeep(qs.parse(split[1]));
});
}
return outGoing;
}

View File

@ -0,0 +1,17 @@
"use strict";
/**
* @param {BrowserSync} browserSync
* @returns {Function}
*/
module.exports = function (browserSync) {
function exit() {
if (browserSync.active) {
browserSync.events.emit("service:exit");
browserSync.cleanup();
}
}
return exit;
};

View File

@ -0,0 +1,32 @@
"use strict";
var _ = require("../../lodash.custom");
var merge = require("../cli/cli-options").merge;
/**
* @param {BrowserSync} browserSync
* @param {String} [name] - instance name
* @param {Object} pjson
* @returns {Function}
*/
module.exports = function (browserSync, name, pjson) {
return function () {
/**
* Handle new + old signatures for init.
*/
var args = require("../args")(_.toArray(arguments));
/**
* If the current instance is already running, just return an error
*/
if (browserSync.active) {
return args.cb(new Error("Instance: " + name + " is already running!"));
}
args.config.version = pjson.version;
return browserSync.init(merge(args.config), args.cb);
};
};

View File

@ -0,0 +1,19 @@
"use strict";
/**
* @param {BrowserSync} browserSync
* @returns {Function}
*/
module.exports = function (browserSync) {
return function (msg, timeout) {
if (msg) {
browserSync.events.emit("browser:notify", {
message: msg,
timeout: timeout || 2000,
override: true
});
}
};
};

View File

@ -0,0 +1,12 @@
"use strict";
/**
* @param {BrowserSync} browserSync
* @returns {Function}
*/
module.exports = function (browserSync) {
return function () {
browserSync.paused = true;
};
};

View File

@ -0,0 +1,65 @@
"use strict";
var _ = require("../../lodash.custom");
module.exports = {
/**
* Emit the internal `file:change` event
* @param {EventEmitter} emitter
* @param {string} path
* @param {boolean} [log]
*/
emitChangeEvent: function emitChangeEvent (emitter, path, log) {
emitter.emit("file:changed", {
path: path,
log: log,
namespace: "core",
event: "change"
});
},
/**
* Emit the internal `browser:reload` event
* @param {EventEmitter} emitter
*/
emitBrowserReload: function emitChangeEvent (emitter) {
emitter.emit("_browser:reload");
},
/**
* Emit the internal `stream:changed` event
* @param {EventEmitter} emitter
* @param {Array} changed
*/
emitStreamChangedEvent: function (emitter, changed) {
emitter.emit("stream:changed", {changed: changed});
},
/**
* This code handles the switch between .reload & .stream
* since 2.6.0
* @param name
* @param args
* @returns {boolean}
*/
isStreamArg: function (name, args) {
if (name === "stream") {
return true;
}
if (name !== "reload") {
return false;
}
var firstArg = args[0];
/**
* If here, it's reload with args
*/
if (_.isObject(firstArg)) {
if (!Array.isArray(firstArg) && Object.keys(firstArg).length) {
return firstArg.stream === true;
}
}
return false;
}
};

View File

@ -0,0 +1,67 @@
"use strict";
var utils = require("../utils");
var publicUtils = require("./public-utils");
var _ = require("../../lodash.custom");
var defaultConfig = require("../default-config");
var stream = require("./stream");
/**
* @param emitter
* @returns {Function}
*/
module.exports = function (emitter) {
/**
* Inform browsers about file changes.
*
* eg: reload("core.css")
*/
function browserSyncReload (opts) {
/**
* BACKWARDS COMPATIBILITY:
* Passing an object as the only arg to the `reload`
* method with at *least* the key-value pair of {stream: true},
* was only ever used for streams support - so it's safe to check
* for that signature here and defer to the
* dedicated `.stream()` method instead.
*/
if (_.isObject(opts)) {
if (!Array.isArray(opts) && Object.keys(opts).length) {
if (opts.stream === true) {
return stream(emitter)(opts);
}
}
}
/**
* Handle single string paths such as
* reload("core.css")
*/
if (typeof opts === "string" && opts !== "undefined") {
return publicUtils.emitChangeEvent(emitter, opts, true);
}
/**
* Handle an array of file paths such as
* reload(["core.css, "ie.css"])
*/
if (Array.isArray(opts)) {
return opts.forEach(function (filepath) {
publicUtils.emitChangeEvent(emitter, filepath, true);
});
}
/**
* At this point the argument given was neither an object,
* array or string so we simply perform a reload. This is to
* allow the following syntax to work as expected
*
* reload();
*/
return publicUtils.emitBrowserReload(emitter);
}
return browserSyncReload;
};

View File

@ -0,0 +1,12 @@
"use strict";
/**
* @param {BrowserSync} browserSync
* @returns {Function}
*/
module.exports = function (browserSync) {
return function () {
browserSync.paused = false;
};
};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,98 @@
"use strict";
var path = require("path");
var micromatch = require("micromatch");
var utils = require("./public-utils");
/**
* @param emitter
* @returns {Function}
*/
module.exports = function (emitter) {
/**
* Return a transform/through stream that listens to file
* paths and fires internal Browsersync events.
* @param {{once: boolean, match: string|array}} [opts]
* @returns {Stream.Transform}
*/
function browserSyncThroughStream (opts) {
opts = opts || {};
var emitted = false;
var Transform = require("stream").Transform;
var reload = new Transform({objectMode: true});
var changed = [];
reload._transform = function (file, encoding, next) {
var stream = this;
/**
* End is always called to send the current file down
* stream. Browsersync never acts upon a stream,
* we only `listen` to it.
*/
function end () {
stream.push(file); // always send the file down-stream
next();
}
/**
* If {match: <pattern>} was provided, test the
* current filepath against it
*/
if (opts.match) {
if (!micromatch(file.path, opts.match, {dot: true}).length) {
return end();
}
}
/**
* if {once: true} provided, emit the reload event for the
* first file only
*/
if (opts.once === true && !emitted) {
utils.emitBrowserReload(emitter);
emitted = true;
} else { // handle multiple
if (opts.once === true && emitted) {
} else {
if (file.path) {
emitted = true;
utils.emitChangeEvent(emitter, file.path, false);
changed.push(path.basename(file.path));
}
}
}
end();
};
/**
* When this current operation has finished, emit the
* steam:changed event so that any loggers can pick up it
* @param next
* @private
*/
reload._flush = function (next) {
if (changed.length) {
utils.emitStreamChangedEvent(emitter, changed);
}
next();
};
return reload;
}
return browserSyncThroughStream;
};

View File

@ -0,0 +1,6 @@
openssl genrsa -des3 -out server.key 2048
openssl req -new -key server.key -out server.csr
openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt
cp server.key server.key.copy
openssl rsa -in server.key.copy -out server.key
rm server.key.copy

View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDBjCCAe4CCQCir/8eGDIE/jANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJH
QjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTE3MDQxMDExNDcyNloXDTI3MDQwODExNDcyNlowRTELMAkG
A1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AMRLR2crKB4X/9pM3gR641iDscZWW3aqo70nUDxzo5Bhk8uupqz0EfdRoCLCUeQi
xVp3HJ1HqnilMW7dETGGkDHKdxJRjrkBrYHhE3Kw/LCC4tEb400F6Ikm6OudVPIB
P+CuwfNAw70KHSx/CtIrbTz0HhDC6XN0azp39pDLRBnWWluz3iU+rFLMx7YT2Q8k
1nQAwcXkzLjeU7txAt2pYGQUgvBQETO5RI7QQ0CmwaV4gfHWGABBTX34WQun7g1Q
YukrG3/4fVeNLzGW787FKCvL07BTymJTwXXbTTPXg4chw9p+YkLLPrr+AOVe/PF1
MJppDT3gKdKMHFo3vMycUf0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAEVmUYRrT
fCQmBDox3evqj4n8dJVStjgdw/7BPkm49SmGtaxsST/eUSGwT7dTvZBLov6BK/OW
+arhHZIvUY/DXlsV4NfCM4TK8KVefrwgd8ZsfQJW73L+FB0IOAY/6s+YKHm/wQGF
ptSOycJvEltsqfIegtYcvvD6c6SkSOvqApaF+Ai10+yiLe20KyOvM3PefZLV7mFE
0zCNyglZ75HftvHHV0wh82T2Et/R+txH+6dTwh065Dd6rrDzljtcAd2HC7B26ERK
dA2zJd9Y4eMz8osacmG/afVuR9rqtFGwdyZ1Kb5xQRzGWlrjvSmAFUx9W9iA4Ilv
3+56a5njSTFYKw==
-----END CERTIFICATE-----

View File

@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICoTCCAYkCAQAwRTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUx
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMRLR2crKB4X/9pM3gR641iDscZWW3aqo70nUDxz
o5Bhk8uupqz0EfdRoCLCUeQixVp3HJ1HqnilMW7dETGGkDHKdxJRjrkBrYHhE3Kw
/LCC4tEb400F6Ikm6OudVPIBP+CuwfNAw70KHSx/CtIrbTz0HhDC6XN0azp39pDL
RBnWWluz3iU+rFLMx7YT2Q8k1nQAwcXkzLjeU7txAt2pYGQUgvBQETO5RI7QQ0Cm
waV4gfHWGABBTX34WQun7g1QYukrG3/4fVeNLzGW787FKCvL07BTymJTwXXbTTPX
g4chw9p+YkLLPrr+AOVe/PF1MJppDT3gKdKMHFo3vMycUf0CAwEAAaAXMBUGCSqG
SIb3DQEJBzEIDAYxMjM0NTYwDQYJKoZIhvcNAQELBQADggEBABlVUaWK/UUovgPZ
+rqNG8/j6aggSCCye9CkauLB/WqhQFfLl9lWTYdUVmWweNU0SJwDU9lWF/TngipF
RZs6501DkXKxaDT9/3JYg4lRz6zHLy+a77pavJOeN0+cFAPZZuGyxZvYHFYPVSVH
EeJL6bO/nZ/ARgIp0YNkQblDarxq1ARj7YT1Z24D5KgO1kQ55+fCY/wtct8TLGk9
ujvWBbFEBSrJCDjRQeSctlP5qG8yfeJqZwZzo4PQMUwABhNLGUz0z0ceK8W1QnOm
T+nCN5Fni04wO4dAZvYPp6wmU0Gpi2XBihbFl9CT3B5AmJmM8hQVpuQlmXeXT0FO
pOZFpko=
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAxEtHZysoHhf/2kzeBHrjWIOxxlZbdqqjvSdQPHOjkGGTy66m
rPQR91GgIsJR5CLFWnccnUeqeKUxbt0RMYaQMcp3ElGOuQGtgeETcrD8sILi0Rvj
TQXoiSbo651U8gE/4K7B80DDvQodLH8K0ittPPQeEMLpc3RrOnf2kMtEGdZaW7Pe
JT6sUszHthPZDyTWdADBxeTMuN5Tu3EC3algZBSC8FARM7lEjtBDQKbBpXiB8dYY
AEFNffhZC6fuDVBi6Ssbf/h9V40vMZbvzsUoK8vTsFPKYlPBddtNM9eDhyHD2n5i
Qss+uv4A5V788XUwmmkNPeAp0owcWje8zJxR/QIDAQABAoIBADbeT/wvnQwkazkL
CXg5HXltfnDRTMmz0wcZiR0MueiuzdA+ZoqrwqXeJCPzK07YxU+PQelY0fbdPh8e
HiM42O+CB5yQPZPLO0O1tWj2vftc6qfG4tdx0lkcDjlmBguLe96DGuWy8cPSousA
K/cpemRyXEEVKopCPYLfa4V3u/Z4be2U/39KNjVkHFhSdSYQl6ferhEfUPwTPi7O
7l1/QUBabqN5FzNc2TeMVhhcJkXtYqF3RxGsaRfT0lK/j2hpbX7Bn2T0CfA/40jY
2OCERqFPfZWx/ShTT52b3fyX/FEua7Nukq/MZdYZou63dDIjCQQyTJSflX6lVojO
SuUoumECgYEA6CSkLiKcRLlTfec3LkjqkWtXR5ibL33g/H1fsZEQKFOyMbIXpUkX
Hybpku8NGeetjKynO3yRirp+NiBHGPn3cHc9WJ5GGG1ew9hRQ9QzyC3Tit15TDbu
J8i50/MaQHZSiUCnPQ/ceIZCNz8STcsEz87o/7utRLJKOvIIAPj+/8kCgYEA2Hd/
v5oUroMRbtzPtMJDMHiGEQyNxEGDNqcuxgXSmiEEqPLfk2qR3yLffzA9UQOg4wkX
/dSXsyomPriKWTvADXu1lNdkPGmW/1tk+onnHu6qgOalva30ZKhtteVjUqxEJEke
mHhNHyIVuj6lExLw9LZhVvzoOi+aj4AD+DRS4pUCgYBEtuveOCJ3eUAMiY9c5PqB
9vsL11FAOouJUXcs8VqOBVA+w4+aPktYzkTfWGFRZLGLbWPHCPVv0gof7Wf+Laef
o7wF6junaWBeqj5LzJlTTLVMaohIFg5iuli/Mzt3D08ZD4kxWuuQxXT+M24wlsKi
3IU9hYkhR4EPd6sE1q9seQKBgEpQRBAgModywbJgpgH1SyHBzqzdtXGx1/0USg97
gkCdoz7pGm4+gNOs4jOE+Rft+fbXcWAX8vh0OOsBaaWWyKkYVk9B3syKp2cFFlaY
rzrETs6v4CiNJsDDvd5bYMzKDR6z54gKjNdqWTE2Pm+c6hHo5uP5MTSAkTxAg5xb
QjU9AoGAaYPXlm3IKVO12FgNg/ffduooi0PKa1yRNJGnhpQKNvBQXs8eV+CQ83aK
kQHUExuJDrOfsC2iwF/2ZywXhEfbhL7ar0aw5zrhV+r7qvYFWxu/YoLoNVMDByw5
wAN0oIbsGWYmtIIti8+b9IcacTbAZ79ctlTLb1HCyPMosHxDkv8=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,95 @@
"use strict";
var enableDestroy = require("server-destroy");
var _ = require("../../lodash.custom");
/**
* Browsersync server
* Three available modes: Snippet, Server or Proxy
*/
module.exports.plugin = function (bs) {
var debug = bs.debug;
var proxy = bs.options.get("proxy");
var type = bs.options.get("mode");
var bsServer = createServer(bs);
if (type === "server" || type === "snippet") {
debug("Static Server running ({magenta:%s}) ...", bs.options.get("scheme"));
}
if (proxy) {
debug("Proxy running, proxing: {magenta:%s}", proxy.get("target"));
}
if (bsServer) {
/**
* Allow server to be destroyed gracefully
*/
enableDestroy(bsServer.server);
/**
* Listen on the available port
*/
bsServer.server.listen(bs.options.get("port"));
/**
* Hack to deal with https://github.com/socketio/socket.io/issues/1602#issuecomment-224270022
*/
bs.registerCleanupTask(function () {
if (bs.io && bs.io.sockets) {
setCloseReceived(bs.io.sockets);
}
if (bs.ui && bs.ui.socket) {
setCloseReceived(bs.ui.socket);
}
});
/**
* Destroy the server on cleanup
*/
bs.registerCleanupTask(function () {
bsServer.server.destroy();
});
}
function setCloseReceived(io) {
Object.keys(io.sockets).forEach(function (key) {
_.set(io.sockets[key], "conn.transport.socket._closeReceived", true);
});
}
debug("Running mode: %s", type.toUpperCase());
return {
server: bsServer.server,
app: bsServer.app
};
};
/**
* Launch the server for serving the client JS plus static files
* @param {BrowserSync} bs
* @returns {{staticServer: (http.Server), proxyServer: (http.Server)}}
*/
function createServer (bs) {
var proxy = bs.options.get("proxy");
var server = bs.options.get("server");
if (!proxy && !server) {
return require("./snippet-server")(bs);
}
if (proxy) {
return require("./proxy-server")(bs);
}
if (server) {
return require("./static-server")(bs);
}
}
module.exports.createServer = createServer;

View File

@ -0,0 +1,195 @@
"use strict";
var httpProxy = require("http-proxy");
var utils = require("./utils");
var proxyUtils = require("./proxy-utils");
var Immutable = require("immutable");
var Map = require("immutable").Map;
var List = require("immutable").List;
/**
* Default options that are passed along to http-proxy
*/
var defaultHttpProxyOptions = Map({
/**
* This ensures targets are more likely to
* accept each request
*/
changeOrigin: true,
/**
* This handles redirects
*/
autoRewrite: true,
/**
* This allows our self-signed certs to be used for development
*/
secure: false,
ws: true
});
var defaultCookieOptions = Map({
stripDomain: true
});
var ProxyOption = Immutable.Record({
route: "",
target: "",
rewriteRules: true,
/**
* Functions to be called on proxy request
* with args [proxyReq, req, res, options]
*/
proxyReq: List([]),
/**
* Functions to be called on proxy response
* with args [proxyRes, req, res]
*/
proxyRes: List([]),
/**
* Functions to be called on proxy response
* with args [proxyReq, req, socket, options, head]
*/
proxyReqWs: List([]),
errHandler: undefined,
url: Map({}),
proxyOptions: Map(defaultHttpProxyOptions),
cookies: Map(defaultCookieOptions),
ws: false,
middleware: List([]),
reqHeaders: undefined
});
/**
* @param {BrowserSync} bs
* @param {String} scripts
* @returns {*}
*/
module.exports = function createProxyServer (bs) {
var opt = new ProxyOption().mergeDeep(bs.options.get("proxy"));
var proxy = httpProxy.createProxyServer(opt.get("proxyOptions").set("target", opt.get("target")).toJS());
var target = opt.get("target");
var proxyReq = getProxyReqFunctions(opt.get("proxyReq"), opt, bs);
var proxyRes = getProxyResFunctions(opt.get("proxyRes"), opt);
var proxyResWs = opt.get("proxyReqWs");
bs.options = bs.options.update("middleware", function (mw) {
return mw.concat({
id: "Browsersync Proxy",
route: opt.get("route"),
handle: function (req, res) {
proxy.web(req, res, {
target: target
});
}
});
});
var app = utils.getBaseApp(bs);
/**
* @type {*|{server, app}}
*/
var browserSyncServer = utils.getServer(app, bs.options);
browserSyncServer.proxy = proxy;
if (opt.get("ws")) {
// debug(`+ ws upgrade for: ${x.get("target")}`);
browserSyncServer.server.on("upgrade", function (req, socket, head) {
proxy.ws(req, socket, head);
});
}
/**
* Add any user provided functions for proxyReq, proxyReqWs and proxyRes
*/
applyFns("proxyReq", proxyReq);
applyFns("proxyRes", proxyRes);
applyFns("proxyReqWs", proxyResWs);
/**
* Handle Proxy errors
*/
proxy.on("error", function (err) {
if (typeof opt.get("errHandler") === "function") {
opt.get("errHandler").call(null, err);
}
});
/**
* Apply functions to proxy events
* @param {string} name - the name of the http-proxy event
* @param {Array} fns - functions to call on each event
*/
function applyFns (name, fns) {
if (!List.isList(fns)) fns = [fns];
proxy.on(name, function () {
var args = arguments;
fns.forEach(function(fn) {
if (typeof fn === "function") {
fn.apply(null, args);
}
});
});
}
return browserSyncServer;
};
/**
* @param resFns
* @returns {*}
*/
function getProxyResFunctions (resFns, opt) {
if (opt.getIn(["cookies", "stripDomain"])) {
return resFns.push(proxyUtils.checkCookies);
}
return resFns;
}
/**
* @param reqFns
* @returns {*}
*/
function getProxyReqFunctions (reqFns, opt, bs) {
var reqHeaders = opt.getIn(["reqHeaders"]);
if (!reqHeaders) {
return reqFns;
}
/**
* Back-compat for old `reqHeaders` option here a
* function was given that returned an object
* where key:value was header-name:header-value
* This didn't really work as it clobbered all other headers,
* but it remains for the unlucky few who used it.
*/
if (typeof reqHeaders === "function") {
var output = reqHeaders.call(bs, opt.toJS());
if (Object.keys(output).length) {
return reqFns.concat(function (proxyReq) {
Object.keys(output).forEach(function (key) {
proxyReq.setHeader(key, output[key]);
});
});
}
}
/**
* Now, if {key:value} given, set the each header
*
* eg: reqHeaders: {
* 'is-dev': 'true'
* }
*/
if (Map.isMap(reqHeaders)) {
return reqFns.concat(function (proxyReq) {
reqHeaders.forEach(function (value, key) {
proxyReq.setHeader(key, value);
});
});
}
return reqFns;
}

View File

@ -0,0 +1,138 @@
var url = require("url");
module.exports.rewriteLinks = function (userServer) {
var host = userServer.hostname;
var string = host;
var port = userServer.port;
if (host && port) {
if (parseInt(port, 10) !== 80) {
string = host + ":" + port;
}
}
return {
match: new RegExp("https?:\\\\/\\\\/" + string + "|('|\")\\\/\\\/" + string + "|https?://" + string + "(\/)?|('|\")(https?://|/|\\.)?" + string + "(\/)?(.*?)(?=[ ,'\"\\s])", "g"),
//match: new RegExp("https?:\\\\/\\\\/" + string + "|https?://" + string + "(\/)?|('|\")(https?://|/|\\.)?" + string + "(\/)?(.*?)(?=[ ,'\"\\s])", "g"),
//match: new RegExp("https?:\\\\?/\\\\?/" + string + "(\/)?|('|\")(https?://|\\\\?/|\\.)?" + string + "(\/)?(.*?)(?=[ ,'\"\\s])", "g"),
//match: new RegExp('https?://' + string + '(\/)?|(\'|")(https?://|/|\\.)?' + string + '(\/)?(.*?)(?=[ ,\'"\\s])', 'g'),
//match: new RegExp("https?:\\\\/\\\\/" + string, "g"),
fn: function (req, res, match) {
var proxyUrl = req.headers["host"];
/**
* Reject subdomains
*/
if (match[0] === ".") {
return match;
}
var captured = match[0] === "'" || match[0] === "\"" ? match[0] : "";
/**
* allow http https
* @type {string}
*/
var pre = "//";
if (match[0] === "'" || match[0] === "\"") {
match = match.slice(1);
}
/**
* parse the url
* @type {number|*}
*/
var out = url.parse(match);
/**
* If host not set, just do a simple replace
*/
if (!out.host) {
string = string.replace(/^(\/)/, "");
return captured + match.replace(string, proxyUrl);
}
/**
* Only add trailing slash if one was
* present in the original match
*/
if (out.path === "/") {
if (match.slice(-1) === "/") {
out.path = "/";
} else {
out.path = "";
}
}
/**
* Finally append all of parsed url
*/
return [
captured,
pre,
proxyUrl,
out.path || "",
out.hash || ""
].join("");
}
};
};
/**
* Remove 'domain' from any cookies
* @param {Object} res
*/
module.exports.checkCookies = function checkCookies (res) {
if (typeof(res.headers["set-cookie"]) !== "undefined") {
res.headers["set-cookie"] = res.headers["set-cookie"].map(function (item) {
return rewriteCookies(item);
});
}
};
/**
* Remove the domain from any cookies.
* @param rawCookie
* @returns {string}
*/
function rewriteCookies (rawCookie) {
var objCookie = (function () {
// simple parse function (does not remove quotes)
var obj = {};
var pairs = rawCookie.split(/; */);
pairs.forEach( function( pair ) {
var eqIndex = pair.indexOf("=");
// skip things that don't look like key=value
if (eqIndex < 0) {
return;
}
var key = pair.substr(0, eqIndex).trim();
obj[key] = pair.substr(eqIndex + 1, pair.length).trim();
});
return obj;
})();
var pairs = Object.keys(objCookie)
.filter(function (item) {
return item !== "domain";
})
.map(function (key) {
return key + "=" + objCookie[key];
});
if (rawCookie.match(/httponly/i)) {
pairs.push("HttpOnly");
}
return pairs.join("; ");
}
module.exports.rewriteCookies = rewriteCookies;

View File

@ -0,0 +1,16 @@
"use strict";
var connect = require("connect");
var serverUtils = require("./utils.js");
/**
* Create a server for the snippet
* @param {BrowserSync} bs
* @param scripts
* @returns {*}
*/
module.exports = function createSnippetServer (bs, scripts) {
var app = serverUtils.getBaseApp(bs, bs.options, scripts);
return serverUtils.getServer(app, bs.options);
};

View File

@ -0,0 +1,79 @@
"use strict";
var connect = require("connect");
var serverUtils = require("./utils.js");
var resolve = require("path").resolve;
var utils = require("../utils.js");
var serveStatic = require("serve-static");
var serveIndex = require("serve-index");
/**
* @param {BrowserSync} bs
* @param scripts
* @returns {*}
*/
module.exports = function createServer (bs) {
var options = bs.options;
var server = options.get("server");
var basedirs = utils.arrayify(server.get("baseDir"));
var serveStaticOptions = server.get("serveStaticOptions").toJS(); // passed to 3rd party
var _serveStatic = 0;
var _routes = 0;
bs.options = bs.options
/**
* Add directory Middleware if given in server.directory
*/
.update("middleware", function (mw) {
if (!server.get("directory")) {
return mw;
}
return mw.concat({
route: "",
handle: serveIndex(resolve(basedirs[0]), {icons:true}),
id: "Browsersync Server Directory Middleware"
});
})
/**
* Add middleware for server.baseDir Option
*/
.update("middleware", function (mw) {
return mw.concat(basedirs.map(function (root) {
return {
route: "",
id: "Browsersync Server ServeStatic Middleware - " + _serveStatic++,
handle: serveStatic(resolve(root), serveStaticOptions)
}
}));
})
/**
* Add middleware for server.routes
*/
.update("middleware", function (mw) {
if (!server.get("routes")) {
return mw;
}
return mw.concat(server.get("routes").map(function (root, urlPath) {
// strip trailing slash
if (urlPath[urlPath.length - 1] === "/") {
urlPath = urlPath.slice(0, -1);
}
return {
route: urlPath,
id: "Browsersync Server Routes Middleware - " + _routes++,
handle: serveStatic(resolve(root))
}
}));
});
var app = serverUtils.getBaseApp(bs);
/**
* Finally, return the server + App
*/
return serverUtils.getServer(app, bs.options);
};

View File

@ -0,0 +1,453 @@
"use strict";
var fs = require("fs");
var path = require("path");
var join = require("path").join;
var connect = require("connect");
var Immutable = require("immutable");
var http = require("http");
var https = require("https");
var Map = require("immutable").Map;
var fromJS = require("immutable").fromJS;
var List = require("immutable").List;
var snippet = require("./../snippet").utils;
var _ = require("./../../lodash.custom");
var serveStatic = require("serve-static");
var logger = require("../logger");
var snippetUtils = require("../snippet").utils;
var lrSnippet = require("resp-modifier");
var utils = require("../utils");
function getCa (options) {
var caOption = options.getIn(["https", "ca"]);
// if not provided, use Browsersync self-signed
if (typeof caOption === "undefined") {
return fs.readFileSync(join(__dirname, "certs", "server.csr"));
}
// if a string was given, read that file from disk
if (typeof caOption === "string") {
return fs.readFileSync(caOption);
}
// if an array was given, read all
if (List.isList(caOption)) {
return caOption.toArray().map(function (x) {
return fs.readFileSync(x);
});
}
}
function getKey(options) {
return fs.readFileSync(options.getIn(["https", "key"]) || join(__dirname, "certs", "server.key"));
}
function getCert(options) {
return fs.readFileSync(options.getIn(["https", "cert"]) || join(__dirname, "certs", "server.crt"));
}
function getHttpsServerDefaults (options) {
return fromJS({
key: getKey(options),
cert: getCert(options),
ca: getCa(options),
passphrase: ""
});
}
function getPFXDefaults (options) {
return fromJS({
pfx: fs.readFileSync(options.getIn(["https", "pfx"]))
});
}
var serverUtils = {
/**
* @param options
* @returns {{key, cert}}
*/
getHttpsOptions: function (options) {
var userOption = options.get("https");
if (Map.isMap(userOption)) {
if (userOption.has("pfx")) {
return userOption.mergeDeep(getPFXDefaults(options));
}
return userOption.mergeDeep(getHttpsServerDefaults(options));
}
return getHttpsServerDefaults(options);
},
/**
* Get either http or https server
* or use the httpModule provided in options if present
*/
getServer: function (app, options) {
return {
server: (function () {
var httpModule = serverUtils.getHttpModule(options);
if (options.get("scheme") === "https") {
var opts = serverUtils.getHttpsOptions(options);
return httpModule.createServer(opts.toJS(), app);
}
return httpModule.createServer(app);
})(),
app: app
};
},
getHttpModule: function (options) {
/**
* Users may provide a string to be used by nodes
* require lookup.
*/
var httpModule = options.get("httpModule");
if (typeof httpModule === "string") {
/**
* Note, this could throw, but let that happen as
* the error message good enough.
*/
var maybe = path.resolve(process.cwd(), "node_modules", httpModule);
return require(maybe);
}
if (options.get("scheme") === "https") {
return https;
}
return http;
},
getMiddlewares: function (bs) {
var clientJs = bs.pluginManager.hook("client:js", {
port: bs.options.get("port"),
options: bs.options
});
var scripts = bs.pluginManager.get("client:script")(
bs.options.toJS(),
clientJs,
"middleware"
);
var defaultMiddlewares = [
{
id: "Browsersync HTTP Protocol",
route: require("../config").httpProtocol.path,
handle: require("../http-protocol").middleware(bs)
},
{
id: "Browsersync IE8 Support",
route: "",
handle: snippet.isOldIe(bs.options.get("excludedFileTypes").toJS())
},
{
id: "Browsersync Response Modifier",
route: "",
handle: serverUtils.getSnippetMiddleware(bs)
},
{
id: "Browsersync Client - versioned",
route: bs.options.getIn(["scriptPaths", "versioned"]),
handle: scripts
},
{
id: "Browsersync Client",
route: bs.options.getIn(["scriptPaths", "path"]),
handle: scripts
}
];
/**
* Add cors middleware to the front of the stack
* if a user provided a 'cors' flag
*/
if (bs.options.get("cors")) {
defaultMiddlewares.unshift({
id: "Browsersync CORS support",
route: "",
handle: serverUtils.getCorsMiddlewware()
})
}
/**
* Add serve static middleware
*/
if (bs.options.get("serveStatic")) {
var ssMiddlewares = serverUtils.getServeStaticMiddlewares(bs.options.get("serveStatic"), bs.options.get("serveStaticOptions", Immutable.Map({})).toJS());
var withErrors = ssMiddlewares.filter(function(x) { return x.get("errors").size > 0 });
var withoutErrors = ssMiddlewares.filter(function(x) { return x.get("errors").size === 0 });
if (withErrors.size) {
withErrors.forEach(function (item) {
logger.logger.error("{red:Warning!} %s", item.getIn(["errors", 0, "data", "message"]));
});
}
if (withoutErrors.size) {
withoutErrors.forEach(function (item) {
defaultMiddlewares.push.apply(defaultMiddlewares, item.get("items").toJS());
});
}
}
/**
* Add user-provided middlewares
*/
var userMiddlewares = bs.options.get("middleware").map(normaliseMiddleware).toArray();
var beforeMiddlewares = userMiddlewares.filter(function (x) { return x.override; });
var afterMiddlewares = userMiddlewares.filter(function (x) { return !x.override; });
return [].concat(beforeMiddlewares, defaultMiddlewares, afterMiddlewares);
function normaliseMiddleware(item) {
/**
* Object given in options, which
* ended up being a Map
*/
if (Map.isMap(item)) {
return item.toJS();
}
/**
* Single function
*/
if (typeof item === "function") {
return {
route: "",
handle: item
}
}
/**
* Plain obj
*/
if ((item.route !== undefined) && item.handle) {
return item;
}
}
},
getBaseApp: function (bs) {
var app = connect();
var middlewares = serverUtils.getMiddlewares(bs);
/**
* Add all internal middlewares
*/
middlewares.forEach(function (item) {
app.stack.push(item);
});
return app;
},
getSnippetMiddleware: function (bs) {
var rules = [];
var blacklist = List([])
.concat(bs.options.getIn(["snippetOptions", "ignorePaths"]))
.concat(bs.options.getIn(["snippetOptions", "blacklist"]))
.filter(Boolean);
var whitelist = List([])
.concat(bs.options.getIn(["snippetOptions", "whitelist"]));
// Snippet
rules.push(snippetUtils.getRegex(bs.options.get("snippet"), bs.options.get("snippetOptions")));
// User
bs.options.get("rewriteRules").forEach(function (rule) {
if (Map.isMap(rule)) {
rules.push(rule.toJS());
}
if (_.isPlainObject(rule)) {
rules.push(rule);
}
});
// Proxy
if (bs.options.get("proxy")) {
var proxyRule = require("./proxy-utils").rewriteLinks(bs.options.getIn(["proxy", "url"]).toJS());
rules.push(proxyRule);
}
var lr = lrSnippet.create({
rules: rules,
blacklist: blacklist.toArray(),
whitelist: whitelist.toArray()
});
return lr.middleware;
},
getCorsMiddlewware: function () {
return function (req, res, next) {
// Website you wish to allow to connect
res.setHeader("Access-Control-Allow-Origin", "*");
// Request methods you wish to allow
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
// Request headers you wish to allow
res.setHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type");
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader("Access-Control-Allow-Credentials", true);
next();
}
},
/**
* @param ssOption
* @param serveStaticOptions
* @returns {*}
*/
getServeStaticMiddlewares: function (ssOption, serveStaticOptions) {
return ssOption.map(function (dir, i) {
/**
* When a user gives a plain string only, eg:
* serveStatic: ['./temp']
* ->
* This means a middleware will be created with
* route: ''
* handle: serveStatic('./temp', options)
*/
if (_.isString(dir)) {
return getFromString(dir)
}
/**
* If a user gave an object eg:
* serveStatic: [{route: "", dir: ["test", "./tmp"]}]
* ->
* This means we need to create a middle for each route + dir combo
*/
if (Immutable.Map.isMap(dir)) {
return getFromMap(dir, i);
}
/**
* At this point, an item in the serveStatic array was not a string
* or an object so we return an error that can be logged
*/
return fromJS({
items: [],
errors: [{
type: "Invalid Type",
data: {
message: "Only strings and Objects (with route+dir) are supported for the ServeStatic option"
}
}]
})
});
/**
* @param {string} x
* @returns {string}
*/
function getRoute (x) {
if (x === "") return "";
return x[0] === "/" ? x : "/" + x;
}
/**
* @param dir
* @returns {Map}
*/
function getFromString(dir) {
return fromJS({
items: [
{
route: "",
handle: serveStatic(dir, serveStaticOptions)
}
],
errors: []
})
}
/**
* @param dir
* @returns {Map}
*/
function getFromMap(dir) {
var ssOptions = (function () {
if (dir.get("options")) {
return dir.get("options").toJS();
}
return {}
})();
var route = Immutable.List([]).concat(dir.get("route")).filter(_.isString);
var _dir = Immutable.List([]).concat(dir.get("dir")).filter(_.isString);
if (_dir.size === 0) {
return fromJS({
items: [],
errors: [{
type: "Invalid Object",
data: {
message: "Serve Static requires a 'dir' property when using an Object"
}
}]
})
}
var ssItems = (function () {
/**
* iterate over every 'route' item
* @type {Immutable.List<any>|Immutable.List<*>|Immutable.List<any>|*}
*/
var routeItems = (function () {
/**
* If no 'route' was given, assume we want to match all
* paths
*/
if (route.size === 0) {
return _dir.map(function (dirString) {
return Map({
route: "",
dir: dirString
});
});
}
return route.reduce(function (acc, routeString) {
/**
* For each 'route' item, also iterate through 'dirs'
* @type {Immutable.Iterable<K, M>}
*/
var perDir = _dir.map(function (dirString) {
return Map({
route: getRoute(routeString),
dir: dirString
})
});
return acc.concat(perDir);
}, List([]));
})();
/**
* Now create a serverStatic Middleware for each item
*/
return routeItems.map(function (routeItem) {
return routeItem.merge({
handle: serveStatic(routeItem.get("dir"), ssOptions)
});
});
})();
return fromJS({
items: ssItems,
errors: []
});
}
}
};
module.exports = serverUtils;

View File

@ -0,0 +1,106 @@
"use strict";
var connectUtils = require("./connect-utils");
var config = require("./config");
var lrSnippet = require("resp-modifier");
var path = require("path");
var _ = require("../lodash.custom");
var utils = require("./utils");
var fs = require("fs");
/**
* Utils for snippet injection
*/
var snippetUtils = {
/**
* @param {String} url
* @param {Array} excludeList
* @returns {boolean}
*/
isExcluded: function (url, excludeList) {
var extension = path.extname(url);
if (extension) {
if (~url.indexOf("?")) {
return true;
}
extension = extension.slice(1);
return _.includes(excludeList, extension);
}
return false;
},
/**
* @param {String} snippet
* @param {Object} options
* @returns {{match: RegExp, fn: Function}}
*/
getRegex: function (snippet, options) {
var fn = options.getIn(["rule", "fn"]);
return {
match: options.getIn(["rule", "match"]),
fn: function (req, res, match) {
return fn.apply(null, [snippet, match]);
},
once: true,
id: "bs-snippet"
};
},
getSnippetMiddleware: function (snippet, options, rewriteRules) {
return lrSnippet.create(snippetUtils.getRules(snippet, options, rewriteRules));
},
getRules: function (snippet, options, rewriteRules) {
var rules = [snippetUtils.getRegex(snippet, options)];
if (rewriteRules) {
rules = rules.concat(rewriteRules);
}
return {
rules: rules,
blacklist: utils.arrayify(options.get("blacklist")),
whitelist: utils.arrayify(options.get("whitelist"))
};
},
/**
* @param {Object} req
* @param {Array} [excludeList]
* @returns {Object}
*/
isOldIe: function (excludeList) {
return function (req, res, next) {
var ua = req.headers["user-agent"];
var match = /MSIE (\d)\.\d/.exec(ua);
if (match) {
if (parseInt(match[1], 10) < 9) {
if (!snippetUtils.isExcluded(req.url, excludeList)) {
req.headers["accept"] = "text/html";
}
}
}
next();
}
},
/**
* @param {Number} port
* @param {BrowserSync.options} options
* @returns {String}
*/
getClientJs: function (port, options) {
var socket = snippetUtils.getSocketScript();
var noConflictJs = "window.___browserSync___oldSocketIo = window.io;";
return noConflictJs + socket + ";" + connectUtils.socketConnector(options);
},
/**
* @returns {String}
*/
getSocketScript: function () {
return fs.readFileSync(path.join(__dirname, config.socketIoScript), "utf-8");
}
};
module.exports.utils = snippetUtils;

View File

@ -0,0 +1,100 @@
"use strict";
var socket = require("socket.io");
var utils = require("./server/utils");
var Steward = require("emitter-steward");
/**
* Plugin interface
* @returns {*|function(this:exports)}
*/
module.exports.plugin = function (server, clientEvents, bs) {
return exports.init(server, clientEvents, bs);
};
/**
* @param {http.Server} server
* @param clientEvents
* @param {BrowserSync} bs
*/
module.exports.init = function (server, clientEvents, bs) {
var emitter = bs.events;
var socketConfig = bs.options
.get("socket")
.toJS();
if (bs.options.get("mode") === "proxy" && bs.options.getIn(["proxy", "ws"])) {
server = utils.getServer(null, bs.options).server;
server.listen(bs.options.getIn(["socket", "port"]));
bs.registerCleanupTask(function () {
server.close();
});
}
var socketIoConfig = socketConfig.socketIoOptions;
socketIoConfig.path = socketConfig.path;
var io = socket(server, socketIoConfig);
// Override default namespace.
io.sockets = io.of(socketConfig.namespace);
io.set("heartbeat interval", socketConfig.clients.heartbeatTimeout);
var steward = new Steward(emitter);
bs.registerCleanupTask(steward.destroy.bind(steward));
/**
* Listen for new connections
*/
io.sockets.on("connection", handleConnection);
/**
* Handle each new connection
* @param {Object} client
*/
function handleConnection (client) {
// set ghostmode callbacks
if (bs.options.get("ghostMode")) {
addGhostMode(client);
}
client.emit("connection", bs.options.toJS()); //todo - trim the amount of options sent to clients
emitter.emit("client:connected", {
ua: client.handshake.headers["user-agent"]
});
}
/**
* @param {string} event
* @param {Socket.client} client
* @param {Object} data
*/
function handleClientEvent(event, client, data) {
if (steward.valid(client.id)) {
client.broadcast.emit(event, data);
}
}
/**
* @param client
*/
function addGhostMode (client) {
clientEvents.forEach(addEvent);
function addEvent(event) {
client.on(event, handleClientEvent.bind(null, event, client));
}
}
return io;
};

View File

@ -0,0 +1,10 @@
window.___browserSync___ = {};
___browserSync___.io = window.io;
window.io = window.___browserSync___oldSocketIo;
window.___browserSync___oldSocketIo=undefined;
___browserSync___.socketConfig = %config%;
___browserSync___.url = %url%;
if (location.protocol == "https:" && /^http:/.test(___browserSync___.url)) {
___browserSync___.url = ___browserSync___.url.replace(/^http:/, "https:");
}
___browserSync___.socket = ___browserSync___.io(___browserSync___.url, ___browserSync___.socketConfig);

View File

@ -0,0 +1 @@
<script %async% id="__bs_script__" src="%script%"></script>

View File

@ -0,0 +1,3 @@
<script id="__bs_script__">//<![CDATA[
document.write("<script %async% src='%script%'><\/script>".replace("HOST", location.hostname));
//]]></script>

View File

@ -0,0 +1,35 @@
"use strict";
var _ = require("../lodash.custom");
var utils = require("util");
/**
* @param {BrowserSync} bs
* @param {Function} cb
*/
module.exports = function (bs, cb) {
var opts = {};
var options = bs.options;
var port = options.get("port");
if (_.isString(options.get("tunnel"))) {
opts.subdomain = options.get("tunnel");
}
bs.debug("Requesting a tunnel connection on port: {magenta:%s}", port);
bs.debug("Requesting a tunnel connection with options: {magenta:%s}", utils.inspect(opts));
require("localtunnel")(port, opts, function (err, tunnel) {
if (err) {
return cb(err);
}
tunnel.on("error", function (err) {
bs.logger.info("Localtunnel issue: " + err.message);
bs.logger.info("Oops! The localtunnel appears to have disconnected. Reconnecting...");
});
return cb(null, tunnel);
});
};

View File

@ -0,0 +1,310 @@
"use strict";
var _ = require("../lodash.custom");
var devIp = require("dev-ip")();
var Immutable = require("immutable");
var portScanner = require("portscanner");
var path = require("path");
var List = require("immutable").List;
var UAParser = require("ua-parser-js");
var parser = new UAParser();
var utils = {
/**
* @param {Object} options
* @returns {String|boolean} - the IP address
* @param devIp
*/
getHostIp: function (options, devIp) {
if (options) {
var host = options.get("host");
if (host && host !== "localhost") {
return host;
}
if (options.get("detect") === false || !devIp.length) {
return false;
}
}
return devIp.length ? devIp[0] : false;
},
/**
* Set URL Options
*/
getUrlOptions: function (options) {
var scheme = options.get("scheme");
var port = options.get("port");
var urls = {};
if (options.get("online") === false) {
urls.local = utils.getUrl(scheme + "://localhost:" + port, options);
return Immutable.fromJS(urls);
}
var external = utils.xip(utils.getHostIp(options, devIp), options);
var localhost = "localhost";
if (options.get("xip")) {
localhost = "127.0.0.1";
}
localhost = utils.xip(localhost, options);
return Immutable.fromJS(utils.getUrls(external, localhost, scheme, options));
},
/**
* Append a start path if given in options
* @param {String} url
* @param {Object} options
* @returns {String}
*/
getUrl: function (url, options) {
var prefix = "/";
var startPath = options.get("startPath");
if (startPath) {
if (startPath.charAt(0) === "/") {
prefix = "";
}
url = url + prefix + startPath;
}
return url;
},
/**
* @param {String} external
* @param {String} local
* @param {String} scheme
* @param {Object} options
* @returns {{local: string, external: string}}
*/
getUrls: function (external, local, scheme, options) {
var urls = {
local: utils.getUrl(utils._makeUrl(scheme, local, options.get("port")), options)
};
if (external !== local) {
urls.external = utils.getUrl(utils._makeUrl(scheme, external, options.get("port")), options);
}
return urls;
},
/**
* @param {String} scheme
* @param {String} host
* @param {Number} port
* @returns {String}
* @private
*/
_makeUrl: function (scheme, host, port) {
return scheme + "://" + host + ":" + port;
},
/**
* Get ports
* @param {Object} options
* @param {Function} cb
*/
getPorts: function (options, cb) {
var port = options.get("port");
var ports = options.get("ports"); // backwards compatibility
var max;
if (ports) {
port = ports.get("min");
max = ports.get("max") || null;
}
utils.getPort(port, max, cb);
},
getPort: function (port, max, cb) {
portScanner.findAPortNotInUse(port, max, {
host: "localhost",
timeout: 1000
}, cb);
},
/**
* @param {String} ua
* @returns {Object}
*/
getUaString: function (ua) {
return parser.setUA(ua).getBrowser();
},
/**
* Open the page in browser
* @param {String} url
* @param {Object} options
* @param {BrowserSync} bs
*/
openBrowser: function (url, options, bs) {
var open = options.get("open");
var browser = options.get("browser");
if (_.isString(open)) {
if (options.getIn(["urls", open])) {
url = options.getIn(["urls", open]);
}
}
if (open) {
if (browser !== "default") {
if (utils.isList(browser)) {
browser.forEach(function (browser) {
utils.open(url, browser, bs);
});
} else {
utils.open(url, browser, bs); // single
}
} else {
utils.open(url, null, bs);
}
}
},
/**
* Wrapper for opn module
* @param url
* @param name
* @param bs
*/
open: function (url, name, bs) {
var options = (function () {
if (_.isString(name)) {
return {app: name};
}
if (Immutable.Map.isMap(name)) {
return name.toJS();
}
return {};
})();
var opn = require("opn");
opn(url, options).catch(function() {
bs.events.emit("browser:error");
});
},
/**
* @param {Boolean} kill
* @param {String|Error} [errMessage]
* @param {Function} [cb]
*/
fail: function (kill, errMessage, cb) {
if (kill) {
if (_.isFunction(cb)) {
if (errMessage.message) { // Is this an error object?
cb(errMessage);
} else {
cb(new Error(errMessage));
}
}
process.exit(1);
}
},
/**
* Add support for xip.io urls
* @param {String} host
* @param {Object} options
* @returns {String}
*/
xip: function (host, options) {
var suffix = options.get("hostnameSuffix");
if (options.get("xip")) {
return host + ".xip.io";
}
if (suffix) {
return host + suffix;
}
return host;
},
/**
* Determine if an array of file paths will cause a full page reload.
* @param {Array} needles - filepath such as ["core.css", "index.html"]
* @param {Array} haystack
* @returns {Boolean}
*/
willCauseReload: function (needles, haystack) {
return needles.some(function (needle) {
return !_.includes(haystack, path.extname(needle).replace(".", ""));
});
},
isList: Immutable.List.isList,
isMap: Immutable.List.isMap,
/**
* @param {Map} options
* @returns {Array}
*/
getConfigErrors: function (options) {
var messages = require("./config").errors;
var errors = [];
if (options.get("server") && options.get("proxy")) {
errors.push(messages["server+proxy"]);
}
return errors;
},
/**
* @param {Map} options
* @param {Function} [cb]
*/
verifyConfig: function (options, cb) {
var errors = utils.getConfigErrors(options);
if (errors.length) {
utils.fail(true, errors.join("\n"), cb);
return false;
}
return true;
},
/**
* @param err
*/
defaultCallback: function (err) {
if (err && err.message) {
console.error(err.message);
}
},
eachSeries: function (arr, iterator, callback) {
callback = callback || function () {};
var completed = 0;
var iterate = function () {
iterator(arr[completed], function (err) {
if (err) {
callback(err);
callback = function () {};
} else {
++completed;
if (completed >= arr.length) {
callback();
} else {
iterate();
}
}
});
};
iterate();
},
/**
* @param {Immutable.List|Array|String} incoming
* @returns {Array}
*/
arrayify: function (incoming) {
if (List.isList(incoming)) {
return incoming.toArray();
}
return [].concat(incoming).filter(Boolean);
}
};
module.exports = utils;
module.exports.portscanner = portScanner;
module.exports.UAParser = UAParser;
module.exports.connect = require("connect");
module.exports.devIp = devIp;
module.exports.serveStatic = require("serve-static");
module.exports.easyExtender = require("easy-extender");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,164 @@
{
"_args": [
[
{
"raw": "browser-sync@^2.18.5",
"scope": null,
"escapedName": "browser-sync",
"name": "browser-sync",
"rawSpec": "^2.18.5",
"spec": ">=2.18.5 <3.0.0",
"type": "range"
},
"C:\\Users\\x2mjbyrn\\Source\\Repos\\Skeleton\\node_modules\\lite-server"
]
],
"_from": "browser-sync@>=2.18.5 <3.0.0",
"_id": "browser-sync@2.18.11",
"_inCache": true,
"_location": "/lite-server/browser-sync",
"_nodeVersion": "6.10.1",
"_npmOperationalInternal": {
"host": "packages-18-east.internal.npmjs.com",
"tmp": "tmp/browser-sync-2.18.11.tgz_1494859170412_0.7870701828505844"
},
"_npmUser": {
"name": "shakyshane",
"email": "shakyshane@gmail.com"
},
"_npmVersion": "3.10.10",
"_phantomChildren": {},
"_requested": {
"raw": "browser-sync@^2.18.5",
"scope": null,
"escapedName": "browser-sync",
"name": "browser-sync",
"rawSpec": "^2.18.5",
"spec": ">=2.18.5 <3.0.0",
"type": "range"
},
"_requiredBy": [
"/lite-server"
],
"_resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.18.11.tgz",
"_shasum": "333600f2efa256ecd22ced2561dfc77dcad11ea5",
"_shrinkwrap": null,
"_spec": "browser-sync@^2.18.5",
"_where": "C:\\Users\\x2mjbyrn\\Source\\Repos\\Skeleton\\node_modules\\lite-server",
"author": {
"name": "Shane Osbourne"
},
"bin": {
"browser-sync": "bin/browser-sync.js"
},
"bugs": {
"url": "https://github.com/browsersync/browser-sync/issues"
},
"dependencies": {
"browser-sync-client": "2.5.0",
"browser-sync-ui": "0.6.3",
"bs-recipes": "1.3.4",
"chokidar": "1.7.0",
"connect": "3.5.0",
"dev-ip": "^1.0.1",
"easy-extender": "2.3.2",
"eazy-logger": "3.0.2",
"emitter-steward": "^1.0.0",
"fs-extra": "3.0.1",
"http-proxy": "1.15.2",
"immutable": "3.8.1",
"localtunnel": "1.8.2",
"micromatch": "2.3.11",
"opn": "4.0.2",
"portscanner": "2.1.1",
"qs": "6.2.1",
"resp-modifier": "6.0.2",
"rx": "4.1.0",
"serve-index": "1.8.0",
"serve-static": "1.12.2",
"server-destroy": "1.0.1",
"socket.io": "1.6.0",
"socket.io-client": "1.6.0",
"ua-parser-js": "0.7.12",
"yargs": "6.4.0"
},
"description": "Live CSS Reload & Browser Syncing",
"devDependencies": {
"browser-sync-spa": "1.0.3",
"bs-html-injector": "3.0.3",
"bs-latency": "1.0.0",
"bs-rewrite-rules": "2.0.0",
"bs-snippet-injector": "2.0.1",
"chai": "3.5.0",
"chalk": "1.1.3",
"compression": "1.6.2",
"eslint": "3.7.1",
"graceful-fs": "4.1.9",
"gulp": "3.9.1",
"gulp-contribs": "0.0.3",
"gulp-conventional-changelog": "1.1.0",
"gulp-filter": "4.0.0",
"http2": "^3.3.6",
"istanbul": "0.4.5",
"istanbul-coveralls": "1.0.3",
"lodash-cli": "4.15.0",
"mocha": "3.4.1",
"q": "1.4.1",
"request": "2.79.0",
"rimraf": "2.5.4",
"sinon": "1.17.5",
"socket.io-client": "1.5.0",
"supertest": "2.0.0",
"vinyl": "1.2.0"
},
"directories": {},
"dist": {
"shasum": "333600f2efa256ecd22ced2561dfc77dcad11ea5",
"tarball": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.18.11.tgz"
},
"engines": {
"node": ">= 0.8.0"
},
"files": [
"bin",
"index.js",
"lib",
"lodash.custom.js"
],
"gitHead": "510fde4e54b98d87896ee7f724f051cad859d1e3",
"homepage": "http://www.browsersync.io/",
"keywords": [
"browser sync",
"css",
"live reload",
"sync"
],
"license": "Apache-2.0",
"maintainers": [
{
"name": "shakyshane",
"email": "shane.osbourne8@gmail.com"
}
],
"name": "browser-sync",
"optionalDependencies": {},
"readme": "ERROR: No README data found!",
"repository": {
"type": "git",
"url": "git+https://github.com/browsersync/browser-sync.git"
},
"scripts": {
"cover": "npm run env && npm run cover-local && npm run coveralls",
"cover-local": "istanbul cover -x lodash.custom.js _mocha -- --timeout 10000 --recursive ./test/specs",
"coveralls": "istanbul-coveralls",
"env": "node ./test/env.js",
"lint": "eslint index.js lib bin examples test/specs gulpfile.js --fix",
"lodash": "lodash include=isUndefined,isFunction,toArray,includes,union,each,isString,merge,isObject,set exports=node",
"pre-release": "npm test && npm run pro-local && npm run pro",
"pro": "protractor test/protractor/config.single.js",
"pro-local": "node test/protractor/setup.js",
"test": "npm run lint && npm run env && npm run unit",
"unit": "mocha --recursive test/specs --timeout 10000 --bail"
},
"version": "2.18.11"
}

View File

@ -0,0 +1,2 @@
service_name: travis-pro
repo_token: 8YyWggHYCJrQmm4qdV2f5LVvo3vBD7Xsa

View File

@ -0,0 +1,7 @@
root = true
[*]
end_of_line = lf
insert_final_newline = false
indent_style = space
indent_size = 2

14
node_modules/lite-server/node_modules/rx/.jamignore generated vendored Normal file
View File

@ -0,0 +1,14 @@
.*
*.bat
*.md
*.min.*
*.txt
*.log
package.json
node_modules
doc
examples
src
tests
.nuget
nuget

54
node_modules/lite-server/node_modules/rx/.jscsrc generated vendored Normal file
View File

@ -0,0 +1,54 @@
{
"excludeFiles": [
"src/core/asyncintro.js",
"src/core/intro.js",
"src/core/outro.js",
"src/core/suboutro.js",
"src/core/subintro.js",
"src/core/testintro.js"
],
"requireCurlyBraces": [
"if",
"else",
"for",
"while",
"do",
"try",
"catch"
],
"requireOperatorBeforeLineBreak": true,
"requireCamelCaseOrUpperCaseIdentifiers": true,
"disallowMultipleLineStrings": true,
"disallowMixedSpacesAndTabs": true,
"disallowTrailingWhitespace": true,
"disallowSpaceAfterPrefixUnaryOperators": true,
"requireSpaceAfterKeywords": [
"if",
"else",
"for",
"while",
"do",
"switch",
"return",
"try",
"catch"
],
"requireSpaceBeforeBinaryOperators": [
"=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
"&=", "|=", "^=", "+=",
"+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",
"|", "^", "&&", "||", "===", "==", ">=",
"<=", "<", ">", "!=", "!=="
],
"requireSpaceAfterBinaryOperators": true,
"requireSpacesInConditionalExpression": true,
"requireSpaceBeforeBlockStatements": true,
"requireLineFeedAtFileEnd": true,
"requireSpacesInFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"disallowSpacesInsideParentheses": true,
"disallowMultipleLineBreaks": true,
"disallowNewlineBeforeBlockStatements": true
}

19
node_modules/lite-server/node_modules/rx/.jscsrc.todo generated vendored Normal file
View File

@ -0,0 +1,19 @@
{
"maximumLineLength": {
"value": 80,
"allowComments": true,
"allowRegex": true
},
"validateIndentation": 2,
"validateQuoteMarks": "'",
"disallowMultipleVarDecl": true,
"disallowSpacesInAnonymousFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInsideObjectBrackets": "all",
"disallowSpacesInsideArrayBrackets": "all",
"validateJSDoc": {
"checkParamNames": true,
"requireParamTypes": true
}
}

3
node_modules/lite-server/node_modules/rx/authors.txt generated vendored Normal file
View File

@ -0,0 +1,3 @@
Matthew Podwysocki <matthewp@microsoft.com>
Bart de Smet <bartde@microsoft.com>
Erik Meijer <emeijer@microsoft.com>

23
node_modules/lite-server/node_modules/rx/bower.json generated vendored Normal file
View File

@ -0,0 +1,23 @@
{
"name": "rxjs",
"main": "dist/rx.all.js",
"repository": {
"type": "git",
"url": "https://github.com/Reactive-Extensions/RxJS.git"
},
"license": "Apache-2.0",
"ignore": [
".sh",
".*",
"*.bat",
"*.md",
"*.txt",
"*.log",
"package.json",
"node_modules",
"doc",
"examples",
"src",
"tests"
]
}

View File

@ -0,0 +1,12 @@
# Code of Conduct #
[_Adapted from the Rust Code of Conduct_](https://github.com/rust-lang/rust/wiki/Note-development-policy#conduct)
We are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, disability, ethnicity, religion, or similar personal characteristic.
- On any communication medium, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all.
- Please be kind and courteous. There's no need to be mean or rude.
- Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer.
- Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works.
- We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term "harassment" as including the definition in the [Citizen Code of Conduct](http://citizencodeofconduct.org/); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups.
- Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one the RxJS team immediately. Whether you're a regular contributor or a newcomer, we care about making this community a safe place for you and we've got your back.
- Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.

View File

@ -0,0 +1,62 @@
{
"name": "rx",
"scripts": [
"dist/rx.aggregates.js",
"dist/rx.aggregates.map",
"dist/rx.aggregates.min.js",
"dist/rx.all.compat.js",
"dist/rx.all.compat.map",
"dist/rx.all.compat.min.js",
"dist/rx.all.js",
"dist/rx.all.min.js",
"dist/rx.all.map",
"dist/rx.async.js",
"dist/rx.async.map",
"dist/rx.async.min.js",
"dist/rx.async.compat.js",
"dist/rx.async.compat.map",
"dist/rx.async.compat.min.js",
"dist/rx.backpressure.js",
"dist/rx.backpressure.map",
"dist/rx.backpressure.min.js",
"dist/rx.backpressure.js",
"dist/rx.backpressure.map",
"dist/rx.backpressure.min.js",
"dist/rx.binding.js",
"dist/rx.binding.map",
"dist/rx.binding.min.js",
"dist/rx.coincidence.js",
"dist/rx.coincidence.map",
"dist/rx.coincidence.min.js",
"dist/rx.js",
"dist/rx.map",
"dist/rx.min.js",
"dist/rx.compat.js",
"dist/rx.compat.map",
"dist/rx.compat.min.js",
"dist/rx.experimental.js",
"dist/rx.experimental.map",
"dist/rx.experimental.min.js",
"dist/rx.joinpatterns.js",
"dist/rx.joinpatterns.map",
"dist/rx.joinpatterns.min.js",
"dist/rx.lite.js",
"dist/rx.lite.map",
"dist/rx.lite.min.js",
"dist/rx.lite.compat.js",
"dist/rx.lite.compat.map",
"dist/rx.lite.compat.min.js",
"dist/rx.lite.extras.js",
"dist/rx.lite.extras.map",
"dist/rx.lite.extras.min.js",
"dist/rx.testing.js",
"dist/rx.testing.map",
"dist/rx.testing.min.js",
"dist/rx.time.js",
"dist/rx.time.map",
"dist/rx.time.min.js",
"dist/rx.virtualtime.js",
"dist/rx.virtualtime.map",
"dist/rx.virtualtime.min.js"
]
}

View File

@ -0,0 +1,169 @@
# Contributing to RxJS #
Want to contribute to the Reactive Extensions for JavaScript (RxJS)? There are many ways of helping whether contributing code, documentation, examples, podcasts, videos and presentations.
# Get Involved!
In [the issue tracker](https://github.com/Reactive-Extensions/RxJS/issues), bugs can only be assigned to people who have commit access. Also, we aspire to make as many bugs as possible "owned" by assigning them to a core Rx contributor. Therefore, just because a bug is assigned doesn't mean it's being actively worked on. We (the core contributors) are all busy, and welcome help from the community. If you see a bug you'd like to work on that's assigned but appears to be dormant, communicate with the bug's owner with an @-reply in a comment on the issue page. If you see a bug you'd like to work on that's unassigned, it's fair game: comment to say you'd like to work on it so that we know it's getting attention.
# Pull Requests
To make a pull request, you will need a GitHub account; if you're unclear on this process, see GitHub's documentation on [forking](https://help.github.com/articles/fork-a-repo/) and [pull requests](https://help.github.com/articles/using-pull-requests). Pull requests should be targeted at RxJS's master branch. Before pushing to your Github repo and issuing the pull request, please do two things:
1. Rebase your local changes against the master branch. Resolve any conflicts that arise.
2. Run the full RxJS test suite by running `grunt` in the root of the repository.
Pull requests will be treated as "review requests", and we will give feedback we expect to see corrected on style and substance before pulling. Changes contributed via pull request should focus on a single issue at a time, like any other. We will not look kindly on pull-requests that try to "sneak" unrelated changes in. Note for bug fixes, regression tests should be included, denoted by Issue Number so that we have full traceability.
# What Are We Looking For?
For documentation, we are looking for the following:
- API Documentation that is missing or out of date
- "How Do I?" examples
- Comparison to other libraries
- Comparison to Promises
- Introduction material
- Tutorials
For coding, we have strict standards that must be adhere to when working on RxJS. In order for us to accept pull requests, they must abide by the following:
- [Coding Standard](#coding-standard)
- [Tests](#tests)
- [Documentation](#documentation)
## Coding Standard
For RxJS, we follow the [Google JavaScript Style Guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml) and adhere to it strictly in areas such as documentation using JSDoc. The only exception to extending native prototypes is to polyfill behavior which may not exist in all browsers yet, for example, many of the [Array#extras](http://blogs.msdn.com/b/ie/archive/2010/12/13/ecmascript-5-part-2-array-extras.aspx) are implemented in compatibility builds. We also strictly follow [our design guidelines](https://github.com/Reactive-Extensions/RxJS/tree/master/doc/designguidelines) as well.
### Supporting Multiple Platforms
RxJS runs on a number of platforms and supports many styles of programming. RxJS supports [Universal Module Definition (UMD)](https://github.com/umdjs/umd) which allows the library to work in a number of environments such as [Asynchronous Module Definition (AMD)](https://github.com/amdjs/amdjs-api/wiki/AMD), [CommonJS](http://wiki.commonjs.org/wiki/CommonJS), [Node.js](http://nodejs.org), [RingoJS](http://ringojs.org/), [Narwhal](https://github.com/280north/narwhal), the browser and other environments such as [Windows Script Host (WSH)](http://msdn.microsoft.com/en-us/library/9bbdkx3k.aspx) and embedded devices such as [Tessel](http://tessel.io).
RxJS is committed to using the latest JavaScript standards as they start to arrive, for example, supporting generators, Maps, Sets, and Observable versions of new Array methods. We also are committed to supporting legacy code as well through compatibility builds, even supporting browsers back to IE6, Firefox 3, and older versions of Node.js. Should behavior not exist in those platforms, that behavior must be polyfilled, and made available in `*.compat.js` files only. For example, we have `rx.lite.js` which supports modern browsers greater than or equal to IE9, and `rx.lite.compat.js` for older browsers before IE9 and modern Firefox builds. In special cases such as event handling is different, we must provide a mainstream version of the file as well as a compat file, the latter which is included in the compat file.
### Implementing Custom Operators
We welcome custom operators to RxJS if they make sense in the core RxJS, as opposed to belonging in user land. There are a number of rules that must be adhered to when implementing a custom operator including:
- Prefer composition over implementing a totally new operator from scratch
- If the operator introduces any notion of concurrency, then a scheduler must introduced. Usage of concurrency primitives such as `setTimeout`, `setInterval`, etc are forbidden. This is to ensure easy testability.
- The scheduler must be optional with the appropriate default picked
- `Rx.Scheduler.immediate` for any immediate blocking operations
- `Rx.Scheduler.currentThread` for any immediate blocking operators that require re-entrant behavior such as recursive scheduling.
- `Rx.Scheduler.timeout` for any operator that has a notion of time
To make this concrete, let's implement a custom operator such as an implementation of `_.reject` from [Underscore.js](http://underscorejs.org/) / [Lo-Dash](http://lodash.com/).
```js
/**
* The opposite of _.filter this method returns the elements of a collection that the callback does **not** return truthy for.
* @param {Function} [callback] The function called per iteration.
* @param {Any} [thisArg] The this binding of callback.
* @returns {Observable} An Observable sequence which contains items that the callback does not return truthy for.
*/
Rx.Observable.prototype.reject = function (callback, thisArg) {
callback || (callback = Rx.helpers.identity);
var source = this;
return new Rx.AnonymousObservable(function (observer) {
var i = 0;
return source.subscribe(
function (x) {
var noYield = true;
try {
noYield = callback.call(thisArg, x, i++, source);
} catch (e) {
observer.onError(e);
return;
}
if (!noYield) { observer.onNext(x); }
},
observer.onError.bind(observer),
observer.onCompleted.bind(observer)
);
});
};
```
Of course, we could have implemented this using composition as well, such as using `Rx.Observable.prototype.filter`.
```js
/**
* The opposite of _.filter this method returns the elements of a collection that the callback does **not** return truthy for.
* @param {Function} [callback] The function called per iteration.
* @param {Any} [thisArg] The this binding of callback.
* @returns {Observable} An Observable sequence which contains items that the callback does not return truthy for.
*/
Rx.Observable.prototype.reject = function (callback, thisArg) {
callback || (callback = Rx.helpers.identity);
return this.filter(function (x, i, o) { return !callback.call(thisArg, x, i o); });
};
```
To show an operator that introduces a level of concurrency, let's implement a custom operator such as an implementation of `_.pairs` from [Underscore.js](http://underscorejs.org/) / [Lo-Dash](http://lodash.com/). Note that since this requires recursion to implement properly, we'll use the `Rx.Scheduler.currentThread` scheduler.
```js
var keysFunction = Object.keys || someKeysPolyfill;
/**
* Creates an Observable with an of an objects key-value pairs.
* @param {Object} obj The object to inspect.
* @returns {Observable} An Observable with an of an objects key-value pairs.
*/
Rx.Observable.pairs = function (obj, scheduler) {
scheduler || (scheduler = Rx.Scheduler.currentThread);
return new Rx.AnonymousObservable(function (observer) {
var keys = keysFunction(object),
i = 0,
len = keys.length;
return scheduler.scheduleRecursive(function (self) {
if (i < len) {
var key = keys[i++], value = obj[key];
observer.onNext([key, value]);
self();
} else {
observer.onCompleted();
}
});
});
};
```
Note that all operators must have the documentation and must be split out into its own file. This allows us to be able to put it in different files, or make it available in custom builds.
## Tests
When a new operator is written for RxJS, in order to accepted, must be accompanied by tests. RxJS currently uses [QUnit](http://qunitjs.com/) as a straight forward way to test our code. These tests are automatically executed by our [Grunt](http://gruntjs.com/) setup to concatenate files, minimize, create source maps, and finally run all the tests in the [tests folder](https://github.com/Reactive-Extensions/RxJS/tree/master/tests). Each file that we produce, for example, `rx.js` has an accompanying test file such as `rx.html`, which includes tests for all operators included in that file.
Each operator under test must be in its own file to cover the following cases:
- Never
- Empty
- Single/Multiple Values
- Error in the sequence
- Never ending sequences
- Early disposal in sequences
If the operator has a callback, then it must cover the following cases:
- Success with all values in the callback
- Success with the context, if any allowed in the operator signature
- If an error is thrown
To get a good feeling on what kind of rigor is required for testing, check out the following examples:
- [`concatMap`](https://github.com/Reactive-Extensions/RxJS/blob/master/tests/observable/concatmap.js)
- [`from`](https://github.com/Reactive-Extensions/RxJS/blob/master/tests/observable/from.js)
## Documentation
Documentation is also a must, as all external operators and types must be documented and put in the [API Folder](https://github.com/Reactive-Extensions/RxJS/tree/master/doc/api). Each operator on an Observable must have its own file in the [Operators Folder](https://github.com/Reactive-Extensions/RxJS/tree/master/doc/api/core/operators).
For operators, they must be linked from the [`Observable`](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md) API document. In addition, each operator must be listed in which file it belongs in the [Libraries Folder](https://github.com/Reactive-Extensions/RxJS/tree/master/doc/libraries).
The standard format of operators must be such as the [`of`](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/of.md) operator which includes:
- File Location
- Method signature
- Method description
- List of Arguments
- Return type (if there is one)
- An example
- File Distribution(s)
- NuGet Distribution
- NPM Distribution
- Unit Tests

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

12388
node_modules/lite-server/node_modules/rx/dist/rx.all.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,532 @@
// Copyright (c) Microsoft, All rights reserved. See License.txt in the project root for license information.
;(function (factory) {
var objectTypes = {
'function': true,
'object': true
};
function checkGlobal(value) {
return (value && value.Object === Object) ? value : null;
}
var freeExports = (objectTypes[typeof exports] && exports && !exports.nodeType) ? exports : null;
var freeModule = (objectTypes[typeof module] && module && !module.nodeType) ? module : null;
var freeGlobal = checkGlobal(freeExports && freeModule && typeof global === 'object' && global);
var freeSelf = checkGlobal(objectTypes[typeof self] && self);
var freeWindow = checkGlobal(objectTypes[typeof window] && window);
var moduleExports = (freeModule && freeModule.exports === freeExports) ? freeExports : null;
var thisGlobal = checkGlobal(objectTypes[typeof this] && this);
var root = freeGlobal || ((freeWindow !== (thisGlobal && thisGlobal.window)) && freeWindow) || freeSelf || thisGlobal || Function('return this')();
// Because of build optimizers
if (typeof define === 'function' && define.amd) {
define(['./rx.binding', 'exports'], function (Rx, exports) {
root.Rx = factory(root, exports, Rx);
return root.Rx;
});
} else if (typeof module === 'object' && module && module.exports === freeExports) {
module.exports = factory(root, module.exports, require('./rx'));
} else {
root.Rx = factory(root, {}, root.Rx);
}
}.call(this, function (root, exp, Rx, undefined) {
// Aliases
var Observable = Rx.Observable,
observableFromPromise = Observable.fromPromise,
observableThrow = Observable.throwError,
AnonymousObservable = Rx.AnonymousObservable,
ObservableBase = Rx.ObservableBase,
AsyncSubject = Rx.AsyncSubject,
disposableCreate = Rx.Disposable.create,
CompositeDisposable = Rx.CompositeDisposable,
immediateScheduler = Rx.Scheduler.immediate,
defaultScheduler = Rx.Scheduler['default'],
inherits = Rx.internals.inherits,
isScheduler = Rx.Scheduler.isScheduler,
isPromise = Rx.helpers.isPromise,
isFunction = Rx.helpers.isFunction,
isIterable = Rx.helpers.isIterable,
isArrayLike = Rx.helpers.isArrayLike;
var errorObj = {e: {}};
function tryCatcherGen(tryCatchTarget) {
return function tryCatcher() {
try {
return tryCatchTarget.apply(this, arguments);
} catch (e) {
errorObj.e = e;
return errorObj;
}
};
}
var tryCatch = Rx.internals.tryCatch = function tryCatch(fn) {
if (!isFunction(fn)) { throw new TypeError('fn must be a function'); }
return tryCatcherGen(fn);
};
function thrower(e) {
throw e;
}
Observable.wrap = function (fn) {
function createObservable() {
return Observable.spawn.call(this, fn.apply(this, arguments));
}
createObservable.__generatorFunction__ = fn;
return createObservable;
};
var spawn = Observable.spawn = function () {
var gen = arguments[0], self = this, args = [];
for (var i = 1, len = arguments.length; i < len; i++) { args.push(arguments[i]); }
return new AnonymousObservable(function (o) {
var g = new CompositeDisposable();
if (isFunction(gen)) { gen = gen.apply(self, args); }
if (!gen || !isFunction(gen.next)) {
o.onNext(gen);
return o.onCompleted();
}
function processGenerator(res) {
var ret = tryCatch(gen.next).call(gen, res);
if (ret === errorObj) { return o.onError(ret.e); }
next(ret);
}
processGenerator();
function onError(err) {
var ret = tryCatch(gen.next).call(gen, err);
if (ret === errorObj) { return o.onError(ret.e); }
next(ret);
}
function next(ret) {
if (ret.done) {
o.onNext(ret.value);
o.onCompleted();
return;
}
var obs = toObservable.call(self, ret.value);
var value = null;
var hasValue = false;
if (Observable.isObservable(obs)) {
g.add(obs.subscribe(function(val) {
hasValue = true;
value = val;
}, onError, function() {
hasValue && processGenerator(value);
}));
} else {
onError(new TypeError('type not supported'));
}
}
return g;
});
};
function toObservable(obj) {
if (!obj) { return obj; }
if (Observable.isObservable(obj)) { return obj; }
if (isPromise(obj)) { return Observable.fromPromise(obj); }
if (isGeneratorFunction(obj) || isGenerator(obj)) { return spawn.call(this, obj); }
if (isFunction(obj)) { return thunkToObservable.call(this, obj); }
if (isArrayLike(obj) || isIterable(obj)) { return arrayToObservable.call(this, obj); }
if (isObject(obj)) {return objectToObservable.call(this, obj);}
return obj;
}
function arrayToObservable (obj) {
return Observable.from(obj).concatMap(function(o) {
if(Observable.isObservable(o) || isObject(o)) {
return toObservable.call(null, o);
} else {
return Rx.Observable.just(o);
}
}).toArray();
}
function objectToObservable (obj) {
var results = new obj.constructor(), keys = Object.keys(obj), observables = [];
for (var i = 0, len = keys.length; i < len; i++) {
var key = keys[i];
var observable = toObservable.call(this, obj[key]);
if(observable && Observable.isObservable(observable)) {
defer(observable, key);
} else {
results[key] = obj[key];
}
}
return Observable.forkJoin.apply(Observable, observables).map(function() {
return results;
});
function defer (observable, key) {
results[key] = undefined;
observables.push(observable.map(function (next) {
results[key] = next;
}));
}
}
function thunkToObservable(fn) {
var self = this;
return new AnonymousObservable(function (o) {
fn.call(self, function () {
var err = arguments[0], res = arguments[1];
if (err) { return o.onError(err); }
if (arguments.length > 2) {
var args = [];
for (var i = 1, len = arguments.length; i < len; i++) { args.push(arguments[i]); }
res = args;
}
o.onNext(res);
o.onCompleted();
});
});
}
function isGenerator(obj) {
return isFunction (obj.next) && isFunction (obj['throw']);
}
function isGeneratorFunction(obj) {
var ctor = obj.constructor;
if (!ctor) { return false; }
if (ctor.name === 'GeneratorFunction' || ctor.displayName === 'GeneratorFunction') { return true; }
return isGenerator(ctor.prototype);
}
function isObject(val) {
return Object == val.constructor;
}
/**
* Invokes the specified function asynchronously on the specified scheduler, surfacing the result through an observable sequence.
*
* @example
* var res = Rx.Observable.start(function () { console.log('hello'); });
* var res = Rx.Observable.start(function () { console.log('hello'); }, Rx.Scheduler.timeout);
* var res = Rx.Observable.start(function () { this.log('hello'); }, Rx.Scheduler.timeout, console);
*
* @param {Function} func Function to run asynchronously.
* @param {Scheduler} [scheduler] Scheduler to run the function on. If not specified, defaults to Scheduler.timeout.
* @param [context] The context for the func parameter to be executed. If not specified, defaults to undefined.
* @returns {Observable} An observable sequence exposing the function's result value, or an exception.
*
* Remarks
* * The function is called immediately, not during the subscription of the resulting sequence.
* * Multiple subscriptions to the resulting sequence can observe the function's result.
*/
Observable.start = function (func, context, scheduler) {
return observableToAsync(func, context, scheduler)();
};
/**
* Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler.
* @param {Function} function Function to convert to an asynchronous function.
* @param {Scheduler} [scheduler] Scheduler to run the function on. If not specified, defaults to Scheduler.timeout.
* @param {Mixed} [context] The context for the func parameter to be executed. If not specified, defaults to undefined.
* @returns {Function} Asynchronous function.
*/
var observableToAsync = Observable.toAsync = function (func, context, scheduler) {
isScheduler(scheduler) || (scheduler = defaultScheduler);
return function () {
var args = arguments,
subject = new AsyncSubject();
scheduler.schedule(null, function () {
var result;
try {
result = func.apply(context, args);
} catch (e) {
subject.onError(e);
return;
}
subject.onNext(result);
subject.onCompleted();
});
return subject.asObservable();
};
};
function createCbObservable(fn, ctx, selector, args) {
var o = new AsyncSubject();
args.push(createCbHandler(o, ctx, selector));
fn.apply(ctx, args);
return o.asObservable();
}
function createCbHandler(o, ctx, selector) {
return function handler () {
var len = arguments.length, results = new Array(len);
for(var i = 0; i < len; i++) { results[i] = arguments[i]; }
if (isFunction(selector)) {
results = tryCatch(selector).apply(ctx, results);
if (results === errorObj) { return o.onError(results.e); }
o.onNext(results);
} else {
if (results.length <= 1) {
o.onNext(results[0]);
} else {
o.onNext(results);
}
}
o.onCompleted();
};
}
/**
* Converts a callback function to an observable sequence.
*
* @param {Function} fn Function with a callback as the last parameter to convert to an Observable sequence.
* @param {Mixed} [ctx] The context for the func parameter to be executed. If not specified, defaults to undefined.
* @param {Function} [selector] A selector which takes the arguments from the callback to produce a single item to yield on next.
* @returns {Function} A function, when executed with the required parameters minus the callback, produces an Observable sequence with a single value of the arguments to the callback as an array.
*/
Observable.fromCallback = function (fn, ctx, selector) {
return function () {
typeof ctx === 'undefined' && (ctx = this);
var len = arguments.length, args = new Array(len)
for(var i = 0; i < len; i++) { args[i] = arguments[i]; }
return createCbObservable(fn, ctx, selector, args);
};
};
function createNodeObservable(fn, ctx, selector, args) {
var o = new AsyncSubject();
args.push(createNodeHandler(o, ctx, selector));
fn.apply(ctx, args);
return o.asObservable();
}
function createNodeHandler(o, ctx, selector) {
return function handler () {
var err = arguments[0];
if (err) { return o.onError(err); }
var len = arguments.length, results = [];
for(var i = 1; i < len; i++) { results[i - 1] = arguments[i]; }
if (isFunction(selector)) {
var results = tryCatch(selector).apply(ctx, results);
if (results === errorObj) { return o.onError(results.e); }
o.onNext(results);
} else {
if (results.length <= 1) {
o.onNext(results[0]);
} else {
o.onNext(results);
}
}
o.onCompleted();
};
}
/**
* Converts a Node.js callback style function to an observable sequence. This must be in function (err, ...) format.
* @param {Function} fn The function to call
* @param {Mixed} [ctx] The context for the func parameter to be executed. If not specified, defaults to undefined.
* @param {Function} [selector] A selector which takes the arguments from the callback minus the error to produce a single item to yield on next.
* @returns {Function} An async function which when applied, returns an observable sequence with the callback arguments as an array.
*/
Observable.fromNodeCallback = function (fn, ctx, selector) {
return function () {
typeof ctx === 'undefined' && (ctx = this);
var len = arguments.length, args = new Array(len);
for(var i = 0; i < len; i++) { args[i] = arguments[i]; }
return createNodeObservable(fn, ctx, selector, args);
};
};
function isNodeList(el) {
if (root.StaticNodeList) {
// IE8 Specific
// instanceof is slower than Object#toString, but Object#toString will not work as intended in IE8
return el instanceof root.StaticNodeList || el instanceof root.NodeList;
} else {
return Object.prototype.toString.call(el) === '[object NodeList]';
}
}
function ListenDisposable(e, n, fn) {
this._e = e;
this._n = n;
this._fn = fn;
this._e.addEventListener(this._n, this._fn, false);
this.isDisposed = false;
}
ListenDisposable.prototype.dispose = function () {
if (!this.isDisposed) {
this._e.removeEventListener(this._n, this._fn, false);
this.isDisposed = true;
}
};
function createEventListener (el, eventName, handler) {
var disposables = new CompositeDisposable();
// Asume NodeList or HTMLCollection
var elemToString = Object.prototype.toString.call(el);
if (isNodeList(el) || elemToString === '[object HTMLCollection]') {
for (var i = 0, len = el.length; i < len; i++) {
disposables.add(createEventListener(el.item(i), eventName, handler));
}
} else if (el) {
disposables.add(new ListenDisposable(el, eventName, handler));
}
return disposables;
}
/**
* Configuration option to determine whether to use native events only
*/
Rx.config.useNativeEvents = false;
var EventObservable = (function(__super__) {
inherits(EventObservable, __super__);
function EventObservable(el, name, fn) {
this._el = el;
this._n = name;
this._fn = fn;
__super__.call(this);
}
function createHandler(o, fn) {
return function handler () {
var results = arguments[0];
if (isFunction(fn)) {
results = tryCatch(fn).apply(null, arguments);
if (results === errorObj) { return o.onError(results.e); }
}
o.onNext(results);
};
}
EventObservable.prototype.subscribeCore = function (o) {
return createEventListener(
this._el,
this._n,
createHandler(o, this._fn));
};
return EventObservable;
}(ObservableBase));
/**
* Creates an observable sequence by adding an event listener to the matching DOMElement or each item in the NodeList.
* @param {Object} element The DOMElement or NodeList to attach a listener.
* @param {String} eventName The event name to attach the observable sequence.
* @param {Function} [selector] A selector which takes the arguments from the event handler to produce a single item to yield on next.
* @returns {Observable} An observable sequence of events from the specified element and the specified event.
*/
Observable.fromEvent = function (element, eventName, selector) {
// Node.js specific
if (element.addListener) {
return fromEventPattern(
function (h) { element.addListener(eventName, h); },
function (h) { element.removeListener(eventName, h); },
selector);
}
// Use only if non-native events are allowed
if (!Rx.config.useNativeEvents) {
// Handles jq, Angular.js, Zepto, Marionette, Ember.js
if (typeof element.on === 'function' && typeof element.off === 'function') {
return fromEventPattern(
function (h) { element.on(eventName, h); },
function (h) { element.off(eventName, h); },
selector);
}
}
return new EventObservable(element, eventName, selector).publish().refCount();
};
var EventPatternObservable = (function(__super__) {
inherits(EventPatternObservable, __super__);
function EventPatternObservable(add, del, fn) {
this._add = add;
this._del = del;
this._fn = fn;
__super__.call(this);
}
function createHandler(o, fn) {
return function handler () {
var results = arguments[0];
if (isFunction(fn)) {
results = tryCatch(fn).apply(null, arguments);
if (results === errorObj) { return o.onError(results.e); }
}
o.onNext(results);
};
}
EventPatternObservable.prototype.subscribeCore = function (o) {
var fn = createHandler(o, this._fn);
var returnValue = this._add(fn);
return new EventPatternDisposable(this._del, fn, returnValue);
};
function EventPatternDisposable(del, fn, ret) {
this._del = del;
this._fn = fn;
this._ret = ret;
this.isDisposed = false;
}
EventPatternDisposable.prototype.dispose = function () {
if(!this.isDisposed) {
isFunction(this._del) && this._del(this._fn, this._ret);
this.isDisposed = true;
}
};
return EventPatternObservable;
}(ObservableBase));
/**
* Creates an observable sequence from an event emitter via an addHandler/removeHandler pair.
* @param {Function} addHandler The function to add a handler to the emitter.
* @param {Function} [removeHandler] The optional function to remove a handler from an emitter.
* @param {Function} [selector] A selector which takes the arguments from the event handler to produce a single item to yield on next.
* @returns {Observable} An observable sequence which wraps an event from an event emitter
*/
var fromEventPattern = Observable.fromEventPattern = function (addHandler, removeHandler, selector) {
return new EventPatternObservable(addHandler, removeHandler, selector).publish().refCount();
};
/**
* Invokes the asynchronous function, surfacing the result through an observable sequence.
* @param {Function} functionAsync Asynchronous function which returns a Promise to run.
* @returns {Observable} An observable sequence exposing the function's result value, or an exception.
*/
Observable.startAsync = function (functionAsync) {
var promise = tryCatch(functionAsync)();
if (promise === errorObj) { return observableThrow(promise.e); }
return observableFromPromise(promise);
};
return Rx;
}));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,532 @@
// Copyright (c) Microsoft, All rights reserved. See License.txt in the project root for license information.
;(function (factory) {
var objectTypes = {
'function': true,
'object': true
};
function checkGlobal(value) {
return (value && value.Object === Object) ? value : null;
}
var freeExports = (objectTypes[typeof exports] && exports && !exports.nodeType) ? exports : null;
var freeModule = (objectTypes[typeof module] && module && !module.nodeType) ? module : null;
var freeGlobal = checkGlobal(freeExports && freeModule && typeof global === 'object' && global);
var freeSelf = checkGlobal(objectTypes[typeof self] && self);
var freeWindow = checkGlobal(objectTypes[typeof window] && window);
var moduleExports = (freeModule && freeModule.exports === freeExports) ? freeExports : null;
var thisGlobal = checkGlobal(objectTypes[typeof this] && this);
var root = freeGlobal || ((freeWindow !== (thisGlobal && thisGlobal.window)) && freeWindow) || freeSelf || thisGlobal || Function('return this')();
// Because of build optimizers
if (typeof define === 'function' && define.amd) {
define(['./rx.binding', 'exports'], function (Rx, exports) {
root.Rx = factory(root, exports, Rx);
return root.Rx;
});
} else if (typeof module === 'object' && module && module.exports === freeExports) {
module.exports = factory(root, module.exports, require('./rx'));
} else {
root.Rx = factory(root, {}, root.Rx);
}
}.call(this, function (root, exp, Rx, undefined) {
// Aliases
var Observable = Rx.Observable,
observableFromPromise = Observable.fromPromise,
observableThrow = Observable.throwError,
AnonymousObservable = Rx.AnonymousObservable,
ObservableBase = Rx.ObservableBase,
AsyncSubject = Rx.AsyncSubject,
disposableCreate = Rx.Disposable.create,
CompositeDisposable = Rx.CompositeDisposable,
immediateScheduler = Rx.Scheduler.immediate,
defaultScheduler = Rx.Scheduler['default'],
inherits = Rx.internals.inherits,
isScheduler = Rx.Scheduler.isScheduler,
isPromise = Rx.helpers.isPromise,
isFunction = Rx.helpers.isFunction,
isIterable = Rx.helpers.isIterable,
isArrayLike = Rx.helpers.isArrayLike;
var errorObj = {e: {}};
function tryCatcherGen(tryCatchTarget) {
return function tryCatcher() {
try {
return tryCatchTarget.apply(this, arguments);
} catch (e) {
errorObj.e = e;
return errorObj;
}
};
}
var tryCatch = Rx.internals.tryCatch = function tryCatch(fn) {
if (!isFunction(fn)) { throw new TypeError('fn must be a function'); }
return tryCatcherGen(fn);
};
function thrower(e) {
throw e;
}
Observable.wrap = function (fn) {
function createObservable() {
return Observable.spawn.call(this, fn.apply(this, arguments));
}
createObservable.__generatorFunction__ = fn;
return createObservable;
};
var spawn = Observable.spawn = function () {
var gen = arguments[0], self = this, args = [];
for (var i = 1, len = arguments.length; i < len; i++) { args.push(arguments[i]); }
return new AnonymousObservable(function (o) {
var g = new CompositeDisposable();
if (isFunction(gen)) { gen = gen.apply(self, args); }
if (!gen || !isFunction(gen.next)) {
o.onNext(gen);
return o.onCompleted();
}
function processGenerator(res) {
var ret = tryCatch(gen.next).call(gen, res);
if (ret === errorObj) { return o.onError(ret.e); }
next(ret);
}
processGenerator();
function onError(err) {
var ret = tryCatch(gen.next).call(gen, err);
if (ret === errorObj) { return o.onError(ret.e); }
next(ret);
}
function next(ret) {
if (ret.done) {
o.onNext(ret.value);
o.onCompleted();
return;
}
var obs = toObservable.call(self, ret.value);
var value = null;
var hasValue = false;
if (Observable.isObservable(obs)) {
g.add(obs.subscribe(function(val) {
hasValue = true;
value = val;
}, onError, function() {
hasValue && processGenerator(value);
}));
} else {
onError(new TypeError('type not supported'));
}
}
return g;
});
};
function toObservable(obj) {
if (!obj) { return obj; }
if (Observable.isObservable(obj)) { return obj; }
if (isPromise(obj)) { return Observable.fromPromise(obj); }
if (isGeneratorFunction(obj) || isGenerator(obj)) { return spawn.call(this, obj); }
if (isFunction(obj)) { return thunkToObservable.call(this, obj); }
if (isArrayLike(obj) || isIterable(obj)) { return arrayToObservable.call(this, obj); }
if (isObject(obj)) {return objectToObservable.call(this, obj);}
return obj;
}
function arrayToObservable (obj) {
return Observable.from(obj).concatMap(function(o) {
if(Observable.isObservable(o) || isObject(o)) {
return toObservable.call(null, o);
} else {
return Rx.Observable.just(o);
}
}).toArray();
}
function objectToObservable (obj) {
var results = new obj.constructor(), keys = Object.keys(obj), observables = [];
for (var i = 0, len = keys.length; i < len; i++) {
var key = keys[i];
var observable = toObservable.call(this, obj[key]);
if(observable && Observable.isObservable(observable)) {
defer(observable, key);
} else {
results[key] = obj[key];
}
}
return Observable.forkJoin.apply(Observable, observables).map(function() {
return results;
});
function defer (observable, key) {
results[key] = undefined;
observables.push(observable.map(function (next) {
results[key] = next;
}));
}
}
function thunkToObservable(fn) {
var self = this;
return new AnonymousObservable(function (o) {
fn.call(self, function () {
var err = arguments[0], res = arguments[1];
if (err) { return o.onError(err); }
if (arguments.length > 2) {
var args = [];
for (var i = 1, len = arguments.length; i < len; i++) { args.push(arguments[i]); }
res = args;
}
o.onNext(res);
o.onCompleted();
});
});
}
function isGenerator(obj) {
return isFunction (obj.next) && isFunction (obj['throw']);
}
function isGeneratorFunction(obj) {
var ctor = obj.constructor;
if (!ctor) { return false; }
if (ctor.name === 'GeneratorFunction' || ctor.displayName === 'GeneratorFunction') { return true; }
return isGenerator(ctor.prototype);
}
function isObject(val) {
return Object == val.constructor;
}
/**
* Invokes the specified function asynchronously on the specified scheduler, surfacing the result through an observable sequence.
*
* @example
* var res = Rx.Observable.start(function () { console.log('hello'); });
* var res = Rx.Observable.start(function () { console.log('hello'); }, Rx.Scheduler.timeout);
* var res = Rx.Observable.start(function () { this.log('hello'); }, Rx.Scheduler.timeout, console);
*
* @param {Function} func Function to run asynchronously.
* @param {Scheduler} [scheduler] Scheduler to run the function on. If not specified, defaults to Scheduler.timeout.
* @param [context] The context for the func parameter to be executed. If not specified, defaults to undefined.
* @returns {Observable} An observable sequence exposing the function's result value, or an exception.
*
* Remarks
* * The function is called immediately, not during the subscription of the resulting sequence.
* * Multiple subscriptions to the resulting sequence can observe the function's result.
*/
Observable.start = function (func, context, scheduler) {
return observableToAsync(func, context, scheduler)();
};
/**
* Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler.
* @param {Function} function Function to convert to an asynchronous function.
* @param {Scheduler} [scheduler] Scheduler to run the function on. If not specified, defaults to Scheduler.timeout.
* @param {Mixed} [context] The context for the func parameter to be executed. If not specified, defaults to undefined.
* @returns {Function} Asynchronous function.
*/
var observableToAsync = Observable.toAsync = function (func, context, scheduler) {
isScheduler(scheduler) || (scheduler = defaultScheduler);
return function () {
var args = arguments,
subject = new AsyncSubject();
scheduler.schedule(null, function () {
var result;
try {
result = func.apply(context, args);
} catch (e) {
subject.onError(e);
return;
}
subject.onNext(result);
subject.onCompleted();
});
return subject.asObservable();
};
};
function createCbObservable(fn, ctx, selector, args) {
var o = new AsyncSubject();
args.push(createCbHandler(o, ctx, selector));
fn.apply(ctx, args);
return o.asObservable();
}
function createCbHandler(o, ctx, selector) {
return function handler () {
var len = arguments.length, results = new Array(len);
for(var i = 0; i < len; i++) { results[i] = arguments[i]; }
if (isFunction(selector)) {
results = tryCatch(selector).apply(ctx, results);
if (results === errorObj) { return o.onError(results.e); }
o.onNext(results);
} else {
if (results.length <= 1) {
o.onNext(results[0]);
} else {
o.onNext(results);
}
}
o.onCompleted();
};
}
/**
* Converts a callback function to an observable sequence.
*
* @param {Function} fn Function with a callback as the last parameter to convert to an Observable sequence.
* @param {Mixed} [ctx] The context for the func parameter to be executed. If not specified, defaults to undefined.
* @param {Function} [selector] A selector which takes the arguments from the callback to produce a single item to yield on next.
* @returns {Function} A function, when executed with the required parameters minus the callback, produces an Observable sequence with a single value of the arguments to the callback as an array.
*/
Observable.fromCallback = function (fn, ctx, selector) {
return function () {
typeof ctx === 'undefined' && (ctx = this);
var len = arguments.length, args = new Array(len)
for(var i = 0; i < len; i++) { args[i] = arguments[i]; }
return createCbObservable(fn, ctx, selector, args);
};
};
function createNodeObservable(fn, ctx, selector, args) {
var o = new AsyncSubject();
args.push(createNodeHandler(o, ctx, selector));
fn.apply(ctx, args);
return o.asObservable();
}
function createNodeHandler(o, ctx, selector) {
return function handler () {
var err = arguments[0];
if (err) { return o.onError(err); }
var len = arguments.length, results = [];
for(var i = 1; i < len; i++) { results[i - 1] = arguments[i]; }
if (isFunction(selector)) {
var results = tryCatch(selector).apply(ctx, results);
if (results === errorObj) { return o.onError(results.e); }
o.onNext(results);
} else {
if (results.length <= 1) {
o.onNext(results[0]);
} else {
o.onNext(results);
}
}
o.onCompleted();
};
}
/**
* Converts a Node.js callback style function to an observable sequence. This must be in function (err, ...) format.
* @param {Function} fn The function to call
* @param {Mixed} [ctx] The context for the func parameter to be executed. If not specified, defaults to undefined.
* @param {Function} [selector] A selector which takes the arguments from the callback minus the error to produce a single item to yield on next.
* @returns {Function} An async function which when applied, returns an observable sequence with the callback arguments as an array.
*/
Observable.fromNodeCallback = function (fn, ctx, selector) {
return function () {
typeof ctx === 'undefined' && (ctx = this);
var len = arguments.length, args = new Array(len);
for(var i = 0; i < len; i++) { args[i] = arguments[i]; }
return createNodeObservable(fn, ctx, selector, args);
};
};
function isNodeList(el) {
if (root.StaticNodeList) {
// IE8 Specific
// instanceof is slower than Object#toString, but Object#toString will not work as intended in IE8
return el instanceof root.StaticNodeList || el instanceof root.NodeList;
} else {
return Object.prototype.toString.call(el) === '[object NodeList]';
}
}
function ListenDisposable(e, n, fn) {
this._e = e;
this._n = n;
this._fn = fn;
this._e.addEventListener(this._n, this._fn, false);
this.isDisposed = false;
}
ListenDisposable.prototype.dispose = function () {
if (!this.isDisposed) {
this._e.removeEventListener(this._n, this._fn, false);
this.isDisposed = true;
}
};
function createEventListener (el, eventName, handler) {
var disposables = new CompositeDisposable();
// Asume NodeList or HTMLCollection
var elemToString = Object.prototype.toString.call(el);
if (isNodeList(el) || elemToString === '[object HTMLCollection]') {
for (var i = 0, len = el.length; i < len; i++) {
disposables.add(createEventListener(el.item(i), eventName, handler));
}
} else if (el) {
disposables.add(new ListenDisposable(el, eventName, handler));
}
return disposables;
}
/**
* Configuration option to determine whether to use native events only
*/
Rx.config.useNativeEvents = false;
var EventObservable = (function(__super__) {
inherits(EventObservable, __super__);
function EventObservable(el, name, fn) {
this._el = el;
this._n = name;
this._fn = fn;
__super__.call(this);
}
function createHandler(o, fn) {
return function handler () {
var results = arguments[0];
if (isFunction(fn)) {
results = tryCatch(fn).apply(null, arguments);
if (results === errorObj) { return o.onError(results.e); }
}
o.onNext(results);
};
}
EventObservable.prototype.subscribeCore = function (o) {
return createEventListener(
this._el,
this._n,
createHandler(o, this._fn));
};
return EventObservable;
}(ObservableBase));
/**
* Creates an observable sequence by adding an event listener to the matching DOMElement or each item in the NodeList.
* @param {Object} element The DOMElement or NodeList to attach a listener.
* @param {String} eventName The event name to attach the observable sequence.
* @param {Function} [selector] A selector which takes the arguments from the event handler to produce a single item to yield on next.
* @returns {Observable} An observable sequence of events from the specified element and the specified event.
*/
Observable.fromEvent = function (element, eventName, selector) {
// Node.js specific
if (element.addListener) {
return fromEventPattern(
function (h) { element.addListener(eventName, h); },
function (h) { element.removeListener(eventName, h); },
selector);
}
// Use only if non-native events are allowed
if (!Rx.config.useNativeEvents) {
// Handles jq, Angular.js, Zepto, Marionette, Ember.js
if (typeof element.on === 'function' && typeof element.off === 'function') {
return fromEventPattern(
function (h) { element.on(eventName, h); },
function (h) { element.off(eventName, h); },
selector);
}
}
return new EventObservable(element, eventName, selector).publish().refCount();
};
var EventPatternObservable = (function(__super__) {
inherits(EventPatternObservable, __super__);
function EventPatternObservable(add, del, fn) {
this._add = add;
this._del = del;
this._fn = fn;
__super__.call(this);
}
function createHandler(o, fn) {
return function handler () {
var results = arguments[0];
if (isFunction(fn)) {
results = tryCatch(fn).apply(null, arguments);
if (results === errorObj) { return o.onError(results.e); }
}
o.onNext(results);
};
}
EventPatternObservable.prototype.subscribeCore = function (o) {
var fn = createHandler(o, this._fn);
var returnValue = this._add(fn);
return new EventPatternDisposable(this._del, fn, returnValue);
};
function EventPatternDisposable(del, fn, ret) {
this._del = del;
this._fn = fn;
this._ret = ret;
this.isDisposed = false;
}
EventPatternDisposable.prototype.dispose = function () {
if(!this.isDisposed) {
isFunction(this._del) && this._del(this._fn, this._ret);
this.isDisposed = true;
}
};
return EventPatternObservable;
}(ObservableBase));
/**
* Creates an observable sequence from an event emitter via an addHandler/removeHandler pair.
* @param {Function} addHandler The function to add a handler to the emitter.
* @param {Function} [removeHandler] The optional function to remove a handler from an emitter.
* @param {Function} [selector] A selector which takes the arguments from the event handler to produce a single item to yield on next.
* @returns {Observable} An observable sequence which wraps an event from an event emitter
*/
var fromEventPattern = Observable.fromEventPattern = function (addHandler, removeHandler, selector) {
return new EventPatternObservable(addHandler, removeHandler, selector).publish().refCount();
};
/**
* Invokes the asynchronous function, surfacing the result through an observable sequence.
* @param {Function} functionAsync Asynchronous function which returns a Promise to run.
* @returns {Observable} An observable sequence exposing the function's result value, or an exception.
*/
Observable.startAsync = function (functionAsync) {
var promise = tryCatch(functionAsync)();
if (promise === errorObj) { return observableThrow(promise.e); }
return observableFromPromise(promise);
};
return Rx;
}));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,616 @@
// Copyright (c) Microsoft, All rights reserved. See License.txt in the project root for license information.
;(function (factory) {
var objectTypes = {
'function': true,
'object': true
};
function checkGlobal(value) {
return (value && value.Object === Object) ? value : null;
}
var freeExports = (objectTypes[typeof exports] && exports && !exports.nodeType) ? exports : null;
var freeModule = (objectTypes[typeof module] && module && !module.nodeType) ? module : null;
var freeGlobal = checkGlobal(freeExports && freeModule && typeof global === 'object' && global);
var freeSelf = checkGlobal(objectTypes[typeof self] && self);
var freeWindow = checkGlobal(objectTypes[typeof window] && window);
var moduleExports = (freeModule && freeModule.exports === freeExports) ? freeExports : null;
var thisGlobal = checkGlobal(objectTypes[typeof this] && this);
var root = freeGlobal || ((freeWindow !== (thisGlobal && thisGlobal.window)) && freeWindow) || freeSelf || thisGlobal || Function('return this')();
// Because of build optimizers
if (typeof define === 'function' && define.amd) {
define(['./rx'], function (Rx, exports) {
return factory(root, exports, Rx);
});
} else if (typeof module === 'object' && module && module.exports === freeExports) {
module.exports = factory(root, module.exports, require('./rx'));
} else {
root.Rx = factory(root, {}, root.Rx);
}
}.call(this, function (root, exp, Rx, undefined) {
// References
var Observable = Rx.Observable,
observableProto = Observable.prototype,
AnonymousObservable = Rx.AnonymousObservable,
AbstractObserver = Rx.internals.AbstractObserver,
CompositeDisposable = Rx.CompositeDisposable,
BinaryDisposable = Rx.BinaryDisposable,
NAryDisposable = Rx.NAryDisposable,
Notification = Rx.Notification,
Subject = Rx.Subject,
Observer = Rx.Observer,
disposableEmpty = Rx.Disposable.empty,
disposableCreate = Rx.Disposable.create,
inherits = Rx.internals.inherits,
addProperties = Rx.internals.addProperties,
defaultScheduler = Rx.Scheduler['default'],
currentThreadScheduler = Rx.Scheduler.currentThread,
identity = Rx.helpers.identity,
isScheduler = Rx.Scheduler.isScheduler,
isFunction = Rx.helpers.isFunction,
checkDisposed = Rx.Disposable.checkDisposed;
var errorObj = {e: {}};
function tryCatcherGen(tryCatchTarget) {
return function tryCatcher() {
try {
return tryCatchTarget.apply(this, arguments);
} catch (e) {
errorObj.e = e;
return errorObj;
}
};
}
var tryCatch = Rx.internals.tryCatch = function tryCatch(fn) {
if (!isFunction(fn)) { throw new TypeError('fn must be a function'); }
return tryCatcherGen(fn);
};
function thrower(e) {
throw e;
}
/**
* Used to pause and resume streams.
*/
Rx.Pauser = (function (__super__) {
inherits(Pauser, __super__);
function Pauser() {
__super__.call(this);
}
/**
* Pauses the underlying sequence.
*/
Pauser.prototype.pause = function () { this.onNext(false); };
/**
* Resumes the underlying sequence.
*/
Pauser.prototype.resume = function () { this.onNext(true); };
return Pauser;
}(Subject));
var PausableObservable = (function (__super__) {
inherits(PausableObservable, __super__);
function PausableObservable(source, pauser) {
this.source = source;
this.controller = new Subject();
this.paused = true;
if (pauser && pauser.subscribe) {
this.pauser = this.controller.merge(pauser);
} else {
this.pauser = this.controller;
}
__super__.call(this);
}
PausableObservable.prototype._subscribe = function (o) {
var conn = this.source.publish(),
subscription = conn.subscribe(o),
connection = disposableEmpty;
var pausable = this.pauser.startWith(!this.paused).distinctUntilChanged().subscribe(function (b) {
if (b) {
connection = conn.connect();
} else {
connection.dispose();
connection = disposableEmpty;
}
});
return new NAryDisposable([subscription, connection, pausable]);
};
PausableObservable.prototype.pause = function () {
this.paused = true;
this.controller.onNext(false);
};
PausableObservable.prototype.resume = function () {
this.paused = false;
this.controller.onNext(true);
};
return PausableObservable;
}(Observable));
/**
* Pauses the underlying observable sequence based upon the observable sequence which yields true/false.
* @example
* var pauser = new Rx.Subject();
* var source = Rx.Observable.interval(100).pausable(pauser);
* @param {Observable} pauser The observable sequence used to pause the underlying sequence.
* @returns {Observable} The observable sequence which is paused based upon the pauser.
*/
observableProto.pausable = function (pauser) {
return new PausableObservable(this, pauser);
};
function combineLatestSource(source, subject, resultSelector) {
return new AnonymousObservable(function (o) {
var hasValue = [false, false],
hasValueAll = false,
isDone = false,
values = new Array(2),
err;
function next(x, i) {
values[i] = x;
hasValue[i] = true;
if (hasValueAll || (hasValueAll = hasValue.every(identity))) {
if (err) { return o.onError(err); }
var res = tryCatch(resultSelector).apply(null, values);
if (res === errorObj) { return o.onError(res.e); }
o.onNext(res);
}
isDone && values[1] && o.onCompleted();
}
return new BinaryDisposable(
source.subscribe(
function (x) {
next(x, 0);
},
function (e) {
if (values[1]) {
o.onError(e);
} else {
err = e;
}
},
function () {
isDone = true;
values[1] && o.onCompleted();
}),
subject.subscribe(
function (x) {
next(x, 1);
},
function (e) { o.onError(e); },
function () {
isDone = true;
next(true, 1);
})
);
}, source);
}
var PausableBufferedObservable = (function (__super__) {
inherits(PausableBufferedObservable, __super__);
function PausableBufferedObservable(source, pauser) {
this.source = source;
this.controller = new Subject();
this.paused = true;
if (pauser && pauser.subscribe) {
this.pauser = this.controller.merge(pauser);
} else {
this.pauser = this.controller;
}
__super__.call(this);
}
PausableBufferedObservable.prototype._subscribe = function (o) {
var q = [], previousShouldFire;
function drainQueue() { while (q.length > 0) { o.onNext(q.shift()); } }
var subscription =
combineLatestSource(
this.source,
this.pauser.startWith(!this.paused).distinctUntilChanged(),
function (data, shouldFire) {
return { data: data, shouldFire: shouldFire };
})
.subscribe(
function (results) {
if (previousShouldFire !== undefined && results.shouldFire !== previousShouldFire) {
previousShouldFire = results.shouldFire;
// change in shouldFire
if (results.shouldFire) { drainQueue(); }
} else {
previousShouldFire = results.shouldFire;
// new data
if (results.shouldFire) {
o.onNext(results.data);
} else {
q.push(results.data);
}
}
},
function (err) {
drainQueue();
o.onError(err);
},
function () {
drainQueue();
o.onCompleted();
}
);
return subscription;
};
PausableBufferedObservable.prototype.pause = function () {
this.paused = true;
this.controller.onNext(false);
};
PausableBufferedObservable.prototype.resume = function () {
this.paused = false;
this.controller.onNext(true);
};
return PausableBufferedObservable;
}(Observable));
/**
* Pauses the underlying observable sequence based upon the observable sequence which yields true/false,
* and yields the values that were buffered while paused.
* @example
* var pauser = new Rx.Subject();
* var source = Rx.Observable.interval(100).pausableBuffered(pauser);
* @param {Observable} pauser The observable sequence used to pause the underlying sequence.
* @returns {Observable} The observable sequence which is paused based upon the pauser.
*/
observableProto.pausableBuffered = function (pauser) {
return new PausableBufferedObservable(this, pauser);
};
var ControlledObservable = (function (__super__) {
inherits(ControlledObservable, __super__);
function ControlledObservable (source, enableQueue, scheduler) {
__super__.call(this);
this.subject = new ControlledSubject(enableQueue, scheduler);
this.source = source.multicast(this.subject).refCount();
}
ControlledObservable.prototype._subscribe = function (o) {
return this.source.subscribe(o);
};
ControlledObservable.prototype.request = function (numberOfItems) {
return this.subject.request(numberOfItems == null ? -1 : numberOfItems);
};
return ControlledObservable;
}(Observable));
var ControlledSubject = (function (__super__) {
inherits(ControlledSubject, __super__);
function ControlledSubject(enableQueue, scheduler) {
enableQueue == null && (enableQueue = true);
__super__.call(this);
this.subject = new Subject();
this.enableQueue = enableQueue;
this.queue = enableQueue ? [] : null;
this.requestedCount = 0;
this.requestedDisposable = null;
this.error = null;
this.hasFailed = false;
this.hasCompleted = false;
this.scheduler = scheduler || currentThreadScheduler;
}
addProperties(ControlledSubject.prototype, Observer, {
_subscribe: function (o) {
return this.subject.subscribe(o);
},
onCompleted: function () {
this.hasCompleted = true;
if (!this.enableQueue || this.queue.length === 0) {
this.subject.onCompleted();
this.disposeCurrentRequest();
} else {
this.queue.push(Notification.createOnCompleted());
}
},
onError: function (error) {
this.hasFailed = true;
this.error = error;
if (!this.enableQueue || this.queue.length === 0) {
this.subject.onError(error);
this.disposeCurrentRequest();
} else {
this.queue.push(Notification.createOnError(error));
}
},
onNext: function (value) {
if (this.requestedCount <= 0) {
this.enableQueue && this.queue.push(Notification.createOnNext(value));
} else {
(this.requestedCount-- === 0) && this.disposeCurrentRequest();
this.subject.onNext(value);
}
},
_processRequest: function (numberOfItems) {
if (this.enableQueue) {
while (this.queue.length > 0 && (numberOfItems > 0 || this.queue[0].kind !== 'N')) {
var first = this.queue.shift();
first.accept(this.subject);
if (first.kind === 'N') {
numberOfItems--;
} else {
this.disposeCurrentRequest();
this.queue = [];
}
}
}
return numberOfItems;
},
request: function (number) {
this.disposeCurrentRequest();
var self = this;
this.requestedDisposable = this.scheduler.schedule(number,
function(s, i) {
var remaining = self._processRequest(i);
var stopped = self.hasCompleted || self.hasFailed;
if (!stopped && remaining > 0) {
self.requestedCount = remaining;
return disposableCreate(function () {
self.requestedCount = 0;
});
// Scheduled item is still in progress. Return a new
// disposable to allow the request to be interrupted
// via dispose.
}
});
return this.requestedDisposable;
},
disposeCurrentRequest: function () {
if (this.requestedDisposable) {
this.requestedDisposable.dispose();
this.requestedDisposable = null;
}
}
});
return ControlledSubject;
}(Observable));
/**
* Attaches a controller to the observable sequence with the ability to queue.
* @example
* var source = Rx.Observable.interval(100).controlled();
* source.request(3); // Reads 3 values
* @param {bool} enableQueue truthy value to determine if values should be queued pending the next request
* @param {Scheduler} scheduler determines how the requests will be scheduled
* @returns {Observable} The observable sequence which only propagates values on request.
*/
observableProto.controlled = function (enableQueue, scheduler) {
if (enableQueue && isScheduler(enableQueue)) {
scheduler = enableQueue;
enableQueue = true;
}
if (enableQueue == null) { enableQueue = true; }
return new ControlledObservable(this, enableQueue, scheduler);
};
var StopAndWaitObservable = (function (__super__) {
inherits(StopAndWaitObservable, __super__);
function StopAndWaitObservable (source) {
__super__.call(this);
this.source = source;
}
function scheduleMethod(s, self) {
return self.source.request(1);
}
StopAndWaitObservable.prototype._subscribe = function (o) {
this.subscription = this.source.subscribe(new StopAndWaitObserver(o, this, this.subscription));
return new BinaryDisposable(
this.subscription,
defaultScheduler.schedule(this, scheduleMethod)
);
};
var StopAndWaitObserver = (function (__sub__) {
inherits(StopAndWaitObserver, __sub__);
function StopAndWaitObserver (observer, observable, cancel) {
__sub__.call(this);
this.observer = observer;
this.observable = observable;
this.cancel = cancel;
this.scheduleDisposable = null;
}
StopAndWaitObserver.prototype.completed = function () {
this.observer.onCompleted();
this.dispose();
};
StopAndWaitObserver.prototype.error = function (error) {
this.observer.onError(error);
this.dispose();
};
function innerScheduleMethod(s, self) {
return self.observable.source.request(1);
}
StopAndWaitObserver.prototype.next = function (value) {
this.observer.onNext(value);
this.scheduleDisposable = defaultScheduler.schedule(this, innerScheduleMethod);
};
StopAndWaitObserver.dispose = function () {
this.observer = null;
if (this.cancel) {
this.cancel.dispose();
this.cancel = null;
}
if (this.scheduleDisposable) {
this.scheduleDisposable.dispose();
this.scheduleDisposable = null;
}
__sub__.prototype.dispose.call(this);
};
return StopAndWaitObserver;
}(AbstractObserver));
return StopAndWaitObservable;
}(Observable));
/**
* Attaches a stop and wait observable to the current observable.
* @returns {Observable} A stop and wait observable.
*/
ControlledObservable.prototype.stopAndWait = function () {
return new StopAndWaitObservable(this);
};
var WindowedObservable = (function (__super__) {
inherits(WindowedObservable, __super__);
function WindowedObservable(source, windowSize) {
__super__.call(this);
this.source = source;
this.windowSize = windowSize;
}
function scheduleMethod(s, self) {
return self.source.request(self.windowSize);
}
WindowedObservable.prototype._subscribe = function (o) {
this.subscription = this.source.subscribe(new WindowedObserver(o, this, this.subscription));
return new BinaryDisposable(
this.subscription,
defaultScheduler.schedule(this, scheduleMethod)
);
};
var WindowedObserver = (function (__sub__) {
inherits(WindowedObserver, __sub__);
function WindowedObserver(observer, observable, cancel) {
this.observer = observer;
this.observable = observable;
this.cancel = cancel;
this.received = 0;
this.scheduleDisposable = null;
__sub__.call(this);
}
WindowedObserver.prototype.completed = function () {
this.observer.onCompleted();
this.dispose();
};
WindowedObserver.prototype.error = function (error) {
this.observer.onError(error);
this.dispose();
};
function innerScheduleMethod(s, self) {
return self.observable.source.request(self.observable.windowSize);
}
WindowedObserver.prototype.next = function (value) {
this.observer.onNext(value);
this.received = ++this.received % this.observable.windowSize;
this.received === 0 && (this.scheduleDisposable = defaultScheduler.schedule(this, innerScheduleMethod));
};
WindowedObserver.prototype.dispose = function () {
this.observer = null;
if (this.cancel) {
this.cancel.dispose();
this.cancel = null;
}
if (this.scheduleDisposable) {
this.scheduleDisposable.dispose();
this.scheduleDisposable = null;
}
__sub__.prototype.dispose.call(this);
};
return WindowedObserver;
}(AbstractObserver));
return WindowedObservable;
}(Observable));
/**
* Creates a sliding windowed observable based upon the window size.
* @param {Number} windowSize The number of items in the window
* @returns {Observable} A windowed observable based upon the window size.
*/
ControlledObservable.prototype.windowed = function (windowSize) {
return new WindowedObservable(this, windowSize);
};
/**
* Pipes the existing Observable sequence into a Node.js Stream.
* @param {Stream} dest The destination Node.js stream.
* @returns {Stream} The destination stream.
*/
observableProto.pipe = function (dest) {
var source = this.pausableBuffered();
function onDrain() {
source.resume();
}
dest.addListener('drain', onDrain);
source.subscribe(
function (x) {
!dest.write(x) && source.pause();
},
function (err) {
dest.emit('error', err);
},
function () {
// Hack check because STDIO is not closable
!dest._isStdio && dest.end();
dest.removeListener('drain', onDrain);
});
source.resume();
return dest;
};
return Rx;
}));

Some files were not shown because too many files have changed in this diff Show More