chore: 🤖 wire up postMessage ipc proxy and Ember ipc service
This commit is contained in:
9 files changed
+196
-44
No files matched your search
@@ -0,0 +1,22 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class IndexRoute extends Route {
|
||||
|
||||
// =services
|
||||
|
||||
@service ipc;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Invokes an IPC method and returns the result.
|
||||
* @return {Promise}
|
||||
*/
|
||||
model() {
|
||||
return this.ipc.invoke('getFoobars', {
|
||||
id: '1'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import Service from '@ember/service';
|
||||
import { Promise } from 'rsvp';
|
||||
|
||||
/**
|
||||
* An IPC request is a promise-like object that invokes a method on the main
|
||||
* process and resolves to its response.
|
||||
*
|
||||
* Since the renderer cannot call into main or access Node modules directly,
|
||||
* it uses the window's `postMessage` interface. Messages posted in this manner
|
||||
* are proxied by `preload.js` to the main process. This ensures that main
|
||||
* and renderer remain isolated and frontend code does not rely on the presence
|
||||
* of a main process.
|
||||
*/
|
||||
class IPCRequest {
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* @type {?MessageChannel}
|
||||
* @private
|
||||
*/
|
||||
#channel = null;
|
||||
|
||||
/**
|
||||
* @type {?Promise}
|
||||
* @private
|
||||
*/
|
||||
#promise = null;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Posts a message to the window object, along with a MessagePort
|
||||
* with which to receive a response. An internal promise is generated which
|
||||
* resolves to the response.
|
||||
* @param {string} method
|
||||
* @param {object} payload
|
||||
* @param {?string} origin
|
||||
*/
|
||||
constructor(method, payload, origin='serve://boundary') {
|
||||
this.method = method;
|
||||
this.payload = payload;
|
||||
this.origin = origin;
|
||||
this.#channel = new MessageChannel();
|
||||
this.#promise = new Promise((resolve/*, reject*/) => {
|
||||
this.#channel.port1.onmessage = (event) => {
|
||||
this.close();
|
||||
resolve(event.data);
|
||||
}
|
||||
});
|
||||
window.postMessage({
|
||||
method: this.method,
|
||||
payload: this.payload
|
||||
}, origin, [ this.#channel.port2 ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the message ports and drops the channel reference.
|
||||
* This is called upon receiving a response for cleanup purposes.
|
||||
*/
|
||||
close() {
|
||||
this.#channel.port1.close();
|
||||
this.#channel.port2.close();
|
||||
this.#channel = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies to the underlying promise.
|
||||
*/
|
||||
then() {
|
||||
return this.#promise.then(...arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies to the underlying promise.
|
||||
*/
|
||||
catch() {
|
||||
return this.#promise.catch(...arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies to the underlying promise.
|
||||
*/
|
||||
finally() {
|
||||
return this.#promise.finally(...arguments);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple factory for generating IPCRequests in Ember.
|
||||
*/
|
||||
export default class IpcService extends Service {
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Creates a new IPC request and returns it.
|
||||
* @param {string} method
|
||||
* @param {object} payload
|
||||
* @return {IPCRequest}
|
||||
*/
|
||||
invoke(method, payload) {
|
||||
return new IPCRequest(method, payload);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{{@model.message}}
|
||||
@@ -1,42 +0,0 @@
|
||||
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.
|
||||
//
|
||||
async function getAssetPath(emberAppDir, url) {
|
||||
let urlPath = fileURLToPath(url);
|
||||
// Get the root of the path -- should be '/' on MacOS or something like
|
||||
// 'C:\' on Windows
|
||||
let { root } = path.parse(urlPath);
|
||||
// Get the relative path from the root to the full path
|
||||
let relPath = path.relative(root, urlPath);
|
||||
// Join the relative path with the Ember app directory
|
||||
let appPath = path.join(emberAppDir, relPath);
|
||||
try {
|
||||
await access(appPath);
|
||||
return appPath;
|
||||
} catch (e) {
|
||||
return urlPath;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function handleFileURLs(emberAppDir) {
|
||||
const { protocol } = require('electron');
|
||||
|
||||
protocol.interceptFileProtocol('file', async ({ url }, callback) => {
|
||||
callback(await getAssetPath(emberAppDir, url));
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getAssetPath = getAssetPath;
|
||||
@@ -0,0 +1,17 @@
|
||||
const { ipcMain } = require('electron');
|
||||
const isDev = require('electron-is-dev');
|
||||
|
||||
const log = (method, request) => {
|
||||
if (isDev) console.log(`[ipc-main](${method}): `, request);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A demonstration method `getFoobars` which returns an object with a message.
|
||||
*/
|
||||
ipcMain.handle('getFoobars', async (event, request) => {
|
||||
log('getFoobars', request);
|
||||
return {
|
||||
message: 'this is a response from an ipcMain handler'
|
||||
};
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
/* eslint-disable no-console */
|
||||
const path = require('path');
|
||||
const { default: installExtension, EMBER_INSPECTOR } = require('electron-devtools-installer');
|
||||
const { session, app, protocol, BrowserWindow } = require('electron');
|
||||
const { session, app, protocol, BrowserWindow, ipcMain } = require('electron');
|
||||
require('./handlers.js');
|
||||
|
||||
const isDev = require('electron-is-dev');
|
||||
|
||||
|
||||
@@ -1 +1,24 @@
|
||||
// nothing here yet
|
||||
const { ipcRenderer } = require('electron');
|
||||
const emberAppOrigin = 'serve://boundary';
|
||||
|
||||
process.once('loaded', () => {
|
||||
|
||||
/**
|
||||
* Ember-land has no access to the renderer or node modules, and thus
|
||||
* cannot call into the main process or make invocations directly.
|
||||
* In order to communicate with the main process, the Ember app uses
|
||||
* the `postMessage` interface to send messages to its browser window.
|
||||
* These messages include message ports with which to receive responses from
|
||||
* the main process.
|
||||
*
|
||||
* This preload script simply wires up the forwarding of
|
||||
* messages received on the window and responses received from main.
|
||||
*/
|
||||
window.addEventListener('message', async function (event) {
|
||||
if (event.origin !== emberAppOrigin) return;
|
||||
const response =
|
||||
await ipcRenderer.invoke(event.data.method, event.data.payload);
|
||||
event.ports[0].postMessage(response);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | index', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:index');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Service | ipc', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let service = this.owner.lookup('service:ipc');
|
||||
assert.ok(service);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user