0.4.3 • Published 4 years ago

node-gotapi v0.4.3

Weekly downloads
4
License
MIT
Repository
github
Last release
4 years ago

node-gotapi

The node-gotapi is a Node.js implementation of the Generic Open Terminal API Framework (GotAPI) 1.1 standardized by the Open Mobile Alliance (OMA).

The OMA Generic Open Terminal API Framework (GotAPI) 1.1 is literally a programing framework specification. It's fair to say that the GotAPI is a framework based on the microservices architecture. The GotAPI is mainly used for an application which connects external devices such as BLE devices, USB devices, IP-based network devices, and so on.

One of the outstanding characteristics of the GotAPI is shared-nothing MVC (Model–View–Controller) model. An application using the GotAPI consists of a front-end application (View), GotAPI Server (Controller), and Plug-Ins (Models). In the GotAPI architecture, each components communicates using message channels in each other. The OMA GotAPI specification defines the Interfaces between the View ,the Controller, and the Model.

Thanks to the characteristics mentioned above, you can use not only Plug-Ins developed by yourself, but also Plug-Ins developed by third-parties. Or, if you are a manufacture of some kind of devices or an enthusiast, you can develop only a Plug-In controlling the device and release it to the public.

The GotAPI Server (Controller) runs a HTTP(S) server and a WebSocket server inside for the front-end application. The GotAPI Server (Controller) exposes web APIs (REST on HTTP and JSON on WebSocket) for the front-end application (View).

The message channels between the GotAPI Server (Controller) and the Plug-Ins (Models) depends on the GotAPI implementation. The OMA GotAPI specification defines that the Intents for Android OS are used for the channels. The node-gotapi provides a messaging mechanism for the channel.

The OMA GotAPI specification is standardized mainly for smartphones (Android, iOS) applications. Actually an implementation for Android and iOS has been open-sourced on GitHub by the name "DeviceConnect". However, the concept and the architecture specified in the GotAPI specification are useful for not only smartphone but also PCs, home gateways, Linux-based single board computers such as Raspberry Pi, and so on. That's why this node-gotapi has been developed. The node-gotapi is developed using Node.js, it can be used on a variety of operating systems as long as Node.js can be installed.


Table of Contents


Installation

Dependencies

How to install

The simplest way to install the node-gotapi is to use the npm command:

$ cd ~
$ npm install fs-extra
$ npm install pem
$ npm install websocket
$ npm install node-gotapi
$ cp -a ~/node_modules/node-gotapi ~/node-gotapi

If you want to install the node-gotapi on Windows, use the PowerShell instead of the Command Prompt.

Never install it globally. That is, never use the -g option of the npm command. The node-gotapi creates some files in its root directory when it is started for the first time. If you installed the node-gotapi using the npm command with the -g option, the node-gotapi could not create the files, you could not start the node-gotapi eventually.

You have to copy the directory of the node-gotapi in the node_modules directory to another location you want. Never run the node-gotapi in the directory where it was installed initially because it will be reset when you update it using the npm command.

Directories

If you install the node-gotapi successfully, you can find files and directories in the root directory of the node-gotapi. If you installed the node-gotapi using the npm command, you can find the root directory at ~/node_modules/node-gotapi.

The node-gotapi consists of the files and directories as follows:

PathDescription
html/This directory is newly created when the node-gotapi is started for the first time. This directory is the document root of the web server for front-end applications. Some sample front-end applications are saved initially.
lib/The relevant JS libraries are saved in this directory
plugins/This directory is newly created when the node-gotapi is started for the first time. The node-gotapi adds this directory into the node.js module search path list. The node modules whose root directory name starts with node-gotapi-plugin- are recognized as Plug-In for the node-gotapi. Some sample Plug-Ins are saved initially.
ssl/A self-signed certificate file and a key file for TLS/SSL are saved in this directory.
config.jsThis file is newly created when the node-gotapi is started for the first time. This file is a configuration file.
start-gotapi.jsThis script starts the node-gotapi.
start-gotapi-debug.jsThis script starts the node-gotapi. Besides, this script shows all messages on the GotAPI-1 Interface on the shell in real time. This script is used for debugging.

Start the GotAPI Server

To start the node-gotapi, run the start-gotapi.js:

$ cd ~/node-gotapi
$ node ./start-gotapi.js

It shows anything on your shell. This mode is used for productions. You can specify some command line options for debugging.

$ node ./start-gotapi.js --enable-debug

It shows all messages on the GotAPI-1 Interface on the shell. If the GotAPI Server was successfully started, you can see the message in the shell as follows:

- The GotAPI Interface-1 has been woken up.
- The GotAPI Interface-4 has been woken up.
  - 3 Plug-Ins were found:
    - Hello World (D:\GitHub\node-gotapi\plugins\node-gotapi-plugin-helloworld)
    - Light Emulator (D:\GitHub\node-gotapi\plugins\node-gotapi-plugin-lightemulator)
    - Simple Clock (D:\GitHub\node-gotapi\plugins\node-gotapi-plugin-simpleclock)
- The GotAPI Interface-5 has been woken up.
- The HTTP server has been woken up.
The GotAPI Server has been started successfully.
Your web application can be accessed at https://localhost:10443

See the section "Starting the GotAPI Server in the debug mode" for details about the command line options for debugging.

Access to https://localhost:10443 using a web browser. You will find a list of the sample applications.

Use the latest version of web browser. Chrome is strongly recommended because you can try the node-gotapi without changing any configurations. Firefox, Safari, and Edge are also supported but you have to disable the TLS/SSL support of the node-gotapi. Internet Explorer is not supported, and it will be never supported in the future.

If you want to shut down the GotAPI Server, press Ctrl + C on your keyboard.

Configurations

You may need to change the some configurations in the config.js. In this configuration file, you can change the security configurations, the TCP port numbers, TLS/SSL configurations, and so on. It is recommended to check the all items in the configuration file.


Architecture

Before developing an application using the node-gotapi, you have to understand the architecture of the GotAPI. This section describes what you should know about the architecture of the GotAPI. There are 3 things you should know: the components which the GotAPI consists of, the concept of service, and the communication flows.

Components

The architecture diagram of the GotAPI

The node-gotapi consists of three servers:

  • Web Server For Application
    • This server hosts front-end web applications and serves them to a client computer through HTTP(S).
  • GotAPI Server
    • The GotAPI Server consists of a HTTP(S) Server and a WebSocket Server. The HTTP(S) Server provides a front-end web application with the GotAPI-1 Interface. The WebSocket Server provides a front-end web application with the GotAPI-5 Interface.
    • The GotAPI Server provides Plug-Ins with the GotAPI-4 Interface.

The GotAPI-1, GotAPI-5, GotAPI-4 Interfaces are specified in the OMA GotAPI 1.1 specification. The node-gotoapi is developed based on the specification.

First of all, the web browser on the client computer requests the front-end application and runs it. The application sends a request to the GotAPI Server using the GotAPI-1 Interface. The GotAPI Server passes the request to the relevant Plug-In using the GotAPI-4 Interface.

When the Plug-In receives the request, it returns the response for the request using the GotAPI-4 Interface, then the GotAPI Server passes it to the originated front-end application using the GotAPI-1 Interface.

The communication flow above is used for one-shot requests. Some Plug-Ins support asynchronous push notification. When the Plug-In pushes a notification, the GotAPI Server passes the notification using the GotAPI-5 Interface (WebSocket connection).

The components colored in red in the figure above represents the node-gotapi. In order to use the node-gotapi, you have to develop a front-end application and Plug-Ins.

What do the numbers assigned to the interfaces mean?

In the figure above, there are 3 interfaces: GotAPI-1, GotAPI-4, and GotAPI-5. The numbers assigned to the interfaces are defined in the OMA GotAPI 1.1 specification. You might wonder why the numbers are not sequential. The number 2 and 3 are missing in the figure above. Actually, the OMA GotAPI specification defines the GotAPI-2 and GotAPI-3.

The GotAPI-2 Interface is a channel connected to the GotAPI Auth Server which authorizes front-end applications. The GotAPI Auth Server is a HTTP Server. In the node-gotapi, the HTTP(S) Server plays the roll of the GotAPI Auth Server too. That is, the GotAPI-1 Interface in the node-gotapi plays the roll of the GotAPI-2 Interface.

The GotAPI-3 Interface is a channel between the GotAPI Auth Server and the Policy Management Server. The Policy Management Server is optional and is not described what it is in the OMA GotAPI 1.1 specification in detail. It is just a concept. The node-gotapi does not implement the Policy Management Server, so the GotAPI-3 Interface is not shown in the figure above.

The numbering of the interfaces is just the order in which the interface was added in the specification in the process of standardization. In fact, the GotAPI 1.0 specification define the GotAPI-1, 2, 3, and 4 Interfaces. The GotAPI-5 Interface was newly added in the GotAPI 1.1. Therefore, the number of the interface is 5.

Service

In the GotAPI architecture, a Plug-In has services. A service represents an external device or a set of functionalities grouped for a certain purpose. A service has profiles. A profile represents a functionality. A profile has attributes. An attribute represents a method or a property related the parent profile.

For example, if a service represents a smart sensor with a battery and a temperature sensor, one of the profiles could represent a set of methods related the battery named as battery, the battery profile could have an attribute named as level and an attribute named as charging. The level attribute could return the percent representing the charge level of the battery, the charging attribute could return whether the smart sensor is now being charged or not.

The service of the GotAPI

An front-end application sends a request on the GotAPI-1 Interface, the request URL contains the service ID, the profile, and the attribute. If a front-end application sends a request with the URL /gotapi/battery/charging/serviceId=smart-sensor-1 using HTTP method GET, the Plug-In could return the percentage of charge level of the battery of the smart sensor corresponded to the service ID smart-sensor-1.

The Plug-In may have a default attribute so that the front-end application omits the name of attribute in the request URL. For example, if the request URL is /gotapi/battery?serviceId=smart-sensor-1, the Plug-In could assume that the attribute was set to level and return the percent representing the charge level of the battery.

The HTTP method is also an important parameter as one of the request parameters. The behavior of an attribute can depend on the HTTP method of request from the front-end application. For example, if the front-end application sends a request to the onchargingchange attribute using PUT method, the Plug-In could start to monitor charge level and return the events asynchronously each time when the charge level changes. If the HTTP method is DELETE, the Plug-In could stop to monitor the charge level.

As describe above, you can define the behavior for a combination of the profile, attribute, and the HTTP method by yourself. Basically, the GotAPI Server is parameter-agnostic as long as the request is for a Plug-In. The GotAPI Server just passes the parameters to the Plug-In. The API design is up to you.

Communication Flow

Establishing a connection with the GotAPI Server

When the front-end web application connects to the GotAPI Server, it needs to send several requests as follows:

The communication flow to establish a connection with the GotAPI Server

At first, the front-end application generates a key (a random string) for the HMAC Server Authentication specified in the OMA GotAPI 1.1 specification. The GotAPI Server uses the key to generate a HMAC digest for each request from the front-end application.

The front-end application generate a nonce (a random string) for each request, send a request with it. The GotAPI Server calculates a HMAC digest using the key and the nonce, then returns it with the response. The front-end application calculates a HMAC digest using the key and the nonce. Comparing the HMAC digest came from the GotAPI Server and the HMAC digest generated by the front-end application, the front-end application can know whether the GotAPI Server has been spoofed or not after the key was sent to the genuine GotAPI Server.

After the key was sent to the GotAPI Server, the front-end application requests a client ID, then requests an access token with the obtained client ID. Thereafter, the front-end application sends requests with the access token every time.

After that, the front-end application requests available services. The GotAPI Server queries available services to all the installed Plug-Ins, then returns all the available services to the front-end application.

Lastly, the front-end application establishes a WebSocket connection with the GotAPI Server, then it sends the access token to the GotAPI Server. If the access token is valid, the WebSocket connection can be used for the GotAPI-5 Interface.

You don't need to know the details of the transactions described above because the gotapi-clients.js automatically handles these transactions. You just need to call the connect() method exposed in the object created by the gotapi-clients.js.

Calling a Plug-In API

After the front-end application establishes a connection with the GotAPI Server, it can send requests to the Plug-In. When the front-end application send a request to the Plug-In at the first time, the GotAPI Server negotiates the request with the Plug-In.

The communication flow of the first request to a Plug-In

The negotiation can be separated in two parts. At first, the GotAPI Server requests a client ID to the Plug-In. The Plug-In can check the origin of the front-end application (package). If the origin is acceptable, the Plug-In creates a client ID for the front-end application. Note that this client ID is used among the Plug-In and the GotAPI Server. This client ID is different from the client ID used between the front-end application and the GotAPI Server.

After that, the GotAPI Server requests an access token to the Plug-In. This transaction is supposed to be used by the Plug-In to check the scope (profiles which the front-end application wants to use) and ask permission from the user showing a dialog on the screen in the OMA GotAPI specification. But the node-gotapi is not developed for smartphones. Therefore, the node-gotapi does not support the concept of the "scope". But the node-gotapi implements this transaction for future use.

The negotiation is automatically handled by the gotapi-plugin-utils.js which is a helper node module and whose instance is passed to the Plug-In module. You don't need to write codes for the transactions in your Plug-In module. But you can intervene in the transactions if needed. The helper module exposes the event handlers for the transactions. You can check the origin of the front-end application and deny the request from the front-end application.

After the negotiation described above, the first request from the front-end application is passed to the Plug-In. After that, requests from the front-end application will be passed to the Plug-In without the negotiation described above.

Requesting notifications

The node-gotapi implements the notification mechanism. If you want to add a notification feature in your Plug-In, you can push notifications to the front-end application using the method exposed in the object of the gotapi-plugin-util.js.

The communication flow of the notifications

Basically, if the front-end application want the Plug-In to start notification, the HTTP method should be PUT. The HTTP method, the profile, and the attribute are passed to the Plug-In. If the Plug-In determines that the combination of the parameters means a trigger of notification, it returns a response meaning the request was accepted immediately. Then the Plug-In starts to notification process. The notifications are sent using the GotAPI-5 Interface (WebSocket channel). If the front-end application want the Plug-In to stop notification, the HTTP method should be DELETE.

As mentioned before the node-gotapi is parameter-agnostic. What combination of parameters corresponds to what the Plug-In do, is up to you.


Security Considerations

The node-gotapi is intended to be used in a local network such as home, office, and so on. Nonetheless, there are threats to be considered.

Authorization Mechanism for Plug-Ins

The OMA GotAPI specifies a mechanism which a Plug-Ins authorizes a front-end application. The node-gotapi implements the mechanism. The node-gotapi sends the origin of the front-end application, Plug-Ins can determine whether the front-end application should be permitted to use the requested profiles.

TLS/SSL Support

For Confidentiality and Integrity, the node-gotapi supports TLS/SSL for the GotAPI-1 Interface, the GotAPI-5 Interface, and the web server for front-end web applications. The TLS/SSL support is enabled by default. When the node-gotapi is started for the first time, a private key and a self-signed certificate are generated automatically.

Though web browsers show an alert to the user for the first time, the TLS/SSL support is meaningful for confidentiality and integrity nevertheless

It is strongly recommended to use the latest version of Chrome. Firefox, Safari, and Edge don't support AJAX and WebSockts on TLS/SSL with a self-signed certificate in a local network. If you want to use such browsers, the TLS/SSL support has to be disabled.

You can also use your own private key and certificate.

HMAC Server Authentication

The node-gotapi supports the "HMAC server authentication" specified in the OMA GotAPI 1.1 specification. The mechanism provides front-end applications with a way to check if the GotAPI Server is spoofed by a malicious attacker.

The front-end application developer don't need to consider this mechanism because the JavaScript library gotapi-cliend.js handles this mechanism automatically.


Tutorials

This section describes how to develop a Plug-In and how to develop a front-end application.

Creating an one-shot API

In this section, we will develop a simplest application named as "hello world". The "hello world" just echoes the message the front-end application sends. Let's see the code of the Plug-In.

Creating a directory for the Plug-In

First of all, you have to create a directory for this Plug-In in the plugins directory. You can find the plugins directory here by default:

 ~/node_modules/node-gotapi/plugins

The name of the root directory of the Plug-In must start with node-gotapi-plugin-. The directory name of this sample Plug-In is node-gotapi-plugin-helloworld.

$ cd ~/node_module/node-gotapi/plugins
$ mkdir ./node-gotapi-plugin-helloworld
$ cd ./node-gotapi-plugin-helloworld

Creating a package.json

In this directory, you must create a JSON file package.json specified by npmjs.com. Read the npmjs.com for details. The minimal package.json for this Plug-In is as follows:

{
  "name": "node-gotapi-plugin-helloworld",
  "version": "0.0.1",
  "main": "./index.js",
}

The main property is the primary entry point. So the file name of your JavaScript file must be index.js here. Let's see the index.js.

Creating an index.js

'use strict';

let GotapiPlugin = function(util) {
  this.util = util;
  this.info = {
    name: 'Hello World',
    services: [
      {
        serviceId : 'com.github.futomi.hello-world.hello',
        name      : 'hello world',
        online    : true,
        scopes    : ['echo']
      }
    ]
  };
};

GotapiPlugin.prototype.init = function(callback) {
  this.util.init(this.info);
  this.util.onmessage = this.receiveMessage.bind(this);
  callback(this.info);
};

