1 # The websocket protocol x-afb-ws-json1
3 The WebSocket protocol *x-afb-ws-json1* is used to communicate between
4 an application and a binder. It allows access to all registered apis
7 This protocol is inspired from the protocol **OCPP - SRPC** as described for
9 [OCPP transport specification - SRPC over WebSocket](http://www.gir.fr/ocppjs/ocpp_srpc_spec.shtml).
11 The registration to the IANA is still to be done, see:
12 [WebSocket Protocol Registries](https://www.iana.org/assignments/websocket/websocket.xml)
14 This document gives a short description of the protocol *x-afb-ws-json1*.
15 A more formal description has to be done.
19 The protocol is intended to be symmetric. It allows:
21 - to CALL a remote procedure that returns a result
22 - to push and receive EVENT
26 Valid messages are made of *text* frames that are all valid JSON.
33 [ 2, ID, PROCN, ARGS ]
34 [ 2, ID, PROCN, ARGS, TOKEN ]
37 Replies (3: OK, 4: ERROR):
52 | Field | Type | Description
53 |-------|--------|------------------
54 | ID | string | A string that identifies the call. A reply to that call use the ID of the CALL.
55 | PROCN | string | The procedure name to call of the form "api/verb"
56 | ARGS | any | Any argument to pass to the call (see afb_req_json that returns it)
57 | RESP | any | The response to the call
58 | TOKEN | string | The authorisation token
59 | EVTN | string | Name of the event in the form "api/event"
60 | OBJ | any | The companion object of the event
62 Below, an example of exchange:
65 C->S: [2,"156","hello/ping",null]
66 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"}}]
73 Removal of token returning. The replies
76 [ 3, ID, RESP, TOKEN ]
77 [ 4, ID, RESP, TOKEN ]
80 are removed from the specification.
84 Here are the planned extensions:
86 - add binary messages with cbor data
87 - add calls with unstructured replies
89 This could be implemented by extending the current protocol or by
90 allowing the binder to accept either protocol including the new ones.
92 ## Javascript implementation
94 The file **AFB.js** is a javascript implementation of the protocol.
100 * Copyright (C) 2017-2019 "IoT.bzh"
101 * Author: José Bollo <jose.bollo@iot.bzh>
103 * Licensed under the Apache License, Version 2.0 (the "License");
104 * you may not use this file except in compliance with the License.
105 * You may obtain a copy of the License at
107 * http://www.apache.org/licenses/LICENSE-2.0
109 * Unless required by applicable law or agreed to in writing, software
110 * distributed under the License is distributed on an "AS IS" BASIS,
111 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
112 * See the License for the specific language governing permissions and
113 * limitations under the License.
115 AFB = function(base, initialtoken){
117 if (typeof base != "object")
118 base = { base: base, token: initialtoken };
121 base: base.base || "api",
122 token: base.token || initialtoken || "HELLO",
123 host: base.host || window.location.host,
124 url: base.url || undefined
127 var urlws = initial.url || "ws://"+initial.host+"/"+initial.base;
129 /*********************************************/
131 /**** AFB_context ****/
133 /*********************************************/
136 var UUID = undefined;
137 var TOKEN = initial.token;
139 var context = function(token, uuid) {
144 context.prototype = {
145 get token() {return TOKEN;},
146 set token(tok) {if(tok) TOKEN=tok;},
147 get uuid() {return UUID;},
148 set uuid(id) {if(id) UUID=id;}
151 AFB_context = new context();
153 /*********************************************/
155 /**** AFB_websocket ****/
157 /*********************************************/
165 var PROTO1 = "x-afb-ws-json1";
167 AFB_websocket = function(on_open, on_abort) {
169 if (AFB_context.token) {
170 u = u + '?x-afb-token=' + AFB_context.token;
171 if (AFB_context.uuid)
172 u = u + '&x-afb-uuid=' + AFB_context.uuid;
174 this.ws = new WebSocket(u, [ PROTO1 ]);
179 this.ws.onopen = onopen.bind(this);
180 this.ws.onerror = onerror.bind(this);
181 this.ws.onclose = onclose.bind(this);
182 this.ws.onmessage = onmessage.bind(this);
183 this.onopen = on_open;
184 this.onabort = on_abort;
187 function onerror(event) {
188 var f = this.onabort;
194 this.onerror && this.onerror(this);
197 function onopen(event) {
204 function onclose(event) {
205 for (var id in this.pendings) {
206 try { this.pendings[id][1](); } catch (x) {/*TODO?*/}
209 this.onclose && this.onclose();
212 function fire(awaitens, name, data) {
213 var a = awaitens[name];
215 a.forEach(function(handler){handler(data);});
216 var i = name.indexOf("/");
218 a = awaitens[name.substring(0,i)];
220 a.forEach(function(handler){handler(data);});
224 a.forEach(function(handler){handler(data);});
227 function reply(pendings, id, ans, offset) {
228 if (id in pendings) {
229 var p = pendings[id];
231 try { p[offset](ans); } catch (x) {/*TODO?*/}
235 function onmessage(event) {
236 var obj = JSON.parse(event.data);
240 AFB_context.token = obj[3];
243 reply(this.pendings, id, ans, 0);
246 reply(this.pendings, id, ans, 1);
250 fire(this.awaitens, id, ans);
262 this.onabort = function(){};
265 function call(method, request, callid) {
266 return new Promise((function(resolve, reject){
270 if (id in this.pendings)
271 throw new Error("pending callid("+id+") exists");
274 id = String(this.counter = 4095 & (this.counter + 1));
275 } while (id in this.pendings);
277 this.pendings[id] = [ resolve, reject ];
278 arr = [CALL, id, method, request ];
279 if (AFB_context.token) arr.push(AFB_context.token);
280 this.ws.send(JSON.stringify(arr));
284 function onevent(name, handler) {
286 var list = this.awaitens[id] || (this.awaitens[id] = []);
290 AFB_websocket.prototype = {
296 /*********************************************/
300 /*********************************************/
302 context: AFB_context,