[lib.client.client-driver] parent class for generic Synchronous|Asynchronous Client management (with message stack automatically sending messages when connection is ready) [lib.client.xhr] XHR driver [lib.client.ws] WebSocket driver
This commit is contained in:
parent
46b2143589
commit
1cedd6284a
|
@ -0,0 +1,183 @@
|
||||||
|
export class ClientDriver{
|
||||||
|
|
||||||
|
/* (0) Constants & Enums
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
static get STATE(){
|
||||||
|
|
||||||
|
return Object.freeze({
|
||||||
|
OPENING: 0,
|
||||||
|
OPENED: 1,
|
||||||
|
CONNECTING: 2,
|
||||||
|
CONNECTED: 3,
|
||||||
|
CLOSED: 4
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (1) Creates a client driver
|
||||||
|
*
|
||||||
|
* @_resource<String> Target resource (typically an URL)
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
constructor(_resource){
|
||||||
|
|
||||||
|
/* (1) Default attributes values */
|
||||||
|
this.error = false;
|
||||||
|
this.state = ClientDriver.STATE.OPENING;
|
||||||
|
this.stack = [];
|
||||||
|
this.callback = {
|
||||||
|
onready: function(){},
|
||||||
|
onclose: function(){},
|
||||||
|
onreceive: function(){}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* (2) Fail: invalid _resource */
|
||||||
|
if( typeof _resource !== 'string' )
|
||||||
|
return ( this.error = true );
|
||||||
|
|
||||||
|
/* (3) Set explicit attributes */
|
||||||
|
this.resource = _resource;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (2) Binds the client to the resource
|
||||||
|
*
|
||||||
|
* @return bound<boolean> Whether the binding has been successful
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
bind(){
|
||||||
|
|
||||||
|
/* (1) Fail: not OPENING OR error */
|
||||||
|
if( this.error || this.state !== ClientDriver.STATE.OPENING )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* (2) By default return success */
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (3) Send request
|
||||||
|
*
|
||||||
|
* @_request<Object> Request data
|
||||||
|
*
|
||||||
|
* @return sent<boolean> Whether the request has been successful
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
send(_request){
|
||||||
|
|
||||||
|
/* (1) Fail: if error */
|
||||||
|
if( this.error )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* (2) Fail: invalid _request */
|
||||||
|
if( !(_request instanceof Object) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* (3) If not already connected -> stack request */
|
||||||
|
if( this.state < ClientDriver.STATE.CONNECTED ){
|
||||||
|
|
||||||
|
this.stack.push(_request);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (4) By default return success */
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (4) Bind event to connection opened
|
||||||
|
*
|
||||||
|
* @_callback<Function> Callback launched when connection is opened
|
||||||
|
*
|
||||||
|
* @return bound<boolean> Whether the callback has successfully been bound
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
onready(_callback){
|
||||||
|
|
||||||
|
/* (1) Fail: already CONNECTED OR error */
|
||||||
|
if( this.error || this.state >= ClientDriver.STATE.CONNECTED )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* (2) Fail: invalid _callback */
|
||||||
|
if( !(_callback instanceof Function) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* (3) Register _callback */
|
||||||
|
this.callback.onready = function(cback){
|
||||||
|
|
||||||
|
// call callback
|
||||||
|
cback();
|
||||||
|
|
||||||
|
// if request(s) in stack -> pop & send them
|
||||||
|
while( this.stack.length > 0 )
|
||||||
|
this.send(this.stack.shift());
|
||||||
|
|
||||||
|
}.bind(this, _callback);
|
||||||
|
|
||||||
|
/* (5) By default return success */
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (5) Bind event to connection closed
|
||||||
|
*
|
||||||
|
* @_callback<Function> Callback launched when connection is closed
|
||||||
|
*
|
||||||
|
* @return bound<boolean> Whether the callback has successfully been bound
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
onclose(_callback){
|
||||||
|
|
||||||
|
/* (1) Fail: already CLOSED OR error */
|
||||||
|
if( this.error || this.state === ClientDriver.STATE.CLOSED )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* (2) Fail: invalid _callback */
|
||||||
|
if( !(_callback instanceof Function) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* (3) Register _callback */
|
||||||
|
this.callback.onclose = _callback;
|
||||||
|
|
||||||
|
/* (4) By default return success */
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (6) Bind event to message reception
|
||||||
|
*
|
||||||
|
* @_callback<Function> Callback launched when a message is received
|
||||||
|
*
|
||||||
|
* @return bound<boolean> Whether the callback has successfully been bound
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
onreceive(_callback){
|
||||||
|
|
||||||
|
/* (1) Fail: already CONNECTED OR error */
|
||||||
|
if( this.error || this.state >= ClientDriver.STATE.CONNECTED )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* (2) Fail: invalid _callback */
|
||||||
|
if( !(_callback instanceof Function) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* (3) Register _callback */
|
||||||
|
this.callback.onreceive = _callback;
|
||||||
|
|
||||||
|
/* (4) By default return success */
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
import {ClientDriver} from './client-driver.js'
|
||||||
|
|
||||||
|
export class WebSocketClientDriver extends ClientDriver{
|
||||||
|
|
||||||
|
|
||||||
|
/* (1) Creates a client driver
|
||||||
|
*
|
||||||
|
* @_resource<String> Target resource (typically an URL)
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
constructor(_resource){
|
||||||
|
|
||||||
|
/* (0) Parent check */
|
||||||
|
if( !super().error )
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* (1) Set inherited attributes */
|
||||||
|
this.resource = _resource;
|
||||||
|
|
||||||
|
/* (2) Create useful attributes */
|
||||||
|
this.ws = null;
|
||||||
|
this.buffer = null; // useful when waiting for WebSocket to open
|
||||||
|
|
||||||
|
/* (N) Set all is ok */
|
||||||
|
this.error = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (2) Binds the client to the resource
|
||||||
|
*
|
||||||
|
* @return bound<boolean> Whether the binding has been successful
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
bind(){
|
||||||
|
|
||||||
|
/* (0) Parent check */
|
||||||
|
if( !super.bind() )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* (1) Create WebSocket client instance */
|
||||||
|
this.ws = new WebSocket(this.resource);
|
||||||
|
|
||||||
|
/* (2) Bind callback.onready */
|
||||||
|
this.ws.onopen = function(){
|
||||||
|
this.state = ClientDriver.STATE.CONNECTED;
|
||||||
|
this.callback.onready();
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
/* (3) Bind callback.onclose */
|
||||||
|
this.ws.onclose = function(){
|
||||||
|
this.state = ClientDriver.STATE.CLOSED;
|
||||||
|
this.callback.onclose();
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
/* (4) Bind callback.onerror */
|
||||||
|
this.ws.onerror = function(){
|
||||||
|
this.state = ClientDriver.STATE.CLOSED;
|
||||||
|
this.callback.onclose();
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
/* (5) Bind callback.onreceive */
|
||||||
|
this.ws.onmessage = function(response){
|
||||||
|
this.state = ClientDriver.STATE.CONNECTED;
|
||||||
|
this.callback.onreceive(response);
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
/* (6) Return success */
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (3) Send request
|
||||||
|
*
|
||||||
|
* @_request<Object> Request data
|
||||||
|
*
|
||||||
|
* @return sent<boolean> Whether the request has been successful
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
send(_request){
|
||||||
|
|
||||||
|
/* (0) Parent check */
|
||||||
|
if( !super.send(_request) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* (1) Error: invalid _request.buffer */
|
||||||
|
if( typeof _request.buffer !== 'string' )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* (2) Send message */
|
||||||
|
console.log('sent', _request.buffer);
|
||||||
|
this.ws.send(_request.buffer);
|
||||||
|
|
||||||
|
/* (3) Return success */
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (4) Bind event to connection opened
|
||||||
|
*
|
||||||
|
* @_callback<Function> Callback launched when connection is opened
|
||||||
|
*
|
||||||
|
* @return bound<boolean> Whether the callback has successfully been bound
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
onready(_callback){
|
||||||
|
|
||||||
|
/* (0) Parent check */
|
||||||
|
if( !super.onready(_callback) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (5) Bind event to connection closed
|
||||||
|
*
|
||||||
|
* @_callback<Function> Callback launched when connection is closed
|
||||||
|
*
|
||||||
|
* @return bound<boolean> Whether the callback has successfully been bound
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
onclose(_callback){
|
||||||
|
|
||||||
|
/* (0) Parent check */
|
||||||
|
if( !super.onclose(_callback) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (6) Bind event to message reception
|
||||||
|
*
|
||||||
|
* @_callback<Function> Callback launched when a message is received
|
||||||
|
*
|
||||||
|
* @return bound<boolean> Whether the callback has successfully been bound
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
onreceive(_callback){
|
||||||
|
|
||||||
|
/* (0) Parent check */
|
||||||
|
if( !super.onreceive(_callback) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,198 @@
|
||||||
|
import {ClientDriver} from './client-driver.js'
|
||||||
|
|
||||||
|
export class XHRClientDriver extends ClientDriver{
|
||||||
|
|
||||||
|
|
||||||
|
/* (1) Creates a client driver
|
||||||
|
*
|
||||||
|
* @_resource<String> Target resource (typically an URL)
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
constructor(_resource){
|
||||||
|
|
||||||
|
/* (0) Parent check */
|
||||||
|
if( !super().error )
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* (1) Set inherited attributes */
|
||||||
|
this.resource = _resource;
|
||||||
|
|
||||||
|
/* (2) Create useful attributes */
|
||||||
|
this.xhr = null;
|
||||||
|
|
||||||
|
/* (N) Set all is ok */
|
||||||
|
this.error = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (2) Binds the client to the resource
|
||||||
|
*
|
||||||
|
* @return bound<boolean> Whether the binding has been successful
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
bind(){
|
||||||
|
|
||||||
|
/* (0) Parent check */
|
||||||
|
if( !super.bind() )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* (1) Create XHR instance */
|
||||||
|
this.xhr = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHttpRequest');
|
||||||
|
|
||||||
|
/* (2) Manage after request callbacks */
|
||||||
|
this.xhr.onreadystatechange = function(){
|
||||||
|
|
||||||
|
switch(this.xhr.readyState){
|
||||||
|
|
||||||
|
// Request sent
|
||||||
|
case 4:
|
||||||
|
|
||||||
|
// Got response
|
||||||
|
if( this.xhr.status === 200 ){
|
||||||
|
|
||||||
|
this.callback.onreceive(this.xhr.responseText);
|
||||||
|
this.state = ClientDriver.STATE.CLOSED;
|
||||||
|
this.callback.onclose();
|
||||||
|
|
||||||
|
// Request error
|
||||||
|
}else{
|
||||||
|
|
||||||
|
this.state = ClientDriver.STATE.CLOSED;
|
||||||
|
this.callback.onclose();
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
/* (3) State is now CONNECTED (wrong but ..) */
|
||||||
|
this.state = ClientDriver.STATE.CONNECTED;
|
||||||
|
|
||||||
|
/* (4) Bind to callback onready() */
|
||||||
|
this.callback.onready();
|
||||||
|
|
||||||
|
/* (5) Return success */
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (3) Send request
|
||||||
|
*
|
||||||
|
* @_request<Object> Request data
|
||||||
|
*
|
||||||
|
* @return sent<boolean> Whether the request has been successful
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
send(_request){
|
||||||
|
|
||||||
|
/* (1) Fast argument check
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
/* (1) Parent check */
|
||||||
|
if( !super.send(_request) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* (2) Error: invalid _request.path */
|
||||||
|
if( typeof _request.path !== 'string' )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* (2) Manage _request.path argument
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
/* (1) Error: invalid path format */
|
||||||
|
if( !/^([A-Z]+) (.+)/i.test(_request.path) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
/* (2) Extract path data */
|
||||||
|
let http_method = RegExp.$1;
|
||||||
|
let http_uri = RegExp.$2;
|
||||||
|
|
||||||
|
|
||||||
|
/* (3) Manage _request.form argument
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
/* (1) Helpers */
|
||||||
|
let is_object = typeof _request.form === 'object';
|
||||||
|
let is_formdata = is_object && _request.form instanceof FormData;
|
||||||
|
|
||||||
|
/* (2) Create default value for _request.form */
|
||||||
|
let form_data = new FormData();
|
||||||
|
|
||||||
|
/* (3) If already FormData object -> store as it is */
|
||||||
|
if( is_object && is_formdata )
|
||||||
|
form_data = _requset.form;
|
||||||
|
|
||||||
|
/* (4) If just a raw object -> convert to FormData */
|
||||||
|
if( is_object && !is_formdata )
|
||||||
|
Object.keys(_request.form).map( (k, v) => form_data.append(k, v) );
|
||||||
|
|
||||||
|
|
||||||
|
/* (4) Open connection
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
/* (1) Build full URL */
|
||||||
|
let request_url = this.resource.split(/\/$/).concat(http_uri.split(/^\//)).filter((v) => v.trim().length).join('/');
|
||||||
|
|
||||||
|
/* (2) Open connection */
|
||||||
|
this.xhr.open(http_method, request_url, true);
|
||||||
|
|
||||||
|
/* (3) Send request */
|
||||||
|
this.xhr.send(form_data);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (4) Bind event to connection opened
|
||||||
|
*
|
||||||
|
* @_callback<Function> Callback launched when connection is opened
|
||||||
|
*
|
||||||
|
* @return bound<boolean> Whether the callback has successfully been bound
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
onready(_callback){
|
||||||
|
|
||||||
|
/* (0) Parent check */
|
||||||
|
if( !super.onready(_callback) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (5) Bind event to connection closed
|
||||||
|
*
|
||||||
|
* @_callback<Function> Callback launched when connection is closed
|
||||||
|
*
|
||||||
|
* @return bound<boolean> Whether the callback has successfully been bound
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
onclose(_callback){
|
||||||
|
|
||||||
|
/* (0) Parent check */
|
||||||
|
if( !super.onclose(_callback) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* (6) Bind event to message reception
|
||||||
|
*
|
||||||
|
* @_callback<Function> Callback launched when a message is received
|
||||||
|
*
|
||||||
|
* @return bound<boolean> Whether the callback has successfully been bound
|
||||||
|
*
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
onreceive(_callback){
|
||||||
|
|
||||||
|
/* (0) Parent check */
|
||||||
|
if( !super.onreceive(_callback) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue