@holochain/client 0.14.1 → 0.15.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.
@@ -25,7 +25,7 @@ export declare class AdminWebsocket implements AdminApi {
25
25
  * @param defaultTimeout - The default timeout for any request.
26
26
  * @returns A promise for a new connected instance.
27
27
  */
28
- static connect(url: string, defaultTimeout?: number): Promise<AdminWebsocket>;
28
+ static connect(url: URL, defaultTimeout?: number): Promise<AdminWebsocket>;
29
29
  _requester<ReqI, ReqO, ResI, ResO>(tag: string, transformer?: Transformer<ReqI, ReqO, ResI, ResO>): (req: ReqI, timeout?: number | undefined) => Promise<ResO>;
30
30
  /**
31
31
  * Send a request to open the given port for {@link AppWebsocket} connections.
@@ -34,7 +34,7 @@ export class AdminWebsocket {
34
34
  // Check if we are in the launcher's environment, and if so, redirect the url to connect to
35
35
  const env = getLauncherEnvironment();
36
36
  if (env?.ADMIN_INTERFACE_PORT) {
37
- url = `ws://127.0.0.1:${env.ADMIN_INTERFACE_PORT}`;
37
+ url = new URL(`ws://127.0.0.1:${env.ADMIN_INTERFACE_PORT}`);
38
38
  }
39
39
  const wsClient = await WsClient.connect(url);
40
40
  return new AdminWebsocket(wsClient, defaultTimeout);
@@ -23,7 +23,7 @@ export declare class AppWebsocket extends Emittery implements AppApi {
23
23
  * @param defaultTimeout - Timeout to default to for all operations.
24
24
  * @returns A new instance of an AppWebsocket.
25
25
  */
26
- static connect(url: string, defaultTimeout?: number): Promise<AppWebsocket>;
26
+ static connect(url: URL, defaultTimeout?: number): Promise<AppWebsocket>;
27
27
  _requester<ReqI, ReqO, ResI, ResO>(tag: string, transformer?: Transformer<ReqI, ReqO, ResI, ResO>): (req: ReqI, timeout?: number | undefined) => Promise<ResO>;
28
28
  /**
29
29
  * Request the app's info, including all cell infos.
@@ -35,7 +35,7 @@ export class AppWebsocket extends Emittery {
35
35
  // Check if we are in the launcher's environment, and if so, redirect the url to connect to
36
36
  const env = getLauncherEnvironment();
37
37
  if (env?.APP_INTERFACE_PORT) {
38
- url = `ws://127.0.0.1:${env.APP_INTERFACE_PORT}`;
38
+ url = new URL(`ws://127.0.0.1:${env.APP_INTERFACE_PORT}`);
39
39
  }
40
40
  const wsClient = await WsClient.connect(url);
41
41
  const appWebsocket = new AppWebsocket(wsClient, defaultTimeout, env?.INSTALLED_APP_ID);
@@ -31,7 +31,7 @@ export declare class AppAgentWebsocket implements AppAgentClient {
31
31
  * @param defaultTimeout - Timeout to default to for all operations.
32
32
  * @returns A new instance of an AppAgentWebsocket.
33
33
  */
34
- static connect(url: string, installed_app_id: InstalledAppId, defaultTimeout?: number): Promise<AppAgentWebsocket>;
34
+ static connect(url: URL, installed_app_id: InstalledAppId, defaultTimeout?: number): Promise<AppAgentWebsocket>;
35
35
  /**
36
36
  * Get a cell id by its role name or clone id.
37
37
  *
@@ -1,30 +1,29 @@
1
1
  /// <reference types="ws" />
2
- import { decode } from "@msgpack/msgpack";
3
2
  import Emittery from "emittery";
4
3
  import IsoWebSocket from "isomorphic-ws";
5
4
  /**
6
5
  * A WebSocket client which can make requests and receive responses,
7
6
  * as well as send and receive signals.
8
7
  *
9
- * Uses Holochain's websocket WireMessage for communication.
8
+ * Uses Holochain's WireMessage for communication.
10
9
  *
11
10
  * @public
12
11
  */
13
12
  export declare class WsClient extends Emittery {
14
13
  socket: IsoWebSocket;
15
- pendingRequests: Record<number, {
16
- resolve: (msg: unknown) => ReturnType<typeof decode>;
17
- reject: (error: Error) => void;
18
- }>;
14
+ url: URL | undefined;
15
+ private pendingRequests;
19
16
  index: number;
20
- constructor(socket: IsoWebSocket);
17
+ private connectRetries;
18
+ constructor(socket: IsoWebSocket, url: URL);
19
+ private setupSocket;
21
20
  /**
22
21
  * Instance factory for creating WsClients.
23
22
  *
24
- * @param url - The `ws://` URL to connect to.
23
+ * @param url - The WebSocket URL to connect to.
25
24
  * @returns An new instance of the WsClient.
26
25
  */
27
- static connect(url: string): Promise<WsClient>;
26
+ static connect(url: URL): Promise<WsClient>;
28
27
  /**
29
28
  * Sends data as a signal.
30
29
  *
@@ -37,7 +36,8 @@ export declare class WsClient extends Emittery {
37
36
  * @param request - The request to send over the websocket.
38
37
  * @returns
39
38
  */
40
- request<Req, Res>(request: Req): Promise<Res>;
39
+ request<Response>(request: unknown): Promise<Response>;
40
+ private sendMessage;
41
41
  private handleResponse;
42
42
  /**
43
43
  * Close the websocket connection.
package/lib/api/client.js CHANGED
@@ -6,20 +6,27 @@ import { SignalType } from "./app/types.js";
6
6
  * A WebSocket client which can make requests and receive responses,
7
7
  * as well as send and receive signals.
8
8
  *
9
- * Uses Holochain's websocket WireMessage for communication.
9
+ * Uses Holochain's WireMessage for communication.
10
10
  *
11
11
  * @public
12
12
  */
13
13
  export class WsClient extends Emittery {
14
14
  socket;
15
+ url;
15
16
  pendingRequests;
16
17
  index;
17
- constructor(socket) {
18
+ connectRetries;
19
+ constructor(socket, url) {
18
20
  super();
19
21
  this.socket = socket;
22
+ this.url = url;
20
23
  this.pendingRequests = {};
21
24
  this.index = 0;
22
- socket.onmessage = async (serializedMessage) => {
25
+ this.connectRetries = 0;
26
+ this.setupSocket();
27
+ }
28
+ setupSocket() {
29
+ this.socket.onmessage = async (serializedMessage) => {
23
30
  // If data is not a buffer (nodejs), it will be a blob (browser)
24
31
  let deserializedData;
25
32
  if (globalThis.window &&
@@ -64,7 +71,7 @@ export class WsClient extends Emittery {
64
71
  console.error(`Got unrecognized Websocket message type: ${message.type}`);
65
72
  }
66
73
  };
67
- socket.onclose = (event) => {
74
+ this.socket.onclose = (event) => {
68
75
  const pendingRequestIds = Object.keys(this.pendingRequests).map((id) => parseInt(id));
69
76
  if (pendingRequestIds.length) {
70
77
  pendingRequestIds.forEach((id) => {
@@ -78,20 +85,17 @@ export class WsClient extends Emittery {
78
85
  /**
79
86
  * Instance factory for creating WsClients.
80
87
  *
81
- * @param url - The `ws://` URL to connect to.
88
+ * @param url - The WebSocket URL to connect to.
82
89
  * @returns An new instance of the WsClient.
83
90
  */
84
91
  static connect(url) {
85
92
  return new Promise((resolve, reject) => {
86
93
  const socket = new IsoWebSocket(url);
87
- // make sure that there are no uncaught connection
88
- // errors because that causes nodejs thread to crash
89
- // with uncaught exception
90
94
  socket.onerror = () => {
91
95
  reject(new Error(`could not connect to holochain conductor, please check that a conductor service is running and available at ${url}`));
92
96
  };
93
97
  socket.onopen = () => {
94
- const client = new WsClient(socket);
98
+ const client = new WsClient(socket, url);
95
99
  resolve(client);
96
100
  };
97
101
  });
@@ -114,25 +118,43 @@ export class WsClient extends Emittery {
114
118
  * @param request - The request to send over the websocket.
115
119
  * @returns
116
120
  */
117
- request(request) {
121
+ async request(request) {
118
122
  if (this.socket.readyState === this.socket.OPEN) {
119
- const id = this.index;
120
- const encodedMsg = encode({
121
- id,
122
- type: "request",
123
- data: encode(request),
124
- });
125
123
  const promise = new Promise((resolve, reject) => {
126
- this.pendingRequests[id] = { resolve, reject };
124
+ this.sendMessage(request, resolve, reject);
127
125
  });
128
- this.socket.send(encodedMsg);
129
- this.index += 1;
130
126
  return promise;
131
127
  }
128
+ else if (this.url) {
129
+ const response = new Promise((resolve, reject) => {
130
+ // typescript forgets in this promise scope that this.url is not undefined
131
+ const socket = new IsoWebSocket(this.url);
132
+ this.socket = socket;
133
+ socket.onerror = () => {
134
+ reject(new Error(`could not connect to Holochain conductor, please check that a conductor service is running and available at ${this.url}`));
135
+ };
136
+ socket.onopen = () => {
137
+ this.sendMessage(request, resolve, reject);
138
+ };
139
+ this.setupSocket();
140
+ });
141
+ return response;
142
+ }
132
143
  else {
133
144
  return Promise.reject(new Error("Socket is not open"));
134
145
  }
135
146
  }
147
+ sendMessage(request, resolve, reject) {
148
+ const id = this.index;
149
+ const encodedMsg = encode({
150
+ id,
151
+ type: "request",
152
+ data: encode(request),
153
+ });
154
+ this.socket.send(encodedMsg);
155
+ this.pendingRequests[id] = { resolve, reject };
156
+ this.index += 1;
157
+ }
136
158
  handleResponse(msg) {
137
159
  const id = msg.id;
138
160
  if (this.pendingRequests[id]) {
@@ -30,6 +30,14 @@ export type Tagged<T> = {
30
30
  * @public
31
31
  */
32
32
  export declare const requesterTransformer: <ReqI, ReqO, ResI, ResO>(requester: Requester<Tagged<ReqO>, Tagged<ResI>>, tag: string, transform?: Transformer<ReqI, ReqO, ResI, ResO>) => (req: ReqI, timeout?: number) => Promise<ResO>;
33
+ /**
34
+ * Error thrown when response from Holochain is an error.
35
+ *
36
+ * @public
37
+ */
38
+ export declare class HolochainError extends Error {
39
+ constructor(name: string, message: string);
40
+ }
33
41
  export declare const catchError: (res: any) => Promise<any>;
34
42
  export declare const promiseTimeout: (promise: Promise<unknown>, tag: string, ms: number) => Promise<unknown>;
35
43
  /**
package/lib/api/common.js CHANGED
@@ -19,16 +19,32 @@ const identityTransformer = {
19
19
  input: identity,
20
20
  output: identity,
21
21
  };
22
+ /**
23
+ * Error thrown when response from Holochain is an error.
24
+ *
25
+ * @public
26
+ */
27
+ export class HolochainError extends Error {
28
+ constructor(name, message) {
29
+ super();
30
+ this.name = name;
31
+ this.message = message;
32
+ }
33
+ }
34
+ // this determines the error format of all error responses
22
35
  export const catchError = (res) => {
23
- return res.type === ERROR_TYPE ? Promise.reject(res) : Promise.resolve(res);
36
+ if (res.type === ERROR_TYPE) {
37
+ const error = new HolochainError(res.data.type, res.data.data);
38
+ return Promise.reject(error);
39
+ }
40
+ else {
41
+ return Promise.resolve(res);
42
+ }
24
43
  };
25
44
  export const promiseTimeout = (promise, tag, ms) => {
26
45
  let id;
27
46
  const timeout = new Promise((_, reject) => {
28
- id = setTimeout(() => {
29
- clearTimeout(id);
30
- reject(new Error(`Timed out in ${ms}ms: ${tag}`));
31
- }, ms);
47
+ id = setTimeout(() => reject(new Error(`Timed out in ${ms}ms: ${tag}`)), ms);
32
48
  });
33
49
  return new Promise((res, rej) => {
34
50
  Promise.race([promise, timeout])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holochain/client",
3
- "version": "0.14.1",
3
+ "version": "0.15.0",
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",