1 The websocket protocol x-afb-ws-json1
2 =====================================
4 The WebSocket protocol *x-afb-ws-json1* is used to communicate between
5 an application and a binder. It allows access to all registered apis
8 This protocol is inspired from the protocol **OCPP - SRPC** as described for
10 [OCPP transport specification - SRPC over WebSocket](http://www.gir.fr/ocppjs/ocpp_srpc_spec.shtml).
12 The registration to the IANA is still to be done, see:
13 [WebSocket Protocol Registries](https://www.iana.org/assignments/websocket/websocket.xml)
15 This document gives a short description of the protocol *x-afb-ws-json1*.
16 A more formal description has to be done.
22 The protocol is intended to be symmetric. It allows:
24 - to CALL a remote procedure that returns a result
25 - to push and receive EVENT
31 Valid messages are made of *text* frames that are all valid JSON.
37 [ 2, ID, PROCN, ARGS ]
38 [ 2, ID, PROCN, ARGS, TOKEN ]
41 Replies (3: OK, 4: ERROR):
44 [ 3, ID, RESP, TOKEN ]
46 [ 4, ID, RESP, TOKEN ]
56 | Field | Type | Description
57 |-------|--------|------------------
58 | ID | string | A string that identifies the call. A reply to that call use the ID of the CALL.
59 | PROCN | string | The procedure name to call of the form "api/verb"
60 | ARGS | any | Any argument to pass to the call (see afb_req_json that returns it)
61 | RESP | any | The response to the call
62 | TOKEN | string | The token in case of refresh
63 | EVTN | string | Name of the event in the form "api/event"
64 | OBJ | any | The companion object of the event
66 Below, an example of exchange:
69 C->S: [2,"156","hello/ping",null]
70 S->C: [3,"156",{"response":"Some String","jtype":"afb-reply","request":{"status":"success","info":"Ping Binder Daemon tag=pingSample count=1 query=\"null\"","uuid":"ec30120c-6997-4529-9d63-c0de0cce56c0"}}]
77 Here are the planned extensions:
79 - add binary messages with cbor data
80 - add calls with unstructured replies
82 This could be implemented by extending the current protocol or by
83 allowing the binder to accept either protocol including the new ones.
86 Javascript implementation
87 -------------------------
89 The file **AFB.js** is a javascript implementation of the protocol.
95 * Copyright (C) 2017-2019 "IoT.bzh"
96 * Author: José Bollo <jose.bollo@iot.bzh>
98 * Licensed under the Apache License, Version 2.0 (the "License");
99 * you may not use this file except in compliance with the License.
100 * You may obtain a copy of the License at
102 * http://www.apache.org/licenses/LICENSE-2.0
104 * Unless required by applicable law or agreed to in writing, software
105 * distributed under the License is distributed on an "AS IS" BASIS,
106 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
107 * See the License for the specific language governing permissions and
108 * limitations under the License.
110 AFB = function(base, initialtoken){
112 if (typeof base != "object")
113 base = { base: base, token: initialtoken };
116 base: base.base || "api",
117 token: base.token || initialtoken || "HELLO",
118 host: base.host || window.location.host,
119 url: base.url || undefined
122 var urlws = initial.url || "ws://"+initial.host+"/"+initial.base;
124 /*********************************************/
126 /**** AFB_context ****/
128 /*********************************************/
131 var UUID = undefined;
132 var TOKEN = initial.token;
134 var context = function(token, uuid) {
139 context.prototype = {
140 get token() {return TOKEN;},
141 set token(tok) {if(tok) TOKEN=tok;},
142 get uuid() {return UUID;},
143 set uuid(id) {if(id) UUID=id;}
146 AFB_context = new context();
148 /*********************************************/
150 /**** AFB_websocket ****/
152 /*********************************************/
160 var PROTO1 = "x-afb-ws-json1";
162 AFB_websocket = function(on_open, on_abort) {
164 if (AFB_context.token) {
165 u = u + '?x-afb-token=' + AFB_context.token;
166 if (AFB_context.uuid)
167 u = u + '&x-afb-uuid=' + AFB_context.uuid;
169 this.ws = new WebSocket(u, [ PROTO1 ]);
174 this.ws.onopen = onopen.bind(this);
175 this.ws.onerror = onerror.bind(this);
176 this.ws.onclose = onclose.bind(this);
177 this.ws.onmessage = onmessage.bind(this);
178 this.onopen = on_open;
179 this.onabort = on_abort;
182 function onerror(event) {
183 var f = this.onabort;
189 this.onerror && this.onerror(this);
192 function onopen(event) {
199 function onclose(event) {
200 for (var id in this.pendings) {
201 try { this.pendings[id][1](); } catch (x) {/*TODO?*/}
204 this.onclose && this.onclose();
207 function fire(awaitens, name, data) {
208 var a = awaitens[name];
210 a.forEach(function(handler){handler(data);});
211 var i = name.indexOf("/");
213 a = awaitens[name.substring(0,i)];
215 a.forEach(function(handler){handler(data);});
219 a.forEach(function(handler){handler(data);});
222 function reply(pendings, id, ans, offset) {
223 if (id in pendings) {
224 var p = pendings[id];
226 try { p[offset](ans); } catch (x) {/*TODO?*/}
230 function onmessage(event) {
231 var obj = JSON.parse(event.data);
235 AFB_context.token = obj[3];
238 reply(this.pendings, id, ans, 0);
241 reply(this.pendings, id, ans, 1);
245 fire(this.awaitens, id, ans);
257 this.onabort = function(){};
260 function call(method, request, callid) {
261 return new Promise((function(resolve, reject){
265 if (id in this.pendings)
266 throw new Error("pending callid("+id+") exists");
269 id = String(this.counter = 4095 & (this.counter + 1));
270 } while (id in this.pendings);
272 this.pendings[id] = [ resolve, reject ];
273 arr = [CALL, id, method, request ];
274 if (AFB_context.token) arr.push(AFB_context.token);
275 this.ws.send(JSON.stringify(arr));
279 function onevent(name, handler) {
281 var list = this.awaitens[id] || (this.awaitens[id] = []);
285 AFB_websocket.prototype = {
291 /*********************************************/
295 /*********************************************/
297 context: AFB_context,