GotapiPlugin.prototype.receiveMessage = function(message) {
  if(message['profile'] === 'echo') {
    message['result'] = 0;
    message['data'] = message['params']['msg'];
  } else {
    message['result'] = 1001;
    message['errorMessage'] = 'Unknow profile was requested.';
  }
  this.util.returnMessage(message);
};

module.exports = GotapiPlugin;

As you can see, you don't need to write the codes for the transactions required by the GotAPI because the helper module do that instead you. You can focus on the logic related to the role of the Plug-In. Let's see the codes in detail step by step.

let GotapiPlugin = function(util) {
  this.util = util;
  ...

};

This is a constructor of this module. The node-gotapi creates an instance from the constructor when the GotAPI Server starts to run. At that time, the node-gotapi passes a GotapiPluginUtil object which helps you to develop the Plug-In. In the code above, the variable util represents the GotapiPluginUtil object. Using the methods exposed by the GotapiPluginUtil object, you are subject to develop the Plug-In.

let GotapiPlugin = function(util) {
  ...
  this.info = {
    name: 'Hello World',
    services: [
      {
        serviceId : 'com.github.futomi.hello-world.hello',
        name      : 'hello world',
        online    : true,
        scopes    : ['echo']
      }
    ]
  };
});

The variable this.info represents the information of this Plug-In. You have to define the information of the Plug-In here. The name property represents the name of this Plug-In. It is just metadeta. You can define the name as you like. The servies property represents the services this module serves. It must be an Array. In this sample, a service is defined.

The serviceId property represents the identifier of the service. Though the node-gotapi does not restrict the naming, it should be universally unique. It is recommended to use the Java-style package name.

The name property represents the name of the service. It is just metadata. You can define the name as you like.

The online property represents the current availability. If the service is always available, it must be true.

The scopes property represents a list of profiles this module provides. It must be an Array. In this sample, a profile is defined.

GotapiPlugin.prototype.init = function(callback) {
  this.util.init(this.info);
  this.util.onmessage = this.receiveMessage.bind(this);
  callback(this.info);
};

This method init() is called by the node-gotapi immediately after the GotAPI Server loads this Plug-In. In this method, you have to do two things:

this.util.init(this.info);

This code initializes the GotapiPluginUtil object. The valiable this.info representing the information of this Plug-In must be passed.

this.util.onmessage = this.receiveMessage.bind(this);

This code attaches an event handler called when a request comes from a front-end application. You have to write the receiveMessage() method as follows:

GotapiPlugin.prototype.receiveMessage = function(message) {
  if(message['profile'] === 'echo') {
    message['result'] = 0;
    message['data'] = message['params']['msg'];
  } else {
    message['result'] = 1001;
    message['errorMessage'] = 'Unknow profile was requested.';
  }
  this.util.returnMessage(message);
};

This method is passed an object (the variable message in the code above) representing the request message from an front-end application. You have to evaluate the value of message['profile']. This value is the attribute which the front-end application requests.

You can also obtain the attribute from the message['attribute'] and evaluate it, though the value is not used in this sample.

You can obtain additional parameters from message['params'] as appropriate. The parameters are set in the request URL by the front-end application.

Lastly, you have to return the result to the GotAPI Server using the this.util.returnMessage() method. You have to append some properties to the message object as follows and execute the this.util.returnMessage() method with the message object as the first argument:

PropertyTypeRequiredDescription
resultNumberRequiredAn integer representing the result of the method. If the method was executed successfully, this value must be 0. Otherwise, the value must be an integer grater than 0.
errorMessageStringOptionalIf the method was failed, you can set a custom error message.
(any)(any)You can set any data representing the result. You can use any property name except the prohibited property names for response. In the sample code above, the data property is set for the response.

You have developed the sample Plug-In now. Let's go to the next step.

Creating the front-end application

The assets of the front-end application must be placed in the document root of the web server for front-end applications provided by the node-gotapi. By default, the document root is the html directory immediately under the root directory of the node-gotapi.

For example, if the HTML file is saved at:

~/node_modules/node-gotapi/html/tutorials/helloworld.html

then the URL is:

https://localhost:10443/tutorials/helloworld.html

by default.

The JavaScript code for helloworld.html is as follows:

<p id="res"></p>

<script src="/gotapi-client.js"></script>

<script>
'use strict';

let gotapi = new GotapiClient();

gotapi.connect().then((services) => {
  return gotapi.request({
    method    : 'get',
    serviceId : 'com.github.futomi.hello-world.echo',
    profile   : 'echo',
    attribute : '',
    msg       : 'hello!'
  });
}).then((res) => {
  document.querySelector('#res').textContent = res['data'];
}).catch((error) => {
  document.querySelector('#res').textContent = error.message;
});

</script>

The node-gotapi provides the JavaScript library gotapi-client.js to help you to develop front-end applications. Though the gotapi-client.js is saved in the document root, you can move it to anywhere as long as the location is in the document root.

In your script, you need to create an instances of the GotapiClient object from the Constructor. In order to communicate with the GotAPI Server and the Plug-In, you have to use the methods exposed by the GotapiClient object. In the code above, the variable gotapi represents the GotapiClient object.

The gotapi.connect() method establishes a connection with the GotAPI Server and returns a Promise object. If the connection was established successfully, an Array representing a list of the available services serviced by the installed Plug-In is passed to the resolve function.

After the connection was established, you can send a request to the Plug-In using the gotapi.request() method. This method takes an object representing the request message. The method, serviceId, profile property are mandatory always. The attribute property and the other properties are optional depending the target Plug-In.

The gotapi.request() method returns a Promise object. An object representing the response is passed to the resolve function. Though the data property of the object represents the response data in this case, it depends on the target Plug-In.

Creating an asynchronous push API

In this section, we will develop "the Simple Clock application". The Plug-In for this application sends notifications with the current time every second. Reading this section, you can learn how to start and the stop the notifications.

Creating the Plug-In

