Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@holochain/client

Package Overview
Dependencies
Maintainers
0
Versions
97
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@holochain/client - npm Package Compare versions

Comparing version 0.18.0-dev.13 to 0.18.0-rc.0

2

lib/api/app/websocket.d.ts

@@ -19,2 +19,3 @@ import { UnsubscribeFunction } from "emittery";

private readonly callZomeTransform;
private readonly appAuthenticationToken;
cachedAppInfo?: AppInfo | null;

@@ -107,3 +108,2 @@ private readonly appInfoRequester;

private static requester;
private containsCell;
}

@@ -110,0 +110,0 @@ /**

import Emittery from "emittery";
import { omit } from "lodash-es";
import { CellType } from "../admin/index.js";
import { CellType, } from "../admin/index.js";
import { catchError, DEFAULT_TIMEOUT, getBaseRoleNameFromCloneId, HolochainError, isCloneId, promiseTimeout, requesterTransformer, } from "../common.js";

@@ -25,2 +25,3 @@ import { getHostZomeCallSigner, getLauncherEnvironment, } from "../../environments/launcher.js";

callZomeTransform;
appAuthenticationToken;
cachedAppInfo;

@@ -35,3 +36,3 @@ appInfoRequester;

networkInfoRequester;
constructor(client, appInfo, callZomeTransform, defaultTimeout) {
constructor(client, appInfo, token, callZomeTransform, defaultTimeout) {
this.client = client;

@@ -42,2 +43,3 @@ this.myPubKey = appInfo.agent_pub_key;

this.callZomeTransform = callZomeTransform ?? defaultCallZomeTransform;
this.appAuthenticationToken = token;
this.emitter = new Emittery();

@@ -83,12 +85,6 @@ this.cachedAppInfo = appInfo;

const client = await WsClient.connect(options.url, options.wsClientOptions);
if (env?.APP_INTERFACE_TOKEN) {
// Note: This will only work for multiple connections if a single_use = false token is provided
await client.authenticate({ token: env.APP_INTERFACE_TOKEN });
}
else {
if (!options.token) {
throw new HolochainError("AppAuthenticationTokenMissing", `unable to connect to Conductor API - no app authentication token provided.`);
}
await client.authenticate({ token: options.token });
}
const token = options.token ?? env?.APP_INTERFACE_TOKEN;
if (!token)
throw new HolochainError("AppAuthenticationTokenMissing", `unable to connect to Conductor API - no app authentication token provided.`);
await client.authenticate({ token });
const appInfo = await AppWebsocket.requester(client, "app_info", DEFAULT_TIMEOUT)(null);

@@ -98,3 +94,3 @@ if (!appInfo) {

}
return new AppWebsocket(client, appInfo, options.callZomeTransform, options.defaultTimeout);
return new AppWebsocket(client, appInfo, token, options.callZomeTransform, options.defaultTimeout);
}

@@ -245,21 +241,2 @@ /**

}
containsCell(cellId) {
const appInfo = this.cachedAppInfo;
if (!appInfo) {
return false;
}
for (const roleName of Object.keys(appInfo.cell_info)) {
for (const cellInfo of appInfo.cell_info[roleName]) {
const currentCellId = CellType.Provisioned in cellInfo
? cellInfo[CellType.Provisioned].cell_id
: CellType.Cloned in cellInfo
? cellInfo[CellType.Cloned].cell_id
: undefined;
if (currentCellId && isSameCell(currentCellId, cellId)) {
return true;
}
}
}
return false;
}
}

@@ -266,0 +243,0 @@ const defaultCallZomeTransform = {

@@ -26,4 +26,4 @@ /// <reference types="ws" />

private index;
private authenticationToken;
constructor(socket: IsoWebSocket, url?: URL, options?: WsClientOptions);
private setupSocket;
/**

@@ -52,2 +52,6 @@ * Instance factory for creating WsClients.

/**
* Close the websocket connection.
*/
close(code?: number): Promise<IsoWebSocket.CloseEvent>;
/**
* Send requests to the connected websocket.

@@ -61,8 +65,7 @@ *

private sendMessage;
private registerMessageListener;
private registerCloseListener;
private reconnectWebsocket;
private handleResponse;
/**
* Close the websocket connection.
*/
close(code?: number): Promise<CloseEvent>;
}
export { IsoWebSocket };

@@ -20,4 +20,7 @@ import { decode, encode } from "@msgpack/msgpack";

index;
authenticationToken;
constructor(socket, url, options) {
super();
this.registerMessageListener(socket);
this.registerCloseListener(socket);
this.socket = socket;

@@ -28,64 +31,3 @@ this.url = url;

this.index = 0;
this.setupSocket();
}
setupSocket() {
this.socket.onmessage = async (serializedMessage) => {
// If data is not a buffer (nodejs), it will be a blob (browser)
let deserializedData;
if (globalThis.window &&
serializedMessage.data instanceof globalThis.window.Blob) {
deserializedData = await serializedMessage.data.arrayBuffer();
}
else {
if (typeof Buffer !== "undefined" &&
Buffer.isBuffer(serializedMessage.data)) {
deserializedData = serializedMessage.data;
}
else {
throw new HolochainError("UnknownMessageFormat", `incoming message has unknown message format - ${deserializedData}`);
}
}
const message = decode(deserializedData);
assertHolochainMessage(message);
if (message.type === "signal") {
if (message.data === null) {
throw new HolochainError("UnknownSignalFormat", "incoming signal has no data");
}
const deserializedSignal = decode(message.data);
assertHolochainSignal(deserializedSignal);
if (SignalType.System in deserializedSignal) {
this.emit("signal", {
System: deserializedSignal[SignalType.System],
});
}
else {
const encodedAppSignal = deserializedSignal[SignalType.App];
// In order to return readable content to the UI, the signal payload must also be deserialized.
const payload = decode(encodedAppSignal.signal);
const signal = {
cell_id: encodedAppSignal.cell_id,
zome_name: encodedAppSignal.zome_name,
payload,
};
this.emit("signal", { App: signal });
}
}
else if (message.type === "response") {
this.handleResponse(message);
}
else {
throw new HolochainError("UnknownMessageType", `incoming message has unknown type - ${message.type}`);
}
};
this.socket.onclose = (event) => {
const pendingRequestIds = Object.keys(this.pendingRequests).map((id) => parseInt(id));
if (pendingRequestIds.length) {
pendingRequestIds.forEach((id) => {
const error = new HolochainError("ClientClosedWithPendingRequests", `client closed with pending requests - close event code: ${event.code}, request id: ${id}`);
this.pendingRequests[id].reject(error);
delete this.pendingRequests[id];
});
}
};
}
/**

@@ -101,9 +43,9 @@ * Instance factory for creating WsClients.

const socket = new IsoWebSocket(url, options);
socket.onerror = (errorEvent) => {
socket.addEventListener("error", (errorEvent) => {
reject(new HolochainError("ConnectionError", `could not connect to Holochain Conductor API at ${url} - ${errorEvent.error}`));
};
socket.onopen = () => {
});
socket.addEventListener("open", (_) => {
const client = new WsClient(socket, url, options);
resolve(client);
};
}, { once: true });
});

@@ -131,3 +73,11 @@ }

async authenticate(request) {
return this.exchange(request, (request, resolve) => {
this.authenticationToken = request.token;
return this.exchange(request, (request, resolve, reject) => {
const invalidTokenCloseListener = (closeEvent) => {
this.authenticationToken = undefined;
reject(new HolochainError("InvalidTokenError", `could not connect to ${this.url} due to an invalid app authentication token - close code ${closeEvent.code}`));
};
this.socket.addEventListener("close", invalidTokenCloseListener, {
once: true,
});
const encodedMsg = encode({

@@ -138,7 +88,18 @@ type: "authenticate",

this.socket.send(encodedMsg);
// Message just needs to be sent first, no need to wait for a response or even require a flush
resolve(null);
// Wait before resolving in case authentication fails.
setTimeout(() => {
this.socket.removeEventListener("close", invalidTokenCloseListener);
resolve(null);
}, 10);
});
}
/**
* Close the websocket connection.
*/
close(code = 1000) {
const closedPromise = new Promise((resolve) => this.socket.addEventListener("close", (closeEvent) => resolve(closeEvent), { once: true }));
this.socket.close(code);
return closedPromise;
}
/**
* Send requests to the connected websocket.

@@ -152,3 +113,3 @@ *

}
exchange(request, sendHandler) {
async exchange(request, sendHandler) {
if (this.socket.readyState === this.socket.OPEN) {

@@ -160,4 +121,11 @@ const promise = new Promise((resolve, reject) => {

}
else if (this.url && this.authenticationToken) {
await this.reconnectWebsocket(this.url, this.authenticationToken);
this.registerMessageListener(this.socket);
this.registerCloseListener(this.socket);
const promise = new Promise((resolve, reject) => sendHandler(request, resolve, reject));
return promise;
}
else {
return Promise.reject(new Error("Socket is not open"));
return Promise.reject(new HolochainError("WebsocketClosedError", "Websocket is not open"));
}

@@ -176,2 +144,93 @@ }

}
registerMessageListener(socket) {
socket.onmessage = async (serializedMessage) => {
// If data is not a buffer (nodejs), it will be a blob (browser)
let deserializedData;
if (globalThis.window &&
serializedMessage.data instanceof globalThis.window.Blob) {
deserializedData = await serializedMessage.data.arrayBuffer();
}
else {
if (typeof Buffer !== "undefined" &&
Buffer.isBuffer(serializedMessage.data)) {
deserializedData = serializedMessage.data;
}
else {
throw new HolochainError("UnknownMessageFormat", `incoming message has unknown message format - ${deserializedData}`);
}
}
const message = decode(deserializedData);
assertHolochainMessage(message);
if (message.type === "signal") {
if (message.data === null) {
throw new HolochainError("UnknownSignalFormat", "incoming signal has no data");
}
const deserializedSignal = decode(message.data);
assertHolochainSignal(deserializedSignal);
if (SignalType.System in deserializedSignal) {
this.emit("signal", {
System: deserializedSignal[SignalType.System],
});
}
else {
const encodedAppSignal = deserializedSignal[SignalType.App];
// In order to return readable content to the UI, the signal payload must also be deserialized.
const payload = decode(encodedAppSignal.signal);
const signal = {
cell_id: encodedAppSignal.cell_id,
zome_name: encodedAppSignal.zome_name,
payload,
};
this.emit("signal", { App: signal });
}
}
else if (message.type === "response") {
this.handleResponse(message);
}
else {
throw new HolochainError("UnknownMessageType", `incoming message has unknown type - ${message.type}`);
}
};
}
registerCloseListener(socket) {
socket.addEventListener("close", (closeEvent) => {
const pendingRequestIds = Object.keys(this.pendingRequests).map((id) => parseInt(id));
if (pendingRequestIds.length) {
pendingRequestIds.forEach((id) => {
const error = new HolochainError("ClientClosedWithPendingRequests", `client closed with pending requests - close event code: ${closeEvent.code}, request id: ${id}`);
this.pendingRequests[id].reject(error);
delete this.pendingRequests[id];
});
}
}, { once: true });
}
async reconnectWebsocket(url, token) {
return new Promise((resolve, reject) => {
this.socket = new IsoWebSocket(url, this.options);
// This error event never occurs in tests. Could be removed?
this.socket.addEventListener("error", (errorEvent) => {
this.authenticationToken = undefined;
reject(new HolochainError("ConnectionError", `could not connect to Holochain Conductor API at ${url} - ${errorEvent.message}`));
}, { once: true });
const invalidTokenCloseListener = (closeEvent) => {
this.authenticationToken = undefined;
reject(new HolochainError("InvalidTokenError", `could not connect to ${this.url} due to an invalid app authentication token - close code ${closeEvent.code}`));
};
this.socket.addEventListener("close", invalidTokenCloseListener, {
once: true,
});
this.socket.addEventListener("open", async (_) => {
const encodedMsg = encode({
type: "authenticate",
data: encode({ token }),
});
this.socket.send(encodedMsg);
// Wait in case authentication fails.
setTimeout(() => {
this.socket.removeEventListener("close", invalidTokenCloseListener);
resolve();
}, 10);
}, { once: true });
});
}
handleResponse(msg) {

@@ -192,16 +251,2 @@ const id = msg.id;

}
/**
* Close the websocket connection.
*/
close(code = 1000) {
const closedPromise = new Promise((resolve) =>
// for an unknown reason "addEventListener" is seen as a non-callable
// property and gives a ts2349 error
// type assertion as workaround
this.socket.addEventListener("close", (event) => resolve(event))
// }
);
this.socket.close(code);
return closedPromise;
}
}

@@ -208,0 +253,0 @@ function assertHolochainMessage(message) {

@@ -55,2 +55,3 @@ const ERROR_TYPE = "error";

.catch((e) => {
clearTimeout(id);
return rej(e);

@@ -57,0 +58,0 @@ }));

@@ -8,5 +8,5 @@ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.

"packageName": "@microsoft/api-extractor",
"packageVersion": "7.47.0"
"packageVersion": "7.47.11"
}
]
}
{
"name": "@holochain/client",
"version": "0.18.0-dev.13",
"version": "0.18.0-rc.0",
"description": "A JavaScript client for the Holochain Conductor API",

@@ -75,2 +75,2 @@ "author": "Holochain Foundation <info@holochain.org> (https://holochain.org)",

}
}
}

@@ -182,4 +182,5 @@ [![Project](https://img.shields.io/badge/Project-Holochain-blue.svg?style=flat-square)](http://holochain.org/)

```bash
nix-shell
./run-test.sh
nix develop
./build-fixture.sh
npm run test
```

@@ -186,0 +187,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc