const ws = new WebSocket(`wss://${url}/api/events-ws/6/?groupId=&offset=`, ['events-1.0.0', 'token-' + token]);
Overview
This document will give you an overview about the HIRO Graph Developer WS API.
Concepts
Please be familiar with the HIRO Graph and the Websocket Protocol.
Requirements of Namespace, IDs and Attributes
-
All Requirements of HIRO Graph apply.
WS API
In order to use the API you need to register an application, please drop us a line at support@hiro.arago.co. |
-
All initial connect requests must contain a valid access token _TOKEN. Request a valid access token from the HIRO IAM server.
-
All requests will be made against a base $url (e.g.
core.arago.co
). -
All further examples assume
wss://
as the protocol.
WS Event Streaming API (events-1.0.0)
The Streaming endpoint is located at /api/events-ws/
.
The standard steps to follow are:
-
Connect
-
Subscribe to scope (if allscopes set to false)
-
Register a filter
-
Receive data
Name | Description | Default |
---|---|---|
groupId |
subscribe to the same event stream by opening multiple connections with the same groupId. The stream will be partitioned amongst all connections. If connection is lost, the groupId will allow you to receive any missed events. |
'' |
offset |
|
'' |
delta |
Only receive the changes to a vertex, not the whole vertex. Each a attribute in the event body is prefixed with |
false |
allscopes |
By default, events from all scopes are subscribed to. Setting this to false, requires use of the |
true |
Requests
const msg = {
'type': 'token',
'args': {
_TOKEN: 'new token'
}
};
ws.send(JSON.stringify(msg));
const msg = {
'type': 'subscribe',
'id': scope_id // The ogit/_id of the ogit/DataScope (i.e. the scope of your instance) you want to subscribe to
};
ws.send(JSON.stringify(msg));
const msg = {
'type': 'register',
'args': {
'filter-id': 'unique filter id for this websocket',
'filter-type': 'jfilter',
'filter-content': '(element.ogit/_type = ogit/Question)'
}
};
ws.send(JSON.stringify(msg));
const msg = {
'type': 'unregister',
'args': {
'filter-id': 'unique filter id for this websocket'
}
};
ws.send(JSON.stringify(msg));
const msg = {
'type': 'clear',
'args': {}
};
ws.send(JSON.stringify(msg));
Responses
Events contain the originator of the event, the action taken, and the contents.
Standard events
By default, the eventstream sends the entire body of the affected vertex.
{
id: string; // ogit/_id from body
body: {
/* properties like ogit/_id */
"ogit/name": "My name"
},
metadata: {
"ogit/_modified-by": string;
"ogit/_modified-by-app": string;
"ogit/_modified-on": number;
};
nanotime: number;
timestamp: number;
type: "CREATE" | "REPLACE" | "UPDATE" | "CONNECT" | "DISCONNECT" | "DELETE" | "WRITE_TIMESERIES";
}
Delta events
Delta events only contain the changes on the node. Each attribute is prefixed by +
, -
, =
.
{
id: string; // ogit/_id from body
body: {
/* properties like ogit/_id prefixed by change */
"+ogit/firstName": "My name" // Added
"-ogit/lastName": "My name" // Removed
"=ogit/email": "My name" // Changed
},
metadata: {
"ogit/_modified-by": string;
"ogit/_modified-by-app": string;
"ogit/_modified-on": number;
};
nanotime: number;
timestamp: number;
type: "CREATE" | "REPLACE" | "UPDATE" | "CONNECT" | "DISCONNECT" | "DELETE" | "WRITE_TIMESERIES";
}
Filters
In order to only receive certain events, filters can be registered. The filterType
is currently only jfilter
.
Syntax
Operator | Description | Supported types | Filter example |
---|---|---|---|
|
equals to |
String, Number |
|
|
differs from |
String, Number |
|
|
more than |
Number |
|
|
more or equals |
Number |
|
|
less than |
Number |
|
|
less or equals |
Number |
|
|
not |
Filter |
|
|
and |
Filters |
|
|
or |
Filters |
|
wildcards |
matches all |
String |
|
{
'filter-content': '&(element.ogit/_type=ogit/Question)(action=C*)'
}
WS Graph API (graph-2.0.0)
The WS Graph API permits to do REST primitives over WS.
The WS Graph API endpoint is located at /api/graph-ws/
.
Blobs can be fetched via this api, but they can only be stored via REST. |
const ws = new WebSocket(`wss://${url}/api/graph-ws/6/`, ['graph-2.0.0', 'token-' + token]);
ws.addEventListener("open", function() {
if (ws.protocol !== "graph-2.0.0") {
throw new Error("Expecting WebSocket protocol 'graph-2.0.0', got " + ws.protocol);
}
//do something with connection
});
Overview
Websocket API requests are JSON, and have the following structure:
const request =
{
'id': 'mandatory id for matching the reply',
'type': 'the type of the request, e.g. get, create, replace, connect, ...',
'_TOKEN': 'optional, if none specified, the one from the establishing the websocket will be used',
'headers':
{
// headers for the request
},
'body':
{
// body for the request
}
};
ws.send(JSON.stringify(request));
In order to do correct multiplexing in the websocket stream, all responses will be wrapped by an envelope.
A client must buffer all messages until the flag more
is false
in an envelope.
Response Envelope:
{
"id": "id of the request",
"more": true|false // `false` if this is the last message for the response
"multi": true|false // `true` if this response is fragmented, the stream of messages represents an array as the final result
"body": []|{}|... // for responses with `multi=true` the body should be put to an array [] and when `more=false` the result should be the array with the assembled parts
}
In order to process these envelope which arrive intermixed with other response envelopes, one must buffer the envelopes unti the last flag is true.
const requests = {}; // holds all responses
ws.onmessage = function(msg)
{
const payload = JSON.parse(what);
const request = requests[payload.id];
if (!request) throw new Error("request could not be found " + req.id);
if (payload.error)
{
// an error must always be delivered
request.cb(payload);
delete(requests[payload.id]);
} else if (!payload.multi && payload.more) {
throw new Error("non-multi messages cannot be fragmented");
} else if (!payload.multi) {
// thats a single response message
request.cb(payload);
delete(requests[payload.id]);
} else {
// thats a fragmented response, buffer up everything until payload.more = false
if (payload.body !== null) request.buf.push(payload.body);request.buf.push(payload.body);
if (!payload.more)
{
request.cb(request.buf);
delete(requests[payload.id]);
}
};
);
Individual Requests
{
"id": "request id",
"type": "get",
/* other optional properties */
"headers":
{
"ogit/_id": "id of the node"
},
"body":
{
// all parameters for the corresponding REST request are available here
}
}
{
"id": "request id",
"type": "create",
/* other optional properties */
"headers":
{
"ogit/_type": "type of the node"
},
"body":
{
// all parameters for the corresponding REST request are available here
}
}
{
"id": "request id",
"type": "update",
/* other optional properties */
"headers":
{
"ogit/_id": "id of the node"
},
"body":
{
// all parameters for the corresponding REST request are available here
}
}
{
"id": "request id",
"type": "replace",
/* other optional properties */
"headers":
{
"ogit/_id": "id of the node",
},
"body":
{
// all parameters for the corresponding REST request are available here
}
}
{
"id": "request id",
"type": "delete",
/* other optional properties */
"headers":
{
"ogit/_id": "id of the node/edge"
},
"body":
{
// all parameters for the corresponding REST request are available here
}
}
{
"id": "request id",
"type": "connect",
/* other optional properties */
"headers":
{
"ogit/_type": "type of the verb"
},
"body":
{
"out": "id of the outgoing node",
"in": "id of the ingoing node"
}
}
{
"id": "request id",
"type": "query",
/* other optional properties */
"headers":
{
"type": "type of the query, e.g. vertices"
},
"body":
{
// all parameters for the corresponding REST request are available here
}
}
{
"id": "request id",
"type": "writets",
/* other optional properties */
"headers":
{
"ogit/_id": "id of the node"
},
"body":
{
// all parameters for the corresponding REST request are available here
}
}
{
"id": "request id",
"type": "streamts",
/* other optional properties */
"headers":
{
"ogit/_id": "id of the node"
},
"body":
{
// all parameters for the corresponding REST request are available here
}
}
{
"id": "request id",
"type": "history",
/* other optional properties */
"headers":
{
"ogit/_id": "id of the node"
},
"body":
{
// all parameters for the corresponding REST request are available here
}
}
{
"id": "request id",
"type": "me",
'_TOKEN': 'optional, if none specified, the one from the establishing the websocket will be used',
"headers":
{
},
"body":
{
// all parameters for the corresponding REST request are available here
}
}
{
"id": "request id",
"type": "getcontent",
'_TOKEN': 'optional, if none specified, the one from the establishing the websocket will be used',
"headers":
{
"ogit/_id": "id of the node"
},
"body":
{
// all parameters for the corresponding REST request are available here
}
}
In order to store blob content, the REST API must be used. |
The responses for receiving blob content are chunked envelopes looking like this, and the client needs to assemble these:
{
"id": "request id",
"multi": true,
"last": true|false
"body":
{
"data": "chunk of blob data",
"encoding": "base64" // base64 is currently the only encoding possible
}
}
Error Codes
-
All error codes of HIRO Graph API apply.
-
All error codes of Websocket Protocol apply.
On Websocket close message HIRO Graph error codes are not propagated and only Websocket Protocol codes are returned. If exists error from close message content should be used to recognize and implement handling. Example error response message:
|
{
"error": {"message": "authentication required"}
}