@holochain/hc-spin 0.500.1 → 0.500.3
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 +51 -15
- package/package.json +5 -5
- package/src/main/index.ts +13 -4
- 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-31: v0.500.3
|
|
13
|
+
### Fixed
|
|
14
|
+
- The `--network-seed` argument to hc sandbox was not actually passed on to hc sandbox. Fixed with [#33](https://github.com/holochain/hc-spin/pull/33)
|
|
15
|
+
|
|
16
|
+
## 2025-07-09: v0.500.2
|
|
17
|
+
### Fixed
|
|
18
|
+
- 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))
|
|
19
|
+
|
|
12
20
|
## 2025-04-30: v0.500.1
|
|
13
21
|
### Fixed
|
|
14
22
|
- 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))
|
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);
|
|
@@ -11661,7 +11664,6 @@ class AppWebsocket {
|
|
|
11661
11664
|
/**
|
|
11662
11665
|
* Instance factory for creating an {@link AppWebsocket}.
|
|
11663
11666
|
*
|
|
11664
|
-
* @param token - A token to authenticate the websocket connection. Get a token using AdminWebsocket#issueAppAuthenticationToken.
|
|
11665
11667
|
* @param options - {@link (WebsocketConnectionOptions:interface)}
|
|
11666
11668
|
* @returns A new instance of an AppWebsocket.
|
|
11667
11669
|
*/
|
|
@@ -11698,6 +11700,26 @@ class AppWebsocket {
|
|
|
11698
11700
|
this.cachedAppInfo = appInfo;
|
|
11699
11701
|
return appInfo;
|
|
11700
11702
|
}
|
|
11703
|
+
/**
|
|
11704
|
+
* Request the currently known agents of the app.
|
|
11705
|
+
*
|
|
11706
|
+
* @param req - An array of DNA hashes or null
|
|
11707
|
+
* @param timeout - A timeout to override the default.
|
|
11708
|
+
* @returns The app's agent infos as JSON string.
|
|
11709
|
+
*/
|
|
11710
|
+
async agentInfo(req, timeout2) {
|
|
11711
|
+
return await this.agentInfoRequester(req, timeout2);
|
|
11712
|
+
}
|
|
11713
|
+
/**
|
|
11714
|
+
* Request agent meta info for an agent by peer Url.
|
|
11715
|
+
*
|
|
11716
|
+
* @param req - The peer Url of the agent and an optional array of DNA hashes
|
|
11717
|
+
* @param timeout - A timeout to override the default.
|
|
11718
|
+
* @returns The meta info stored for this peer URL.
|
|
11719
|
+
*/
|
|
11720
|
+
async agentMetaInfo(req, timeout2) {
|
|
11721
|
+
return await this.agentMetaInfoRequester(req, timeout2);
|
|
11722
|
+
}
|
|
11701
11723
|
/**
|
|
11702
11724
|
* Request network stats.
|
|
11703
11725
|
*
|
|
@@ -11969,7 +11991,7 @@ class WsClient extends Emittery {
|
|
|
11969
11991
|
this.registerCloseListener(socket);
|
|
11970
11992
|
this.socket = socket;
|
|
11971
11993
|
this.url = url2;
|
|
11972
|
-
this.options = options
|
|
11994
|
+
this.options = options;
|
|
11973
11995
|
this.pendingRequests = {};
|
|
11974
11996
|
this.index = 0;
|
|
11975
11997
|
}
|
|
@@ -12304,6 +12326,10 @@ class AdminWebsocket {
|
|
|
12304
12326
|
* Add an existing agent to Holochain.
|
|
12305
12327
|
*/
|
|
12306
12328
|
addAgentInfo = this._requester("add_agent_info");
|
|
12329
|
+
/**
|
|
12330
|
+
* Request agent meta info about an agent.
|
|
12331
|
+
*/
|
|
12332
|
+
agentMetaInfo = this._requester("agent_meta_info");
|
|
12307
12333
|
/**
|
|
12308
12334
|
* Delete a disabled clone cell.
|
|
12309
12335
|
*/
|
|
@@ -12533,7 +12559,7 @@ const rustUtils = require("@holochain/hc-spin-rust-utils");
|
|
|
12533
12559
|
const cliPackageJsonPath = path.resolve(path.join(electron.app.getAppPath(), "../../package.json"));
|
|
12534
12560
|
const cliPackageJson = require(cliPackageJsonPath);
|
|
12535
12561
|
const cli = new commander.Command();
|
|
12536
|
-
cli.name("hc-spin").description("CLI to run Holochain
|
|
12562
|
+
cli.name("hc-spin").description("CLI to run Holochain apps during development.").version(`${cliPackageJson.version} (built for holochain ${cliPackageJson.holochainVersion})`).argument(
|
|
12537
12563
|
"<path>",
|
|
12538
12564
|
"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
12565
|
).option(
|
|
@@ -12668,6 +12694,10 @@ async function spawnSandboxes(nAgents, happPath, bootStrapUrl, signalUrl, appId,
|
|
|
12668
12694
|
appPorts += `${appPort},`;
|
|
12669
12695
|
}
|
|
12670
12696
|
generateArgs.push(appPorts.slice(0, appPorts.length - 1));
|
|
12697
|
+
if (networkSeed) {
|
|
12698
|
+
generateArgs.push("--network-seed");
|
|
12699
|
+
generateArgs.push(networkSeed);
|
|
12700
|
+
}
|
|
12671
12701
|
generateArgs.push(happPath, "network", "--bootstrap", bootStrapUrl, "webrtc", signalUrl);
|
|
12672
12702
|
let readyConductors = 0;
|
|
12673
12703
|
const portsInfo = {};
|
|
@@ -12718,7 +12748,8 @@ electron.app.whenReady().then(async () => {
|
|
|
12718
12748
|
happTargetDir ? happTargetDir : CLI_OPTS.happOrWebhappPath.path,
|
|
12719
12749
|
CLI_OPTS.bootstrapUrl ? CLI_OPTS.bootstrapUrl : bootstrapUrl,
|
|
12720
12750
|
CLI_OPTS.singalingUrl ? CLI_OPTS.singalingUrl : signalingUrl,
|
|
12721
|
-
CLI_OPTS.appId
|
|
12751
|
+
CLI_OPTS.appId,
|
|
12752
|
+
CLI_OPTS.networkSeed
|
|
12722
12753
|
);
|
|
12723
12754
|
const lairUrls = [];
|
|
12724
12755
|
sandboxPaths.forEach((sandbox) => {
|
|
@@ -12760,18 +12791,23 @@ electron.app.whenReady().then(async () => {
|
|
|
12760
12791
|
if (!appInfo) throw new Error("AppInfo is null.");
|
|
12761
12792
|
const happWindow = await createHappWindow(
|
|
12762
12793
|
CLI_OPTS.uiSource,
|
|
12763
|
-
CLI_OPTS.happOrWebhappPath,
|
|
12764
12794
|
CLI_OPTS.appId,
|
|
12765
12795
|
i + 1,
|
|
12766
12796
|
appPort,
|
|
12767
12797
|
appAuthTokenResponse.token,
|
|
12768
|
-
DATA_ROOT_DIR
|
|
12769
|
-
CLI_OPTS.openDevtools
|
|
12798
|
+
DATA_ROOT_DIR
|
|
12770
12799
|
);
|
|
12771
12800
|
WINDOW_INFO_MAP[happWindow.webContents.id] = {
|
|
12772
12801
|
agentPubKey: appInfo.agent_pub_key,
|
|
12773
12802
|
zomeCallSigner
|
|
12774
12803
|
};
|
|
12804
|
+
await loadHappWindow(
|
|
12805
|
+
happWindow,
|
|
12806
|
+
CLI_OPTS.uiSource,
|
|
12807
|
+
CLI_OPTS.happOrWebhappPath,
|
|
12808
|
+
i + 1,
|
|
12809
|
+
CLI_OPTS.openDevtools
|
|
12810
|
+
);
|
|
12775
12811
|
}
|
|
12776
12812
|
});
|
|
12777
12813
|
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.3",
|
|
4
|
+
"holochainVersion": "0.5.4",
|
|
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.2",
|
|
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>',
|
|
@@ -301,6 +301,7 @@ app.whenReady().then(async () => {
|
|
|
301
301
|
CLI_OPTS.bootstrapUrl ? CLI_OPTS.bootstrapUrl : bootstrapUrl,
|
|
302
302
|
CLI_OPTS.singalingUrl ? CLI_OPTS.singalingUrl : signalingUrl,
|
|
303
303
|
CLI_OPTS.appId,
|
|
304
|
+
CLI_OPTS.networkSeed,
|
|
304
305
|
);
|
|
305
306
|
|
|
306
307
|
const lairUrls: string[] = [];
|
|
@@ -352,18 +353,26 @@ app.whenReady().then(async () => {
|
|
|
352
353
|
if (!appInfo) throw new Error('AppInfo is null.');
|
|
353
354
|
const happWindow = await createHappWindow(
|
|
354
355
|
CLI_OPTS.uiSource,
|
|
355
|
-
CLI_OPTS.happOrWebhappPath,
|
|
356
356
|
CLI_OPTS.appId,
|
|
357
357
|
i + 1,
|
|
358
358
|
appPort,
|
|
359
359
|
appAuthTokenResponse.token,
|
|
360
360
|
DATA_ROOT_DIR,
|
|
361
|
-
CLI_OPTS.openDevtools,
|
|
362
361
|
);
|
|
362
|
+
// We need to add the window to the window map before loading its UI, otherwise
|
|
363
|
+
// zome calls can be made before handleSignZomeCall() can verify that the
|
|
364
|
+
// zome call is made from an authorized window (https://github.com/holochain/hc-spin/issues/30)
|
|
363
365
|
WINDOW_INFO_MAP[happWindow.webContents.id] = {
|
|
364
366
|
agentPubKey: appInfo.agent_pub_key,
|
|
365
367
|
zomeCallSigner,
|
|
366
368
|
};
|
|
369
|
+
await loadHappWindow(
|
|
370
|
+
happWindow,
|
|
371
|
+
CLI_OPTS.uiSource,
|
|
372
|
+
CLI_OPTS.happOrWebhappPath,
|
|
373
|
+
i + 1,
|
|
374
|
+
CLI_OPTS.openDevtools,
|
|
375
|
+
);
|
|
367
376
|
}
|
|
368
377
|
|
|
369
378
|
// 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
|