@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.
- package/.github/workflows/test.yaml +8 -2
- package/CHANGELOG.md +16 -0
- package/dist/main/index.js +326 -312
- package/docs/DEVSETUP.md +12 -21
- package/electron.vite.config.ts +6 -0
- package/flake.lock +234 -0
- package/flake.nix +31 -0
- package/package.json +9 -8
- package/src/main/index.ts +30 -20
- package/src/main/menu.ts +23 -23
- package/src/main/validateArgs.ts +16 -1
- package/src/main/vite-env.d.ts +3 -0
- package/src/main/windows.ts +6 -5
- package/tsconfig.node.json +3 -2
package/dist/main/index.js
CHANGED
|
@@ -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 } =
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
11721
|
-
return await this.
|
|
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,
|
|
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 =
|
|
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(
|
|
11779
|
+
static connect(url, options) {
|
|
12006
11780
|
return new Promise((resolve, reject) => {
|
|
12007
|
-
const socket = new IsoWebSocket(
|
|
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 ${
|
|
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,
|
|
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(
|
|
11932
|
+
async reconnectWebsocket(url, token) {
|
|
12159
11933
|
return new Promise((resolve, reject) => {
|
|
12160
|
-
this.socket = new IsoWebSocket(
|
|
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 ${
|
|
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
|
|
12098
|
+
* Request peer meta info for a peer.
|
|
12331
12099
|
*/
|
|
12332
|
-
|
|
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
|
-
|
|
12424
|
-
|
|
12425
|
-
|
|
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
|
-
|
|
12436
|
-
|
|
12437
|
-
|
|
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
|
-
|
|
12441
|
-
|
|
12442
|
-
|
|
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
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
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
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
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
|
|
12457
|
-
const
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
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(`${
|
|
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.").
|
|
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 (
|
|
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"
|
|
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 (
|
|
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({
|