@holochain/hc-spin 0.400.0-dev.3 → 0.400.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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
+
5
+ ## \[Unreleased\]
6
+
7
+ ### Added
8
+
9
+ ### Fixed
10
+
11
+ ### Changed
12
+
13
+ ### Removed
package/README.md CHANGED
@@ -4,31 +4,23 @@ CLI to run Holochain apps in development mode.
4
4
 
5
5
  ## Installation
6
6
 
7
- To install the latest version compatible with **holochain 0.1.x**:
8
-
9
- ⚠️ Requires `@holochain/client 0.12.6` or newer ⚠️
10
-
11
- ```
12
- npm install --save-dev @holochain/hc-spin@">=0.100.0 <0.200.0"
13
- ```
14
-
15
- To install the latest version compatible with **holochain 0.2.x**:
7
+ To install the latest version compatible with **holochain 0.3.x**:
16
8
 
17
- ⚠️ Requires `@holochain/client 0.16.2` or newer ⚠️
9
+ ⚠️ Requires `@holochain/client 0.17.0-dev.5` or newer ⚠️
18
10
 
19
11
  ```
20
- npm install --save-dev @holochain/hc-spin@">=0.200.0 <0.300.0"
12
+ npm install --save-dev @holochain/hc-spin@">=0.300.0 <0.400.0"
21
13
  ```
22
14
 
23
- To install the latest version compatible with **holochain 0.3.x**:
15
+ To install the latest version compatible with **holochain 0.4.x**:
24
16
 
25
- ⚠️ Requires `@holochain/client 0.17.0-dev.5` or newer ⚠️
17
+ ⚠️ Requires `@holochain/client 0.18.0-rc.1` or newer ⚠️
26
18
 
27
19
  ```
28
- npm install --save-dev @holochain/hc-spin@">=0.300.0 <0.400.0"
20
+ npm install --save-dev @holochain/hc-spin@">=0.400.0 <0.500.0"
29
21
  ```
30
22
 
31
- ## Usage (holochain 0.2)
23
+ ## Usage (holochain 0.4)
32
24
 
33
25
  ```
34
26
  Usage: hc-spin [options] <path>
@@ -52,6 +44,7 @@ Options:
52
44
  --ui-port <number> Port pointing to a localhost dev server that serves your UI assets.
53
45
  --signaling-url <url> Url of the signaling server to use. By default, hc spin spins up a local development signaling server for you
54
46
  but this argument allows you to specify a custom one.
47
+ --open-devtools Automatically open the devtools on startup.
55
48
  -h, --help display help for command
56
49
  ```
57
50
 
@@ -786,18 +786,18 @@ var CapAccessType;
786
786
  CapAccessType2["Transferable"] = "Transferable";
787
787
  CapAccessType2["Assigned"] = "Assigned";
788
788
  })(CapAccessType || (CapAccessType = {}));
789
- var DhtOpType;
790
- (function(DhtOpType2) {
791
- DhtOpType2["StoreRecord"] = "StoreRecord";
792
- DhtOpType2["StoreEntry"] = "StoreEntry";
793
- DhtOpType2["RegisterAgentActivity"] = "RegisterAgentActivity";
794
- DhtOpType2["RegisterUpdatedContent"] = "RegisterUpdatedContent";
795
- DhtOpType2["RegisterUpdatedRecord"] = "RegisterUpdatedRecord";
796
- DhtOpType2["RegisterDeletedBy"] = "RegisterDeletedBy";
797
- DhtOpType2["RegisterDeletedEntryAction"] = "RegisterDeletedEntryAction";
798
- DhtOpType2["RegisterAddLink"] = "RegisterAddLink";
799
- DhtOpType2["RegisterRemoveLink"] = "RegisterRemoveLink";
800
- })(DhtOpType || (DhtOpType = {}));
789
+ var ChainOpType;
790
+ (function(ChainOpType2) {
791
+ ChainOpType2["StoreRecord"] = "StoreRecord";
792
+ ChainOpType2["StoreEntry"] = "StoreEntry";
793
+ ChainOpType2["RegisterAgentActivity"] = "RegisterAgentActivity";
794
+ ChainOpType2["RegisterUpdatedContent"] = "RegisterUpdatedContent";
795
+ ChainOpType2["RegisterUpdatedRecord"] = "RegisterUpdatedRecord";
796
+ ChainOpType2["RegisterDeletedBy"] = "RegisterDeletedBy";
797
+ ChainOpType2["RegisterDeletedEntryAction"] = "RegisterDeletedEntryAction";
798
+ ChainOpType2["RegisterAddLink"] = "RegisterAddLink";
799
+ ChainOpType2["RegisterRemoveLink"] = "RegisterRemoveLink";
800
+ })(ChainOpType || (ChainOpType = {}));
801
801
  const anyMap = /* @__PURE__ */ new WeakMap();