You can find the Plug-in for this application in the directory blow:

~/node_modules/node-gotapi/plugins/node-gotapi-plugin-simpleclock

The package.json is as follows:

{
  "name": "node-gotapi-plugin-simpleclock",
  "version": "0.0.1",
  "main": "./index.js"
}

The index.js is a little bit more complex than the previous one. The Plug-In exposes two APIs: the API to start the notification and the API stop it.

At first, let's look at the constructor:

let GotapiPlugin = function(util) {
  this.util = util;
  this.info = {
    name: 'Simple Clock',
    services: [
      {
        serviceId : 'com.github.futomi.hello-clock.clock',
        name      : 'Simple Clock',
        online    : true,
        scopes    : ['clock']
      }
    ]
  };
  this.ticktack_timer_id = 0;
};

The constructor is pretty much as same as the previous one. The difference is that the this.ticktack_timer_id is declared. This property is used later.

The init() method is as follows:

GotapiPlugin.prototype.init = function(callback) {
  this.util.init(this.info);
  this.util.onmessage = this.receiveMessage.bind(this);
  callback(this.info);
};

The code above is completely as same as the previous one. Basically, the init() method is always same.

The receiveMessage() method is as follows:

GotapiPlugin.prototype.receiveMessage = function(message) {
  if(message['profile'] === 'clock' && message['attribute'] === 'ticktack') {
    this.ticktack(message);
  } else {
    message['result'] = 1001;
    message['errorMessage'] = 'Unknow profile was requested.';
    this.util.returnMessage(message);
  }
};

In this method, the profile and the attribute in the request message are evaluated. If they are valid, the ticktack() method is called.

The ticktack() method is as follows:

GotapiPlugin.prototype.ticktack = function(message) {
  if(message['method'] === 'put') {
    this.startTicktack(message);
  } else if(message['method'] === 'delete') {
    this.stopTicktack(message);
  } else {
    message['result'] = 1001;
    message['errorMessage'] = 'The HTTP Method `' + message['method'] + '` is not supported.';
    this.util.returnMessage(message);
  }
};

What you should pay attention is that the value of the method property is evaluated. The method represents the HTTP method which the front-end application uses for the request. If the method is put, the startTicktack() method is called. Otherwise, if the method is delete, the stopTicktack() method is called. That is, this Plug-In behaves differently depending on the method even if the attribute is same.

The startTicktack() method is as follows:

GotapiPlugin.prototype.startTicktack = function(message) {
  message['result'] = 0;
  this.util.returnMessage(message);

  this.ticktack_timer_id = setInterval(() => {
    message['result'] = 0;
    message['data'] = (new Date()).toLocaleString();
    this.util.pushMessage(message);
  }, 1000);
};

This method returns the response using the this.util.returnMessage() method immediately. After that, a timer is set. What you should pay attention is that the this.util.pushMessage() method is called every second instead of the this.util.returnMessage() method.

While the this.util.returnMessage() method returns a response to the front-end application through the GotAPI-1 Interface (HTTP channel), the this.util.pushMessage() method returns a response to the front-end application through the GotAPI-5 Interface (WebSocket channel).

Creating the front-end application

You can find the front-end application for this application in the directory blow:

~/node_modules/node-gotapi/html/tutorials/simpleclock.html

You can access it at the URL by default as follows:

https://localhost:10443/tutorials/helloworld.html

The HTML code of the front-end application is as follows:

<button id="btn" type="button">Start</button>
<span id="res"></span>

A button is placed in the front-end application. Pressing the button causes to start the notification, then the time reported from the Plug-In is shown in the span element with the id attribute whose value is res. Pressing the button again causes to stop the notification.

The JavaScript code is as follows:

<script>
'use strict';

let gotapi = new GotapiClient();

gotapi.connect().then((services) => {
  let btn_el = document.querySelector('#btn');
  btn_el.addEventListener('click', clickedButton, false);
}).catch((error) => {
  document.querySelector('#res').textContent = error.message;
});

function clickedButton(event) {
  let btn_el = event.target;
  gotapi.request({
    method    : (btn_el.textContent === 'Start') ? 'put' : 'delete',
    serviceId : 'com.github.futomi.hello-clock.clock',
    profile   : 'clock',
    attribute : 'ticktack'
  }).then((res) => {
    gotapi.onmessage = (message) => {
      document.querySelector('#res').textContent = message['data'];
    };
    btn_el.textContent = (btn_el.textContent === 'Start') ? 'Stop' : 'Start';
  }).catch((error) => {
    window.alert(error.message);
  });
}

</script>

API Reference for Plug-In

This section describes the APIs to help you to develop Plug-Ins.

Files and Directories

The Plug-In must be developed as a node module. There are some restrictions for the Plug-In:

  • The name of the root directory of the Plug-In (node module) must start with node-gotapi-plugin-. For example, you want to develop a Plug-In named sample, the name of the root directory must be node-gotapi-plugin-sample.
  • The Plug-In must be a npm package. Especially, the package.json is important. It is recommended to read the description of the package.json.

The simplest package.json is as follows:

{
  "name": "node-gotapi-plugin-sample",
  "version": "0.0.1",
  "main": "./index.js"
}

In this section, it is assumed that the main property of package.json is set to ./index.js.

The Plug-In module is detected by the node-gotapi under conditions as follows:

  • The root directory name starts with node-gotapi-plugin-.
  • The Plug-In module is placed in the plugins directory of the node-gotapi or in the module search path of your node environment.

You can publish your Plug-In module at the npmjs.com. The node-gotapi detects your Plug-In module as long as the conditions described above are satisfied.

Template code of the Plug-In

Your Plug-In must be developed based on the code below:

'use strict';

// Constructor
let GotapiPlugin = function(util) {
  this.util = util;
  // Define the information for your Plug-In
  this.info = {
    name: 'The name of your Plug-In',
    services: [...]
    ]
  };
};

