WSLinkClient

Introduction

vtkWSLinkClient is a WSLink client for talking to a server over WebSocket

Methods

beginBusy

Virtually increase work load to maybe keep isBusy() on
while executing a synchronous task.

connect

Initiate the connection with the server

Argument Type Required Description
config Object Yes
configDecorator Function No (default: null)

disconnect

Disconnect from server

Argument Type Required Description
timeout Number Yes amount of second to wait before the server exit as well. If we want to avoid the server from quitting, -1 should be provided. (default=60)

endBusy

Virtually decreasing work load to maybe free isBusy()
after executing a synchronous task. Other async calls
could still keep the state as busy.

extend

Method use to decorate a given object (publicAPI+model) with vtkWSLinkClient characteristics.

Argument Type Required Description
publicAPI Yes object on which methods will be bounds (public)
model Yes object on which data structure will be bounds (protected)
initialValues object No (default: {})

getConfig

getConfigDecorator

Returns

Type Description
Function configDecorator function if any was provided

getConnection

getCreateImageStream

Returns

Type Description
Boolean the autoCreate state for imageStream

getImageStream

getNotBusyList

Returns

Type Description
object the current set of methods to ignore from busy state

getProtocols

Get protocols that were either provided in newInstance or via its set

getRemote

invokeBusyChange

isBusy

Return the current state of busy.
Do we still have pending calls?

isConnected

Return true if the client is currently connected to a server

newInstance

Method use to create a new instance of vtkWSLinkClient

Argument Type Required Description
initialValues object No for pre-setting some of its content

onBusyChange

Argument Type Required Description
callback Yes
priority Yes

registerProtocol

Register dynamically a protocol after being connected

Argument Type Required Description
name String Yes
protocol Function Yes

setConfigDecorator

Set a config decorator to possibly alterate the config object that get received from the launcher.

Argument Type Required Description
decorator Yes function for config object

setCreateImageStream

Should the client auto listen to image stream topic by creating its imageStream object

Argument Type Required Description
autoCreate Boolean Yes (default: true)

Returns

Type Description
Boolean true if the set method modified the object

setNotBusyList

Update the list of methods that should be ignore from the busy state monitoring

Returns

Type Description
Boolean true if the set method modified the object

setProtocols

Assign protocols to the client. Those will only be used at connect time and therefore needs to be set before being connected otherwise registerProtocol should be used instead.

Returns

Type Description
Boolean true if the set method modified the object

setSmartConnectClass

Bind optional dependency from WSLink to our current class.
This is mandatory when using that class

import SmartConnect from 'wslink/src/SmartConnect';
import vtkWSLinkClient from '@kitware/vtk.js/IO/Core/WSLinkClient';

vtkWSLinkClient.setSmartConnectClass(SmartConnect);
Argument Type Required Description
smartConnectClass Yes

unregisterProtocol

Remove a given protocol from the available list

Argument Type Required Description
name String Yes

Source

index.d.ts
import { vtkObject, vtkSubscription } from '../../../interfaces';
import vtkImageStream from '../ImageStream';

/**
* Bind optional dependency from WSLink to our current class.
* This is mandatory when using that class
*
* ```
* import SmartConnect from 'wslink/src/SmartConnect';
* import vtkWSLinkClient from '@kitware/vtk.js/IO/Core/WSLinkClient';
*
* vtkWSLinkClient.setSmartConnectClass(SmartConnect);
* ```
*
* @param smartConnectClass
*/
export function setSmartConnectClass(smartConnectClass: object): void;

