@holochain/hc-spin 0.600.0-dev.0 → 0.600.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";
@@ -3096,7 +2873,7 @@ const net = require$$3;
3096
2873
  const tls = require$$4;
3097
2874
  const { randomBytes, createHash: createHash$1 } = require$$1;
3098
2875
  const { Duplex: Duplex$2, Readable } = require$$0$2;
3099
- const { URL: URL$1 } = url;
2876
+ const { URL: URL$1 } = require$$7;
3100
2877
  const PerMessageDeflate$1 = permessageDeflate;
3101
2878
  const Receiver2 = receiver;
3102
2879
  const Sender2 = sender;
@@ -11982,12 +11759,12 @@ class WsClient extends Emittery {
11982
11759
  pendingRequests;
11983
11760
  index;
11984
11761
  authenticationToken;
11985
- constructor(socket, url2, options) {
11762
+ constructor(socket, url, options) {
11986
11763
  super();
11987
11764
  this.registerMessageListener(socket);
11988
11765
  this.registerCloseListener(socket);
11989
11766
  this.socket = socket;
11990
- this.url = url2;
11767
+ this.url = url;
11991
11768
  this.options = options;
11992
11769
  this.pendingRequests = {};
11993
11770
  this.index = 0;
@@ -11999,14 +11776,14 @@ class WsClient extends Emittery {
11999
11776
  * @param options - Options for the WsClient.
12000
11777
  * @returns An new instance of the WsClient.
12001
11778
  */
12002
- static connect(url2, options) {
11779
+ static connect(url, options) {
12003
11780
  return new Promise((resolve, reject) => {
12004
- const socket = new IsoWebSocket(url2, options);
11781
+ const socket = new IsoWebSocket(url, options);
12005
11782
  socket.addEventListener("error", (errorEvent) => {
12006
- 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}`));
12007
11784
  });
12008
11785
  socket.addEventListener("open", (_) => {
12009
- const client = new WsClient(socket, url2, options);
11786
+ const client = new WsClient(socket, url, options);
12010
11787
  resolve(client);
12011
11788
  }, { once: true });
12012
11789
  });
@@ -12152,12 +11929,12 @@ class WsClient extends Emittery {
12152
11929
  }
12153
11930
  }, { once: true });
12154
11931
  }
12155
- async reconnectWebsocket(url2, token) {
11932
+ async reconnectWebsocket(url, token) {
12156
11933
  return new Promise((resolve, reject) => {
12157
- this.socket = new IsoWebSocket(url2, this.options);
11934
+ this.socket = new IsoWebSocket(url, this.options);
12158
11935
  this.socket.addEventListener("error", (errorEvent) => {
12159
11936
  this.authenticationToken = void 0;
12160
- 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}`));
12161
11938
  }, { once: true });
12162
11939
  const invalidTokenCloseListener = (closeEvent) => {
12163
11940
  this.authenticationToken = void 0;
@@ -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
- );
12200
+ class Locked extends Error {
12201
+ constructor(port) {
12202
+ super(`${port} is locked`);
12428
12203
  }
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
- );
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
+ }
12434
12218
  }
12435
- if (!fs.existsSync(happOrWebhappPath)) {
12436
- throw new Error(
12437
- `Path to .happ or .webhapp file passed as argument does not exist: ${happOrWebhappPath}`
12438
- );
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);
12439
12235
  }
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
- );
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
+ }
12444
12244
  }
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
- );
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
+ }
12450
12261
  }
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
- );
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
+ }
12455
12282
  }
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
- };
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"} (built for holochain ${"0.6.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({