Skip to content

Commit

Permalink
fixes livereload
Browse files Browse the repository at this point in the history
  • Loading branch information
jelhan committed Aug 6, 2019
1 parent 2324766 commit d71386b
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 39 deletions.
73 changes: 34 additions & 39 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ let appendSourceList = function(policyObject, name, sourceList) {
};

// appends directives needed for Ember CLI live reload feature to policy object
let allowLiveReload = function(policyObject) {
let { hostname, port, ssl } = this._config.liveReload;
let allowLiveReload = function(policyObject, config) {
let { hostname, port, ssl } = config.liveReload;

['localhost', '0.0.0.0', hostname].filter(Boolean).forEach(function(hostname) {
let protocol = ssl ? 'wss://' : 'ws://';
Expand All @@ -86,10 +86,28 @@ module.exports = {
name: require('./package').name,

serverMiddleware: function(config) {
let app = config.app;
let options = config.options;

app.use((req, res, next) => {
let expressApp = config.app;

let emberApp = this.app;
let environment = emberApp.env;
let ownConfig = readConfig(emberApp.project, environment); // config/content-security-policy.js
let middlewareOptions = config.options;
let liveReloadConfig = {
enabled: middlewareOptions.liveReload,
hostname: middlewareOptions.liveReloadHost,
port: middlewareOptions.liveReloadPort,
ssl: middlewareOptions.ssl
};
let runConfig = emberApp.project.config(); // config/environment.js
let ui = emberApp.project.ui;

// Not all information needed to calculate configuration is available by public API
// in all hooks. Especially `contentFor` hook is missing most informations that are
// required. Therefore configuration is calculated in `serverMiddleware` hook, cached
// and reused in `contentFor` hook, which is executed later.
this._config = calculateConfig(environment, ownConfig, liveReloadConfig, runConfig, ui);

expressApp.use((req, res, next) => {
if (!this._config.enabled) {
next();
return;
Expand All @@ -106,14 +124,14 @@ module.exports = {
}

if (this._config.liveReload.enabled) {
allowLiveReload(policyObject, options);
allowLiveReload(policyObject, this._config);
}

// only needed for headers, since report-uri cannot be specified in meta tag
if (header.indexOf('Report-Only') !== -1 && !('report-uri' in policyObject)) {
let ecHost = options.host || 'localhost';
let ecProtocol = options.ssl ? 'https://' : 'http://';
let ecOrigin = ecProtocol + ecHost + ':' + options.port;
let ecHost = this._config.liveReload.host || 'localhost';
let ecProtocol = this._config.liveReload.ssl ? 'https://' : 'http://';
let ecOrigin = ecProtocol + ecHost + ':' + this._config.liveReload.port;
appendSourceList(policyObject, 'connect-src', ecOrigin);
policyObject['report-uri'] = ecOrigin + REPORT_PATH;
}
Expand All @@ -139,9 +157,9 @@ module.exports = {
});

let bodyParser = require('body-parser');
app.use(REPORT_PATH, bodyParser.json({ type: 'application/csp-report' }));
app.use(REPORT_PATH, bodyParser.json({ type: 'application/json' }));
app.use(REPORT_PATH, function(req, res, _next) {
expressApp.use(REPORT_PATH, bodyParser.json({ type: 'application/csp-report' }));
expressApp.use(REPORT_PATH, bodyParser.json({ type: 'application/json' }));
expressApp.use(REPORT_PATH, function(req, res, _next) {
// eslint-disable-next-line no-console
console.log(chalk.red('Content Security Policy violation:') + '\n\n' + JSON.stringify(req.body, null, 2));
res.send({ status:'ok' });
Expand All @@ -168,7 +186,7 @@ module.exports = {
}

if (this._config.liveReload.enabled) {
allowLiveReload(policyObject);
allowLiveReload(policyObject, this._config);
}

// clone policy object cause config should not be mutated
Expand Down Expand Up @@ -201,28 +219,13 @@ module.exports = {
includedCommands: function() {
return require('./lib/commands');
},

// Configuration is only available by public API in `app` passed to `included` hook.
// We calculate configuration in `included` hook and use it in `serverMiddleware`
// and `contentFor` hooks, which are executed later. This is necessary cause Ember CLI
// does not provide a public API to read build time configuation (`ember-cli-build.js`)
// yet. `this._findHost(this).options` seems to be the only reliable way to get it in
// these hooks but is private API.
included: function(app) {
let environment = app.env;
let ownConfig = readConfig(app.project, environment); // config/content-security-policy.js
let buildConfig = app.options || {}; // build-time configuration including livereload and ssl options
let runConfig = app.project.config(); // config/environment.js
let ui = app.project.ui;

this._config = calculateConfig(environment, ownConfig, buildConfig, runConfig, ui);
},
};

function calculateConfig(environment, ownConfig, buildConfig, runConfig, ui) {
function calculateConfig(environment, ownConfig, liveReloadConfig, runConfig, ui) {
let config = {
delivery: [DELIVERY_HEADER],
enabled: true,
liveReload: liveReloadConfig,
policy: {
'default-src': [CSP_NONE],
'script-src': [CSP_SELF],
Expand Down Expand Up @@ -261,14 +264,6 @@ function calculateConfig(environment, ownConfig, buildConfig, runConfig, ui) {
config.reportOnly = runConfig.contentSecurityPolicyHeader !== CSP_HEADER;
}

// live reload configuration is required to allow the hosts used by it
config.liveReload = {
enabled: buildConfig.liveReload,
host: buildConfig.liveReloadHost,
port: buildConfig.liveReloadPort,
ssl: buildConfig.ssl
}

// apply configuration
Object.assign(config, ownConfig);

Expand Down
19 changes: 19 additions & 0 deletions node-tests/e2e/deliver-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,23 @@ describe('e2e: delivers CSP as configured', function() {
expect(response.body).to.not.match(CSP_META_TAG_REG_EXP);
});
});

describe('supports livereaload', function() {
it('adds CSP directives required by livereload', async function() {
await app.startServer();

let response = await request({
url: 'http://localhost:49741',
headers: {
'Accept': 'text/html'
}
});

let csp = response.headers['content-security-policy-report-only'];
expect(csp).to.match(/connect-src [^;]* ws:\/\/0.0.0.0:49741/);
expect(csp).to.match(/connect-src [^;]* ws:\/\/localhost:49741/);
expect(csp).to.match(/script-src [^;]* 0.0.0.0:49741/);
expect(csp).to.match(/script-src [^;]* localhost:49741/);
});
});
});

0 comments on commit d71386b

Please sign in to comment.