diff --git a/webpack/common.js b/webpack/common.js index ccbc236..c01b377 100644 --- a/webpack/common.js +++ b/webpack/common.js @@ -1,3 +1,4 @@ +import {APIClient} from './lib/api-client' import {GlobalStore} from './lib/gstore' import VueRouter from 'vue-router' import routes from './routes' @@ -6,8 +7,14 @@ import {ContentController} from './lib/content-controller' import {RoomController} from './lib/room-controller' import {ChannelController} from './lib/channel-controller' +import XHRClientDriver from './lib/client/xhr.js' +import WebSocketClientDriver from './lib/client/ws.js' -window.gs = new GlobalStore(); + +window.gs = new GlobalStore(); +window.api = new APIClient('localhost', null, false); +window.xhrcd = XHRClientDriver; +window.wscd = WebSocketClientDriver; /* (1) Global data ---------------------------------------------------------*/ diff --git a/webpack/lib/api-client.js b/webpack/lib/api-client.js index 02b5769..65abd2f 100644 --- a/webpack/lib/api-client.js +++ b/webpack/lib/api-client.js @@ -1,81 +1,142 @@ /* classe API */ export class APIClient{ - constructor(target){ - this.target = target; - this.xhr = []; // tableau d'objets pour les requêtes ajax - this.buffer = null; // Contiendra le buffer pour debugger si erreur de parsage - this.error = { // error constants - '-1': 'Invalid target format: "METHOD module/method"', - '-2': 'XHR error', - '-3': 'Invalid JSON response', + /* (1) Constructs an API client manager + * + * @_hostname Server hostname (without http, port number, etc) + * @_baseuri Server base URI + * @_ssl [OPT] Whether SSL is activated (http vs https) (default: https) + * @_port [OPT] optional HTTP port (default: none) + * + * @return http_url Built http_url + * + ---------------------------------------------------------*/ + constructor(_hostname, _baseuri, _ssl=true, _port=null){ + + /* (1) Build URL parts */ + this.http_parts = { + + ssl: _ssl === true, + hostname: _hostname, + baseuri: this.fix_uri(_baseuri), + port: !isNaN(_port) ? _port: null + }; + this.xhr_stack = []; // Ajax request stack + this.buffer = null; // Last request buffer + + } + + + fix_uri(_uri){ + + if( typeof _uri !== 'string' ) + return ''; + + return _uri.split('/').filter((r) => r.trim().length).join('/') + + } + + + /* (2) HTTP url dynamic getter + * + * @_uri [OPT] optional URI string + * @_token [OPT] optional HTTP token + * + * @return http_url Built http_url + * + ---------------------------------------------------------*/ + build_url(_uri, _token=null){ + + /* (1) Initialize URL buffer */ + let bufurl = 'http'; + + /* (2) Manage @ssl */ + this.http_parts.ssl && ( bufurl = bufurl.concat('s') ); + bufurl = bufurl.concat('://'); + + /* (3) Manage token */ + ( typeof _token === 'string' ) && ( bufurl = bufurl.concat(`${_token}@`) ); + + /* (4) Manage hostname */ + bufurl = bufurl.concat(this.http_parts.hostname); + + /* (5) Manage port */ + ( this.http_parts.port !== null ) && ( bufurl = bufurl.concat(`:${this.http_parts.port}`) ); + + /* (6) Base uri */ + bufurl = bufurl.concat(`/${this.http_parts.baseuri}/`); + + /* (7) Manage URI */ + bufurl = bufurl.concat( this.fix_uri(_uri) ); + + return bufurl; + } - /* transaction avec le serveur (http://{host}/api/) + /* Server Transaction * - * @param pTarget URI cible, format "HTTP_METHOD uri/uri/uri" - * @param pArgs Le tableu d'arguments passé en POST (attribut<->postfield) à http://{host}/api/ - * @param pHandler Fonction qui s'éxécutera lors de la réponse (1 argument -> réponse) - * @param pToken Optionnel, token d'auth pour l'api + * @param _path target path (format "HTTP_METHOD uri/uri/uri") + * @param _args formdata object (as raw object) + * @param _callback Response callback + * @param _token [OPT] http token * *************************************************************************************************** * * @usecase * 1. api.call( - * 2. "PUT newspaper/article/4" - * 2. { content: "New article content" }, - * 3. function(resp){ - * 4. alert(resp.error); - * 5. } + * 2. 'PUT newspaper/article/4' + * 3. { content: "new content" }, + * 4. (r) => alert(r.error), + * 5. 'sometoken' * 6. ); * */ - call(pTarget, pArgs, pHandler, pToken=null){ + call(_path, _args, _callback, _token=null){ - /* (1) Check @pHandler (for dispatching errors) + /* (1) Argument management ---------------------------------------------------------*/ - /* (1) Check if is a Function */ - if( !(pHandler instanceof Function) ) - throw new Error("3rd argument must be a function, but is of type '"+typeof(pHandler)+"' !"); + /* (1) Set default callback if @callback not callable */ + if( !(_callback instanceof Function) ) + _callback = function(r){ console.warn('The API callback function is missing, default callback set.', 'Response', r); }; - - - /* (2) Check @pTarget - ---------------------------------------------------------*/ - /* (1) Check format */ - if( !/^([A-Z]+) (.+)/i.test(pTarget) ){ - pHandler({ error: -1, ErrorDescription: this.error['-1'] }); + /* (2) Check @path format */ + if( !/^([A-Z]+) (.+)/i.test(_path) ){ + _callback({ error: -1 }); return false; } - /* (2) Store locally data */ - var lHttpMethod = RegExp.$1; - var lUri = RegExp.$2; + var http_method = RegExp.$1; + var http_uri = RegExp.$2; + + /* (3) Default @_token */ + if( typeof _token !== 'string' ) + _token = null; + /* (3) Create form data ---------------------------------------------------------*/ /* (1) Create virtual form */ - var lForm = new FormData(); + var form_data = new FormData(); /* (2) Add attributes */ - for( var key in pArgs ){ + for( var key in _args ){ // {2.1} If a file -> send as it // - if( pArgs[key] instanceof File ) - lForm.append(key, pArgs[key]); + if( _args[key] instanceof File ) + form_data.append(key, _args[key]); // {2.2} Else -> JSON stringify // else - lForm.append(key, JSON.stringify(pArgs[key])); + form_data.append(key, JSON.stringify(_args[key])); } @@ -84,74 +145,69 @@ export class APIClient{ /* (4) Create XHR request ---------------------------------------------------------*/ /* (1) Clean ended requests */ - for( var i = this.xhr.length-1 ; i >= 0 ; i-- ){ + for( var i = this.xhr_stack.length-1 ; i >= 0 ; i-- ){ - if( this.xhr[i] != null ) + if( this.xhr_stack[i] != null ) break; - this.xhr.pop(); + this.xhr_stack.pop(); } /* (2) Push a new entry -> fetch its index */ - i = this.xhr.push(null) - 1; + i = this.xhr_stack.push(null) - 1; /* (3) Create XHR object */ - this.xhr[i] = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHttpRequest'); + this.xhr_stack[i] = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHttpRequest'); /* (5) Bind response event ---------------------------------------------------------*/ - var self = this; // to access the buffer - - this.xhr[i].onreadystatechange = function(i){ + this.xhr_stack[i].onreadystatechange = function(i, parent){ /* (1) If request over */ - if( this.xhr[i].readyState == 4 ){ + if( this[i].readyState === 4 ){ /* (2) Update buffer (for debug) */ - self.buffer = this.xhr[i].responseText; + parent.buffer = this[i].responseText; /* (3) If request success */ - if( [0, 200, 417].indexOf(this.xhr[i].status) > -1 ){ + if( [0, 200].indexOf(this[i].status) > -1 ){ /* (3.1) Create default response (if JSON error) */ - var response = {error:-3, ErrorDescription: self.error['-3']}; + var response = { error: -2 }; /* (3.2) Try to parse JSON */ - try{ response = JSON.parse(this.xhr[i].responseText); }catch(e){} + try{ response = JSON.parse(this[i].responseText); }catch(e){} - /* (3.3) Launch @pHandler with response */ - pHandler(response); + /* (3.3) Launch @_callback with response */ + _callback(response); /* (4) If request error */ }else - pHandler({ error:-2, ErrorDescription: self.error['-2'] }); + _callback({ error: -3 }); /* (5) Notify current xhr instance is done */ - this.xhr[i] = null; + this[i] = null; } - }.bind(this, i); + }.bind(this.xhr_stack, i, this); /* (6) Finish & send request ---------------------------------------------------------*/ /* (1) Open the XHR */ - this.xhr[i].open(lHttpMethod, this.target+lUri, true); + console.log(http_method, this.build_url(http_uri, _token)); + this.xhr_stack[i].open(http_method, this.build_url(http_uri, _token), true); - /* (2) Manage optional token */ - if( pToken != null ) - this.xhr[i].setRequestHeader('Authorization', 'Digest '+pToken); + /* (2) Custom header to notify we're using Ajax */ + this.xhr_stack[i].setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - /* (3) Custom header to notify we're using Ajax */ - this.xhr[i].setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - - /* (4) Make the call */ - this.xhr[i].send( lForm ); + /* (3) Make the call */ + this.xhr_stack[i].send(form_data); return true; diff --git a/webpack/lib/client/client-driver.js b/webpack/lib/client/client-driver.js index becfba9..65f9d9a 100644 --- a/webpack/lib/client/client-driver.js +++ b/webpack/lib/client/client-driver.js @@ -1,15 +1,15 @@ -export class ClientDriver{ +export default class ClientDriver{ /* (0) Constants & Enums ---------------------------------------------------------*/ static get STATE(){ return Object.freeze({ - OPENING: 0, - OPENED: 1, + CLOSED: 0, + READY: 1, CONNECTING: 2, CONNECTED: 3, - CLOSED: 4 + TRANFERING: 4 }); } @@ -22,27 +22,81 @@ export class ClientDriver{ ---------------------------------------------------------*/ constructor(_resource){ + /* (1) Initialize driver + ---------------------------------------------------------*/ /* (1) Default attributes values */ - this.error = false; - this.state = ClientDriver.STATE.OPENING; - this.stack = []; + this.force_close = false; + this.state = ClientDriver.STATE.READY; + this.stack = []; + + /* (2) Default User callbacks */ this.callback = { - onready: function(){}, - onclose: function(){}, - onreceive: function(){} + onconnected: function(){}, + onclose: function(){}, + onreceive: function(){} }; - /* (2) Fail: invalid _resource */ + this.event = { + + /* (3) Manage connection opened */ + onconnected: function(){ + + // update state + this.state = ClientDriver.STATE.CONNECTED; + + // call callback + this.callback.onconnected(); + + // if request(s) in stack -> pop & send them + while( this.stack.length > 0 ) + this.send(this.stack.shift()); + + }.bind(this), + + /* (4) Manage connection closed */ + onclose: function(){ + + // if called close() -> CLOSED + if( this.force_close ) + this.state = ClientDriver.STATE.CLOSED; + + // else set state back to READY + else + this.state = ClientDriver.STATE.READY; + + // call callback + this.callback.onclose(); + + }.bind(this), + + /* (5) Manage response received */ + onreceive: function(_response){ + + // set state from TRANSFERING to CONNECTED + this.state = ClientDriver.STATE.CONNECTED; + + // call callback + this.callback.onreceive(_response); + + }.bind(this) + + }; + + /* (2) Manage errors + ---------------------------------------------------------*/ + /* (1) Fail: invalid _resource */ if( typeof _resource !== 'string' ) return ( this.error = true ); - /* (3) Set explicit attributes */ + /* (2) Set explicit attributes */ this.resource = _resource; return; + } + /* (2) Binds the client to the resource * * @return bound Whether the binding has been successful @@ -50,17 +104,43 @@ export class ClientDriver{ ---------------------------------------------------------*/ bind(){ - /* (1) Fail: not OPENING OR error */ - if( this.error || this.state !== ClientDriver.STATE.OPENING ) + /* (1) force_close -> fail */ + if( this.force_close ) return false; - /* (2) By default return success */ + /* (2) Fail: not READY */ + if( this.state !== ClientDriver.STATE.READY ) + return false; + + /* (3) Update state */ + this.state = ClientDriver.STATE.CONNECTING; + + /* (4) Child dispatch */ return true; } - /* (3) Send request + /* (3) Closes the connection + * + * @return closed Whether the connection has been closed + * + ---------------------------------------------------------*/ + close(){ + + /* (1) set force_close */ + this.force_close = true; + + /* (2) Update state */ + this.state = ClientDriver.STATE.CLOSED; + + /* (4) Child dispatch */ + return true; + + } + + + /* (4) Send request * * @_request Request data * @@ -69,111 +149,99 @@ export class ClientDriver{ ---------------------------------------------------------*/ send(_request){ - /* (1) Fail: if error */ - if( this.error ) + /* (1) force_close -> fail */ + if( this.force_close ) return false; - /* (2) Fail: invalid _request */ + /* (2) bufferise 'state' */ + let state = this.state; + + /* (3) 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); + /* (4) CLOSED -> abort */ + if( state === ClientDriver.STATE.CLOSED ) return false; - } + /* (5) READY -> stack message + bind() [in case connection failed)] */ + if( state === ClientDriver.STATE.READY ) + return ( this.stack.push(_request) > -1 ) && this.bind(); + /* (6) CONNECTING -> stack message */ + if( state === ClientDriver.STATE.CONNECTING ) + return ( this.stack.push(_request) > -1 ); - /* (4) By default return success */ + /* (7) update state */ + this.state = ClientDriver.STATE.TRANFERING; + + /* (8) Child dispatch */ return true; } - /* (4) Bind event to connection opened + /* (5) Bind event to connection successfully opened * * @_callback Callback launched when connection is opened * * @return bound Whether the callback has successfully been bound * ---------------------------------------------------------*/ - onready(_callback){ + set onconnected(_callback){ - /* (1) Fail: already CONNECTED OR error */ - if( this.error || this.state >= ClientDriver.STATE.CONNECTED ) - return false; - - /* (2) Fail: invalid _callback */ + /* (1) Fail: invalid _callback */ if( !(_callback instanceof Function) ) return false; - /* (3) Register _callback */ - this.callback.onready = function(cback){ + /* (2) Register _callback */ + this.callback.onconnected = _callback; - // 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 */ + /* (3) By default return success */ return true; } - /* (5) Bind event to connection closed + /* (6) Bind event to connection closed * * @_callback Callback launched when connection is closed * * @return bound Whether the callback has successfully been bound * ---------------------------------------------------------*/ - onclose(_callback){ + set onclose(_callback){ - /* (1) Fail: already CLOSED OR error */ - if( this.error || this.state === ClientDriver.STATE.CLOSED ) - return false; - - /* (2) Fail: invalid _callback */ + /* (1) Fail: invalid _callback */ if( !(_callback instanceof Function) ) return false; - /* (3) Register _callback */ + /* (2) Register _callback */ this.callback.onclose = _callback; - /* (4) By default return success */ + /* (3) By default return success */ return true; } - /* (6) Bind event to message reception + /* (7) Bind event to message reception * * @_callback Callback launched when a message is received * * @return bound Whether the callback has successfully been bound * ---------------------------------------------------------*/ - onreceive(_callback){ + set onreceive(_callback){ - /* (1) Fail: already CONNECTED OR error */ - if( this.error || this.state >= ClientDriver.STATE.CONNECTED ) - return false; - - /* (2) Fail: invalid _callback */ + /* (1) Fail: invalid _callback */ if( !(_callback instanceof Function) ) return false; - /* (3) Register _callback */ + /* (2) Register _callback */ this.callback.onreceive = _callback; - /* (4) By default return success */ + /* (3) By default return success */ return true; } diff --git a/webpack/lib/client/ws.js b/webpack/lib/client/ws.js index fffb96a..243b861 100644 --- a/webpack/lib/client/ws.js +++ b/webpack/lib/client/ws.js @@ -1,6 +1,6 @@ -import {ClientDriver} from './client-driver.js' +import ClientDriver from './client-driver.js' -export class WebSocketClientDriver extends ClientDriver{ +export default class WebSocketClientDriver extends ClientDriver{ /* (1) Creates a client driver @@ -21,9 +21,6 @@ export class WebSocketClientDriver extends ClientDriver{ this.ws = null; this.buffer = null; // useful when waiting for WebSocket to open - /* (N) Set all is ok */ - this.error = false; - } @@ -42,28 +39,16 @@ export class WebSocketClientDriver extends ClientDriver{ this.ws = new WebSocket(this.resource); /* (2) Bind callback.onready */ - this.ws.onopen = function(){ - this.state = ClientDriver.STATE.CONNECTED; - this.callback.onready(); - }.bind(this); + this.ws.onopen = this.event.onconnected; /* (3) Bind callback.onclose */ - this.ws.onclose = function(){ - this.state = ClientDriver.STATE.CLOSED; - this.callback.onclose(); - }.bind(this); + this.ws.onclose = this.event.onclose; /* (4) Bind callback.onerror */ - this.ws.onerror = function(){ - this.state = ClientDriver.STATE.CLOSED; - this.callback.onclose(); - }.bind(this); + this.ws.onerror = this.event.onclose; /* (5) Bind callback.onreceive */ - this.ws.onmessage = function(response){ - this.state = ClientDriver.STATE.CONNECTED; - this.callback.onreceive(response); - }.bind(this); + this.ws.onmessage = (_message_event) => this.event.onreceive(_message_event.data); /* (6) Return success */ return true; @@ -85,11 +70,12 @@ export class WebSocketClientDriver extends ClientDriver{ return false; /* (1) Error: invalid _request.buffer */ - if( typeof _request.buffer !== 'string' ) + if( typeof _request.buffer !== 'string' ){ + this.state = ClientDriver.STATE.CONNECTED; return false; + } /* (2) Send message */ - console.log('sent', _request.buffer); this.ws.send(_request.buffer); /* (3) Return success */ @@ -98,50 +84,22 @@ export class WebSocketClientDriver extends ClientDriver{ } - /* (4) Bind event to connection opened + /* (3) Closes the connection * - * @_callback Callback launched when connection is opened - * - * @return bound Whether the callback has successfully been bound + * @return closed Whether the connection has been closed * ---------------------------------------------------------*/ - onready(_callback){ + close(){ /* (0) Parent check */ - if( !super.onready(_callback) ) + if( !super.close() ) return false; - } + /* (1) Close websocket */ + this.ws.close(); - - /* (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; + /* (2) Return success */ + return true; } diff --git a/webpack/lib/client/xhr.js b/webpack/lib/client/xhr.js index be96da0..e4e5361 100644 --- a/webpack/lib/client/xhr.js +++ b/webpack/lib/client/xhr.js @@ -1,6 +1,6 @@ -import {ClientDriver} from './client-driver.js' +import ClientDriver from './client-driver.js' -export class XHRClientDriver extends ClientDriver{ +export default class XHRClientDriver extends ClientDriver{ /* (1) Creates a client driver @@ -20,9 +20,6 @@ export class XHRClientDriver extends ClientDriver{ /* (2) Create useful attributes */ this.xhr = null; - /* (N) Set all is ok */ - this.error = false; - } @@ -51,17 +48,13 @@ export class XHRClientDriver extends ClientDriver{ // Got response if( this.xhr.status === 200 ){ - this.callback.onreceive(this.xhr.responseText); - this.state = ClientDriver.STATE.CLOSED; - this.callback.onclose(); + this.event.onreceive(this.xhr.responseText); + this.event.onclose(); // Request error - }else{ + }else + this.event.onclose(); - this.state = ClientDriver.STATE.CLOSED; - this.callback.onclose(); - - } break; } @@ -69,11 +62,11 @@ export class XHRClientDriver extends ClientDriver{ }.bind(this); - /* (3) State is now CONNECTED (wrong but ..) */ + /* (3) State is now CONNECTED (ready to send) */ this.state = ClientDriver.STATE.CONNECTED; - /* (4) Bind to callback onready() */ - this.callback.onready(); + /* (4) Bind to callback onconnected() */ + this.event.onconnected(); /* (5) Return success */ return true; @@ -97,14 +90,18 @@ export class XHRClientDriver extends ClientDriver{ return false; /* (2) Error: invalid _request.path */ - if( typeof _request.path !== 'string' ) + if( typeof _request.path !== 'string' ){ + this.state = ClientDriver.STATE.CONNECTED; return false; + } /* (2) Manage _request.path argument ---------------------------------------------------------*/ /* (1) Error: invalid path format */ - if( !/^([A-Z]+) (.+)/i.test(_request.path) ) + if( !/^([A-Z]+) (.+)/i.test(_request.path) ){ + this.state = ClientDriver.STATE.CONNECTED; return; + } /* (2) Extract path data */ @@ -145,54 +142,4 @@ export class XHRClientDriver extends ClientDriver{ } - - /* (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/mockup/api.list b/webpack/mockup/api.list new file mode 100644 index 0000000..3b68046 --- /dev/null +++ b/webpack/mockup/api.list @@ -0,0 +1,81 @@ +=== USER === + + >>> POST /user/ + { + username: + password: + } + + >>> GET /user/:uid + {} + + >>> PUT /user/:uid + { + username: + passowrd: + } + + >>> DELETE /user/:uid + {} + +=== USER AUTHENTICATION === + + >>> POST /user/token + { + username: + password: + } + + +=== CHANNEL === + + + >>> POST @token:/channel/ + { + link: + name: + } + + >>> GET @token:/channel/:cid --> channel specific data + >>> GET @token:/channel/ --> all available channels for user + {} + + >>> PUT @token:/channel/:cid + { + link: + name: + } + + >>> DELETE @token:/channel/:cid + {} + + + + +=== CHANNEL <> USER === + + + >>> POST @token:/channel/:cid/subscribe + {} + + >>> DELETE @token:/channel/:cid/subscribe + {} + + +=== ROOM === + + >>> POST @token:/channel/:cid/room/:type/ + { + name: + } + + >>> GET @token:/channel/:cid/room/:type/ --> all channel's rooms of this type + + >>> GET @token:/channel/:cid/room/:type/:rid --> room information of this type + + >>> PUT @token:/channel/:cid/room/:type/:rid + { + name: + } + + >>> DELETE @token:/channel/:cid/room/:type/:rid --> remove room