@holochain/hc-spin 0.500.1 → 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 CHANGED
@@ -9,6 +9,10 @@ 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
+
12
16
  ## 2025-04-30: v0.500.1
13
17
  ### Fixed
14
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))
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);
@@ -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 aps during development.").version(`${cliPackageJson.version} (built for holochain ${cliPackageJson.holochainVersion})`).argument(
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(
@@ -12760,18 +12787,23 @@ electron.app.whenReady().then(async () => {
12760
12787
  if (!appInfo) throw new Error("AppInfo is null.");
12761
12788
  const happWindow = await createHappWindow(
12762
12789
  CLI_OPTS.uiSource,
12763
- CLI_OPTS.happOrWebhappPath,
12764
12790
  CLI_OPTS.appId,
12765
12791
  i + 1,
12766
12792
  appPort,
12767
12793
  appAuthTokenResponse.token,
12768
- DATA_ROOT_DIR,
12769
- CLI_OPTS.openDevtools
12794
+ DATA_ROOT_DIR
12770
12795
  );
12771
12796
  WINDOW_INFO_MAP[happWindow.webContents.id] = {
12772
12797
  agentPubKey: appInfo.agent_pub_key,
12773
12798
  zomeCallSigner
12774
12799
  };
12800
+ await loadHappWindow(
12801
+ happWindow,
12802
+ CLI_OPTS.uiSource,
12803
+ CLI_OPTS.happOrWebhappPath,
12804
+ i + 1,
12805
+ CLI_OPTS.openDevtools
12806
+ );
12775
12807
  }
12776
12808
  });
12777
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.1",
4
- "holochainVersion": "0.5.0",
5
- "description": "CLI to run Holochain aps during development.",
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.0",
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 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>',
@@ -352,18 +352,26 @@ app.whenReady().then(async () => {
352
352
  if (!appInfo) throw new Error('AppInfo is null.');
353
353
  const happWindow = await createHappWindow(
354
354
  CLI_OPTS.uiSource,
355
- CLI_OPTS.happOrWebhappPath,
356
355
  CLI_OPTS.appId,
357
356
  i + 1,
358
357
  appPort,
359
358
  appAuthTokenResponse.token,
360
359
  DATA_ROOT_DIR,
361
- CLI_OPTS.openDevtools,
362
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)
363
364
  WINDOW_INFO_MAP[happWindow.webContents.id] = {
364
365
  agentPubKey: appInfo.agent_pub_key,
365
366
  zomeCallSigner,
366
367
  };
368
+ await loadHappWindow(
369
+ happWindow,
370
+ CLI_OPTS.uiSource,
371
+ CLI_OPTS.happOrWebhappPath,
372
+ i + 1,
373
+ CLI_OPTS.openDevtools,
374
+ );
367
375
  }
368
376
 
369
377
  // 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