2 title: WebSocket protocol x-afb-ws-json1
5 The WebSocket protocol *x-afb-ws-json1* is used to communicate between
6 an application and a binder. It allows access to all registered apis
9 This protocol is inspired from the protocol **OCPP - SRPC** as described for
11 [OCPP transport specification - SRPC over WebSocket](http://www.gir.fr/ocppjs/ocpp_srpc_spec.shtml).
13 The registration to the IANA is still to be done, see:
14 [WebSocket Protocol Registries](https://www.iana.org/assignments/websocket/websocket.xml)
16 This document gives a short description of the protocol *x-afb-ws-json1*.
17 A more formal description has to be done.
21 The protocol is intended to be symmetric. It allows:
23 - to CALL a remote procedure that returns a result
24 - to push and receive EVENT
28 Valid messages are made of *text* frames that are all valid JSON.
35 [ 2, ID, PROCN, ARGS ]
36 [ 2, ID, PROCN, ARGS, TOKEN ]
39 Replies (3: OK, 4: ERROR):
54 | Field | Type | Description
55 |-------|--------|------------------
56 | ID | string | A string that identifies the call. A reply to that call use the ID of the CALL.
57 | PROCN | string | The procedure name to call of the form "api/verb"
58 | ARGS | any | Any argument to pass to the call (see afb_req_json that returns it)
59 | RESP | any | The response to the call
60 | TOKEN | string | The authorisation token
61 | EVTN | string | Name of the event in the form "api/event"
62 | OBJ | any | The companion object of the event
64 Below, an example of exchange:
67 C->S: [2,"156","hello/ping",null]
68 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"}}]
75 Removal of token returning. The replies
78 [ 3, ID, RESP, TOKEN ]
79 [ 4, ID, RESP, TOKEN ]
82 are removed from the specification.
86 Here are the planned extensions:
88 - add binary messages with cbor data
89 - add calls with unstructured replies
91 This could be implemented by extending the current protocol or by
92 allowing the binder to accept either protocol including the new ones.
94 ## Javascript implementation
96 The file **AFB.js** is a javascript implementation of the protocol.
102 * Copyright (C) 2017-2019 "IoT.bzh"
103 * Author: José Bollo <jose.bollo@iot.bzh>
105 * Licensed under the Apache License, Version 2.0 (the "License");
106 * you may not use this file except in compliance with the License.
107 * You may obtain a copy of the License at
109 * http://www.apache.org/licenses/LICENSE-2.0
111 * Unless required by applicable law or agreed to in writing, software
112 * distributed under the License is distributed on an "AS IS" BASIS,
113 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
114 * See the License for the specific language governing permissions and
115 * limitations under the License.
117 AFB = function(base, initialtoken){
119 if (typeof base != "object")
120 base = { base: base, token: initialtoken };
123 base: base.base || "api",
124 token: base.token || initialtoken || "HELLO",
125 host: base.host || window.location.host,
126 url: base.url || undefined
129 var urlws = initial.url || "ws://"+initial.host+"/"+initial.base;
131 /*********************************************/
133 /**** AFB_context ****/
135 /*********************************************/
138 var UUID = undefined;
139 var TOKEN = initial.token;
141 var context = function(token, uuid) {
146 context.prototype = {
147 get token() {return TOKEN;},
148 set token(tok) {if(tok) TOKEN=tok;},
149 get uuid() {return UUID;},
150 set uuid(id) {if(id) UUID=id;}
153 AFB_context = new context();
155 /*********************************************/
157 /**** AFB_websocket ****/
159 /*********************************************/
167 var PROTO1 = "x-afb-ws-json1";
169 AFB_websocket = function(on_open, on_abort) {
171 if (AFB_context.token) {
172 u = u + '?x-afb-token=' + AFB_context.token;
173 if (AFB_context.uuid)
174 u = u + '&x-afb-uuid=' + AFB_context.uuid;
176 this.ws = new WebSocket(u, [ PROTO1 ]);
181 this.ws.onopen = onopen.bind(this);
182 this.ws.onerror = onerror.bind(this);
183 this.ws.onclose = onclose.bind(this);
184 this.ws.onmessage = onmessage.bind(this);
185 this.onopen = on_open;
186 this.onabort = on_abort;
189 function onerror(event) {
190 var f = this.onabort;
196 this.onerror && this.onerror(this);
199 function onopen(event) {
206 function onclose(event) {
207 for (var id in this.pendings) {
208 try { this.pendings[id][1](); } catch (x) {/*TODO?*/}
211 this.onclose && this.onclose();
214 function fire(awaitens, name, data) {
215 var a = awaitens[name];
217 a.forEach(function(handler){handler(data);});
218 var i = name.indexOf("/");
220 a = awaitens[name.substring(0,i)];
222 a.forEach(function(handler){handler(data);});
226 a.forEach(function(handler){handler(data);});
229 function reply(pendings, id, ans, offset) {
230 if (id in pendings) {
231 var p = pendings[id];
233 try { p[offset](ans); } catch (x) {/*TODO?*/}
237 function onmessage(event) {
238 var obj = JSON.parse(event.data);
242 AFB_context.token = obj[3];
245 reply(this.pendings, id, ans, 0);
248 reply(this.pendings, id, ans, 1);
252 fire(this.awaitens, id, ans);
264 this.onabort = function(){};
267 function call(method, request, callid) {
268 return new Promise((function(resolve, reject){
272 if (id in this.pendings)
273 throw new Error("pending callid("+id+") exists");
276 id = String(this.counter = 4095 & (this.counter + 1));
277 } while (id in this.pendings);
279 this.pendings[id] = [ resolve, reject ];
280 arr = [CALL, id, method, request ];
281 if (AFB_context.token) arr.push(AFB_context.token);
282 this.ws.send(JSON.stringify(arr));
286 function onevent(name, handler) {
288 var list = this.awaitens[id] || (this.awaitens[id] = []);
292 AFB_websocket.prototype = {
298 /*********************************************/
302 /*********************************************/
304 context: AFB_context,