export interface vtkWSLinkClient extends vtkObject {

/**
* Virtually increase work load to maybe keep isBusy() on
* while executing a synchronous task.
*/
beginBusy(): void;

/**
* Virtually decreasing work load to maybe free isBusy()
* after executing a synchronous task. Other async calls
* could still keep the state as busy.
*/
endBusy(): void;

/**
* Return the current state of busy.
* Do we still have pending calls?
*/
isBusy(): boolean;

/**
* Return true if the client is currently connected to a server
*/
isConnected(): boolean;

/**
* Initiate the connection with the server
* @param {Object} config
* @param {Function} [configDecorator] (default: null)
*/
connect(config: object, configDecorator?: (config: object) => object): Promise<vtkWSLinkClient>;

/**
* Disconnect from server
* @param {Number} timeout amount of second to wait before the server exit as well. If we want to avoid the server from quitting, `-1` should be provided. (default=60)
*/
disconnect(timeout: number): void;

/**
* Register dynamically a protocol after being connected
*
* @param {String} name
* @param {Function} protocol
*/
registerProtocol(name: string, protocol: (session: object) => object): void;

/**
* Remove a given protocol from the available list
*
* @param {String} name
*/
unregisterProtocol(name: string): void;

// --- via macro --

/**
* Assign protocols to the client. Those will only be used at connect time and therefore needs to be set before being connected otherwise `registerProtocol` should be used instead.
* @returns {Boolean} true if the set method modified the object
*/
setProtocols(protocols: Record<string, any>): boolean;

/**
* Get protocols that were either provided in `newInstance` or via its set
*/
getProtocols(): Record<string, any>;

/**
* Update the list of methods that should be ignore from the busy state monitoring
* @returns {Boolean} true if the set method modified the object
*/
setNotBusyList(methodList: [string]): boolean;

/**
* @returns {object} the current set of methods to ignore from busy state
*/
getNotBusyList(): object;

/**
* Should the client auto listen to image stream topic by creating its imageStream object
* @param {Boolean} autoCreate (default: true)
* @returns {Boolean} true if the set method modified the object
*/
setCreateImageStream(autoCreate: boolean): boolean;

/**
* @returns {Boolean} the autoCreate state for imageStream
*/
getCreateImageStream(): boolean;

/**
* Set a config decorator to possibly alterate the config object that get received from the launcher.
* @param decorator function for config object
*/
setConfigDecorator(decorator: (config: object) => object): boolean;

/**
* @returns {Function} configDecorator function if any was provided
*/
getConfigDecorator(): (config: object) => object;

/**
*
*/
getConnection(): any;

/**
*
*/
getConfig(): object;

/**
*
*/
getRemote(): Record<string, any>;

/**
*
*/
getImageStream(): vtkImageStream;

/**
*
* @param callback
* @param priority
*/
onBusyChange(callback: Function, priority: number): vtkSubscription;

/**
*
*/
invokeBusyChange(): void;

onConnectionReady(callback: (httpReq: any) => void): vtkSubscription;
// invokeConnectionReady(): void

onConnectionError(callback: (httpReq: any) => void): vtkSubscription;
// invokeConnectionError(): void

onConnectionClose(callback: (httpReq: any) => void): vtkSubscription;
// invokeConnectionClose(): void
}

/**
* Method use to decorate a given object (publicAPI+model) with vtkWSLinkClient characteristics.
*
* @param publicAPI object on which methods will be bounds (public)
* @param model object on which data structure will be bounds (protected)
* @param {object} [initialValues] (default: {})
*/
export function extend(publicAPI: object, model: object, initialValues?: object): void;

// ----------------------------------------------------------------------------

/**
* Method use to create a new instance of vtkWSLinkClient
* @param {object} [initialValues] for pre-setting some of its content
*/
export function newInstance(initialValues?: object): vtkWSLinkClient;

/**
* vtkWSLinkClient is a WSLink client for talking to a server over WebSocket
*/
export declare const vtkWSLinkClient: {
newInstance: typeof newInstance,
extend: typeof extend,
// static
setSmartConnectClass: typeof setSmartConnectClass,
};

export default vtkWSLinkClient;
index.js
import macro from 'vtk.js/Sources/macros';

import vtkImageStream from 'vtk.js/Sources/IO/Core/ImageStream';

// ----------------------------------------------------------------------------
// Dependency injection
// ----------------------------------------------------------------------------

let SMART_CONNECT_CLASS = null;

// ----------------------------------------------------------------------------

function setSmartConnectClass(klass) {
SMART_CONNECT_CLASS = klass;
}

// ----------------------------------------------------------------------------
// Busy feedback handling
// ----------------------------------------------------------------------------

function busy(fn, update) {
return (...args) =>
new Promise((resolve, reject) => {
update(1);
fn(...args).then(
(response) => {
update(-1);
resolve(response);
},
(error) => {
update(-1);
reject(error);
}
);
});
}

// ----------------------------------------------------------------------------

