Skip to content

Commit

Permalink
fix livereload (#105)
Browse files Browse the repository at this point in the history
fix livereload
  • Loading branch information
rwjblue committed Aug 8, 2019
2 parents 0065c0f + 1454fd6 commit fbddcc6
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 34 deletions.
54 changes: 30 additions & 24 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, liveReloadConfig) {
let { hostname, port, ssl } = liveReloadConfig;

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

serverMiddleware: function(config) {
let app = config.app;
let options = config.options;
serverMiddleware: function({ app, options }) {
// Calculate livereload settings and cache it to be reused in `contentFor` hook.
// Can't do that one in another hook cause it depends on middleware options,
// which are only available in `serverMiddleware` hook.
if (options.liveReload) {
this._liveReload = {
host: options.liveReloadHost,
port: options.liveReloadPort,
ssl: options.ssl
}
}

app.use((req, res, next) => {
if (!this._config.enabled) {
Expand All @@ -105,8 +113,8 @@ module.exports = {
appendSourceList(policyObject, 'script-src', "'nonce-" + STATIC_TEST_NONCE + "'");
}

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

// only needed for headers, since report-uri cannot be specified in meta tag
Expand Down Expand Up @@ -167,8 +175,8 @@ module.exports = {
appendSourceList(policyObject, 'script-src', "'nonce-" + STATIC_TEST_NONCE + "'");
}

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

// clone policy object cause config should not be mutated
Expand Down Expand Up @@ -204,22 +212,28 @@ module.exports = {

// 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.
// and `contentFor` hooks, which are executed later. This prevents us from needing to
// calculate the config more than once. We can't do this in `contentFor` hook cause
// that one is executed after `serverMiddleware` and can't do it in `serverMiddleware`
// hook cause that one is only executed on `ember serve` but not on `ember build` or
// `ember test`. We can't do it in `init` hook cause app is not available by then.
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);
this._config = calculateConfig(environment, ownConfig, runConfig, ui);
},

// holds configuration for this addon
_config: null,

// holds live reload configuration if express server is used and live reload is enabled
_liveReload: null,
};

function calculateConfig(environment, ownConfig, buildConfig, runConfig, ui) {
function calculateConfig(environment, ownConfig, runConfig, ui) {
let config = {
delivery: [DELIVERY_HEADER],
enabled: true,
Expand Down Expand Up @@ -261,14 +275,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
50 changes: 49 additions & 1 deletion node-tests/e2e/deliver-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,27 @@ const fs = require('fs-extra');

const CSP_META_TAG_REG_EXP = /<meta http-equiv="Content-Security-Policy" content="(.*)">/i;

function getConfigPath(app) {
return app.filePath('config/content-security-policy.js');
}

async function setConfig(app, config) {
let file = app.filePath('config/content-security-policy.js');
let file = getConfigPath(app);
let content = `module.exports = function() { return ${JSON.stringify(config)}; }`;

await fs.writeFile(file, content);
}

async function removeConfig(app) {
let file = getConfigPath(app);

if (!fs.existsSync(file)) {
return;
}

await fs.remove(file);
}

describe('e2e: delivers CSP as configured', function() {
this.timeout(300000);

Expand All @@ -24,6 +38,10 @@ describe('e2e: delivers CSP as configured', function() {
await app.create('default', { noFixtures: true });
});

afterEach(async function() {
await removeConfig(app);
});

// Server isn't shutdown successfully if `app.startServer()` and `app.stopServer()`
// are not wrapped inside a describe block. Therefore all tests after the first one
// fail with a "Port 49741 is already in use" error.
Expand Down Expand Up @@ -122,4 +140,34 @@ describe('e2e: delivers CSP as configured', function() {
expect(response.body).to.not.match(CSP_META_TAG_REG_EXP);
});
});

describe('supports live reload', function() {
afterEach(async function() {
await app.stopServer();
});

it('adds CSP directives required by live reload', async function() {
await setConfig(app, {
delivery: ['header', 'meta'],
});

await app.startServer();

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

let cspInHeader = response.headers['content-security-policy-report-only'];
let cspInMetaElement = response.body.match(CSP_META_TAG_REG_EXP)[1];
[cspInHeader, cspInMetaElement].forEach((csp) => {
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/);
});
});
});
});
12 changes: 3 additions & 9 deletions node-tests/unit/config-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ describe('unit: configuration', function() {
});

it('is enabled by default', function() {
let config = calculateConfig('development', {}, {}, {}, UIMock);
let config = calculateConfig('development', {}, {}, UIMock);
expect(config.enabled).to.be.true;
});

it('delivers CSP by HTTP header by default', function() {
let config = calculateConfig('development', {}, {}, {}, UIMock);
let config = calculateConfig('development', {}, {}, UIMock);
expect(config.delivery).to.deep.equal(['header']);
});

it('defaults to report only mode', function() {
let config = calculateConfig('development', {}, {}, {}, UIMock);
let config = calculateConfig('development', {}, {}, UIMock);
expect(config.reportOnly).to.be.true;
});

Expand All @@ -35,7 +35,6 @@ describe('unit: configuration', function() {
}
},
{},
{},
UIMock
);
expect(config.policy).to.deep.equal({
Expand All @@ -49,7 +48,6 @@ describe('unit: configuration', function() {
let config = calculateConfig(
'development',
{},
{},
{
contentSecurityPolicy: {
'default-src': ["'self'"],
Expand All @@ -73,7 +71,6 @@ describe('unit: configuration', function() {
let config = calculateConfig(
'development',
{},
{},
{ contentSecurityPolicyMeta: true },
UIMock
);
Expand All @@ -82,7 +79,6 @@ describe('unit: configuration', function() {
config = calculateConfig(
'development',
{},
{},
{ contentSecurityPolicyMeta: false },
UIMock
);
Expand All @@ -93,7 +89,6 @@ describe('unit: configuration', function() {
let config = calculateConfig(
'development',
{},
{},
{ contentSecurityPolicyHeader: 'Content-Security-Policy-Report-Only' },
UIMock
);
Expand All @@ -102,7 +97,6 @@ describe('unit: configuration', function() {
config = calculateConfig(
'development',
{},
{},
{ contentSecurityPolicyHeader: 'Content-Security-Policy' },
UIMock
);
Expand Down

0 comments on commit fbddcc6

Please sign in to comment.