@holochain/client
Advanced tools
Comparing version 0.17.0-dev.9 to 0.17.0-dev.10
@@ -1,5 +0,4 @@ | ||
import { Action, DhtOp, Entry, ZomeCallCapGrant } from "../../hdk/index.js"; | ||
import { Action, DhtOp, Entry, ZomeCallCapGrant } from "../../hdk"; | ||
import { ActionHash, AgentPubKey, CellId, DnaHash, DnaProperties, Duration, HoloHash, HoloHashB64, InstalledAppId, KitsuneAgent, KitsuneSpace, RoleName, Signature, Timestamp, WasmHash } from "../../types.js"; | ||
import { Requester } from "../common.js"; | ||
import { DisableCloneCellRequest } from "../index.js"; | ||
/** | ||
@@ -503,3 +502,12 @@ * @public | ||
*/ | ||
export type DeleteCloneCellRequest = DisableCloneCellRequest; | ||
export interface DeleteCloneCellRequest { | ||
/** | ||
* The app id that the clone cell belongs to | ||
*/ | ||
app_id: InstalledAppId; | ||
/** | ||
* The clone id or cell id of the clone cell | ||
*/ | ||
clone_cell_id: RoleName | CellId; | ||
} | ||
/** | ||
@@ -726,2 +734,21 @@ * @public | ||
*/ | ||
export interface IssueAppAuthenticationTokenRequest { | ||
installed_app_id: InstalledAppId; | ||
expiry_seconds?: number; | ||
single_use?: boolean; | ||
} | ||
/** | ||
* @public | ||
*/ | ||
export type AppAuthenticationToken = number[]; | ||
/** | ||
* @public | ||
*/ | ||
export interface IssueAppAuthenticationTokenResponse { | ||
token: AppAuthenticationToken; | ||
expires_at?: Timestamp; | ||
} | ||
/** | ||
* @public | ||
*/ | ||
export type DumpNetworkStatsRequest = void; | ||
@@ -755,3 +782,4 @@ /** | ||
storageInfo: Requester<StorageInfoRequest, StorageInfoResponse>; | ||
issueAppAuthenticationToken: Requester<IssueAppAuthenticationTokenRequest, IssueAppAuthenticationTokenResponse>; | ||
dumpNetworkStats: Requester<DumpNetworkStatsRequest, DumpNetworkStatsResponse>; | ||
} |
@@ -5,3 +5,3 @@ import { CapSecret, GrantedFunctions } from "../../hdk/capabilities.js"; | ||
import { WebsocketConnectionOptions, Requester, Transformer } from "../common.js"; | ||
import { AddAgentInfoRequest, AddAgentInfoResponse, AdminApi, AgentInfoRequest, AgentInfoResponse, AttachAppInterfaceRequest, AttachAppInterfaceResponse, DeleteCloneCellRequest, DeleteCloneCellResponse, DisableAppRequest, DisableAppResponse, DumpFullStateRequest, DumpFullStateResponse, DumpNetworkStatsRequest, DumpNetworkStatsResponse, DumpStateRequest, DumpStateResponse, EnableAppRequest, EnableAppResponse, GenerateAgentPubKeyRequest, GenerateAgentPubKeyResponse, GetDnaDefinitionRequest, GetDnaDefinitionResponse, GrantZomeCallCapabilityRequest, GrantZomeCallCapabilityResponse, InstallAppRequest, InstallAppResponse, ListAppInterfacesRequest, ListAppInterfacesResponse, ListAppsRequest, ListAppsResponse, ListCellIdsRequest, ListCellIdsResponse, ListDnasRequest, ListDnasResponse, RegisterDnaRequest, RegisterDnaResponse, StorageInfoRequest, StorageInfoResponse, UninstallAppRequest, UninstallAppResponse, UpdateCoordinatorsRequest, UpdateCoordinatorsResponse } from "./types.js"; | ||
import { AddAgentInfoRequest, AddAgentInfoResponse, AdminApi, AgentInfoRequest, AgentInfoResponse, AttachAppInterfaceRequest, AttachAppInterfaceResponse, DeleteCloneCellRequest, DeleteCloneCellResponse, DisableAppRequest, DisableAppResponse, DumpFullStateRequest, DumpFullStateResponse, DumpNetworkStatsRequest, DumpNetworkStatsResponse, DumpStateRequest, DumpStateResponse, EnableAppRequest, EnableAppResponse, GenerateAgentPubKeyRequest, GenerateAgentPubKeyResponse, GetDnaDefinitionRequest, GetDnaDefinitionResponse, GrantZomeCallCapabilityRequest, GrantZomeCallCapabilityResponse, InstallAppRequest, InstallAppResponse, IssueAppAuthenticationTokenRequest, IssueAppAuthenticationTokenResponse, ListAppInterfacesRequest, ListAppInterfacesResponse, ListAppsRequest, ListAppsResponse, ListCellIdsRequest, ListCellIdsResponse, ListDnasRequest, ListDnasResponse, RegisterDnaRequest, RegisterDnaResponse, StorageInfoRequest, StorageInfoResponse, UninstallAppRequest, UninstallAppResponse, UpdateCoordinatorsRequest, UpdateCoordinatorsResponse } from "./types.js"; | ||
/** | ||
@@ -111,2 +111,3 @@ * A class for interacting with a conductor's Admin API. | ||
storageInfo: Requester<StorageInfoRequest, StorageInfoResponse>; | ||
issueAppAuthenticationToken: Requester<IssueAppAuthenticationTokenRequest, IssueAppAuthenticationTokenResponse>; | ||
dumpNetworkStats: Requester<DumpNetworkStatsRequest, DumpNetworkStatsResponse>; | ||
@@ -113,0 +114,0 @@ /** |
@@ -128,2 +128,3 @@ import { getLauncherEnvironment } from "../../environments/launcher.js"; | ||
storageInfo = this._requester("storage_info"); | ||
issueAppAuthenticationToken = this._requester("issue_app_authentication_token"); | ||
dumpNetworkStats = this._requester("dump_network_stats"); | ||
@@ -130,0 +131,0 @@ // zome call signing related methods |
@@ -1,7 +0,62 @@ | ||
import { AgentPubKey, CellId, DnaHash, DnaProperties, InstalledAppId, NetworkInfo, RoleName, Timestamp } from "../../types.js"; | ||
import { AppInfo, ClonedCell, FunctionName, MembraneProof, NetworkSeed, ZomeName } from "../admin/types.js"; | ||
import { Requester } from "../common.js"; | ||
import { UnsubscribeFunction } from "emittery"; | ||
import { AgentPubKey, AppInfo, CapSecret, CellId, ClonedCell, DnaHash, DnaProperties, FunctionName, MembraneProof, NetworkInfo, NetworkSeed, Nonce256Bit, RoleName, Timestamp, ZomeName } from "../../index.js"; | ||
/** | ||
* @public | ||
*/ | ||
export type NonProvenanceCallZomeRequest = Omit<CallZomeRequest, "provenance">; | ||
/** | ||
* @public | ||
*/ | ||
export type RoleNameCallZomeRequest = Omit<NonProvenanceCallZomeRequest, "cell_id"> & { | ||
role_name: RoleName; | ||
}; | ||
/** | ||
* @public | ||
*/ | ||
export type RoleNameCallZomeRequestSigned = Omit<CallZomeRequestSigned, "cell_id"> & { | ||
role_name: RoleName; | ||
}; | ||
/** | ||
* @public | ||
*/ | ||
export type AppCallZomeRequest = NonProvenanceCallZomeRequest | RoleNameCallZomeRequest | CallZomeRequestSigned | RoleNameCallZomeRequestSigned; | ||
/** | ||
* @public | ||
*/ | ||
export type AppCreateCloneCellRequest = Omit<CreateCloneCellRequest, "app_id">; | ||
/** | ||
* @public | ||
*/ | ||
export type AppEnableCloneCellRequest = Omit<EnableCloneCellRequest, "app_id">; | ||
/** | ||
* @public | ||
*/ | ||
export type AppDisableCloneCellRequest = Omit<DisableCloneCellRequest, "app_id">; | ||
/** | ||
* @public | ||
*/ | ||
export type AppNetworkInfoRequest = Omit<NetworkInfoRequest, "agent_pub_key">; | ||
/** | ||
* @public | ||
*/ | ||
export interface AppEvents { | ||
signal: AppSignal; | ||
} | ||
/** | ||
* @public | ||
*/ | ||
export interface CallZomeRequestUnsigned extends CallZomeRequest { | ||
cap_secret: CapSecret | null; | ||
nonce: Nonce256Bit; | ||
expires_at: number; | ||
} | ||
/** | ||
* @public | ||
*/ | ||
export interface CallZomeRequestSigned extends CallZomeRequestUnsigned { | ||
signature: Uint8Array; | ||
} | ||
/** | ||
* @public | ||
*/ | ||
export type CallZomeRequestGeneric<Payload> = { | ||
@@ -29,8 +84,2 @@ cell_id: CellId; | ||
*/ | ||
export type AppInfoRequest = { | ||
installed_app_id: InstalledAppId; | ||
}; | ||
/** | ||
* @public | ||
*/ | ||
export type AppInfoResponse = AppInfo | null; | ||
@@ -42,6 +91,2 @@ /** | ||
/** | ||
* The app id that the DNA to clone belongs to | ||
*/ | ||
app_id: InstalledAppId; | ||
/** | ||
* The DNA's role id to clone. | ||
@@ -94,6 +139,2 @@ */ | ||
/** | ||
* The app id that the clone cell belongs to | ||
*/ | ||
app_id: InstalledAppId; | ||
/** | ||
* The clone id or cell id of the clone cell | ||
@@ -174,7 +215,11 @@ */ | ||
*/ | ||
export interface AppApi { | ||
appInfo: Requester<AppInfoRequest, AppInfoResponse>; | ||
callZome: Requester<CallZomeRequest, CallZomeResponse>; | ||
enableCloneCell: Requester<EnableCloneCellRequest, EnableCloneCellResponse>; | ||
disableCloneCell: Requester<DisableCloneCellRequest, DisableCloneCellResponse>; | ||
export interface AppClient { | ||
callZome(args: AppCallZomeRequest, timeout?: number): Promise<any>; | ||
on<Name extends keyof AppEvents>(eventName: Name | readonly Name[], listener: AppSignalCb): UnsubscribeFunction; | ||
appInfo(): Promise<AppInfoResponse>; | ||
myPubKey: AgentPubKey; | ||
createCloneCell(args: AppCreateCloneCellRequest): Promise<CreateCloneCellResponse>; | ||
enableCloneCell(args: AppEnableCloneCellRequest): Promise<EnableCloneCellResponse>; | ||
disableCloneCell(args: AppDisableCloneCellRequest): Promise<DisableCloneCellResponse>; | ||
networkInfo(args: AppNetworkInfoRequest): Promise<NetworkInfoResponse>; | ||
} |
@@ -1,34 +0,50 @@ | ||
import Emittery from "emittery"; | ||
import { CapSecret } from "../../hdk/capabilities.js"; | ||
import { InstalledAppId } from "../../types.js"; | ||
import { WsClient } from "../client.js"; | ||
import { WebsocketConnectionOptions, Requester, Transformer } from "../common.js"; | ||
import { Nonce256Bit } from "../zome-call-signing.js"; | ||
import { AppApi, AppInfoRequest, AppInfoResponse, CallZomeRequest, CallZomeResponse, CreateCloneCellRequest, CreateCloneCellResponse, DisableCloneCellRequest, DisableCloneCellResponse, EnableCloneCellRequest, EnableCloneCellResponse, NetworkInfoRequest, NetworkInfoResponse } from "./types.js"; | ||
import { UnsubscribeFunction } from "emittery"; | ||
import { AgentPubKey, CellId, RoleName } from "../../types.js"; | ||
import { AppAuthenticationToken, AppInfo } from "../admin"; | ||
import { WebsocketConnectionOptions } from "../common.js"; | ||
import { AppCallZomeRequest, AppClient, AppEvents, AppNetworkInfoRequest, AppCreateCloneCellRequest, AppDisableCloneCellRequest, AppEnableCloneCellRequest, AppSignalCb, CallZomeRequest, CallZomeRequestSigned, CallZomeResponse, CreateCloneCellResponse, DisableCloneCellResponse, EnableCloneCellResponse, NetworkInfoResponse } from "./types.js"; | ||
import { WsClient } from "../client"; | ||
/** | ||
* A class to establish a websocket connection to an App interface of a | ||
* Holochain conductor. | ||
* A class to establish a websocket connection to an App interface, for a | ||
* specific agent and app. | ||
* | ||
* @public | ||
*/ | ||
export declare class AppWebsocket extends Emittery implements AppApi { | ||
export declare class AppWebsocket implements AppClient { | ||
readonly client: WsClient; | ||
defaultTimeout: number; | ||
overrideInstalledAppId?: InstalledAppId; | ||
readonly myPubKey: AgentPubKey; | ||
private readonly defaultTimeout; | ||
private readonly emitter; | ||
cachedAppInfo?: AppInfo | null; | ||
private readonly appInfoRequester; | ||
private readonly callZomeRequester; | ||
private readonly createCloneCellRequester; | ||
private readonly enableCloneCellRequester; | ||
private readonly disableCloneCellRequester; | ||
private readonly networkInfoRequester; | ||
private constructor(); | ||
/** | ||
* Instance factory for creating AppWebsockets. | ||
* Instance factory for creating an {@link AppWebsocket}. | ||
* | ||
* @param token - A token to authenticate the websocket connection. Get a token using AdminWebsocket#issueAppAuthenticationToken. | ||
* @param options - {@link (WebsocketConnectionOptions:interface)} | ||
* @returns A new instance of an AppWebsocket. | ||
*/ | ||
static connect(options?: WebsocketConnectionOptions): Promise<AppWebsocket>; | ||
_requester<ReqI, ReqO, ResI, ResO>(tag: string, transformer?: Transformer<ReqI, ReqO, ResI, ResO>): (req: ReqI, timeout?: number | undefined) => Promise<ResO>; | ||
static connect(token: AppAuthenticationToken, options?: WebsocketConnectionOptions): Promise<AppWebsocket>; | ||
/** | ||
* Request the app's info, including all cell infos. | ||
* | ||
* @param timeout - A timeout to override the default. | ||
* @returns The app's {@link AppInfo}. | ||
*/ | ||
appInfo: Requester<AppInfoRequest, AppInfoResponse>; | ||
appInfo(timeout?: number): Promise<AppInfo>; | ||
/** | ||
* Get a cell id by its role name or clone id. | ||
* | ||
* @param roleName - The role name or clone id of the cell. | ||
* @param appInfo - The app info containing all cell infos. | ||
* @returns The cell id or throws an error if not found. | ||
*/ | ||
getCellIdFromRoleName(roleName: RoleName, appInfo: AppInfo): CellId; | ||
/** | ||
* Call a zome. | ||
@@ -40,3 +56,3 @@ * | ||
*/ | ||
callZome: Requester<CallZomeRequest | CallZomeRequestSigned, CallZomeResponse>; | ||
callZome(request: AppCallZomeRequest, timeout?: number): Promise<CallZomeResponse>; | ||
/** | ||
@@ -48,3 +64,3 @@ * Clone an existing provisioned cell. | ||
*/ | ||
createCloneCell: Requester<CreateCloneCellRequest, CreateCloneCellResponse>; | ||
createCloneCell(args: AppCreateCloneCellRequest): Promise<CreateCloneCellResponse>; | ||
/** | ||
@@ -56,3 +72,3 @@ * Enable a disabled clone cell. | ||
*/ | ||
enableCloneCell: Requester<EnableCloneCellRequest, EnableCloneCellResponse>; | ||
enableCloneCell(args: AppEnableCloneCellRequest): Promise<EnableCloneCellResponse>; | ||
/** | ||
@@ -63,7 +79,19 @@ * Disable an enabled clone cell. | ||
*/ | ||
disableCloneCell: Requester<DisableCloneCellRequest, DisableCloneCellResponse>; | ||
disableCloneCell(args: AppDisableCloneCellRequest): Promise<DisableCloneCellResponse>; | ||
/** | ||
* Request network info about gossip status. | ||
* @param args - Specify the DNAs for which you want network info | ||
* @returns Network info for the specified DNAs | ||
*/ | ||
networkInfo: Requester<NetworkInfoRequest, NetworkInfoResponse>; | ||
networkInfo(args: AppNetworkInfoRequest): Promise<NetworkInfoResponse>; | ||
/** | ||
* Register an event listener for signals. | ||
* | ||
* @param eventName - Event name to listen to (currently only "signal"). | ||
* @param listener - The function to call when event is triggered. | ||
* @returns A function to unsubscribe the event listener. | ||
*/ | ||
on<Name extends keyof AppEvents>(eventName: Name | readonly Name[], listener: AppSignalCb): UnsubscribeFunction; | ||
private static requester; | ||
private containsCell; | ||
} | ||
@@ -73,16 +101,2 @@ /** | ||
*/ | ||
export interface CallZomeRequestUnsigned extends CallZomeRequest { | ||
cap_secret: CapSecret | null; | ||
nonce: Nonce256Bit; | ||
expires_at: number; | ||
} | ||
/** | ||
* @public | ||
*/ | ||
export interface CallZomeRequestSigned extends CallZomeRequestUnsigned { | ||
signature: Uint8Array; | ||
} | ||
/** | ||
* @public | ||
*/ | ||
export declare const signZomeCall: (request: CallZomeRequest) => Promise<CallZomeRequestSigned>; |
@@ -0,43 +1,65 @@ | ||
import Emittery from "emittery"; | ||
import { omit } from "lodash-es"; | ||
import { CellType } from "../admin"; | ||
import { catchError, DEFAULT_TIMEOUT, getBaseRoleNameFromCloneId, HolochainError, isCloneId, promiseTimeout, requesterTransformer, } from "../common.js"; | ||
import { getHostZomeCallSigner, getLauncherEnvironment, signZomeCallElectron, signZomeCallTauri, } from "../../environments/launcher"; | ||
import { decode, encode } from "@msgpack/msgpack"; | ||
import { getNonceExpiration, getSigningCredentials, randomNonce, } from "../zome-call-signing"; | ||
import { encodeHashToBase64 } from "../../utils"; | ||
import { hashZomeCall } from "@holochain/serialization"; | ||
import { decode, encode } from "@msgpack/msgpack"; | ||
import _sodium from "libsodium-wrappers"; | ||
import Emittery from "emittery"; | ||
import { getLauncherEnvironment, signZomeCallTauri, signZomeCallElectron, getHostZomeCallSigner, } from "../../environments/launcher.js"; | ||
import { encodeHashToBase64 } from "../../utils/base64.js"; | ||
import { WsClient } from "../client.js"; | ||
import { DEFAULT_TIMEOUT, catchError, promiseTimeout, requesterTransformer, HolochainError, } from "../common.js"; | ||
import { getNonceExpiration, getSigningCredentials, randomNonce, } from "../zome-call-signing.js"; | ||
import { WsClient } from "../client"; | ||
/** | ||
* A class to establish a websocket connection to an App interface of a | ||
* Holochain conductor. | ||
* A class to establish a websocket connection to an App interface, for a | ||
* specific agent and app. | ||
* | ||
* @public | ||
*/ | ||
export class AppWebsocket extends Emittery { | ||
export class AppWebsocket { | ||
client; | ||
myPubKey; | ||
defaultTimeout; | ||
overrideInstalledAppId; | ||
constructor(client, defaultTimeout, overrideInstalledAppId) { | ||
super(); | ||
emitter; | ||
cachedAppInfo; | ||
appInfoRequester; | ||
callZomeRequester; | ||
createCloneCellRequester; | ||
enableCloneCellRequester; | ||
disableCloneCellRequester; | ||
networkInfoRequester; | ||
constructor(client, appInfo, defaultTimeout) { | ||
this.client = client; | ||
this.myPubKey = appInfo.agent_pub_key; | ||
this.defaultTimeout = defaultTimeout ?? DEFAULT_TIMEOUT; | ||
this.emitter = new Emittery(); | ||
this.cachedAppInfo = appInfo; | ||
this.appInfoRequester = AppWebsocket.requester(this.client, "app_info", this.defaultTimeout); | ||
this.callZomeRequester = AppWebsocket.requester(this.client, "call_zome", this.defaultTimeout, callZomeTransform); | ||
this.createCloneCellRequester = AppWebsocket.requester(this.client, "create_clone_cell", this.defaultTimeout); | ||
this.enableCloneCellRequester = AppWebsocket.requester(this.client, "enable_clone_cell", this.defaultTimeout); | ||
this.disableCloneCellRequester = AppWebsocket.requester(this.client, "disable_clone_cell", this.defaultTimeout); | ||
this.networkInfoRequester = AppWebsocket.requester(this.client, "network_info", this.defaultTimeout); | ||
// Ensure all super methods are bound to this instance because Emittery relies on `this` being the instance. | ||
// Please retain until the upstream is fixed https://github.com/sindresorhus/emittery/issues/86. | ||
Object.getOwnPropertyNames(Emittery.prototype).forEach((name) => { | ||
const to_bind = this[name]; | ||
const to_bind = this.emitter[name]; | ||
if (typeof to_bind === "function") { | ||
this[name] = | ||
to_bind.bind(this); | ||
this.emitter[name] = | ||
to_bind.bind(this.emitter); | ||
} | ||
}); | ||
this.client = client; | ||
this.defaultTimeout = | ||
defaultTimeout === undefined ? DEFAULT_TIMEOUT : defaultTimeout; | ||
this.overrideInstalledAppId = overrideInstalledAppId; | ||
this.client.on("signal", (signal) => { | ||
if (this.containsCell(signal.cell_id)) { | ||
this.emitter.emit("signal", signal).catch(console.error); | ||
} | ||
}); | ||
} | ||
/** | ||
* Instance factory for creating AppWebsockets. | ||
* Instance factory for creating an {@link AppWebsocket}. | ||
* | ||
* @param token - A token to authenticate the websocket connection. Get a token using AdminWebsocket#issueAppAuthenticationToken. | ||
* @param options - {@link (WebsocketConnectionOptions:interface)} | ||
* @returns A new instance of an AppWebsocket. | ||
*/ | ||
static async connect(options = {}) { | ||
static async connect(token, options = {}) { | ||
// Check if we are in the launcher's environment, and if so, redirect the url to connect to | ||
@@ -51,17 +73,53 @@ const env = getLauncherEnvironment(); | ||
} | ||
const wsClient = await WsClient.connect(options.url, options.wsClientOptions); | ||
const appWebsocket = new AppWebsocket(wsClient, options.defaultTimeout, env?.INSTALLED_APP_ID); | ||
wsClient.on("signal", (signal) => appWebsocket.emit("signal", signal)); | ||
return appWebsocket; | ||
const client = await WsClient.connect(options.url, options.wsClientOptions); | ||
await client.authenticate({ token }); | ||
const appInfo = await this.requester(client, "app_info", DEFAULT_TIMEOUT)(null); | ||
if (!appInfo) { | ||
throw new HolochainError("AppNotFound", `The app your connection token was issued for was not found. The app needs to be installed and enabled.`); | ||
} | ||
return new AppWebsocket(client, appInfo, options.defaultTimeout); | ||
} | ||
_requester(tag, transformer) { | ||
return requesterTransformer((req, timeout) => promiseTimeout(this.client.request(req), tag, timeout || this.defaultTimeout).then(catchError), tag, transformer); | ||
} | ||
/** | ||
* Request the app's info, including all cell infos. | ||
* | ||
* @param timeout - A timeout to override the default. | ||
* @returns The app's {@link AppInfo}. | ||
*/ | ||
appInfo = this._requester("app_info", appInfoTransform(this)); | ||
async appInfo(timeout) { | ||
const appInfo = await this.appInfoRequester(null, timeout); | ||
if (!appInfo) { | ||
throw new HolochainError("AppNotFound", `App info not found. App needs to be installed and enabled.`); | ||
} | ||
this.cachedAppInfo = appInfo; | ||
return appInfo; | ||
} | ||
/** | ||
* Get a cell id by its role name or clone id. | ||
* | ||
* @param roleName - The role name or clone id of the cell. | ||
* @param appInfo - The app info containing all cell infos. | ||
* @returns The cell id or throws an error if not found. | ||
*/ | ||
getCellIdFromRoleName(roleName, appInfo) { | ||
if (isCloneId(roleName)) { | ||
const baseRoleName = getBaseRoleNameFromCloneId(roleName); | ||
if (!(baseRoleName in appInfo.cell_info)) { | ||
throw new HolochainError("NoCellForRoleName", `no cell found with role_name ${roleName}`); | ||
} | ||
const cloneCell = appInfo.cell_info[baseRoleName].find((c) => CellType.Cloned in c && c[CellType.Cloned].clone_id === roleName); | ||
if (!cloneCell || !(CellType.Cloned in cloneCell)) { | ||
throw new HolochainError("NoCellForCloneId", `no clone cell found with clone id ${roleName}`); | ||
} | ||
return cloneCell[CellType.Cloned].cell_id; | ||
} | ||
if (!(roleName in appInfo.cell_info)) { | ||
throw new HolochainError("NoCellForRoleName", `no cell found with role_name ${roleName}`); | ||
} | ||
const cell = appInfo.cell_info[roleName].find((c) => CellType.Provisioned in c); | ||
if (!cell || !(CellType.Provisioned in cell)) { | ||
throw new HolochainError("NoProvisionedCellForRoleName", `no provisioned cell found with role_name ${roleName}`); | ||
} | ||
return cell[CellType.Provisioned].cell_id; | ||
} | ||
/** | ||
* Call a zome. | ||
@@ -73,3 +131,24 @@ * | ||
*/ | ||
callZome = this._requester("call_zome", callZomeTransform); | ||
async callZome(request, timeout) { | ||
if (!("provenance" in request)) { | ||
request = { | ||
...request, | ||
provenance: this.myPubKey, | ||
}; | ||
} | ||
if ("role_name" in request && request.role_name) { | ||
const appInfo = this.cachedAppInfo || (await this.appInfo()); | ||
const cell_id = this.getCellIdFromRoleName(request.role_name, appInfo); | ||
const zomeCallPayload = { | ||
...omit(request, "role_name"), | ||
provenance: this.myPubKey, | ||
cell_id: [cell_id[0], cell_id[1]], | ||
}; | ||
return this.callZomeRequester(zomeCallPayload, timeout); | ||
} | ||
else if ("cell_id" in request && request.cell_id) { | ||
return this.callZomeRequester(request, timeout); | ||
} | ||
throw new HolochainError("MissingRoleNameOrCellId", "callZome requires a role_name or cell_id argument"); | ||
} | ||
/** | ||
@@ -81,3 +160,9 @@ * Clone an existing provisioned cell. | ||
*/ | ||
createCloneCell = this._requester("create_clone_cell"); | ||
async createCloneCell(args) { | ||
const clonedCell = this.createCloneCellRequester({ | ||
...args, | ||
}); | ||
this.cachedAppInfo = undefined; | ||
return clonedCell; | ||
} | ||
/** | ||
@@ -89,3 +174,7 @@ * Enable a disabled clone cell. | ||
*/ | ||
enableCloneCell = this._requester("enable_clone_cell"); | ||
async enableCloneCell(args) { | ||
return this.enableCloneCellRequester({ | ||
...args, | ||
}); | ||
} | ||
/** | ||
@@ -96,7 +185,50 @@ * Disable an enabled clone cell. | ||
*/ | ||
disableCloneCell = this._requester("disable_clone_cell"); | ||
async disableCloneCell(args) { | ||
return this.disableCloneCellRequester({ | ||
...args, | ||
}); | ||
} | ||
/** | ||
* Request network info about gossip status. | ||
* @param args - Specify the DNAs for which you want network info | ||
* @returns Network info for the specified DNAs | ||
*/ | ||
networkInfo = this._requester("network_info"); | ||
async networkInfo(args) { | ||
return this.networkInfoRequester({ | ||
...args, | ||
agent_pub_key: this.myPubKey, | ||
}); | ||
} | ||
/** | ||
* Register an event listener for signals. | ||
* | ||
* @param eventName - Event name to listen to (currently only "signal"). | ||
* @param listener - The function to call when event is triggered. | ||
* @returns A function to unsubscribe the event listener. | ||
*/ | ||
on(eventName, listener) { | ||
return this.emitter.on(eventName, listener); | ||
} | ||
static requester(client, tag, defaultTimeout, transformer) { | ||
return requesterTransformer((req, timeout) => promiseTimeout(client.request(req), tag, timeout || defaultTimeout).then(catchError), tag, transformer); | ||
} | ||
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; | ||
} | ||
} | ||
@@ -125,13 +257,4 @@ const callZomeTransform = { | ||
}; | ||
const appInfoTransform = (appWs) => ({ | ||
input: (request) => { | ||
if (appWs.overrideInstalledAppId) { | ||
return { | ||
installed_app_id: appWs.overrideInstalledAppId, | ||
}; | ||
} | ||
return request; | ||
}, | ||
output: (response) => response, | ||
}); | ||
const isSameCell = (cellId1, cellId2) => cellId1[0].every((byte, index) => byte === cellId2[0][index]) && | ||
cellId1[1].every((byte, index) => byte === cellId2[1][index]); | ||
/** | ||
@@ -138,0 +261,0 @@ * @public |
@@ -5,3 +5,10 @@ /// <reference types="ws" /> | ||
import { WsClientOptions } from "./common.js"; | ||
import { AppAuthenticationToken } from "./admin"; | ||
/** | ||
* @public | ||
*/ | ||
export interface AppAuthenticationRequest { | ||
token: AppAuthenticationToken; | ||
} | ||
/** | ||
* A WebSocket client which can make requests and receive responses, | ||
@@ -26,2 +33,3 @@ * as well as send and receive signals. | ||
* @param url - The WebSocket URL to connect to. | ||
* @param options - Options for the WsClient. | ||
* @returns An new instance of the WsClient. | ||
@@ -37,2 +45,10 @@ */ | ||
/** | ||
* Authenticate the client with the conductor. | ||
* | ||
* This is only relevant for app websockets. | ||
* | ||
* @param request - The authentication request, containing an app authentication token. | ||
*/ | ||
authenticate(request: AppAuthenticationRequest): Promise<void>; | ||
/** | ||
* Send requests to the connected websocket. | ||
@@ -44,2 +60,3 @@ * | ||
request<Response>(request: unknown): Promise<Response>; | ||
private exchange; | ||
private sendMessage; | ||
@@ -46,0 +63,0 @@ private handleResponse; |
import { decode, encode } from "@msgpack/msgpack"; | ||
import Emittery from "emittery"; | ||
import IsoWebSocket from "isomorphic-ws"; | ||
import { SignalType } from "./app/types.js"; | ||
import { HolochainError } from "./common.js"; | ||
import { SignalType } from "./app"; | ||
/** | ||
@@ -90,2 +90,3 @@ * A WebSocket client which can make requests and receive responses, | ||
* @param url - The WebSocket URL to connect to. | ||
* @param options - Options for the WsClient. | ||
* @returns An new instance of the WsClient. | ||
@@ -118,2 +119,20 @@ */ | ||
/** | ||
* Authenticate the client with the conductor. | ||
* | ||
* This is only relevant for app websockets. | ||
* | ||
* @param request - The authentication request, containing an app authentication token. | ||
*/ | ||
async authenticate(request) { | ||
return this.exchange(request, (request, resolve) => { | ||
const encodedMsg = encode({ | ||
type: "authenticate", | ||
data: encode(request), | ||
}); | ||
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); | ||
}); | ||
} | ||
/** | ||
* Send requests to the connected websocket. | ||
@@ -125,23 +144,11 @@ * | ||
async request(request) { | ||
return this.exchange(request, this.sendMessage.bind(this)); | ||
} | ||
exchange(request, sendHandler) { | ||
if (this.socket.readyState === this.socket.OPEN) { | ||
const promise = new Promise((resolve, reject) => { | ||
this.sendMessage(request, resolve, reject); | ||
sendHandler(request, resolve, reject); | ||
}); | ||
return promise; | ||
} | ||
else if (this.url) { | ||
const response = new Promise((resolve, reject) => { | ||
// typescript forgets in this promise scope that this.url is not undefined | ||
const socket = new IsoWebSocket(this.url, this.options); | ||
this.socket = socket; | ||
socket.onerror = (errorEvent) => { | ||
reject(new HolochainError("ConnectionError", `could not connect to Holochain Conductor API at ${this.url} - ${errorEvent.error}`)); | ||
}; | ||
socket.onopen = () => { | ||
this.sendMessage(request, resolve, reject); | ||
}; | ||
this.setupSocket(); | ||
}); | ||
return response; | ||
} | ||
else { | ||
@@ -148,0 +155,0 @@ return Promise.reject(new Error("Socket is not open")); |
@@ -19,3 +19,3 @@ /// <reference types="ws" /> | ||
*/ | ||
export type RequesterUnit<Res> = () => Promise<Res>; | ||
export type RequesterNoArg<Res> = (timeout?: number) => Promise<Res>; | ||
/** | ||
@@ -22,0 +22,0 @@ * @public |
@@ -14,4 +14,3 @@ const ERROR_TYPE = "error"; | ||
const response = await requester(input, timeout); | ||
const output = transform.output(response.data); | ||
return output; | ||
return transform.output(response.data); | ||
}; | ||
@@ -18,0 +17,0 @@ const identity = (x) => x; |
export { hashZomeCall } from "@holochain/serialization"; | ||
export * from "./admin/index.js"; | ||
export * from "./app-agent/index.js"; | ||
export * from "./app/index.js"; | ||
export { IsoWebSocket, WsClient } from "./client.js"; | ||
export { IsoWebSocket, WsClient, AppAuthenticationRequest } from "./client.js"; | ||
export { CloneId, HolochainError, getBaseRoleNameFromCloneId, isCloneId, type Requester, type Transformer, type WebsocketConnectionOptions, type WsClientOptions, } from "./common.js"; | ||
export * from "./zome-call-signing.js"; |
export { hashZomeCall } from "@holochain/serialization"; | ||
export * from "./admin/index.js"; | ||
export * from "./app-agent/index.js"; | ||
export * from "./app/index.js"; | ||
@@ -5,0 +4,0 @@ export { IsoWebSocket, WsClient } from "./client.js"; |
@@ -1,3 +0,3 @@ | ||
import { CallZomeRequest } from "../api/app/types.js"; | ||
import { CallZomeRequestSigned, CallZomeRequestUnsigned } from "../api/app/websocket.js"; | ||
import { CallZomeRequest } from "../api"; | ||
import { CallZomeRequestSigned, CallZomeRequestUnsigned } from "../api"; | ||
import { InstalledAppId } from "../types.js"; | ||
@@ -4,0 +4,0 @@ export interface LauncherEnvironment { |
import { encode } from "@msgpack/msgpack"; | ||
import { invoke } from "@tauri-apps/api/tauri"; | ||
import { getNonceExpiration, randomNonce } from "../api/zome-call-signing.js"; | ||
import { getNonceExpiration, randomNonce } from "../api"; | ||
const __HC_LAUNCHER_ENV__ = "__HC_LAUNCHER_ENV__"; | ||
@@ -5,0 +5,0 @@ const __HC_ZOME_CALL_SIGNER__ = "__HC_ZOME_CALL_SIGNER__"; |
{ | ||
"name": "@holochain/client", | ||
"version": "0.17.0-dev.9", | ||
"version": "0.17.0-dev.10", | ||
"description": "A JavaScript client for the Holochain Conductor API", | ||
"author": "Holochain Foundation <info@holochain.org> (http://holochain.org)", | ||
"author": "Holochain Foundation <info@holochain.org> (https://holochain.org)", | ||
"license": "CAL-1.0", | ||
@@ -35,3 +35,4 @@ "repository": { | ||
"lint": "eslint --fix --ext .ts src test .eslintrc.cjs", | ||
"test:app-agent": "RUST_LOG=error RUST_BACKTRACE=1 tsx test/e2e/app-agent-websocket.ts", | ||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", | ||
"test:app-agent": "RUST_LOG=error RUST_BACKTRACE=1 tsx test/e2e/app-websocket.ts", | ||
"test:utils": "RUST_LOG=error RUST_BACKTRACE=1 tsx test/e2e/utils.ts", | ||
@@ -38,0 +39,0 @@ "test": "RUST_LOG=error RUST_BACKTRACE=1 tsx test/index.ts", |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
128840
54
3577
1