@holochain/client 0.17.0-dev.7 → 0.17.0-dev.8

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.
@@ -7,6 +7,11 @@ import { DisableCloneCellRequest } from "../index.js";
7
7
  */
8
8
  export type AttachAppInterfaceRequest = {
9
9
  port?: number;
10
+ /**
11
+ * Comma separated list of origins, or `*` to allow any origin.
12
+ * For example: `http://localhost:3000,http://localhost:3001`
13
+ */
14
+ allowed_origins: string;
10
15
  };
11
16
  /**
12
17
  * @public
@@ -1,7 +1,7 @@
1
1
  import { getLauncherEnvironment } from "../../environments/launcher.js";
2
2
  import { GrantedFunctionsType, } from "../../hdk/capabilities.js";
3
3
  import { WsClient } from "../client.js";
4
- import { catchError, DEFAULT_TIMEOUT, promiseTimeout, requesterTransformer, } from "../common.js";
4
+ import { catchError, DEFAULT_TIMEOUT, promiseTimeout, requesterTransformer, HolochainError, } from "../common.js";
5
5
  import { generateSigningKeyPair, randomCapSecret, setSigningCredentials, } from "../zome-call-signing.js";
6
6
  import { AppStatusFilter, } from "./types.js";
7
7
  /**
@@ -36,9 +36,9 @@ export class AdminWebsocket {
36
36
  options.url = new URL(`ws://127.0.0.1:${env.ADMIN_INTERFACE_PORT}`);
37
37
  }
38
38
  if (!options.url) {
39
- throw new Error("Unable to connect to Admin Websocket: No url provided and not in a Launcher environment.");
39
+ throw new HolochainError("ConnectionUrlMissing", `unable to connect to Conductor API - no url provided and not in a launcher environment.`);
40
40
  }
41
- const wsClient = await WsClient.connect(options.url);
41
+ const wsClient = await WsClient.connect(options.url, options.wsClientOptions);
42
42
  return new AdminWebsocket(wsClient, options.defaultTimeout);
43
43
  }
44
44
  _requester(tag, transformer) {
@@ -5,7 +5,7 @@ import Emittery from "emittery";
5
5
  import { getLauncherEnvironment, signZomeCallTauri, signZomeCallElectron, getHostZomeCallSigner, } from "../../environments/launcher.js";
6
6
  import { encodeHashToBase64 } from "../../utils/base64.js";
7
7
  import { WsClient } from "../client.js";
8
- import { DEFAULT_TIMEOUT, catchError, promiseTimeout, requesterTransformer, } from "../common.js";
8
+ import { DEFAULT_TIMEOUT, catchError, promiseTimeout, requesterTransformer, HolochainError, } from "../common.js";
9
9
  import { getNonceExpiration, getSigningCredentials, randomNonce, } from "../zome-call-signing.js";
10
10
  /**
11
11
  * A class to establish a websocket connection to an App interface of a
@@ -46,9 +46,9 @@ export class AppWebsocket extends Emittery {
46
46
  options.url = new URL(`ws://127.0.0.1:${env.APP_INTERFACE_PORT}`);
47
47
  }
48
48
  if (!options.url) {
49
- throw new Error("Unable to connect to App Websocket: No url provided and not in a Launcher environment.");
49
+ throw new HolochainError("ConnectionUrlMissing", `unable to connect to Conductor API - no url provided and not in a launcher environment.`);
50
50
  }
51
- const wsClient = await WsClient.connect(options.url);
51
+ const wsClient = await WsClient.connect(options.url, options.wsClientOptions);
52
52
  const appWebsocket = new AppWebsocket(wsClient, options.defaultTimeout, env?.INSTALLED_APP_ID);
53
53
  wsClient.on("signal", (signal) => appWebsocket.emit("signal", signal));
54
54
  return appWebsocket;
@@ -134,7 +134,7 @@ const appInfoTransform = (appWs) => ({
134
134
  export const signZomeCall = async (request) => {
135
135
  const signingCredentialsForCell = getSigningCredentials(request.cell_id);
136
136
  if (!signingCredentialsForCell) {
137
- throw new Error(`cannot sign zome call: no signing credentials have been authorized for cell [${encodeHashToBase64(request.cell_id[0])}, ${encodeHashToBase64(request.cell_id[1])}]`);
137
+ throw new HolochainError("NoSigningCredentialsForCell", `no signing credentials have been authorized for cell [${encodeHashToBase64(request.cell_id[0])}, ${encodeHashToBase64(request.cell_id[1])}]`);
138
138
  }
139
139
  const unsignedZomeCallPayload = {
140
140
  cap_secret: signingCredentialsForCell.capSecret,
@@ -81,20 +81,20 @@ export class AppAgentWebsocket {
81
81
  if (isCloneId(roleName)) {
82
82
  const baseRoleName = getBaseRoleNameFromCloneId(roleName);
83
83
  if (!(baseRoleName in appInfo.cell_info)) {
84
- throw new Error(`No cell found with role_name ${roleName}`);
84
+ throw new HolochainError("NoCellForRoleName", `no cell found with role_name ${roleName}`);
85
85
  }
86
86
  const cloneCell = appInfo.cell_info[baseRoleName].find((c) => CellType.Cloned in c && c[CellType.Cloned].clone_id === roleName);
87
87
  if (!cloneCell || !(CellType.Cloned in cloneCell)) {
88
- throw new Error(`No clone cell found with clone id ${roleName}`);
88
+ throw new HolochainError("NoCellForCloneId", `no clone cell found with clone id ${roleName}`);
89
89
  }
90
90
  return cloneCell[CellType.Cloned].cell_id;
91
91
  }
92
92
  if (!(roleName in appInfo.cell_info)) {
93
- throw new Error(`No cell found with role_name ${roleName}`);
93
+ throw new HolochainError("NoCellForRoleName", `no cell found with role_name ${roleName}`);
94
94
  }
95
95
  const cell = appInfo.cell_info[roleName].find((c) => CellType.Provisioned in c);
96
96
  if (!cell || !(CellType.Provisioned in cell)) {
97
- throw new Error(`No provisioned cell found with role_name ${roleName}`);
97
+ throw new HolochainError("NoProvisionedCellForRoleName", `no provisioned cell found with role_name ${roleName}`);
98
98
  }
99
99
  return cell[CellType.Provisioned].cell_id;
100
100
  }
@@ -125,7 +125,7 @@ export class AppAgentWebsocket {
125
125
  else if ("cell_id" in request && request.cell_id) {
126
126
  return this.appWebsocket.callZome(request, timeout);
127
127
  }
128
- throw new Error("callZome requires a role_name or cell_id arg");
128
+ throw new HolochainError("MissingRoleNameOrCellId", "callZome requires a role_name or cell_id argument");
129
129
  }
130
130
  /**
131
131
  * Clone an existing provisioned cell.
@@ -1,6 +1,7 @@
1
1
  /// <reference types="ws" />
2
2
  import Emittery from "emittery";
3
3
  import IsoWebSocket from "isomorphic-ws";
4
+ import { WsClientOptions } from "./common.js";
4
5
  /**
5
6
  * A WebSocket client which can make requests and receive responses,
6
7
  * as well as send and receive signals.
@@ -12,9 +13,10 @@ import IsoWebSocket from "isomorphic-ws";
12
13
  export declare class WsClient extends Emittery {
13
14
  socket: IsoWebSocket;
14
15
  url: URL | undefined;
16
+ options: WsClientOptions;
15
17
  private pendingRequests;
16
18
  private index;
17
- constructor(socket: IsoWebSocket, url?: URL);
19
+ constructor(socket: IsoWebSocket, url?: URL, options?: WsClientOptions);
18
20
  private setupSocket;
19
21
  /**
20
22
  * Instance factory for creating WsClients.
@@ -22,7 +24,7 @@ export declare class WsClient extends Emittery {
22
24
  * @param url - The WebSocket URL to connect to.
23
25
  * @returns An new instance of the WsClient.
24
26
  */
25
- static connect(url: URL): Promise<WsClient>;
27
+ static connect(url: URL, options?: WsClientOptions): Promise<WsClient>;
26
28
  /**
27
29
  * Sends data as a signal.
28
30
  *
package/lib/api/client.js CHANGED
@@ -2,6 +2,7 @@ import { decode, encode } from "@msgpack/msgpack";
2
2
  import Emittery from "emittery";
3
3
  import IsoWebSocket from "isomorphic-ws";
4
4
  import { SignalType } from "./app/types.js";
5
+ import { HolochainError } from "./common.js";
5
6
  /**
6
7
  * A WebSocket client which can make requests and receive responses,
7
8
  * as well as send and receive signals.
@@ -13,12 +14,14 @@ import { SignalType } from "./app/types.js";
13
14
  export class WsClient extends Emittery {
14
15
  socket;
15
16
  url;
17
+ options;
16
18
  pendingRequests;
17
19
  index;
18
- constructor(socket, url) {
20
+ constructor(socket, url, options) {
19
21
  super();
20
22
  this.socket = socket;
21
23
  this.url = url;
24
+ this.options = options || {};
22
25
  this.pendingRequests = {};
23
26
  this.index = 0;
24
27
  this.setupSocket();
@@ -37,14 +40,14 @@ export class WsClient extends Emittery {
37
40
  deserializedData = serializedMessage.data;
38
41
  }
39
42
  else {
40
- throw new Error("websocket client: unknown message format");
43
+ throw new HolochainError("UnknownMessageFormat", `incoming message has unknown message format - ${deserializedData}`);
41
44
  }
42
45
  }
43
46
  const message = decode(deserializedData);
44
47
  assertHolochainMessage(message);
45
48
  if (message.type === "signal") {
46
49
  if (message.data === null) {
47
- throw new Error("received a signal without data");
50
+ throw new HolochainError("UnknownSignalFormat", "incoming signal has no data");
48
51
  }
49
52
  const deserializedSignal = decode(message.data);
50
53
  assertHolochainSignal(deserializedSignal);
@@ -66,14 +69,14 @@ export class WsClient extends Emittery {
66
69
  this.handleResponse(message);
67
70
  }
68
71
  else {
69
- console.error(`Got unrecognized Websocket message type: ${message.type}`);
72
+ throw new HolochainError("UnknownMessageType", `incoming message has unknown type - ${message.type}`);
70
73
  }
71
74
  };
72
75
  this.socket.onclose = (event) => {
73
76
  const pendingRequestIds = Object.keys(this.pendingRequests).map((id) => parseInt(id));
74
77
  if (pendingRequestIds.length) {
75
78
  pendingRequestIds.forEach((id) => {
76
- const error = new Error(`Websocket closed with pending requests. Close event code: ${event.code}, request id: ${id}`);
79
+ const error = new HolochainError("ClientClosedWithPendingRequests", `client closed with pending requests - close event code: ${event.code}, request id: ${id}`);
77
80
  this.pendingRequests[id].reject(error);
78
81
  delete this.pendingRequests[id];
79
82
  });
@@ -86,14 +89,14 @@ export class WsClient extends Emittery {
86
89
  * @param url - The WebSocket URL to connect to.
87
90
  * @returns An new instance of the WsClient.
88
91
  */
