@holochain/hc-spin 0.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +9 -0
- package/.eslintignore +4 -0
- package/.eslintrc +31 -0
- package/.prettierignore +6 -0
- package/.prettierrc.yaml +3 -0
- package/.vscode/extensions.json +3 -0
- package/.vscode/launch.json +39 -0
- package/.vscode/settings.json +12 -0
- package/.yarnrc.yml +1 -0
- package/README.md +61 -0
- package/build/entitlements.mac.plist +12 -0
- package/build/icon.icns +0 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/cli/cli.js +34 -0
- package/dist/cli.js +34 -0
- package/dist/main/index.js +11934 -0
- package/dist/preload/index.js +8 -0
- package/dist/renderer/assets/renderer-2UdJ5Bnz.js +1 -0
- package/dist/renderer/index.html +44 -0
- package/dist/renderer/indexNotFound1.html +44 -0
- package/dist/renderer/indexNotFound2.html +44 -0
- package/docs/DEVSETUP.md +36 -0
- package/electron.vite.config.ts +22 -0
- package/package.json +51 -0
- package/resources/icon.png +0 -0
- package/src/main/index.ts +310 -0
- package/src/main/validateArgs.ts +81 -0
- package/src/main/windows.ts +178 -0
- package/src/preload/index.ts +16 -0
- package/src/renderer/index.html +44 -0
- package/src/renderer/indexNotFound1.html +44 -0
- package/src/renderer/indexNotFound2.html +44 -0
- package/src/renderer/src/renderer.ts +1 -0
- package/tsconfig.json +4 -0
- package/tsconfig.node.json +8 -0
- package/tsconfig.web.json +7 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import url from 'url';
|
|
4
|
+
import { InstalledAppId } from '@holochain/client';
|
|
5
|
+
import { BrowserWindow, NativeImage, nativeImage, net, session, shell } from 'electron';
|
|
6
|
+
import { is } from '@electron-toolkit/utils';
|
|
7
|
+
import { HappOrWebhappPath } from './validateArgs';
|
|
8
|
+
|
|
9
|
+
export type UISource =
|
|
10
|
+
| {
|
|
11
|
+
type: 'path';
|
|
12
|
+
path: string;
|
|
13
|
+
}
|
|
14
|
+
| {
|
|
15
|
+
type: 'port';
|
|
16
|
+
port: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const createHappWindow = async (
|
|
20
|
+
uiSource: UISource,
|
|
21
|
+
happOrWebhappPath: HappOrWebhappPath,
|
|
22
|
+
appId: InstalledAppId,
|
|
23
|
+
agentNum: number,
|
|
24
|
+
appPort: number,
|
|
25
|
+
appDataRootDir: string,
|
|
26
|
+
): Promise<BrowserWindow> => {
|
|
27
|
+
// TODO create mapping between installed-app-id's and window ids
|
|
28
|
+
if (!appPort) throw new Error('App port not defined.');
|
|
29
|
+
|
|
30
|
+
const partition = `persist:${agentNum}:${appId}`;
|
|
31
|
+
|
|
32
|
+
if (uiSource.type === 'path') {
|
|
33
|
+
const ses = session.fromPartition(partition);
|
|
34
|
+
ses.protocol.handle('webhapp', async (request) => {
|
|
35
|
+
const uriWithoutProtocol = request.url.slice('webhapp://'.length);
|
|
36
|
+
const filePathComponents = uriWithoutProtocol.split('/').slice(1);
|
|
37
|
+
const filePath = path.join(...filePathComponents);
|
|
38
|
+
return net.fetch(url.pathToFileURL(path.join(uiSource.path, filePath)).toString());
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Extend preload script to add window.__HC_LAUNCHER_ENV__
|
|
43
|
+
let preloadScript = fs.readFileSync(path.join(__dirname, '../preload/index.js')).toString();
|
|
44
|
+
|
|
45
|
+
preloadScript += `
|
|
46
|
+
electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
|
|
47
|
+
APP_INTERFACE_PORT: ${appPort},
|
|
48
|
+
INSTALLED_APP_ID: "${appId}",
|
|
49
|
+
FRAMEWORK: "electron"
|
|
50
|
+
});
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
const preloadPath = path.join(appDataRootDir, `preload-${agentNum}-${appId}.js`);
|
|
54
|
+
|
|
55
|
+
fs.writeFileSync(preloadPath, preloadScript);
|
|
56
|
+
|
|
57
|
+
let icon: NativeImage | undefined;
|
|
58
|
+
|
|
59
|
+
if (uiSource.type === 'path') {
|
|
60
|
+
const iconPath = path.join(uiSource.path, 'icon.png');
|
|
61
|
+
if (!fs.existsSync(iconPath) && agentNum === 1) {
|
|
62
|
+
console.warn(
|
|
63
|
+
'\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',
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
icon = nativeImage.createFromPath(iconPath);
|
|
67
|
+
} else {
|
|
68
|
+
try {
|
|
69
|
+
const iconResponse = await net.fetch(`http://127.0.0.1:${uiSource.port}/icon.png`);
|
|
70
|
+
const buffer = await iconResponse.arrayBuffer();
|
|
71
|
+
if (buffer.byteLength === 0 && agentNum === 1) {
|
|
72
|
+
console.warn(
|
|
73
|
+
'\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',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
icon = nativeImage.createFromBuffer(Buffer.from(buffer));
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.error('Failed to get icon.png: ', e);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const happWindow = new BrowserWindow({
|
|
83
|
+
width: 1200,
|
|
84
|
+
height: 800,
|
|
85
|
+
show: false,
|
|
86
|
+
icon,
|
|
87
|
+
title: `Agent ${agentNum} - ${appId}`,
|
|
88
|
+
webPreferences: {
|
|
89
|
+
preload: preloadPath,
|
|
90
|
+
partition,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const [windowPositionX, windowPositionY] = happWindow.getPosition();
|
|
95
|
+
const windowPositionXMoved = windowPositionX + agentNum * 20;
|
|
96
|
+
const windowPositionYMoved = windowPositionY + agentNum * 20;
|
|
97
|
+
happWindow.setPosition(windowPositionXMoved, windowPositionYMoved);
|
|
98
|
+
|
|
99
|
+
happWindow.menuBarVisible = false;
|
|
100
|
+
|
|
101
|
+
setLinkOpenHandlers(happWindow);
|
|
102
|
+
|
|
103
|
+
happWindow.on('page-title-updated', (evt) => {
|
|
104
|
+
evt.preventDefault();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
happWindow.webContents.openDevTools();
|
|
108
|
+
|
|
109
|
+
if (uiSource.type === 'port') {
|
|
110
|
+
try {
|
|
111
|
+
// Check whether dev server is responsive and index.html exists
|
|
112
|
+
await net.fetch(`http://127.0.0.1:${uiSource.port}/index.html`);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.error(`No index.html file found at http://127.0.0.1:${uiSource.port}/index.html`, e);
|
|
115
|
+
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
|
116
|
+
happWindow.loadURL(process.env['ELECTRON_RENDERER_URL']);
|
|
117
|
+
} else {
|
|
118
|
+
happWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
|
|
119
|
+
}
|
|
120
|
+
happWindow.show();
|
|
121
|
+
return happWindow;
|
|
122
|
+
}
|
|
123
|
+
await happWindow.loadURL(`http://127.0.0.1:${uiSource.port}`);
|
|
124
|
+
} else if (uiSource.type === 'path') {
|
|
125
|
+
try {
|
|
126
|
+
await happWindow.loadURL(`webhapp://webhappwindow/index.html`);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
console.error('[ERROR] Failed to fetch index.html');
|
|
129
|
+
|
|
130
|
+
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
|
131
|
+
happWindow.loadURL(process.env['ELECTRON_RENDERER_URL']);
|
|
132
|
+
} else {
|
|
133
|
+
const notFoundPath =
|
|
134
|
+
happOrWebhappPath.type === 'webhapp'
|
|
135
|
+
? path.join(__dirname, '../renderer/indexNotFound1.html')
|
|
136
|
+
: path.join(__dirname, '../renderer/indexNotFound2.html');
|
|
137
|
+
happWindow.loadFile(notFoundPath);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
happWindow.show();
|
|
141
|
+
return happWindow;
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
throw new Error('Unsupported uiSource type: ', (uiSource as any).type);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
happWindow.show();
|
|
148
|
+
|
|
149
|
+
return happWindow;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export function setLinkOpenHandlers(browserWindow: BrowserWindow): void {
|
|
153
|
+
// links in happ windows should open in the system default application
|
|
154
|
+
// instead of the webview
|
|
155
|
+
browserWindow.webContents.on('will-navigate', (e) => {
|
|
156
|
+
if (e.url.startsWith('http://localhost') || e.url.startsWith('http://127.0.0.1')) {
|
|
157
|
+
// ignore dev server reload
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (
|
|
161
|
+
e.url.startsWith('http://') ||
|
|
162
|
+
e.url.startsWith('https://') ||
|
|
163
|
+
e.url.startsWith('mailto://')
|
|
164
|
+
) {
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
shell.openExternal(e.url);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Links with target=_blank should open in the system default browser and
|
|
171
|
+
// happ windows are not allowed to spawn new electron windows
|
|
172
|
+
browserWindow.webContents.setWindowOpenHandler((details) => {
|
|
173
|
+
if (details.url.startsWith('http://') || details.url.startsWith('https://')) {
|
|
174
|
+
shell.openExternal(details.url);
|
|
175
|
+
}
|
|
176
|
+
return { action: 'deny' };
|
|
177
|
+
});
|
|
178
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// See the Electron documentation for details on how to use preload scripts:
|
|
2
|
+
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
|
|
3
|
+
import { contextBridge, ipcRenderer } from 'electron';
|
|
4
|
+
import { ZomeCallUnsignedNapi } from '@holochain/hc-spin-rust-utils';
|
|
5
|
+
|
|
6
|
+
import { CallZomeRequestUnsigned } from '@holochain/client';
|
|
7
|
+
|
|
8
|
+
contextBridge.exposeInMainWorld('__HC_ZOME_CALL_SIGNER__', {
|
|
9
|
+
signZomeCall: (zomeCall: CallZomeRequestUnsigned) =>
|
|
10
|
+
ipcRenderer.invoke('sign-zome-call', zomeCall),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
contextBridge.exposeInMainWorld('electronAPI', {
|
|
14
|
+
signZomeCall: (zomeCall: ZomeCallUnsignedNapi) =>
|
|
15
|
+
ipcRenderer.invoke('sign-zome-call-legacy', zomeCall),
|
|
16
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>Holochain dev CLI</title>
|
|
6
|
+
<meta
|
|
7
|
+
http-equiv="Content-Security-Policy"
|
|
8
|
+
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
|
|
9
|
+
/>
|
|
10
|
+
</head>
|
|
11
|
+
|
|
12
|
+
<body>
|
|
13
|
+
<div class="container">
|
|
14
|
+
<h1>index.html not found.</h1>
|
|
15
|
+
<div style="margin-bottom: 100px">
|
|
16
|
+
Is your dev server running at the port specified with the
|
|
17
|
+
<span class="code">--ui-port</span> option?
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
<script type="module" src="./src/renderer.ts"></script>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
23
|
+
<style>
|
|
24
|
+
body {
|
|
25
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
26
|
+
margin: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.container {
|
|
30
|
+
display: flex;
|
|
31
|
+
height: 100vh;
|
|
32
|
+
margin: 0;
|
|
33
|
+
padding: 0;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.code {
|
|
40
|
+
background-color: lightgray;
|
|
41
|
+
border-radius: 3px;
|
|
42
|
+
padding: 1px 3px;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>Holochain dev CLI</title>
|
|
6
|
+
<meta
|
|
7
|
+
http-equiv="Content-Security-Policy"
|
|
8
|
+
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
|
|
9
|
+
/>
|
|
10
|
+
</head>
|
|
11
|
+
|
|
12
|
+
<body>
|
|
13
|
+
<div class="container">
|
|
14
|
+
<h1>index.html not found.</h1>
|
|
15
|
+
<div style="margin-bottom: 100px">
|
|
16
|
+
Make sure the UI assets in your .webhapp file contain an index.html file at the asset
|
|
17
|
+
folder's root level.
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
<script type="module" src="./src/renderer.ts"></script>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
23
|
+
<style>
|
|
24
|
+
body {
|
|
25
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
26
|
+
margin: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.container {
|
|
30
|
+
display: flex;
|
|
31
|
+
height: 100vh;
|
|
32
|
+
margin: 0;
|
|
33
|
+
padding: 0;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.code {
|
|
40
|
+
background-color: lightgray;
|
|
41
|
+
border-radius: 3px;
|
|
42
|
+
padding: 1px 3px;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>Holochain dev CLI</title>
|
|
6
|
+
<meta
|
|
7
|
+
http-equiv="Content-Security-Policy"
|
|
8
|
+
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
|
|
9
|
+
/>
|
|
10
|
+
</head>
|
|
11
|
+
|
|
12
|
+
<body>
|
|
13
|
+
<div class="container">
|
|
14
|
+
<h1>index.html not found.</h1>
|
|
15
|
+
<div style="margin-bottom: 100px">
|
|
16
|
+
Make sure that the path you provided via the <span class="code">--ui-path</span> option
|
|
17
|
+
contains an index.html file.
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
<script type="module" src="./src/renderer.ts"></script>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
23
|
+
<style>
|
|
24
|
+
body {
|
|
25
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
26
|
+
margin: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.container {
|
|
30
|
+
display: flex;
|
|
31
|
+
height: 100vh;
|
|
32
|
+
margin: 0;
|
|
33
|
+
padding: 0;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.code {
|
|
40
|
+
background-color: lightgray;
|
|
41
|
+
border-radius: 3px;
|
|
42
|
+
padding: 1px 3px;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
console.error('index.html not found.');
|
package/tsconfig.json
ADDED