From 1cedd6284abcab8106b34893c8c71905122cb037 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Fri, 23 Mar 2018 18:43:59 +0100 Subject: [PATCH] [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 --- webpack/lib/client/client-driver.js | 183 +++++++++++++++++++++++++ webpack/lib/client/ws.js | 150 +++++++++++++++++++++ webpack/lib/client/xhr.js | 198 ++++++++++++++++++++++++++++ 3 files changed, 531 insertions(+) create mode 100644 webpack/lib/client/client-driver.js create mode 100644 webpack/lib/client/ws.js create mode 100644 webpack/lib/client/xhr.js diff --git a/webpack/lib/client/client-driver.js b/webpack/lib/client/client-driver.js new file mode 100644 index 0000000..becfba9 --- /dev/null +++ b/webpack/lib/client/client-driver.js @@ -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 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 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 Request data + * + * @return sent 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 Callback launched when connection is opened + * + * @return bound 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 Callback launched when connection is closed + * + * @return bound 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 Callback launched when a message is received + * + * @return bound 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; + + } + + + +} \ No newline at end of file diff --git a/webpack/lib/client/ws.js b/webpack/lib/client/ws.js new file mode 100644 index 0000000..fffb96a --- /dev/null +++ b/webpack/lib/client/ws.js @@ -0,0 +1,150 @@ +import {ClientDriver} from './client-driver.js' + +export class WebSocketClientDriver extends ClientDriver{ + + + /* (1) Creates a client driver + * + * @_resource 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 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 Request data + * + * @return sent 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 Callback launched when connection is opened + * + * @return bound 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 Callback launched when connection is closed + * + * @return bound 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 Callback launched when a message is received + * + * @return bound Whether the callback has successfully been bound + * + ---------------------------------------------------------*/ + onreceive(_callback){ + + /* (0) Parent check */ + if( !super.onreceive(_callback) ) + return false; + + } + + + +} \ No newline at end of file diff --git a/webpack/lib/client/xhr.js b/webpack/lib/client/xhr.js new file mode 100644 index 0000000..be96da0 --- /dev/null +++ b/webpack/lib/client/xhr.js @@ -0,0 +1,198 @@ +import {ClientDriver} from './client-driver.js' + +export class XHRClientDriver extends ClientDriver{ + + + /* (1) Creates a client driver + * + * @_resource 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 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 Request data + * + * @return sent 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 Callback launched when connection is opened + * + * @return bound 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 Callback launched when connection is closed + * + * @return bound 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 Callback launched when a message is received + * + * @return bound Whether the callback has successfully been bound + * + ---------------------------------------------------------*/ + onreceive(_callback){ + + /* (0) Parent check */ + if( !super.onreceive(_callback) ) + return false; + + } + + + +} \ No newline at end of file