802
802
  const eventsMap = /* @__PURE__ */ new WeakMap();
803
803
  const producersMap = /* @__PURE__ */ new WeakMap();
@@ -5026,6 +5026,7 @@ const promiseTimeout = (promise2, tag, ms) => {
5026
5026
  clearTimeout(id);
5027
5027
  return res(a);
5028
5028
  }).catch((e) => {
5029
+ clearTimeout(id);
5029
5030
  return rej(e);
5030
5031
  }));
5031
5032
  };
@@ -12333,23 +12334,33 @@ b2wasm.ready(function(err) {
12333
12334
  class AppWebsocket {
12334
12335
  client;
12335
12336
  myPubKey;
12337
+ installedAppId;
12336
12338
  defaultTimeout;
12337
12339
  emitter;
12340
+ callZomeTransform;
12341
+ appAuthenticationToken;
12338
12342
  cachedAppInfo;
12339
12343
  appInfoRequester;
12340
12344
  callZomeRequester;
12345
+ provideMemproofRequester;
12346
+ enableAppRequester;
12341
12347
  createCloneCellRequester;
12342
12348
  enableCloneCellRequester;
12343
12349
  disableCloneCellRequester;
12344
12350
  networkInfoRequester;
12345
- constructor(client, appInfo, defaultTimeout) {
12351
+ constructor(client, appInfo, token, callZomeTransform, defaultTimeout) {
12346
12352
  this.client = client;
12347
12353
  this.myPubKey = appInfo.agent_pub_key;
12354
+ this.installedAppId = appInfo.installed_app_id;
12348
12355
  this.defaultTimeout = defaultTimeout ?? DEFAULT_TIMEOUT;
12356
+ this.callZomeTransform = callZomeTransform ?? defaultCallZomeTransform;
12357
+ this.appAuthenticationToken = token;
12349
12358
  this.emitter = new Emittery();
12350
12359
  this.cachedAppInfo = appInfo;
12351
12360
  this.appInfoRequester = AppWebsocket.requester(this.client, "app_info", this.defaultTimeout);
12352
- this.callZomeRequester = AppWebsocket.requester(this.client, "call_zome", this.defaultTimeout, callZomeTransform);
12361
+ this.callZomeRequester = AppWebsocket.requester(this.client, "call_zome", this.defaultTimeout, this.callZomeTransform);
12362
+ this.provideMemproofRequester = AppWebsocket.requester(this.client, "provide_memproofs", this.defaultTimeout);
12363
+ this.enableAppRequester = AppWebsocket.requester(this.client, "enable_app", this.defaultTimeout);
12353
12364
  this.createCloneCellRequester = AppWebsocket.requester(this.client, "create_clone_cell", this.defaultTimeout);
12354
12365
  this.enableCloneCellRequester = AppWebsocket.requester(this.client, "enable_clone_cell", this.defaultTimeout);
12355
12366
  this.disableCloneCellRequester = AppWebsocket.requester(this.client, "disable_clone_cell", this.defaultTimeout);
@@ -12361,9 +12372,7 @@ class AppWebsocket {
12361
12372
  }
12362
12373
  });
12363
12374
  this.client.on("signal", (signal) => {
12364
- if (this.containsCell(signal.cell_id)) {
12365
- this.emitter.emit("signal", signal).catch(console.error);
12366
- }
12375
+ this.emitter.emit("signal", signal).catch(console.error);
12367
12376
  });
12368
12377
  }
12369
12378
  /**
@@ -12382,19 +12391,15 @@ class AppWebsocket {
12382
12391
  throw new HolochainError("ConnectionUrlMissing", `unable to connect to Conductor API - no url provided and not in a launcher environment.`);
12383
12392
  }
12384
12393
  const client = await WsClient.connect(options.url, options.wsClientOptions);
12385
- if (env?.APP_INTERFACE_TOKEN) {
12386
- await client.authenticate({ token: env.APP_INTERFACE_TOKEN });
12387
- } else {
12388
- if (!options.token) {
12389
- throw new HolochainError("AppAuthenticationTokenMissing", `unable to connect to Conductor API - no app authentication token provided.`);
12390
- }
12391
- await client.authenticate({ token: options.token });
12392
- }
12393
- const appInfo = await this.requester(client, "app_info", DEFAULT_TIMEOUT)(null);
12394
+ const token = options.token ?? env?.APP_INTERFACE_TOKEN;
12395
+ if (!token)
12396
+ throw new HolochainError("AppAuthenticationTokenMissing", `unable to connect to Conductor API - no app authentication token provided.`);
12397
+ await client.authenticate({ token });
12398
+ const appInfo = await AppWebsocket.requester(client, "app_info", DEFAULT_TIMEOUT)(null);
12394
12399
  if (!appInfo) {
12395
12400
  throw new HolochainError("AppNotFound", `The app your connection token was issued for was not found. The app needs to be installed and enabled.`);
12396
12401
  }
12397
- return new AppWebsocket(client, appInfo, options.defaultTimeout);
12402
+ return new AppWebsocket(client, appInfo, token, options.callZomeTransform, options.defaultTimeout);
12398
12403
  }
12399
12404
  /**
12400
12405
  * Request the app's info, including all cell infos.
@@ -12410,6 +12415,21 @@ class AppWebsocket {
12410
12415
  this.cachedAppInfo = appInfo;
12411
12416
  return appInfo;
12412
12417
  }
12418
+ /**
12419
+ * Provide membrane proofs for the app.
12420
+ *
12421
+ * @param memproofs - A map of {@link MembraneProof}s.
12422
+ */
12423
+ async provideMemproofs(memproofs) {
12424
+ await this.provideMemproofRequester(memproofs);
12425
+ }
12426
+ /**
12427
+ * Enablie an app only if the app is in the `AppStatus::Disabled(DisabledAppReason::NotStartedAfterProvidingMemproofs)`
12428
+ * state. Attempting to enable the app from other states (other than Running) will fail.
12429
+ */
12430
+ async enableApp() {
12431
+ await this.enableAppRequester();
12432
+ }
12413
12433
  /**
12414
12434
  * Get a cell id by its role name or clone id.
12415
12435
  *
@@ -12524,23 +12544,8 @@ class AppWebsocket {
12524
12544
  static requester(client, tag, defaultTimeout, transformer) {
12525
12545
  return requesterTransformer((req, timeout2) => promiseTimeout(client.request(req), tag, timeout2 || defaultTimeout).then(catchError), tag, transformer);
12526
12546
  }
12527
- containsCell(cellId) {
12528
- const appInfo = this.cachedAppInfo;
12529
- if (!appInfo) {
12530
- return false;
12531
- }
12532
- for (const roleName of Object.keys(appInfo.cell_info)) {
12533
- for (const cellInfo of appInfo.cell_info[roleName]) {
12534
- const currentCellId = CellType.Provisioned in cellInfo ? cellInfo[CellType.Provisioned].cell_id : CellType.Cloned in cellInfo ? cellInfo[CellType.Cloned].cell_id : void 0;
12535
- if (currentCellId && isSameCell(currentCellId, cellId)) {
12536
- return true;
12537
- }
12538
- }
12539
- }
12540
- return false;
12541
- }
12542
12547
  }
12543
- const callZomeTransform = {
12548
+ const defaultCallZomeTransform = {
12544
12549
  input: async (request) => {
12545
12550
  if ("signature" in request) {
12546
12551
  return request;
@@ -12554,7 +12559,6 @@ const callZomeTransform = {
12554
12559
  },
12555
12560
  output: (response) => msgpack.decode(response)
12556
12561
  };
12557
- const isSameCell = (cellId1, cellId2) => cellId1[0].every((byte, index) => byte === cellId2[0][index]) && cellId1[1].every((byte, index) => byte === cellId2[1][index]);
12558
12562
  const signZomeCall = async (request) => {
12559
12563
  const signingCredentialsForCell = getSigningCredentials(request.cell_id);
12560
12564
  if (!signingCredentialsForCell) {
@@ -12586,62 +12590,16 @@ class WsClient extends Emittery {
12586
12590
  options;
12587
12591
  pendingRequests;
12588
12592
  index;
12593
+ authenticationToken;
12589
12594
  constructor(socket, url2, options) {
12590
12595
  super();
12596
+ this.registerMessageListener(socket);
12597
+ this.registerCloseListener(socket);
12591
12598
  this.socket = socket;
12592
12599
  this.url = url2;
12593
12600
  this.options = options || {};
12594
12601
  this.pendingRequests = {};
12595
12602
  this.index = 0;
12596
- this.setupSocket();
12597
- }
12598
- setupSocket() {
12599
- this.socket.onmessage = async (serializedMessage) => {
12600
- let deserializedData;
12601
- if (globalThis.window && serializedMessage.data instanceof globalThis.window.Blob) {
12602
- deserializedData = await serializedMessage.data.arrayBuffer();
12603
- } else {
12604
- if (typeof Buffer !== "undefined" && Buffer.isBuffer(serializedMessage.data)) {
12605
- deserializedData = serializedMessage.data;
12606
- } else {
12607
- throw new HolochainError("UnknownMessageFormat", `incoming message has unknown message format - ${deserializedData}`);
12608
- }
12609
- }
12610
- const message = msgpack.decode(deserializedData);
12611
- assertHolochainMessage(message);
12612
- if (message.type === "signal") {
12613
- if (message.data === null) {
12614
- throw new HolochainError("UnknownSignalFormat", "incoming signal has no data");
12615
- }
12616
- const deserializedSignal = msgpack.decode(message.data);
12617
- assertHolochainSignal(deserializedSignal);
12618
- if (SignalType.System in deserializedSignal) {
12619
- return;
12620
- }
12621
- const encodedAppSignal = deserializedSignal[SignalType.App];
12622
- const payload = msgpack.decode(encodedAppSignal.signal);
12623
- const signal = {
12624
- cell_id: encodedAppSignal.cell_id,
12625
- zome_name: encodedAppSignal.zome_name,
12626
- payload
12627
- };
12628
- this.emit("signal", signal);
12629
- } else if (message.type === "response") {
12630
- this.handleResponse(message);
12631
- } else {
12632
- throw new HolochainError("UnknownMessageType", `incoming message has unknown type - ${message.type}`);
12633
- }
12634
- };
12635
- this.socket.onclose = (event) => {
12636
- const pendingRequestIds = Object.keys(this.pendingRequests).map((id) => parseInt(id));
12637
- if (pendingRequestIds.length) {
12638
- pendingRequestIds.forEach((id) => {
12639
- const error = new HolochainError("ClientClosedWithPendingRequests", `client closed with pending requests - close event code: ${event.code}, request id: ${id}`);
12640
- this.pendingRequests[id].reject(error);
12641
- delete this.pendingRequests[id];
12642
- });
12643
- }
12644
- };
12645
12603
  }
12646
12604
  /**
12647
12605
  * Instance factory for creating WsClients.
@@ -12653,13 +12611,13 @@ class WsClient extends Emittery {
12653
12611
  static connect(url2, options) {
12654
12612
  return new Promise((resolve, reject) => {
12655
12613
  const socket = new IsoWebSocket(url2, options);
12656
- socket.onerror = (errorEvent) => {
12614
+ socket.addEventListener("error", (errorEvent) => {
12657
12615
  reject(new HolochainError("ConnectionError", `could not connect to Holochain Conductor API at ${url2} - ${errorEvent.error}`));
12658
- };
12659
- socket.onopen = () => {
12616
+ });
12617
+ socket.addEventListener("open", (_) => {
12660
12618
  const client = new WsClient(socket, url2, options);
12661
12619
  resolve(client);
12662
- };
12620
+ }, { once: true });
12663
12621
  });
12664
12622
  }
12665
12623
  /**
@@ -12682,15 +12640,34 @@ class WsClient extends Emittery {
12682
12640
  * @param request - The authentication request, containing an app authentication token.
12683
12641
  */
12684
12642
  async authenticate(request) {
12685
- return this.exchange(request, (request2, resolve) => {
12643
+ this.authenticationToken = request.token;
12644
+ return this.exchange(request, (request2, resolve, reject) => {
12645
+ const invalidTokenCloseListener = (closeEvent) => {
12646
+ this.authenticationToken = void 0;
12647
+ reject(new HolochainError("InvalidTokenError", `could not connect to ${this.url} due to an invalid app authentication token - close code ${closeEvent.code}`));
12648
+ };
12649
+ this.socket.addEventListener("close", invalidTokenCloseListener, {
12650
+ once: true
12651
+ });
12686
12652
  const encodedMsg = msgpack.encode({
12687
12653
  type: "authenticate",
12688
12654
  data: msgpack.encode(request2)
12689
12655
  });
12690
12656
  this.socket.send(encodedMsg);
12691
- resolve(null);
12657
+ setTimeout(() => {
12658
+ this.socket.removeEventListener("close", invalidTokenCloseListener);
12659
+ resolve(null);
12660
+ }, 10);
12692
12661
  });
12693
12662
  }
12663
+ /**
12664
+ * Close the websocket connection.
12665
+ */
12666
+ close(code = 1e3) {
12667
+ const closedPromise = new Promise((resolve) => this.socket.addEventListener("close", (closeEvent) => resolve(closeEvent), { once: true }));
12668
+ this.socket.close(code);
12669
+ return closedPromise;
12670
+ }
12694
12671
  /**
12695
12672
  * Send requests to the connected websocket.
12696
12673
  *
@@ -12700,14 +12677,20 @@ class WsClient extends Emittery {
12700
12677
  async request(request) {
12701
12678
  return this.exchange(request, this.sendMessage.bind(this));
12702
12679
  }
12703
- exchange(request, sendHandler) {
12680
+ async exchange(request, sendHandler) {
12704
12681
  if (this.socket.readyState === this.socket.OPEN) {
12705
12682
  const promise2 = new Promise((resolve, reject) => {
12706
12683
  sendHandler(request, resolve, reject);
12707
12684
  });
12708
12685
  return promise2;
12686
+ } else if (this.url && this.authenticationToken) {
12687
+ await this.reconnectWebsocket(this.url, this.authenticationToken);
12688
+ this.registerMessageListener(this.socket);
12689
+ this.registerCloseListener(this.socket);
12690
+ const promise2 = new Promise((resolve, reject) => sendHandler(request, resolve, reject));
12691
+ return promise2;
12709
12692
  } else {
12710
- return Promise.reject(new Error("Socket is not open"));
12693
+ return Promise.reject(new HolochainError("WebsocketClosedError", "Websocket is not open"));
12711
12694
  }
12712
12695
  }
12713
12696
  sendMessage(request, resolve, reject) {
@@ -12721,6 +12704,86 @@ class WsClient extends Emittery {
12721
12704
  this.pendingRequests[id] = { resolve, reject };
12722
12705
  this.index += 1;
12723
12706
  }
12707
+ registerMessageListener(socket) {
12708
+ socket.onmessage = async (serializedMessage) => {
12709
+ let deserializedData;
12710
+ if (globalThis.window && serializedMessage.data instanceof globalThis.window.Blob) {
12711
+ deserializedData = await serializedMessage.data.arrayBuffer();
12712
+ } else {
12713
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(serializedMessage.data)) {
12714
+ deserializedData = serializedMessage.data;
12715
+ } else {
12716
+ throw new HolochainError("UnknownMessageFormat", `incoming message has unknown message format - ${deserializedData}`);
12717
+ }
12718
+ }
12719
+ const message = msgpack.decode(deserializedData);
12720
+ assertHolochainMessage(message);
12721
+ if (message.type === "signal") {
12722
+ if (message.data === null) {
12723
+ throw new HolochainError("UnknownSignalFormat", "incoming signal has no data");
12724
+ }
12725
+ const deserializedSignal = msgpack.decode(message.data);
12726
+ assertHolochainSignal(deserializedSignal);
12727
+ if (SignalType.System in deserializedSignal) {
12728
+ this.emit("signal", {
12729
+ System: deserializedSignal[SignalType.System]
12730
+ });
12731
+ } else {
12732
+ const encodedAppSignal = deserializedSignal[SignalType.App];
12733
+ const payload = msgpack.decode(encodedAppSignal.signal);
12734
+ const signal = {
12735
+ cell_id: encodedAppSignal.cell_id,
12736
+ zome_name: encodedAppSignal.zome_name,
12737
+ payload
12738
+ };
12739
+ this.emit("signal", { App: signal });
12740
+ }
12741
+ } else if (message.type === "response") {
12742
+ this.handleResponse(message);
12743
+ } else {
12744
+ throw new HolochainError("UnknownMessageType", `incoming message has unknown type - ${message.type}`);
12745
+ }
12746
+ };
12747
+ }
12748
+ registerCloseListener(socket) {
12749
+ socket.addEventListener("close", (closeEvent) => {
12750
+ const pendingRequestIds = Object.keys(this.pendingRequests).map((id) => parseInt(id));
12751
+ if (pendingRequestIds.length) {
12752
+ pendingRequestIds.forEach((id) => {
12753
+ const error = new HolochainError("ClientClosedWithPendingRequests", `client closed with pending requests - close event code: ${closeEvent.code}, request id: ${id}`);
12754
+ this.pendingRequests[id].reject(error);
12755
+ delete this.pendingRequests[id];
12756
+ });
12757
+ }
12758
+ }, { once: true });
12759
+ }
12760
+ async reconnectWebsocket(url2, token) {
12761
+ return new Promise((resolve, reject) => {
12762
+ this.socket = new IsoWebSocket(url2, this.options);
12763
+ this.socket.addEventListener("error", (errorEvent) => {
12764
+ this.authenticationToken = void 0;
12765
+ reject(new HolochainError("ConnectionError", `could not connect to Holochain Conductor API at ${url2} - ${errorEvent.message}`));
12766
+ }, { once: true });
12767
+ const invalidTokenCloseListener = (closeEvent) => {
12768
+ this.authenticationToken = void 0;
12769
+ reject(new HolochainError("InvalidTokenError", `could not connect to ${this.url} due to an invalid app authentication token - close code ${closeEvent.code}`));
12770
+ };
12771
+ this.socket.addEventListener("close", invalidTokenCloseListener, {
12772
+ once: true
12773
+ });
12774
+ this.socket.addEventListener("open", async (_) => {
12775
+ const encodedMsg = msgpack.encode({
12776
+ type: "authenticate",
12777
+ data: msgpack.encode({ token })
12778
+ });
12779
+ this.socket.send(encodedMsg);
12780
+ setTimeout(() => {
12781
+ this.socket.removeEventListener("close", invalidTokenCloseListener);
12782
+ resolve();
12783
+ }, 10);
12784
+ }, { once: true });
12785
+ });
12786
+ }
12724
12787
  handleResponse(msg) {
12725
12788
  const id = msg.id;
12726
12789
  if (this.pendingRequests[id]) {
@@ -12734,22 +12797,6 @@ class WsClient extends Emittery {
12734
12797
  console.error(`got response with no matching request. id = ${id} msg = ${msg}`);
12735
12798
  }
12736
12799
  }
12737
- /**
12738
- * Close the websocket connection.
12739
- */
12740
- close(code = 1e3) {
12741
- const closedPromise = new Promise(
12742
- (resolve) => (
12743
- // for an unknown reason "addEventListener" is seen as a non-callable
12744
- // property and gives a ts2349 error
12745
- // type assertion as workaround
12746
- this.socket.addEventListener("close", (event) => resolve(event))
12747
- )
12748
- // }
12749
- );
12750
- this.socket.close(code);
12751
- return closedPromise;
12752
- }
12753
12800
  }
12754
12801
  function assertHolochainMessage(message) {
12755
12802
  if (typeof message === "object" && message !== null && "type" in message && "data" in message) {
@@ -12821,6 +12868,10 @@ class AdminWebsocket {
12821
12868
  * Generate a new agent pub key.
12822
12869
  */
12823
12870
  generateAgentPubKey = this._requester("generate_agent_pub_key");
12871
+ /**
12872
+ * Generate a new agent pub key.
12873
+ */
12874
+ revokeAgentKey = this._requester("revoke_agent_key");
12824
12875
  /**
12825
12876
  * Register a DNA for later app installation.
12826
12877
  *
@@ -12831,6 +12882,9 @@ class AdminWebsocket {
12831
12882
  * Get the DNA definition for the specified DNA hash.
12832
12883
  */
12833
12884
  getDnaDefinition = this._requester("get_dna_definition");
12885
+ /// Find installed cells which use a DNA that's forward-compatible with the given DNA hash.
12886
+ /// Namely, this finds cells with DNAs whose manifest lists the given DNA hash in its `lineage` field.
12887
+ getCompatibleCells = this._requester("get_compatible_cells");
12834
12888
  /**
12835
12889
  * Uninstall the specified app from Holochain.
12836
12890
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@holochain/hc-spin",
3
- "version": "0.400.0-dev.3",
4
- "holochainVersion": "0.4.0-dev.16",
3
+ "version": "0.400.0",
4
+ "holochainVersion": "0.4.0-rc.2",
5
5
  "description": "CLI to run Holochain aps during development.",
6
6
  "author": "matthme",
7
7
  "homepage": "https://developer.holochain.org",
@@ -35,8 +35,8 @@
35
35
  "dependencies": {
36
36
  "@electron-toolkit/preload": "^3.0.0",
37
37
  "@electron-toolkit/utils": "^3.0.0",
38
- "@holochain/client": "0.18.0-dev.0",
39
- "@holochain/hc-spin-rust-utils": "^0.300.1",
38
+ "@holochain/client": "0.18.0",
39
+ "@holochain/hc-spin-rust-utils": "0.400.0",
40
40
  "@msgpack/msgpack": "^2.8.0",
41
41
  "bufferutil": "4.0.8",
42
42
  "commander": "11.1.0",