// Basically this method don't need to be modified.
GotapiPlugin.prototype.init = function(callback) {
  this.util.init(this.info);
  this.util.onmessage = this.receiveMessage.bind(this);
  callback(this.info);
};

// This method is called when a request comes from the front-end application
GotapiPlugin.prototype.receiveMessage = function(message) {
  // Do something depending on the parameters as follows:
  // - message['params']['serviceId']
  // - message['profile`]
  // - message['attribute']
  // - message['method'].

  // return a response
  this.util.returnMessage(message);
};

module.exports = GotapiPlugin;

Defining the services which your Plug-In serves

As described the previous section, the information of your Plug-In must be defined in the constructor:

let GotapiPlugin = function(util) {
  ...
  this.info = {
    name: 'The name of your Plug-In',
    services: [...]
    ]
  };
};

The this.info must have the properties as follows:

PropertyTypeRequiredDescription
nameStringRequiredThe name of your Plug-In.
servicesArrayRequiredThe list of objects representing the services which your Plug-In serves.

If the services could not be confirmed at the moment when the Plug-In module is initialized, the `this.info'services' may be an empty string.

this.info = {
  name: 'Sample',
  services: []
};

In this case, you have to attach a callback to the this.util.onservicediscoverry property and return the available services in the callback. See the section this.util.onservicediscoverry for details.

The object representing a service in the `this.info'services' must have the properties as follows:

PropertyTypeRequiredDescription
serviceIdStringRequiredThis property represents the identifier of the service. Though the node-gotapi does not restrict the naming, it should be universally unique. It is recommended to use the Java-style package name.
nameStringRequiredThis property represents the name of the service. It is just metadata. You can define the name as you like.
onlineStringRequiredThis property represents the current availability. If the service is available when the Plug-In initialized, it must be true. Otherwise, it must be false.
scopesArrayRequiredThis property represents a list of profiles this module provides. It must be an Array.
manufacturerStringOptionalIf this service represents the external device which is connected through this Plug-In, this value must be the manufacturer of the external device. Otherwise, this value must be the name of the provider or the developer of this Plug-In.
versionStringOptionalIf this service represents the external device which is connected through this Plug-In, this value must be the version of the external device. Otherwise, this value must be the version of this Plug-In.
typeStringOptionalIf this service represents the external device which is connected through this Plug-In, this value represents the type of the network used to connect to the external device. The value must be either "WiFi", "BLE", "NFC", "USB", or "Bluetooth". If the network type is not one of them, the value of this property must be an empty string.

The object representing a service described above is sent to the front-end application as-is when the Network Service Discovery is processed. You can add some custom property. The code blow shows an example of declaration of the Plug-In information in the constructor:

this.info = {
  name: 'Home Gateway Resource Monitor',
  services: [
    {
      serviceId : 'com.github.futomi.resource',
      name      : 'CPU Monitor',
      online    : true,
      scopes    : ['cpu', 'mem', 'disk'],
      version   : '1.0.0',
      copyright : 'Copyright (c) 2017 Futomi Hatano',
      license   : 'MIT'
    }
  ]
};

In the code above, two custom properties copyright and license are added.

this.util.init() method

This method initializes the this.util object. This method takes the this.info object as an argument. This method must be called in the GotapiPlugin.prototype.init() method at first.

GotapiPlugin.prototype.init = function(callback) {
  this.util.init(this.info);
  ...
};

this.util.onservicediscoverry property

This property is an event handler called when the service discovery process started. If no callback function is attached to this property, the this.info.services property is returned to the GotAPI Server automatically. But if you want to generate the services to be returned on the fly, you can use this property. A callback function must be attach to this property in the the GotapiPlugin.prototype.init() method.

GotapiPlugin.prototype.init = function(callback) {
  this.util.init(this.info);
  ...
  this.util.onservicediscoverry = (message) => {
    message['services'] = [...]; // Define the services on the fly
    this.util.returnServiceDiscovery(message);
  }
  ...
  callback(this.info);
};

A ServiceDiscoveryRequestMessage object is passed to the callback function. In the code above, the variable message represents a ServiceDiscoveryRequestMessage object.

In the callback function attached to the this.util.onservicediscoverry property, an Array object representing the list of the services must be set to the services property in the ServiceDiscoveryRequestMessage object.

Lastly, the this.util.returnServiceDiscovery() method must be called with the ServiceDiscoveryRequestMessage object as the 1st argument.

ServiceDiscoveryRequestMessage object

This object is passed to the callback function attached to the this.util.onservicediscoverry property. This object represents the request of the service discovery. This object consists of the properties as follows:

PropertyTypeDescription
packageStringThe origin of the front-end application (e.g. "https://localhost:10443")
profileStringnetworkServiceDiscovery
attributeStringgetNetworkServices
receiverStringThe application ID of the node-gotapi (e.g. "com.github.futomi.node-gotapi")
servicesArrayan empty Array object

this.util.returnServiceDiscovery() method

This method responds to the service discovery. See the previous section "The this.util.onservicediscoverry property" for details.

this.util.onclinetid property

This property is an event handler called when the request for a client ID comes from the GotAPI Server. If no callback function is attached to this property, a client ID is automatically generated and returned it to the GotAPI Server. That is, the front-end application is unconditionally accepted by default. But if you want to check if the front-end application should be accepted or not, you can use this property. A callback function must be attached to this property in the the GotapiPlugin.prototype.init() method.

GotapiPlugin.prototype.init = function(callback) {
  this.util.init(this.info);
  ...
  this.util.onclinetid = (message) => {
    if(message['package'] !== 'https://localhost:10443') {
      message['accept'] = false;
      message['errorMessage'] = 'I dislike you.';
    }
    this.util.returnClientIdRequest(message);
  }
  ...
  callback(this.info);
};

A ClientIdRequestMessage object is passed to the callback function. In the code above, the variable message represents a ClientIdRequestMessage object.