function busyWrap(methodMap, update, skipList = []) {
const busyContainer = {};
Object.keys(methodMap).forEach((methodName) => {
if (skipList.indexOf(methodName) === -1) {
busyContainer[methodName] = busy(methodMap[methodName], update);
} else {
busyContainer[methodName] = methodMap[methodName];
}
});
return busyContainer;
}

// ----------------------------------------------------------------------------
// vtkWSLinkClient
// ----------------------------------------------------------------------------

function vtkWSLinkClient(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkWSLinkClient');

// --------------------------------------------------------------------------
// Internal methods
// --------------------------------------------------------------------------

function notifyBusy() {
publicAPI.invokeBusyChange(model.busyCount);
}

// --------------------------------------------------------------------------

function updateBusy(delta = 0) {
model.busyCount += delta;

// Clear any pending timeout
if (model.timeoutId) {
clearTimeout(model.timeoutId);
model.timeoutId = 0;
}

// Delay notification when idle
if (model.busyCount) {
notifyBusy();
} else {
model.timeoutId = setTimeout(notifyBusy, model.notificationTimeout);
}
}

// --------------------------------------------------------------------------
// Public methods
// --------------------------------------------------------------------------

publicAPI.beginBusy = () => updateBusy(+1);
publicAPI.endBusy = () => updateBusy(-1);
publicAPI.isBusy = () => !!model.busyCount;
publicAPI.isConnected = () => !!model.connection;

// --------------------------------------------------------------------------

publicAPI.connect = (config = {}, configDecorator = null) => {
if (!SMART_CONNECT_CLASS) {
return Promise.reject(new Error('Need to provide SmartConnect'));
}
if (model.connection) {
return Promise.reject(new Error('Need to disconnect first'));
}

model.config = config;
model.configDecorator = configDecorator || model.configDecorator;
return new Promise((resolve, reject) => {
model.smartConnect = SMART_CONNECT_CLASS.newInstance({
config,
configDecorator: model.configDecorator,
});

// ready
model.smartConnect.onConnectionReady((connection) => {
model.connection = connection;
model.remote = {};
model.config = model.smartConnect.getConfig();
const session = connection.getSession();

// Link remote API
model.protocols = model.protocols || {};
Object.keys(model.protocols).forEach((name) => {
model.remote[name] = busyWrap(
model.protocols[name](session),
updateBusy,
model.notBusyList
);
});

// Handle image stream if needed
if (model.createImageStream) {
model.imageStream = vtkImageStream.newInstance();
model.imageStream.connect(session);
}

// Forward ready info as well
publicAPI.invokeConnectionReady(publicAPI);

resolve(publicAPI);
});

// error
model.smartConnect.onConnectionError((error) => {
publicAPI.invokeConnectionError(error);
reject(error);
});

// close
model.smartConnect.onConnectionClose((close) => {
publicAPI.invokeConnectionClose(close);
reject(close);
});

// Start connection
model.smartConnect.connect();
});
};

// --------------------------------------------------------------------------

publicAPI.disconnect = (timeout = 60) => {
if (model.connection) {
model.connection.destroy(timeout);
model.connection = null;
}
};

// --------------------------------------------------------------------------

publicAPI.registerProtocol = (name, protocol) => {
model.remote[name] = busyWrap(
protocol(model.connection.getSession()),
updateBusy,
model.notBusyList
);
};

// --------------------------------------------------------------------------

publicAPI.unregisterProtocol = (name) => {
delete model.remote[name];
};
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
// protocols: null,
// connection: null,
// config: null,
// imageStream
notBusyList: [],
busyCount: 0,
timeoutId: 0,
notificationTimeout: 50,
createImageStream: true,
// configDecorator: null,
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);

// Object methods
macro.obj(publicAPI, model);
macro.setGet(publicAPI, model, [
'protocols',
'notBusyList',
'createImageStream',
'configDecorator',
]);
macro.get(publicAPI, model, [
'connection',
'config',
'remote',
'imageStream',
]);
macro.event(publicAPI, model, 'BusyChange');
macro.event(publicAPI, model, 'ConnectionReady');
macro.event(publicAPI, model, 'ConnectionError');
macro.event(publicAPI, model, 'ConnectionClose');

// Object specific methods
vtkWSLinkClient(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkWSLinkClient');

// ----------------------------------------------------------------------------

export default { newInstance, extend, setSmartConnectClass };