UNPKG

toloframework

Version:

Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.

449 lines (412 loc) 14 kB
"use strict"; exports.BAD_ROLE = -1; exports.BAD_TYPE = -2; exports.CONNECTION_FAILURE = -3; exports.MISSING_AUTOLOGIN = -4; exports.UNKNOWN_USER = -5; exports.HTTP_ERROR = -6; /** * Call a webservice. * @param {string} name - Service's name. * @param {object} args - Service's arguments. * @param {string=undefined} url - You can define your own URL to your `svc.php` file. */ exports.get = callWebService; /** * Request any web server with POST method and returns text. * * @param {string} url - URL of the server to request. Can contain GET params. * @param {object} args - Arguments to send with the POST methos. * @type {Promise} Resolves in the response as string. */ exports.request = request; /** * @return {boolean} */ exports.isAdmin = isAdmin; /** * @param {string} role - Role's name. * @return {boolean} */ exports.hasRole = hasRole; /** * @return Current user. */ exports.user = user; /* * Load a JSON file and return a Promise. * @param {string} path Local path relative to the current HTML page. */ exports.loadJSON = loadJSON; /** * Event fired when login status has changed. * @type {tfw.listeners} */ exports.eventChange = new( require( "tfw.listeners" ) )(); exports.changeEvent = exports.eventChange; /** * @return If there is a user connected or not. */ exports.isLogged = isLogged; /** * Disconnect current user. * @return {Promise} A _thenable_ object resolved as soon as the server answered. */ exports.logout = logout; /** * Try to connect a user. * @param {string} usr Login name. * @param {string} pwd Password. * @return {Promise} * */ exports.login = login; /** * @param {string} key * @param {any} val */ exports.config = configGetSet; Object.defineProperty( exports, 'userData', { get: function() { if ( currentUser ) return currentUser.data || {}; return {}; }, set: function() {}, configurable: true, enumerable: true } ); require( "polyfill.promise" ); const Cfg = require( "$" ).config, Storage = require( "tfw.storage" ); const config = { // In `package.json`, you can override the services URL. // { "tfw": { "consts": { "debug": "tfw", "release": "http://tolokoban.org/tfw" } } } url: typeof Cfg.consts.tfw === 'string' ? Cfg.consts.tfw : "tfw" }, saved = Storage.local.get( "nigolotua" ); let currentUser = null; if ( Array.isArray( saved ) ) { config.usr = saved[ 0 ]; config.pwd = saved[ 1 ]; } /** * Request any web server with POST method and returns text. * * @param {string} url - URL of the server to request. Can contain GET params. * @param {object} args - Arguments to send with the POST methos. * @returns {Promise} Resolves in the response as string. */ function request( url, args ) { return new Promise( function( resolve, reject ) { const xhr = new XMLHttpRequest(); xhr.open( "POST", url, true ); // true is for async. xhr.onload = () => { const DONE = 4; if ( xhr.readyState !== DONE ) return; console.info( "xhr.status=", xhr.status ); console.info( "xhr.responseText=", xhr.responseText ); if ( xhr.status === 200 ) resolve( xhr.responseText ); /* else reject( { id: exports.HTTP_ERROR, msg: "(" + xhr.status + ") " + xhr.statusText, status: xhr.status } ); */ }; xhr.onerror = function() { reject( { id: exports.HTTP_ERROR, err: "HTTP_ERROR (" + xhr.status + ") " + xhr.statusText, status: xhr.status } ); }; const params = Object.keys( args ) .map( key => `${key}=${encodeURIComponent(args[key])}` ) .join( "&" ); xhr.setRequestHeader( "Content-type", "application/x-www-form-urlencoded" ); xhr.send( params ); } ); } function svc( name, args, url ) { return new Promise( function( resolve, reject ) { if ( typeof url === 'undefined' ) url = config.url; var that = this; var xhr = new XMLHttpRequest( { mozSystem: true } ); if ( 'withCredentials' in xhr ) { xhr.open( "POST", url + "/svc.php", true ); xhr.withCredentials = true; // Indispensable pour le CORS. } else { // IE xhr = new XDomainRequest(); xhr.open( "POST", url + "/svc.php" ); } xhr.onload = function() { if ( xhr.status != 200 ) { return reject( { id: exports.HTTP_ERROR, msg: "(" + xhr.status + ") " + xhr.statusText, status: xhr.status } ); } var value = xhr.responseText; if ( typeof value === "string" ) { if ( value.substr( 0, 1 ) == "!" ) { reject( { id: exports.BAD_ROLE, err: Error( "Service \"" + name + "\" needs role \"" + value.substr( 1 ) + "\"!" ) } ); } var valueObject; try { valueObject = JSON.parse( value ); } catch ( ex ) { console.error( "[tfw.web-service:svc] Value = ", value ); reject( { id: exports.BAD_TYPE, err: Error( "Service \"" + name + "\" should return a valid JSON!\n" + ex ) } ); } resolve( valueObject ); } else { reject( { id: exports.BAD_TYPE, err: Error( "Service \"" + name + "\" should return a string!" ) } ); } }; xhr.onerror = function() { reject( { id: exports.HTTP_ERROR, err: "HTTP_ERROR (" + xhr.status + ") " + xhr.statusText, status: xhr.status } ); }; var params = "s=" + encodeURIComponent( name ); if ( typeof args !== 'undefined' ) { params += "&i=" + encodeURIComponent( JSON.stringify( args ) ); } xhr.setRequestHeader( "Content-type", "application/x-www-form-urlencoded" ); xhr.withCredentials = true; // Indispensable pour le CORS. xhr.send( params ); } ); } /** * Load a JSON file and return a Promise. * @param {string} path Local path relative to the current HTML page. */ function loadJSON( path ) { return new Promise( function( resolve, reject ) { var xhr = new XMLHttpRequest( { mozSystem: true } ); xhr.onload = function() { var text = xhr.responseText; try { resolve( JSON.parse( text ) ); } catch ( ex ) { reject( Error( "Bad JSON format for \"" + path + "\"!\n" + ex + "\n" + text ) ); } }; xhr.onerror = function() { reject( Error( "Unable to load file \"" + path + "\"!\n" + xhr.statusText ) ); }; xhr.open( "GET", path, true ); xhr.withCredentials = true; // Indispensable pour le CORS. xhr.send(); } ); }; /** * @return If there is a user connected or not. */ function isLogged() { if ( !currentUser ) return false; return true; }; /** * Disconnect current user. * @return {Promise} A _thenable_ object resolved as soon as the server answered. */ function logout() { currentUser = null; delete config.usr; delete config.pwd; Storage.local.set( "nigolotua", null ); exports.eventChange.fire(); return svc( "tfw.login.Logout" ); }; /** * Try to connect a user. * @param {string} usr Login name. * @param {string} pwd Password. * @return {Promise} * */ function login( usr, pwd ) { if ( typeof usr === 'undefined' ) usr = config.usr || ''; if ( typeof pwd === 'undefined' ) pwd = config.pwd || ''; return new Promise( function( resolve, reject ) { if ( typeof usr === 'undefined' ) { var autologin = Storage.local.get( "nigolotua" ); if ( !Array.isArray( autologin ) ) return reject( { id: exports.MISSING_AUTOLOGIN } ); usr = autologin[ 0 ] || ''; pwd = autologin[ 1 ] || ''; } Storage.local.set( "nigolotua", null ); svc( "tfw.login.Challenge", usr ) .then( function( code ) { // Hashage du mot de passe à l'aide du code. var output = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], i, j = 0, pass = [], k1, k2, k3; for ( i = 0; i < pwd.length; i++ ) { pass.push( pwd.charCodeAt( i ) ); } if ( 256 % pass.length == 0 ) { pass.push( 0 ); } for ( i = 0; i < 256; i++ ) { output[ i % 16 ] ^= i + pass[ i % pass.length ]; k1 = code[ j++ % code.length ] % 16; k2 = code[ j++ % code.length ] % 16; k3 = code[ j++ % code.length ] % 16; output[ k3 ] ^= ( output[ k3 ] + 16 * k2 + k3 ) % 256; output[ k2 ] ^= ( output[ k1 ] + output[ k3 ] ) % 256; } return svc( "tfw.login.Response", output ); }, reject ) .then( function( user ) { if ( typeof user === 'object' ) { currentUser = { data: user, hasRole: function( role ) { for ( var i = 0; i < user.roles.length; i++ ) { var item = user.roles[ i ]; if ( item == role ) return true; } return false; } }; Storage.local.set( "nigolotua", [ usr, pwd ] ); exports.eventChange.fire(); resolve( user ); } else { currentUser = null; reject( { id: exports.UNKNOWN_USER, err: "Unknown user '" + usr + "'!" } ); } }, reject ); } ); }; /** * Call a webservice. */ function callWebService( name, args, url ) { return new Promise( function( resolve, reject ) { svc( name, args, url ).then( resolve, function( err ) { if ( typeof err === 'object' && err.id === exports.BAD_ROLE ) { // Echec de connexion, on retente de se connecter avant d'abandonner. login().then( function() { svc( name, args, url ).then( resolve, reject ); }, reject ); } else { reject( err ); } } ); } ); }; function isAdmin() { return hasRole( 'ADMIN' ); }; function hasRole( role ) { if ( !currentUser ) return false; return currentUser.hasRole( role ); }; function user() { return currentUser; }; function configGetSet( key, val ) { if ( typeof val === 'undefined' ) { return config[ key ]; } config[ key ] = val; return val; }; // _Backward compatibility. if ( window.$$ ) { window.$$.service = function( name, args, caller, onSuccess, onError ) { var p = exports.get( name, args ); p.then( function( value ) { if ( onSuccess ) { return caller[ onSuccess ].call( caller, value ); } return value; }, function( reason ) { if ( onError ) { return caller[ onError ].call( caller, reason ); } return reason; } ); }; } var properties = { userID: function() { if ( !currentUser ) return 0; return currentUser.data.id; }, userLogin: function() { if ( !currentUser ) return null; return currentUser.data.login; }, userName: function() { if ( !currentUser ) return null; return currentUser.data.name; } }; for ( var key in properties ) { Object.defineProperty( exports, key, { get: properties[ key ], set: function( v ) { throw Error( "Property " + key + " is read-only!" ); }, configurable: true, enumerable: true } ); }