# Javascript Client for RealityHub API

A helper module to connect third-party modules to RealityHub.

## Support Requests

This repository is for distribution only. For support please visit: [Zero Density Support](https://support.zerodensity.io).

## Installation

```bash
npm install @zerodensity/realityhub-api
```

## Using In a Browser

The client can be bundled for browsers with a bundling tool (e.g. rollup).

## Usage

### Connecting to Hub

```js
import { BrokerClient } from '@zerodensity/realityhub-api';

BrokerClient.initModule({
  menuTitle: '<menu title>',
  clientModuleName: '<vendor name>.<your client module name>',
  moduleName: '<vendor name>.<your backend module name>',
  serverURL: '<your backend module>',
  hub: {
    host: '127.0.0.1',
    port: 80,
  },
}).then((brokerClient) => {
  // brokerClient is connected to RealityHub and ready to use
});
```

### Listing Reality 5 (API v1.2+) engines

```js
brokerClient.api.hub.reality5_1_2_world
  .listEngines()
  .then((engines) => {
    console.log(engines);

    /**
     * [
     *    {
     *      ip: '172.16.1.181',
     *      port: 50052,
     *      id: 79,
     *      name: 'ZDHQ-HUB-AMPERE',
     *      ready: true,
     *      status: 'connected',
     *      fps: '', // If there is no UE5 node, FPS will be an empty string
     *      engineType: 'RE5_1_2'
     *    }
     *  ]
     */
  })
  .catch((ex) => console.trace(ex));
```

### Listing the nodes running on an Reality 5 engine (API v1.2+)

```js
brokerClient.api.hub.reality5_1_2_world
  .getNodes(79 /* id of the engine */)
  .then((nodes) => {
    console.log(nodes);

    /**
     * {
     *    ...
     *    // The keys are the NodePath
     *    Cyclorama: {
     *       ...
     *       Functions: {
     *        // The keys are the FunctionPath
     *        'Cyclorama/AddProjection': {...},
     *        'Cyclorama/ClearProjection': {...},
     *       }
     *       ...
     *    }
     *    ...
     * }
     */

    // NOTE: For backward compatiblity, the NodePath and FunctionPath omit the leading '/'.
  })
  .catch((ex) => console.trace(ex));
```

### Calling a Node's Function

You can use `callNodeFunction(params[, engineIds])` to call a node function. `engineIds` is an array
of engine IDs. If `engineIds` is not supplied then the function will be called on all the engines.

```js
/**
 * Cyclorama's Add Projection function is called
 * @param {object} params
 * @param {string} params.NodePath
 * @param {string} params.PropertyPath
 * @param {number} [engineIds] - optional, default is all engines
 */
brokerClient.api.hub.reality5_1_2_world
  .callNodeFunction(
    {
      NodePath: '/Cyclorama',
      FunctionPath: '/Cyclorama/AddProjection',
    },
    [/* engine id = */ 79]
  )
  .catch((ex) => console.trace(ex));
```

### Setting a Node's Property Value

You can use `setNodeProperty(params[, engineIds])` to set a node's property value. `engineIds` is an
array of engine IDs. If `engineIds` not supplied then all of the engines will receive the set node
property command.

```js
/**
 * @param {object} params
 * @param {string} params.NodePath
 * @param {string} params.PropertyPath
 * @param {*} params.Value
 * @param {number} [engineIds] - (optional)
 */
brokerClient.api.hub.reality5_1_2_world.setNodeProperty({
  NodePath: '/Add_f32',
  PropertyPath: 'X',
  Value: 0.240,
}, [/* engine id = */ = 79]).catch((ex) => console.trace(ex));
```

### Interpolating a Node's Property Value

You can use `interpolate(params[, engineIds])` to interpolate a node's property value. `engineIds` is an array of engine IDs. If `engineIds` not supplied then all of the engines will receive the same interpolate command.

```js
brokerClient.api.hub.reality5_1_2_world
  .interpolate(
    {
      NodePath: '/Add_f32',
      PropertyPath: 'X',
      StartValue: 5.0, // optional, default is the current value
      EndValue: 10.0,
      Duration: 2000, // in milliseconds, optional, default is 0
      Delay: 2000, // in milliseconds, optional, default is 0

      /** @type {'Jump' | 'Linear' | 'EaseIn' | 'EaseOut' | 'EaseInOut'} */
      InterpType: 'EaseIn', // optional, default is 'Jump'
    },
    [/* engine id = */ 79]
  )
  .catch((ex) => console.trace(ex));
```

### Getting a Node's Property Value

You can use `getNodeProperty(params[, engineIds])` to get a node's property value. `engineIds` is an array of engine IDs. If `engineIds` not supplied then all of the engines will be queried. That is why this method returns an array of promises.

```js
brokerClient.api.hub.reality5_1_2_world
  .getNodeProperty({ NodePath: '/Add_f32', PropertyPath: 'X' }, [/* engine id = */ 79])
  .then((results) => {
    console.log(results[0].Value);
  })
  .catch((ex) => console.trace(ex));
```

### Registering Your Own Methods to RealityHub

```js
// server.js

// This will simply return the sum of 2 numbers.
function addNumbers(number1, number2) {
  return number1 + number2;
}

// This will return a promise that will resolve after 1 second.
function multiplyNumbers(number1, number2) {
  return new Promise((resolve) => {
    setTimeout(() => {
      const result = number1 * number2;
      resolve(result);
    }, 1000);
  });
}

// This function will resolve after `taskDuration` seconds.
function longTask(taskDuration) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`Long Task has finished after ${taskDuration} seconds.`);
    }, taskDuration * 1000);
  });
}

// Let's register these as our API methods
brokerClient.api.exampleCompany.exampleModule
  .registerAPIHandlers({
    addNumbers,
    multiplyNumbers,
    performSlowTask: longTask,
  })
  .catch((ex) => console.trace(ex));
```

```js
// client.js
brokerClient.api.exampleCompany.exampleModule.addNumbers(3, 5).then((result) => {
  console.log('The result of addNumbers() is', result);
});

brokerClient.api.exampleCompany.exampleModule.performAsyncMultiplication(3, 5).then((result) => {
  console.log('The result of performAsyncMultiplication() is', result);
});

// Default timeout for API requests is 2 seconds. We need to specify a longer timeout for our slow async task.
const timeout = 10 * 1000; // 10 seconds
brokerClient.api.exampleCompany.exampleModule
  .callTimeout(timeout)
  .slowAsyncTask(5)
  .then((result) => console.log('Slow async task returned:', result));
```

### Emitting Events

```js
// server.js

// Emit a random number every second
setInterval(() => {
  // A random number between 0 and 1000.
  const randomNumber = Math.round(Math.random() * 1000);
  brokerClient.api.exampleCompany.exampleModule.emit('randomnumber', randomNumber);
}, 1000);
```

```js
// client.js

brokerClient.api.exampleCompany.exampleModule.on('randomnumber', (randomNumber) => {
  console.log('Received a random number from the server', randomNumber);
});
```

### Questions and Feedback

We have a growing community. You can join to [RealityHub User Group on Facebook](https://www.facebook.com/groups/realityengine).

You are welcome to open an issue if you have found a bug or have a feature request.
