npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

visionappster

v1.0.1

Published

VisionAppster Engine client API

Downloads

22

Readme

Installation

To install the VisionAppster JavaScript API to your Node.js environment:

npm i visionappster

To use in your script:

var VisionAppster = require('visionappster');
new VisionAppster.RemoteObject('http://localhost:2015/info').connect()
  .then(function() { console.log('Connected'); });

If you want to use the API on a web page, download VisionAppster.js from your local Engine. A browser-compatible file (iife bundle) is also available in the Node.js package at dist/VisionAppster.js and in the SDK directory under your VisionAppster installation (sdk/client/js). To get started, insert this to your HTML document:

<script src="VisionAppster.js"></script>
<script type="text/javascript">
new VisionAppster.RemoteObject('http://localhost:2015/info').connect()
  .then(function() { console.log('Connected'); });
</script>

Usage

If you have an instance of the VisionAppster Engine running locally, open its front page and inspect the source code for a comprehensive example.

Connecting and disconnecting

To connect to a remote object, create an instance of VisionAppster.RemoteObject and call its connect method:

const ctrl = new VisionAppster.RemoteObject('http://localhost:2015/manager');
let manager;
ctrl.connect().then(obj => manager = obj);

All methods that need to make requests to the server are asynchronous and return a Promise object. As shown in the example, you can use Promise.then() to fire a callback when the asynchronous operation is done. The connect() method returns a promise that resolves with an object that reflects the functions, properties and signals of the object on the server.

If you need to break a connection, call the disconnect() method.

// isConnected() is a synchronous function
if (ctrl.isConnected()) {
  ctrl.disconnect().then(() => { console.log('disconnected'); });
}

The interface of a remote object is split into two parts. VisionAppster.RemoteObject provides an interface that lets you to control the object. Upon a successful connection, a reflection object is created. As shown above, the connect() function resolves this object. It is also available through the object member of RemoteObject:

const ctrl = new VisionAppster.RemoteObject('http://localhost:2015/manager');
let manager;
ctrl.connect().then(() => manager = ctrl.object);

Instead of explicitly chaining promises, one can use the await keyword in async functions:

async function test() {
  const ctrl = new VisionAppster.RemoteObject('http://localhost:2015/manager');
  const manager = await ctrl.connect();
}

Return channel

The JS client uses a WebSocket connection as a return channel to push data from the server. The return channel is used to pass signals, callbacks and asynchronous function call results.

Unless instructed otherwise, the JS client establishes a single WebSocket connection per host. Thus, if many remote objects are accessed on the same server, they all use a shared return channel.

Return channels are identified by a client ID that is generated on the client side. By convention, UUIDs are used. The JS client automatically generates one for each return channel, but it is also possible to pass a pre-generated UUID to the RemoteObject constructor. This may be useful if you want to control which objects share a return channel. To give one object a dedicated channel, generate the client ID yourself:

const clientId = generateClientUuid();
const ctrl = new VisionAppster.RemoteObject({
  url: 'http://localhost:2015/manager',
  clientId: clientId
});

In some cases, you may want to use a remote object without a return channel. For example, if you just want to set or retrieve the value of a property or call a function, establishing a WebSocket connection would be an overkill. The return channel can be disabled by explicitly passing null as the client ID:

const ctrl = new VisionAppster.RemoteObject({
  url: 'http://localhost:2015/manager',
  clientId: null
});

Calling functions

The functions of the object on the server are mapped to methods in the local reflection object instance. You can call remote functions as if they were ordinary methods of the object instance, but instead of returning a value directly, the methods will return a Promise that resolves with the return value. For example:

manager.start('id://my-app-id')
  .then(pid => console.log(`Process id: ${pid}`));

A list of functions is available at functions/. At a minimum, a function description specifies the name of the function. Optionally, there may be a return type and a list or parameter descriptions.

Usually, the most convenient way of calling a function is to pass arguments as a comma-separated list in the order they appear in the function declaration. If the function provides names for its parameters, it is also possible to pack the parameters in an object. This is equivalent to the previous example:

manager.start({appUri: 'id://my-app-id'})
  .then(pid => console.log(`Process id: ${pid}`));

Although calling functions with named arguments is handy, it comes with a pitfall: if the function to be called takes an object as the first argument, the caller must wrap the object into an array:

remoteObject.funcThatTakesObject([{key: "value"}]);

When a remote function is called, the server responds with the return value. This synchronous mode of operation has the disadvantage that it blocks the HTTP connection to the server: a new request can only be served once the previous one has finished. With simple functions this is usually not an issue, but may become such with functions that take time to complete.

Asynchronous calls can be utilised to avoid blocking the HTTP connection. When an asynchronous request is made, the server puts the call in a queue, returns immediately and pushes the results back through the client’s return channel once the function finishes.

Just like other function calls, asynchronous calls return a Promise that resolves with the return value of the called function. To call a function asynchronously, append .async to the function name:

manager.start.async({appUri: 'id://my-app-id'})
  .then(pid => console.log(`Async call returned a pid: ${pid}`))
  .catch(e => console.log('Async call failed.'));

If the server is not able to enqueue the request or if it does not respond in a timely manner, the returned promise will be rejected. The asyncTimeout member of the function can be used to adjust the timeout:

manager.start.asyncTimeout = 10000;

If the client is connected without a return channel, async functions will not be available.

Reading and writing properties

The properties of an object on the server are mapped directly to properties on the local reflection object instance. Since reading or writing a property may require a remote call, property accessor functions return a Promise.

manager.lastError.get()
  .then(e => { console.log(`Last error: ${e}`); });

// Trying to set a "const" property
manager.lastError.set('Error')
  .catch(e => { console.log(e); }); // Method Not Allowed

