2019-05-11 19:18:17 +02:00
|
|
|
// desktop.js
|
|
|
|
// Electron script to run Hyperspace as an app
|
|
|
|
// © 2018 Hyperspace developers. Licensed under Apache 2.0.
|
2019-04-30 22:53:50 +02:00
|
|
|
|
2019-05-12 20:25:05 +02:00
|
|
|
const { app, Menu, protocol, BrowserWindow, shell } = require('electron');
|
2019-05-12 18:52:22 +02:00
|
|
|
const windowStateKeeper = require('electron-window-state');
|
2019-05-11 19:18:17 +02:00
|
|
|
const { autoUpdater } = require('electron-updater');
|
2019-04-30 22:53:50 +02:00
|
|
|
const path = require('path');
|
2019-05-11 19:18:17 +02:00
|
|
|
|
|
|
|
// Check for any updates to the app
|
2019-04-30 22:53:50 +02:00
|
|
|
autoUpdater.checkForUpdatesAndNotify();
|
|
|
|
|
2019-05-11 19:18:17 +02:00
|
|
|
// Create a container for the window
|
2019-04-30 22:53:50 +02:00
|
|
|
let mainWindow;
|
|
|
|
|
2019-05-12 18:44:39 +02:00
|
|
|
// Register the "hyperspace://" protocol so that it supports
|
|
|
|
// HTTPS and acts like a standard protocol instead of a fake
|
|
|
|
// file:// protocol, which is necessary for Mastodon to redirect
|
|
|
|
// to when authorizing Hyperspace.
|
2019-05-11 22:03:40 +02:00
|
|
|
protocol.registerSchemesAsPrivileged([
|
|
|
|
{ scheme: 'hyperspace', privileges: { standard: true, secure: true } }
|
|
|
|
])
|
|
|
|
|
2019-05-11 19:18:17 +02:00
|
|
|
/**
|
|
|
|
* Register the protocol for Hyperspace
|
|
|
|
*/
|
|
|
|
function registerProtocol() {
|
|
|
|
protocol.registerFileProtocol('hyperspace', (request, callback) => {
|
2019-05-11 22:03:40 +02:00
|
|
|
|
|
|
|
// Check to make sure we're doing a GET request
|
2019-05-11 19:18:17 +02:00
|
|
|
if (request.method !== "GET") {
|
|
|
|
callback({error: -322});
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-05-11 22:03:40 +02:00
|
|
|
// Check to make sure we're actually working with a hyperspace
|
|
|
|
// protocol and that the host is 'hyperspace'
|
2019-05-11 19:18:17 +02:00
|
|
|
const parsedUrl = new URL(request.url);
|
2019-05-11 22:03:40 +02:00
|
|
|
if (parsedUrl.protocol !== "hyperspace:") {
|
2019-05-11 19:18:17 +02:00
|
|
|
callback({error: -302});
|
|
|
|
return;
|
|
|
|
}
|
2019-05-12 18:44:39 +02:00
|
|
|
|
2019-05-11 19:18:17 +02:00
|
|
|
if (parsedUrl.host !== "hyperspace") {
|
|
|
|
callback({error: -105});
|
|
|
|
return;
|
|
|
|
}
|
2019-05-11 22:03:40 +02:00
|
|
|
|
2019-05-12 18:44:39 +02:00
|
|
|
// Convert the parsed URL to a list of strings.
|
2019-05-11 22:03:40 +02:00
|
|
|
const target = parsedUrl.pathname.split("/");
|
|
|
|
|
2019-05-12 18:44:39 +02:00
|
|
|
// Check that the target isn't trying to go somewhere
|
|
|
|
// else. If it is, throw a "FILE_NOT_FOUND" error
|
2019-05-11 22:03:40 +02:00
|
|
|
if (target[0] !== "") {
|
|
|
|
callback({error: -6});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-05-12 18:44:39 +02:00
|
|
|
// Check if the last target item in the list is empty.
|
|
|
|
// If so, replace it with "index.html" so that it can
|
|
|
|
// load a page.
|
2019-05-11 22:03:40 +02:00
|
|
|
if (target[target.length -1] === "") {
|
|
|
|
target[target.length -1] = "index.html";
|
2019-05-11 19:18:17 +02:00
|
|
|
}
|
2019-05-11 22:03:40 +02:00
|
|
|
|
2019-05-12 18:44:39 +02:00
|
|
|
// Check the middle target and redirect to the appropriate
|
|
|
|
// build files of the desktop app when running.
|
2019-05-11 22:03:40 +02:00
|
|
|
let baseDirectory;
|
|
|
|
if (target[1] === "app" || target[1] === "oauth") {
|
|
|
|
baseDirectory = __dirname + "/../build/";
|
|
|
|
} else {
|
2019-05-12 18:44:39 +02:00
|
|
|
// If it doesn't match above, throw a "FILE_NOT_FOUND" error.
|
2019-05-11 22:03:40 +02:00
|
|
|
callback({error: -6});
|
|
|
|
}
|
|
|
|
|
2019-05-12 18:44:39 +02:00
|
|
|
// Create a normalized version of the strring.
|
2019-05-11 22:03:40 +02:00
|
|
|
baseDirectory = path.normalize(baseDirectory);
|
|
|
|
|
2019-05-12 18:44:39 +02:00
|
|
|
// Check to make sure the target isn't trying to go out of bounds.
|
|
|
|
// If it is, throw a "FILE_NOT_FOUND" error.
|
2019-05-11 22:03:40 +02:00
|
|
|
const relTarget = path.normalize(path.join(...target.slice(2)));
|
|
|
|
if (relTarget.startsWith('..')) {
|
|
|
|
callback({error: -6});
|
|
|
|
return;
|
|
|
|
}
|
2019-05-12 18:44:39 +02:00
|
|
|
|
|
|
|
// Create the absolute target path and return it.
|
2019-05-11 22:03:40 +02:00
|
|
|
const absTarget = path.join(baseDirectory, relTarget);
|
2019-05-12 18:44:39 +02:00
|
|
|
callback({ path: absTarget });
|
2019-05-11 22:03:40 +02:00
|
|
|
|
|
|
|
}, (error) => {
|
2019-05-12 18:44:39 +02:00
|
|
|
if (error) console.error('Failed to register protocol');
|
2019-05-11 19:18:17 +02:00
|
|
|
});
|
2019-05-11 22:03:40 +02:00
|
|
|
|
2019-05-11 19:18:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create the window and all of its properties
|
|
|
|
*/
|
2019-04-30 22:53:50 +02:00
|
|
|
function createWindow() {
|
2019-05-12 18:52:22 +02:00
|
|
|
|
|
|
|
// Create a window state manager that keeps track of the width
|
|
|
|
// and height of the main window.
|
|
|
|
let mainWindowState = windowStateKeeper({
|
|
|
|
defaultHeight: 624,
|
|
|
|
defaultWidth: 1024
|
|
|
|
});
|
|
|
|
|
2019-05-12 18:44:39 +02:00
|
|
|
// Create a browser window with some settings
|
2019-04-30 22:53:50 +02:00
|
|
|
mainWindow = new BrowserWindow(
|
|
|
|
{
|
2019-05-12 18:52:22 +02:00
|
|
|
// Use the values from the window state keeper
|
|
|
|
// to draw the window exactly as it was left.
|
|
|
|
// If not possible, derive it from the default
|
|
|
|
// values defined earlier.
|
|
|
|
x: mainWindowState.x,
|
|
|
|
y: mainWindowState.y,
|
|
|
|
width: mainWindowState.width,
|
|
|
|
height: mainWindowState.height,
|
|
|
|
|
|
|
|
// Set a minimum width to prevent element collisions.
|
2019-05-12 18:44:39 +02:00
|
|
|
minWidth: 300,
|
2019-05-12 18:52:22 +02:00
|
|
|
|
|
|
|
// Set important web preferences.
|
2019-05-12 18:44:39 +02:00
|
|
|
webPreferences: {nodeIntegration: true},
|
|
|
|
|
2019-05-12 18:52:22 +02:00
|
|
|
// Set some preferences that are specific to macOS.
|
2019-05-12 18:44:39 +02:00
|
|
|
titleBarStyle: 'hidden',
|
2019-04-30 22:53:50 +02:00
|
|
|
}
|
|
|
|
);
|
2019-05-12 18:52:22 +02:00
|
|
|
|
|
|
|
// Set up event listeners to track changes in the window state.
|
|
|
|
mainWindowState.manage(mainWindow);
|
2019-04-30 22:53:50 +02:00
|
|
|
|
2019-05-12 18:44:39 +02:00
|
|
|
// Load the main app and open the index page.
|
2019-05-11 22:03:40 +02:00
|
|
|
mainWindow.loadURL("hyperspace://hyperspace/app/");
|
2019-04-30 22:53:50 +02:00
|
|
|
|
2019-05-12 18:44:39 +02:00
|
|
|
// Delete the window when closed
|
2019-04-30 22:53:50 +02:00
|
|
|
mainWindow.on('closed', () => {
|
|
|
|
mainWindow = null
|
|
|
|
});
|
2019-05-12 19:37:04 +02:00
|
|
|
|
|
|
|
// Hijack any links with a blank target and open them in the default
|
|
|
|
// browser instead of a new Electron window
|
|
|
|
mainWindow.webContents.on('new-window', (event, url) => {
|
|
|
|
event.preventDefault();
|
|
|
|
shell.openExternal(url);
|
|
|
|
});
|
2019-04-30 22:53:50 +02:00
|
|
|
}
|
|
|
|
|
2019-05-11 19:18:17 +02:00
|
|
|
/**
|
|
|
|
* Create the menu bar and attach it to a window
|
|
|
|
*/
|
2019-04-30 22:53:50 +02:00
|
|
|
function createMenubar() {
|
2019-05-12 20:30:02 +02:00
|
|
|
|
|
|
|
// Create an instance of the Menu class
|
2019-05-12 20:25:05 +02:00
|
|
|
let menu = Menu;
|
2019-05-12 20:30:02 +02:00
|
|
|
|
2019-05-12 18:44:39 +02:00
|
|
|
// Create a menu bar template
|
2019-04-30 22:53:50 +02:00
|
|
|
const menuBar = [
|
|
|
|
{
|
|
|
|
label: 'File',
|
|
|
|
submenu: [
|
|
|
|
{
|
|
|
|
label: 'New Window',
|
|
|
|
accelerator: 'CmdOrCtrl+N',
|
|
|
|
click() {
|
2019-05-11 19:18:17 +02:00
|
|
|
if (mainWindow == null) {
|
|
|
|
registerProtocol();
|
2019-04-30 22:53:50 +02:00
|
|
|
createWindow();
|
2019-05-11 19:18:17 +02:00
|
|
|
}
|
2019-04-30 22:53:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: 'Edit',
|
|
|
|
submenu: [
|
|
|
|
{ role: 'undo' },
|
|
|
|
{ role: 'redo' },
|
|
|
|
{ type: 'separator' },
|
|
|
|
{ role: 'cut' },
|
|
|
|
{ role: 'copy' },
|
|
|
|
{ role: 'paste' },
|
|
|
|
{ role: 'pasteandmatchstyle' },
|
|
|
|
{ role: 'delete' },
|
|
|
|
{ role: 'selectall' }
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: 'View',
|
|
|
|
submenu: [
|
|
|
|
{ role: 'reload' },
|
|
|
|
{ role: 'forcereload' },
|
|
|
|
{
|
|
|
|
label: 'Open Dev Tools',
|
|
|
|
click () {
|
|
|
|
try {
|
|
|
|
mainWindow.webContents.openDevTools({mode: 'undocked'});
|
|
|
|
} catch (err) {
|
|
|
|
console.error("Couldn't open dev tools: " + err);
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
accelerator: 'Shift+CmdOrCtrl+I'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{ role: 'togglefullscreen' }
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
role: 'window',
|
|
|
|
submenu: [
|
|
|
|
{ role: 'minimize' },
|
|
|
|
{ role: 'close' }
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
role: 'help',
|
|
|
|
submenu: [
|
|
|
|
{
|
|
|
|
label: 'Report a Bug',
|
|
|
|
click () { require('electron').shell.openExternal('https://github.com/hyperspacedev/hyperspace/issues') }
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
if (process.platform === 'darwin') {
|
|
|
|
menuBar.unshift({
|
|
|
|
label: app.getName(),
|
|
|
|
submenu: [
|
|
|
|
{ role: 'about' },
|
|
|
|
{ type: 'separator' },
|
|
|
|
{ role: 'services' },
|
|
|
|
{ type: 'separator' },
|
|
|
|
{ role: 'hide' },
|
|
|
|
{ role: 'hideothers' },
|
|
|
|
{ role: 'unhide' },
|
|
|
|
{ type: 'separator' },
|
|
|
|
{ role: 'quit' }
|
|
|
|
]
|
|
|
|
})
|
|
|
|
|
|
|
|
// Edit menu
|
|
|
|
menuBar[2].submenu.push(
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
label: 'Speech',
|
|
|
|
submenu: [
|
|
|
|
{ role: 'startspeaking' },
|
|
|
|
{ role: 'stopspeaking' }
|
|
|
|
]
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// Window menu
|
|
|
|
menuBar[4].submenu = [
|
|
|
|
{ role: 'close' },
|
|
|
|
{ role: 'minimize' },
|
|
|
|
{ role: 'zoom' },
|
|
|
|
{ type: 'separator' },
|
|
|
|
{ role: 'front' }
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2019-05-12 18:44:39 +02:00
|
|
|
// Create the template for the menu and attach it to the application
|
2019-04-30 22:53:50 +02:00
|
|
|
const thisMenu = menu.buildFromTemplate(menuBar);
|
|
|
|
menu.setApplicationMenu(thisMenu);
|
|
|
|
}
|
|
|
|
|
2019-05-11 19:18:17 +02:00
|
|
|
// When the app is ready, create the window and menu bar
|
2019-04-30 22:53:50 +02:00
|
|
|
app.on('ready', () => {
|
2019-05-11 19:18:17 +02:00
|
|
|
registerProtocol();
|
2019-04-30 22:53:50 +02:00
|
|
|
createWindow();
|
2019-05-12 18:44:39 +02:00
|
|
|
createMenubar();
|
2019-04-30 22:53:50 +02:00
|
|
|
});
|
|
|
|
|
2019-05-11 19:18:17 +02:00
|
|
|
// Standard quit behavior changes for macOS
|
2019-04-30 22:53:50 +02:00
|
|
|
app.on('window-all-closed', () => {
|
|
|
|
if (process.platform !== 'darwin') {
|
|
|
|
app.quit()
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-05-11 19:18:17 +02:00
|
|
|
// When the app is activated, create the window and menu bar
|
2019-04-30 22:53:50 +02:00
|
|
|
app.on('activate', () => {
|
|
|
|
if (mainWindow === null) {
|
2019-05-11 19:18:17 +02:00
|
|
|
createWindow();
|
|
|
|
createMenubar();
|
2019-04-30 22:53:50 +02:00
|
|
|
}
|
|
|
|
});
|