90 lines
2.6 KiB
JavaScript
90 lines
2.6 KiB
JavaScript
const { net } = require('electron');
|
|
|
|
const requestTimeoutSeconds = 10;
|
|
// Simple promise wrapper around Electron's net.request feature.
|
|
// https://www.electronjs.org/docs/api/client-request
|
|
const netRequestPromise = (url) =>
|
|
new Promise((resolve, reject) => {
|
|
try {
|
|
const request = net.request(url);
|
|
// We don't worry about canceling the request timeout timer once a
|
|
// request ends because: request.abort() has no effect after a request
|
|
// ends and the promise will already have been completed, so a rejection
|
|
// has no effect either.
|
|
const requestTimeout = setTimeout(() => {
|
|
request.abort();
|
|
reject(new Error('Request timeout'));
|
|
}, requestTimeoutSeconds * 1000);
|
|
request.on('error', reject);
|
|
request.on('response', resolve);
|
|
request.end();
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
});
|
|
|
|
// Provides a singleton class instance to enable a consistent view of
|
|
// runtime settings across the application.
|
|
|
|
// Runtime settings are any configuration, especially user-specified,
|
|
// set up at runtime. For example, the Boundary origin is a runtime setting.
|
|
|
|
class RuntimeSettings {
|
|
// Internal private origin is exposed via getter/setter below.
|
|
#origin = undefined;
|
|
|
|
/**
|
|
* The user-specified Boundary origin, which should be allowed by CSP.
|
|
* @type {?string}
|
|
*/
|
|
get origin() {
|
|
return this.#origin;
|
|
}
|
|
set origin(origin) {
|
|
if (this.#origin !== origin) {
|
|
this.#origin = origin;
|
|
this.triggerOriginChanged();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates the origin is reachable and has a scopes endpoint.
|
|
*/
|
|
async validateOrigin(origin) {
|
|
// If the origin is the Electron origin, it is automatically valid.
|
|
if (origin === 'serve://boundary') return true;
|
|
// Otherwise, check if scopes can be loaded from the specified origin.
|
|
// Scopes are required by the desktop client, so this is a simple smoke
|
|
// test to see if the origin is API-compatible with the desktop client.
|
|
const scopesEndpoint = `${origin}/v1/scopes`;
|
|
try {
|
|
await netRequestPromise(scopesEndpoint);
|
|
} catch (e) {
|
|
throw new Error(`Origin ${origin} could not be validated.`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the origin to null.
|
|
*/
|
|
async resetOrigin() {
|
|
this.origin = null;
|
|
}
|
|
|
|
// Quick and dirty event handler pattern to enable the application to respond
|
|
// when the origin is changed.
|
|
#originWatchers = [];
|
|
|
|
onOriginChange(fn) {
|
|
this.#originWatchers.push(fn);
|
|
}
|
|
|
|
triggerOriginChanged() {
|
|
this.#originWatchers.forEach((fn) => fn(this.#origin));
|
|
}
|
|
}
|
|
|
|
const runtimeSettings = new RuntimeSettings();
|
|
|
|
module.exports = runtimeSettings;
|