The last example shows how to handle remote call errors. It is a good practice to always catch errors, but doing so may become tedious if an error handler is put on each remote call separately. There is however another, more convenient way:

async function test() {
  try {
    await manager.lastError.set('Error');
  } catch (e) {
    console.log(e); // Method Not Allowed
  }
}

A function and a property may have the same name. In this case the property of the RemoteObject instance also works as a function. The properties of a remote object are listed at properties/.

Receiving signals

To invoke an action upon receiving a signal from the server one needs to connect a handler function to it. We’ll do this in an asynchronous function to illustrate how remote calls can be used in an apparently synchronous manner:

async function signalTest(infoCtrl) {
  try {
    const info = await infoCtrl.connect();
    console.log(`VisionAppster AE version: ${await info.appEngineVersion.get()}`);
    info.$userDefinedNameChanged.connect(name => {
      console.log(`Name changed to ${name}`);
    });
  } catch (e) {
    console.log(e);
  }
}

let infoCtrl = new VisionAppster.RemoteObject('http://localhost:2015/info');

signalTest(infoCtrl)
  .then(() => { console.log('done'); });

Signals are separated from functions and properties by a $ prefix. If a property has a change notifier signal, it will be automatically bound to the $changed member of the property. These two ways of connecting a signal are equivalent:

info.$userDefinedNameChanged.connect(() => {});
info.userDefinedName.$changed.connect(() => {});

The latter is easier as it makes it unnecessary to find out which change notifier signal corresponds to which property.

An arbitrary number of functions can be connected to each signal, and connections can be also be broken:

function showName(name) {
  console.log(name);
}

function showFirstChar(name) {
  console.log(name[0]);
}

async function test() {
  // Connect two handler functions
  await info.userDefinedName.$changed.connect(showName);
  await info.userDefinedName.$changed.connect(showFirstChar);

  // Check if a signal is connected to a specific function.
  // This is a synchronous call.
  if (info.userDefinedName.$changed.isConnected(showFirstChar)) {
    console.log('Yep.');
  }

  // Disconnect one of them
  await info.userDefinedName.$changed.disconnect(snowName);
  // Disconnect everything
  await info.userDefinedName.$changed.disconnect();

  // Check if a signal is connected to any function.
  if (info.userDefinedName.$changed.isConnected()) {
    console.log('This will not happen.');
  }
}

By default, the remote object system will find a suitable encoding and data type for the signal’s parameters automatically. In some cases, the result may however not be what you want. A typical example is an image you want to just display instead of processing it on the client side. In such cases, it is possible to parameterize the connection:

function handleImage(blob) {
  console.log(`Received ${blob.size} bytes of encoded image data.`);
}

async function test() {
  let remote = new VisionAppster.RemoteObject('http://localhost:2015/apis/api-id');
  const obj = await remote.connect();
  // Push the image as JPEG and use Blob as the storage object type.
  await obj.$image.connect(handleImage, {mediaType: ['image/jpeg']});
}

The mediaType parameter tells the client’s preferred encoding for each of the signal’s parameters. Depending on the type of the signal, different media types are available. The list of supported encoding schemes evolves constantly and is beyond the scope of this document. It is however always safe to request images as “image/jpeg” or “image/png”.

Note that there is a subtle difference between a media type and an array of media types. If mediaType is a string, it applies to the whole argument array, not individual elements. For example, it is not possible to encode a parameter array as “image/png”, but it may be possible to encode a single argument as an image by specifying ["image/png"] as the media type. On the other hand, “application/json” can be used to encode both the whole array and each individual argument, provided that all of the arguments are representable as JSON.

Finally, it is possible to filter out signal parameters by giving null as the media type. This will save bandwidth if you don’t need all of the parameters. If all parameters are filtered out, the server will send an empty message.

obj.$imgAndParams.connect(
  (params) => console.log('received', params),
  {mediaType: [null, 'application/json']});

Callback functions

The mechanism for invoking callback functions is similar to that of signals, with the exception that at most one handler function can be connected to each callback.

// Let's assume there is a callback with the signature
// int32 plus(int 32 a, int32 b)
// This will return a + b to the server:
obj.$plus.connect((a, b) => a + b);

To find out whether the member of an object is a signal or a callback, you can check the type:

if (obj.$plus instanceof VisionAppster.Callback) {
  console.log('Callback');
} else if (obj.$plus instanceof VisionAppster.Signal) {
  console.log('Signal');
}

Callbacks also work as function call arguments. Let’s assume the server provides a function that has two arguments: a callback function and a value that will be passed to the callback. The function returns whatever the callback returns. The signature of the function is call: (callback: (double) -> double, value: double) -> double.

// Returns 16
let sixteen = await obj.call(value => value * value, 4);

Handling errors

Each RemoteObject instance provides a $connectedChanged signal. The signal has a single Boolean parameter that tells the current status of the connection. This signal is delivered locally and requires no remote call when connected or disconnected. Thus, one does not need to await connect(). This signal is especially useful in recovering lost connections:

ctrl.$connectedChanged.connect(connected => {
  if (!connected) {
    console.log('Connection lost. Reconnecting in a second.');
    setTimeout(() => ctrl.connect(), 1000);
  }
});

If the connection breaks spontaneously, calling connect() will try to automatically re-register all pushable sources (signals and callbacks) to the return channel. If you call disconnect() yourself, all connections must be manually re-established after reconnecting.

Other errors such as unexpected server responses and failures in decoding pushed data are signaled through the $error signal. The signal has one parameter that is an Error object. These errors are usually recoverable and don’t cause a connection failure.

ctrl.$error.connect(error => console.log(error.message));

Errors in functions that are called directly must be handled by the caller. In the simplest case:

ctrl.connect().catch(e => console.log(e));