@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 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 aps during development.
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
@@ -62,7 +62,7 @@ function nanoid(size = 21) {
62
62
  }
63
63
  return id;
64
64
  }
65
- const createHappWindow = async (uiSource, happOrWebhappPath, appId, agentNum, appPort, appAuthToken, appDataRootDir, openDevtools) => {
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
- const happWindow = new electron.BrowserWindow({
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
- happWindow.show();
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
- happWindow.show();
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
- return happWindow;
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 aps during development.").version(`${cliPackageJson.version} (built for holochain ${cliPackageJson.holochainVersion})`).argument(
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.1",
4
- "holochainVersion": "0.5.0",
5
- "description": "CLI to run Holochain aps during development.",
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.0",
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 aps during development.')
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 () {
@@ -16,16 +16,14 @@ export type UISource =
16
16
  port: number;
17
17
  };
18
18
 
19
- export const createHappWindow = async (
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
- openDevtools: boolean,
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
- const happWindow = new BrowserWindow({
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
- happWindow.show();
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