431b41f75ac69e3562f4e784f80cc145869e0dab
[AGL/documentation.git] / docs / 4_APIs_and_Services / 4.3_Application_Framework_Binder / 6_Annexes / 2_WebSocket_protocol_x-afb-ws-json1.md
1 ---
2 edit_link: ''
3 title: WebSocket protocol x-afb-ws-json1
4 origin_url: >-
5   https://git.automotivelinux.org/src/app-framework-binder/plain/docs/protocol-x-afb-ws-json1.md?h=master
6 ---
7
8 <!-- WARNING: This file is generated by fetch_docs.js using /home/boron/Documents/AGL/docs-webtemplate/site/_data/tocs/apis_services/master/app-framework-binder-developer-guides-api-services-book.yml -->
9
10 # The websocket protocol x-afb-ws-json1
11
12 The WebSocket protocol *x-afb-ws-json1* is used to communicate between
13 an application and a binder. It allows access to all registered apis
14 of the binder.
15
16 This protocol is inspired from the protocol **OCPP - SRPC** as described for
17 example here:
18 [OCPP transport specification - SRPC over WebSocket](http://www.gir.fr/ocppjs/ocpp_srpc_spec.shtml).
19
20 The registration to the IANA is still to be done, see:
21 [WebSocket Protocol Registries](https://www.iana.org/assignments/websocket/websocket.xml)
22
23 This document gives a short description of the protocol *x-afb-ws-json1*.
24 A more formal description has to be done.
25
26 ## Architecture
27
28 The protocol is intended to be symmetric. It allows:
29
30 - to CALL a remote procedure that returns a result
31 - to push and receive EVENT
32
33 ## Messages
34
35 Valid messages are made of *text* frames that are all valid JSON.
36
37 Valid messages are:
38
39 Calls:
40
41 ```txt
42 [ 2, ID, PROCN, ARGS ]
43 [ 2, ID, PROCN, ARGS, TOKEN ]
44 ```
45
46 Replies (3: OK, 4: ERROR):
47
48 ```txt
49 [ 3, ID, RESP ]
50 [ 4, ID, RESP ]
51 ```
52
53 Events:
54
55 ```txt
56 [ 5, EVTN, OBJ ]
57 ```
58
59 Where:
60
61 | Field | Type   | Description
62 |-------|--------|------------------
63 | ID    | string | A string that identifies the call. A reply to that call use the ID of the CALL.
64 | PROCN | string | The procedure name to call of the form "api/verb"
65 | ARGS  | any    | Any argument to pass to the call (see afb_req_json that returns it)
66 | RESP  | any    | The response to the call
67 | TOKEN | string | The authorisation token
68 | EVTN  | string | Name of the event in the form "api/event"
69 | OBJ   | any    | The companion object of the event
70
71 Below, an example of exchange:
72
73 ```txt
74 C->S:   [2,"156","hello/ping",null]
75 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"}}]
76 ```
77
78 ## History
79
80 ### 14 November 2019
81
82 Removal of token returning. The replies
83
84 ```txt
85 [ 3, ID, RESP, TOKEN ]
86 [ 4, ID, RESP, TOKEN ]
87 ```
88
89 are removed from the specification.
90
91 ## Future
92
93 Here are the planned extensions:
94
95 - add binary messages with cbor data
96 - add calls with unstructured replies
97
98 This could be implemented by extending the current protocol or by
99 allowing the binder to accept either protocol including the new ones.
100
101 ## Javascript implementation
102
103 The file **AFB.js** is a javascript implementation of the protocol.
104
105 Here is that code:
106
107 ```javascript
108 /*
109  * Copyright (C) 2017-2019 "IoT.bzh"
110  * Author: José Bollo <jose.bollo@iot.bzh>
111  *
112  * Licensed under the Apache License, Version 2.0 (the "License");
113  * you may not use this file except in compliance with the License.
114  * You may obtain a copy of the License at
115  *
116  *   http://www.apache.org/licenses/LICENSE-2.0
117  *
118  * Unless required by applicable law or agreed to in writing, software
119  * distributed under the License is distributed on an "AS IS" BASIS,
120  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
121  * See the License for the specific language governing permissions and
122  * limitations under the License.
123  */
124 AFB = function(base, initialtoken){
125
126 if (typeof base != "object")
127    base = { base: base, token: initialtoken };
128
129 var initial = {
130    base: base.base || "api",
131    token: base.token || initialtoken || "HELLO",
132    host: base.host || window.location.host,
133    url: base.url || undefined
134 };
135
136 var urlws = initial.url || "ws://"+initial.host+"/"+initial.base;
137
138 /*********************************************/
139 /****                                     ****/
140 /****             AFB_context             ****/
141 /****                                     ****/
142 /*********************************************/
143 var AFB_context;
144 {
145    var UUID = undefined;
146    var TOKEN = initial.token;
147
148    var context = function(token, uuid) {
149       this.token = token;
150       this.uuid = uuid;
151    }
152
153    context.prototype = {
154       get token() {return TOKEN;},
155       set token(tok) {if(tok) TOKEN=tok;},
156       get uuid() {return UUID;},
157       set uuid(id) {if(id) UUID=id;}
158    };
159
160    AFB_context = new context();
161 }
162 /*********************************************/
163 /****                                     ****/
164 /****             AFB_websocket           ****/
165 /****                                     ****/
166 /*********************************************/
167 var AFB_websocket;
168 {
169    var CALL = 2;
170    var RETOK = 3;
171    var RETERR = 4;
172    var EVENT = 5;
173
174    var PROTO1 = "x-afb-ws-json1";
175
176    AFB_websocket = function(on_open, on_abort) {
177       var u = urlws;
178       if (AFB_context.token) {
179          u = u + '?x-afb-token=' + AFB_context.token;
180          if (AFB_context.uuid)
181             u = u + '&x-afb-uuid=' + AFB_context.uuid;
182       }
183       this.ws = new WebSocket(u, [ PROTO1 ]);
184       this.url = u;
185       this.pendings = {};
186       this.awaitens = {};
187       this.counter = 0;
188       this.ws.onopen = onopen.bind(this);
189       this.ws.onerror = onerror.bind(this);
190       this.ws.onclose = onclose.bind(this);
191       this.ws.onmessage = onmessage.bind(this);
192       this.onopen = on_open;
193       this.onabort = on_abort;
194    }
195
196    function onerror(event) {
197       var f = this.onabort;
198       if (f) {
199          delete this.onopen;
200          delete this.onabort;
201          f && f(this);
202       }
203       this.onerror && this.onerror(this);
204    }
205
206    function onopen(event) {
207       var f = this.onopen;
208       delete this.onopen;
209       delete this.onabort;
210       f && f(this);
211    }
212
213    function onclose(event) {
214       for (var id in this.pendings) {
215          try { this.pendings[id][1](); } catch (x) {/*TODO?*/}
216       }
217       this.pendings = {};
218       this.onclose && this.onclose();
219    }
220
221    function fire(awaitens, name, data) {
222       var a = awaitens[name];
223       if (a)
224          a.forEach(function(handler){handler(data);});
225       var i = name.indexOf("/");
226       if (i >= 0) {
227          a = awaitens[name.substring(0,i)];
228          if (a)
229             a.forEach(function(handler){handler(data);});
230       }
231       a = awaitens["*"];
232       if (a)
233          a.forEach(function(handler){handler(data);});
234    }
235
236    function reply(pendings, id, ans, offset) {
237       if (id in pendings) {
238          var p = pendings[id];
239          delete pendings[id];
240          try { p[offset](ans); } catch (x) {/*TODO?*/}
241       }
242    }
243
244    function onmessage(event) {
245       var obj = JSON.parse(event.data);
246       var code = obj[0];
247       var id = obj[1];
248       var ans = obj[2];
249       AFB_context.token = obj[3];
250       switch (code) {
251       case RETOK:
252          reply(this.pendings, id, ans, 0);
253          break;
254       case RETERR:
255          reply(this.pendings, id, ans, 1);
256          break;
257       case EVENT:
258       default:
259          fire(this.awaitens, id, ans);
260          break;
261       }
262    }
263
264    function close() {
265       this.ws.close();
266       this.ws.onopen =
267       this.ws.onerror =
268       this.ws.onclose =
269       this.ws.onmessage =
270       this.onopen =
271       this.onabort = function(){};
272    }
273
274    function call(method, request, callid) {
275       return new Promise((function(resolve, reject){
276          var id, arr;
277          if (callid) {
278             id = String(callid);
279             if (id in this.pendings)
280                throw new Error("pending callid("+id+") exists");
281          } else {
282             do {
283                id = String(this.counter = 4095 & (this.counter + 1));
284             } while (id in this.pendings);
285          }
286          this.pendings[id] = [ resolve, reject ];
287          arr = [CALL, id, method, request ];
288          if (AFB_context.token) arr.push(AFB_context.token);
289          this.ws.send(JSON.stringify(arr));
290       }).bind(this));
291    }
292
293    function onevent(name, handler) {
294       var id = name;
295       var list = this.awaitens[id] || (this.awaitens[id] = []);
296       list.push(handler);
297    }
298
299    AFB_websocket.prototype = {
300       close: close,
301       call: call,
302       onevent: onevent
303    };
304 }
305 /*********************************************/
306 /****                                     ****/
307 /****                                     ****/
308 /****                                     ****/
309 /*********************************************/
310 return {
311    context: AFB_context,
312    ws: AFB_websocket
313 };
314 };
315 ```