@holochain/hc-spin 0.300.0 → 0.300.2-dev.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/README.md CHANGED
@@ -20,6 +20,14 @@ To install the latest version compatible with **holochain 0.2.x**:
20
20
  npm install --save-dev @holochain/hc-spin@">=0.200.0 <0.300.0"
21
21
  ```
22
22
 
23
+ To install the latest version compatible with **holochain 0.3.x**:
24
+
25
+ ⚠️ Requires `@holochain/client 0.17.0-dev.5` or newer ⚠️
26
+
27
+ ```
28
+ npm install --save-dev @holochain/hc-spin@">=0.300.0 <0.400.0"
29
+ ```
30
+
23
31
  ## Usage (holochain 0.2)
24
32
 
25
33
  ```
package/cli/cli.js CHANGED
@@ -4,7 +4,11 @@ const path = require('path');
4
4
  const fs = require('fs');
5
5
 
6
6
  let electronBinary;
7
- let pathStr = '../node_modules/.bin/electron';
7
+ let pathStr =
8
+ process.platform === 'win32'
9
+ ? '../node_modules/electron/dist/electron.exe'
10
+ : '../node_modules/.bin/electron';
11
+
8
12
  // recursively look for electron binary in node_modules folder
9
13
  for (let i = 0; i < 7; i++) {
10
14
  const maybeElectronBinary = path.resolve(__dirname, pathStr);
package/dist/cli.js CHANGED
@@ -4,7 +4,11 @@ const path = require('path');
4
4
  const fs = require('fs');
5
5
 
6
6
  let electronBinary;
7
- let pathStr = '../node_modules/.bin/electron';
7
+ let pathStr =
8
+ process.platform === 'win32'
9
+ ? '../node_modules/electron/dist/electron.exe'
10
+ : '../node_modules/.bin/electron';
11
+
8
12
  // recursively look for electron binary in node_modules folder
9
13
  for (let i = 0; i < 7; i++) {
10
14
  const maybeElectronBinary = path.resolve(__dirname, pathStr);
@@ -95,7 +95,7 @@ electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
95
95
  icon = electron.nativeImage.createFromPath(iconPath);
96
96
  } else {
97
97
  try {
98
- const iconResponse = await electron.net.fetch(`http://127.0.0.1:${uiSource.port}/icon.png`);
98
+ const iconResponse = await electron.net.fetch(`http://localhost:${uiSource.port}/icon.png`);
99
99
  if (iconResponse.status === 404 && agentNum === 1) {
100
100
  console.warn(
101
101
  "\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"
@@ -131,9 +131,9 @@ electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
131
131
  happWindow.webContents.openDevTools();
132
132
  if (uiSource.type === "port") {
133
133
  try {
134
- await electron.net.fetch(`http://127.0.0.1:${uiSource.port}/index.html`);
134
+ await electron.net.fetch(`http://localhost:${uiSource.port}/index.html`);
135
135
  } catch (e) {
136
- console.error(`No index.html file found at http://127.0.0.1:${uiSource.port}/index.html`, e);
136
+ console.error(`No index.html file found at http://localhost:${uiSource.port}/index.html`, e);
137
137
  if (utils.is.dev && process.env["ELECTRON_RENDERER_URL"]) {
138
138
  happWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
139
139
  } else {
@@ -142,7 +142,7 @@ electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
142
142
  happWindow.show();
143
143
  return happWindow;
144
144
  }
145
- await happWindow.loadURL(`http://127.0.0.1:${uiSource.port}`);
145
+ await happWindow.loadURL(`http://localhost:${uiSource.port}`);
146
146
  } else if (uiSource.type === "path") {
147
147
  try {
148
148
  await happWindow.loadURL(`webhapp://webhappwindow/index.html`);
@@ -10609,15 +10609,61 @@ const SignalType = {
10609
10609
  App: "App",
10610
10610
  System: "System"
10611
10611
  };
10612
+ const ERROR_TYPE = "error";
10613
+ const DEFAULT_TIMEOUT = 6e4;
10614
+ const requesterTransformer = (requester, tag, transform = identityTransformer) => async (req, timeout2) => {
10615
+ const transformedInput = await transform.input(req);
10616
+ const input = { type: { [tag]: null }, data: transformedInput };
10617
+ const response = await requester(input, timeout2);
10618
+ const output = transform.output(response.data);
10619
+ return output;
10620
+ };
10621
+ const identity = (x) => x;
10622
+ const identityTransformer = {
10623
+ input: identity,
10624
+ output: identity
10625
+ };
10626
+ class HolochainError extends Error {
10627
+ constructor(name, message) {
10628
+ super();
10629
+ this.name = name;
10630
+ this.message = message;
10631
+ }
10632
+ }
10633
+ const catchError = (res) => {
10634
+ if (ERROR_TYPE in res.type) {
10635
+ const errorName = Object.keys(res.data.type)[0];
10636
+ const error = new HolochainError(errorName, res.data.data);
10637
+ return Promise.reject(error);
10638
+ } else {
10639
+ return Promise.resolve(res);
10640
+ }
10641
+ };
10642
+ const promiseTimeout = (promise2, tag, ms) => {
10643
+ let id;
10644
+ const timeout2 = new Promise((_, reject) => {
10645
+ id = setTimeout(() => reject(new Error(`Timed out in ${ms}ms: ${tag}`)), ms);
10646
+ });
10647
+ return new Promise((res, rej) => {
10648
+ Promise.race([promise2, timeout2]).then((a) => {
10649
+ clearTimeout(id);
10650
+ return res(a);
10651
+ }).catch((e) => {
10652
+ return rej(e);
10653
+ });
10654
+ });
10655
+ };
10612
10656
  class WsClient extends Emittery {
10613
10657
  socket;
10614
10658
  url;
10659
+ options;
10615
10660
  pendingRequests;
10616
10661
  index;
10617
- constructor(socket, url2) {
10662
+ constructor(socket, url2, options) {
10618
10663
  super();
10619
10664
  this.socket = socket;
10620
10665
  this.url = url2;
10666
+ this.options = options || {};
10621
10667
  this.pendingRequests = {};
10622
10668
  this.index = 0;
10623
10669
  this.setupSocket();
@@ -10631,14 +10677,14 @@ class WsClient extends Emittery {
10631
10677
  if (typeof Buffer !== "undefined" && Buffer.isBuffer(serializedMessage.data)) {
10632
10678
  deserializedData = serializedMessage.data;
10633
10679
  } else {
10634
- throw new Error("websocket client: unknown message format");
10680
+ throw new HolochainError("UnknownMessageFormat", `incoming message has unknown message format - ${deserializedData}`);
10635
10681
  }
10636
10682
  }
10637
10683
  const message = msgpack.decode(deserializedData);
10638
10684
  assertHolochainMessage(message);
10639
10685
  if (message.type === "signal") {
10640
10686
  if (message.data === null) {
10641
- throw new Error("received a signal without data");
10687
+ throw new HolochainError("UnknownSignalFormat", "incoming signal has no data");
10642
10688
  }
10643
10689
  const deserializedSignal = msgpack.decode(message.data);
10644
10690
  assertHolochainSignal(deserializedSignal);
@@ -10656,14 +10702,14 @@ class WsClient extends Emittery {
10656
10702
  } else if (message.type === "response") {
10657
10703
  this.handleResponse(message);
10658
10704
  } else {
10659
- console.error(`Got unrecognized Websocket message type: ${message.type}`);
10705
+ throw new HolochainError("UnknownMessageType", `incoming message has unknown type - ${message.type}`);
10660
10706
  }
10661
10707
  };
10662
10708
  this.socket.onclose = (event) => {
10663
10709
  const pendingRequestIds = Object.keys(this.pendingRequests).map((id) => parseInt(id));
10664
10710
  if (pendingRequestIds.length) {
10665
10711
  pendingRequestIds.forEach((id) => {
10666
- const error = new Error(`Websocket closed with pending requests. Close event code: ${event.code}, request id: ${id}`);
10712
+ const error = new HolochainError("ClientClosedWithPendingRequests", `client closed with pending requests - close event code: ${event.code}, request id: ${id}`);
10667
10713
  this.pendingRequests[id].reject(error);
10668
10714
  delete this.pendingRequests[id];
10669
10715
  });
@@ -10676,14 +10722,14 @@ class WsClient extends Emittery {
10676
10722
  * @param url - The WebSocket URL to connect to.
10677
10723
  * @returns An new instance of the WsClient.
10678
10724
  */
10679
- static connect(url2) {
10725
+ static connect(url2, options) {
10680
10726
  return new Promise((resolve, reject) => {
10681
- const socket = new IsoWebSocket(url2);
10682
- socket.onerror = () => {
10683
- reject(new Error(`could not connect to holochain conductor, please check that a conductor service is running and available at ${url2}`));
10727
+ const socket = new IsoWebSocket(url2, options);
10728
+ socket.onerror = (errorEvent) => {
10729
+ reject(new HolochainError("ConnectionError", `could not connect to Holochain Conductor API at ${url2} - ${errorEvent.error}`));
10684
10730
  };
10685
10731
  socket.onopen = () => {
10686
- const client = new WsClient(socket, url2);
10732
+ const client = new WsClient(socket, url2, options);
10687
10733
  resolve(client);
10688
10734
  };
10689
10735
  });
@@ -10714,10 +10760,10 @@ class WsClient extends Emittery {
10714
10760
  return promise2;
10715
10761
  } else if (this.url) {
10716
10762
  const response = new Promise((resolve, reject) => {
10717
- const socket = new IsoWebSocket(this.url);
10763
+ const socket = new IsoWebSocket(this.url, this.options);
10718
10764
  this.socket = socket;
10719
- socket.onerror = () => {
10720
- reject(new Error(`could not connect to Holochain conductor, please check that a conductor service is running and available at ${this.url}`));
10765
+ socket.onerror = (errorEvent) => {
10766
+ reject(new HolochainError("ConnectionError", `could not connect to Holochain Conductor API at ${this.url} - ${errorEvent.error}`));
10721
10767
  };
10722
10768
  socket.onopen = () => {
10723
10769
  this.sendMessage(request, resolve, reject);
@@ -10750,7 +10796,7 @@ class WsClient extends Emittery {
10750
10796
  }
10751
10797
  delete this.pendingRequests[id];
10752
10798
  } else {
10753
- console.error(`Got response with no matching request. id=${id}`);
10799
+ console.error(`got response with no matching request. id = ${id} msg = ${msg}`);
10754
10800
  }
10755
10801
  }
10756
10802
  /**
@@ -10774,58 +10820,14 @@ function assertHolochainMessage(message) {
10774
10820
  if (typeof message === "object" && message !== null && "type" in message && "data" in message) {
10775
10821
  return;
10776
10822
  }
10777
- throw new Error(`unknown message format ${JSON.stringify(message, null, 4)}`);
10823
+ throw new HolochainError("UnknownMessageFormat", `incoming message has unknown message format ${JSON.stringify(message, null, 4)}`);
10778
10824
  }
10779
10825
  function assertHolochainSignal(signal) {
10780
10826
  if (typeof signal === "object" && signal !== null && Object.values(SignalType).some((type) => type in signal)) {
10781
10827
  return;
10782
10828
  }
10783
- throw new Error(`unknown signal format ${JSON.stringify(signal, null, 4)}`);
10829
+ throw new HolochainError("UnknownSignalFormat", `incoming signal has unknown signal format ${JSON.stringify(signal, null, 4)}`);
10784
10830
  }
10785
- const ERROR_TYPE = "error";
10786
- const DEFAULT_TIMEOUT = 6e4;
10787
- const requesterTransformer = (requester, tag, transform = identityTransformer) => async (req, timeout2) => {
10788
- const transformedInput = await transform.input(req);
10789
- const input = { type: { [tag]: null }, data: transformedInput };
10790
- const response = await requester(input, timeout2);
10791
- const output = transform.output(response.data);
10792
- return output;
10793
- };
10794
- const identity = (x) => x;
10795
- const identityTransformer = {
10796
- input: identity,
10797
- output: identity
10798
- };
10799
- class HolochainError extends Error {
10800
- constructor(name, message) {
10801
- super();
10802
- this.name = name;
10803
- this.message = message;
10804
- }
10805
- }
10806
- const catchError = (res) => {
10807
- if (ERROR_TYPE in res.type) {
10808
- const errorName = Object.keys(res.data.type)[0];
10809
- const error = new HolochainError(errorName, res.data.data);
10810
- return Promise.reject(error);
10811
- } else {
10812
- return Promise.resolve(res);
10813
- }
10814
- };
10815
- const promiseTimeout = (promise2, tag, ms) => {
10816
- let id;
10817
- const timeout2 = new Promise((_, reject) => {
10818
- id = setTimeout(() => reject(new Error(`Timed out in ${ms}ms: ${tag}`)), ms);
10819
- });
10820
- return new Promise((res, rej) => {
10821
- Promise.race([promise2, timeout2]).then((a) => {
10822
- clearTimeout(id);
10823
- return res(a);
10824
- }).catch((e) => {
10825
- return rej(e);
10826
- });
10827
- });
10828
- };
10829
10831
  class AppWebsocket extends Emittery {
10830
10832
  client;
10831
10833
  defaultTimeout;
@@ -10845,17 +10847,19 @@ class AppWebsocket extends Emittery {
10845
10847
  /**
10846
10848
  * Instance factory for creating AppWebsockets.
10847
10849
  *
10848
- * @param url - The `ws://` URL of the App API to connect to.
10849
- * @param defaultTimeout - Timeout to default to for all operations.
10850
+ * @param options - {@link (WebsocketConnectionOptions:interface)}
10850
10851
  * @returns A new instance of an AppWebsocket.
10851
10852
  */
10852
- static async connect(url2, defaultTimeout) {
10853
+ static async connect(options = {}) {
10853
10854
  const env = getLauncherEnvironment();
10854
10855
  if (env?.APP_INTERFACE_PORT) {
10855
- url2 = new URL(`ws://127.0.0.1:${env.APP_INTERFACE_PORT}`);
10856
+ options.url = new URL(`ws://127.0.0.1:${env.APP_INTERFACE_PORT}`);
10857
+ }
10858
+ if (!options.url) {
10859
+ throw new HolochainError("ConnectionUrlMissing", `unable to connect to Conductor API - no url provided and not in a launcher environment.`);
10856
10860
  }
10857
- const wsClient = await WsClient.connect(url2);
10858
- const appWebsocket = new AppWebsocket(wsClient, defaultTimeout, env?.INSTALLED_APP_ID);
10861
+ const wsClient = await WsClient.connect(options.url, options.wsClientOptions);
10862
+ const appWebsocket = new AppWebsocket(wsClient, options.defaultTimeout, env?.INSTALLED_APP_ID);
10859
10863
  wsClient.on("signal", (signal) => appWebsocket.emit("signal", signal));
10860
10864
  return appWebsocket;
10861
10865
  }
@@ -10936,7 +10940,7 @@ const appInfoTransform = (appWs) => ({
10936
10940
  const signZomeCall = async (request) => {
10937
10941
  const signingCredentialsForCell = getSigningCredentials(request.cell_id);
10938
10942
  if (!signingCredentialsForCell) {
10939
- throw new Error(`cannot sign zome call: no signing credentials have been authorized for cell [${encodeHashToBase64(request.cell_id[0])}, ${encodeHashToBase64(request.cell_id[1])}]`);
10943
+ throw new HolochainError("NoSigningCredentialsForCell", `no signing credentials have been authorized for cell [${encodeHashToBase64(request.cell_id[0])}, ${encodeHashToBase64(request.cell_id[1])}]`);
10940
10944
  }
10941
10945
  const unsignedZomeCallPayload = {
10942
10946
  cap_secret: signingCredentialsForCell.capSecret,
@@ -11803,8 +11807,10 @@ const menu = electron.Menu.buildFromTemplate([
11803
11807
  }
11804
11808
  ]);
11805
11809
  const rustUtils = require("@holochain/hc-spin-rust-utils");
11810
+ const cliPackageJsonPath = path.resolve(path.join(electron.app.getAppPath(), "../../package.json"));
11811
+ const cliPackageJson = require(cliPackageJsonPath);
11806
11812
  const cli = new commander.Command();
11807
- cli.name("hc-spin").description("CLI to run Holochain aps during development.").version(`0.300.0 (for holochain 0.3.x)`).argument(
11813
+ cli.name("hc-spin").description("CLI to run Holochain aps during development.").version(`${cliPackageJson.version} (built for holochain ${cliPackageJson.holochainVersion})`).argument(
11808
11814
  "<path>",
11809
11815
  "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"
11810
11816
  ).option(
@@ -12018,8 +12024,15 @@ electron.app.whenReady().then(async () => {
12018
12024
  for (var i = 0; i < CLI_OPTS.numAgents; i++) {
12019
12025
  const zomeCallSigner = await rustUtils.ZomeCallSigner.connect(lairUrls[i], "pass");
12020
12026
  const appPort = portsInfo[i].app_ports[0];
12021
- const appWs = await AppWebsocket.connect(new URL(`ws://127.0.0.1:${appPort}`));
12027
+ const appWs = await AppWebsocket.connect({
12028
+ url: new URL(`ws://127.0.0.1:${appPort}`),
12029
+ wsClientOptions: {
12030
+ origin: "hc-spin"
12031
+ }
12032
+ });
12022
12033
  const appInfo = await appWs.appInfo({ installed_app_id: CLI_OPTS.appId });
12034
+ if (!appInfo)
12035
+ throw new Error("AppInfo is null.");
12023
12036
  const happWindow = await createHappWindow(
12024
12037
  CLI_OPTS.uiSource,
12025
12038
  CLI_OPTS.happOrWebhappPath,
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@holochain/hc-spin",
3
- "version": "0.300.0",
3
+ "version": "0.300.2-dev.0",
4
+ "holochainVersion": "0.3.0-beta-dev.44",
4
5
  "description": "CLI to run Holochain aps during development.",
5
6
  "author": "matthme",
6
7
  "homepage": "https://developer.holochain.org",
@@ -34,7 +35,7 @@
34
35
  "dependencies": {
35
36
  "@electron-toolkit/preload": "^3.0.0",
36
37
  "@electron-toolkit/utils": "^3.0.0",
37
- "@holochain/client": "^0.17.0-dev.5",
38
+ "@holochain/client": "^0.17.0-dev.8",
38
39
  "@holochain/hc-spin-rust-utils": "^0.300.1",
39
40
  "@msgpack/msgpack": "^2.8.0",
40
41
  "commander": "11.1.0",
package/src/main/index.ts CHANGED
@@ -23,12 +23,15 @@ import { menu } from './menu';
23
23
 
24
24
  const rustUtils = require('@holochain/hc-spin-rust-utils');
25
25
 
26
+ const cliPackageJsonPath = path.resolve(path.join(app.getAppPath(), '../../package.json'));
27
+ const cliPackageJson = require(cliPackageJsonPath);
28
+
26
29
  const cli = new Command();
27
30
 
28
31
  cli
29
32
  .name('hc-spin')
30
33
  .description('CLI to run Holochain aps during development.')
31
- .version(`0.300.0 (for holochain 0.3.x)`)
34
+ .version(`${cliPackageJson.version} (built for holochain ${cliPackageJson.holochainVersion})`)
32
35
  .argument(
33
36
  '<path>',
34
37
  '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',
@@ -327,8 +330,14 @@ app.whenReady().then(async () => {
327
330
  const zomeCallSigner = await rustUtils.ZomeCallSigner.connect(lairUrls[i], 'pass');
328
331
 
329
332
  const appPort = portsInfo[i].app_ports[0];
330
- const appWs = await AppWebsocket.connect(new URL(`ws://127.0.0.1:${appPort}`));
333
+ const appWs = await AppWebsocket.connect({
334
+ url: new URL(`ws://127.0.0.1:${appPort}`),
335
+ wsClientOptions: {
336
+ origin: 'hc-spin',
337
+ },
338
+ });
331
339
  const appInfo = await appWs.appInfo({ installed_app_id: CLI_OPTS.appId });
340
+ if (!appInfo) throw new Error('AppInfo is null.');
332
341
  const happWindow = await createHappWindow(
333
342
  CLI_OPTS.uiSource,
334
343
  CLI_OPTS.happOrWebhappPath,
@@ -67,7 +67,7 @@ electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
67
67
  icon = nativeImage.createFromPath(iconPath);
68
68
  } else {
69
69
  try {
70
- const iconResponse = await net.fetch(`http://127.0.0.1:${uiSource.port}/icon.png`);
70
+ const iconResponse = await net.fetch(`http://localhost:${uiSource.port}/icon.png`);
71
71
  if (iconResponse.status === 404 && agentNum === 1) {
72
72
  console.warn(
73
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',
@@ -110,9 +110,9 @@ electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
110
110
  if (uiSource.type === 'port') {
111
111
  try {
112
112
  // Check whether dev server is responsive and index.html exists
113
- await net.fetch(`http://127.0.0.1:${uiSource.port}/index.html`);
113
+ await net.fetch(`http://localhost:${uiSource.port}/index.html`);
114
114
  } catch (e) {
115
- console.error(`No index.html file found at http://127.0.0.1:${uiSource.port}/index.html`, e);
115
+ console.error(`No index.html file found at http://localhost:${uiSource.port}/index.html`, e);
116
116
  if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
117
117
  happWindow.loadURL(process.env['ELECTRON_RENDERER_URL']);
118
118
  } else {
@@ -121,7 +121,7 @@ electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
121
121
  happWindow.show();
122
122
  return happWindow;
123
123
  }
124
- await happWindow.loadURL(`http://127.0.0.1:${uiSource.port}`);
124
+ await happWindow.loadURL(`http://localhost:${uiSource.port}`);
125
125
  } else if (uiSource.type === 'path') {
126
126
  try {
127
127
  await happWindow.loadURL(`webhapp://webhappwindow/index.html`);