Skip to content

Commit

Permalink
Remove electron-protocol-serve
Browse files Browse the repository at this point in the history
`electron-protocol-serve` seems to have taken us too far off the beaten path, so this changes `ember-electron` to set up projects to load from file: URLs instead of the custom protocol. This requires a couple of workarounds to make Ember apps work properly when loaded from file: URLs -- all explained in detail in the new FAQ page. This gets us sourcemaps back in Electron 7+, and also removes the need for a variety of minor workarounds for security/sandboxing issues caused by our use of a custom protocol.

Fixes #442
  • Loading branch information
bendemboski committed May 21, 2020
1 parent 05f16f8 commit f2bba3f
Show file tree
Hide file tree
Showing 20 changed files with 416 additions and 166 deletions.
61 changes: 46 additions & 15 deletions blueprints/ember-electron/index.js
Expand Up @@ -7,9 +7,9 @@ const fs = require('fs');
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
const YAWN = require('yawn-yaml/cjs');
const SilentError = require('silent-error');
const {
upgradingUrl,
routingAndAssetLoadingUrl,
ciUrl
} = require('../../lib/utils/documentation-urls');

Expand All @@ -24,31 +24,62 @@ module.exports = class EmberElectronBlueprint extends Blueprint {
return entityName;
}

beforeInstall() {
if (fs.existsSync(electronProjectPath)) {
return Promise.reject(
new SilentError([
`Cannot create electron-forge project at './${electronProjectPath}'`,
`because a file or directory already exists there. Please remove/rename`,
`it and run the blueprint again: 'ember generate ember-electron'.`
].join(' '))
);
}

async afterInstall() {
if (fs.existsSync('ember-electron')) {
this.ui.writeLine(chalk.yellow([
`\n'ember-electron' directory detected -- this looks like an ember-electron`,
`v2 project. Setting up an updated project will not be destructive, but you`,
`should read the upgrading documentation at ${upgradingUrl}.\n`
].join(' ')));
}
}

async afterInstall() {
await this.updateEnvironmentConfig();
await this.updateTravisYml();
await this.updateEslintIgnore();
await this.updateEslintRc();
await this.createElectronProject();

if (!fs.existsSync(electronProjectPath)) {
await this.createElectronProject();
} else {
this.ui.writeLine(chalk.yellow([
`An electron-forge project already exists at './${electronProjectPath}'.`,
`If you're running the blueprint manually as part of an ember-electron`,
`upgrade, make sure to check for upgrade instructions relevant to your`,
`version upgrade at ${upgradingUrl}.\n`
].join(' ')));
}
}

async updateEnvironmentConfig() {
this.ui.writeLine(chalk.green('Updating config/environment.js'));

let contents = (await readFile('config/environment.js')).toString();

const rootURLRegex = /(\srootURL\s*:)/;
if (rootURLRegex.test(contents)) {
contents = contents.replace(rootURLRegex, `$1 process.env.EMBER_CLI_ELECTRON ? '' :`);
} else {
this.ui.writeLine(chalk.yellow([
`\nUnable to update rootURL setting to`,
`\`process.env.EMBER_CLI_ELECTRON ? '' : <previous value>\`,`,
`which is needed for your Ember app to load assets under Electron.`,
`See ${routingAndAssetLoadingUrl} for more information.`
].join(' ')));
}

const locationTypeRegex = /(\slocationType\s*:)/;
if (locationTypeRegex.test(contents)) {
contents = contents.replace(locationTypeRegex, `$1 process.env.EMBER_CLI_ELECTRON ? 'hash' :`);
} else {
this.ui.writeLine(chalk.yellow([
`\nUnable to update locationType setting to`,
`\`process.env.EMBER_CLI_ELECTRON ? 'hash' : <previous value>\`,`,
`which is needed for your Ember app's routing to work under Electron.`,
`See ${routingAndAssetLoadingUrl} for more information.`
].join(' ')));
}

await writeFile('config/environment.js', contents);
}

async updateTravisYml() {
Expand Down
29 changes: 29 additions & 0 deletions forge/files/src/handle-file-urls.js
@@ -0,0 +1,29 @@
const { protocol } = require('electron');
const { fileURLToPath } = require('url');
const path = require('path');
const fs = require('fs');
const { promisify } = require('util');

const access = promisify(fs.access);

//
// Patch asset loading -- Ember apps use absolute paths to reference their
// assets, e.g. `<img src="/images/foo.jpg">`. When the current URL is a `file:`
// URL, that ends up resolving to the absolute filesystem path `/images/foo.jpg`
// rather than being relative to the root of the Ember app. So, we intercept
// `file:` URL request and look to see if they point to an asset when
// interpreted as being relative to the root of the Ember app. If so, we return
// that path, and if not we leave them as-is, as their absolute path.
//
module.exports = function handleFileURLs(emberAppDir) {
protocol.interceptFileProtocol('file', async ({ url }, callback) => {
let urlPath = fileURLToPath(url);
let appPath = path.join(emberAppDir, urlPath);
try {
await access(appPath);
callback(appPath);
} catch (e) {
callback(urlPath);
}
});
};
13 changes: 7 additions & 6 deletions forge/files/src/index.js
@@ -1,17 +1,16 @@
/* eslint-disable no-console */
const { default: installExtension, EMBER_INSPECTOR } = require('electron-devtools-installer');
const { pathToFileURL } = require('url');
const { app, BrowserWindow } = require('electron');
const path = require('path');
const isDev = require('electron-is-dev');
const setupServeProtocol = require('./setup-serve-protocol');
const handleFileUrls = require('./handle-file-urls');

const emberAppDir = path.resolve(__dirname, '..', 'ember-dist');
const emberAppURL = 'serve://dist';
const emberAppURL = pathToFileURL(path.join(emberAppDir, 'index.html')).toString();

let mainWindow = null;

setupServeProtocol(emberAppDir);

// Uncomment the lines below to enable Electron's crash reporter
// For more information, see http://electron.atom.io/docs/api/crash-reporter/
// electron.crashReporter.start({
Expand All @@ -32,15 +31,17 @@ app.on('ready', async () => {
try {
require('devtron').install();
} catch (err) {
console.log('Failed to install Devtrom: ', err);
console.log('Failed to install Devtron: ', err);
}
try {
await installExtension(EMBER_INSPECTOR);
} catch (err) {
console.log('Failed to install Ember Inspector: ', err)
console.log('Failed to install Ember Inspector: ', err);
}
}

await handleFileUrls(emberAppDir);

mainWindow = new BrowserWindow({
width: 800,
height: 600,
Expand Down
25 changes: 0 additions & 25 deletions forge/files/src/setup-serve-protocol.js

This file was deleted.

11 changes: 5 additions & 6 deletions forge/files/tests/index.js
@@ -1,27 +1,26 @@
const { default: installExtension, EMBER_INSPECTOR } = require('electron-devtools-installer');
const path = require('path');
const { app } = require('electron');
const setupServeProtocol = require('../src/setup-serve-protocol');
const handleFileUrls = require('../src/handle-file-urls');
const { setupTestem, openTestWindow } = require('ember-electron/lib/test-support');

const emberAppDir = path.resolve(__dirname, '..', 'ember-test');

setupServeProtocol(emberAppDir);

app.on('ready', async function onReady() {
try {
require('devtron').install();
} catch (err) {
console.log('Failed to install Devtrom: ', err);
console.log('Failed to install Devtron: ', err);
}
try {
await installExtension(EMBER_INSPECTOR);
} catch (err) {
console.log('Failed to install Ember Inspector: ', err)
console.log('Failed to install Ember Inspector: ', err);
}

await handleFileUrls(emberAppDir);
setupTestem();
openTestWindow();
openTestWindow(emberAppDir);
});

app.on('window-all-closed', function onWindowAllClosed() {
Expand Down
1 change: 0 additions & 1 deletion forge/template.js
Expand Up @@ -68,7 +68,6 @@ class EmberElectronTemplates extends BaseTemplate {
return [
'electron-devtools-installer',
'electron-is-dev',
'electron-protocol-serve'
];
}

Expand Down
17 changes: 14 additions & 3 deletions index.js
Expand Up @@ -44,9 +44,20 @@ module.exports = {
node = replace(node, {
files: [ 'tests/index.html' ],
pattern: {
match: /src="[^"]*testem\.js"/,
replacement: 'src="http://testemserver/testem.js"',
},
match: /(src|href)="([^"]+)"/g,
replacement(match, attr, value) {
if (value.endsWith('testem.js')) {
// Replace testem script source so our test main process code can
// recognize and redirect requests to the testem server
value = 'http://testemserver/testem.js';
} else if (!value.includes(':/')) {
// Since we're loading from the filesystem, asset URLs in
// tests/index.html need to be prepended with '../'
value = `../${value}`;
}
return `${attr}="${value}"`;
}
}
});
}
return node;
Expand Down
34 changes: 0 additions & 34 deletions lib/resources/shim-head.js
Expand Up @@ -2,40 +2,6 @@
((win) => {
win.ELECTRON = true;

// On linux the renderer process doesn't inherit the main process'
// environment, so we need to fall back to using the remote module.
let serveIndex;
// If nodeIntegration is disabled, this will throw and serveIndex will remain
// undefined, which is fine because we only need it to fix module search paths
// that aren't relevant if node integration is disabled.
try {
serveIndex = process.env.ELECTRON_PROTOCOL_SERVE_INDEX || require('electron').remote.process.env.ELECTRON_PROTOCOL_SERVE_INDEX;
} catch (e) {
// When nodeIntegration is false we expect process to be undefined. Don't swallow the exception if something else is wrong
if (e.message !== "process is not defined") {
throw e;
}
}

if (serveIndex && window.location.protocol !== 'file:') {
// Using electron-protocol-serve to load index.html via a 'serve:' URL
// prevents electron's renderer/init.js from setting the module search
// paths correctly. So this is basically a copy of that code, but using an
// environment variable set by electron-protocol-serve containing the
// filesystem path to index.html instead of window.location.
const path = require('path');
const Module = require('module');

global.__filename = path.normalize(serveIndex);
global.__dirname = path.dirname(serveIndex);

// Set module's filename so relative require can work as expected.
module.filename = global.__filename;

// Also search for module under the html file.
module.paths = module.paths.concat(Module._nodeModulePaths(global.__dirname));
}

// Store electrons node environment injections for later usage
win.moduleNode = win.module;
win.processNode = win.process;
Expand Down
18 changes: 10 additions & 8 deletions lib/test-support/index.js
@@ -1,8 +1,8 @@
const { session, BrowserWindow } = require('electron');
const { URL } = require('url');
const { URL, pathToFileURL } = require('url');

// These are the command-line arguments passed to us by test-runner.js
const [ , , , testPageURL, testemUrl, testemId ] = process.argv;
const [ , , , testPageURL, testemURL, testemId ] = process.argv;

// Set up communication with the testem server
//
Expand All @@ -12,7 +12,7 @@ const [ , , , testPageURL, testemUrl, testemId ] = process.argv;
// actual testem server, so we can intercept any requests to http://testemserver
// and redirect them to the actual testem server.
function setupTestem() {
let { host: testemHost } = new URL(testemUrl);
let { host: testemHost } = new URL(testemURL);

session.defaultSession.webRequest.onBeforeRequest((details, callback) => {
let urlObj = new URL(details.url);
Expand All @@ -27,8 +27,9 @@ function setupTestem() {
});
}

// Open the test window
function openTestWindow() {
// Open the test window. `emberAppDir` is the path to the directory containing
// the built Ember app that we are testing
function openTestWindow(emberAppDir) {
let window = new BrowserWindow({
width: 800,
height: 600,
Expand All @@ -40,8 +41,9 @@ function openTestWindow() {

delete window.module;

// Combine the test page URL with our root serve://dist URL
let url = new URL(testPageURL, 'serve://dist');
// Convert the emberAppDir to a file URL and append a '/' so when it's joined
// with the testPageURL the last path component isn't dropped
let url = new URL(testPageURL, `${pathToFileURL(emberAppDir)}/`);

// We need to set this query param so the script in shim-test-head.js can
// expose it to testem to use when communicating with the testem server
Expand All @@ -60,4 +62,4 @@ function openTestWindow() {
module.exports = {
setupTestem,
openTestWindow
}
}
6 changes: 5 additions & 1 deletion lib/utils/documentation-urls.js
Expand Up @@ -6,8 +6,12 @@ const upgradingUrl = `${guidesUrl}upgrading`;
const ciUrl = `${guidesUrl}ci`;
const structureUrl = `${guidesUrl}structure`

const faqUrl = `${baseUrl}faq/`;
const routingAndAssetLoadingUrl = `${faqUrl}routing-and-asset-loading`

module.exports = {
upgradingUrl,
ciUrl,
structureUrl
structureUrl,
routingAndAssetLoadingUrl
};

0 comments on commit f2bba3f

Please sign in to comment.