@holochain/hc-spin 0.500.3 → 0.600.0-rc.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.
@@ -1,16 +1,4 @@
1
1
  "use strict";
2
- const electron = require("electron");
3
- const fs = require("fs");
4
- const path = require("path");
5
- const node_crypto = require("node:crypto");
6
- const commander = require("commander");
7
- const contextMenu = require("electron-context-menu");
8
- const split = require("split");
9
- const childProcess = require("child_process");
10
- const url = require("url");
11
- const utils = require("@electron-toolkit/utils");
12
- const net$1 = require("node:net");
13
- const os = require("node:os");
14
2
  const msgpack = require("@msgpack/msgpack");
15
3
  const require$$0$3 = require("events");
16
4
  const require$$1$1 = require("https");
@@ -19,10 +7,22 @@ const require$$3 = require("net");
19
7
  const require$$4 = require("tls");
20
8
  const require$$1 = require("crypto");
21
9
  const require$$0$2 = require("stream");
10
+ const require$$7 = require("url");
22
11
  const require$$0 = require("zlib");
23
12
  const require$$0$1 = require("buffer");
13
+ const fs = require("fs");
14
+ const path = require("path");
24
15
  const require$$2 = require("os");
25
16
  const jsSha512 = require("js-sha512");
17
+ const childProcess = require("child_process");
18
+ const commander = require("commander");
19
+ const electron = require("electron");
20
+ const contextMenu = require("electron-context-menu");
21
+ const net$1 = require("node:net");
22
+ const os = require("node:os");
23
+ const node_crypto = require("node:crypto");
24
+ const split = require("split");
25
+ const utils = require("@electron-toolkit/utils");
26
26
  function _interopNamespaceDefault(e) {
27
27
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
28
28
  if (e) {
@@ -40,229 +40,6 @@ function _interopNamespaceDefault(e) {
40
40
  return Object.freeze(n);
41
41
  }
42
42
  const childProcess__namespace = /* @__PURE__ */ _interopNamespaceDefault(childProcess);
43
- const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
44
- const POOL_SIZE_MULTIPLIER = 128;
45
- let pool, poolOffset;
46
- function fillPool(bytes) {
47
- if (!pool || pool.length < bytes) {
48
- pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
49
- node_crypto.webcrypto.getRandomValues(pool);
50
- poolOffset = 0;
51
- } else if (poolOffset + bytes > pool.length) {
52
- node_crypto.webcrypto.getRandomValues(pool);
53
- poolOffset = 0;
54
- }
55
- poolOffset += bytes;
56
- }
57
- function nanoid(size = 21) {
58
- fillPool(size -= 0);
59
- let id = "";
60
- for (let i = poolOffset - size; i < poolOffset; i++) {
61
- id += urlAlphabet[pool[i] & 63];
62
- }
63
- return id;
64
- }
65
- async function createHappWindow(uiSource, appId, agentNum, appPort, appAuthToken, appDataRootDir) {
66
- if (!appPort) throw new Error("App port not defined.");
67
- const partition = `persist:${agentNum}:${appId}`;
68
- if (uiSource.type === "path") {
69
- const ses = electron.session.fromPartition(partition);
70
- ses.protocol.handle("webhapp", async (request) => {
71
- const uriWithoutProtocol = request.url.slice("webhapp://".length);
72
- const filePathComponents = uriWithoutProtocol.split("/").slice(1);
73
- const filePath = path.join(...filePathComponents);
74
- return electron.net.fetch(url.pathToFileURL(path.join(uiSource.path, filePath)).toString());
75
- });
76
- }
77
- let preloadScript = fs.readFileSync(path.join(__dirname, "../preload/index.js")).toString();
78
- preloadScript += `
79
- electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
80
- APP_INTERFACE_PORT: ${appPort},
81
- INSTALLED_APP_ID: "${appId}",
82
- APP_INTERFACE_TOKEN: [${appAuthToken}],
83
- });
84
- `;
85
- const preloadPath = path.join(appDataRootDir, `preload-${agentNum}-${appId}.js`);
86
- fs.writeFileSync(preloadPath, preloadScript);
87
- let icon;
88
- if (uiSource.type === "path") {
89
- const iconPath = path.join(uiSource.path, "icon.png");
90
- if (!fs.existsSync(iconPath) && agentNum === 1) {
91
- console.warn(
92
- "\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"
93
- );
94
- }
95
- icon = electron.nativeImage.createFromPath(iconPath);
96
- } else {
97
- try {
98
- const iconResponse = await electron.net.fetch(`http://localhost:${uiSource.port}/icon.png`);
99
- if (iconResponse.status === 404 && agentNum === 1) {
100
- console.warn(
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"
102
- );
103
- }
104
- const buffer = await iconResponse.arrayBuffer();
105
- icon = electron.nativeImage.createFromBuffer(Buffer.from(buffer));
106
- } catch (e) {
107
- console.error("Failed to get icon.png: ", e);
108
- }
109
- }
110
- return new electron.BrowserWindow({
111
- width: 1200,
112
- height: 800,
113
- show: false,
114
- icon,
115
- title: `Agent ${agentNum} - ${appId}`,
116
- webPreferences: {
117
- preload: preloadPath,
118
- partition
119
- }
120
- });
121
- }
122
- async function loadHappWindow(happWindow, uiSource, happOrWebhappPath, agentNum, openDevtools) {
123
- const [windowPositionX, windowPositionY] = happWindow.getPosition();
124
- const windowPositionXMoved = windowPositionX + agentNum * 20;
125
- const windowPositionYMoved = windowPositionY + agentNum * 20;
126
- happWindow.setPosition(windowPositionXMoved, windowPositionYMoved);
127
- happWindow.menuBarVisible = false;
128
- setLinkOpenHandlers(happWindow);
129
- happWindow.on("page-title-updated", (evt) => {
130
- evt.preventDefault();
131
- });
132
- if (openDevtools) happWindow.webContents.openDevTools();
133
- if (uiSource.type === "port") {
134
- try {
135
- await electron.net.fetch(`http://localhost:${uiSource.port}/index.html`);
136
- } catch (e) {
137
- console.error(`No index.html file found at http://localhost:${uiSource.port}/index.html`, e);
138
- if (utils.is.dev && process.env["ELECTRON_RENDERER_URL"]) {
139
- happWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
140
- } else {
141
- happWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
142
- }
143
- return;
144
- }
145
- await happWindow.loadURL(`http://localhost:${uiSource.port}`);
146
- } else if (uiSource.type === "path") {
147
- try {
148
- await happWindow.loadURL(`webhapp://webhappwindow/index.html`);
149
- } catch (e) {
150
- console.error("[ERROR] Failed to fetch index.html");
151
- if (utils.is.dev && process.env["ELECTRON_RENDERER_URL"]) {
152
- happWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
153
- } else {
154
- const notFoundPath = happOrWebhappPath.type === "webhapp" ? path.join(__dirname, "../renderer/indexNotFound1.html") : path.join(__dirname, "../renderer/indexNotFound2.html");
155
- happWindow.loadFile(notFoundPath);
156
- }
157
- return;
158
- }
159
- } else {
160
- throw new Error("Unsupported uiSource type: ", uiSource.type);
161
- }
162
- happWindow.show();
163
- }
164
- function setLinkOpenHandlers(browserWindow) {
165
- browserWindow.webContents.on("will-navigate", (e) => {
166
- if (e.url.startsWith("http://localhost") || e.url.startsWith("http://127.0.0.1")) {
167
- return;
168
- }
169
- if (e.url.startsWith("http://") || e.url.startsWith("https://") || e.url.startsWith("mailto://")) {
170
- e.preventDefault();
171
- electron.shell.openExternal(e.url);
172
- }
173
- });
174
- browserWindow.webContents.setWindowOpenHandler((details) => {
175
- if (details.url.startsWith("http://") || details.url.startsWith("https://")) {
176
- electron.shell.openExternal(details.url);
177
- }
178
- return { action: "deny" };
179
- });
180
- }
181
- class Locked extends Error {
182
- constructor(port) {
183
- super(`${port} is locked`);
184
- }
185
- }
186
- const lockedPorts = {
187
- old: /* @__PURE__ */ new Set(),
188
- young: /* @__PURE__ */ new Set()
189
- };
190
- const releaseOldLockedPortsIntervalMs = 1e3 * 15;
191
- let timeout;
192
- const getLocalHosts = () => {
193
- const interfaces = os.networkInterfaces();
194
- const results = /* @__PURE__ */ new Set([void 0, "0.0.0.0"]);
195
- for (const _interface of Object.values(interfaces)) {
196
- for (const config of _interface) {
197
- results.add(config.address);
198
- }
199
- }
200
- return results;
201
- };
202
- const checkAvailablePort = (options) => new Promise((resolve, reject) => {
203
- const server = net$1.createServer();
204
- server.unref();
205
- server.on("error", reject);
206
- server.listen(options, () => {
207
- const { port } = server.address();
208
- server.close(() => {
209
- resolve(port);
210
- });
211
- });
212
- });
213
- const getAvailablePort = async (options, hosts) => {
214
- if (options.host || options.port === 0) {
215
- return checkAvailablePort(options);
216
- }
217
- for (const host of hosts) {
218
- try {
219
- await checkAvailablePort({ port: options.port, host });
220
- } catch (error) {
221
- if (!["EADDRNOTAVAIL", "EINVAL"].includes(error.code)) {
222
- throw error;
223
- }
224
- }
225
- }
226
- return options.port;
227
- };
228
- const portCheckSequence = function* (ports) {
229
- yield 0;
230
- };
231
- async function getPorts(options) {
232
- let exclude = /* @__PURE__ */ new Set();
233
- if (timeout === void 0) {
234
- timeout = setTimeout(() => {
235
- timeout = void 0;
236
- lockedPorts.old = lockedPorts.young;
237
- lockedPorts.young = /* @__PURE__ */ new Set();
238
- }, releaseOldLockedPortsIntervalMs);
239
- if (timeout.unref) {
240
- timeout.unref();
241
- }
242
- }
243
- const hosts = getLocalHosts();
244
- for (const port of portCheckSequence()) {
245
- try {
246
- if (exclude.has(port)) {
247
- continue;
248
- }
249
- let availablePort = await getAvailablePort({ ...options, port }, hosts);
250
- while (lockedPorts.old.has(availablePort) || lockedPorts.young.has(availablePort)) {
251
- if (port !== 0) {
252
- throw new Locked(port);
253
- }
254
- availablePort = await getAvailablePort({ ...options, port }, hosts);
255
- }
256
- lockedPorts.young.add(availablePort);
257
- return availablePort;
258
- } catch (error) {
259
- if (!["EADDRINUSE", "EACCES"].includes(error.code) && !(error instanceof Locked)) {
260
- throw error;
261
- }
262
- }
263
- }
264
- throw new Error("No available ports found");
265
- }
266
43
  var CellType;
267
44
  (function(CellType2) {
268
45
  CellType2["Provisioned"] = "provisioned";
@@ -279,9 +56,6 @@ var AppStatusFilter;
279
56
  (function(AppStatusFilter2) {
280
57
  AppStatusFilter2["Enabled"] = "enabled";
281
58
  AppStatusFilter2["Disabled"] = "disabled";
282
- AppStatusFilter2["Running"] = "running";
283
- AppStatusFilter2["Stopped"] = "stopped";
284
- AppStatusFilter2["Paused"] = "paused";
285
59
  })(AppStatusFilter || (AppStatusFilter = {}));
286
60
  const __HC_LAUNCHER_ENV__ = "__HC_LAUNCHER_ENV__";
287
61
  const __HC_ZOME_CALL_SIGNER__ = "__HC_ZOME_CALL_SIGNER__";
@@ -3099,7 +2873,7 @@ const net = require$$3;
3099
2873
  const tls = require$$4;
3100
2874
  const { randomBytes, createHash: createHash$1 } = require$$1;
3101
2875
  const { Duplex: Duplex$2, Readable } = require$$0$2;
3102
- const { URL: URL$1 } = url;
2876
+ const { URL: URL$1 } = require$$7;
3103
2877
  const PerMessageDeflate$1 = permessageDeflate;
3104
2878
  const Receiver2 = receiver;
3105
2879
  const Sender2 = sender;
@@ -11617,7 +11391,7 @@ class AppWebsocket {
11617
11391
  cachedAppInfo;
11618
11392
  appInfoRequester;
11619
11393
  agentInfoRequester;
11620
- agentMetaInfoRequester;
11394
+ peerMetaInfoRequester;
11621
11395
  callZomeRequester;
11622
11396
  provideMemproofRequester;
11623
11397
  enableAppRequester;
@@ -11639,7 +11413,7 @@ class AppWebsocket {
11639
11413
  this.cachedAppInfo = appInfo;
11640
11414
  this.appInfoRequester = AppWebsocket.requester(this.client, "app_info", this.defaultTimeout);
11641
11415
  this.agentInfoRequester = AppWebsocket.requester(this.client, "agent_info", this.defaultTimeout);
11642
- this.agentMetaInfoRequester = AppWebsocket.requester(this.client, "agent_meta_info", this.defaultTimeout);
11416
+ this.peerMetaInfoRequester = AppWebsocket.requester(this.client, "peer_meta_info", this.defaultTimeout);
11643
11417
  this.callZomeRequester = AppWebsocket.requester(this.client, "call_zome", this.defaultTimeout, this.callZomeTransform);
11644
11418
  this.provideMemproofRequester = AppWebsocket.requester(this.client, "provide_memproofs", this.defaultTimeout);
11645
11419
  this.enableAppRequester = AppWebsocket.requester(this.client, "enable_app", this.defaultTimeout);
@@ -11711,14 +11485,14 @@ class AppWebsocket {
11711
11485
  return await this.agentInfoRequester(req, timeout2);
11712
11486
  }
11713
11487
  /**
11714
- * Request agent meta info for an agent by peer Url.
11488
+ * Request peer meta info for a peer by Url.
11715
11489
  *
11716
11490
  * @param req - The peer Url of the agent and an optional array of DNA hashes
11717
11491
  * @param timeout - A timeout to override the default.
11718
11492
  * @returns The meta info stored for this peer URL.
11719
11493
  */
11720
- async agentMetaInfo(req, timeout2) {
11721
- return await this.agentMetaInfoRequester(req, timeout2);
11494
+ async peerMetaInfo(req, timeout2) {
11495
+ return await this.peerMetaInfoRequester(req, timeout2);
11722
11496
  }
11723
11497
  /**
11724
11498
  * Request network stats.
@@ -11985,12 +11759,12 @@ class WsClient extends Emittery {
11985
11759
  pendingRequests;
11986
11760
  index;
11987
11761
  authenticationToken;
11988
- constructor(socket, url2, options) {
11762
+ constructor(socket, url, options) {
11989
11763
  super();
11990
11764
  this.registerMessageListener(socket);
11991
11765
  this.registerCloseListener(socket);
11992
11766
  this.socket = socket;
11993
- this.url = url2;
11767
+ this.url = url;
11994
11768
  this.options = options;
11995
11769
  this.pendingRequests = {};
11996
11770
  this.index = 0;
@@ -12002,14 +11776,14 @@ class WsClient extends Emittery {
12002
11776
  * @param options - Options for the WsClient.
12003
11777
  * @returns An new instance of the WsClient.
12004
11778
  */
12005
- static connect(url2, options) {
11779
+ static connect(url, options) {
12006
11780
  return new Promise((resolve, reject) => {
12007
- const socket = new IsoWebSocket(url2, options);
11781
+ const socket = new IsoWebSocket(url, options);
12008
11782
  socket.addEventListener("error", (errorEvent) => {
12009
- reject(new HolochainError("ConnectionError", `could not connect to Holochain Conductor API at ${url2} - ${errorEvent.error}`));
11783
+ reject(new HolochainError("ConnectionError", `could not connect to Holochain Conductor API at ${url} - ${errorEvent.error}`));
12010
11784
  });
12011
11785
  socket.addEventListener("open", (_) => {
12012
- const client = new WsClient(socket, url2, options);
11786
+ const client = new WsClient(socket, url, options);
12013
11787
  resolve(client);
12014
11788
  }, { once: true });
12015
11789
  });
@@ -12155,12 +11929,12 @@ class WsClient extends Emittery {
12155
11929
  }
12156
11930
  }, { once: true });
12157
11931
  }
12158
- async reconnectWebsocket(url2, token) {
11932
+ async reconnectWebsocket(url, token) {
12159
11933
  return new Promise((resolve, reject) => {
12160
- this.socket = new IsoWebSocket(url2, this.options);
11934
+ this.socket = new IsoWebSocket(url, this.options);
12161
11935
  this.socket.addEventListener("error", (errorEvent) => {
12162
11936
  this.authenticationToken = void 0;
12163
- reject(new HolochainError("ConnectionError", `could not connect to Holochain Conductor API at ${url2} - ${errorEvent.message}`));
11937
+ reject(new HolochainError("ConnectionError", `could not connect to Holochain Conductor API at ${url} - ${errorEvent.message}`));
12164
11938
  }, { once: true });
12165
11939
  const invalidTokenCloseListener = (closeEvent) => {
12166
11940
  this.authenticationToken = void 0;
@@ -12280,12 +12054,6 @@ class AdminWebsocket {
12280
12054
  * Generate a new agent pub key.
12281
12055
  */
12282
12056
  revokeAgentKey = this._requester("revoke_agent_key");
12283
- /**
12284
- * Register a DNA for later app installation.
12285
- *
12286
- * Stores the given DNA into the Holochain DNA database and returns the hash of it.
12287
- */
12288
- registerDna = this._requester("register_dna");
12289
12057
  /**
12290
12058
  * Get the DNA definition for the specified DNA hash.
12291
12059
  */
@@ -12327,9 +12095,9 @@ class AdminWebsocket {
12327
12095
  */
12328
12096
  addAgentInfo = this._requester("add_agent_info");
12329
12097
  /**
12330
- * Request agent meta info about an agent.
12098
+ * Request peer meta info for a peer.
12331
12099
  */
12332
- agentMetaInfo = this._requester("agent_meta_info");
12100
+ peerMetaInfo = this._requester("peer_meta_info");
12333
12101
  /**
12334
12102
  * Delete a disabled clone cell.
12335
12103
  */
@@ -12339,6 +12107,15 @@ class AdminWebsocket {
12339
12107
  * calls.
12340
12108
  */
12341
12109
  grantZomeCallCapability = this._requester("grant_zome_call_capability");
12110
+ /**
12111
+ * Revoke a zome call capability for an agent, which was previously granted
12112
+ * using {@link AdminWebsocket.grantZomeCallCapability}.
12113
+ */
12114
+ revokeZomeCallCapability = this._requester("revoke_zome_call_capability");
12115
+ /**
12116
+ * List all capability grants for all cells.
12117
+ */
12118
+ listCapabilityGrants = this._requester("list_capability_grants");
12342
12119
  storageInfo = this._requester("storage_info");
12343
12120
  issueAppAuthenticationToken = this._requester("issue_app_authentication_token");
12344
12121
  dumpNetworkStats = this._requester("dump_network_stats");
@@ -12420,53 +12197,112 @@ var ChainOpType;
12420
12197
  ChainOpType2["RegisterAddLink"] = "RegisterAddLink";
12421
12198
  ChainOpType2["RegisterRemoveLink"] = "RegisterRemoveLink";
12422
12199
  })(ChainOpType || (ChainOpType = {}));
12423
- function validateCliArgs(cliArgs, cliOpts, appDataRootDir) {
12424
- if (cliArgs.length !== 1) {
12425
- throw new Error(
12426
- `hc spin takes exactly one argument (the path to the .happ or .webhapp file) but got ${cliArgs.length} arguments: ${cliArgs}`
12427
- );
12428
- }
12429
- const happOrWebhappPath = cliArgs[0];
12430
- if (!happOrWebhappPath.endsWith(".happ") && !happOrWebhappPath.endsWith(".webhapp")) {
12431
- throw new Error(
12432
- `The path passed to hc spin must either be a .happ or a .webhapp file but got path '${happOrWebhappPath}'`
12433
- );
12200
+ class Locked extends Error {
12201
+ constructor(port) {
12202
+ super(`${port} is locked`);
12434
12203
  }
12435
- if (!fs.existsSync(happOrWebhappPath)) {
12436
- throw new Error(
12437
- `Path to .happ or .webhapp file passed as argument does not exist: ${happOrWebhappPath}`
12438
- );
12204
+ }
12205
+ const lockedPorts = {
12206
+ old: /* @__PURE__ */ new Set(),
12207
+ young: /* @__PURE__ */ new Set()
12208
+ };
12209
+ const releaseOldLockedPortsIntervalMs = 1e3 * 15;
12210
+ let timeout;
12211
+ const getLocalHosts = () => {
12212
+ const interfaces = os.networkInterfaces();
12213
+ const results = /* @__PURE__ */ new Set([void 0, "0.0.0.0"]);
12214
+ for (const _interface of Object.values(interfaces)) {
12215
+ for (const config of _interface) {
12216
+ results.add(config.address);
12217
+ }
12439
12218
  }
12440
- if (cliOpts.numAgents && typeof cliOpts.numAgents !== "number") {
12441
- throw new Error(
12442
- `The --num-agents (-n) option must be of type number but got: ${cliOpts.numAgents}`
12443
- );
12219
+ return results;
12220
+ };
12221
+ const checkAvailablePort = (options) => new Promise((resolve, reject) => {
12222
+ const server = net$1.createServer();
12223
+ server.unref();
12224
+ server.on("error", reject);
12225
+ server.listen(options, () => {
12226
+ const { port } = server.address();
12227
+ server.close(() => {
12228
+ resolve(port);
12229
+ });
12230
+ });
12231
+ });
12232
+ const getAvailablePort = async (options, hosts) => {
12233
+ if (options.host || options.port === 0) {
12234
+ return checkAvailablePort(options);
12444
12235
  }
12445
- const isHapp = happOrWebhappPath.endsWith(".happ");
12446
- if (isHapp && !cliOpts.uiPath && !cliOpts.uiPort) {
12447
- throw new Error(
12448
- "If you pass a .happ file as argument, you must also provide either the --ui-port or the --ui-path option pointing to the UI assets."
12449
- );
12236
+ for (const host of hosts) {
12237
+ try {
12238
+ await checkAvailablePort({ port: options.port, host });
12239
+ } catch (error) {
12240
+ if (!["EADDRNOTAVAIL", "EINVAL"].includes(error.code)) {
12241
+ throw error;
12242
+ }
12243
+ }
12450
12244
  }
12451
- if (cliOpts.uiPath && cliOpts.uiPort) {
12452
- throw new Error(
12453
- "Only one of --ui-port and --ui-path is allowed at the same time but got values for both."
12454
- );
12245
+ return options.port;
12246
+ };
12247
+ const portCheckSequence = function* (ports) {
12248
+ yield 0;
12249
+ };
12250
+ async function getPorts(options) {
12251
+ let exclude = /* @__PURE__ */ new Set();
12252
+ if (timeout === void 0) {
12253
+ timeout = setTimeout(() => {
12254
+ timeout = void 0;
12255
+ lockedPorts.old = lockedPorts.young;
12256
+ lockedPorts.young = /* @__PURE__ */ new Set();
12257
+ }, releaseOldLockedPortsIntervalMs);
12258
+ if (timeout.unref) {
12259
+ timeout.unref();
12260
+ }
12455
12261
  }
12456
- const appId = cliOpts.appId ? cliOpts.appId : path.parse(path.basename(cliArgs[0])).name;
12457
- const holochainPath = cliOpts.holochainPath;
12458
- const numAgents = cliOpts.numAgents ? cliOpts.numAgents : 2;
12459
- return {
12460
- appId,
12461
- holochainPath,
12462
- numAgents,
12463
- networkSeed: cliOpts.networkSeed,
12464
- uiSource: cliOpts.uiPath ? { type: "path", path: cliOpts.uiPath } : cliOpts.uiPort ? { type: "port", port: cliOpts.uiPort } : { type: "path", path: path.join(appDataRootDir, "apps", appId, "ui") },
12465
- singalingUrl: cliOpts.signalingUrl,
12466
- bootstrapUrl: cliOpts.bootstrapUrl,
12467
- happOrWebhappPath: isHapp ? { type: "happ", path: happOrWebhappPath } : { type: "webhapp", path: happOrWebhappPath },
12468
- openDevtools: cliOpts.openDevtools ? true : false
12469
- };
12262
+ const hosts = getLocalHosts();
12263
+ for (const port of portCheckSequence()) {
12264
+ try {
12265
+ if (exclude.has(port)) {
12266
+ continue;
12267
+ }
12268
+ let availablePort = await getAvailablePort({ ...options, port }, hosts);
12269
+ while (lockedPorts.old.has(availablePort) || lockedPorts.young.has(availablePort)) {
12270
+ if (port !== 0) {
12271
+ throw new Locked(port);
12272
+ }
12273
+ availablePort = await getAvailablePort({ ...options, port }, hosts);
12274
+ }
12275
+ lockedPorts.young.add(availablePort);
12276
+ return availablePort;
12277
+ } catch (error) {
12278
+ if (!["EADDRINUSE", "EACCES"].includes(error.code) && !(error instanceof Locked)) {
12279
+ throw error;
12280
+ }
12281
+ }
12282
+ }
12283
+ throw new Error("No available ports found");
12284
+ }
12285
+ const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
12286
+ const POOL_SIZE_MULTIPLIER = 128;
12287
+ let pool, poolOffset;
12288
+ function fillPool(bytes) {
12289
+ if (!pool || pool.length < bytes) {
12290
+ pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
12291
+ node_crypto.webcrypto.getRandomValues(pool);
12292
+ poolOffset = 0;
12293
+ } else if (poolOffset + bytes > pool.length) {
12294
+ node_crypto.webcrypto.getRandomValues(pool);
12295
+ poolOffset = 0;
12296
+ }
12297
+ poolOffset += bytes;
12298
+ }
12299
+ function nanoid(size = 21) {
12300
+ fillPool(size -= 0);
12301
+ let id = "";
12302
+ for (let i = poolOffset - size; i < poolOffset; i++) {
12303
+ id += urlAlphabet[pool[i] & 63];
12304
+ }
12305
+ return id;
12470
12306
  }
12471
12307
  const menu = electron.Menu.buildFromTemplate([
12472
12308
  {
@@ -12555,11 +12391,179 @@ const menu = electron.Menu.buildFromTemplate([
12555
12391
  ]
12556
12392
  }
12557
12393
  ]);
12394
+ function validateCliArgs(cliArgs, cliOpts, appDataRootDir) {
12395
+ if (cliArgs.length !== 1) {
12396
+ throw new Error(
12397
+ `hc spin takes exactly one argument (the path to the .happ or .webhapp file) but got ${cliArgs.length} arguments: ${cliArgs}`
12398
+ );
12399
+ }
12400
+ const happOrWebhappPath = cliArgs[0];
12401
+ if (!happOrWebhappPath.endsWith(".happ") && !happOrWebhappPath.endsWith(".webhapp")) {
12402
+ throw new Error(
12403
+ `The path passed to hc spin must either be a .happ or a .webhapp file but got path '${happOrWebhappPath}'`
12404
+ );
12405
+ }
12406
+ if (!fs.existsSync(happOrWebhappPath)) {
12407
+ throw new Error(
12408
+ `Path to .happ or .webhapp file passed as argument does not exist: ${happOrWebhappPath}`
12409
+ );
12410
+ }
12411
+ if (cliOpts.numAgents !== void 0 && (typeof cliOpts.numAgents !== "number" || Number.isNaN(cliOpts.numAgents))) {
12412
+ throw new Error(
12413
+ `The --num-agents (-n) option must be of type number but got: ${cliOpts.numAgents}`
12414
+ );
12415
+ }
12416
+ if (cliOpts.targetArcFactor !== void 0 && (typeof cliOpts.targetArcFactor !== "number" || Number.isNaN(cliOpts.targetArcFactor))) {
12417
+ throw new Error(
12418
+ `The --target-arc-factor (-t) option must be a valid number but got: ${cliOpts.targetArcFactor}`
12419
+ );
12420
+ }
12421
+ const isHapp = happOrWebhappPath.endsWith(".happ");
12422
+ if (isHapp && !cliOpts.uiPath && !cliOpts.uiPort) {
12423
+ throw new Error(
12424
+ "If you pass a .happ file as argument, you must also provide either the --ui-port or the --ui-path option pointing to the UI assets."
12425
+ );
12426
+ }
12427
+ if (cliOpts.uiPath && cliOpts.uiPort) {
12428
+ throw new Error(
12429
+ "Only one of --ui-port and --ui-path is allowed at the same time but got values for both."
12430
+ );
12431
+ }
12432
+ const appId = cliOpts.appId ? cliOpts.appId : path.parse(path.basename(cliArgs[0])).name;
12433
+ const holochainPath = cliOpts.holochainPath;
12434
+ const numAgents = cliOpts.numAgents ? cliOpts.numAgents : 2;
12435
+ return {
12436
+ appId,
12437
+ holochainPath,
12438
+ numAgents,
12439
+ networkSeed: cliOpts.networkSeed,
12440
+ targetArcFactor: cliOpts.targetArcFactor,
12441
+ uiSource: cliOpts.uiPath ? { type: "path", path: cliOpts.uiPath } : cliOpts.uiPort ? { type: "port", port: cliOpts.uiPort } : { type: "path", path: path.join(appDataRootDir, "apps", appId, "ui") },
12442
+ singalingUrl: cliOpts.signalingUrl,
12443
+ bootstrapUrl: cliOpts.bootstrapUrl,
12444
+ happOrWebhappPath: isHapp ? { type: "happ", path: happOrWebhappPath } : { type: "webhapp", path: happOrWebhappPath },
12445
+ openDevtools: cliOpts.openDevtools ? true : false
12446
+ };
12447
+ }
12448
+ async function createHappWindow(uiSource, appId, agentNum, appPort, appAuthToken, appDataRootDir) {
12449
+ if (!appPort) throw new Error("App port not defined.");
12450
+ const partition = `persist:${agentNum}:${appId}`;
12451
+ if (uiSource.type === "path") {
12452
+ const ses = electron.session.fromPartition(partition);
12453
+ ses.protocol.handle("webhapp", async (request) => {
12454
+ const uriWithoutProtocol = request.url.slice("webhapp://".length);
12455
+ const filePathComponents = uriWithoutProtocol.split("/").slice(1);
12456
+ const filePath = path.join(...filePathComponents);
12457
+ return electron.net.fetch(require$$7.pathToFileURL(path.join(uiSource.path, filePath)).toString());
12458
+ });
12459
+ }
12460
+ let preloadScript = fs.readFileSync(path.join(__dirname, "../preload/index.js")).toString();
12461
+ preloadScript += `
12462
+ electron.contextBridge.exposeInMainWorld("__HC_LAUNCHER_ENV__", {
12463
+ APP_INTERFACE_PORT: ${appPort},
12464
+ INSTALLED_APP_ID: "${appId}",
12465
+ APP_INTERFACE_TOKEN: [${appAuthToken}],
12466
+ });
12467
+ `;
12468
+ const preloadPath = path.join(appDataRootDir, `preload-${agentNum}-${appId}.js`);
12469
+ fs.writeFileSync(preloadPath, preloadScript);
12470
+ let icon;
12471
+ if (uiSource.type === "path") {
12472
+ const iconPath = path.join(uiSource.path, "icon.png");
12473
+ if (!fs.existsSync(iconPath) && agentNum === 1) {
12474
+ console.warn(
12475
+ "\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"
12476
+ );
12477
+ }
12478
+ icon = electron.nativeImage.createFromPath(iconPath);
12479
+ } else {
12480
+ try {
12481
+ const iconResponse = await electron.net.fetch(`http://localhost:${uiSource.port}/icon.png`);
12482
+ if (iconResponse.status === 404 && agentNum === 1) {
12483
+ console.warn(
12484
+ "\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"
12485
+ );
12486
+ }
12487
+ const buffer = await iconResponse.arrayBuffer();
12488
+ icon = electron.nativeImage.createFromBuffer(Buffer.from(buffer));
12489
+ } catch (e) {
12490
+ console.error("Failed to get icon.png: ", e);
12491
+ }
12492
+ }
12493
+ return new electron.BrowserWindow({
12494
+ width: 1200,
12495
+ height: 800,
12496
+ show: false,
12497
+ icon,
12498
+ title: `Agent ${agentNum} - ${appId}`,
12499
+ webPreferences: {
12500
+ preload: preloadPath,
12501
+ partition
12502
+ }
12503
+ });
12504
+ }
12505
+ async function loadHappWindow(happWindow, uiSource, happOrWebhappPath, agentNum, openDevtools) {
12506
+ const [windowPositionX, windowPositionY] = happWindow.getPosition();
12507
+ const windowPositionXMoved = windowPositionX + agentNum * 20;
12508
+ const windowPositionYMoved = windowPositionY + agentNum * 20;
12509
+ happWindow.setPosition(windowPositionXMoved, windowPositionYMoved);
12510
+ happWindow.menuBarVisible = false;
12511
+ setLinkOpenHandlers(happWindow);
12512
+ happWindow.on("page-title-updated", (evt) => {
12513
+ evt.preventDefault();
12514
+ });
12515
+ if (openDevtools) happWindow.webContents.openDevTools();
12516
+ if (uiSource.type === "port") {
12517
+ try {
12518
+ await electron.net.fetch(`http://localhost:${uiSource.port}/index.html`);
12519
+ } catch (e) {
12520
+ console.error(`No index.html file found at http://localhost:${uiSource.port}/index.html`, e);
12521
+ if (utils.is.dev && process.env["ELECTRON_RENDERER_URL"]) {
12522
+ happWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
12523
+ } else {
12524
+ happWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
12525
+ }
12526
+ return;
12527
+ }
12528
+ await happWindow.loadURL(`http://localhost:${uiSource.port}`);
12529
+ } else if (uiSource.type === "path") {
12530
+ try {
12531
+ await happWindow.loadURL(`webhapp://webhappwindow/index.html`);
12532
+ } catch (e) {
12533
+ console.error("[ERROR] Failed to fetch index.html");
12534
+ if (utils.is.dev && process.env["ELECTRON_RENDERER_URL"]) {
12535
+ happWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
12536
+ } else {
12537
+ const notFoundPath = happOrWebhappPath.type === "webhapp" ? path.join(__dirname, "../renderer/indexNotFound1.html") : path.join(__dirname, "../renderer/indexNotFound2.html");
12538
+ happWindow.loadFile(notFoundPath);
12539
+ }
12540
+ return;
12541
+ }
12542
+ } else {
12543
+ throw new Error(`Unsupported uiSource: ${JSON.stringify(uiSource)}`);
12544
+ }
12545
+ happWindow.show();
12546
+ }
12547
+ function setLinkOpenHandlers(browserWindow) {
12548
+ browserWindow.webContents.on("will-navigate", (e) => {
12549
+ if (e.url.startsWith("http://localhost") || e.url.startsWith("http://127.0.0.1")) {
12550
+ return;
12551
+ }
12552
+ if (e.url.startsWith("http://") || e.url.startsWith("https://") || e.url.startsWith("mailto://")) {
12553
+ e.preventDefault();
12554
+ electron.shell.openExternal(e.url);
12555
+ }
12556
+ });
12557
+ browserWindow.webContents.setWindowOpenHandler((details) => {
12558
+ if (details.url.startsWith("http://") || details.url.startsWith("https://")) {
12559
+ electron.shell.openExternal(details.url);
12560
+ }
12561
+ return { action: "deny" };
12562
+ });
12563
+ }
12558
12564
  const rustUtils = require("@holochain/hc-spin-rust-utils");
12559
- const cliPackageJsonPath = path.resolve(path.join(electron.app.getAppPath(), "../../package.json"));
12560
- const cliPackageJson = require(cliPackageJsonPath);
12561
12565
  const cli = new commander.Command();
12562
- cli.name("hc-spin").description("CLI to run Holochain apps during development.").version(`${cliPackageJson.version} (built for holochain ${cliPackageJson.holochainVersion})`).argument(
12566
+ cli.name("hc-spin").description("CLI to run Holochain apps during development.").version(`${"0.600.0-rc.0"} (built for holochain ${"0.6.0-rc.0"})`).argument(
12563
12567
  "<path>",
12564
12568
  "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"
12565
12569
  ).option(
@@ -12572,7 +12576,12 @@ cli.name("hc-spin").description("CLI to run Holochain apps during development.")
12572
12576
  new commander.Option("-n, --num-agents <number>", "How many agents to spawn the app for.").argParser(
12573
12577
  parseInt
12574
12578
  )
12575
- ).option("--network-seed <string>", "Install the app with a specific network seed.").option("--ui-path <path>", "Path to the folder containing the index.html of the webhapp's UI.").option(
12579
+ ).option("--network-seed <string>", "Install the app with a specific network seed.").addOption(
12580
+ new commander.Option(
12581
+ "-t, --target-arc-factor <number>",
12582
+ "Set the target arc factor for all conductors. In normal operation, leave this as the default 1. For leacher/zero-arc nodes that do not contribute to gossip, set to 0."
12583
+ ).argParser(parseInt)
12584
+ ).option("--ui-path <path>", "Path to the folder containing the index.html of the webhapp's UI.").option(
12576
12585
  "--ui-port <number>",
12577
12586
  "Port pointing to a localhost dev server that serves your UI assets."
12578
12587
  ).option(
@@ -12677,7 +12686,7 @@ async function startLocalServices() {
12677
12686
  });
12678
12687
  });
12679
12688
  }
12680
- async function spawnSandboxes(nAgents, happPath, bootStrapUrl, signalUrl, appId, networkSeed) {
12689
+ async function spawnSandboxes(nAgents, happPath, bootStrapUrl, signalUrl, appId, networkSeed, targetArcFactor) {
12681
12690
  const generateArgs = [
12682
12691
  "sandbox",
12683
12692
  "--piped",
@@ -12689,7 +12698,7 @@ async function spawnSandboxes(nAgents, happPath, bootStrapUrl, signalUrl, appId,
12689
12698
  "--run"
12690
12699
  ];
12691
12700
  let appPorts = "";
12692
- for (var i = 1; i <= nAgents; i++) {
12701
+ for (let i = 1; i <= nAgents; i++) {
12693
12702
  const appPort = await getPorts();
12694
12703
  appPorts += `${appPort},`;
12695
12704
  }
@@ -12698,7 +12707,11 @@ async function spawnSandboxes(nAgents, happPath, bootStrapUrl, signalUrl, appId,
12698
12707
  generateArgs.push("--network-seed");
12699
12708
  generateArgs.push(networkSeed);
12700
12709
  }
12701
- generateArgs.push(happPath, "network", "--bootstrap", bootStrapUrl, "webrtc", signalUrl);
12710
+ generateArgs.push(happPath, "network");
12711
+ if (targetArcFactor !== void 0) {
12712
+ generateArgs.push("--target-arc-factor", targetArcFactor.toString());
12713
+ }
12714
+ generateArgs.push("--bootstrap", bootStrapUrl, "webrtc", signalUrl);
12702
12715
  let readyConductors = 0;
12703
12716
  const portsInfo = {};
12704
12717
  const sandboxPaths = [];
@@ -12749,7 +12762,8 @@ electron.app.whenReady().then(async () => {
12749
12762
  CLI_OPTS.bootstrapUrl ? CLI_OPTS.bootstrapUrl : bootstrapUrl,
12750
12763
  CLI_OPTS.singalingUrl ? CLI_OPTS.singalingUrl : signalingUrl,
12751
12764
  CLI_OPTS.appId,
12752
- CLI_OPTS.networkSeed
12765
+ CLI_OPTS.networkSeed,
12766
+ CLI_OPTS.targetArcFactor
12753
12767
  );
12754
12768
  const lairUrls = [];
12755
12769
  sandboxPaths.forEach((sandbox) => {
@@ -12765,7 +12779,7 @@ electron.app.whenReady().then(async () => {
12765
12779
  }
12766
12780
  });
12767
12781
  SANDBOX_PROCESSES.push(sandboxHandle);
12768
- for (var i = 0; i < CLI_OPTS.numAgents; i++) {
12782
+ for (let i = 0; i < CLI_OPTS.numAgents; i++) {
12769
12783
  const zomeCallSigner = await rustUtils.ZomeCallSigner.connect(lairUrls[i], "pass");
12770
12784
  const adminPort = portsInfo[i].admin_port;
12771
12785
  const adminWs = await AdminWebsocket.connect({