UNPKG

@holochain/hc-spin

Version:

CLI to run Holochain aps during development.

181 lines (157 loc) 6.14 kB
import path from 'path'; import fs from 'fs'; import url from 'url'; import { AppAuthenticationToken, InstalledAppId } from '@holochain/client'; import { BrowserWindow, NativeImage, nativeImage, net, session, shell } from 'electron'; import { is } from '@electron-toolkit/utils'; import { HappOrWebhappPath } from './validateArgs'; export type UISource = | { type: 'path'; path: string; } | { type: 'port'; port: number; }; export const createHappWindow = async ( uiSource: UISource, happOrWebhappPath: HappOrWebhappPath, appId: InstalledAppId, agentNum: number, appPort: number, appAuthToken: AppAuthenticationToken, appDataRootDir: string, openDevtools: boolean, ): Promise<BrowserWindow> => { // TODO create mapping between installed-app-id's and window ids if (!appPort) throw new Error('App port not defined.'); const partition = `persist:${agentNum}:${appId}`; if (uiSource.type === 'path') { const ses = session.fromPartition(partition); ses.protocol.handle('webhapp', async (request) => { const uriWithoutProtocol = request.url.slice('webhapp://'.length); const filePathComponents = uriWithoutProtocol.split('/').slice(1); const filePath = path.join(...filePathComponents); return net.fetch(url.pathToFileURL(path.join(uiSource.path, filePath)).toString()); }); } // Extend preload script to add window.__HC_LAUNCHER_ENV__ let preloadScript = fs.readFileSync(path.join(__dirname, '../preload/index.js')).toString(); preloadScript += ` electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", { APP_INTERFACE_PORT: ${appPort}, INSTALLED_APP_ID: "${appId}", APP_INTERFACE_TOKEN: [${appAuthToken}], }); `; const preloadPath = path.join(appDataRootDir, `preload-${agentNum}-${appId}.js`); fs.writeFileSync(preloadPath, preloadScript); let icon: NativeImage | undefined; if (uiSource.type === 'path') { const iconPath = path.join(uiSource.path, 'icon.png'); if (!fs.existsSync(iconPath) && agentNum === 1) { console.warn( '\n\n+++++ WARNING +++++\n[hc-spin] No icon.png found. It is recommended to put an icon.png file (1024x1024 pixel) in the root of your UI assets directory which can be used by the Holochain Launcher.\n+++++++++++++++++++\n\n', ); } icon = nativeImage.createFromPath(iconPath); } else { try { const iconResponse = await net.fetch(`http://localhost:${uiSource.port}/icon.png`); if (iconResponse.status === 404 && agentNum === 1) { console.warn( '\n\n+++++ WARNING +++++\n[hc-spin] No icon.png found. It is recommended to put an icon.png file (1024x1024 pixel) in the root of your UI assets directory which can be used by the Holochain Launcher.\n+++++++++++++++++++\n\n', ); } const buffer = await iconResponse.arrayBuffer(); icon = nativeImage.createFromBuffer(Buffer.from(buffer)); } catch (e) { console.error('Failed to get icon.png: ', e); } } const happWindow = new BrowserWindow({ width: 1200, height: 800, show: false, icon, title: `Agent ${agentNum} - ${appId}`, webPreferences: { preload: preloadPath, partition, }, }); const [windowPositionX, windowPositionY] = happWindow.getPosition(); const windowPositionXMoved = windowPositionX + agentNum * 20; const windowPositionYMoved = windowPositionY + agentNum * 20; happWindow.setPosition(windowPositionXMoved, windowPositionYMoved); happWindow.menuBarVisible = false; setLinkOpenHandlers(happWindow); happWindow.on('page-title-updated', (evt) => { evt.preventDefault(); }); if (openDevtools) happWindow.webContents.openDevTools(); if (uiSource.type === 'port') { try { // Check whether dev server is responsive and index.html exists await net.fetch(`http://localhost:${uiSource.port}/index.html`); } catch (e) { console.error(`No index.html file found at http://localhost:${uiSource.port}/index.html`, e); if (is.dev && process.env['ELECTRON_RENDERER_URL']) { happWindow.loadURL(process.env['ELECTRON_RENDERER_URL']); } else { happWindow.loadFile(path.join(__dirname, '../renderer/index.html')); } happWindow.show(); return happWindow; } await happWindow.loadURL(`http://localhost:${uiSource.port}`); } else if (uiSource.type === 'path') { try { await happWindow.loadURL(`webhapp://webhappwindow/index.html`); } catch (e) { console.error('[ERROR] Failed to fetch index.html'); if (is.dev && process.env['ELECTRON_RENDERER_URL']) { happWindow.loadURL(process.env['ELECTRON_RENDERER_URL']); } else { const notFoundPath = happOrWebhappPath.type === 'webhapp' ? path.join(__dirname, '../renderer/indexNotFound1.html') : path.join(__dirname, '../renderer/indexNotFound2.html'); happWindow.loadFile(notFoundPath); } happWindow.show(); return happWindow; } } else { throw new Error('Unsupported uiSource type: ', (uiSource as any).type); } happWindow.show(); return happWindow; }; export function setLinkOpenHandlers(browserWindow: BrowserWindow): void { // links in happ windows should open in the system default application // instead of the webview browserWindow.webContents.on('will-navigate', (e) => { if (e.url.startsWith('http://localhost') || e.url.startsWith('http://127.0.0.1')) { // ignore dev server reload return; } if ( e.url.startsWith('http://') || e.url.startsWith('https://') || e.url.startsWith('mailto://') ) { e.preventDefault(); shell.openExternal(e.url); } }); // Links with target=_blank should open in the system default browser and // happ windows are not allowed to spawn new electron windows browserWindow.webContents.setWindowOpenHandler((details) => { if (details.url.startsWith('http://') || details.url.startsWith('https://')) { shell.openExternal(details.url); } return { action: 'deny' }; }); }