89
- static connect(url) {
92
+ static connect(url, options) {
90
93
  return new Promise((resolve, reject) => {
91
- const socket = new IsoWebSocket(url);
92
- socket.onerror = () => {
93
- reject(new Error(`could not connect to holochain conductor, please check that a conductor service is running and available at ${url}`));
94
+ const socket = new IsoWebSocket(url, options);
95
+ socket.onerror = (errorEvent) => {
96
+ reject(new HolochainError("ConnectionError", `could not connect to Holochain Conductor API at ${url} - ${errorEvent.error}`));
94
97
  };
95
98
  socket.onopen = () => {
96
- const client = new WsClient(socket, url);
99
+ const client = new WsClient(socket, url, options);
97
100
  resolve(client);
98
101
  };
99
102
  });
@@ -126,10 +129,10 @@ export class WsClient extends Emittery {
126
129
  else if (this.url) {
127
130
  const response = new Promise((resolve, reject) => {
128
131
  // typescript forgets in this promise scope that this.url is not undefined
129
- const socket = new IsoWebSocket(this.url);
132
+ const socket = new IsoWebSocket(this.url, this.options);
130
133
  this.socket = socket;
131
- socket.onerror = () => {
132
- reject(new Error(`could not connect to Holochain conductor, please check that a conductor service is running and available at ${this.url}`));
134
+ socket.onerror = (errorEvent) => {
135
+ reject(new HolochainError("ConnectionError", `could not connect to Holochain Conductor API at ${this.url} - ${errorEvent.error}`));
133
136
  };
134
137
  socket.onopen = () => {
135
138
  this.sendMessage(request, resolve, reject);
@@ -165,7 +168,7 @@ export class WsClient extends Emittery {
165
168
  delete this.pendingRequests[id];
166
169
  }
167
170
  else {
168
- console.error(`Got response with no matching request. id=${id}`);
171
+ console.error(`got response with no matching request. id = ${id} msg = ${msg}`);
169
172
  }
170
173
  }
171
174
  /**
@@ -190,7 +193,7 @@ function assertHolochainMessage(message) {
190
193
  "data" in message) {
191
194
  return;
192
195
  }
193
- throw new Error(`unknown message format ${JSON.stringify(message, null, 4)}`);
196
+ throw new HolochainError("UnknownMessageFormat", `incoming message has unknown message format ${JSON.stringify(message, null, 4)}`);
194
197
  }
195
198
  function assertHolochainSignal(signal) {
196
199
  if (typeof signal === "object" &&
@@ -198,6 +201,6 @@ function assertHolochainSignal(signal) {
198
201
  Object.values(SignalType).some((type) => type in signal)) {
199
202
  return;
200
203
  }
201
- throw new Error(`unknown signal format ${JSON.stringify(signal, null, 4)}`);
204
+ throw new HolochainError("UnknownSignalFormat", `incoming signal has unknown signal format ${JSON.stringify(signal, null, 4)}`);
202
205
  }
203
206
  export { IsoWebSocket };
@@ -1,4 +1,6 @@
1
+ /// <reference types="ws" />
1
2
  import { RoleName } from "../types.js";
3
+ import { IsoWebSocket } from "./client.js";
2
4
  export declare const DEFAULT_TIMEOUT = 60000;
3
5
  /**
4
6
  * @public
@@ -78,6 +80,10 @@ export declare class CloneId {
78
80
  toString(): string;
79
81
  getBaseRoleName(): string;
80
82
  }
83
+ /**
84
+ * @public
85
+ */
86
+ export type WsClientOptions = Pick<IsoWebSocket.ClientOptions, "origin">;
81
87
  /**
82
88
  * Options for a Websocket connection.
83
89
  *
@@ -88,6 +94,10 @@ export interface WebsocketConnectionOptions {
88
94
  * The `ws://` URL of the Websocket server to connect to. Not required when connecting to App API from a Launcher or Kangaroo environment.
89
95
  */
90
96
  url?: URL;
97
+ /**
98
+ * Options to pass to the underlying websocket connection.
99
+ */
100
+ wsClientOptions?: WsClientOptions;
91
101
  /**
92
102
  * Timeout to default to for all operations.
93
103
  */
package/lib/api/common.js CHANGED
@@ -75,7 +75,7 @@ export const isCloneId = (roleName) => roleName.includes(CLONE_ID_DELIMITER);
75
75
  */
76
76
  export const getBaseRoleNameFromCloneId = (roleName) => {
77
77
  if (!isCloneId(roleName)) {
78
- throw new Error("invalid clone id: no clone id delimiter found in role name");
78
+ throw new HolochainError("MissingCloneIdDelimiter", `invalid clone id - no clone id delimiter found in role name ${roleName}`);
79
79
  }
80
80
  return roleName.split(CLONE_ID_DELIMITER)[0];
81
81
  };
@@ -102,7 +102,7 @@ export class CloneId {
102
102
  static fromRoleName(roleName) {
103
103
  const parts = roleName.split(CLONE_ID_DELIMITER);
104
104
  if (parts.length !== 2) {
105
- throw new Error("Malformed clone id: must consist of {role id.clone index}");
105
+ throw new HolochainError("MalformedCloneId", `clone id must consist of 'role_id.clone_index', but got ${roleName}`);
106
106
  }
107
107
  return new CloneId(parts[0], parseInt(parts[1]));
108
108
  }
@@ -3,5 +3,5 @@ export * from "./admin/index.js";
3
3
  export * from "./app-agent/index.js";
4
4
  export * from "./app/index.js";
5
5
  export { IsoWebSocket, WsClient } from "./client.js";
6
- export { CloneId, HolochainError, Requester, Transformer, WebsocketConnectionOptions, getBaseRoleNameFromCloneId, isCloneId, } from "./common.js";
6
+ export { CloneId, HolochainError, Requester, Transformer, WebsocketConnectionOptions, WsClientOptions, getBaseRoleNameFromCloneId, isCloneId, } from "./common.js";
7
7
  export * from "./zome-call-signing.js";
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.41.0"
8
+ "packageVersion": "7.43.0"
9
9
  }
10
10
  ]
11
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holochain/client",
3
- "version": "0.17.0-dev.7",
3
+ "version": "0.17.0-dev.8",
4
4
  "description": "A JavaScript client for the Holochain Conductor API",
5
5
  "author": "Holochain Foundation <info@holochain.org> (http://holochain.org)",
6
6
  "license": "CAL-1.0",
@@ -37,7 +37,8 @@
37
37
  "test": "RUST_LOG=error RUST_BACKTRACE=1 node --loader ts-node/esm test/index.ts",
38
38
  "build:lib": "rimraf ./lib && tsc -p tsconfig.build.json",
39
39
  "build:docs": "api-extractor run --local && api-documenter markdown -i docs/temp -o docs",
40
- "build": "npm run build:lib && npm run build:docs"
40
+ "build": "npm run build:lib && npm run build:docs",
41
+ "prepublishOnly": "npm run lint && npm run build"
41
42
  },
42
43
  "dependencies": {
43
44
  "@bitgo/blake2b": "^3.2.4",