@holochain/hc-spin 0.500.0 → 0.500.2
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/CHANGELOG.md +8 -0
- package/README.md +1 -1
- package/dist/main/index.js +47 -14
- package/package.json +5 -5
- package/src/main/index.ts +15 -8
- package/src/main/windows.ts +14 -13
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
9
9
|
### Changed
|
|
10
10
|
### Removed
|
|
11
11
|
|
|
12
|
+
## 2025-07-09: v0.500.2
|
|
13
|
+
### Fixed
|
|
14
|
+
- Fixes issue [#31](https://github.com/holochain/hc-spin/issues/30) that made initial zome calls fail in cases where the UI loaded faster than the zome call signing logic was ready (PR [#31](https://github.com/holochain/hc-spin/pull/31))
|
|
15
|
+
|
|
16
|
+
## 2025-04-30: v0.500.1
|
|
17
|
+
### Fixed
|
|
18
|
+
- Fixed an error that could prevent hc-spin to start up properly (`ERROR: error: invalid value 'undefined' for '--bootstrap <BOOTSTRAP>': relative URL without a base`) ([#25](https://github.com/holochain/hc-spin/pull/25))
|
|
19
|
+
|
|
12
20
|
## 2025-04-10: v0.500.0-rc.0
|
|
13
21
|
|
|
14
22
|
### Changed
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ npm install --save-dev @holochain/hc-spin@">=0.500.0 <0.600.0"
|
|
|
25
25
|
```
|
|
26
26
|
Usage: hc-spin [options] <path>
|
|
27
27
|
|
|
28
|
-
CLI to run Holochain
|
|
28
|
+
CLI to run Holochain apps during development.
|
|
29
29
|
|
|
30
30
|
Arguments:
|
|
31
31
|
path Path to .webhapp or .happ file to launch. If a .happ file is passed, either a UI path must be specified via
|
package/dist/main/index.js
CHANGED
|
@@ -62,7 +62,7 @@ function nanoid(size = 21) {
|
|
|
62
62
|
}
|
|
63
63
|
return id;
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
async function createHappWindow(uiSource, appId, agentNum, appPort, appAuthToken, appDataRootDir) {
|
|
66
66
|
if (!appPort) throw new Error("App port not defined.");
|
|
67
67
|
const partition = `persist:${agentNum}:${appId}`;
|
|
68
68
|
if (uiSource.type === "path") {
|
|
@@ -107,7 +107,7 @@ electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
|
|
|
107
107
|
console.error("Failed to get icon.png: ", e);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
|
|
110
|
+
return new electron.BrowserWindow({
|
|
111
111
|
width: 1200,
|
|
112
112
|
height: 800,
|
|
113
113
|
show: false,
|
|
@@ -118,6 +118,8 @@ electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
|
|
|
118
118
|
partition
|
|
119
119
|
}
|
|
120
120
|
});
|
|
121
|
+
}
|
|
122
|
+
async function loadHappWindow(happWindow, uiSource, happOrWebhappPath, agentNum, openDevtools) {
|
|
121
123
|
const [windowPositionX, windowPositionY] = happWindow.getPosition();
|
|
122
124
|
const windowPositionXMoved = windowPositionX + agentNum * 20;
|
|
123
125
|
const windowPositionYMoved = windowPositionY + agentNum * 20;
|
|
@@ -138,8 +140,7 @@ electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
|
|
|
138
140
|
} else {
|
|
139
141
|
happWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
|
|
140
142
|
}
|
|
141
|
-
|
|
142
|
-
return happWindow;
|
|
143
|
+
return;
|
|
143
144
|
}
|
|
144
145
|
await happWindow.loadURL(`http://localhost:${uiSource.port}`);
|
|
145
146
|
} else if (uiSource.type === "path") {
|
|
@@ -153,15 +154,13 @@ electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
|
|
|
153
154
|
const notFoundPath = happOrWebhappPath.type === "webhapp" ? path.join(__dirname, "../renderer/indexNotFound1.html") : path.join(__dirname, "../renderer/indexNotFound2.html");
|
|
154
155
|
happWindow.loadFile(notFoundPath);
|
|
155
156
|
}
|
|
156
|
-
|
|
157
|
-
return happWindow;
|
|
157
|
+
return;
|
|
158
158
|
}
|
|
159
159
|
} else {
|
|
160
160
|
throw new Error("Unsupported uiSource type: ", uiSource.type);
|
|
161
161
|
}
|
|
162
162
|
happWindow.show();
|
|
163
|
-
|
|
164
|
-
};
|
|
163
|
+
}
|
|
165
164
|
function setLinkOpenHandlers(browserWindow) {
|
|
166
165
|
browserWindow.webContents.on("will-navigate", (e) => {
|
|
167
166
|
if (e.url.startsWith("http://localhost") || e.url.startsWith("http://127.0.0.1")) {
|
|
@@ -11617,6 +11616,8 @@ class AppWebsocket {
|
|
|
11617
11616
|
callZomeTransform;
|
|
11618
11617
|
cachedAppInfo;
|
|
11619
11618
|
appInfoRequester;
|
|
11619
|
+
agentInfoRequester;
|
|
11620
|
+
agentMetaInfoRequester;
|
|
11620
11621
|
callZomeRequester;
|
|
11621
11622
|
provideMemproofRequester;
|
|
11622
11623
|
enableAppRequester;
|
|
@@ -11637,6 +11638,8 @@ class AppWebsocket {
|
|
|
11637
11638
|
this.emitter = new Emittery();
|
|
11638
11639
|
this.cachedAppInfo = appInfo;
|
|
11639
11640
|
this.appInfoRequester = AppWebsocket.requester(this.client, "app_info", this.defaultTimeout);
|
|
11641
|
+
this.agentInfoRequester = AppWebsocket.requester(this.client, "agent_info", this.defaultTimeout);
|
|
11642
|
+
this.agentMetaInfoRequester = AppWebsocket.requester(this.client, "agent_meta_info", this.defaultTimeout);
|
|
11640
11643
|
this.callZomeRequester = AppWebsocket.requester(this.client, "call_zome", this.defaultTimeout, this.callZomeTransform);
|
|
11641
11644
|
this.provideMemproofRequester = AppWebsocket.requester(this.client, "provide_memproofs", this.defaultTimeout);
|
|
11642
11645
|
this.enableAppRequester = AppWebsocket.requester(this.client, "enable_app", this.defaultTimeout);
|
|
@@ -11698,6 +11701,26 @@ class AppWebsocket {
|
|
|
11698
11701
|
this.cachedAppInfo = appInfo;
|
|
11699
11702
|
return appInfo;
|
|
11700
11703
|
}
|
|
11704
|
+
/**
|
|
11705
|
+
* Request the currently known agents of the app.
|
|
11706
|
+
*
|
|
11707
|
+
* @param req - An array of DNA hashes or null
|
|
11708
|
+
* @returns The app's agent infos as JSON string.
|
|
11709
|
+
*/
|
|
11710
|
+
async agentInfo(req, timeout2) {
|
|
11711
|
+
const agentInfos = await this.agentInfoRequester(req, timeout2);
|
|
11712
|
+
return agentInfos;
|
|
11713
|
+
}
|
|
11714
|
+
/**
|
|
11715
|
+
* Request agent meta info for an agent by peer Url.
|
|
11716
|
+
*
|
|
11717
|
+
* @param req - The peer Url of the agent and an optional array of DNA hashes
|
|
11718
|
+
* @returns The app's agent infos as JSON string.
|
|
11719
|
+
*/
|
|
11720
|
+
async agentMetaInfo(req, timeout2) {
|
|
11721
|
+
const agentInfos = await this.agentMetaInfoRequester(req, timeout2);
|
|
11722
|
+
return agentInfos;
|
|
11723
|
+
}
|
|
11701
11724
|
/**
|
|
11702
11725
|
* Request network stats.
|
|
11703
11726
|
*
|
|
@@ -11969,7 +11992,7 @@ class WsClient extends Emittery {
|
|
|
11969
11992
|
this.registerCloseListener(socket);
|
|
11970
11993
|
this.socket = socket;
|
|
11971
11994
|
this.url = url2;
|
|
11972
|
-
this.options = options
|
|
11995
|
+
this.options = options;
|
|
11973
11996
|
this.pendingRequests = {};
|
|
11974
11997
|
this.index = 0;
|
|
11975
11998
|
}
|
|
@@ -12304,6 +12327,10 @@ class AdminWebsocket {
|
|
|
12304
12327
|
* Add an existing agent to Holochain.
|
|
12305
12328
|
*/
|
|
12306
12329
|
addAgentInfo = this._requester("add_agent_info");
|
|
12330
|
+
/**
|
|
12331
|
+
* Request agent meta info about an agent.
|
|
12332
|
+
*/
|
|
12333
|
+
agentMetaInfo = this._requester("agent_meta_info");
|
|
12307
12334
|
/**
|
|
12308
12335
|
* Delete a disabled clone cell.
|
|
12309
12336
|
*/
|
|
@@ -12533,7 +12560,7 @@ const rustUtils = require("@holochain/hc-spin-rust-utils");
|
|
|
12533
12560
|
const cliPackageJsonPath = path.resolve(path.join(electron.app.getAppPath(), "../../package.json"));
|
|
12534
12561
|
const cliPackageJson = require(cliPackageJsonPath);
|
|
12535
12562
|
const cli = new commander.Command();
|
|
12536
|
-
cli.name("hc-spin").description("CLI to run Holochain
|
|
12563
|
+
cli.name("hc-spin").description("CLI to run Holochain apps during development.").version(`${cliPackageJson.version} (built for holochain ${cliPackageJson.holochainVersion})`).argument(
|
|
12537
12564
|
"<path>",
|
|
12538
12565
|
"Path to .webhapp or .happ file to launch. If a .happ file is passed, either a UI path must be specified via --ui-path or a port pointing to a localhost server via --ui-port"
|
|
12539
12566
|
).option(
|
|
@@ -12643,7 +12670,8 @@ async function startLocalServices() {
|
|
|
12643
12670
|
bootstrapRunning = true;
|
|
12644
12671
|
signalRunnig = true;
|
|
12645
12672
|
}
|
|
12646
|
-
if (bootstrapRunning && signalRunnig
|
|
12673
|
+
if (bootstrapRunning && signalRunnig && bootStrapUrl && signalUrl)
|
|
12674
|
+
resolve([bootStrapUrl, signalUrl]);
|
|
12647
12675
|
});
|
|
12648
12676
|
localServicesHandle.stderr.pipe(split()).on("data", async (line) => {
|
|
12649
12677
|
console.log(`[hc-spin] | [hc run-local-services] ERROR: ${line}`);
|
|
@@ -12759,18 +12787,23 @@ electron.app.whenReady().then(async () => {
|
|
|
12759
12787
|
if (!appInfo) throw new Error("AppInfo is null.");
|
|
12760
12788
|
const happWindow = await createHappWindow(
|
|
12761
12789
|
CLI_OPTS.uiSource,
|
|
12762
|
-
CLI_OPTS.happOrWebhappPath,
|
|
12763
12790
|
CLI_OPTS.appId,
|
|
12764
12791
|
i + 1,
|
|
12765
12792
|
appPort,
|
|
12766
12793
|
appAuthTokenResponse.token,
|
|
12767
|
-
DATA_ROOT_DIR
|
|
12768
|
-
CLI_OPTS.openDevtools
|
|
12794
|
+
DATA_ROOT_DIR
|
|
12769
12795
|
);
|
|
12770
12796
|
WINDOW_INFO_MAP[happWindow.webContents.id] = {
|
|
12771
12797
|
agentPubKey: appInfo.agent_pub_key,
|
|
12772
12798
|
zomeCallSigner
|
|
12773
12799
|
};
|
|
12800
|
+
await loadHappWindow(
|
|
12801
|
+
happWindow,
|
|
12802
|
+
CLI_OPTS.uiSource,
|
|
12803
|
+
CLI_OPTS.happOrWebhappPath,
|
|
12804
|
+
i + 1,
|
|
12805
|
+
CLI_OPTS.openDevtools
|
|
12806
|
+
);
|
|
12774
12807
|
}
|
|
12775
12808
|
});
|
|
12776
12809
|
electron.app.on("quit", () => {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holochain/hc-spin",
|
|
3
|
-
"version": "0.500.
|
|
4
|
-
"holochainVersion": "0.5.
|
|
5
|
-
"description": "CLI to run Holochain
|
|
3
|
+
"version": "0.500.2",
|
|
4
|
+
"holochainVersion": "0.5.3",
|
|
5
|
+
"description": "CLI to run Holochain apps during development.",
|
|
6
6
|
"author": "matthme",
|
|
7
7
|
"homepage": "https://developer.holochain.org",
|
|
8
8
|
"repository": {
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@electron-toolkit/preload": "^3.0.0",
|
|
37
37
|
"@electron-toolkit/utils": "^3.0.0",
|
|
38
|
-
"@holochain/client": "0.19.
|
|
39
|
-
"@holochain/hc-spin-rust-utils": "0.500.0",
|
|
38
|
+
"@holochain/client": "^0.19.1",
|
|
39
|
+
"@holochain/hc-spin-rust-utils": "^0.500.0",
|
|
40
40
|
"@msgpack/msgpack": "^2.8.0",
|
|
41
41
|
"bufferutil": "4.0.8",
|
|
42
42
|
"commander": "11.1.0",
|
package/src/main/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import contextMenu from 'electron-context-menu';
|
|
|
7
7
|
import split from 'split';
|
|
8
8
|
import * as childProcess from 'child_process';
|
|
9
9
|
import { ZomeCallSigner } from '@holochain/hc-spin-rust-utils';
|
|
10
|
-
import { createHappWindow } from './windows';
|
|
10
|
+
import { createHappWindow, loadHappWindow } from './windows';
|
|
11
11
|
import getPort from 'get-port';
|
|
12
12
|
import {
|
|
13
13
|
AdminWebsocket,
|
|
@@ -32,7 +32,7 @@ const cli = new Command();
|
|
|
32
32
|
|
|
33
33
|
cli
|
|
34
34
|
.name('hc-spin')
|
|
35
|
-
.description('CLI to run Holochain
|
|
35
|
+
.description('CLI to run Holochain apps during development.')
|
|
36
36
|
.version(`${cliPackageJson.version} (built for holochain ${cliPackageJson.holochainVersion})`)
|
|
37
37
|
.argument(
|
|
38
38
|
'<path>',
|
|
@@ -177,9 +177,7 @@ async function startLocalServices(): Promise<[string, string]> {
|
|
|
177
177
|
localServicesHandle.stdout.pipe(split()).on('data', async (line: string) => {
|
|
178
178
|
console.log(`[hc-spin] | [kitsune2-bootstrap-srv]: ${line}`);
|
|
179
179
|
if (line.includes('#kitsune2_bootstrap_srv#listening#')) {
|
|
180
|
-
const hostAndPort = line
|
|
181
|
-
.split('#kitsune2_bootstrap_srv#listening#')[1]
|
|
182
|
-
.split("#")[0]
|
|
180
|
+
const hostAndPort = line.split('#kitsune2_bootstrap_srv#listening#')[1].split('#')[0];
|
|
183
181
|
bootStrapUrl = `http://${hostAndPort}`;
|
|
184
182
|
signalUrl = `ws://${hostAndPort}`;
|
|
185
183
|
}
|
|
@@ -187,7 +185,8 @@ async function startLocalServices(): Promise<[string, string]> {
|
|
|
187
185
|
bootstrapRunning = true;
|
|
188
186
|
signalRunnig = true;
|
|
189
187
|
}
|
|
190
|
-
if (bootstrapRunning && signalRunnig
|
|
188
|
+
if (bootstrapRunning && signalRunnig && bootStrapUrl && signalUrl)
|
|
189
|
+
resolve([bootStrapUrl, signalUrl]);
|
|
191
190
|
});
|
|
192
191
|
localServicesHandle.stderr.pipe(split()).on('data', async (line: string) => {
|
|
193
192
|
console.log(`[hc-spin] | [hc run-local-services] ERROR: ${line}`);
|
|
@@ -353,18 +352,26 @@ app.whenReady().then(async () => {
|
|
|
353
352
|
if (!appInfo) throw new Error('AppInfo is null.');
|
|
354
353
|
const happWindow = await createHappWindow(
|
|
355
354
|
CLI_OPTS.uiSource,
|
|
356
|
-
CLI_OPTS.happOrWebhappPath,
|
|
357
355
|
CLI_OPTS.appId,
|
|
358
356
|
i + 1,
|
|
359
357
|
appPort,
|
|
360
358
|
appAuthTokenResponse.token,
|
|
361
359
|
DATA_ROOT_DIR,
|
|
362
|
-
CLI_OPTS.openDevtools,
|
|
363
360
|
);
|
|
361
|
+
// We need to add the window to the window map before loading its UI, otherwise
|
|
362
|
+
// zome calls can be made before handleSignZomeCall() can verify that the
|
|
363
|
+
// zome call is made from an authorized window (https://github.com/holochain/hc-spin/issues/30)
|
|
364
364
|
WINDOW_INFO_MAP[happWindow.webContents.id] = {
|
|
365
365
|
agentPubKey: appInfo.agent_pub_key,
|
|
366
366
|
zomeCallSigner,
|
|
367
367
|
};
|
|
368
|
+
await loadHappWindow(
|
|
369
|
+
happWindow,
|
|
370
|
+
CLI_OPTS.uiSource,
|
|
371
|
+
CLI_OPTS.happOrWebhappPath,
|
|
372
|
+
i + 1,
|
|
373
|
+
CLI_OPTS.openDevtools,
|
|
374
|
+
);
|
|
368
375
|
}
|
|
369
376
|
|
|
370
377
|
// app.on('activate', function () {
|
package/src/main/windows.ts
CHANGED
|
@@ -16,16 +16,14 @@ export type UISource =
|
|
|
16
16
|
port: number;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
export
|
|
19
|
+
export async function createHappWindow(
|
|
20
20
|
uiSource: UISource,
|
|
21
|
-
happOrWebhappPath: HappOrWebhappPath,
|
|
22
21
|
appId: InstalledAppId,
|
|
23
22
|
agentNum: number,
|
|
24
23
|
appPort: number,
|
|
25
24
|
appAuthToken: AppAuthenticationToken,
|
|
26
25
|
appDataRootDir: string,
|
|
27
|
-
|
|
28
|
-
): Promise<BrowserWindow> => {
|
|
26
|
+
): Promise<BrowserWindow> {
|
|
29
27
|
// TODO create mapping between installed-app-id's and window ids
|
|
30
28
|
if (!appPort) throw new Error('App port not defined.');
|
|
31
29
|
|
|
@@ -81,7 +79,7 @@ electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
|
|
|
81
79
|
}
|
|
82
80
|
}
|
|
83
81
|
|
|
84
|
-
|
|
82
|
+
return new BrowserWindow({
|
|
85
83
|
width: 1200,
|
|
86
84
|
height: 800,
|
|
87
85
|
show: false,
|
|
@@ -92,7 +90,15 @@ electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
|
|
|
92
90
|
partition,
|
|
93
91
|
},
|
|
94
92
|
});
|
|
93
|
+
}
|
|
95
94
|
|
|
95
|
+
export async function loadHappWindow(
|
|
96
|
+
happWindow: BrowserWindow,
|
|
97
|
+
uiSource: UISource,
|
|
98
|
+
happOrWebhappPath: HappOrWebhappPath,
|
|
99
|
+
agentNum: number,
|
|
100
|
+
openDevtools: boolean,
|
|
101
|
+
): Promise<void> {
|
|
96
102
|
const [windowPositionX, windowPositionY] = happWindow.getPosition();
|
|
97
103
|
const windowPositionXMoved = windowPositionX + agentNum * 20;
|
|
98
104
|
const windowPositionYMoved = windowPositionY + agentNum * 20;
|
|
@@ -119,8 +125,7 @@ electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
|
|
|
119
125
|
} else {
|
|
120
126
|
happWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
|
|
121
127
|
}
|
|
122
|
-
|
|
123
|
-
return happWindow;
|
|
128
|
+
return;
|
|
124
129
|
}
|
|
125
130
|
await happWindow.loadURL(`http://localhost:${uiSource.port}`);
|
|
126
131
|
} else if (uiSource.type === 'path') {
|
|
@@ -138,18 +143,14 @@ electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
|
|
|
138
143
|
: path.join(__dirname, '../renderer/indexNotFound2.html');
|
|
139
144
|
happWindow.loadFile(notFoundPath);
|
|
140
145
|
}
|
|
141
|
-
|
|
142
|
-
happWindow.show();
|
|
143
|
-
return happWindow;
|
|
146
|
+
return;
|
|
144
147
|
}
|
|
145
148
|
} else {
|
|
146
149
|
throw new Error('Unsupported uiSource type: ', (uiSource as any).type);
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
happWindow.show();
|
|
150
|
-
|
|
151
|
-
return happWindow;
|
|
152
|
-
};
|
|
153
|
+
}
|
|
153
154
|
|
|
154
155
|
export function setLinkOpenHandlers(browserWindow: BrowserWindow): void {
|
|
155
156
|
// links in happ windows should open in the system default application
|