In the callback function attached to the this.util.onclinetid property, the accept property must be set in the ClientIdRequestMessage object. By default, the value of the accept property is set to true which means the Plug-In accepts the front-end application. If you want to deny, assign false to the accept property;

If you deny the request, you can also define your custom error message setting the errorMessage property of the ClientIdRequestMessage object to your custome message.

Lastly, the this.util.returnClientIdRequest() method must be called with the ClientIdRequestMessage object as the 1st argument.

ClientIdRequestMessage object

This object is passed to the callback function attached to the this.util.onclinetid property. This object represents the request for a client ID. This object consists of the properties as follows:

PropertyTypeDescription
packageStringThe origin of the front-end application (e.g. "https://localhost:10443")
profileStringauthorization
attributeStringcreateClient
receiverStringThe application ID of the node-gotapi (e.g. "com.github.futomi.node-gotapi")
acceptBooleanBy default, the value is true. If you want to deny the request, set it to false
errorMessageStringBy default, the value is an empty string. If you want to define your custom error message, set this property to your custom message.

this.util.returnClientIdRequest() method

This method returns a response for the request for a client ID. See the previous section "The this.util.onclinetid property" for details.

this.util.onaccesstoken property

This property is an event handler called when the request for an access token comes from the GotAPI Server. If no callback function is attached to this property, an access token is automatically generated and returned it to the GotAPI Server. That is, the front-end application is unconditionally accepted by default.

The OMA GotAPI 1.1 specification defines the access token request for authorization of the scope which is a list of profiles the front-end application want to use. But the node-gotapi does not support the scope authorization for Plug-Ins as of now. Therefore, this property is practically as same as the onclinetid property.

But if you want to check if the front-end application should be accepted or not, you can use this property. A callback function must be attached to this property in the the GotapiPlugin.prototype.init() method.

GotapiPlugin.prototype.init = function(callback) {
  this.util.init(this.info);
  ...
  this.util.onaccesstoken = (message) => {
    if(message['package'] !== 'https://localhost:10443') {
      message['accept'] = false;
      message['errorMessage'] = 'I dislike you.';
    }
    this.util.returnAccessTokenRequest(message);
  }
  ...
  callback(this.info);
};

A AccessTokenRequestMessage object is passed to the callback function. In the code above, the variable message represents a AccessTokenRequestMessage object.

In the callback function assigned to the this.util.onaccesstoken property, the accept property must be set in the AccessTokenRequestMessage object. By default, the value of the accept property is set to true which means the Plug-In accepts the front-end application. If you want to deny, assign false to the accept property.

If you deny the request, you can also define your custom error message setting the errorMessage property of the AccessTokenRequestMessage object to your custom message.

Lastly, the this.util.returnAccessTokenRequest() method must be called with the AccessTokenRequestMessage object as the 1st argument.

AccessTokenRequestMessage object

This object is passed to the callback function attached to the this.util.onaccesstoken property. This object represents the request for an access token. This object consists of the properties as follows:

PropertyTypeDescription
packageStringThe origin of the front-end application (e.g. "https://localhost:10443")
profileStringauthorization
attributeStringcreateClient
receiverStringThe application ID of the node-gotapi (e.g. "com.github.futomi.node-gotapi")
clientIdStringThe client ID assigned to the front-end application.
acceptBooleanBy default, the value is true. If you want to deny the request, set it to false
errorMessageStringBy default, the value is an empty string. If you want deny the request and define your custom error message, set this property to your custom message.

this.util.returnAccessTokenRequest method

This method returns a response for the request for a access token. See the previous section "The this.util.onaccesstoken property" for details.

this.util.onmessage property

This property is an event handler called when a message comes from the front-end application through the GotAPI-1 Interface and the GotAPI Server. A callback function must be attached to this property in the the GotapiPlugin.prototype.init() method.

GotapiPlugin.prototype.init = function(callback) {
  this.util.init(this.info);
  ...
  this.util.onmessage = (message) => {
    ...
    message['data'] = 'something';
    this.util.returnMessage(message);
  };
  ...
  callback(this.info);
};

A RequestMessage object is passed to the callback function. In the code above, the variable message represents a RequestMessage object. The RequestMessage object represents a request message coming from the front-end application through the GotAPI Server. See the section "The RequestMessage object" for details.

In this callback function, the data property must be added in the RequestMessage object, then the this.util.returnMessage() method must be called with the RequestMessage object.

RequestMessage object

This object is passed to the callback function attached to the this.util.onmessage property. This object represents a request message coming from the front-end application. The object consists of the properties as follows:

PropertyTypeDescription
paramsObjectThe parameters which the front-end application sent in the request to the GotAPI-1 message.
serviceIdStringThe service ID
(any)StringIf the front-end application sent any other parameters, the parameters are stored in the params property.
packageStringThe origin of the front-end application (e.g. "https://localhost:10443")
profileStringThe profile name
attributeStringThe attribute name
methodStringThe HTTP method used when the front end application sent the request to the GotAPI-1 Interface. The value is either get, post, put, or delete.
clientIdStringThe client ID assigned to the front end application.
accessTokenStringThe access token assigned to the front end application.
receiverStringThe application ID of the node-gotapi (e.g. "com.github.futomi.node-gotapi")

this.util.returnMessage() method

This method returns a response to the front-end application through the GotAPI Server and GotAPI-1 Interface. This method takes an 'RequestMessage` object as the 1st argument.

If the request was accepted successfully, the result property must be set to 0 on the 'RequestMessage` object, then the object must be passed to this method.

You can set any properties to the 'RequestMessage` object as the response data except the prohibited property names for response. In the code snippt below, the data property is added.

If the request failed, the result property must be set to an integer grater than 0 on the 'RequestMessage` object. You can also add the errorMessage property for your custom error message.

By default, the response is returned to the front-end application with HTTP status code 200 if the request succeeded, or 400 (Bad Request) if the request failed. If you want change the HTTP status code, you can add the statusCode property on the 'RequestMessage` object. Be sure that the value of the statusCode is consistent with the result property. If the result property is set to 0, the statusCode must be "2xx Success". Otherwise, it must be "4xx Client errors" or "5xx Server error".

GotapiPlugin.prototype.receiveMessage = function(message) {
  ...
  if(isSuccess) {
    message['result'] = 0;
    message['data'] = 'Something';
    message['statusCode'] = 201;
    this.util.returnMessage(message);
  } else {
    message['result'] = 1001;
    message['errorCode'] = "1001";
    message['errorMessage'] = 'Unknow profile was requested.';
    message['statusCode'] = 400;
    this.util.returnMessage(message);
  }
};

See the section "Creating an one-shot API" for more information.

this.util.pushMessage() method

This method pushes a notification to the front-end application through the GotAPI Server. Unlike the this.util.returnMessage(), the notification sent by this method is transfered to the front-end application through the GotAPI-5 Interface.

This method takes an RequestMessage object as the 1st argument.

The data property must be added to the RequestMessage object, then passed it to this method.

When you want to push an error, the result property must be added to the 'RequestMessage` object, then passed it to this method. You can also add the errorMessage property for your custom error message.

The value of the result must be an integer grater than or equal to 400. Basically it is recommended that the value is assigned to an meaningful HTTP status code. If you want to assign it to your custom error code, see the section "Returning an Error" for details.

See the section "Creating an asynchronous push API" for more information.

Prohibited property names for response

As described above, when the Plug-In is subject to respond for a request, the RequestMessage object representing the request must be passed to the this.util.returnMessage() or the this.util.pushMessage() method as a response.

Though you can append any properties in the RequestMessage object, some property names are prohibited as response data as follows:

if_type
request_id
request_url
params
package
api
profile
attribute
method
receiver
requestCode
clientId
accessToken
action
errorMessage
result
errorCode
errorText
statusCode

Besides, the property name which starts with "_" (underscore) is prohibited as well. That is, you can use a property name "something", while you can not use a property name "_something".

If you use prohibited property names, the values are ignored when it is passed to the front end application.

Returning an Error

If you want to return an error in response to the request from the front-end application, the properties as follows must be added to the RequestMessage object and returned it using the returnMessage() or pushMessage().

PropertyRequiredTypeDescription
resultRequiredNumberThis value represents an error code for the Plug-In. It must be an integer grater than 0. The meaning of the error code depends on the Plug-In.
errorCodeOptionalString
errorMessageOptionalStringThis value is an error message.
statusCodeOptionalNumberThis value represents an HTTP status code. It must be "4xx Client errors" (e.g., 400, 403) or "5xx Server error" (e.g., 500).

The code below shows how to return an error with a custom error code:

GotapiPlugin.prototype.receiveMessage = function(message) {
  ...
  message['result'] = 1009; // Result code 
  message['errorCode'] = "E-03F1"; // Custom error code
  message['errorMessage'] = 'The device was disconnected.';
  message['statusCode'] = 404; // HTTP status code
  this.util.returnMessage(message);
};

API Reference for Front-End Application

This section describes the APIs to help you to develop front-end applications.

GotapiClient object

The node-gotapi serves a JavaScript library for helping to develop front-end applications through the Web Server for front-end applications. You can access it at:

https://localhost:10443/gotapi-client.js

by default.

In order to use it, use a script element in the HTML file of your front-end application like this:

<script src="/gotapi-client.js"></script>
<script>
// Write your code here
</script>

Loading the gotapi-client.js, you can access the GotapiClient constructor. You have to create an instance from the GotapiClient constructor as follows:

let gotapi = new GotapiClient();

The variable gotapi represents a GotapiClient object. You can access some methods exposed by the object.

connect() method

The connect() method establishes a connection with the GotAPI Server. This method runs the service discovery transaction, the client ID request transaction, and the access token request transaction automatically. This method takes no argument.

This method returns a Promise object. If the front-end application successfuly established with the GotAPI Server, an Array object representing the list of the available services is passed to the resolve function. At this point, it is ready to send requests to the Plug-In through the GotAPI Server.

gotapi.connect().then((services) => {
  // Do something
});

You can find the details of the service object in the list of services in the section "Defining the services which your Plug-In serves".

request() method

The request() method sends a request to the Plug-In using the GotAPI-1 Interface. This method takes a Request object representing the request parameters. The Request object is just a hash object, it must have the properties as follows:

PropertyTypeDescription
methodStringThe HTTP method. the value must be either "get", "post", "put", "delete".
servcieIdStringThe service ID. You can know it from the service object obtained from the connect() method.
profileStringThe profile name.
attributeStringThe attribute name.
(any)StringIf the Plug-In requires any parameters, you can add an
0.4.3

4 years ago

0.4.2

5 years ago

0.4.1

5 years ago

0.4.0

5 years ago

0.3.7

5 years ago

0.3.6

6 years ago

0.3.5

6 years ago

0.3.4

6 years ago

0.3.3

7 years ago

0.3.0

7 years ago

0.2.2

7 years ago

0.2.1

7 years ago

0.2.0

7 years ago

0.1.8

7 years ago

0.1.7

7 years ago

0.1.6

7 years ago

0.1.5

7 years ago

0.1.4

7 years ago

0.1.3

7 years ago

0.1.2

7 years ago

0.1.1

7 years ago

0.1.0

7 years ago

0.0.18

7 years ago

0.0.17

7 years ago

0.0.16

7 years ago

0.0.15

7 years ago

0.0.14

7 years ago

0.0.13

7 years ago

0.0.12

7 years ago

0.0.11

7 years ago

0.0.10

7 years ago

0.0.9

7 years ago

0.0.8

7 years ago

0.0.7

7 years ago

0.0.6

7 years ago

0.0.5

7 years ago

0.0.4

7 years ago

0.0.3

7 years ago

0.0.2

7 years ago

0.0.1

7 years ago