/*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ if( window.friendUP ) throw new Error( 'namespace.js - friendUP namespace is already defined, make sure this is the first script executed' ); window.friendUP = { app : {}, component : {}, gui : {}, io : {}, media : {}, system : {}, tool : {}, util : {} }; window.Friend = window.Friend || {}; /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ /** @file * * Friend New API definition - Low level * * This source must be the second after namespace.js! * * @author FL (Francois Lionet) * @date first pushed on 19/08/2018 */ // Root of the API Friend = window.Friend || {}; // Current versions of the API Friend.APIVersion = '1.2'; Friend.APIVersionMinimal = '1.2'; // We might want to change that one day when the number of versions gets too high // but we should not really... // API Variables Friend.APIDefinition = {}; Friend.NORETURN = 'noReturn'; Friend.ERROR = 'error'; // Friend API engine - only possible in Javascript! (Y) :) ////////////////////////////////////////////////////////////////////////////// // To be called at first pass of Javascript Friend.addToAPI = function( functionPath, argumentNames, properties, parentClass ) { var definition = {}; definition.functionName = functionPath.substring( functionPath.lastIndexOf( '.' ) + 1 ); definition.functionPath = functionPath; definition.argumentNames = argumentNames; // Can be ommited definition.properties = properties; if ( !parentClass ) parentClass = Friend; // Find the position of the callback definition.callbackPosition = -1; for ( var n = 0; n < argumentNames.length; n++ ) { if ( argumentNames[ n ] == 'callback' ) { definition.callbackPosition = n; break; } } definition.numberOfArguments = argumentNames.length; definition.isDirect = ( properties.tags.indexOf( '#direct' ) >= 0 ); definition.isCallback = ( properties.tags.indexOf( '#callback' ) >= 0 ); // Find the function var functionClass; if ( properties.redirection ) { functionClass = GetClass( properties.redirection.functionPath, parentClass ); } else { functionClass = GetClass( functionPath, parentClass ); } // Add to API! if ( functionClass ) { definition.klass = functionClass; parentClass.APIDefinition[ functionPath ] = definition; return true; } console.log( 'ERROR - API function not found: ' + functionPath ); return 'ERROR - API function not found: ' + functionPath; }; Friend.removeFromAPI = function( functionPath ) { if ( Friend.APIDefinition[ functionPath ] ) { Friend.APIDefinition = Friend.Utilities.cleanArray( Friend.APIDefinition, Friend.APIDefinition[ functionPath ] ); return true; } console.log( 'ERROR - API function not found: ' + functionPath ); return false; } // Create a sourcecode with the API definition, to add to your view/iFrame Friend.getAPI = function( applicationId, options, callback, extra ) { var source = ""; source += "/*******************************************************************************\n"; source += "* *\n"; source += "* Friend Unifying Platform *\n"; source += "* ------------------------ *\n"; source += "* *\n"; source += "* Copyright 2014-2017 Friend Software Labs AS, all rights reserved. *\n"; source += "* Hillevaagsveien 14, 4016 Stavanger, Norway *\n"; source += "* Tel.: (+47) 40 72 96 56 *\n"; source += "* Mail: info@friendos.com *\n"; source += "* *\n"; source += "********************************************************************************/\n"; source += "/** @file\n"; source += "*\n"; source += "* Friend API - Application level\n"; source += "*/\n"; // Get the good version... var version = Friend.APIVersion; if ( options.version ) version = options.version; var pos = version.indexOf( '.' ); if ( Friend.APIVersion == version ) { // Simple, use the current API doExport( Friend.APIDefinition, Friend ); } else { // Convert dots into underscore while ( pos >= 0 ) { version = version.substring( 0, pos ) + '_' + version.substring( pos + 1 ); pos = version.indexOf( '.', pos + 1 ); }; // Load file from server Friend.DOS.loadFile( 'System:js/api/apiv' + version + '.js', {}, function( response, data, extra ) { if ( !response ) { callback( false, 'ERROR - API not found.', extra ); } // Creates the APIDefinition from the code var fakeFriend = { Friend: {} }; var scanner = new Friend.Utilities.TextScanner( data, {} ); var definition = {}; var line = scanner.getLine( { next: true } ); while( line ) { if ( line.indexOf( 'Friend.' ) == 0 ) { // Direct source? if ( line.indexOf( 'Friend.APIDirectSources' ) == 0 ) { var pos = line.indexOf( 'System:' ); if ( pos ) { var end = line.indexOf( "'", pos ); fakeFriend.APIDirectSource.push( line.substring( start, end ) ); } } else if ( line.indexOf( 'Friend.addToAPI' ) == 0 ) { // Function definitions var parser = new Friend.Utilities.CodeParser( line, {} ); parser.setSection( '(', ')' ); var functionPath = parser.getNextToken().value; var parameters = parser.getNextToken().value; var options = parser.GetNextToken(); var error = Friend.addToAPI( functionPath, parameters, options, fakeFriend ); if ( error ) { callback( false, error, extra ); return; } } } }; doExport( definition, fakeFriend ); } ); } function doExport( definition, parent ) { // HOGNE: We need to filter the entry based on the authorisations of the application! if ( true ) { // Only specific APIs? if ( options.APIs && options.APIs.length ) { for ( var a = 0; a < options.APIs.length; a++ ) { var api = options.APIs[ a ]; for ( var d = 0; d < parent.APIDefinition.length; d++ ) { var definition = parent.APIDefinition[ d ]; if ( definition.indexOf( api ) == 0 ) { doExportDefinition( definition, parent ); } } } } else { // All the APIs for ( var d in Friend.APIDefinition ) { doExportDefinition( Friend.APIDefinition[ d ], parent ); } } // Add the functions defined in plain in the sourcecode -> duplication up and down... if ( !options.noDirectCode ) { for ( var s = 0; s < parent.APIDirectSources.length; s++ ) { var path = parent.APIDirectSources[ s ]; Friend.DOS.loadFile( path, {}, function( response, code, extra ) { if ( response ) { var markerStart = 'APIDIRECTCODESTART'; var markerEnd = 'APIDIRECTCODEEND'; var start = code.indexOf( markerStart ); if ( start >= 0 ) { var end = code.indexOf( markerEnd ); if ( end >= 0 ) { code = code.substring( start + markerStart.length, end ); source += code; } } callback( true, source, extra ); } else { callback( false, 'ERROR - Cannot load source from server.', extra ); console.log( 'ERROR - Cannot load source from server: ' + path ); } } ); } } else { callback( true, source, extra ); } } } function doExportDefinition( definition ) { // Check that the class is already defined var dot = definition.functionPath.indexOf( '.' ); while ( dot >= 0 ) { var name = definition.functionPath.substring( 0, dot ); if ( source.indexOf( name + '.' ) < 0 ) { source += name + ' = window.' + name + ' || {};\n'; } dot = definition.functionPath.indexOf( '.', dot + 1 ); } // Call the API! source += definition.functionPath + ' = function( '; for ( var p = 0; p < definition.argumentNames.length; p++ ) { if ( p > 0 ) source += ', '; source += definition.argumentNames[ p ]; } source += ' )\n'; source += '{\n'; source += ' CallLowLevelAPI( arguments, '; source += "'" + definition.functionPath + "', [ "; for ( p = 0; p < definition.argumentNames.length; p++ ) { if ( p > 0 ) source += ', '; source += "'" + definition.argumentNames[ p ] + "'"; } source += " ],{ tags: '" + definition.properties.tags + "' } );\n"; source += '};\n'; } } // Called by APIWrapper.js Friend.callAPIFunction = function( msg ) { // Get information from message var messageInfo = {}; if( msg.applicationId ) { messageInfo.view = GetContentWindowByAppMessage( findApplication( msg.applicationId ), msg ); messageInfo.applicationId = msg.applicationId; if( msg.applicationName ) messageInfo.applicationName = msg.applicationName; messageInfo.viewId = msg.viewId; messageInfo.callback = msg.callback; } // Call the function var definition = Friend.APIDefinition[ msg.method ]; if( definition ) { // Replace callback by local callback if( definition.callbackPosition >= 0 ) msg.arguments[ definition.callbackPosition ] = thisCallback; // Up to 10 arguments (Javascript -> pass more in objects) switch( definition.numberOfArguments ) { case 0: ret = definition.klass(); break; case 1: ret = definition.klass( msg.arguments[ 0 ] ); break; case 2: ret = definition.klass( msg.arguments[ 0 ], msg.arguments[ 1 ] ); break; case 3: ret = definition.klass( msg.arguments[ 0 ], msg.arguments[ 1 ], msg.arguments[ 2 ] ); break; case 4: ret = definition.klass( msg.arguments[ 0 ], msg.arguments[ 1 ], msg.arguments[ 2 ], msg.arguments[ 3 ] ); break; case 5: ret = definition.klass( msg.arguments[ 0 ], msg.arguments[ 1 ], msg.arguments[ 2 ], msg.arguments[ 3 ], msg.arguments[ 4 ] ); break; case 6: ret = definition.klass( msg.arguments[ 0 ], msg.arguments[ 1 ], msg.arguments[ 2 ], msg.arguments[ 3 ], msg.arguments[ 4 ], msg.arguments[ 5 ] ); break; case 7: ret = definition.klass( msg.arguments[ 0 ], msg.arguments[ 1 ], msg.arguments[ 2 ], msg.arguments[ 3 ], msg.arguments[ 4 ], msg.arguments[ 5 ], msg.arguments[ 6 ] ); break; case 8: ret = definition.klass( msg.arguments[ 0 ], msg.arguments[ 1 ], msg.arguments[ 2 ], msg.arguments[ 3 ], msg.arguments[ 4 ], msg.arguments[ 5 ], msg.arguments[ 6 ], msg.arguments[ 7 ] ); break; case 9: ret = definition.klass( msg.arguments[ 0 ], msg.arguments[ 1 ], msg.arguments[ 2 ], msg.arguments[ 3 ], msg.arguments[ 4 ], msg.arguments[ 5 ], msg.arguments[ 6 ], msg.arguments[ 7 ], msg.arguments[ 8 ] ); break; case 10: ret = definition.klass( msg.arguments[ 0 ], msg.arguments[ 1 ], msg.arguments[ 2 ], msg.arguments[ 3 ], msg.arguments[ 4 ], msg.arguments[ 5 ], msg.arguments[ 6 ], msg.arguments[ 7 ], msg.arguments[ 8 ], msg.arguments[ 9 ] ); break; } // If value is by return, send the message back... if( definition.isDirect ) { // Except for double access functions, when it is returned by callback. if( ret != Friend.NORETURN ) { var nmsg = { command: definition.functionName + 'Response', response: ret == Friend.ERROR ? false : true, data: ret, extra: msg.extra, isFriendAPI: true }; sendItBack( nmsg ); } } function thisCallback( response, data, extra ) { var nmsg = { command: definition.functionName + 'Response', response: response, data: data, extra: extra, isFriendAPI: true }; sendItBack( nmsg ); } } else { // API Function not found var functionName = msg.method.substring( msg.method.lastIndexOf( '.' ) + 1 ); var nmsg = { command: functionName + 'Response', response: false, data: { error: 'ERROR - API function not found.' }, extra: false, isFriendAPI: true }; sendItBack( nmsg ); } function sendItBack( message ) { if ( typeof messageInfo.callback == 'function' ) { messageInfo.callback( message ); return; } if ( messageInfo.view ) { if ( typeof messageInfo.callback == 'string' ) { message.type = 'callback'; message.callback = messageInfo.callback; } message.applicationId = messageInfo.applicationId; message.viewId = messageInfo.viewId; messageInfo.view.postMessage( JSON.stringify( message ), '*' ); } } }; GetClass = function( source, root ) { var start = 0; var end = source.indexOf( '.' ); if ( end < 0 ) end = source.length; var klass = window[ source.substring( start, end ) ]; if ( typeof klass == 'undefined' ) return null; while( end < source.length ) { start = end + 1; end = source.indexOf( '.', start ), source.length; if ( end < 0 ) end = source.length; klass = klass[ source.substring( start, end ) ]; if ( typeof klass == 'undefined' ) return null; }; return klass; }; // Returns the documentation about this function... Friend.getFunctionDocumentation = function( functionPath, options, callback, extra ) { }; Friend.addToAPI( 'Friend.getAPI', [ 'applicationId', 'options', 'callback', 'extra' ], { tags: '#callback ' } ); Friend.addToAPI( 'Friend.getFunctionDocumenation', [ 'functionPath', 'options', 'callback', 'extra' ], { tags: '#direct #callback ' } ); /* json2.js 2013-05-26 Public Domain. NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. See http://www.JSON.org/js.html This code should be minified before deployment. See http://javascript.crockford.com/jsmin.html USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO NOT CONTROL. This file creates a global JSON object containing two methods: stringify and parse. JSON.stringify(value, replacer, space) value any JavaScript value, usually an object or array. replacer an optional parameter that determines how object values are stringified for objects. It can be a function or an array of strings. space an optional parameter that specifies the indentation of nested structures. If it is omitted, the text will be packed without extra whitespace. If it is a number, it will specify the number of spaces to indent at each level. If it is a string (such as '\t' or ' '), it contains the characters used to indent at each level. This method produces a JSON text from a JavaScript value. When an object value is found, if the object contains a toJSON method, its toJSON method will be called and the result will be stringified. A toJSON method does not serialize: it returns the value represented by the name/value pair that should be serialized, or undefined if nothing should be serialized. The toJSON method will be passed the key associated with the value, and this will be bound to the value For example, this would serialize Dates as ISO strings. Date.prototype.toJSON = function (key) { function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z'; }; You can provide an optional replacer method. It will be passed the key and value of each member, with this bound to the containing object. The value that is returned from your method will be serialized. If your method returns undefined, then the member will be excluded from the serialization. If the replacer parameter is an array of strings, then it will be used to select the members to be serialized. It filters the results such that only members with keys listed in the replacer array are stringified. Values that do not have JSON representations, such as undefined or functions, will not be serialized. Such values in objects will be dropped; in arrays they will be replaced with null. You can use a replacer function to replace those with JSON values. JSON.stringify(undefined) returns undefined. The optional space parameter produces a stringification of the value that is filled with line breaks and indentation to make it easier to read. If the space parameter is a non-empty string, then that string will be used for indentation. If the space parameter is a number, then the indentation will be that many spaces. Example: text = JSON.stringify(['e', {pluribus: 'unum'}]); // text is '["e",{"pluribus":"unum"}]' text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' text = JSON.stringify([new Date()], function (key, value) { return this[key] instanceof Date ? 'Date(' + this[key] + ')' : value; }); // text is '["Date(---current time---)"]' JSON.parse(text, reviver) This method parses a JSON text to produce an object or array. It can throw a SyntaxError exception. The optional reviver parameter is a function that can filter and transform the results. It receives each of the keys and values, and its return value is used instead of the original value. If it returns what it received, then the structure is not modified. If it returns undefined then the member is deleted. Example: // Parse the text. Values that look like ISO date strings will // be converted to Date objects. myData = JSON.parse(text, function (key, value) { var a; if (typeof value === 'string') { a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); if (a) { return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])); } } return value; }); myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { var d; if (typeof value === 'string' && value.slice(0, 5) === 'Date(' && value.slice(-1) === ')') { d = new Date(value.slice(5, -1)); if (d) { return d; } } return value; }); This is a reference implementation. You are free to copy, modify, or redistribute. */ /*jslint evil: true, regexp: true */ /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length, parse, prototype, push, replace, slice, stringify, test, toJSON, toString, valueOf */ // Create a JSON object only if one does not already exist. We create the // methods in a closure to avoid creating global variables. if (typeof JSON !== 'object') { JSON = {}; } (function () { 'use strict'; function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } if (typeof Date.prototype.toJSON !== 'function') { Date.prototype.toJSON = function () { return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z' : null; }; String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function () { return this.valueOf(); }; } var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\' }, rep; function quote(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } function str(key, holder) { // Produce a string from holder[key]. var i, // The loop counter. k, // The member key. v, // The member value. length, mind = gap, partial, value = holder[key]; // If the value has a toJSON method, call it to obtain a replacement value. if (value && typeof value === 'object' && typeof value.toJSON === 'function') { value = value.toJSON(key); } // If we were called with a replacer function, then call the replacer to // obtain a replacement value. if (typeof rep === 'function') { value = rep.call(holder, key, value); } // What happens next depends on the value's type. switch (typeof value) { case 'string': return quote(value); case 'number': // JSON numbers must be finite. Encode non-finite numbers as null. return isFinite(value) ? String(value) : 'null'; case 'boolean': case 'null': // If the value is a boolean or null, convert it to a string. Note: // typeof null does not produce 'null'. The case is included here in // the remote chance that this gets fixed someday. return String(value); // If the type is 'object', we might be dealing with an object or an array or // null. case 'object': // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. if (!value) { return 'null'; } // Make an array to hold the partial results of stringifying this object value. gap += indent; partial = []; // Is the value an array? if (Object.prototype.toString.apply(value) === '[object Array]') { // The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null'; } // Join all of the elements together, separated with commas, and wrap them in // brackets. v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']'; gap = mind; return v; } // If the replacer is an array, use it to select the members to be stringified. if (rep && typeof rep === 'object') { length = rep.length; for (i = 0; i < length; i += 1) { if (typeof rep[i] === 'string') { k = rep[i]; v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } else { // Otherwise, iterate through all of the keys in the object. for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } // Join all of the member texts together, separated with commas, // and wrap them in braces. v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}'; gap = mind; return v; } } // If the JSON object does not yet have a stringify method, give it one. if (typeof JSON.stringify !== 'function') { JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional // space parameter, and returns a JSON text. The replacer can be a function // that can replace values, or an array of strings that will select the keys. // A default replacer method can be provided. Use of the space parameter can // produce text that is more easily readable. var i; gap = ''; indent = ''; // If the space parameter is a number, make an indent string containing that // many spaces. if (typeof space === 'number') { for (i = 0; i < space; i += 1) { indent += ' '; } // If the space parameter is a string, it will be used as the indent string. } else if (typeof space === 'string') { indent = space; } // If there is a replacer, it must be a function or an array. // Otherwise, throw an error. rep = replacer; if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { throw new Error('JSON.stringify'); } // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. return str('', {'': value}); }; } // If the JSON object does not yet have a parse method, give it one. if (typeof JSON.parse !== 'function') { JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns // a JavaScript value if the text is a valid JSON text. var j; function walk(holder, key) { // The walk method is used to recursively walk the resulting structure so // that modifications can be made. var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } } } } return reviver.call(holder, key, value); } // Parsing happens in four stages. In the first stage, we replace certain // Unicode characters with escape sequences. JavaScript handles many characters // incorrectly, either silently deleting them, or treating them as line endings. text = String(text); cx.lastIndex = 0; if (cx.test(text)) { text = text.replace(cx, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } // In the second stage, we run the text against regular expressions that look // for non-JSON patterns. We are especially concerned with '()' and 'new' // because they can cause invocation, and '=' because it can cause mutation. // But just to be safe, we want to reject all unexpected forms. // We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. if (/^[\],:{}\s]*$/ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { // In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. j = eval('(' + text + ')'); // In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. return typeof reviver === 'function' ? walk({'': j}, '') : j; } // If the text is not JSON parseable, then a SyntaxError is thrown. throw new SyntaxError('JSON.parse'); }; } }()); /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ // We need friend! Friend = window.Friend || {}; // Get elements or an element by id ge = function( el ) { switch( typeof ( el ) ) { case 'string': if( el.substr ( 0, 1 ) == '#' ) return document.getElementById( el.substr ( 1, el.length - 1 ) ); else if( el.substr ( 0, 1 ) == '.' ) { var elements = document.getElementsByTagName( '*' ); var out = []; var cl = el.split( '.' ).join( '' ); for( var a = 0; a < elements.length; a++ ) { if( !elements[a].className.split ) continue; var classes = elements[a].className.split( ' ' ); for( var b = 0; b < classes.length; b++ ) { if( classes[b] == cl ) { out.push( elements[a] ); break; } } } return out; } return document.getElementById( el ); case 'array': var r = []; for( var a in el ) { var t = document.getElementById( el[a] ); if( t ) r.push( t ); } return r; case 'object': if( el.nodeName ) return el; return false; default: return false; } } // Deep clone an element function deepClone( ele ) { var obj = ele.cloneNode(); // Get objects and functions for( var a in ele ) { switch( a ) { case 'onclick': case 'command': case 'name': case 'items': case 'innerHMTL': case 'touchend': case 'onmouseup': case 'onmousedown': case 'ontouchstart': case 'ontouchend': case 'disabled': obj[a] = ele[a]; break; } } // Get attributes var supported = [ 'name', 'disabled', 'divider' ]; for( var a = 0; a < supported.length; a++ ) { if( ele.getAttribute ) { var v = ele.getAttribute( supported[ a ] ); if( v ) obj.setAttribute( supported[ a ], v ); } } // Get childnodes recursively if( ele.childNodes ) { for( var a = 0; a < ele.childNodes.length; a++ ) { var d = deepClone( ele.childNodes[ a ] ); obj.appendChild( d ); } } // Return clone return obj; } // var i18n_translations = new Array (); var SEPARATOR = ''; // Get an element by id function Ge ( ele ) { var e = document.getElementById ( ele ); if ( e ) return e; return false; } // Get elements by name function GeByName ( nm, el ) { var out = new Array (); if ( !el ) el = document; var eles = el.getElementsByTagName ( '*' ); for ( var a = 0; a < eles.length; a++ ) if ( eles[a].name == nm ) out.push ( eles[a] ); return out.length == 1 ? out[0] : out; } function GetElementTop ( ele ) { var top = 0; do { var t = parseInt ( ele.offsetTop ); if ( !isNaN ( t ) && t >= 0 ) top += t; ele = ele.offsetParent; } while ( ele && ele != document.body ) return top; } function GetElementLeft ( ele ) { var left = 0; do { var t = parseInt ( ele.offsetLeft ); if ( !isNaN ( t ) && t >= 0 ) left += t; ele = ele.offsetParent; } while ( ele && ele != document.body ) return left; } // See if an element has a class function HasClass( ele, className ) { var cls = ele.className ? ele.className.split( ' ' ) : false; if ( !cls ) return false; for ( var a = 0; a < cls.length; a++ ) if ( cls[a] == className ) return true; return false; } // Get elements by class name function GeByClass ( nm, el ) { var out = new Array (); var eles = document.getElementsByTagName ( '*' ); for ( var a = 0; a < eles.length; a++ ) { if ( eles[a].className == nm ) { var belongstoel = false; if ( el ) { var e = eles[a]; while ( e != document.body ) { if ( e == el ) { belongstoel = true; break; } e = e.parentNode; } } else belongstoel = true; if ( belongstoel ) out.push ( eles[a] ); } } return out.length == 1 ? out[0] : out; } var _is_touch_device; function isTouchDevice() { if( _is_touch_device === false || _is_touch_device === true ) return _is_touch_device; try { document.createEvent( 'TouchEvent' ); window.isTablet = true; _is_touch_device = true; return true; } catch ( e ) { _is_touch_device = false; return false; } } isTouchDevice(); // Generate a unique id function UniqueId () { var el; do { el = 'element_' + Math.floor ( (Math.random() * 1000) + (Math.random() * 1000) ); } while ( Ge ( el ) ); return el; } function UniqueHash( str ) { if( !str ) str = Math.random() + '' + Math.random() + '' + Math.random(); else str = str + ''; if( window.SHA256 ) { return SHA256( str ); } return str; } // set a cookie function SetCookie( key, value, expiry ) { try { var t = new Date (); if ( !expiry ) expiry = 1; expiry = new Date( t.getTime() + ( expiry*1000*60*60*24 ) ); document.cookie = key + '=' + escape ( value ) + ';expires='+expiry.toGMTString(); return; } catch( e ) { } return false; } function DelCookie ( key ) { document.cookie = key + '=;'; } // get a cookie function GetCookie( key ) { if ( !key ) return false; try { var c = document.cookie.split ( ';' ); for ( var a = 0; a < c.length; a++ ) { c[a] = c[a].split ( /^\s+|\s+$/g ).join ( '' ); // rm whitespace var v = c[a].split ( '=' ); if ( v[0] == key ) { return unescape ( v[1] ); } } return false; } catch( e ) { } return false; } // Set opacity on an element function SetOpacity( element, opacity ) { if ( !element ) return; if ( navigator.userAgent.indexOf ( 'MSIE' ) >= 0 && navigator.userAgent.indexOf ( 'MSIE 9' ) < 0 && navigator.userAgent.indexOf ( 'MSIE 10' ) < 0 ) { if ( opacity == 1 ) element.style.filter = null; else element.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(Opacity='+Math.round(opacity*100)+')'; } else element.style.opacity = opacity; } // Get data through an ajax query function GetByAjax( url, execfunc ) { var client = new XMLHttpRequest (); client.loadfunc = execfunc ? execfunc : false; client.onreadystatechange = function () { if ( this.readyState == 4 ) { if ( this.loadfunc != false ) this.loadfunc (); } } client.open ( 'POST', url ); client.send (); } function hideKeyboard() { if( !isMobile && !isTablet ) return; setTimeout( function() { var field = document.createElement( 'input' ); field.setAttribute( 'type', 'text' ); field.setAttribute( 'style', 'position: absolute; top: 0px; opacity: 0; -webkit-user-modify: read-write-plaintext-only; left: 0px;' ); document.body.appendChild( field ); field.onfocus = function() { setTimeout( function() { field.setAttribute('style', 'display:none;'); setTimeout( function() { document.body.removeChild( field ); document.body.focus(); }, 14 ); }, 200); }; field.focus(); setTimeout( function() { if( field.parentNode == document.body ) document.body.removeChild( field ); }, 500 ); }, 50 ); } // Make sure the objs are in array form function MakeArray ( objs ) { if ( objs.length >= 1 ) return objs; return [ objs ]; } /** * HTML Entity map */ Friend.HTMLEntities = { "'": "'", "<": "<", ">": ">", " ": " ", "¡": "¡", "¢": "¢", "£": "£", "¤": "¤", "¥": "¥", "¦": "¦", "§": "§", "¨": "¨", "©": "©", "ª": "ª", "«": "«", "¬": "¬", "®": "®", "¯": "¯", "°": "°", "±": "±", "²": "²", "³": "³", "´": "´", "µ": "µ", "¶": "¶", "·": "·", "¸": "¸", "¹": "¹", "º": "º", "»": "»", "¼": "¼", "½": "½", "¾": "¾", "¿": "¿", "À": "À", "Á": "Á", "Â": "Â", "Ã": "Ã", "Ä": "Ä", "Å": "Å", "Æ": "Æ", "Ç": "Ç", "È": "È", "É": "É", "Ê": "Ê", "Ë": "Ë", "Ì": "Ì", "Í": "Í", "Î": "Î", "Ï": "Ï", "Ð": "Ð", "Ñ": "Ñ", "Ò": "Ò", "Ó": "Ó", "Ô": "Ô", "Õ": "Õ", "Ö": "Ö", "×": "×", "Ø": "Ø", "Ù": "Ù", "Ú": "Ú", "Û": "Û", "Ü": "Ü", "Ý": "Ý", "Þ": "Þ", "ß": "ß", "à": "à", "á": "á", "â": "â", "ã": "ã", "ä": "ä", "å": "å", "æ": "æ", "ç": "ç", "è": "è", "é": "é", "ê": "ê", "ë": "ë", "ì": "ì", "í": "í", "î": "î", "ï": "ï", "ð": "ð", "ñ": "ñ", "ò": "ò", "ó": "ó", "ô": "ô", "õ": "õ", "ö": "ö", "÷": "÷", "ø": "ø", "ù": "ù", "ú": "ú", "û": "û", "ü": "ü", "ý": "ý", "þ": "þ", "ÿ": "ÿ", "Œ": "Œ", "œ": "œ", "Š": "Š", "š": "š", "Ÿ": "Ÿ", "ƒ": "ƒ", "ˆ": "ˆ", "˜": "˜", "Α": "Α", "Β": "Β", "Γ": "Γ", "Δ": "Δ", "Ε": "Ε", "Ζ": "Ζ", "Η": "Η", "Θ": "Θ", "Ι": "Ι", "Κ": "Κ", "Λ": "Λ", "Μ": "Μ", "Ν": "Ν", "Ξ": "Ξ", "Ο": "Ο", "Π": "Π", "Ρ": "Ρ", "Σ": "Σ", "Τ": "Τ", "Υ": "Υ", "Φ": "Φ", "Χ": "Χ", "Ψ": "Ψ", "Ω": "Ω", "α": "α", "β": "β", "γ": "γ", "δ": "δ", "ε": "ε", "ζ": "ζ", "η": "η", "θ": "θ", "ι": "ι", "κ": "κ", "λ": "λ", "μ": "μ", "ν": "ν", "ξ": "ξ", "ο": "ο", "π": "π", "ρ": "ρ", "ς": "ς", "σ": "σ", "τ": "τ", "υ": "υ", "φ": "φ", "χ": "χ", "ψ": "ψ", "ω": "ω", "ϑ": "ϑ", "ϒ": "&Upsih;", "ϖ": "ϖ", "–": "–", "—": "—", "‘": "‘", "’": "’", "‚": "‚", "“": "“", "”": "”", "„": "„", "†": "†", "‡": "‡", "•": "•", "…": "…", "‰": "‰", "′": "′", "″": "″", "‹": "‹", "›": "›", "‾": "‾", "⁄": "⁄", "€": "€", "ℑ": "ℑ", "℘": "℘", "ℜ": "ℜ", "™": "™", "ℵ": "ℵ", "←": "←", "↑": "↑", "→": "→", "↓": "↓", "↔": "↔", "↵": "↵", "⇐": "⇐", "⇑": "&UArr;", "⇒": "⇒", "⇓": "⇓", "⇔": "⇔", "∀": "∀", "∂": "∂", "∃": "∃", "∅": "∅", "∇": "∇", "∈": "∈", "∉": "∉", "∋": "∋", "∏": "∏", "∑": "∑", "−": "−", "∗": "∗", "√": "√", "∝": "∝", "∞": "∞", "∠": "∠", "∧": "∧", "∨": "∨", "∩": "∩", "∪": "∪", "∫": "∫", "∴": "∴", "∼": "∼", "≅": "≅", "≈": "≈", "≠": "≠", "≡": "≡", "≤": "≤", "≥": "≥", "⊂": "⊂", "⊃": "⊃", "⊄": "⊄", "⊆": "⊆", "⊇": "⊇", "⊕": "⊕", "⊗": "⊗", "⊥": "⊥", "⋅": "⋅", "⌈": "⌈", "⌉": "⌉", "⌊": "⌊", "⌋": "⌋", "⟨": "⟨", "⟩": "⟩", "◊": "◊", "♠": "♠", "♣": "♣", "♥": "♥", "♦": "♦", "&": "&" }; /** * Encodes string with HTML entities * @param string string to be encoded */ function EntityEncode( string ) { let output = []; for( let a = string.length - 1; a >= 0; a-- ) { let k = string[a]; for( let b in Friend.HTMLEntities ) { if( b == string[a] ) { k = Friend.HTMLEntities[b]; break; } } output.push( k ); } return output.join( '' ); } /** * Decodes string with HTML entities * @param string string to be decoded */ function EntityDecode( string ) { for( let b in Friend.HTMLEntities ) { string = string.split( Friend.HTMLEntities[b] ).join( b ); } return string; } // Get a translated string function i18n( string ) { if( i18n_translations[string] ) return i18n_translations[string]; if( typeof( translations ) != 'undefined' ) if ( translations[string] ) return translations[string]; return string; } // Add translations by path function i18nAddPath( path, callback ) { var j = new cAjax(); j.open( 'get', path, true ); j.onload = function() { var s = this.responseText().split( "\n" ); for( var a = 0; a < s.length; a++ ) { var p = s[a].split( ":" ); if( Trim( p[0] ).length && Trim( p[1] ).length ) { i18n_translations[Trim( p[0] )] = Trim( p[1] ); } } if( typeof callback == 'function' ) callback(); } j.send(); } // Add translations from string function i18nAddTranslations( string ) { var s = string.split( "\n" ); for( var a = 0; a < s.length; a++ ) { var p = s[a].split( ":" ); if( Trim( p[0] ).length && Trim( p[1] ).length ) { i18n_translations[Trim( p[0] )] = Trim( p[1] ); } } } // Search and execute replacements in string (Note from FL: I'll certainly remove this later, redundant) function i18nReplaceInString( str ) { var pos = 0; while ( ( pos = str.indexOf( "{i18n_", pos ) ) >= 0 ) { var pos2 = str.indexOf( "}", pos ); if ( pos2 >=0 ) { var key = str.substring( pos + 1, pos2 - pos - 1 ); var r = i18n( key ); if ( r != key ) { str = str.substring(0, pos) + r + str.substring(pos2 + 1); } pos = pos2 + 1; } else { break; } } return str; } // Execute replacements (Note from FL: I'll certainly remove this later) function i18nReplace( data, replacements ) { var str = data ? data : this.data; if( !str ) return ''; // Array if( replacements.length && typeof( replacements[ 0 ] ) != 'undefined' ) { for( var a = 0; a < replacements.length; a++ ) { str = str.split( '{' + replacements[ a ] + '}' ).join( i18n( replacements[ a ] ) ); } } // Object? else { for( var a in replacements ) { str = str.split( '{' + a + '}' ).join( replacements[a] ); } } return str; } function i18nClearLocale() { i18n_translations = []; } // Alter Div Contents function AlterDivContents ( element, string, spd ) { if ( !spd ) spd = 10; else if ( spd > 100 ) spd = 100; // Make sure element has id if ( !element.id ) { var base = 'ele'; var sugg_id = ''; do { sugg_id = base + '_' + Math.floor ( Math.random() * 999 ); } while ( Ge ( base ) ); element.id = sigg_id; } // Reset vars element._phase = element.innerHTML.length > 0 ? 0 : 100; element._data = string; element._int = 0; element._speed = spd; element._dataShown = false; // The actual fade code element._brain = function () { if ( this._phase < 200 ) { if ( this._phase <= 100 ) SetOpacity ( this, ( 100 - this._phase ) / 100 ); else { if ( !this._dataShown ) { this._dataShown = true; this.innerHTML = this._data; } SetOpacity ( this, ( this._phase - 100 ) / 100 ); } this._phase += this._speed; } else { SetOpacity ( this, 1 ); clearInterval ( this._int ); } } // Initialize fade element._int = setInterval ( 'Ge(\''+element.id+'\')._brain()', 25 ); } function StrPad ( num, len, ch ) { var number = num + ""; var out = ''; for ( var a = 0; a < len; a++ ) { if ( len - a > number.length ) out += ch; else out += number.substr ( a - len, 1 ); } return out; } function ShowDialog ( width, url, func, endfunc ) { var d, bg; if ( !( d = Ge ( 'Dialog' ) ) ) { bg = document.createElement ( 'div' ); bg.id = 'DialogBackground'; bg.onmousedown = function () { return false; } Ge ( 'Empty' ).appendChild ( bg ); d = document.createElement ( 'div' ); d.id = 'Dialog'; Ge ( 'Empty' ).appendChild ( d ); d.bg = bg; } else bg = d.bg; bg.style.zIndex = 100; bg.style.opacity = 0; bg.style.display = ''; bg.style.background = '#000000'; bg.style.position = 'fixed'; bg.style.top = '0px'; bg.style.left = '0px'; bg.style.width = '100%'; bg.style.height = '100%'; d.style.zIndex = 101; bg.style.filter = 'alpha(opacity=60)'; d.style.opacity = 0; d.style.display = ''; d.style.boxShadow = '0px 3px 15px rgba(0,0,0,0.4)'; d.style.webkitBoxShadow = '0px 3px 15px rgba(0,0,0,0.4)'; d.style.mozBoxShadow = '0px 3px 15px rgba(0,0,0,0.4)'; d.style.width = Math.floor ( width ) + 'px'; d.style.left = Math.floor ( GetWindowWidth() >> 1 - ( width >> 1 ) ) + 'px'; d.style.top = Math.floor ( GetWindowHeight() >> 1 - ( d.offsetHeight >> 1 ) ) + 'px'; d.style.visibility = 'hidden'; d.op = 0; d.func = null; d._time = 0; d.endfunc = endfunc; document.body.style.overflow = 'hidden'; d.fadeIn = function ( init ) { if ( init ) { if ( this.interval ) clearInterval ( this.interval ); this.interval = setInterval ( 'ge(\'' + this.id + '\').fadeIn()', 25 ); this._time = 0; this.style.visibility = 'visible'; } if ( this._time == 0 ) this._time = ( new Date () ).getTime (); this.op = (( new Date () ).getTime () - this._time) / 250.0; if ( this.op >= 1 ) { this.op = 1; clearInterval ( this.interval ); if ( this.endfunc ) this.endfunc (); } SetOpacity ( this, this.op ); if ( window.addEventListener ) SetOpacity ( this.bg, this.op >> 1 ); } d.fadeOut = function ( init ) { if ( init ) { if ( this.interval ) clearInterval ( this.interval ); this.interval = setInterval ( 'ge(\'' + this.id + '\').fadeOut()', 25 ); this._time = 0; } if ( this._time == 0 ) this._time = ( new Date () ).getTime (); this.op = (( new Date () ).getTime () - this._time) / 250.0; if ( this.op >= 1 ) { this.op = 1; clearInterval ( this.interval ); if ( this.func ) this.func(); } SetOpacity ( this, 1-this.op ); SetOpacity ( this.bg, (1-this.op) >> 1 ); } GetByAjax ( url, function () { d.innerHTML = '
' + this.responseText + '
'; d.style.top = Math.floor ( GetWindowHeight() >> 1 - ( d.offsetHeight >> 1 ) ) + 'px'; d.fadeIn ( true ); } ); } function HideDialog ( ) { var d = ge ( 'Dialog' ); if ( d ) { d.func = function () { this.bg.style.display = 'none'; this.style.display = 'none'; this.parentNode.removeChild ( this ); } ge(d.id).fadeOut(1); document.body.style.overflow = 'auto'; } } var _GuiOpenPopups = new Array (); function ShowPopup ( width, url, func ) { var j = new cAjax (); j.open ( 'post', url, true ); j.w = width; j.onload = function () { var r = this.responseText ().split ( '' ); if ( r[0] == 'ok' ) { var p = document.createElement ( 'div' ); p.className = 'GuiPopup'; p.innerHTML = '
' + r[1] + '
'; document.body.appendChild ( p ); p.firstChild.firstChild.style.left = 0 - ( this.w >> 1 ) + 'px'; p.firstChild.firstChild.style.top = -150 + 'px'; _GuiOpenPopups.push ( p ); } } j.send (); } function RefreshPopup ( width, url, func ) { if ( !_GuiOpenPopups.length ) { return ShowPopup ( width, url, func ); } else { var j = new cAjax (); j.open ( 'post', url, true ); j.w = width; j.onload = function () { var r = this.responseText ().split ( '' ); if ( r[0] == 'ok' ) { var p = _GuiOpenPopups[ _GuiOpenPopups.length - 1 ]; p.innerHTML = '
' + r[1] + '
'; p.firstChild.firstChild.style.left = 0 - ( this.w >> 1 ) + 'px'; p.firstChild.firstChild.style.top = -150 + 'px'; } } j.send (); } } function HidePopup () { var o = new Array (); var l = _GuiOpenPopups.length; for ( var a = 0; a < l - 1; a++ ) { o.push ( _GuiOpenPopups ); } _GuiOpenPopups[l-1].parentNode.removeChild ( _GuiOpenPopups[l-1] ); _GuiOpenPopups = o; } /* from inkassohjelpen function numberExtract ( num ) { var innum = num; num += ""; // We have both , and . if ( num.indexOf ( ',' ) >= 0 && num.indexOf ( '.' ) >= 0 ) { // dot is comma if ( num.indexOf ( '.' ) > num.indexOf ( ',' ) ) { num = num.split ( ',' ).join ( '' ); num = num.split ( '.' ); } // comma is comma else { num = num.split ( '.' ).join ( '' ); num = num.split ( ',' ); } } // Only split on comma else if ( num.indexOf ( ',' ) >= 0 ) { num = num.split ( ',' ); } // Only split on dot else if ( num.indexOf ( '.' ) >= 0 ) { num = num.split ( '.' ); } // No decimal else num = [ num, '0' ]; // If we have no number, make it if ( !num[0] ) num[0] = '0'; // Convert to number num = parseFloat ( num[0] + '.' + num[1] ); return num; }*/ // Add intrest to a value in a month period function AddIntrest ( value, intrest, datefrom, dateto ) { var df = datefrom.split ( '-' ); var dt = dateto.split ( '-' ); var dn = df[2]; var mn = df[1]; var yn = df[0]; while ( mn++ != dt[1] && yn != dt[2] ) { if ( mn > 12 ){ mn = 1; yn++ } // Add cumulnative intrest value += value * intrest; } return value; } // For use with autocomplete fields function ActivateAutocomplete ( ele, completeurl ) { ele.onkeyup = function () { getByAjax ( completeurl, function () { var d = document.createElement ( 'div' ); d.className = 'AutocompletePopup'; ele.parentNode.appendChild ( d ); d.style.position = 'absolute'; ele.style.top = (ele.offsetTop + 32)+'px'; ele.style.left = (ele.offsetLeft)+'px'; ele.style.width = (ele.offsetWidth)+'px'; ele.innerHTML = this.responseText; d.onclick = function () { this.parentNode.removeChild ( this ); } } ); } } // Include a javascript source and eval it globally function Include ( url, callback ) { var ele = document.createElement ( "script" ); ele.type = "text/javascript"; ele.src = url; if( callback ) { ele.onload = function( e ) { callback( e ); } } document.body.appendChild ( ele ); } // Add several events on each event type var _events = new Array (); function TriggerEvent( eventName, object ) { var event = false; if ( typeof document.createEvent == 'function' ) { event = document.createEvent ( 'HTMLEvents' ); event.initEvent ( eventName, true, true ); } else { event = document.createEventObject (); event.eventType = eventName; } event.eventName = eventName; if ( typeof document.createEvent == 'function' && event != false) { object.dispatchEvent(event); } else { object.fireEvent( 'on' + event.eventType, event ); } } // Add an event with type 'onmousemove', function, then dom element (or false) function AddEvent( type, func, ele ) { if ( !_events[type] ) _events[type] = []; var obj = ele ? ele : window; _events[type].push( { func: func, ele: obj } ); if( obj.attachEvent ) obj.attachEvent( type, func, false ); else obj.addEventListener( type.substr ( 2, type.length - 2 ), func, false ); return func; } // Removes an event from the event pool function DelEvent( func ) { var success = false; for( a in _events ) { var out = []; for( b in _events[a] ) { if( func != _events[a][b].func ) { out.push ( _events[a][b] ); } else { if( _events[a][b].ele.detachEvent ) _events[a][b].ele.detachEvent( a, func ); else _events[a][b].ele.removeEventListener ( a.substr ( 2, a.length - 2 ), func ); success = true; } } _events[a] = out; } return success; } // Selection an option in a select element by value function SelectOption( selectElement, value ) { var opts = selectElement.getElementsByTagName( 'option' ); if( isNaN( value ) ) { for( var a = 0; a < opts.length; a++ ) { if( opts[a].value == value ) { opts[a].selected = 'selected'; } else opts[a].selected = ''; } } // Numeric else { for( var a = 0; a < opts.length; a++ ) { if( a == value ) { opts[a].selected = 'selected'; } else opts[a].selected = ''; } } } function Trim( string, direction ) { if( !direction ) direction = false; string = ( string + "" ); var out = ''; var start = true; if( !direction || direction == 'left' ) { for ( var a = 0; a < string.length; a++ ) { var letter = string.substr( a, 1 ); var wspace = letter == ' ' || letter == '\t' || letter == '\n' || letter == '\r'; if ( wspace && start ) continue; else start = false; if ( !start ) out += letter; } string = out; } else out = string; if( !direction || direction == 'right' ) { var end = true; string = out; out = ''; for ( var a = string.length-1; a >= 0; a-- ) { var letter = string.substr ( a, 1 ); var wspace = letter == ' ' || letter == '\t' || letter == '\n' || letter == '\r'; if ( wspace && end ) continue; else end = false; if ( !end ) out = letter + out; } string = out; } return string; } // Extract a number from the string function NumberExtract ( string ) { string = Trim ( string ).split ( ' ' ).join ( '' ); var dot = string.indexOf ( '.' ); var com = string.indexOf ( ',' ); if ( dot >= 0 && com >= 0 ) { if ( com > dot ) { string = string.split ( '.' ).join ( '' ); string = string.split ( ',' ).join ( '.' ); } else { string = string.split ( ',' ).join ( '' ); } } else if ( com >= 0 ) { string = string.split ( ',' ).join ( '.' ); } return parseFloat ( string ); } // Use date string dt "YYYY-MM-DD hh:mm:ss", return human readable string function HumanDateString( dt ) { function tPad( num ) { if( ( num + '' ).length < 2 ) return '0' + ( num + '' ); return num; } let months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Des' ]; let now = new Date(); let then = new Date( dt ); let nyear = now.getFullYear(); let nmonth = now.getMonth() + 1; let ndate = now.getDate(); let tyear = then.getFullYear(); let tmonth = then.getMonth() + 1; let tdate = then.getDate(); // Last year we do not care about time if( nyear > tyear ) { return tdate + '. ' + months[ then.getMonth() ] + ' ' + tyear; } // Check span of time let secs = 0; let nowTime = now.getTime(); let thenTime = then.getTime(); // In the past if( nowTime > thenTime ) { secs = Math.floor( ( nowTime - thenTime ) / 1000 ); if( secs < 60 ) { return 'Just now.'; return secs + ' seconds ago.'; } if( secs / 60 < 60 ) { let mins = Math.floor( secs / 60 ); return mins + ' ' + ( mins == 1 ? 'minute' : 'minutes' ) + ' ago.'; } if( secs / 60 / 60 < 24 ) { let hours = Math.floor( secs / 60 / 60 ); return hours + ' ' + ( hours == 1 ? 'hour' : 'hours' ) + ' ago.'; } if( tyear == ( new Date() ).getFullYear() ) return tPad( tdate ) + '. ' + tPad( months[ then.getMonth() ] ); return tPad( tdate ) + '. ' + tPad( months[ then.getMonth() ] ) + ' ' + tyear; } else { secs = Math.floor( ( thenTime - nowTime ) / 1000 ); if( secs < 60 ) { return 'In ' + secs + ' seconds.'; } if( secs / 60 < 60 ) { let mins = Math.floor( secs / 60 ); return 'In ' + mins + ' ' + ( mins == 1 ? 'minute' : 'minutes' ) + '.'; } if( secs / 60 / 60 < 24 ) { let hours = Math.floor( secs / 60 / 60 ); return 'In ' + hours + ' ' + ( hours == 1 ? 'hour' : 'hours' ) + '.'; } return tPad( tdate ) + '. ' + tPad( months[ then.getMonth() ] ) + ' ' + tyear; } } // Easy tweener function that lets you var __m_seed = 1; function mTween ( obj, time, func, endfunc ) { // Setup if ( !obj ) { if ( !this ) return; var p = ( new Date () ).getTime () - this.ph; this.val = p / this.t; if ( this.val > 1 ) this.val = 1; if ( this.func ) this.func (); if ( this.val == 1 ) { clearInterval ( this.intr ); this.intr = false; if ( this.endfunc ) { if ( this.endfunc == 'destroy' ) this.parentNode.removeChild ( this ); else this.endfunc(); } } return; } if ( !obj.id ) obj.id = 'mtween_' + __m_seed++; if ( !obj.intr ) { obj.ph = ( new Date () ).getTime (); obj.val = 0; obj.t = time; obj.func = func; obj.endfunc = endfunc; obj.mTween = mTween; obj.intr = setInterval ( 'ge(\'' + obj.id + '\').mTween()', 15 ); } } function NumberFormat ( string, decimals ) { if ( !decimals ) decimals = 2; var decimalsliteral = ''; for ( var a = 0; a < decimals; a++ ) decimalsliteral += '0'; string = NumberExtract ( string ); var num = ( string + "" ).split ( '.' ); var dec = num.length > 1 ? num[1] : decimalsliteral; while ( dec.length < decimals ) dec += "0"; var num = num[0]; var out = ''; var i = 0; for ( var a = num.length-1; a >= 0; a-- ) { out = num.substr( a, 1 ) + out; if ( i++ == 2 ) out = '.' + out; } string = out + "," + dec; if ( string.substr ( 0, 1 ) == '.' ) string = string.substr ( 1, string.length - 1 ); else if ( string.substr ( 0, 2 ) == '-.' ) string = '-' + string.substr ( 2, string.length - 2 ); return string; } function GetElementTop ( ele ) { var t = 0; do { t += ele.offsetTop; ele = ele.offsetParent; } while( ele && ele.offsetParent != document.body ); return t; } function GetElementLeft ( ele ) { var l = 0; do { l += ele.offsetLeft; ele = ele.offsetParent; } while ( ele && ele.offsetParent != document.body ); return l; } function getTotalWidthOfObject(object) { if(object == null || object.length == 0) { return 0; } var value = object.width(); value += parseInt(object.css("padding-left"), 10) + parseInt(object.css("padding-right"), 10); //Total Padding Width value += parseInt(object.css("margin-left"), 10) + parseInt(object.css("margin-right"), 10); //Total Margin Width value += parseInt(object.css("borderLeftWidth"), 10) + parseInt(object.css("borderRightWidth"), 10); //Total Border Width return value; } GetElementWidthTotal = getTotalWidthOfObject; // Correct alias function GetElementWidth( ele ) { if( ele == null || ele.length == 0 ) return 0; var css = window.getComputedStyle( ele, null ); var value = ele.offsetWidth; if( css.boxSizing != 'border-box' ) { value += parseInt( css.paddingLeft, 10) + parseInt( css.paddingRight, 10); value += parseInt( css.borderLeftWidth, 10) + parseInt( css.borderRightWidth, 10); } value += parseInt( css.marginLeft, 10) + parseInt( css.marginRight, 10); return value; } function GetElementHeight( ele ) { if( ele == null || ele.length == 0 ) return 0; var css = window.getComputedStyle( ele, null ); var value = ele.offsetHeight; if( css.boxSizing != 'border-box' ) { value += parseInt( css.paddingTop, 10) + parseInt( css.paddingBottom, 10); value += parseInt( css.borderTopWidth, 10) + parseInt( css.borderBottomWidth, 10); } value += parseInt( css.marginTop, 10) + parseInt( css.marginBottom, 10); return value; } function GetWindowWidth() { if ( typeof ( window.innerWidth ) == 'number' ) { return window.innerWidth; } else if ( document.documentElement && document.documentElement.clientWidth ) { return document.documentElement.clientWidth; } else if ( document.body && document.body.clientWidth ) { return document.body.clientWidth; } return false; } function GetWindowHeight() { if ( typeof ( window.innerHeight ) == 'number' ) { return window.innerHeight; } else if ( document.documentElement && document.documentElement.clientHeight ) { return document.documentElement.clientHeight; } else if ( document.body && document.body.clientHeight ) { return document.body.clientHeight; } return false; } function GetScrollPosition () { // Explorer if ( window.attachEvent ) { if ( document.body && document.body.scrollTop ) return { x: document.body.scrollLeft, y: document.body.scrollTop }; return { x: document.documentElement.scrollLeft, y: document.documentElement.scrollTop }; } else { if ( document.scrollTop ) return { x: document.scrollLeft, y: document.scrollTop }; return { x: window.pageXOffset, y: window.pageYOffset }; } } function FixServerUrl( url ) { var ur = url.match( /http[s]{0,1}\:\/\/.*?\/(.*)/i ); if( ur ) { var l = document.location.href.match( /(http[s]{0,1}\:\/\/)(.*?\/).*/i ); if( l ) { return l[1] + l[2]+ ur[1]; } } return url; } function GetUrlVar ( vari ) { var line = document.location.href.split ( '#' )[0].split ( '?' ); if ( line.length > 1 ) { line = line[1]; line = line.split ( '&' ); for( var a = 0; a < line.length; a++ ) { var l = line[a].split( '=' ); if( l[0] && l[0] == vari ) return l[1]; } } return ''; } function ArrayToString ( arr ) { var ustr = new Array (); for ( var a in arr ) { ustr.push ( a + "\t" + arr[a].split ( "\n" ).join ( "" ).split ( "\t" ).join ( "" ) ); } return ustr.join ( "\n" ); } function StringToArray ( str ) { var a = str.split ( "\n" ); if ( a.length ) { for ( var c in a ) { a[c] = a[c].split ( "\t" ); if ( a[c].length ) a[c][1] = a[c][1].split ( "" ).join ( "\n" ).split ( "" ).join ( "\t" ); } } return a; } function ObjectToString ( arr ) { var ustr = new Array(); for( var a in arr ) { arr[a] = arr[a]+""; ustr.push( a + "\t" + arr[a].split ( "\n" ).join ( "" ).split ( "\t" ).join ( "" ) ); } return ustr.join( "\n" ); } function StringToObject ( str ) { if( !str || !str.split ) return false; var a = str.split ( "\n" ); var o = new Object (); if ( a.length ) { for ( var c in a ) { var s = a[c].split ( "\t" ); if ( s.length ) { o[s[0]] = s[1].split ( "" ).join ( "\n" ).split ( "" ).join ( "\t" ); } } } return o; } // Run scripts found in string function RunScripts( str, context ) { if( !str ) return; if ( !str.length ) return; var scripts; while ( scripts = str.match ( /\]*?\>([\w\W]*?)\<\/script\>/i ) ) { str = str.split ( scripts[0] ).join ( '' ); if( context ) { context.doEvaluate = function( scriptCode ) { context.eval( scriptCode ); } context.doEvaluate( scripts[ 1 ] ); } else { eval ( scripts[1] ); } } } // Extract scripts and add on dom! function ActivateScripts( str ) { if( !str ) return; if ( !str.length ) return; var scripts; var totalLoading = 0; var out = []; // Scripts to load var inl = []; // Inline scripts to run in sequence after load function runInline() { for( var a = 0; a < inl.length; a++ ) { RunScripts( '' ); } } // Grab all scripts while( scripts = str.match ( /\[\s]{0,}\<\/script\>/i ) ) { str = str.split ( scripts[0] ).join ( '' ); var s = document.createElement( 'script' ); s.setAttribute( 'src', scripts[1] ); out.push( s ); } while( scripts = str.match ( /\[\s]{0,}\<\/script\>/i ) ) { str = str.split ( scripts[0] ).join ( '' ); var s = document.createElement( 'script' ); s.setAttribute( 'src', scripts[1] ); out.push( s ); } // Inline scripts to run last while ( scripts = str.match ( /\]*?\>([\w\W]*?)\<\/script\>/i ) ) { inl.push( scripts[1] ); str = str.split ( scripts[0] ).join ( '' ); } // Support running application in an included script src if( window.applicationStarted == false && out.length > 0 ) { var o = out.length > 1 ? ( out.length - 1 ) : 0; totalLoading++; out[ o ].onload = function() { totalLoading--; if( !window.applicationStarted ) { if( Application.run ) { window.applicationStarted = true; Application.run(); if( Application.messageQueue ) { var mq = []; for( var a = 0; a < Application.messageQueue.length; a++ ) mq.push( Application.messageQueue[a] ); Application.messageQueue = false; for( var a = 0; a < mq.length; a++ ) { Application.receiveMessage( mq[a] ); console.log( 'Executing queued event' ); } } } } if( totalLoading == 0 ) { runInline(); } } document.body.appendChild( out[o] ); } // Forget about applicationStarted else if( out.length > 0 ) { // Append to dom tree for( var a = 0; a < out.length; a++ ) { out[a].onload = function() { if( --totalLoading == 0 ) { runInline(); } } totalLoading++; document.body.appendChild( out[a] ); } } } function ExecuteScript( str, scope = false ) { if( scope ) { scope.eval( str ); } eval( str ); } // Add a script function AddScript( scriptsrc, callback = false ) { var h = document.getElementsByTagName ( 'head' )[0]; var s = h.getElementsByTagName ( 'script' ); var found = false; for ( var a = 0; a < s.length; a++ ) { if ( s[a].src == scriptsrc ) { found = true; break; } } if ( !found ) { var sc = document.createElement ( 'script' ); sc.src = scriptsrc; h.appendChild ( sc ); if( callback ) { sc.onload = function() { callback( true ); } } return true; } if( callback ) callback( false ); return false; } // Fire event on element function dispatchEvent( ele, evt, spec ) { // Make sure we use the ownerDocument from the provided node to avoid cross-window problems var doc; if( ele.ownerDocument ) doc = ele.ownerDocument; else if( ele.nodeType == 9 ) doc = ele; else return; if( ele.dispatchEvent ) { var eventClass = ''; switch( evt ) { case 'click': case 'mousedown': case 'mouseup': eventClass = 'MouseEvents'; break; case 'focus': case 'change': case 'blur': case 'select': eventClass = 'HTMLEvents'; break; case 'change': eventClass = 'HashChangeEvent'; break; case 'keydown': case 'keypress': case 'keyup': case 'input': eventClass = 'KeyboardEvents'; break; case 'touchstart': case 'touchend': eventClass = 'TouchEvents'; break; case 'cut': case 'copy': case 'paste': console.log('ClipboardEvent dispatched here in engine.js',evt); eventClass = 'ClipboardEvent'; break; default: console.log( 'Illegal event class.' ); eventClass = 'Event'; return; } var event = false; try { event = doc.createEvent( eventClass ); var bubbles = evt == 'change' ? false : true; if( event) event.initEvent( evt, bubbles, true ); if( event) event.synthetic = true; // Add to event data if( event && spec ) { for( var g in spec ) event[g] = spec[g]; } if( event ) ele.dispatchEvent( event, true ); } catch(e) { console.log('could not create event of type ' + eventClass + ': ' + e); } } else if( ele.fireEvent ) { // IE-old school style var event = doc.createEventObject(); event.synthetic = true; ele.fireEvent( 'on' + evt, event ); } } function cancelBubble ( ev ) { if( !ev ) ev = window.event; if( !ev ) return false; if( ev.cancelBubble && typeof( ev.cancelBubble ) == 'function' ) ev.cancelBubble(); if( ev.stopPropagation ) ev.stopPropagation(); if( ev.preventDefault ) ev.preventDefault(); return false; } function TextareaToWYSIWYG( inp ) { if( inp && inp.nodeName == 'TEXTAREA' ) { if( inp.richTextArea ) inp.richTextArea.parentNode.removeChild( inp.richTextArea ); var d = document.createElement( 'div' ); d.className = 'TextareaRich FullWidth Padding BackgroundDefault ColorDefault BordersDefault'; d.setAttribute( 'contentEditable', 'true' ); d.innerHTML = inp.value == '' ? '

' : inp.value; inp.style.position = 'absolute'; inp.style.visibility = 'hidden'; inp.style.top = '-100000px'; if( inp.style.height ) d.style.height = inp.style.height; d.onkeydown = function( e ) { inp.value = this.innerHTML.split( '' ) ); } function GetLoadProgress () { return '
Loading...
'; } function jsonSafeObject ( o, depth ) { if( !depth ) depth = 0; if( typeof( o ) != 'object' && typeof( o ) != 'array' ) return o; var n = new Object (); for( var a in o ) { if ( typeof ( o[a] ) == 'object' ) { if ( depth > 1 ) { n[a] = null; } else { n[a] = jsonSafeObject ( o[a], depth+1 ); } } else { n[a] = o[a]; } } return n; } // Helpers function humanFilesize( bts ) { if ( bts > 1000000000000 ) { filesize = ( Math.round ( bts / 1000000000000 * 100 ) / 100 ) + 'tb'; } else if ( bts > 1000000000 ) { filesize = ( Math.round ( bts / 1000000000 * 100 ) / 100 ) + 'gb'; } else if ( bts > 1000000 ) { filesize = ( Math.round ( bts / 1000000 * 100 ) / 100 ) + 'mb'; } else if ( bts > 1000 ) { filesize = ( Math.round ( bts / 1000 * 100 ) / 100 ) + 'kb'; } else { filesize = bts + 'b'; } return filesize; } // Return an array sorted by column, high to low function sortArray( array, sortColumn, order ) { if( !array || !array.length ) return false; // Output var out = []; // Create sortable array for ( var a = 0; a < array.length; a++ ) { // make unique key var key = ''; if( typeof( sortColumn ) == 'object' ) { for( var ak in sortColumn ) key += array[a][sortColumn[ak]] + '_'; } else key = array[a][sortColumn] + '_'; key += a.toString(); out.push ( key.toLowerCase() ); } out.sort (); // Create final output var fin = []; for ( var a = 0; a < out.length; a++ ) { var keys = out[a].split( '_' ); fin.push( array[parseInt(keys[keys.length-1])] ); } // Descending order if( order == 'descending' || order == 'desc' ) { fin.reverse(); } return fin; } // Generates a number based on string function stringToNumber ( string ) { var num = 0; var len = string.length; for ( var a = 0; a < len; a++ ) { var val = string.charCodeAt ( a ) / (a+1); num += val; } return num; } // Clean a fileInfo structure function CleanFileinfo( fi ) { var o = {}; for( var a in fi ) { switch( a ) { case 'fileInfo': case 'domNode': case 'Door': break; default: o[a] = fi[a]; break; } } return o; } /* Base64 encode / decode */ /* Usage var str = "Hey"; return Base64.encode( str ); */ var Base64 = {_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(f>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/\r\n/g,"\n");var t="";for(var n=0;n127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t } } // because Base64 gets overwritten by the crypto library window.Base64alt = {_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64alt._utf8_encode(e);while(f>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(f>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64alt._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/\r\n/g,"\n");var t="";for(var n=0;n127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t } } /* Vertical tabs ------------------------------------------------------------ */ // // Create a vertical tab list // VertTabContainer = function( domElement ) { this.dom = domElement; this.dom.innerHTML = ''; this.dom.classList.add( 'VertTabContainer', 'ScrollArea' ); this.tabs = []; this.initialized = false; } // // tabObject = { // name: "name", // label: "Click me", // pageDiv: domElement // }; // VertTabContainer.prototype.addTab = function( tabObject ) { var d = document.createElement( 'div' ); this.tabs.push( d ); d.name = tabObject.name; d.className = 'VertTab'; d.innerHTML = '' + tabObject.label + ''; d.tabs = this.tabs; d.pageDiv = tabObject.pageDiv; d.pageDiv.className = 'VertTabPage'; d.onclick = function() { for( var a = 0; a < this.tabs.length; a++ ) { if( this.tabs[a] != this ) { this.tabs[a].className = 'VertTab'; this.tabs[a].pageDiv.className = 'VertPage'; } } this.className = 'VertTab Active'; this.pageDiv.className = 'VertPage Active'; } this.dom.appendChild( d ); if( !this.initialized ) this.initialize( d ); else this.initialize( this.initialized ); } // // just initialize // VertTabContainer.prototype.initialize = function( ele ) { this.initialized = ele ? ele : tabs[0]; if( ele ) ele.click(); else this.tabs[0].click(); } /* Sliders ------------------------------------------------------------------ */ function InitSliders( pdiv ) { if( !Friend.slidersInitialized ) { Friend.slidersInitialized = true; // Move func Friend.sliderMove = function( e ) { if( Friend.sliderCurrent ) { var el = Friend.sliderCurrent; // Mind the direction of the slider if( el.parentNode.direction == 'horizontal' ) { var left = ( e.clientX - el.posx ) - el.clickX; if( left < 0 ) left = 0; if( left + el.offsetWidth >= el.parentNode.offsetWidth - 2 ) left = el.parentNode.offsetWidth - el.offsetWidth - 2; el.style.left = left + 'px'; var scaleP = el.parentNode.scale; var scale = left / ( el.parentNode.offsetWidth - ( el.offsetWidth + 2 ) ) * ( scaleP.to - scaleP.from ) + scaleP.from; if( el.parentNode.onSlide ) el.parentNode.onSlide( { scale: scale } ); } else { var top = ( e.clientY - el.posy ) - el.clickY; if( top < 0 ) top = 0; if( top + el.offsetHeight >= el.parentNode.offsetHeight - 2 ) top = el.parentNode.offsetHeight - el.offsetHeight - 2; el.style.top = top + 'px'; var scaleP = el.parentNode.scale; var scale = top / ( el.parentNode.offsetHeight - ( el.offsetHeight + 2 ) ) * ( scaleP.to - scaleP.from ) + scaleP.from; if( el.parentNode.onSlide ) el.parentNode.onSlide( { scale: scale } ); } } } Friend.sliderMouseUp = function( e ) { Friend.sliderCurrent = false; } // Getister events window.addEventListener( 'mousemove', Friend.sliderMove, true ); window.addEventListener( 'mouseup', Friend.sliderMouseUp, true ); } if( typeof( pdiv ) == 'string' ) pdiv = ge( pdiv ); // Fetch all sliders var sliders = []; var divs = pdiv.getElementsByTagName( 'div' ); for( var a = 0; a < divs.length; a++ ) { if( divs[a].classList.contains( 'Slider' ) ) { sliders.push( divs[a] ); } } // Set up each slider that remains uninitialized! for( var a = 0; a < sliders.length; a++ ) { var slider = sliders[a]; if( slider.sliderInitialized ) continue; slider.sliderInitialized = true; slider.scale = { from: 0, to: 1 }; slider.position = 0; // Set the scale! slider.setScale = function( from, to ) { this.scale = { from: from, to: to }; } // Set a position slider.setPosition = function( pos ) { this.position = pos; var el = this.button; if( this.direction == 'horizontal' ) { var scaleP = el.parentNode.scale; var scale = ( pos - scaleP.from ) / ( scaleP.to - scaleP.from ); el.style.left = scale * ( el.parentNode.offsetWidth - el.offsetWidth - 2 ) + 'px'; if( el.parentNode.onSlide ) el.parentNode.onSlide( { scale: scale } ); } else { var scaleP = el.parentNode.scale; var scale = ( pos - scaleP.from ) / ( scaleP.to - scaleP.from ); el.style.top = scale * ( el.parentNode.offsetHeight - el.offsetHeight - 2 ) + 'px'; if( el.parentNode.onSlide ) el.parentNode.onSlide( { scale: scale } ); } } // Register mouse down and touch start! // TODO: Complete touch for( var b = 0; b < slider.childNodes.length; b++ ) { if( slider.childNodes[b].className && slider.childNodes[b].classList.contains( 'SliderButton' ) ) { slider.childNodes[b].onmousedown = function( e ) { var el = this; el.parentNode.button = el; el.parentNode.direction = 'horizontal'; el.posx = GetElementLeft( el ); // Slider position el.posy = GetElementTop( el ); el.clickX = e.clientX - ( el.posx + el.offsetLeft ); // Mouse click offset el.clickY = e.clientY - ( el.posy + el.offsetTop ); Friend.sliderCurrent = this; } slider.childNodes[b].ontouchstart = slider.childNodes[b].onmousedown; } } } } /* Standard tabs ------------------------------------------------------------ */ // Initializes tab system on the subsequent divs one level under parent div Friend.horizontalTabs = {}; function InitTabs( pdiv, tabCallback ) { if( typeof( pdiv ) == 'string' ) pdiv = ge( pdiv ); // Save these Friend.horizontalTabs[ pdiv.id ] = pdiv; // Find window var wobj = pdiv; while( wobj ) { if( wobj.classList && wobj.classList.contains( 'Content' ) && wobj.windowObject ) { wobj = wobj.windowObject.content; break; } wobj = wobj.parentNode; } // Ah we are in an api! if( !wobj ) wobj = window; var divs = pdiv.getElementsByTagName( 'div' ); var tabs = []; var pages = []; var active = 0; var tabContainer = pdiv.getElementsByClassName( 'TabContainer' ); if( !tabContainer.length || tabContainer[0].parentNode != pdiv ) { tabContainer = false; } else tabContainer = tabContainer[0]; var hasContainer = tabContainer; var setPageState = true; for( var a = 0; a < divs.length; a++ ) { // Skip orphan tabs and out of bounds subelements if( ( divs[a].classList.contains( 'Tab' ) && hasContainer && divs[a].parentNode != tabContainer ) || ( !hasContainer && divs[a].parentNode != pdiv ) ) { continue; } if( divs[a].classList.contains( 'TabContainer' ) ) { hasContainer = divs[a]; tabContainer = divs[a]; continue; } if( divs[a].classList.contains( 'Tab' ) ) { tabs.push( divs[a] ); divs[a].pdiv = pdiv; divs[a].tabs = tabs; divs[a].pages = pages; divs[a].index = tabs.length - 1; divs[a].onclick = function() { // Already active? Just return if( this.classList.contains( 'TabActive' ) && this.pages[ this.index ].classList.contains( 'PageActive' ) ) return; // Assume it is ok to activate this tab var result = true; SetCookie ( 'Tabs' + this.pdiv.id, this.index ); this.classList.add( 'TabActive' ); var ind; for( var b = 0; b < this.tabs.length; b++ ) { if( this.tabs[b] != this ) { this.tabs[b].classList.remove( 'TabActive' ); } else ind = b; } if( tabCallback ) { var r = tabCallback( this, this.pages ); if( r === false || r === true ) result = r; } // Only continue if the tab callback has a positive result or doesn't exist if( result ) { for( var b = 0; b < this.pages.length; b++ ) { if( b != ind ) { this.pages[b].classList.remove( 'PageActive' ); } else { this.pages[b].classList.add( 'PageActive' ); if( navigator.userAgent.indexOf ( 'MSIE' ) > 0 ) { this.pages[b].style.display = 'none'; var idz = 1; if( !this.pages[b].id ) { var bs = 'page'; idz++; while ( ge ( bs ) ) bs = [ bs, idz ].join ( '' ); this.pages[b].id = bs; } var bid = this.pages[b].id; setTimeout ( 'ge(\'' + bid + '\').style.display = \'\'', 50 ); } } } } // Do magic with resize if( typeof ( AutoResizeWindow ) != 'undefined' ) { var pdiv = this.pdiv; while ( pdiv.className.indexOf ( ' View' ) < 0 && pdiv != document.body ) pdiv = pdiv.parentNode; if ( pdiv != document.body && pdiv.autoResize == true ) AutoResizeWindow ( pdiv ); } } if( GetCookie ( 'Tabs' + pdiv.id ) == divs[a].index ) { active = divs[a].index; } } else if( divs[a].classList.contains( 'Page' ) ) { divs[a].classList.remove( 'PageActive' ); divs[a].classList.add( 'Page' ); pages.push( divs[a] ); } } // Reorder the tabs if( !hasContainer ) { // Abort! if( !tabs.length ) { return; } var d = document.createElement( 'div' ); d.className = 'TabContainer'; tabs[0].parentNode.insertBefore( d, tabs[0] ); for( var a = 0; a < tabs.length; a++ ) { tabs[a].parentNode.removeChild( tabs[a] ); d.appendChild( tabs[a] ); } hasContainer = d; } if( hasContainer ) { // Scroll on mouse move hasContainer.addEventListener( 'mousemove', function( e ) { if( this.scrollWidth <= this.offsetWidth ) return; var rest = this.scrollWidth - this.offsetWidth; var position = ( e.clientX - GetElementLeft( this ) ); if( position > this.offsetWidth ) position = this.offsetWidth; else if( position < 0 ) position = 0; position /= this.offsetWidth; this.scrollLeft = Math.round( position * rest ); } ); // Allow touch slide hasContainer.addEventListener( 'touchstart', function( e ) { this.touchDownX = this.scrollLeft; this.touchX = e.touches[0].clientX; } ); hasContainer.addEventListener( 'touchmove', function( e ) { if( this.scrollWidth <= this.offsetWidth ) return; var diff = this.touchX - e.touches[0].clientX; var rest = this.scrollWidth - this.offsetWidth; var position = this.touchDownX + diff; if( position > rest ) position = rest; else if( position < 0 ) position = 0; this.scrollLeft = position; } ); hasContainer.addEventListener( 'touchend', function( e ) { this.touchDownX = false; this.touchX = false; } ); } // Scroll areas for( var a = 0; a < pages.length; a++ ) { var pag = pages[a]; for( var b = 0; b < pag.childNodes.length; b++ ) { // Find content container var cr = pag.childNodes[ b ]; var resizeObject = false; var spaceSize = 0; // margins and paddings while( cr && cr != document.body ) { var cl = cr.classList; if( cl && ( cl.contains( 'VContentTop' ) || cl.contains( 'VContentLeft' ) || cl.contains( 'VContentRight' ) || cl.contains( 'ContentFull' ) || cl.contains( 'VContentBottom' ) ) ) { resizeObject = cr; break; } cr = cr.parentNode; } // Resize pagescroll and set resize event var cl = pag.childNodes[ b ].classList; if( cl && cl.contains( 'PageScroll' ) ) { // New scope for resize event function addResizeEvent( n, page ) { var ch = 0; var ph = 0; function resiz( force ) { // Take last container height ph = ch; ch = wobj.offsetHeight ? wobj.offsetHeight : wobj.innerHeight; // We succeeded in getting a stable tabpage container height // Check if it changed, and abort if it didn't if( ch == ph && !force ) return; // Containing element var hhh = GetElementHeight( resizeObject ) - ( hasContainer ? GetElementHeight( hasContainer ) : n.tab.offsetHeight ); // Page scroll height (other elements contained minus page scroll element) var psh = 0; for( var pa = 0; pa < n.parentNode.childNodes.length; pa++ ) { var nn = n.parentNode.childNodes[ pa ]; // Skip elements after page scroll if( nn.className && nn.classList.contains( 'PageScroll' ) ) { break; } if( n.parentNode.childNodes[ pa ] != n ) { if( n.parentNode && n.parentNode.childNodes[ pa ].nodeName == 'DIV' ) psh += GetElementHeight( n.parentNode.childNodes[ pa ] ); } } // See if containing page has padding var css = window.getComputedStyle( page, null ); var targets = [ 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth' ]; for( var zz = 0; zz < targets.length; zz++ ) { if( css[ targets[ zz ] ] ) psh += parseInt( css[ targets[ zz ] ] ); } // See if page scroller has margin css = window.getComputedStyle( n, null ); var targets = [ 'marginTop', 'marginBottom', 'borderTopWidth', 'borderBottomWidth' ]; for( var zz = 0; zz < targets.length; zz++ ) { if( css[ targets[ zz ] ] ) psh += parseInt( css[ targets[ zz ] ] ); } // Page scroll n.style.height = hhh - psh + 'px'; // Container page.style.height = hhh + 'px'; // Refresh again in case height changes setTimeout( function(){ resiz(); }, 25 ); } // Add events and window.addEventListener( 'resize', resiz ); if( !Friend.resizeTabs ) Friend.resizeTabs = []; n.tab.addEventListener( 'click', function(){ resiz( 1 ); } ); Friend.resizeTabs.push( { element: pdiv, resize: function() { resiz( 1 ); } } ); // Resize now! (in 5ms) setTimeout( function() { resiz(); }, 5 ); } // Register the page and associate it so we can add the resize event var n = pag.childNodes[ b ]; n.tab = tabs[a]; n.style.position = 'relative'; n.style.overflow = 'auto'; n.parentNode.style.height = n.style.height; if( wobj && n.tab ) { addResizeEvent( n, pag ); } } } } if( tabs.length && tabs[active] ) { tabs[active].click(); } } // Double click simulator for youch function touchDoubleClick( element, callback, e ) { if( !element.touchClickCount ) { element.touchClickCount = 1; } else { element.touchClickCount = false; callback( element, e ); } // Simulate timeout between clicks setTimeout( function() { element.touchClickCount = false; }, 500 ); } function checkMobile() { var check = false; (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); return check; } function checkTablet() { var check = false; (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); return check; } // Are we on a mobile browser? function checkMobileBrowser() { if( !document.body ) return setTimeout( checkMobileBrowser, 50 ); window.isMobile = checkMobile(); if( !window.isTablet ) window.isTablet = checkTablet() || isTouchDevice(); if( window.isMobile && ( window.innerWidth <= 760 || window.innerHeight <= 500 ) ) { if( !window.isTablet ) window.isTablet = false; } else if( window.isTablet ) { window.isMobile = false; } if( !window.isMobile && !window.isTablet ) { if( window.isTouch || !document.getElementsByTagName( 'head' )[0].getAttribute( 'touchdesktop' ) ) { window.isMobile = ( window.Workspace && window.innerWidth <= 760 ) && ( navigator.userAgent.toLowerCase().indexOf( 'android' ) > 0 || navigator.userAgent.toLowerCase().indexOf( 'phone' ) > 0 || navigator.userAgent.toLowerCase().indexOf( 'pad' ) > 0 || navigator.userAgent.toLowerCase().indexOf( 'bowser' ) > 0 ); if( ( window.isMobile || navigator.userAgent.indexOf( 'Mobile' ) > 0 ) && window.innerWidth >= 1024 ) { window.isTablet = true; window.isMobile = false; } } } // Ipads are always mobiles for apple users at least if( navigator.userAgent.toLowerCase().indexOf( 'ipad' ) > 0 && typeof(Workspace) != 'undefined' && Workspace.loginUsername == 'applereview' ) { //console.log( 'IPAD! ' + navigator.userAgent ); window.isMobile = true; } window.isTouch = !!('ontouchstart' in window); if( window.isMobile ) { document.body.setAttribute( 'mobile', 'mobile' ); } else if( window.isTablet ) { document.body.setAttribute( 'tablet', 'tablet' ); } else { document.body.removeAttribute( 'tablet' ); } if( navigator.userAgent.toLowerCase().indexOf( 'playstation' ) > 0 ) { document.body.setAttribute( 'settopbox', 'playstation' ); window.isSettopBox = 'playstation'; if (typeof console != "undefined") { if (typeof console.log != 'undefined') console.olog = console.log; else console.olog = function() {}; } console.log = function(message) { console.olog(message); Notify( { title: 'Playstation error', text: message } ); }; console.error = console.debug = console.info = console.log } return window.isMobile; } // Binary to string conversions for transport in postmessage function ConvertArrayBufferToString( arraybuffer, method ) { if( !method || method == 'binaryString' ) { var v = new Uint8Array( arraybuffer ); return Array.prototype.join.call( v, ',' ); } else if ( method == 'base64' ) { var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var bytes = new Uint8Array( arraybuffer ), i, len = bytes.length, base64 = ""; for (i = 0; i < len; i+=3) { base64 += chars[bytes[i] >> 2]; base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; base64 += chars[bytes[i + 2] & 63]; } if ((len % 3) === 2) { base64 = base64.substring(0, base64.length - 1) + "="; } else if (len % 3 === 1) { base64 = base64.substring(0, base64.length - 2) + "=="; } return base64; } return false; } function ConvertStringToArrayBuffer( str, method ) { if( !method || method == 'binaryString' ) { var data = str.split( ',' ); return ( new Uint8Array( data ) ).buffer; } else if ( method == 'base64' ) { var lookup = window.base64Lookup; if ( !lookup ) { var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; lookup = new Uint8Array(256); for ( var i = 0; i < chars.length; i++ ) { lookup[ chars.charCodeAt( i ) ] = i; } window.base64Lookup = lookup; } var bufferLength = str.length * 0.75, len = str.length, i, p = 0, encoded1, encoded2, encoded3, encoded4; if ( str[ str.length - 1 ] === "=") { bufferLength--; if ( str[ str.length - 2 ] === "=") { bufferLength--; } } var arraybuffer = new ArrayBuffer( bufferLength ), bytes = new Uint8Array( arraybuffer ); for ( i = 0; i < len; i += 4 ) { encoded1 = lookup[str.charCodeAt(i)]; encoded2 = lookup[str.charCodeAt(i+1)]; encoded3 = lookup[str.charCodeAt(i+2)]; encoded4 = lookup[str.charCodeAt(i+3)]; bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); } return arraybuffer; } return false; } function ConvertBase64StringToString( str ) { var arrayBuffer = ConvertStringToArrayBuffer( str, 'base64' ); var bytes = new Uint8Array( arrayBuffer ); var len = bytes.length; var result = ''; for ( var c = 0; c < len; c++ ) result += String.fromCharCode( bytes[ c ] ); return result; } function ConvertStringToBase64String( input ) { // private property var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; // public method for encoding var output = ""; var chr1, chr2, chr3, enc1, enc2, enc3, enc4; var i = 0; while ( i < input.length ) { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4); }; return output; } // Extract the name of a file from a path function GetFilename( path ) { if ( path.charAt( path.length - 1 ) == '/' ) path = path.substring( 0, path.length - 1 ); var slash = path.lastIndexOf( '/' ); if ( slash >= 0 ) return path.substring( slash + 1 ); var split = path.split( ':' ); if ( split[ 1 ] && split[ 1 ].length ) return split[ 1 ]; return split[ 0 ]; } // Clean the properties of a Javascript object function CleanArray( keys, exclude ) { var out = [ ]; for ( var key in keys ) { if ( keys[ key ] && keys[ key ] != exclude ) out[ key ] = keys[ key ]; } return out; } var __randDevId = false; function GetDeviceId() { // Try to get the device id from cookie /*var ck = GetCookie( 'deviceId' ); if( ck ) return ck;*/ if( !__randDevId ) { var md5 = deps ? deps.MD5 : window.MD5; __randDevId = md5( ( Math.random() % 999 ) + ( Math.random() % 999 ) + ( Math.random() % 999 ) + '' ); } var id = !!('ontouchstart' in window) ? 'touch' : 'wimp'; var ua = navigator.userAgent.toLowerCase() var type = ua.indexOf( 'android' ) > 0 ? 'android' : false; var platform = ''; if( !type ) type = ua.indexOf( 'phone' ) > 0 ? 'iphone' : false; if( !type ) type = 'other'; if( ua.indexOf( 'ios' ) > 0 ){ platform = 'iOS'; } else if( ua.indexOf( 'iphone' ) > 0 ){ platform = 'iOS'; } else if( ua.indexOf( 'mac' ) > 0 ){ platform = 'Apple'; } else if( ua.indexOf( 'windows' ) > 0 ){ platform = 'Microsoft'; } else if( ua.indexOf( 'linux' ) > 0 ){ platform = 'Linux'; } if( !platform ) platform = 'Generic'; let r = id + '_' + type + '_' + platform + '_' + __randDevId; //application token is needed for iOS push notifications if( window.friendApp ) { if( window.friendApp.get_app_token ) { let oldToken = friendApp.get_app_token(); if( window.friendApp.get_platform ) { if( friendApp.get_platform() == 'iOS' ) { platform = 'iOS'; // Already has a token if( oldToken.indexOf( '_ios_app_' ) > 0 ) { r = friendApp.get_app_token(); } else { r = id + '_ios_app_' + friendApp.get_app_token(); } } else { platform = 'Android'; // Already has a token if( oldToken.indexOf( '_android_app_' ) > 0 ) { r = friendApp.get_app_token(); } else { r = id + '_android_app_' + friendApp.get_app_token(); } } } else { if( platform === 'iOS' ) { platform = 'iOS'; // Already has a token if( oldToken.indexOf( '_ios_app_' ) > 0 ) { r = friendApp.get_app_token(); } else { r = id + '_ios_app_' + friendApp.get_app_token(); } } else { platform = 'Android'; // Already has a token if( oldToken.indexOf( '_android_app_' ) > 0 ) { r = friendApp.get_app_token(); } else { r = id + '_android_app_' + friendApp.get_app_token(); } } } } } // Avoid duplicates if( platform != 'iOS' ) { while( r.indexOf( 'android_app_touch_android_app_' ) >= 0 ) r = r.split( 'android_app_touch_android_app_' ).join( 'android_app_' ); } else { while( r.indexOf( 'ios_app_touch_ios_app_' ) >= 0 ) r = r.split( 'ios_app_touch_ios_app_' ).join( 'ios_app_' ); } // Store the cookie for later use //SetCookie( 'deviceId', r ); return r; } checkMobileBrowser(); /* Ranges and cursors */ /* TODO: Implement this! */ function SetCursorPosition( element, position ) { setTimeout( function() { element.focus(); var s = window.getSelection(); var r = document.createRange(); var pos = position; switch( position ) { case 'end': pos = element.childElementCount; r.selectNodeContents( element ); break; case 'start': pos = 0; r.setStart( element, 0 ); r.setEnd( element, 0 ); default: r.setStart( element, position ); r.setEnd( element, position ); break; } r.collapse(); s.removeAllRanges(); s.addRange( r ); }, 0 ); } /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ // Parse a string / file and make compact, logical css function ParseCssFile( string, path, callback ) { if( !path ) path = document.location.href.split( 'index.' )[0]; path = path.split( 'sandboxed.' )[0]; if( typeof( cAjax ) == 'undefined' ) return setTimeout( function(){ ParseCssFile( string, path, callback ); }, 25 ); var j = new cAjax (); j.open ( 'get', string, false ); j.sourcePath = string; j.onload = function() { var string = Trim ( this.responseText () ); var loading = 0; var paths = []; // ones to concat // Make relative path var rpath = path.split( /http[^:]*?\:\/\/[^/]*?\// ).join( '' ); var dmain = ''; if( dmain = path.match( /(http[^:]*?\:\/\/[^/]*?\/)/ ) ) dmain = dmain[1]; else dmain = ''; // Import other included parsed css files with their own rules while( true ) { var matches = string.match ( /\@append[^u]*?url[^(]*?\(([^)]*?)\)[^;]*?\;/ ); if ( matches ) { string = string.split ( matches[0] ).join ( '' ); matches[1] = rpath + matches[1].split ( /["|']/ ).join ( '' ); paths.push( matches[1] ); } else break; } // Do it var out = ''; for( var a = 0; a < paths.length; a++ ) { if( a > 0 ) out += ';'; out += paths[a]; } // Final path out = dmain + out; // Load extracted paths at once and parse them var jax = new cAjax(); jax.open( 'get', out, true ); jax.onload = function() { AddParsedCSS( string, this.responseText(), callback, path ); } jax.send(); } j.send (); } function AddParsedCSS ( string, dataqueue, callback, originalPath ) { // Add queued data (array) if( typeof( dataqueue ) != 'string' ) { for ( var a = 0; a < dataqueue.length; a++ ) { string += "\n" + Trim ( dataqueue[a].responseText () ) + "\n"; } } // String else { string += "\n" + Trim( dataqueue ); } // Check condition blocks var mDat = false; while( mDat = string.match( /(\@if.*?[\n|\r]+)/i ) ) { var vari = mDat[1].match( /\@if\ ([^\r\n]+)/i ); if( vari[1] ) { var v = vari[1].substr( 1, vari[1].length - 1 ); // True? if( window[v] ) { string = string.split( mDat[1] ).join( "@media (min-width: 0)" ); } // Remove block! else { string = string.split( mDat[1] ).join( "@media (min-width: 999999999)" ); } } } // Parse this css complete file, with these toplevel declarations var theme = new Object (); var replacements = []; var matches = ''; while ( matches = string.match ( /\@declaration[^{]*?\{([^}]*?)\}/i ) ) { string = string.split ( matches[0] ).join ( '!parsing_css_working_on_it!' ); var rules = matches[1].split ( "\n" ); if ( rules.length ) { for ( var a in rules ) { var rule = rules[a]; if ( !Trim ( rule ).length ) continue; var mat = ''; while ( mat = rule.match ( /(\$[^:]*?)\:[\s]+([^;]*?)\;/i ) ) { replacements.push ( [ mat[1], mat[2] ] ); var vkey = mat[1].substr ( 1, mat[1].length - 1 ); theme.vkey = mat[2]; rule = rule.split ( mat[0] ).join ( '' ); } } string = string.split ( '!parsing_css_working_on_it!' ).join ( '' ); } } // Code blocks while ( matches = string.match ( /\@block\ (\$[^{]*?)\{([^}]*?)\}/i ) ) { replacements.push ( [ Trim ( matches[1] ), Trim ( matches[2] ) ] ); string = string.split ( matches[0] ).join ( '' ); } // Declaration of "with" rules var rwith = []; //var t = ( new Date() ).getTime(); while( matches = string.match( /(.*?)\ with (\.[^\n|{| ]*?)[\n|{| ]/i ) ) { if( typeof( rwith[matches[2]] ) == 'undefined' ) rwith[matches[2]] = []; rwith[matches[2]].push( matches[1] ); //console.log( matches[2] + ' -> ' + matches[1] ); string = string.split( matches[0] ).join ( matches[1] + "\n" ); } // Execute replacements if ( replacements.length ) { for ( var a in replacements ) { var replacement = replacements[a]; // Remove variable lines with the value delete if( replacement[1] == 'delete' ) { // Add slashes var rp = replacement[1]; var op = ''; for( var u = 0; u < rp.length; u++ ) { var c = rp.substr ( u, 1 ); if ( c.match ( /[^a-zA-Z0-9]/ ) ) op += '\''; op += c; } var xp = new RegExp( '/.*?\:.*?' + op + '\;[\n|\r]+/i' ); string = string.split( xp ).join ( '' ); } else string = string.split( replacement[0] + ';' ).join ( replacement[1] + ';' ); } } // Execute block replacements if( rwith.length ) { for ( var cls in rwith ) { var elements = rwith[cls]; cls = str_replace ( '.', '', cls ); var findex = new RegExp( '/\.'+cls+'([\n|{])/i' ); var foundd = string.match ( findex ); if ( foundd ) { string = string.split ( findex ).join ( foundd[1] ); } } } /*// Strip comments string = string.split ( /\/\*[^\/]*?\/[\n|\r]+/i ).join ( '' ); // Strip character returns|tabs and whitespace string = string.split ( /[\n|\t|\r]+/i ).join ( '' ); string = string.split ( /[\s]{2,}/i ).join ( '' );*/ var style = document.createElement ( 'style' ); style.innerHTML = string; document.getElementsByTagName( 'head' )[0].appendChild ( style ); if( callback ) setTimeout( callback, 0 ); } /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ var friendUP = window.friendUP || {}; if ( window.ApplicationStorage ) throw new Error( 'window.ApplicationStorage is already defined' ); var lib = {}; // temp obj (function( ns, undefined ) { ns.ApplicationStorage = function() { var self = this; self.init(); } ns.ApplicationStorage.prototype.init = function() { var self = this; self.methodMap = { 'set' : setItem, 'get' : getItem, 'remove' : removeItem, }; //console.log( '--- localStorage ---' ); //console.log( window.localStorage ); function setItem( msg, app ) { self.setItem( msg, app ); } function getItem( msg, app ) { self.getItem( msg, app ); } function removeItem( msg, app ) { self.removeItem( msg, app ); } } ns.ApplicationStorage.prototype.receiveMsg = function( msg, app ) { var self = this; var handler = self.methodMap[ msg.method ]; if ( !handler ) { msg.data.success = false; msg.data.message = 'no such handler'; self.send( msg, app ); return; } var success = self.checkId( msg ); if ( !success ) return; handler( msg, app ); } ns.ApplicationStorage.prototype.setItem = function( msg, app ) { var self = this; var bundle = msg.data; var appData = self.load( app ); appData[ bundle.id ] = bundle.data; var success = self.save( appData, app ); bundle.success = success; self.send( msg, app ); } ns.ApplicationStorage.prototype.getItem = function( msg, app ) { var self = this; var bundle = msg.data; var appData = self.load( app ); var data = appData[ bundle.id ]; bundle.data = data; self.send( msg, app ); } ns.ApplicationStorage.prototype.removeItem = function( msg, app ) { var self = this; var bundle = msg.data; var id = bundle.id; var appData = self.load( app ); delete appData[ id ]; var success = self.save( appData, app ); bundle.success = success; self.send( msg, app ); } ns.ApplicationStorage.prototype.send = function( msg, app ) { var self = this; msg.command = 'applicationstorage'; app.contentWindow.postMessage( msg, '*' ); } ns.ApplicationStorage.prototype.load = function( app ) { var self = this; var appName = app.applicationName; var appData = window.localStorage.getItem( appName ); appData = friendUP.tool.parse( appData ); if ( !appData ) appData = {}; return appData; } ns.ApplicationStorage.prototype.save = function( appData, app ) { var self = this; var appName = app.applicationName; var appData = friendUP.tool.stringify( appData ); if ( !appData ) return false; window.localStorage.setItem( appName, appData ); return true; } ns.ApplicationStorage.prototype.checkId = function( msg ) { var self = this; var bundle = msg.data; if ( !bundle.id ) { returnError(); return false; } var cleanId = bundle.id.toString().trim(); if ( cleanId !== bundle.id ) { returnError(); return false; } return true; function returnError() { console.log( 'applicationstorage - invalid msg', msg ); bundle.success = false; bundle.message = 'invalid id'; bundle.cleanId = cleanId || null; self.send( msg, app ); } } })( lib ); window.ApplicationStorage = new lib.ApplicationStorage(); /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ // Simple abstraction for FriendUP modules var FriendLibrary = function ( library, encryption ) { // Get cleaned library this.encryption = encryption ? true : false; this.library = library.split( '.library' ).join ( '' ).toLowerCase(); this.args = false; this.method = false; this.vars = []; this.addVar = function( k, value ) { this.vars[k] = value; } this.destroy = function() { this.encryption = null; this.library = null; this.args = null; this.method = null; this.vars = null; if( this.currentRequest ) { this.currentRequest.destroy(); this.currentRequest = null; } delete this; } // Execute a method to a Friend UP module this.execute = function( method, args ) { if ( method ) this.method = method; var data = ''; var j = new cAjax (); if( this.cancelId ) j.cancelId = this.cancelId; if( this.onQueue ) j.onQueue = this.onQueue; this.currentRequest = j; if( this.forceHTTP ) j.forceHTTP = true; if( this.forceSend ) j.forceSend = true; if( this.loginCall ) j.loginCall = true; var ex = ''; if( args ) { this.args = args; if( typeof( args ) == 'string' ) { ex += '/' + args; } else if( typeof( args ) == 'object' ) { for( var a in args ) { if( a == 'command' ) { ex += '/' + args[a]; } else { if( typeof( args[a] ) == 'object' ) { this.addVar( a, JSON.stringify( args[a] ) ); } else this.addVar( a, args[a] ); } } } } j.open ( 'post', '/' + this.library + '.library/' + this.method + ex, true, true ); if( typeof( Workspace ) != 'undefined' && Workspace.sessionId ) { this.addVar( 'sessionid', Workspace.sessionId ); } if( this.encryption ) { // If ssl is enabled add vars encrypted data string to send as post raw data with cAjax if( this.vars && typeof( fcrypt ) != 'undefined' && typeof( Workspace ) != 'undefined' && Workspace.encryption.keys.server && Workspace.encryption.keys.client ) { var json = JSON.stringify( this.vars ); if( json ) { // TODO: This will probably not work in C code only made for js/php since encryptString is a RSA+AES combination to support large blocks of data outside of RSA limitations, make encryptRSA() support stacking of blocks split by block limit //var encrypted = fcrypt.encryptString( json, Workspace.keys.server.publickey ); //if( encrypted && encrypted.cipher ) //{ // data = encrypted.cipher; //} //data = fcrypt.encryptRSA( json, Workspace.keys.server.publickey ); var encrypted = fcrypt.encryptRSA( json, Workspace.encryption.keys.server.publickey ); j.addVar( 'encryptedblob', encrypted ); console.log( 'data', { vars: this.vars, data: ( data ? data : encrypted ) } ); } } } else { // Add vars for( var a in this.vars ) { j.addVar( a, this.vars[a] ); } } if( this.onExecuted ) { var t = this; j.onload = function( rc, d ) { // First try to parse a pure JSON string try { if( t.encryption ) { // If ssl is enabled decrypt the data returned by cAjax if( rc && typeof( fcrypt ) != 'undefined' && typeof( Workspace ) != 'undefined' && Workspace.encryption.keys.server && Workspace.encryption.keys.client ) { // TODO: This will probably not work in C code only made for js/php since decryptString is a RSA+AES combination to support large blocks of data outside of RSA limitations, make decryptRSA() support stacking of blocks split by block limit //var decrypted = fcrypt.decryptString( rc, Workspace.keys.client.privatekey ); //if( decrypted && decrypted.plaintext ) //{ // rc = decrypted.plaintext; //} rc = fcrypt.decryptRSA( rc, Workspace.encryption.keys.client.privatekey ); } } var json = JSON.parse( rc ); if( json ) { return t.onExecuted( json ); } // No json then.. t.onExecuted( rc, d ); t.destroy(); } // No, it's not that catch( e ) { // Used for localization of responses etc if( d && d.length && t.replacements ) { for( var z in t.replacements ) { d = d.split ( '{'+z+'}' ).join ( t.replacements[z] ); } } t.onExecuted( rc, d ); t.destroy(); } } } j.send ( data ); } } // Eventually move to new Library() var Library = FriendLibrary; /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ let _cajax_process_count = 0; let _cajax_connection_seed = Math.random(0,999)+Math.random(0,999)+Math.random(0,999)+'_'; let _cajax_http_connections = 0; // How many? let _cajax_http_max_connections = 6; // Max let _cajax_http_last_time = 0; // Time since last let _cajax_mutex = 0; let _cajax_ws_connections = 0; // How many? let _cajax_ws_max_connections = 12; // Max let _cajax_ws_disabled = 0; // Disable websocket usage? let _cajax_origin = document.location.origin; // For debug let _c_count = 0; let _c_destroyed = 0; if( !window.Friend ) window.Friend = {}; if( !Friend.cajax ) { Friend.cajax = {}; Friend.cajaxTypes = []; } function AddToCajaxQueue( ele ) { if( typeof( Friend.cajax[ ele.type ] ) == 'undefined' ) { Friend.cajax[ ele.type ] = { count: 0, max: 0, queue: [] }; Friend.cajaxTypes.push( ele.type ); } let queue = Friend.cajax[ ele.type ].queue; // If we're queueing it if( ele.onQueue ) ele.onQueue(); // Queued objects get eternal life if( ele.life ) { clearTimeout( ele.life ); ele.life = null; } ele.queued = true; // Don't add to queue if we are offline if( !Friend.User || !Friend.User.ServerIsThere ) { if( ele.onload ) { ele.onload( null, null ); } return ele.destroy(); } // TODO: Support a nice queue.. :-) if( !window.Friend ) { return false; } // Duplicate check for( let a = 0; a < queue.length; a++ ) { if( queue[a] == ele ) { return false; } } // Add ajax element to the bottom of the queue let o = []; for( let a = 0; a < queue.length; a++ ) o.push( queue[ a ] ); o.push( ele ); Friend.cajax[ ele.type ].queue = o; } function RemoveFromCajaxQueue( ele ) { if( typeof( Friend.cajax[ ele.type ] ) == 'undefined' ) return; let queue = Friend.cajax[ ele.type ].queue; let o = []; for( let a = 0; a < queue.length; a++ ) { if( queue[a] != ele ) { o.push( queue[a] ); } } Friend.cajax[ ele.type ].queue = o; } // Cancel all queued cajax calls on id function CancelCajaxOnId( id ) { for( let type in Friend.cajax ) { let queue = Friend.cajax[ type ].queue; let o = []; for( let a = 0; a < queue.length; a++ ) { if( queue[ a ].cancelId != id ) o.push( queue[ a ] ); else { // Tell it it failed queue[ a ].destroy(); } } Friend.cajax[ type ].queue = o; } } // A simple ajax function // Can have a cancellable series cAjax = function() { let self = this; self.type = 'normal'; // Type of ajax call (group for queueing) _cajax_process_count++; // cajax only survives for so long.. self.life = setTimeout( function() { self.destroy(); }, 5000 ); // Vars to mark success / process count this.decreasedProcessCount = false; // Other this.openFunc = null; this.opened = false; this.context = false; // Named contexts, so that we can kill an entire context this.setResponseType = function( type ) { switch( type ) { case 'arraybuffer': this.proxy.responseType = 'arraybuffer'; break; case 'blob': this.proxy.responseType = 'blob'; break; default: this.proxy.responseType = ''; break; } }; this.vars = []; this.mode = 'ajax'; this.varcount = 0; // TODO: Enable for later //this.worker = new Worker( '/webclient/js/io/cajax_worker.js' ); // Get correct AJAX base object if ( typeof( ActiveXObject ) != 'undefined' ) this.proxy = new ActiveXObject( 'Microsoft.XMLHTTP' ); else this.proxy = new XMLHttpRequest(); this.proxy.timeout = 8000; // State call let jax = this; this.proxy.onreadystatechange = function() { // We're finished handshaking if( this.readyState == 4 && this.status == 200 ) { if( this.responseType == 'arraybuffer' ) { jax.rawData = this.response; } else if( this.responseType == 'blob' ) { jax.rawData = new Blob( [ this.response ] ); } else { jax.rawData = this.responseText; } if( this.responseType != '' ) { jax.returnData = jax.rawData; jax.returnCode = 'ok'; } else if( this.hasReturnCode ) { let sep = ''; if( this.responseText.indexOf( sep ) > 0) { jax.returnCode = this.responseText.substr( 0, this.responseText.indexOf( sep ) ); jax.returnData = this.responseText.substr( this.responseText.indexOf( sep ) + sep.length ); } else { jax.returnData = false; jax.returnCode = this.responseText; } } else { jax.returnCode = false; jax.returnData = this.responseText; } // TODO: This error is general if( this.responseType != 'arraybuffer' && this.responseType != 'blob' ) { if( JSON && jax.rawData.charAt( 0 ) == '{' ) { try { let t = JSON.parse( jax.rawData ); // Deprecate from 1.0 beta 2 "no user!" let res = t ? t.response.toLowerCase() : ''; if( t && ( res == 'user not found' || res.toLowerCase() == 'user session not found' ) ) { if( window.Workspace && res.toLowerCase() == 'user session not found' ) Workspace.flushSession(); if( window.Workspace ) { // Drop these (don't retry!) because of remote fs disconnect if( jax.url.indexOf( 'file/info' ) > 0 ) { return jax.destroy(); } // Add to queue AddToCajaxQueue( jax ); return Friend.User.CheckServerConnection(); } } } catch( e ) { if( !jax.rawData ) { console.log( '[cAjax] Can not understand server response: ', jax.rawData ); jax.destroy(); return; } } } // Respond to old expired sessions! else if( jax.returnCode == 'fail' ) { try { let r = JSON.parse( jax.returnData ); let res = r ? r.response.toLowerCase() : ''; if( res == 'user not found' || res.toLowerCase() == 'user session not found' ) { if( window.Workspace && res.toLowerCase() == 'user session not found' ) Workspace.flushSession(); if( window.Workspace && Workspace.postInitialized && Workspace.sessionId ) { // Add to queue AddToCajaxQueue( jax ); return Friend.User.CheckServerConnection(); } } } catch( e ) { } } } else { if( jax.rawData ) { this.returnCode = 'ok'; this.returnData = this.rawData; } else { this.returnCode = 'false'; this.returnData = null; } } // Clean up if( jax.mode != 'websocket' ) { if( !jax.forceSend ) { _cajax_http_connections--; if( _cajax_http_connections < 0 ) _cajax_http_connections = 0; } } // End clean queue // Execute onload action with appropriate data if( jax.onload ) { jax.onload( jax.returnCode, jax.returnData ); } jax.destroy(); } // Something went wrong! else if( this.readyState == 4 && ( this.status == 500 || this.status == 0 || this.status == 404 ) ) { // If we have available slots, but we have other ajax calls in pipe, execute them if( _cajax_http_connections < _cajax_http_max_connections ) { CleanAjaxCalls(); } // tell our caller... if( jax.onload ) { jax.onload( 'error', '' ); } jax.destroy(); } else { } } } // Clean up object // Never use this one outside the destroy() function!!! cAjax.prototype.destroySilent = function() { let self = this; // No more activity here! self.decreaseProcessCount(); if( self.opened ) self.close(); if( self.life ) clearTimeout( self.life ); self.life = null; self.vars = null; self.mode = null; self.url = null; self.hasReturnCode = null; self.lastOptions = null; self.proxy = null; if( self.worker ) self.worker.terminate(); self.worker = null; self.data = null; self.rawData = null; self.varcount = null; self.wsRequestID = null; self.wsData = null; self.connectionId = null; self.hasReturnCode = null; self.returnCode = null; self.returnData = null; self.method = null; self.onload = null; self.openFunc = null; // finally delete this; } cAjax.prototype.destroy = function() { this.destroy = function(){}; // We can use this for tracing if( this.ondestroy ) { this.ondestroy(); } // Terminate with onload if( this.onload ) { //console.log( 'Should never happen.' ); this.onload( null, null ); this.onload = null; if( this.proxy ) { this.proxy.abort(); } } else { this.proxy.abort(); } // Clean out possible queue and replenish RemoveFromCajaxQueue( this ) // Clean up this.destroySilent(); } // Open an ajax query cAjax.prototype.open = function( method, url, syncing, hasReturnCode ) { const self = this if( this.opened ) { //console.log( '[cajax] Impossible error! Illegal reuse of object.' ); this.destroy(); return; } this.opened = true; // Move dos calls onto http let dosCall = false; if( url.indexOf( '/file/read' ) >= 0 || url.indexOf( '/file/copy' ) >= 0 || url.indexOf( '/file/delete' ) >= 0 || url.indexOf( '/file/write' ) >= 0 || url.indexOf( '/file/dir' ) >= 0 || url.indexOf( '/file/expose' ) ) { dosCall = true; } // Try websockets!! if( !_cajax_ws_disabled && !this.forceHTTP && window.Workspace && Workspace.conn && Workspace.conn.ws && Workspace.conn.ws.ready && Workspace.conn.ws.ws && ( this.proxy && this.proxy.responseType != 'arraybuffer' ) && typeof( url ) == 'string' && url.indexOf( 'http' ) != 0 && url.indexOf( 'system.library' ) >= 0 && !dosCall ) { this.mode = 'websocket'; if( self.type.indexOf( '_websocket' ) < 0 ) self.type += '_websocket'; this.url = url; this.hasReturnCode = hasReturnCode; //console.log( 'WebSocket call: ' + url ); return true; } /*// HTTP call, sanitize else { // Repair websocket // TODO: Remove completely after real fix found if( window.Workspace && Workspace.conn && Workspace.conn.ws && !Workspace.conn.ws.ws ) { console.log( 'Repairing websocket.' ); Workspace.initWebSocket(); } //console.log( 'HTTP call: ' + url ); }*/ // If we are running this on friendup recreate url to support old method if ( typeof AjaxUrl == 'function' ) { url = AjaxUrl( url ); } if( this.lastOptions && !method && !url && !syncing && !hasReturnCode ) { this.proxy.hasReturnCode = this.lastOptions.hasReturnCode; this.openFunc = function() { if( window.Workspace ) self.addVar( 'sessionid', Workspace.sessionId ); self.proxy.open( self.lastOptions.method, self.lastOptions.url, self.lastOptions.syncing ); }; } else { this.lastOptions = { method : method, url : url, syncing : syncing, hasReturnCode : hasReturnCode }; if( !method ) method = this.method ? this.method : 'POST'; else method = method.toUpperCase(); if( !syncing ) syncing = true; if( !hasReturnCode ) hasReturnCode = false; this.method = method.toUpperCase(); if( !url ) url = this.url; this.url = url; this.proxy.hasReturnCode = hasReturnCode; this.openFunc = function() { if( window.Workspace ) self.addVar( 'sessionid', Workspace.sessionId ); let u = self.url; if( u.substr( 0, 1 ) == '/' ) { let urlbase = _cajax_origin; u = urlbase + u; } self.proxy.open( self.method, u, syncing ); }; } } // Close it! cAjax.prototype.close = function() { if( this.opened ) { // Abort connection this.proxy.abort(); this.opened = false; } } // Add a variable to ajax query cAjax.prototype.addVar = function( key, val ) { if( typeof( val ) == 'undefined' ) { //// console.log( 'Test3: Trying to add undefined var.', key, val ); return; } if( typeof( this.vars[ key ] ) == 'undefined' ) this.varcount++; this.vars[ key ] = encodeURIComponent( val ); return true; } // Set a request header cAjax.prototype.setRequestHeader = function( type, data ) { return this.proxy.setRequestHeader( type, data ); } // Just generate a random unique number cAjax.prototype.getRandNumbers = function() { if( window.UniqueHash ) return UniqueHash(); let i = ''; for( let a = 0; a < 2; a++ ) i += Math.floor( Math.random() * 1000 ) + ''; i += ( new Date() ).getTime(); return i; } cAjax.prototype.responseText = function() { return this.wsData ? this.wsData : this.proxy.responseText; } // Send ajax query cAjax.prototype.send = function( data, callback ) { let self = this; RemoveFromCajaxQueue( this ); // Make sure we don't f this up! if( this.onload && !this.onloadAfter ) { this.onloadAfter = this.onload; this.onload = function( e, d ) { this.onload = null; this.onloadAfter( e, d ); this.onloadAfter = null; CleanAjaxCalls(); } } if( self.life ) { clearTimeout( self.life ); self.life = false; } if( self.queued ) { self.queued = false; //console.log( '[cajax] This is running from queue.' ); } if( !data && this.cachedData ) { data = this.cachedData; this.cachedData = null; } if( !callback && this.cachedCallback ) { callback = this.cachedCallback; this.cachedCallback = null; } // Keep for later if( data ) this.cachedData = data; // Wait in case of check server connection if( window.Workspace && ( window.Friend && Friend.User && Friend.User.State == 'offline' ) && !this.forceSend ) { AddToCajaxQueue( self ); return; } // Can't have too many! Queue control if( this.mode != 'websocket' ) { if( !this.forceSend && ( _cajax_http_connections >= _cajax_http_max_connections ) ) { //console.log( 'We got max connections!' ); AddToCajaxQueue( self ); return; } if( !this.forceSend ) { _cajax_http_connections++; } } // Limit on websockets else { if( _cajax_ws_connections >= _cajax_ws_max_connections ) { AddToCajaxQueue( self ); return; } _cajax_ws_connections++; } // Register successful send _cajax_http_last_time = ( new Date() ).getTime(); if( this.mode == 'websocket' && this.proxy && this.proxy.responseType == 'arraybuffer' ) { this.mode = ''; } // Check if we can use websockets if( self.mode == 'websocket' && window.Workspace && Workspace.conn && Workspace.conn.ws && Workspace.conn.ws.ready ) { let u = self.url.split( '?' ); let wsdata = ( data ? data : {} ); if( self.vars ) { for (dataIndex in self.vars) { wsdata[ dataIndex ] = self.vars[ dataIndex ]; } } // if we have parameters set in the URL we add them to the socket... if( u[1] ) { let pairs = u[1].split( '&' ); for( let a = 0; a < pairs.length; a++ ) { let p = pairs[a].split( '=' ); wsdata[p[0]] = p[1]; } } let req = { path : u[0].substr(1), data : wsdata }; let reqID = Workspace.conn.request( req, bindSingleParameterMethod( self, 'handleWebSocketResponse' ) ); if( typeof( reqID ) != 'undefined' && !reqID ) { AddToCajaxQueue( self ); return; } else if( typeof( reqID ) == 'undefined' ) { } self.wsRequestID = reqID; return; } // standard HTTP way.... if( this.openFunc ) { this.openFunc(); let res = null; if( this.method == 'POST' ) { let u = this.url.split( '?' ); u = u[ 0 ] + '?' + ( u[ 1 ] ? ( u[ 1 ] + '&' ) : '' ) + 'cachekiller=' + this.getRandNumbers(); this.proxy.setRequestHeader( 'Method', 'POST ' + u + ' HTTP/1.1' ); this.proxy.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' ); // Send data if( data ) { try { res = this.proxy.send( data ); } catch( err ) { if( self.onload ) { console.log( 'This error could be.' ); self.onload( false, false ); self.destroy(); } } } else if( this.varcount > 0 ) { let out = []; for( let a in this.vars ) out.push( a + '=' + this.vars[ a ] ); new Promise( function( resolve, reject ) { // Will throw an error unless we are forcing http for testing! if( window.Friend && Friend.User && Friend.User.State != 'online' && !self.forceHTTP ) { reject( 'error' ); return; } try { res = self.proxy.send( out.join ( '&' ) ); resolve( 'success' ); } catch( err ) { reject( 'error' ); if( self.onload ) { console.log( 'Error...' ); self.onload( false, false ); self.destroy(); } Friend.User.CheckServerConnection(); } } ).catch( function( err ) { if( err == 'error' ) { if( callback ) { console.log( 'Other error' ); callback( false, false ); } else { //console.log( 'No callback.' ); } } else if( err == 'success' ); { // success } } ); } // All else fails? else { try { res = this.proxy.send ( null ); } catch ( e ) { res = this.proxy.send( NULL ); } } } // Normal GET request else { let u = this.url.split( '?' ); u = u[0] + '?' + ( u[ 1 ] ? ( u[ 1 ] + '&' ) : '' ) + 'cachekiller=' + this.getRandNumbers(); this.proxy.setRequestHeader( 'Method', 'GET ' + u + ' HTTP/1.1' ); try { res = this.proxy.send( null ); } catch ( e ) { res = this.proxy.send( NULL ); } } // New life if( self.life ) clearTimeout( self.life ); self.life = setTimeout( function() { if( self.mode == 'websocket' ) { console.log( 'Destroy on life.' ); self.destroySilent(); } else { self.destroy(); } }, 15000 ); return; } else { //console.log( '[cajax] No openfunc!' ); } // We were not successful! //console.log( '[cajax] Just destroying.' ); this.destroy(); } cAjax.prototype.decreaseProcessCount = function() { if( this.decreasedProcessCount ) return; this.decreasedProcessCount = true; _cajax_process_count--; if( _cajax_process_count == 0 ) { let titleBars = document.getElementsByClassName( 'TitleBar' ); for( let b = 0; b < titleBars.length; b++ ) { titleBars[b].classList.remove( 'Busy' ); } document.body.classList.remove( 'Busy' ); } } cAjax.prototype.handleWebSocketResponse = function( wsdata ) { let self = this; _cajax_ws_connections--; if( self.life ) clearTimeout( self.life ); self.life = setTimeout( function() { //console.log( '[cajax] Defunct ajax object destroying self after five seconds. 2' ); if( self.mode == 'websocket' ) { self.destroySilent(); } else { self.destroy(); } self.life = false; }, 15000 ); if( !self.onload ) { //console.log( '[cajax] No onload.' ); return; } // The data just failed - which means the websocket went away! if( typeof( wsdata ) == 'undefined' ) { //console.log( '[cajax] Got undefined error...' ); if( window.Workspace ) { // Add to queue //console.log( 'We got strange ws data!' ); AddToCajaxQueue( self ); return Friend.User.CheckServerConnection(); } self.destroy(); return; } else if( typeof( wsdata ) == 'object' && wsdata.response ) { //console.log( '[cajax] Got error...' ); self.rawData = 'error'; if( self.proxy ) self.proxy.responseText = self.rawData; //else console.log( 'No more proxy 1..', wsdata, self.onload ); self.returnCode = 'error'; self.destroy(); return false; } if( self.proxy ) self.proxy.responseText = wsdata; //else console.log( 'No more proxy..', wsdata, self.onload ); self.rawData = wsdata; self.wsData = wsdata; if( self.rawData && typeof( self.rawData ) == 'string' && ( self == 'ok' || self == 'fail' || self.rawData.indexOf( '' ) > 0 ) ) { self.hasReturnCode = true; } else { self.hasReturnCode = false; } // Has return code, so separates return code and data if( self.hasReturnCode ) { // With a separator let sep = ''; let sepaIndex = self.rawData.indexOf( sep ); if( sepaIndex > 0 ) { self.returnCode = self.rawData.substr( 0, sepaIndex ); self.returnData = self.rawData.substr( sepaIndex + sep.length ); } // No data else { self.returnCode = self.rawData; self.returnData = false; } } // No return code and perhaps raw data else if( self.rawData ) { self.returnCode = self.rawData; self.returnData = ''; } // This is a fail (no error code..) else { self.returnCode = ''; self.returnData = ''; } // TODO: This error is general if( JSON && typeof( self.returnData ) == 'string' && self.returnData.charAt( 0 ) == '{' ) { try { let t = JSON.parse( self.returnData ); // Deprecate from 1.0 beta 2 "no user!" if( t && ( t.response.toLowerCase() == 'user not found' || t.response.toLowerCase() == 'user session not found' ) ) { if( window.Workspace && t.response.toLowerCase() == 'user session not found' ) { Workspace.flushSession(); console.log( 'KILLED WHO!?' ); } if( Workspace ) { // Add to queue AddToCajaxQueue( self ); console.log( 'KILLED WHO, YOU!?' ); return Friend.User.CheckServerConnection(); } } } catch( e ) { if( !self.rawData ) { //console.log( '[cAjax] Could not understand server response: ', self.returnData ); self.destroy(); return; } else { // console.log( 'Test3: Impossible server response: ', self.returnData ); } } } // Respond to old expired sessions! else if( self.returnCode == 'fail' ) { try { let r = JSON.parse( self.returnData ); if( r.response.toLowerCase() == 'user session not found' ) { if( window.Workspace ) Workspace.flushSession(); AddToCajaxQueue( self ); return Friend.User.CheckServerConnection(); } } catch( e ) { //console.log( 'Test3: Impossible server response: ', self.returnData, self.returnData, wsdata ); } } //simulate HTTP response string with escaped slashes... if( typeof( self.returnData ) == 'string' ) { if( self.returnData.length > 0 && !self.returnCode ) { self.returnCode = 'ok'; } } if( self.onload ) { self.onload( self.returnCode, self.returnData ); } else { //console.log( 'got ws data... but nowhere to send it' ); } self.destroy(); } if( typeof bindSingleParameterMethod != 'function' ) { function bindSingleParameterMethod( toObject, methodName ) { return function(e){ toObject[ methodName ]( e ) } }; } // Clean ajax calls! let currentCajaxType = 0; function CleanAjaxCalls( depth = false ) { // Cycle types let type; do { currentCajaxType++; if( currentCajaxType >= Friend.cajaxTypes.length ) currentCajaxType = 0; type = Friend.cajaxTypes[ currentCajaxType ]; } while( currentCajaxType != 0 && Friend.cajax[ type ].queue.length < 1 ); // Try to execute queue if( typeof( Friend.cajax[ type ] ) != 'undefined' ) { if( Friend.cajax[ type ].queue.length == 0 ) { // Clean it up! _cajax_process_count = 0; let titleBars = document.getElementsByClassName( 'TitleBar' ); for( let b = 0; b < titleBars.length; b++ ) { titleBars[b].classList.remove( 'Busy' ); } document.body.classList.remove( 'Busy' ); return false; } else { Friend.cajax[ type ].queue[ 0 ].send(); return true; } } return false; } /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ /* Design: A Database object will attach itself to System:Devices/Databases/ Here, it should be possible to interact with it using the Workspace or Friend Shell. The object also has a Javascript API which will be available to api.js by proxy - using the same function calls as available in this class. cd System:Devices/Database/MyDatabase/ | Enters database list | Lists tables dir Books/ | Lists records info Books/1908754 | Gets table description infoget Books/1908754 Title | Gets Title column from record delete Books/1908754 | Deletes a book echo "Alice in Wonderland" > Books/1908754 in Title | Updates Title A Database directory has a special flag on it, and allows you to disconnect it with the context menu. You will also be able to connect to a new database the same way. If doing Icon Info using the GUI, you will be able to edit record information. */ friendUP = window.friendUP || {}; friendUP.databases = []; // Create a database object Database = function( options ) { // Set options this.options = {}; for( var a in options ) { switch( a ) { case 'username': case 'password': case 'hostname': case 'key': case 'database': case 'port': this.options[ a ] = options[ a ]; break; case 'type': switch( options[ a ] ) { case 'sqlite': case 'server_mysql': this.options[ a ] = options[ a ]; break; default: break; } break; default: break; } } } // Open a connection to a database Database.prototype.Open = function( callback ) { var self = this; var m = new Module( 'database' ); m.onExecuted = function( e, d ) { try { if( d ) d = JSON.parse( d ); } catch( e ){}; if( e != 'ok' ) { Notify( { title: 'Database connection failed', text: d.message } ); } // On success, register global reference for( var a = 0; a < friendUP.databases.length; a++ ) { if( friendUP.databases[ a ] == self ) { e = 'fail'; break; } } if( e == 'ok' ) { friendUP.databases.push( this ); } if( callback ) callback( e == 'ok' ? d : false ); } m.execute( 'open', { options: this.options } ); } // Find a record in a database /* Definition: { table: 'Books', order: { <- // optional - TODO title: 'DESC', date: 'ASC' }, definition: { id: '> 3', title: 'Alice in Wonderland', date: '>= 28-01-80 13:37:00' }, join: [ <- // optional - TODO { alias: 'a', definition: ... } ] } */ Database.prototype.Find = function( definition, callback ) { var m = new Module( 'database' ); m.onExecuted = function( e, d ) { try { if( d ) d = JSON.parse( d ); } catch( e ){}; if( e != 'ok' ) { Notify( { title: 'Database query failed', text: d.message } ); } if( callback ) callback( e == 'ok' ? d : false ); } m.execute( 'find', { definition: definition, options: this.options } ); } Database.prototype.Update = function( definition, callback ){}; Database.prototype.Delete = function( definition, callback ){}; Database.prototype.CreateTable = function( definition, callback ){}; Database.prototype.DeleteTable = function( definition, callback ){}; Database.prototype.CreateDatabase = function( definition, callback ){}; Database.prototype.DeleteDatabase = function( definition, callback ){}; Database.prototype.SetPermissions = function( definition, callback ){}; // Close a database connection and remove global reference Database.prototype.Close = function() { var o = []; for( var a = 0; a < friendUP.databases.length; a++ ) { if( friendUP.databases[ a ] != this ) o.push( friendUP.databases[ a ] ); } friendUP.databases = o; delete this; } /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ /** @file * * System interface with Friend Network * DOS low-level interface * A set of easy file functions * * @author FL (Francois Lionet) * @date first pushed on 12/04/2018 */ Friend.DOS = Friend.DOS || {}; /** * Friend.DOS.getDisks * * Returns the list of drives currently mounted * * @param options (object) various options * #object * #string #todo filter if defined, filter the names of the drives. MSDOS-like syntax (name*, n?me) * #boolean sort sort the result in alphabetical order * #boolean types array of strings with the type of disks to scan, ex 'Dormant' or 'Door' (case sensitive, exact match on the name) * * @return (array) An array of objects containing information about the disks * #array #object * #string title the name of the disk * #string volume the name of the volume */ Friend.DOS.getDisks = function( options, callback, extra ) { let out = []; for( let a = 0; a < Workspace.icons.length; a++ ) { let disk = Workspace.icons[ a ]; if( disk.Type != 'Door' ) continue; if ( options.type ) { let found = false; for ( let t = 0; t < options.type.length; t++ ) { if ( options.types[ t ] == disk.type ) { found = true; break; } } if ( !found ) continue; } out.push ( { Title: disk.Title, Volume: disk.Volume, Driver: disk.Driver } ); } if ( options.sort ) { out.sort( compare ); } if( callback ) callback( true, out ); return out; // Comparaison function function compare( iconA, iconB ) { if ( iconA.Title < iconB.Title ) return -1; if ( iconA.Title > iconB.Title ) return 1; return 0; } }; /** * Friend.DOS.getDirectory * * Returns the list of files in a directory * * @param path (string) path to the directory to scan * @param options (object) various options * #object * #boolean recursive explores sub-directories * #boolean sort sort the result in alphabetical order * #boolean noDirectories do not list the directories * #boolean noFiles do not list the files * #stringarray #todo fileTypes array of strings with the types of files to list, starting with a dot (example: [ '.txt', '.doc' ]) * #stringarray #todo fileMimes array of strings with the mime types of the files (example: [ 'video/jpeg', 'image/bmp' ] ) * #boolean types array of strings with the type of disks to scan, ex 'Dormant' or 'Door' (case sensitive, exact match on the name) * * @callback * #array * #fileInfo */ Friend.DOS.getDirectory = function( path, options, callback, extra ) { // Remove flags that could interfere with the door let flags = {}; for ( let f in options ) { if ( f == 'recursive' || f == 'sort' ) flags[ f ] = options[ f ]; } // Star recursion let list = []; let depth = 0; getDir( list, path, flags ); // Watchdog for the end of recursion let response = true; let handle = setInterval( function() { if ( depth <= 0 ) { clearInterval( handle ); callback( response, response ? list : [], extra ); } }, 20 ); // Recursive function function getDir( listDir, path, flags ) { depth++; let door = ( new Door().get( path ) ); if( door ) { door.getIcons( null, function( icons, path, pth ) { depth--; // No error? if( icons ) { if( !options.noDirectories ) { // Look for directories let icon; for( let i = 0; i < icons.length; i++ ) { icon = icons[ i ]; if ( icon.Type == 'Directory' ) { if ( icon.Dormant ) icon.Dormant.windows = []; listDir.push( icon ); if ( flags.recursive ) { icon.Children = []; getDir( icon.Children, icon.Path, flags ); } } } // Sort? if( options.sort ) listDir.sort( compare ); } // Look for files let listTemp = []; if( !options.noFiles ) { for( let i = 0; i < icons.length; i++ ) { icon = icons[ i ]; if( icon.Type == 'File' ) { if( icon.Dormant ) icon.Dormant.windows = []; listTemp.push( icon ); } } // Sort? if( options.sort ) listTemp.sort( compare ); } // Adds to the main array if( !options.filesFirst ) { for( i = 0; i < listTemp.length; i++ ) listDir.push( listTemp[ i ] ); } else { for( i = 0; i < listTemp.length; i++ ) listDir.unshift( listTemp[ i ] ); } } else { response = false; } }, flags ); } else { depth--; } } // Comparaison function function compare( iconA, iconB ) { if( iconA.Title && iconB.Title ) { if( iconA.Title < iconB.Title ) return -1; else if( iconA.Title > iconB.Title ) return 1; return 0; } if( iconA.Filename < iconB.Filename ) return -1; else if( iconA.Filename > iconB.Filename ) return 1; return 0; } }; /** * Friend.DOS.executeJSX * * launches a JSX application * * @param path (string) path to the JSX file * @param options (object) various options * #object * #array #string args arguments to transmit to the function * * @callback * #object * #iFrame iFrame */ Friend.DOS.executeJSX = function( path, options, callback, extra ) { ExecuteJSXByPath( path, options.args, function( response, message, iframe ) { if ( callback ) { callback( response, { iFrame: iframe }, extra ); } } ); }; /** * Friend.DOS.getServerPath * * Converts a symbolic path to actual path to the Friend server * * @param applicationId (string) identifier of the application * @param path (string) path to convert * @param options (object) various options * #object * #array #string args arguments to transmit to the function * * @return * #string the converted path */ Friend.DOS.getServerPath = function( applicationId, path, options ) { // Get the application path let aPath; for( let a = 0; a < Workspace.applications.length; a++ ) { if( Workspace.applications[ a ].applicationId == applicationId ) { application = Workspace.applications[ a ]; aPath = application.appPath ? application.appPath : application.filePath; break; } } if( path.toLowerCase().substr( 0, 8 ) == 'progdir:' ) { path = aPath + path.substr( 8, path.length - 8 ); } else if( path.toLowerCase().substr( 0, 7 ) == 'system:' ) { path = path.split( /system\:/i ).join( '/webclient/' ); } else if( path.toLowerCase().substr( 0, 5 ) == 'libs:' ) { path = path.split( /libs\:/i ).join( '/webclient/' ); } return path; //if( path.indexOf( 'http:' ) == 0 || path.indexOf( 'https:' ) == 0 ) //{ // return path; //} }; /** * Friend.DOS.isFriendNetworkDrive * * Checks if a path points to the Friend Network drive * * @param path (string) path to convert * @param options (object) various options * * @return * #boolean true if it is, false if it not */ Friend.DOS.isFriendNetworkDrive = function( path, options ) { // Is the path on a Friend Network drive? let drive; let pos = path.indexOf( ':' ); if ( pos >= 0 ) drive = path.substring( 0, pos + 1 ); if ( drive ) { let friendNetwork = false; let doors = DormantMaster.getDoors(); if( doors ) { for( let d in doors ) { let door = doors[ d ]; if( door.Title == drive ) { return true; } } } } return false; }; /** * Friend.DOS.loadHTML * * Load an HTML file and relocates it so that it can be displayed in an iFrame * * @param applicationId (string) identifier of the application * #string * @param path (string) path to the HTML file * #string * @param options (object) various options * #object * * @callback * #string the relocated file */ Friend.DOS.loadHTML = function( applicationId, path, options, callback, extra ) { // Load the file this.loadFile( path, {}, function( response, data, extra ) { if ( response ) { let isFriendNetwork = Friend.DOS.isFriendNetworkDrive( path ); if ( isFriendNetwork ) { let drive = path.substring( 0, path.indexOf( ':' ) + 1 ); // Relocates the file FriendNetworkDoor.relocateHTML( html, drive, '', '', function( response, html ) { callback( true, html, extra ); } ); } else { callback( true, html, extra ); } } }, extra ); }; /** * Friend.DOS.loadFile * * Load an HTML file and relocates it so that it can be displayed in an iFrame * * @param path (string) path to the file * #string * @param options (object) various options * #object * #boolean binary indicates that the file should be loaded as binary * * @callback * #string #or #arrayBuffer * if not binary, a string containing the content of the file, * if binary, an arrayBuffer with the file */ Friend.DOS.loadFile = function( path, options, callback, extra ) { let file = new File( path ); file.onLoad = function( data ) { // Check for error if ( typeof data == 'string' && data.indexOf( '404 - File not found!') >= 0 ) { callback( false, { error: 'ERROR - File not found.' }, extra ); } else { // OK! callback( true, data, extra ); } }; let mode = ''; if ( options && options.binary ) mode = 'rb'; file.load( mode ); }; /** * Friend.DOS.getDriveInfo * * Returns information about the disk pointed to by a path * * @param path (string) path to the file * #string * @param options (object) various options * #object * #boolean binary indicates that the file should be loaded as binary * * @return * #fileInfo */ Friend.DOS.getDriveInfo = function( path, options, callback, extra ) { // Just a name? Must be a drive.. Add the colon.. if( path.indexOf( ':' ) < 0 && path.indexOf( '/' ) < 0 ) { path += ':'; } let icon = false; for( let a = 0; a < Workspace.icons.length; a++ ) { if( Workspace.icons[a].Volume == path ) { icon = Workspace.icons[ a ]; break; } } if( callback ) { callback( icon ? icon : Friend.ERROR ); } return ( icon ? icon : Friend.ERROR ); }; /** * Friend.DOS.getFileAccess * * Returns the file access information of a file or directory pointed to by a path * * @param path (string) path to the file * #string * @param options (object) various options * #object * #boolean binary indicates that the file should be loaded as binary * * @return * #fileInfo */ Friend.DOS.getFileAccess = function( path, options, callback, extra ) { let sn = new Library( 'system.library' ); sn.onExecuted = function( returnCode, returnData ) { // If we got an OK result, then parse the return data (json data) let rd = false; if( returnCode == 'ok' ) { rd = JSON.parse( returnData ); callback( true, rd, extra ); } else { // Default permissions. HOGNE: not normal, it always returns not ok rd = [ { access: '-rwed', type: 'user' }, { access: '-rwed', type: 'group' }, { access: '-rwed', type: 'others' } ]; callback( true, rd, extra ); } }; sn.execute( 'file/access', { path: path } ); }; /** * Friend.DOS.getFileInfo * * Returns the file access information of a file or directory pointed to by a path * * @param path (string) path to the file * #string * @param options (object) various options * #object * * @return * #fileInfo */ Friend.DOS.getFileInfo = function( path, options, callback, extra ) { // FRANCOIS: TODO! handle Dormant drives! let l = new Library( 'system.library' ); l.onExecuted = function( e, d ) { if ( e == 'ok' ) { let fileinfo; try { fileinfo = JSON.parse( d ); } catch( e ) { callback( false, 'ERROR - Bad response from server.', extra ); return; } callback( true, fileinfo, extra ); } else { callback( false, 'ERROR - File not found.', extra ); } }; l.execute( 'file/info', { path: path } ); }; Friend.DOS.getServerURL = function( path, options, callback, extra ) { let gpath = getImageUrl( path ); callback( true, gpath, extra ); return gpath; }; // Opens a window based on filepath (used for opening files hosted external) Friend.DOS.openWindowByFilename = function( fileInfo, ext, appId = false ) { if( typeof( fileInfo ) === "string" ) { if( !ext ) { ext = fileInfo.split( '.' ); ext = ext[ext.length-1]; } fileInfo = { Extension : ext, Path : fileInfo }; } else { if( !ext ) { ext = fileInfo.Path ? fileInfo.Path.split( '.' ) : ( fileInfo.Filename ? fileInfo.Filename.split( '.' ) : ( fileInfo.Title ? fileInfo.Title.split( '.' ) : false ) ); if( ext == false ) { // Support url instead if( fileInfo.Url ) { return OpenWindowByUrl( fileInfo.Url, fileInfo ); } return false; } ext = ext[ext.length-1]; } } fileInfo = { Title : ( fileInfo.Title ? fileInfo.Title : '' ), Filename : ( fileInfo.Filename ? fileInfo.Filename : '' ), DateCreated : ( fileInfo.DateCreated ? fileInfo.DateCreated : 0 ), DateModified : ( fileInfo.DateModified ? fileInfo.DateModified : 0 ), Extension : ( fileInfo.Extension ? fileInfo.Extension : ext ), Filesize : ( fileInfo.Filesize ? fileInfo.Filesize : 0 ), MetaType : ( fileInfo.MetaType ? fileInfo.MetaType : 'File' ), Path : ( fileInfo.Path ? fileInfo.Path : '' ), Type : ( fileInfo.Type ? fileInfo.Type : 'File' ), downloadhref : ( fileInfo.downloadhref ? fileInfo.downloadhref : '' ), flags : ( fileInfo.flags ? fileInfo.flags : null ), applicationId: appId }; return OpenWindowByFileinfo( fileInfo ); } /******************************************************************************* * * * @dependencies included * * * * cryptojs/components/aes.js * * cryptojs/components/pbkdf2.js * * jsencrypt.js * * base64.js * * hash.js * * jsbn.js * * random.js * * rsa.js * * jscrypto.js * * * *******************************************************************************/ var cryptodeps = window.cryptodeps || {}; (function( ns, undefined ) { /*ns.CryptoJS = {};*/ // --- aes.js --------------------------------------------------------------------------------------------------- // /* CryptoJS v3.1.2 code.google.com/p/crypto-js (c) 2009-2013 by Jeff Mott. All rights reserved. code.google.com/p/crypto-js/wiki/License */ (function ( CryptoJS ) { return; // Shortcuts var C = CryptoJS; var C_lib = C.lib; var BlockCipher = C_lib.BlockCipher; var C_algo = C.algo; // Lookup tables var SBOX = []; var INV_SBOX = []; var SUB_MIX_0 = []; var SUB_MIX_1 = []; var SUB_MIX_2 = []; var SUB_MIX_3 = []; var INV_SUB_MIX_0 = []; var INV_SUB_MIX_1 = []; var INV_SUB_MIX_2 = []; var INV_SUB_MIX_3 = []; // Compute lookup tables (function () { // Compute double table var d = []; for (var i = 0; i < 256; i++) { if (i < 128) { d[i] = i << 1; } else { d[i] = (i << 1) ^ 0x11b; } } // Walk GF(2^8) var x = 0; var xi = 0; for (var i = 0; i < 256; i++) { // Compute sbox var sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4); sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63; SBOX[x] = sx; INV_SBOX[sx] = x; // Compute multiplication var x2 = d[x]; var x4 = d[x2]; var x8 = d[x4]; // Compute sub bytes, mix columns tables var t = (d[sx] * 0x101) ^ (sx * 0x1010100); SUB_MIX_0[x] = (t << 24) | (t >>> 8); SUB_MIX_1[x] = (t << 16) | (t >>> 16); SUB_MIX_2[x] = (t << 8) | (t >>> 24); SUB_MIX_3[x] = t; // Compute inv sub bytes, inv mix columns tables var t = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100); INV_SUB_MIX_0[sx] = (t << 24) | (t >>> 8); INV_SUB_MIX_1[sx] = (t << 16) | (t >>> 16); INV_SUB_MIX_2[sx] = (t << 8) | (t >>> 24); INV_SUB_MIX_3[sx] = t; // Compute next counter if (!x) { x = xi = 1; } else { x = x2 ^ d[d[d[x8 ^ x2]]]; xi ^= d[d[xi]]; } } }()); // Precomputed Rcon lookup var RCON = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; /** * AES block cipher algorithm. */ var AES = C_algo.AES = BlockCipher.extend({ _doReset: function () { // Shortcuts var key = this._key; var keyWords = key.words; var keySize = key.sigBytes / 4; // Compute number of rounds var nRounds = this._nRounds = keySize + 6 // Compute number of key schedule rows var ksRows = (nRounds + 1) * 4; // Compute key schedule var keySchedule = this._keySchedule = []; for (var ksRow = 0; ksRow < ksRows; ksRow++) { if (ksRow < keySize) { keySchedule[ksRow] = keyWords[ksRow]; } else { var t = keySchedule[ksRow - 1]; if (!(ksRow % keySize)) { // Rot word t = (t << 8) | (t >>> 24); // Sub word t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; // Mix Rcon t ^= RCON[(ksRow / keySize) | 0] << 24; } else if (keySize > 6 && ksRow % keySize == 4) { // Sub word t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; } keySchedule[ksRow] = keySchedule[ksRow - keySize] ^ t; } } // Compute inv key schedule var invKeySchedule = this._invKeySchedule = []; for (var invKsRow = 0; invKsRow < ksRows; invKsRow++) { var ksRow = ksRows - invKsRow; if (invKsRow % 4) { var t = keySchedule[ksRow]; } else { var t = keySchedule[ksRow - 4]; } if (invKsRow < 4 || ksRow <= 4) { invKeySchedule[invKsRow] = t; } else { invKeySchedule[invKsRow] = INV_SUB_MIX_0[SBOX[t >>> 24]] ^ INV_SUB_MIX_1[SBOX[(t >>> 16) & 0xff]] ^ INV_SUB_MIX_2[SBOX[(t >>> 8) & 0xff]] ^ INV_SUB_MIX_3[SBOX[t & 0xff]]; } } }, encryptBlock: function (M, offset) { this._doCryptBlock(M, offset, this._keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX); }, decryptBlock: function (M, offset) { // Swap 2nd and 4th rows var t = M[offset + 1]; M[offset + 1] = M[offset + 3]; M[offset + 3] = t; this._doCryptBlock(M, offset, this._invKeySchedule, INV_SUB_MIX_0, INV_SUB_MIX_1, INV_SUB_MIX_2, INV_SUB_MIX_3, INV_SBOX); // Inv swap 2nd and 4th rows var t = M[offset + 1]; M[offset + 1] = M[offset + 3]; M[offset + 3] = t; }, _doCryptBlock: function (M, offset, keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX) { // Shortcut var nRounds = this._nRounds; // Get input, add round key var s0 = M[offset] ^ keySchedule[0]; var s1 = M[offset + 1] ^ keySchedule[1]; var s2 = M[offset + 2] ^ keySchedule[2]; var s3 = M[offset + 3] ^ keySchedule[3]; // Key schedule row counter var ksRow = 4; // Rounds for (var round = 1; round < nRounds; round++) { // Shift rows, sub bytes, mix columns, add round key var t0 = SUB_MIX_0[s0 >>> 24] ^ SUB_MIX_1[(s1 >>> 16) & 0xff] ^ SUB_MIX_2[(s2 >>> 8) & 0xff] ^ SUB_MIX_3[s3 & 0xff] ^ keySchedule[ksRow++]; var t1 = SUB_MIX_0[s1 >>> 24] ^ SUB_MIX_1[(s2 >>> 16) & 0xff] ^ SUB_MIX_2[(s3 >>> 8) & 0xff] ^ SUB_MIX_3[s0 & 0xff] ^ keySchedule[ksRow++]; var t2 = SUB_MIX_0[s2 >>> 24] ^ SUB_MIX_1[(s3 >>> 16) & 0xff] ^ SUB_MIX_2[(s0 >>> 8) & 0xff] ^ SUB_MIX_3[s1 & 0xff] ^ keySchedule[ksRow++]; var t3 = SUB_MIX_0[s3 >>> 24] ^ SUB_MIX_1[(s0 >>> 16) & 0xff] ^ SUB_MIX_2[(s1 >>> 8) & 0xff] ^ SUB_MIX_3[s2 & 0xff] ^ keySchedule[ksRow++]; // Update state s0 = t0; s1 = t1; s2 = t2; s3 = t3; } // Shift rows, sub bytes, add round key var t0 = ((SBOX[s0 >>> 24] << 24) | (SBOX[(s1 >>> 16) & 0xff] << 16) | (SBOX[(s2 >>> 8) & 0xff] << 8) | SBOX[s3 & 0xff]) ^ keySchedule[ksRow++]; var t1 = ((SBOX[s1 >>> 24] << 24) | (SBOX[(s2 >>> 16) & 0xff] << 16) | (SBOX[(s3 >>> 8) & 0xff] << 8) | SBOX[s0 & 0xff]) ^ keySchedule[ksRow++]; var t2 = ((SBOX[s2 >>> 24] << 24) | (SBOX[(s3 >>> 16) & 0xff] << 16) | (SBOX[(s0 >>> 8) & 0xff] << 8) | SBOX[s1 & 0xff]) ^ keySchedule[ksRow++]; var t3 = ((SBOX[s3 >>> 24] << 24) | (SBOX[(s0 >>> 16) & 0xff] << 16) | (SBOX[(s1 >>> 8) & 0xff] << 8) | SBOX[s2 & 0xff]) ^ keySchedule[ksRow++]; // Set output M[offset] = t0; M[offset + 1] = t1; M[offset + 2] = t2; M[offset + 3] = t3; }, keySize: 256/32 }); /** * Shortcut functions to the cipher's object interface. * * @example * * var ciphertext = CryptoJS.AES.encrypt(message, key, cfg); * var plaintext = CryptoJS.AES.decrypt(ciphertext, key, cfg); */ C.AES = BlockCipher._createHelper(AES); }( /*ns.CryptoJS*/undefined )); // TODO: Unmess this mess under ... /* CryptoJS v3.1.2 code.google.com/p/crypto-js (c) 2009-2013 by Jeff Mott. All rights reserved. code.google.com/p/crypto-js/wiki/License */ var CryptoJS=CryptoJS||function(u,p){var d={},l=d.lib={},s=function(){},t=l.Base={extend:function(a){s.prototype=this;var c=new s;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, r=l.WordArray=t.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=p?c:4*a.length},toString:function(a){return(a||v).stringify(this)},concat:function(a){var c=this.words,e=a.words,j=this.sigBytes;a=a.sigBytes;this.clamp();if(j%4)for(var k=0;k>>2]|=(e[k>>>2]>>>24-8*(k%4)&255)<<24-8*((j+k)%4);else if(65535>>2]=e[k>>>2];else c.push.apply(c,e);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< 32-8*(c%4);a.length=u.ceil(c/4)},clone:function(){var a=t.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],e=0;e>>2]>>>24-8*(j%4)&255;e.push((k>>>4).toString(16));e.push((k&15).toString(16))}return e.join("")},parse:function(a){for(var c=a.length,e=[],j=0;j>>3]|=parseInt(a.substr(j, 2),16)<<24-4*(j%8);return new r.init(e,c/2)}},b=w.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var e=[],j=0;j>>2]>>>24-8*(j%4)&255));return e.join("")},parse:function(a){for(var c=a.length,e=[],j=0;j>>2]|=(a.charCodeAt(j)&255)<<24-8*(j%4);return new r.init(e,c)}},x=w.Utf8={stringify:function(a){try{return decodeURIComponent(escape(b.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return b.parse(unescape(encodeURIComponent(a)))}}, q=l.BufferedBlockAlgorithm=t.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=x.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,e=c.words,j=c.sigBytes,k=this.blockSize,b=j/(4*k),b=a?u.ceil(b):u.max((b|0)-this._minBufferSize,0);a=b*k;j=u.min(4*a,j);if(a){for(var q=0;q>>2]>>>24-8*(r%4)&255)<<16|(l[r+1>>>2]>>>24-8*((r+1)%4)&255)<<8|l[r+2>>>2]>>>24-8*((r+2)%4)&255,v=0;4>v&&r+0.75*v>>6*(3-v)&63));if(l=t.charAt(64))for(;d.length%4;)d.push(l);return d.join("")},parse:function(d){var l=d.length,s=this._map,t=s.charAt(64);t&&(t=d.indexOf(t),-1!=t&&(l=t));for(var t=[],r=0,w=0;w< l;w++)if(w%4){var v=s.indexOf(d.charAt(w-1))<<2*(w%4),b=s.indexOf(d.charAt(w))>>>6-2*(w%4);t[r>>>2]|=(v|b)<<24-8*(r%4);r++}return p.create(t,r)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})(); (function(u){function p(b,n,a,c,e,j,k){b=b+(n&a|~n&c)+e+k;return(b<>>32-j)+n}function d(b,n,a,c,e,j,k){b=b+(n&c|a&~c)+e+k;return(b<>>32-j)+n}function l(b,n,a,c,e,j,k){b=b+(n^a^c)+e+k;return(b<>>32-j)+n}function s(b,n,a,c,e,j,k){b=b+(a^(n|~c))+e+k;return(b<>>32-j)+n}for(var t=CryptoJS,r=t.lib,w=r.WordArray,v=r.Hasher,r=t.algo,b=[],x=0;64>x;x++)b[x]=4294967296*u.abs(u.sin(x+1))|0;r=r.MD5=v.extend({_doReset:function(){this._hash=new w.init([1732584193,4023233417,2562383102,271733878])}, _doProcessBlock:function(q,n){for(var a=0;16>a;a++){var c=n+a,e=q[c];q[c]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360}var a=this._hash.words,c=q[n+0],e=q[n+1],j=q[n+2],k=q[n+3],z=q[n+4],r=q[n+5],t=q[n+6],w=q[n+7],v=q[n+8],A=q[n+9],B=q[n+10],C=q[n+11],u=q[n+12],D=q[n+13],E=q[n+14],x=q[n+15],f=a[0],m=a[1],g=a[2],h=a[3],f=p(f,m,g,h,c,7,b[0]),h=p(h,f,m,g,e,12,b[1]),g=p(g,h,f,m,j,17,b[2]),m=p(m,g,h,f,k,22,b[3]),f=p(f,m,g,h,z,7,b[4]),h=p(h,f,m,g,r,12,b[5]),g=p(g,h,f,m,t,17,b[6]),m=p(m,g,h,f,w,22,b[7]), f=p(f,m,g,h,v,7,b[8]),h=p(h,f,m,g,A,12,b[9]),g=p(g,h,f,m,B,17,b[10]),m=p(m,g,h,f,C,22,b[11]),f=p(f,m,g,h,u,7,b[12]),h=p(h,f,m,g,D,12,b[13]),g=p(g,h,f,m,E,17,b[14]),m=p(m,g,h,f,x,22,b[15]),f=d(f,m,g,h,e,5,b[16]),h=d(h,f,m,g,t,9,b[17]),g=d(g,h,f,m,C,14,b[18]),m=d(m,g,h,f,c,20,b[19]),f=d(f,m,g,h,r,5,b[20]),h=d(h,f,m,g,B,9,b[21]),g=d(g,h,f,m,x,14,b[22]),m=d(m,g,h,f,z,20,b[23]),f=d(f,m,g,h,A,5,b[24]),h=d(h,f,m,g,E,9,b[25]),g=d(g,h,f,m,k,14,b[26]),m=d(m,g,h,f,v,20,b[27]),f=d(f,m,g,h,D,5,b[28]),h=d(h,f, m,g,j,9,b[29]),g=d(g,h,f,m,w,14,b[30]),m=d(m,g,h,f,u,20,b[31]),f=l(f,m,g,h,r,4,b[32]),h=l(h,f,m,g,v,11,b[33]),g=l(g,h,f,m,C,16,b[34]),m=l(m,g,h,f,E,23,b[35]),f=l(f,m,g,h,e,4,b[36]),h=l(h,f,m,g,z,11,b[37]),g=l(g,h,f,m,w,16,b[38]),m=l(m,g,h,f,B,23,b[39]),f=l(f,m,g,h,D,4,b[40]),h=l(h,f,m,g,c,11,b[41]),g=l(g,h,f,m,k,16,b[42]),m=l(m,g,h,f,t,23,b[43]),f=l(f,m,g,h,A,4,b[44]),h=l(h,f,m,g,u,11,b[45]),g=l(g,h,f,m,x,16,b[46]),m=l(m,g,h,f,j,23,b[47]),f=s(f,m,g,h,c,6,b[48]),h=s(h,f,m,g,w,10,b[49]),g=s(g,h,f,m, E,15,b[50]),m=s(m,g,h,f,r,21,b[51]),f=s(f,m,g,h,u,6,b[52]),h=s(h,f,m,g,k,10,b[53]),g=s(g,h,f,m,B,15,b[54]),m=s(m,g,h,f,e,21,b[55]),f=s(f,m,g,h,v,6,b[56]),h=s(h,f,m,g,x,10,b[57]),g=s(g,h,f,m,t,15,b[58]),m=s(m,g,h,f,D,21,b[59]),f=s(f,m,g,h,z,6,b[60]),h=s(h,f,m,g,C,10,b[61]),g=s(g,h,f,m,j,15,b[62]),m=s(m,g,h,f,A,21,b[63]);a[0]=a[0]+f|0;a[1]=a[1]+m|0;a[2]=a[2]+g|0;a[3]=a[3]+h|0},_doFinalize:function(){var b=this._data,n=b.words,a=8*this._nDataBytes,c=8*b.sigBytes;n[c>>>5]|=128<<24-c%32;var e=u.floor(a/ 4294967296);n[(c+64>>>9<<4)+15]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360;n[(c+64>>>9<<4)+14]=(a<<8|a>>>24)&16711935|(a<<24|a>>>8)&4278255360;b.sigBytes=4*(n.length+1);this._process();b=this._hash;n=b.words;for(a=0;4>a;a++)c=n[a],n[a]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360;return b},clone:function(){var b=v.clone.call(this);b._hash=this._hash.clone();return b}});t.MD5=v._createHelper(r);t.HmacMD5=v._createHmacHelper(r)})(Math); (function(){var u=CryptoJS,p=u.lib,d=p.Base,l=p.WordArray,p=u.algo,s=p.EvpKDF=d.extend({cfg:d.extend({keySize:4,hasher:p.MD5,iterations:1}),init:function(d){this.cfg=this.cfg.extend(d)},compute:function(d,r){for(var p=this.cfg,s=p.hasher.create(),b=l.create(),u=b.words,q=p.keySize,p=p.iterations;u.length>>2]&255}};d.BlockCipher=v.extend({cfg:v.cfg.extend({mode:b,padding:q}),reset:function(){v.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a, this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b=this._process(!0),a.unpad(b);return b},blockSize:4});var n=d.CipherParams=l.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),b=(p.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?s.create([1398893684, 1701076831]).concat(a).concat(b):b).toString(r)},parse:function(a){a=r.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=s.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return n.create({ciphertext:a,salt:c})}},a=d.SerializableCipher=l.extend({cfg:l.extend({format:b}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var l=a.createEncryptor(c,d);b=l.finalize(b);l=l.cfg;return n.create({ciphertext:b,key:c,iv:l.iv,algorithm:a,mode:l.mode,padding:l.padding,blockSize:a.blockSize,formatter:d.format})}, decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return"string"==typeof a?b.parse(a,this):a}}),p=(p.kdf={}).OpenSSL={execute:function(a,b,c,d){d||(d=s.random(8));a=w.create({keySize:b+c}).compute(a,d);c=s.create(a.words.slice(b),4*c);a.sigBytes=4*b;return n.create({key:a,iv:c,salt:d})}},c=d.PasswordBasedCipher=a.extend({cfg:a.cfg.extend({kdf:p}),encrypt:function(b,c,d,l){l=this.cfg.extend(l);d=l.kdf.execute(d, b.keySize,b.ivSize);l.iv=d.iv;b=a.encrypt.call(this,b,c,d.key,l);b.mixIn(d);return b},decrypt:function(b,c,d,l){l=this.cfg.extend(l);c=this._parse(c,l.format);d=l.kdf.execute(d,b.keySize,b.ivSize,c.salt);l.iv=d.iv;return a.decrypt.call(this,b,c,d.key,l)}})}(); (function(){for(var u=CryptoJS,p=u.lib.BlockCipher,d=u.algo,l=[],s=[],t=[],r=[],w=[],v=[],b=[],x=[],q=[],n=[],a=[],c=0;256>c;c++)a[c]=128>c?c<<1:c<<1^283;for(var e=0,j=0,c=0;256>c;c++){var k=j^j<<1^j<<2^j<<3^j<<4,k=k>>>8^k&255^99;l[e]=k;s[k]=e;var z=a[e],F=a[z],G=a[F],y=257*a[k]^16843008*k;t[e]=y<<24|y>>>8;r[e]=y<<16|y>>>16;w[e]=y<<8|y>>>24;v[e]=y;y=16843009*G^65537*F^257*z^16843008*e;b[k]=y<<24|y>>>8;x[k]=y<<16|y>>>16;q[k]=y<<8|y>>>24;n[k]=y;e?(e=z^a[a[a[G^z]]],j^=a[a[j]]):e=j=1}var H=[0,1,2,4,8, 16,32,64,128,27,54],d=d.AES=p.extend({_doReset:function(){for(var a=this._key,c=a.words,d=a.sigBytes/4,a=4*((this._nRounds=d+6)+1),e=this._keySchedule=[],j=0;j>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255]):(k=k<<8|k>>>24,k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255],k^=H[j/d|0]<<24);e[j]=e[j-d]^k}c=this._invKeySchedule=[];for(d=0;dd||4>=j?k:b[l[k>>>24]]^x[l[k>>>16&255]]^q[l[k>>> 8&255]]^n[l[k&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,t,r,w,v,l)},decryptBlock:function(a,c){var d=a[c+1];a[c+1]=a[c+3];a[c+3]=d;this._doCryptBlock(a,c,this._invKeySchedule,b,x,q,n,s);d=a[c+1];a[c+1]=a[c+3];a[c+3]=d},_doCryptBlock:function(a,b,c,d,e,j,l,f){for(var m=this._nRounds,g=a[b]^c[0],h=a[b+1]^c[1],k=a[b+2]^c[2],n=a[b+3]^c[3],p=4,r=1;r>>24]^e[h>>>16&255]^j[k>>>8&255]^l[n&255]^c[p++],s=d[h>>>24]^e[k>>>16&255]^j[n>>>8&255]^l[g&255]^c[p++],t= d[k>>>24]^e[n>>>16&255]^j[g>>>8&255]^l[h&255]^c[p++],n=d[n>>>24]^e[g>>>16&255]^j[h>>>8&255]^l[k&255]^c[p++],g=q,h=s,k=t;q=(f[g>>>24]<<24|f[h>>>16&255]<<16|f[k>>>8&255]<<8|f[n&255])^c[p++];s=(f[h>>>24]<<24|f[k>>>16&255]<<16|f[n>>>8&255]<<8|f[g&255])^c[p++];t=(f[k>>>24]<<24|f[n>>>16&255]<<16|f[g>>>8&255]<<8|f[h&255])^c[p++];n=(f[n>>>24]<<24|f[g>>>16&255]<<16|f[h>>>8&255]<<8|f[k&255])^c[p++];a[b]=q;a[b+1]=s;a[b+2]=t;a[b+3]=n},keySize:8});u.AES=p._createHelper(d)})(); // --- pbkdf2.js ------------------------------------------------------------------------------------------------ // /* CryptoJS v3.1.2 code.google.com/p/crypto-js (c) 2009-2013 by Jeff Mott. All rights reserved. code.google.com/p/crypto-js/wiki/License */ (function ( CryptoJS ) { return; // Shortcuts var C = CryptoJS; var C_lib = C.lib; var Base = C_lib.Base; var WordArray = C_lib.WordArray; var C_algo = C.algo; var SHA1 = C_algo.SHA1; var HMAC = C_algo.HMAC; /** * Password-Based Key Derivation Function 2 algorithm. */ var PBKDF2 = C_algo.PBKDF2 = Base.extend({ /** * Configuration options. * * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) * @property {Hasher} hasher The hasher to use. Default: SHA1 * @property {number} iterations The number of iterations to perform. Default: 1 */ cfg: Base.extend({ keySize: 128/32, hasher: SHA1, iterations: 1 }), /** * Initializes a newly created key derivation function. * * @param {Object} cfg (Optional) The configuration options to use for the derivation. * * @example * * var kdf = CryptoJS.algo.PBKDF2.create(); * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8 }); * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8, iterations: 1000 }); */ init: function (cfg) { this.cfg = this.cfg.extend(cfg); }, /** * Computes the Password-Based Key Derivation Function 2. * * @param {WordArray|string} password The password. * @param {WordArray|string} salt A salt. * * @return {WordArray} The derived key. * * @example * * var key = kdf.compute(password, salt); */ compute: function (password, salt) { // Shortcut var cfg = this.cfg; // Init HMAC var hmac = HMAC.create(cfg.hasher, password); // Initial values var derivedKey = WordArray.create(); var blockIndex = WordArray.create([0x00000001]); // Shortcuts var derivedKeyWords = derivedKey.words; var blockIndexWords = blockIndex.words; var keySize = cfg.keySize; var iterations = cfg.iterations; // Generate key while (derivedKeyWords.length < keySize) { var block = hmac.update(salt).finalize(blockIndex); hmac.reset(); // Shortcuts var blockWords = block.words; var blockWordsLength = blockWords.length; // Iterations var intermediate = block; for (var i = 1; i < iterations; i++) { intermediate = hmac.finalize(intermediate); hmac.reset(); // Shortcut var intermediateWords = intermediate.words; // XOR intermediate with block for (var j = 0; j < blockWordsLength; j++) { blockWords[j] ^= intermediateWords[j]; } } derivedKey.concat(block); blockIndexWords[0]++; } derivedKey.sigBytes = keySize * 4; return derivedKey; } }); /** * Computes the Password-Based Key Derivation Function 2. * * @param {WordArray|string} password The password. * @param {WordArray|string} salt A salt. * @param {Object} cfg (Optional) The configuration options to use for this computation. * * @return {WordArray} The derived key. * * @static * * @example * * var key = CryptoJS.PBKDF2(password, salt); * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8 }); * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8, iterations: 1000 }); */ C.PBKDF2 = function (password, salt, cfg) { return PBKDF2.create(cfg).compute(password, salt); }; }( /*ns.CryptoJS*/undefined )); // TODO: Unmess this mess under ... /* CryptoJS v3.1.2 code.google.com/p/crypto-js (c) 2009-2013 by Jeff Mott. All rights reserved. code.google.com/p/crypto-js/wiki/License */ var CryptoJS=CryptoJS||function(g,j){var e={},d=e.lib={},m=function(){},n=d.Base={extend:function(a){m.prototype=this;var c=new m;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, q=d.WordArray=n.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=j?c:4*a.length},toString:function(a){return(a||l).stringify(this)},concat:function(a){var c=this.words,p=a.words,f=this.sigBytes;a=a.sigBytes;this.clamp();if(f%4)for(var b=0;b>>2]|=(p[b>>>2]>>>24-8*(b%4)&255)<<24-8*((f+b)%4);else if(65535>>2]=p[b>>>2];else c.push.apply(c,p);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< 32-8*(c%4);a.length=g.ceil(c/4)},clone:function(){var a=n.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],b=0;b>>2]>>>24-8*(f%4)&255;b.push((d>>>4).toString(16));b.push((d&15).toString(16))}return b.join("")},parse:function(a){for(var c=a.length,b=[],f=0;f>>3]|=parseInt(a.substr(f, 2),16)<<24-4*(f%8);return new q.init(b,c/2)}},k=b.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var b=[],f=0;f>>2]>>>24-8*(f%4)&255));return b.join("")},parse:function(a){for(var c=a.length,b=[],f=0;f>>2]|=(a.charCodeAt(f)&255)<<24-8*(f%4);return new q.init(b,c)}},h=b.Utf8={stringify:function(a){try{return decodeURIComponent(escape(k.stringify(a)))}catch(b){throw Error("Malformed UTF-8 data");}},parse:function(a){return k.parse(unescape(encodeURIComponent(a)))}}, u=d.BufferedBlockAlgorithm=n.extend({reset:function(){this._data=new q.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=h.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var b=this._data,d=b.words,f=b.sigBytes,l=this.blockSize,e=f/(4*l),e=a?g.ceil(e):g.max((e|0)-this._minBufferSize,0);a=e*l;f=g.min(4*a,f);if(a){for(var h=0;ha;a++){if(16>a)m[a]=d[e+a]|0;else{var c=m[a-3]^m[a-8]^m[a-14]^m[a-16];m[a]=c<<1|c>>>31}c=(l<<5|l>>>27)+j+m[a];c=20>a?c+((k&h|~k&g)+1518500249):40>a?c+((k^h^g)+1859775393):60>a?c+((k&h|k&g|h&g)-1894007588):c+((k^h^ g)-899497514);j=g;g=h;h=k<<30|k>>>2;k=l;l=c}b[0]=b[0]+l|0;b[1]=b[1]+k|0;b[2]=b[2]+h|0;b[3]=b[3]+g|0;b[4]=b[4]+j|0},_doFinalize:function(){var d=this._data,e=d.words,b=8*this._nDataBytes,l=8*d.sigBytes;e[l>>>5]|=128<<24-l%32;e[(l+64>>>9<<4)+14]=Math.floor(b/4294967296);e[(l+64>>>9<<4)+15]=b;d.sigBytes=4*e.length;this._process();return this._hash},clone:function(){var e=d.clone.call(this);e._hash=this._hash.clone();return e}});g.SHA1=d._createHelper(j);g.HmacSHA1=d._createHmacHelper(j)})(); (function(){var g=CryptoJS,j=g.enc.Utf8;g.algo.HMAC=g.lib.Base.extend({init:function(e,d){e=this._hasher=new e.init;"string"==typeof d&&(d=j.parse(d));var g=e.blockSize,n=4*g;d.sigBytes>n&&(d=e.finalize(d));d.clamp();for(var q=this._oKey=d.clone(),b=this._iKey=d.clone(),l=q.words,k=b.words,h=0;h= 0) { var v = x*this[i++]+w[j]+c; c = Math.floor(v/0x4000000); w[j++] = v&0x3ffffff; } return c; } // am2 avoids a big mult-and-extract completely. // Max digit bits should be <= 30 because we do bitwise ops // on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) function am2(i,x,w,j,c,n) { var xl = x&0x7fff, xh = x>>15; while(--n >= 0) { var l = this[i]&0x7fff; var h = this[i++]>>15; var m = xh*l+h*xl; l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff); c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); w[j++] = l&0x3fffffff; } return c; } // Alternately, set max digit bits to 28 since some // browsers slow down when dealing with 32-bit numbers. function am3(i,x,w,j,c,n) { var xl = x&0x3fff, xh = x>>14; while(--n >= 0) { var l = this[i]&0x3fff; var h = this[i++]>>14; var m = xh*l+h*xl; l = xl*l+((m&0x3fff)<<14)+w[j]+c; c = (l>>28)+(m>>14)+xh*h; w[j++] = l&0xfffffff; } return c; } if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) { BigInteger.prototype.am = am2; dbits = 30; } else if(j_lm && (navigator.appName != "Netscape")) { BigInteger.prototype.am = am1; dbits = 26; } else { // Mozilla/Netscape seems to prefer am3 BigInteger.prototype.am = am3; dbits = 28; } BigInteger.prototype.DB = dbits; BigInteger.prototype.DM = ((1<= 0; --i) r[i] = this[i]; r.t = this.t; r.s = this.s; } // (protected) set from integer value x, -DV <= x < DV function bnpFromInt(x) { this.t = 1; this.s = (x<0)?-1:0; if(x > 0) this[0] = x; else if(x < -1) this[0] = x+this.DV; else this.t = 0; } // return bigint initialized to value function nbv(i) { var r = nbi(); r.fromInt(i); return r; } // (protected) set from string and radix function bnpFromString(s,b) { var k; if(b == 16) k = 4; else if(b == 8) k = 3; else if(b == 256) k = 8; // byte array else if(b == 2) k = 1; else if(b == 32) k = 5; else if(b == 4) k = 2; else { this.fromRadix(s,b); return; } this.t = 0; this.s = 0; var i = s.length, mi = false, sh = 0; while(--i >= 0) { var x = (k==8)?s[i]&0xff:intAt(s,i); if(x < 0) { if(s.charAt(i) == "-") mi = true; continue; } mi = false; if(sh == 0) this[this.t++] = x; else if(sh+k > this.DB) { this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<>(this.DB-sh)); } else this[this.t-1] |= x<= this.DB) sh -= this.DB; } if(k == 8 && (s[0]&0x80) != 0) { this.s = -1; if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)< 0 && this[this.t-1] == c) --this.t; } // (public) return string representation in given radix function bnToString(b) { if(this.s < 0) return "-"+this.negate().toString(b); var k; if(b == 16) k = 4; else if(b == 8) k = 3; else if(b == 2) k = 1; else if(b == 32) k = 5; else if(b == 4) k = 2; else return this.toRadix(b); var km = (1< 0) { if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); } while(i >= 0) { if(p < k) { d = (this[i]&((1<>(p+=this.DB-k); } else { d = (this[i]>>(p-=k))&km; if(p <= 0) { p += this.DB; --i; } } if(d > 0) m = true; if(m) r += int2char(d); } } return m?r:"0"; } // (public) -this function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; } // (public) |this| function bnAbs() { return (this.s<0)?this.negate():this; } // (public) return + if this > a, - if this < a, 0 if equal function bnCompareTo(a) { var r = this.s-a.s; if(r != 0) return r; var i = this.t; r = i-a.t; if(r != 0) return (this.s<0)?-r:r; while(--i >= 0) if((r=this[i]-a[i]) != 0) return r; return 0; } // returns bit length of the integer x function nbits(x) { var r = 1, t; if((t=x>>>16) != 0) { x = t; r += 16; } if((t=x>>8) != 0) { x = t; r += 8; } if((t=x>>4) != 0) { x = t; r += 4; } if((t=x>>2) != 0) { x = t; r += 2; } if((t=x>>1) != 0) { x = t; r += 1; } return r; } // (public) return the number of bits in "this" function bnBitLength() { if(this.t <= 0) return 0; return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM)); } // (protected) r = this << n*DB function bnpDLShiftTo(n,r) { var i; for(i = this.t-1; i >= 0; --i) r[i+n] = this[i]; for(i = n-1; i >= 0; --i) r[i] = 0; r.t = this.t+n; r.s = this.s; } // (protected) r = this >> n*DB function bnpDRShiftTo(n,r) { for(var i = n; i < this.t; ++i) r[i-n] = this[i]; r.t = Math.max(this.t-n,0); r.s = this.s; } // (protected) r = this << n function bnpLShiftTo(n,r) { var bs = n%this.DB; var cbs = this.DB-bs; var bm = (1<= 0; --i) { r[i+ds+1] = (this[i]>>cbs)|c; c = (this[i]&bm)<= 0; --i) r[i] = 0; r[ds] = c; r.t = this.t+ds+1; r.s = this.s; r.clamp(); } // (protected) r = this >> n function bnpRShiftTo(n,r) { r.s = this.s; var ds = Math.floor(n/this.DB); if(ds >= this.t) { r.t = 0; return; } var bs = n%this.DB; var cbs = this.DB-bs; var bm = (1<>bs; for(var i = ds+1; i < this.t; ++i) { r[i-ds-1] |= (this[i]&bm)<>bs; } if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<>= this.DB; } if(a.t < this.t) { c -= a.s; while(i < this.t) { c += this[i]; r[i++] = c&this.DM; c >>= this.DB; } c += this.s; } else { c += this.s; while(i < a.t) { c -= a[i]; r[i++] = c&this.DM; c >>= this.DB; } c -= a.s; } r.s = (c<0)?-1:0; if(c < -1) r[i++] = this.DV+c; else if(c > 0) r[i++] = c; r.t = i; r.clamp(); } // (protected) r = this * a, r != this,a (HAC 14.12) // "this" should be the larger one if appropriate. function bnpMultiplyTo(a,r) { var x = this.abs(), y = a.abs(); var i = x.t; r.t = i+y.t; while(--i >= 0) r[i] = 0; for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t); r.s = 0; r.clamp(); if(this.s != a.s) BigInteger.ZERO.subTo(r,r); } // (protected) r = this^2, r != this (HAC 14.16) function bnpSquareTo(r) { var x = this.abs(); var i = r.t = 2*x.t; while(--i >= 0) r[i] = 0; for(i = 0; i < x.t-1; ++i) { var c = x.am(i,x[i],r,2*i,0,1); if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) { r[i+x.t] -= x.DV; r[i+x.t+1] = 1; } } if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1); r.s = 0; r.clamp(); } // (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) // r != q, this != m. q or r may be null. function bnpDivRemTo(m,q,r) { var pm = m.abs(); if(pm.t <= 0) return; var pt = this.abs(); if(pt.t < pm.t) { if(q != null) q.fromInt(0); if(r != null) this.copyTo(r); return; } if(r == null) r = nbi(); var y = nbi(), ts = this.s, ms = m.s; var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } else { pm.copyTo(y); pt.copyTo(r); } var ys = y.t; var y0 = y[ys-1]; if(y0 == 0) return; var yt = y0*(1<1)?y[ys-2]>>this.F2:0); var d1 = this.FV/yt, d2 = (1<= 0) { r[r.t++] = 1; r.subTo(t,r); } BigInteger.ONE.dlShiftTo(ys,t); t.subTo(y,y); // "negative" y so we can replace sub with am later while(y.t < ys) y[y.t++] = 0; while(--j >= 0) { // Estimate quotient digit var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out y.dlShiftTo(j,t); r.subTo(t,r); while(r[i] < --qd) r.subTo(t,r); } } if(q != null) { r.drShiftTo(ys,q); if(ts != ms) BigInteger.ZERO.subTo(q,q); } r.t = ys; r.clamp(); if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder if(ts < 0) BigInteger.ZERO.subTo(r,r); } // (public) this mod a function bnMod(a) { var r = nbi(); this.abs().divRemTo(a,null,r); if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r); return r; } // Modular reduction using "classic" algorithm function Classic(m) { this.m = m; } function cConvert(x) { if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); else return x; } function cRevert(x) { return x; } function cReduce(x) { x.divRemTo(this.m,null,x); } function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); } Classic.prototype.convert = cConvert; Classic.prototype.revert = cRevert; Classic.prototype.reduce = cReduce; Classic.prototype.mulTo = cMulTo; Classic.prototype.sqrTo = cSqrTo; // (protected) return "-1/this % 2^DB"; useful for Mont. reduction // justification: // xy == 1 (mod m) // xy = 1+km // xy(2-xy) = (1+km)(1-km) // x[y(2-xy)] = 1-k^2m^2 // x[y(2-xy)] == 1 (mod m^2) // if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 // should reduce x and y(2-xy) by m^2 at each step to keep size bounded. // JS multiply "overflows" differently from C/C++, so care is needed here. function bnpInvDigit() { if(this.t < 1) return 0; var x = this[0]; if((x&1) == 0) return 0; var y = x&3; // y == 1/x mod 2^2 y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4 y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8 y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16 // last step - calculate inverse mod DV directly; // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits // we really want the negative inverse, and -DV < y < DV return (y>0)?this.DV-y:-y; } // Montgomery reduction function Montgomery(m) { this.m = m; this.mp = m.invDigit(); this.mpl = this.mp&0x7fff; this.mph = this.mp>>15; this.um = (1<<(m.DB-15))-1; this.mt2 = 2*m.t; } // xR mod m function montConvert(x) { var r = nbi(); x.abs().dlShiftTo(this.m.t,r); r.divRemTo(this.m,null,r); if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r); return r; } // x/R mod m function montRevert(x) { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } // x = x/R mod m (HAC 14.32) function montReduce(x) { while(x.t <= this.mt2) // pad x so am has enough room later x[x.t++] = 0; for(var i = 0; i < this.m.t; ++i) { // faster way of calculating u0 = x[i]*mp mod DV var j = x[i]&0x7fff; var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM; // use am to combine the multiply-shift-add into one call j = i+this.m.t; x[j] += this.m.am(0,u0,x,i,0,this.m.t); // propagate carry while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; } } x.clamp(); x.drShiftTo(this.m.t,x); if(x.compareTo(this.m) >= 0) x.subTo(this.m,x); } // r = "x^2/R mod m"; x != r function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); } // r = "xy/R mod m"; x,y != r function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } Montgomery.prototype.convert = montConvert; Montgomery.prototype.revert = montRevert; Montgomery.prototype.reduce = montReduce; Montgomery.prototype.mulTo = montMulTo; Montgomery.prototype.sqrTo = montSqrTo; // (protected) true iff this is even function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; } // (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) function bnpExp(e,z) { if(e > 0xffffffff || e < 1) return BigInteger.ONE; var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; g.copyTo(r); while(--i >= 0) { z.sqrTo(r,r2); if((e&(1< 0) z.mulTo(r2,g,r); else { var t = r; r = r2; r2 = t; } } return z.revert(r); } // (public) this^e % m, 0 <= e < 2^32 function bnModPowInt(e,m) { var z; if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); return this.exp(e,z); } // protected BigInteger.prototype.copyTo = bnpCopyTo; BigInteger.prototype.fromInt = bnpFromInt; BigInteger.prototype.fromString = bnpFromString; BigInteger.prototype.clamp = bnpClamp; BigInteger.prototype.dlShiftTo = bnpDLShiftTo; BigInteger.prototype.drShiftTo = bnpDRShiftTo; BigInteger.prototype.lShiftTo = bnpLShiftTo; BigInteger.prototype.rShiftTo = bnpRShiftTo; BigInteger.prototype.subTo = bnpSubTo; BigInteger.prototype.multiplyTo = bnpMultiplyTo; BigInteger.prototype.squareTo = bnpSquareTo; BigInteger.prototype.divRemTo = bnpDivRemTo; BigInteger.prototype.invDigit = bnpInvDigit; BigInteger.prototype.isEven = bnpIsEven; BigInteger.prototype.exp = bnpExp; // public BigInteger.prototype.toString = bnToString; BigInteger.prototype.negate = bnNegate; BigInteger.prototype.abs = bnAbs; BigInteger.prototype.compareTo = bnCompareTo; BigInteger.prototype.bitLength = bnBitLength; BigInteger.prototype.mod = bnMod; BigInteger.prototype.modPowInt = bnModPowInt; // "constants" BigInteger.ZERO = nbv(0); BigInteger.ONE = nbv(1); // Copyright (c) 2005-2009 Tom Wu // All Rights Reserved. // See "LICENSE" for details. // Extended JavaScript BN functions, required for RSA private ops. // Version 1.1: new BigInteger("0", 10) returns "proper" zero // Version 1.2: square() API, isProbablePrime fix // (public) function bnClone() { var r = nbi(); this.copyTo(r); return r; } // (public) return value as integer function bnIntValue() { if(this.s < 0) { if(this.t == 1) return this[0]-this.DV; else if(this.t == 0) return -1; } else if(this.t == 1) return this[0]; else if(this.t == 0) return 0; // assumes 16 < DB < 32 return ((this[1]&((1<<(32-this.DB))-1))<>24; } // (public) return value as short (assumes DB>=16) function bnShortValue() { return (this.t==0)?this.s:(this[0]<<16)>>16; } // (protected) return x s.t. r^x < DV function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); } // (public) 0 if this == 0, 1 if this > 0 function bnSigNum() { if(this.s < 0) return -1; else if(this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0; else return 1; } // (protected) convert to radix string function bnpToRadix(b) { if(b == null) b = 10; if(this.signum() == 0 || b < 2 || b > 36) return "0"; var cs = this.chunkSize(b); var a = Math.pow(b,cs); var d = nbv(a), y = nbi(), z = nbi(), r = ""; this.divRemTo(d,y,z); while(y.signum() > 0) { r = (a+z.intValue()).toString(b).substr(1) + r; y.divRemTo(d,y,z); } return z.intValue().toString(b) + r; } // (protected) convert from radix string function bnpFromRadix(s,b) { this.fromInt(0); if(b == null) b = 10; var cs = this.chunkSize(b); var d = Math.pow(b,cs), mi = false, j = 0, w = 0; for(var i = 0; i < s.length; ++i) { var x = intAt(s,i); if(x < 0) { if(s.charAt(i) == "-" && this.signum() == 0) mi = true; continue; } w = b*w+x; if(++j >= cs) { this.dMultiply(d); this.dAddOffset(w,0); j = 0; w = 0; } } if(j > 0) { this.dMultiply(Math.pow(b,j)); this.dAddOffset(w,0); } if(mi) BigInteger.ZERO.subTo(this,this); } // (protected) alternate constructor function bnpFromNumber(a,b,c) { if("number" == typeof b) { // new BigInteger(int,int,RNG) if(a < 2) this.fromInt(1); else { this.fromNumber(a,c); if(!this.testBit(a-1)) // force MSB set this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this); if(this.isEven()) this.dAddOffset(1,0); // force odd while(!this.isProbablePrime(b)) { this.dAddOffset(2,0); if(this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a-1),this); } } } else { // new BigInteger(int,RNG) var x = new Array(), t = a&7; x.length = (a>>3)+1; b.nextBytes(x); if(t > 0) x[0] &= ((1< 0) { if(p < this.DB && (d = this[i]>>p) != (this.s&this.DM)>>p) r[k++] = d|(this.s<<(this.DB-p)); while(i >= 0) { if(p < 8) { d = (this[i]&((1<>(p+=this.DB-8); } else { d = (this[i]>>(p-=8))&0xff; if(p <= 0) { p += this.DB; --i; } } if((d&0x80) != 0) d |= -256; if(k == 0 && (this.s&0x80) != (d&0x80)) ++k; if(k > 0 || d != this.s) r[k++] = d; } } return r; } function bnEquals(a) { return(this.compareTo(a)==0); } function bnMin(a) { return(this.compareTo(a)<0)?this:a; } function bnMax(a) { return(this.compareTo(a)>0)?this:a; } // (protected) r = this op a (bitwise) function bnpBitwiseTo(a,op,r) { var i, f, m = Math.min(a.t,this.t); for(i = 0; i < m; ++i) r[i] = op(this[i],a[i]); if(a.t < this.t) { f = a.s&this.DM; for(i = m; i < this.t; ++i) r[i] = op(this[i],f); r.t = this.t; } else { f = this.s&this.DM; for(i = m; i < a.t; ++i) r[i] = op(f,a[i]); r.t = a.t; } r.s = op(this.s,a.s); r.clamp(); } // (public) this & a function op_and(x,y) { return x&y; } function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; } // (public) this | a function op_or(x,y) { return x|y; } function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; } // (public) this ^ a function op_xor(x,y) { return x^y; } function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; } // (public) this & ~a function op_andnot(x,y) { return x&~y; } function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; } // (public) ~this function bnNot() { var r = nbi(); for(var i = 0; i < this.t; ++i) r[i] = this.DM&~this[i]; r.t = this.t; r.s = ~this.s; return r; } // (public) this << n function bnShiftLeft(n) { var r = nbi(); if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r); return r; } // (public) this >> n function bnShiftRight(n) { var r = nbi(); if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r); return r; } // return index of lowest 1-bit in x, x < 2^31 function lbit(x) { if(x == 0) return -1; var r = 0; if((x&0xffff) == 0) { x >>= 16; r += 16; } if((x&0xff) == 0) { x >>= 8; r += 8; } if((x&0xf) == 0) { x >>= 4; r += 4; } if((x&3) == 0) { x >>= 2; r += 2; } if((x&1) == 0) ++r; return r; } // (public) returns index of lowest 1-bit (or -1 if none) function bnGetLowestSetBit() { for(var i = 0; i < this.t; ++i) if(this[i] != 0) return i*this.DB+lbit(this[i]); if(this.s < 0) return this.t*this.DB; return -1; } // return number of 1 bits in x function cbit(x) { var r = 0; while(x != 0) { x &= x-1; ++r; } return r; } // (public) return number of set bits function bnBitCount() { var r = 0, x = this.s&this.DM; for(var i = 0; i < this.t; ++i) r += cbit(this[i]^x); return r; } // (public) true iff nth bit is set function bnTestBit(n) { var j = Math.floor(n/this.DB); if(j >= this.t) return(this.s!=0); return((this[j]&(1<<(n%this.DB)))!=0); } // (protected) this op (1<>= this.DB; } if(a.t < this.t) { c += a.s; while(i < this.t) { c += this[i]; r[i++] = c&this.DM; c >>= this.DB; } c += this.s; } else { c += this.s; while(i < a.t) { c += a[i]; r[i++] = c&this.DM; c >>= this.DB; } c += a.s; } r.s = (c<0)?-1:0; if(c > 0) r[i++] = c; else if(c < -1) r[i++] = this.DV+c; r.t = i; r.clamp(); } // (public) this + a function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; } // (public) this - a function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; } // (public) this * a function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; } // (public) this^2 function bnSquare() { var r = nbi(); this.squareTo(r); return r; } // (public) this / a function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; } // (public) this % a function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; } // (public) [this/a,this%a] function bnDivideAndRemainder(a) { var q = nbi(), r = nbi(); this.divRemTo(a,q,r); return new Array(q,r); } // (protected) this *= n, this >= 0, 1 < n < DV function bnpDMultiply(n) { this[this.t] = this.am(0,n-1,this,0,0,this.t); ++this.t; this.clamp(); } // (protected) this += n << w words, this >= 0 function bnpDAddOffset(n,w) { if(n == 0) return; while(this.t <= w) this[this.t++] = 0; this[w] += n; while(this[w] >= this.DV) { this[w] -= this.DV; if(++w >= this.t) this[this.t++] = 0; ++this[w]; } } // A "null" reducer function NullExp() {} function nNop(x) { return x; } function nMulTo(x,y,r) { x.multiplyTo(y,r); } function nSqrTo(x,r) { x.squareTo(r); } NullExp.prototype.convert = nNop; NullExp.prototype.revert = nNop; NullExp.prototype.mulTo = nMulTo; NullExp.prototype.sqrTo = nSqrTo; // (public) this^e function bnPow(e) { return this.exp(e,new NullExp()); } // (protected) r = lower n words of "this * a", a.t <= n // "this" should be the larger one if appropriate. function bnpMultiplyLowerTo(a,n,r) { var i = Math.min(this.t+a.t,n); r.s = 0; // assumes a,this >= 0 r.t = i; while(i > 0) r[--i] = 0; var j; for(j = r.t-this.t; i < j; ++i) r[i+this.t] = this.am(0,a[i],r,i,0,this.t); for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a[i],r,i,0,n-i); r.clamp(); } // (protected) r = "this * a" without lower n words, n > 0 // "this" should be the larger one if appropriate. function bnpMultiplyUpperTo(a,n,r) { --n; var i = r.t = this.t+a.t-n; r.s = 0; // assumes a,this >= 0 while(--i >= 0) r[i] = 0; for(i = Math.max(n-this.t,0); i < a.t; ++i) r[this.t+i-n] = this.am(n-i,a[i],r,0,0,this.t+i-n); r.clamp(); r.drShiftTo(1,r); } // Barrett modular reduction function Barrett(m) { // setup Barrett this.r2 = nbi(); this.q3 = nbi(); BigInteger.ONE.dlShiftTo(2*m.t,this.r2); this.mu = this.r2.divide(m); this.m = m; } function barrettConvert(x) { if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m); else if(x.compareTo(this.m) < 0) return x; else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } } function barrettRevert(x) { return x; } // x = x mod m (HAC 14.42) function barrettReduce(x) { x.drShiftTo(this.m.t-1,this.r2); if(x.t > this.m.t+1) { x.t = this.m.t+1; x.clamp(); } this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3); this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2); while(x.compareTo(this.r2) < 0) x.dAddOffset(1,this.m.t+1); x.subTo(this.r2,x); while(x.compareTo(this.m) >= 0) x.subTo(this.m,x); } // r = x^2 mod m; x != r function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); } // r = x*y mod m; x,y != r function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } Barrett.prototype.convert = barrettConvert; Barrett.prototype.revert = barrettRevert; Barrett.prototype.reduce = barrettReduce; Barrett.prototype.mulTo = barrettMulTo; Barrett.prototype.sqrTo = barrettSqrTo; // (public) this^e % m (HAC 14.85) function bnModPow(e,m) { var i = e.bitLength(), k, r = nbv(1), z; if(i <= 0) return r; else if(i < 18) k = 1; else if(i < 48) k = 3; else if(i < 144) k = 4; else if(i < 768) k = 5; else k = 6; if(i < 8) z = new Classic(m); else if(m.isEven()) z = new Barrett(m); else z = new Montgomery(m); // precomputation var g = new Array(), n = 3, k1 = k-1, km = (1< 1) { var g2 = nbi(); z.sqrTo(g[1],g2); while(n <= km) { g[n] = nbi(); z.mulTo(g2,g[n-2],g[n]); n += 2; } } var j = e.t-1, w, is1 = true, r2 = nbi(), t; i = nbits(e[j])-1; while(j >= 0) { if(i >= k1) w = (e[j]>>(i-k1))&km; else { w = (e[j]&((1<<(i+1))-1))<<(k1-i); if(j > 0) w |= e[j-1]>>(this.DB+i-k1); } n = k; while((w&1) == 0) { w >>= 1; --n; } if((i -= n) < 0) { i += this.DB; --j; } if(is1) { // ret == 1, don't bother squaring or multiplying it g[w].copyTo(r); is1 = false; } else { while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; } if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; } z.mulTo(r2,g[w],r); } while(j >= 0 && (e[j]&(1< 0) { x.rShiftTo(g,x); y.rShiftTo(g,y); } while(x.signum() > 0) { if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x); if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y); if(x.compareTo(y) >= 0) { x.subTo(y,x); x.rShiftTo(1,x); } else { y.subTo(x,y); y.rShiftTo(1,y); } } if(g > 0) y.lShiftTo(g,y); return y; } // (protected) this % n, n < 2^26 function bnpModInt(n) { if(n <= 0) return 0; var d = this.DV%n, r = (this.s<0)?n-1:0; if(this.t > 0) if(d == 0) r = this[0]%n; else for(var i = this.t-1; i >= 0; --i) r = (d*r+this[i])%n; return r; } // (public) 1/this % m (HAC 14.61) function bnModInverse(m) { var ac = m.isEven(); if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO; var u = m.clone(), v = this.clone(); var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); while(u.signum() != 0) { while(u.isEven()) { u.rShiftTo(1,u); if(ac) { if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); } a.rShiftTo(1,a); } else if(!b.isEven()) b.subTo(m,b); b.rShiftTo(1,b); } while(v.isEven()) { v.rShiftTo(1,v); if(ac) { if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); } c.rShiftTo(1,c); } else if(!d.isEven()) d.subTo(m,d); d.rShiftTo(1,d); } if(u.compareTo(v) >= 0) { u.subTo(v,u); if(ac) a.subTo(c,a); b.subTo(d,b); } else { v.subTo(u,v); if(ac) c.subTo(a,c); d.subTo(b,d); } } if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO; if(d.compareTo(m) >= 0) return d.subtract(m); if(d.signum() < 0) d.addTo(m,d); else return d; if(d.signum() < 0) return d.add(m); else return d; } var lowprimes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997]; var lplim = (1<<26)/lowprimes[lowprimes.length-1]; // (public) test primality with certainty >= 1-.5^t function bnIsProbablePrime(t) { var i, x = this.abs(); if(x.t == 1 && x[0] <= lowprimes[lowprimes.length-1]) { for(i = 0; i < lowprimes.length; ++i) if(x[0] == lowprimes[i]) return true; return false; } if(x.isEven()) return false; i = 1; while(i < lowprimes.length) { var m = lowprimes[i], j = i+1; while(j < lowprimes.length && m < lplim) m *= lowprimes[j++]; m = x.modInt(m); while(i < j) if(m%lowprimes[i++] == 0) return false; } return x.millerRabin(t); } // (protected) true if probably prime (HAC 4.24, Miller-Rabin) function bnpMillerRabin(t) { var n1 = this.subtract(BigInteger.ONE); var k = n1.getLowestSetBit(); if(k <= 0) return false; var r = n1.shiftRight(k); t = (t+1)>>1; if(t > lowprimes.length) t = lowprimes.length; var a = nbi(); for(var i = 0; i < t; ++i) { //Pick bases at random, instead of starting at 2 a.fromInt(lowprimes[Math.floor(Math.random()*lowprimes.length)]); var y = a.modPow(r,this); if(y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) { var j = 1; while(j++ < k && y.compareTo(n1) != 0) { y = y.modPowInt(2,this); if(y.compareTo(BigInteger.ONE) == 0) return false; } if(y.compareTo(n1) != 0) return false; } } return true; } // protected BigInteger.prototype.chunkSize = bnpChunkSize; BigInteger.prototype.toRadix = bnpToRadix; BigInteger.prototype.fromRadix = bnpFromRadix; BigInteger.prototype.fromNumber = bnpFromNumber; BigInteger.prototype.bitwiseTo = bnpBitwiseTo; BigInteger.prototype.changeBit = bnpChangeBit; BigInteger.prototype.addTo = bnpAddTo; BigInteger.prototype.dMultiply = bnpDMultiply; BigInteger.prototype.dAddOffset = bnpDAddOffset; BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo; BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo; BigInteger.prototype.modInt = bnpModInt; BigInteger.prototype.millerRabin = bnpMillerRabin; // public BigInteger.prototype.clone = bnClone; BigInteger.prototype.intValue = bnIntValue; BigInteger.prototype.byteValue = bnByteValue; BigInteger.prototype.shortValue = bnShortValue; BigInteger.prototype.signum = bnSigNum; BigInteger.prototype.toByteArray = bnToByteArray; BigInteger.prototype.equals = bnEquals; BigInteger.prototype.min = bnMin; BigInteger.prototype.max = bnMax; BigInteger.prototype.and = bnAnd; BigInteger.prototype.or = bnOr; BigInteger.prototype.xor = bnXor; BigInteger.prototype.andNot = bnAndNot; BigInteger.prototype.not = bnNot; BigInteger.prototype.shiftLeft = bnShiftLeft; BigInteger.prototype.shiftRight = bnShiftRight; BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit; BigInteger.prototype.bitCount = bnBitCount; BigInteger.prototype.testBit = bnTestBit; BigInteger.prototype.setBit = bnSetBit; BigInteger.prototype.clearBit = bnClearBit; BigInteger.prototype.flipBit = bnFlipBit; BigInteger.prototype.add = bnAdd; BigInteger.prototype.subtract = bnSubtract; BigInteger.prototype.multiply = bnMultiply; BigInteger.prototype.divide = bnDivide; BigInteger.prototype.remainder = bnRemainder; BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder; BigInteger.prototype.modPow = bnModPow; BigInteger.prototype.modInverse = bnModInverse; BigInteger.prototype.pow = bnPow; BigInteger.prototype.gcd = bnGCD; BigInteger.prototype.isProbablePrime = bnIsProbablePrime; // JSBN-specific extension BigInteger.prototype.square = bnSquare; // BigInteger interfaces not implemented in jsbn: // BigInteger(int signum, byte[] magnitude) // double doubleValue() // float floatValue() // int hashCode() // long longValue() // static BigInteger valueOf(long val) // prng4.js - uses Arcfour as a PRNG function Arcfour() { this.i = 0; this.j = 0; this.S = new Array(); } // Initialize arcfour context from key, an array of ints, each from [0..255] function ARC4init(key) { var i, j, t; for(i = 0; i < 256; ++i) this.S[i] = i; j = 0; for(i = 0; i < 256; ++i) { j = (j + this.S[i] + key[i % key.length]) & 255; t = this.S[i]; this.S[i] = this.S[j]; this.S[j] = t; } this.i = 0; this.j = 0; } function ARC4next() { var t; this.i = (this.i + 1) & 255; this.j = (this.j + this.S[this.i]) & 255; t = this.S[this.i]; this.S[this.i] = this.S[this.j]; this.S[this.j] = t; return this.S[(t + this.S[this.i]) & 255]; } Arcfour.prototype.init = ARC4init; Arcfour.prototype.next = ARC4next; // Plug in your RNG constructor here function prng_newstate() { return new Arcfour(); } // Pool size must be a multiple of 4 and greater than 32. // An array of bytes the size of the pool will be passed to init() var rng_psize = 256; // Random number generator - requires a PRNG backend, e.g. prng4.js var rng_state; var rng_pool; var rng_pptr; // Initialize the pool with junk if needed. if(rng_pool == null) { rng_pool = new Array(); rng_pptr = 0; var t; if(window.crypto && window.crypto.getRandomValues) { // Extract entropy (2048 bits) from RNG if available var z = new Uint32Array(256); window.crypto.getRandomValues(z); for (t = 0; t < z.length; ++t) rng_pool[rng_pptr++] = z[t] & 255; } // Use mouse events for entropy, if we do not have enough entropy by the time // we need it, entropy will be generated by Math.random. var onMouseMoveListener = function(ev) { this.count = this.count || 0; if (this.count >= 256 || rng_pptr >= rng_psize) { if (window.removeEventListener) window.removeEventListener("mousemove", onMouseMoveListener); else if (window.detachEvent) window.detachEvent("onmousemove", onMouseMoveListener); return; } this.count += 1; var mouseCoordinates = ev.x + ev.y; rng_pool[rng_pptr++] = mouseCoordinates & 255; }; if (window.addEventListener) window.addEventListener("mousemove", onMouseMoveListener); else if (window.attachEvent) window.attachEvent("onmousemove", onMouseMoveListener); } function rng_get_byte() { if(rng_state == null) { rng_state = prng_newstate(); // At this point, we may not have collected enough entropy. If not, fall back to Math.random while (rng_pptr < rng_psize) { var random = Math.floor(65536 * Math.random()); rng_pool[rng_pptr++] = random & 255; } rng_state.init(rng_pool); for(rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr) rng_pool[rng_pptr] = 0; rng_pptr = 0; } // TODO: allow reseeding after first request return rng_state.next(); } function rng_get_bytes(ba) { var i; for(i = 0; i < ba.length; ++i) ba[i] = rng_get_byte(); } function SecureRandom() {} SecureRandom.prototype.nextBytes = rng_get_bytes; // Depends on jsbn.js and rng.js // Version 1.1: support utf-8 encoding in pkcs1pad2 // convert a (hex) string to a bignum object function parseBigInt(str,r) { return new BigInteger(str,r); } function linebrk(s,n) { var ret = ""; var i = 0; while(i + n < s.length) { ret += s.substring(i,i+n) + "\n"; i += n; } return ret + s.substring(i,s.length); } function byte2Hex(b) { if(b < 0x10) return "0" + b.toString(16); else return b.toString(16); } // PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint function pkcs1pad2(s,n) { if(n < s.length + 11) { // TODO: fix for utf-8 console.error("Message too long for RSA"); return null; } var ba = new Array(); var i = s.length - 1; while(i >= 0 && n > 0) { var c = s.charCodeAt(i--); if(c < 128) { // encode using utf-8 ba[--n] = c; } else if((c > 127) && (c < 2048)) { ba[--n] = (c & 63) | 128; ba[--n] = (c >> 6) | 192; } else { ba[--n] = (c & 63) | 128; ba[--n] = ((c >> 6) & 63) | 128; ba[--n] = (c >> 12) | 224; } } ba[--n] = 0; var rng = new SecureRandom(); var x = new Array(); while(n > 2) { // random non-zero pad x[0] = 0; while(x[0] == 0) rng.nextBytes(x); ba[--n] = x[0]; } ba[--n] = 2; ba[--n] = 0; return new BigInteger(ba); } // "empty" RSA key constructor function RSAKey() { this.n = null; this.e = 0; this.d = null; this.p = null; this.q = null; this.dmp1 = null; this.dmq1 = null; this.coeff = null; } // Set the public key fields N and e from hex strings function RSASetPublic(N,E) { if(N != null && E != null && N.length > 0 && E.length > 0) { this.n = parseBigInt(N,16); this.e = parseInt(E,16); } else console.error("Invalid RSA public key"); } // Perform raw public operation on "x": return x^e (mod n) function RSADoPublic(x) { return x.modPowInt(this.e, this.n); } // Return the PKCS#1 RSA encryption of "text" as an even-length hex string function RSAEncrypt(text) { var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3); if(m == null) return null; var c = this.doPublic(m); if(c == null) return null; var h = c.toString(16); if((h.length & 1) == 0) return h; else return "0" + h; } // Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string //function RSAEncryptB64(text) { // var h = this.encrypt(text); // if(h) return hex2b64(h); else return null; //} // protected RSAKey.prototype.doPublic = RSADoPublic; // public RSAKey.prototype.setPublic = RSASetPublic; RSAKey.prototype.encrypt = RSAEncrypt; //RSAKey.prototype.encrypt_b64 = RSAEncryptB64; // Depends on rsa.js and jsbn2.js // Version 1.1: support utf-8 decoding in pkcs1unpad2 // Undo PKCS#1 (type 2, random) padding and, if valid, return the plaintext function pkcs1unpad2(d,n) { var b = d.toByteArray(); var i = 0; while(i < b.length && b[i] == 0) ++i; if(b.length-i != n-1 || b[i] != 2) return null; ++i; while(b[i] != 0) if(++i >= b.length) return null; var ret = ""; while(++i < b.length) { var c = b[i] & 255; if(c < 128) { // utf-8 decode ret += String.fromCharCode(c); } else if((c > 191) && (c < 224)) { ret += String.fromCharCode(((c & 31) << 6) | (b[i+1] & 63)); ++i; } else { ret += String.fromCharCode(((c & 15) << 12) | ((b[i+1] & 63) << 6) | (b[i+2] & 63)); i += 2; } } return ret; } // Set the private key fields N, e, and d from hex strings function RSASetPrivate(N,E,D) { if(N != null && E != null && N.length > 0 && E.length > 0) { this.n = parseBigInt(N,16); this.e = parseInt(E,16); this.d = parseBigInt(D,16); } else console.error("Invalid RSA private key"); } // Set the private key fields N, e, d and CRT params from hex strings function RSASetPrivateEx(N,E,D,P,Q,DP,DQ,C) { if(N != null && E != null && N.length > 0 && E.length > 0) { this.n = parseBigInt(N,16); this.e = parseInt(E,16); this.d = parseBigInt(D,16); this.p = parseBigInt(P,16); this.q = parseBigInt(Q,16); this.dmp1 = parseBigInt(DP,16); this.dmq1 = parseBigInt(DQ,16); this.coeff = parseBigInt(C,16); } else console.error("Invalid RSA private key"); } // Generate a new random private key B bits long, using public expt E function RSAGenerate(B,E) { var rng = new SecureRandom(); var qs = B>>1; this.e = parseInt(E,16); var ee = new BigInteger(E,16); for(;;) { for(;;) { this.p = new BigInteger(B-qs,1,rng); if(this.p.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.p.isProbablePrime(10)) break; } for(;;) { this.q = new BigInteger(qs,1,rng); if(this.q.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.q.isProbablePrime(10)) break; } if(this.p.compareTo(this.q) <= 0) { var t = this.p; this.p = this.q; this.q = t; } var p1 = this.p.subtract(BigInteger.ONE); var q1 = this.q.subtract(BigInteger.ONE); var phi = p1.multiply(q1); if(phi.gcd(ee).compareTo(BigInteger.ONE) == 0) { this.n = this.p.multiply(this.q); this.d = ee.modInverse(phi); this.dmp1 = this.d.mod(p1); this.dmq1 = this.d.mod(q1); this.coeff = this.q.modInverse(this.p); break; } } } // Perform raw private operation on "x": return x^d (mod n) function RSADoPrivate(x) { if(this.p == null || this.q == null) return x.modPow(this.d, this.n); // TODO: re-calculate any missing CRT params var xp = x.mod(this.p).modPow(this.dmp1, this.p); var xq = x.mod(this.q).modPow(this.dmq1, this.q); while(xp.compareTo(xq) < 0) xp = xp.add(this.p); return xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq); } // Return the PKCS#1 RSA decryption of "ctext". // "ctext" is an even-length hex string and the output is a plain string. function RSADecrypt(ctext) { var c = parseBigInt(ctext, 16); var m = this.doPrivate(c); if(m == null) return null; return pkcs1unpad2(m, (this.n.bitLength()+7)>>3); } // Return the PKCS#1 RSA decryption of "ctext". // "ctext" is a Base64-encoded string and the output is a plain string. //function RSAB64Decrypt(ctext) { // var h = b64tohex(ctext); // if(h) return this.decrypt(h); else return null; //} // protected RSAKey.prototype.doPrivate = RSADoPrivate; // public RSAKey.prototype.setPrivate = RSASetPrivate; RSAKey.prototype.setPrivateEx = RSASetPrivateEx; RSAKey.prototype.generate = RSAGenerate; RSAKey.prototype.decrypt = RSADecrypt; //RSAKey.prototype.b64_decrypt = RSAB64Decrypt; // Copyright (c) 2011 Kevin M Burns Jr. // All Rights Reserved. // See "LICENSE" for details. // // Extension to jsbn which adds facilities for asynchronous RSA key generation // Primarily created to avoid execution timeout on mobile devices // // http://www-cs-students.stanford.edu/~tjw/jsbn/ // // --- (function(){ // Generate a new random private key B bits long, using public expt E var RSAGenerateAsync = function (B, E, callback) { //var rng = new SeededRandom(); var rng = new SecureRandom(); var qs = B >> 1; this.e = parseInt(E, 16); var ee = new BigInteger(E, 16); var rsa = this; // These functions have non-descript names because they were originally for(;;) loops. // I don't know about cryptography to give them better names than loop1-4. var loop1 = function() { var loop4 = function() { if (rsa.p.compareTo(rsa.q) <= 0) { var t = rsa.p; rsa.p = rsa.q; rsa.q = t; } var p1 = rsa.p.subtract(BigInteger.ONE); var q1 = rsa.q.subtract(BigInteger.ONE); var phi = p1.multiply(q1); if (phi.gcd(ee).compareTo(BigInteger.ONE) == 0) { rsa.n = rsa.p.multiply(rsa.q); rsa.d = ee.modInverse(phi); rsa.dmp1 = rsa.d.mod(p1); rsa.dmq1 = rsa.d.mod(q1); rsa.coeff = rsa.q.modInverse(rsa.p); setTimeout(function(){callback()},0); // escape } else { setTimeout(loop1,0); } }; var loop3 = function() { rsa.q = nbi(); rsa.q.fromNumberAsync(qs, 1, rng, function(){ rsa.q.subtract(BigInteger.ONE).gcda(ee, function(r){ if (r.compareTo(BigInteger.ONE) == 0 && rsa.q.isProbablePrime(10)) { setTimeout(loop4,0); } else { setTimeout(loop3,0); } }); }); }; var loop2 = function() { rsa.p = nbi(); rsa.p.fromNumberAsync(B - qs, 1, rng, function(){ rsa.p.subtract(BigInteger.ONE).gcda(ee, function(r){ if (r.compareTo(BigInteger.ONE) == 0 && rsa.p.isProbablePrime(10)) { setTimeout(loop3,0); } else { setTimeout(loop2,0); } }); }); }; setTimeout(loop2,0); }; setTimeout(loop1,0); }; RSAKey.prototype.generateAsync = RSAGenerateAsync; // Public API method var bnGCDAsync = function (a, callback) { var x = (this.s < 0) ? this.negate() : this.clone(); var y = (a.s < 0) ? a.negate() : a.clone(); if (x.compareTo(y) < 0) { var t = x; x = y; y = t; } var i = x.getLowestSetBit(), g = y.getLowestSetBit(); if (g < 0) { callback(x); return; } if (i < g) g = i; if (g > 0) { x.rShiftTo(g, x); y.rShiftTo(g, y); } // Workhorse of the algorithm, gets called 200 - 800 times per 512 bit keygen. var gcda1 = function() { if ((i = x.getLowestSetBit()) > 0){ x.rShiftTo(i, x); } if ((i = y.getLowestSetBit()) > 0){ y.rShiftTo(i, y); } if (x.compareTo(y) >= 0) { x.subTo(y, x); x.rShiftTo(1, x); } else { y.subTo(x, y); y.rShiftTo(1, y); } if(!(x.signum() > 0)) { if (g > 0) y.lShiftTo(g, y); setTimeout(function(){callback(y)},0); // escape } else { setTimeout(gcda1,0); } }; setTimeout(gcda1,10); }; BigInteger.prototype.gcda = bnGCDAsync; // (protected) alternate constructor var bnpFromNumberAsync = function (a,b,c,callback) { if("number" == typeof b) { if(a < 2) { this.fromInt(1); } else { this.fromNumber(a,c); if(!this.testBit(a-1)){ this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this); } if(this.isEven()) { this.dAddOffset(1,0); } var bnp = this; var bnpfn1 = function(){ bnp.dAddOffset(2,0); if(bnp.bitLength() > a) bnp.subTo(BigInteger.ONE.shiftLeft(a-1),bnp); if(bnp.isProbablePrime(b)) { setTimeout(function(){callback()},0); // escape } else { setTimeout(bnpfn1,0); } }; setTimeout(bnpfn1,0); } } else { var x = new Array(), t = a&7; x.length = (a>>3)+1; b.nextBytes(x); if(t > 0) x[0] &= ((1<> 6) + b64map.charAt(c & 63); } if(i+1 == h.length) { c = parseInt(h.substring(i,i+1),16); ret += b64map.charAt(c << 2); } else if(i+2 == h.length) { c = parseInt(h.substring(i,i+2),16); ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4); } while((ret.length & 3) > 0) ret += b64pad; return ret; } // convert a base64 string to hex function b64tohex(s) { var ret = "" var i; var k = 0; // b64 state, 0-3 var slop; for(i = 0; i < s.length; ++i) { if(s.charAt(i) == b64pad) break; v = b64map.indexOf(s.charAt(i)); if(v < 0) continue; if(k == 0) { ret += int2char(v >> 2); slop = v & 3; k = 1; } else if(k == 1) { ret += int2char((slop << 2) | (v >> 4)); slop = v & 0xf; k = 2; } else if(k == 2) { ret += int2char(slop); ret += int2char(v >> 2); slop = v & 3; k = 3; } else { ret += int2char((slop << 2) | (v >> 4)); ret += int2char(v & 0xf); k = 0; } } if(k == 1) ret += int2char(slop << 2); return ret; } // convert a base64 string to a byte/number array function b64toBA(s) { //piggyback on b64tohex for now, optimize later var h = b64tohex(s); var i; var a = new Array(); for(i = 0; 2*i < h.length; ++i) { a[i] = parseInt(h.substring(2*i,2*i+2),16); } return a; } /*! asn1-1.0.2.js (c) 2013 Kenji Urushima | kjur.github.com/jsrsasign/license */ var JSX = JSX || {}; JSX.env = JSX.env || {}; var L = JSX, OP = Object.prototype, FUNCTION_TOSTRING = '[object Function]',ADD = ["toString", "valueOf"]; JSX.env.parseUA = function(agent) { var numberify = function(s) { var c = 0; return parseFloat(s.replace(/\./g, function() { return (c++ == 1) ? '' : '.'; })); }, nav = navigator, o = { ie: 0, opera: 0, gecko: 0, webkit: 0, chrome: 0, mobile: null, air: 0, ipad: 0, iphone: 0, ipod: 0, ios: null, android: 0, webos: 0, caja: nav && nav.cajaVersion, secure: false, os: null }, ua = agent || (navigator && navigator.userAgent), loc = window && window.location, href = loc && loc.href, m; o.secure = href && (href.toLowerCase().indexOf("https") === 0); if (ua) { if ((/windows|win32/i).test(ua)) { o.os = 'windows'; } else if ((/macintosh/i).test(ua)) { o.os = 'macintosh'; } else if ((/rhino/i).test(ua)) { o.os = 'rhino'; } if ((/KHTML/).test(ua)) { o.webkit = 1; } m = ua.match(/AppleWebKit\/([^\s]*)/); if (m && m[1]) { o.webkit = numberify(m[1]); if (/ Mobile\//.test(ua)) { o.mobile = 'Apple'; // iPhone or iPod Touch m = ua.match(/OS ([^\s]*)/); if (m && m[1]) { m = numberify(m[1].replace('_', '.')); } o.ios = m; o.ipad = o.ipod = o.iphone = 0; m = ua.match(/iPad|iPod|iPhone/); if (m && m[0]) { o[m[0].toLowerCase()] = o.ios; } } else { m = ua.match(/NokiaN[^\/]*|Android \d\.\d|webOS\/\d\.\d/); if (m) { o.mobile = m[0]; } if (/webOS/.test(ua)) { o.mobile = 'WebOS'; m = ua.match(/webOS\/([^\s]*);/); if (m && m[1]) { o.webos = numberify(m[1]); } } if (/ Android/.test(ua)) { o.mobile = 'Android'; m = ua.match(/Android ([^\s]*);/); if (m && m[1]) { o.android = numberify(m[1]); } } } m = ua.match(/Chrome\/([^\s]*)/); if (m && m[1]) { o.chrome = numberify(m[1]); // Chrome } else { m = ua.match(/AdobeAIR\/([^\s]*)/); if (m) { o.air = m[0]; // Adobe AIR 1.0 or better } } } if (!o.webkit) { m = ua.match(/Opera[\s\/]([^\s]*)/); if (m && m[1]) { o.opera = numberify(m[1]); m = ua.match(/Version\/([^\s]*)/); if (m && m[1]) { o.opera = numberify(m[1]); // opera 10+ } m = ua.match(/Opera Mini[^;]*/); if (m) { o.mobile = m[0]; // ex: Opera Mini/2.0.4509/1316 } } else { // not opera or webkit m = ua.match(/MSIE\s([^;]*)/); if (m && m[1]) { o.ie = numberify(m[1]); } else { // not opera, webkit, or ie m = ua.match(/Gecko\/([^\s]*)/); if (m) { o.gecko = 1; // Gecko detected, look for revision m = ua.match(/rv:([^\s\)]*)/); if (m && m[1]) { o.gecko = numberify(m[1]); } } } } } } return o; }; JSX.env.ua = JSX.env.parseUA(); JSX.isFunction = function(o) { return (typeof o === 'function') || OP.toString.apply(o) === FUNCTION_TOSTRING; }; JSX._IEEnumFix = (JSX.env.ua.ie) ? function(r, s) { var i, fname, f; for (i=0;iMIT License */ /** * kjur's class library name space *

* This name space provides following name spaces: *

    *
  • {@link KJUR.asn1} - ASN.1 primitive hexadecimal encoder
  • *
  • {@link KJUR.asn1.x509} - ASN.1 structure for X.509 certificate and CRL
  • *
  • {@link KJUR.crypto} - Java Cryptographic Extension(JCE) style MessageDigest/Signature * class and utilities
  • *
*

* NOTE: Please ignore method summary and document of this namespace. This caused by a bug of jsdoc2. * @name KJUR * @namespace kjur's class library name space */ if (typeof KJUR == "undefined" || !KJUR) KJUR = {}; /** * kjur's ASN.1 class library name space *

* This is ITU-T X.690 ASN.1 DER encoder class library and * class structure and methods is very similar to * org.bouncycastle.asn1 package of * well known BouncyCaslte Cryptography Library. * *

PROVIDING ASN.1 PRIMITIVES

* Here are ASN.1 DER primitive classes. *
    *
  • {@link KJUR.asn1.DERBoolean}
  • *
  • {@link KJUR.asn1.DERInteger}
  • *
  • {@link KJUR.asn1.DERBitString}
  • *
  • {@link KJUR.asn1.DEROctetString}
  • *
  • {@link KJUR.asn1.DERNull}
  • *
  • {@link KJUR.asn1.DERObjectIdentifier}
  • *
  • {@link KJUR.asn1.DERUTF8String}
  • *
  • {@link KJUR.asn1.DERNumericString}
  • *
  • {@link KJUR.asn1.DERPrintableString}
  • *
  • {@link KJUR.asn1.DERTeletexString}
  • *
  • {@link KJUR.asn1.DERIA5String}
  • *
  • {@link KJUR.asn1.DERUTCTime}
  • *
  • {@link KJUR.asn1.DERGeneralizedTime}
  • *
  • {@link KJUR.asn1.DERSequence}
  • *
  • {@link KJUR.asn1.DERSet}
  • *
* *

OTHER ASN.1 CLASSES

*
    *
  • {@link KJUR.asn1.ASN1Object}
  • *
  • {@link KJUR.asn1.DERAbstractString}
  • *
  • {@link KJUR.asn1.DERAbstractTime}
  • *
  • {@link KJUR.asn1.DERAbstractStructured}
  • *
  • {@link KJUR.asn1.DERTaggedObject}
  • *
*

* NOTE: Please ignore method summary and document of this namespace. This caused by a bug of jsdoc2. * @name KJUR.asn1 * @namespace */ if (typeof KJUR.asn1 == "undefined" || !KJUR.asn1) KJUR.asn1 = {}; /** * ASN1 utilities class * @name KJUR.asn1.ASN1Util * @classs ASN1 utilities class * @since asn1 1.0.2 */ KJUR.asn1.ASN1Util = new function() { this.integerToByteHex = function(i) { var h = i.toString(16); if ((h.length % 2) == 1) h = '0' + h; return h; }; this.bigIntToMinTwosComplementsHex = function(bigIntegerValue) { var h = bigIntegerValue.toString(16); if (h.substr(0, 1) != '-') { if (h.length % 2 == 1) { h = '0' + h; } else { if (! h.match(/^[0-7]/)) { h = '00' + h; } } } else { var hPos = h.substr(1); var xorLen = hPos.length; if (xorLen % 2 == 1) { xorLen += 1; } else { if (! h.match(/^[0-7]/)) { xorLen += 2; } } var hMask = ''; for (var i = 0; i < xorLen; i++) { hMask += 'f'; } var biMask = new BigInteger(hMask, 16); var biNeg = biMask.xor(bigIntegerValue).add(BigInteger.ONE); h = biNeg.toString(16).replace(/^-/, ''); } return h; }; /** * get PEM string from hexadecimal data and header string * @name getPEMStringFromHex * @memberOf KJUR.asn1.ASN1Util * @function * @param {String} dataHex hexadecimal string of PEM body * @param {String} pemHeader PEM header string (ex. 'RSA PRIVATE KEY') * @return {String} PEM formatted string of input data * @description * @example * var pem = KJUR.asn1.ASN1Util.getPEMStringFromHex('616161', 'RSA PRIVATE KEY'); * // value of pem will be: * -----BEGIN PRIVATE KEY----- * YWFh * -----END PRIVATE KEY----- */ this.getPEMStringFromHex = function(dataHex, pemHeader) { var dataWA = CryptoJS.enc.Hex.parse(dataHex); var dataB64 = CryptoJS.enc.Base64.stringify(dataWA); var pemBody = dataB64.replace(/(.{64})/g, "$1\r\n"); pemBody = pemBody.replace(/\r\n$/, ''); return "-----BEGIN " + pemHeader + "-----\r\n" + pemBody + "\r\n-----END " + pemHeader + "-----\r\n"; }; }; // ******************************************************************** // Abstract ASN.1 Classes // ******************************************************************** // ******************************************************************** /** * base class for ASN.1 DER encoder object * @name KJUR.asn1.ASN1Object * @class base class for ASN.1 DER encoder object * @property {Boolean} isModified flag whether internal data was changed * @property {String} hTLV hexadecimal string of ASN.1 TLV * @property {String} hT hexadecimal string of ASN.1 TLV tag(T) * @property {String} hL hexadecimal string of ASN.1 TLV length(L) * @property {String} hV hexadecimal string of ASN.1 TLV value(V) * @description */ KJUR.asn1.ASN1Object = function() { var isModified = true; var hTLV = null; var hT = '00' var hL = '00'; var hV = ''; /** * get hexadecimal ASN.1 TLV length(L) bytes from TLV value(V) * @name getLengthHexFromValue * @memberOf KJUR.asn1.ASN1Object * @function * @return {String} hexadecimal string of ASN.1 TLV length(L) */ this.getLengthHexFromValue = function() { if (typeof this.hV == "undefined" || this.hV == null) { throw "this.hV is null or undefined."; } if (this.hV.length % 2 == 1) { throw "value hex must be even length: n=" + hV.length + ",v=" + this.hV; } var n = this.hV.length / 2; var hN = n.toString(16); if (hN.length % 2 == 1) { hN = "0" + hN; } if (n < 128) { return hN; } else { var hNlen = hN.length / 2; if (hNlen > 15) { throw "ASN.1 length too long to represent by 8x: n = " + n.toString(16); } var head = 128 + hNlen; return head.toString(16) + hN; } }; /** * get hexadecimal string of ASN.1 TLV bytes * @name getEncodedHex * @memberOf KJUR.asn1.ASN1Object * @function * @return {String} hexadecimal string of ASN.1 TLV */ this.getEncodedHex = function() { if (this.hTLV == null || this.isModified) { this.hV = this.getFreshValueHex(); this.hL = this.getLengthHexFromValue(); this.hTLV = this.hT + this.hL + this.hV; this.isModified = false; //console.error("first time: " + this.hTLV); } return this.hTLV; }; /** * get hexadecimal string of ASN.1 TLV value(V) bytes * @name getValueHex * @memberOf KJUR.asn1.ASN1Object * @function * @return {String} hexadecimal string of ASN.1 TLV value(V) bytes */ this.getValueHex = function() { this.getEncodedHex(); return this.hV; } this.getFreshValueHex = function() { return ''; }; }; // == BEGIN DERAbstractString ================================================ /** * base class for ASN.1 DER string classes * @name KJUR.asn1.DERAbstractString * @class base class for ASN.1 DER string classes * @param {Array} params associative array of parameters (ex. {'str': 'aaa'}) * @property {String} s internal string of value * @extends KJUR.asn1.ASN1Object * @description *
* As for argument 'params' for constructor, you can specify one of * following properties: *
    *
  • str - specify initial ASN.1 value(V) by a string
  • *
  • hex - specify initial ASN.1 value(V) by a hexadecimal string
  • *
* NOTE: 'params' can be omitted. */ KJUR.asn1.DERAbstractString = function(params) { KJUR.asn1.DERAbstractString.superclass.constructor.call(this); var s = null; var hV = null; /** * get string value of this string object * @name getString * @memberOf KJUR.asn1.DERAbstractString * @function * @return {String} string value of this string object */ this.getString = function() { return this.s; }; /** * set value by a string * @name setString * @memberOf KJUR.asn1.DERAbstractString * @function * @param {String} newS value by a string to set */ this.setString = function(newS) { this.hTLV = null; this.isModified = true; this.s = newS; this.hV = stohex(this.s); }; /** * set value by a hexadecimal string * @name setStringHex * @memberOf KJUR.asn1.DERAbstractString * @function * @param {String} newHexString value by a hexadecimal string to set */ this.setStringHex = function(newHexString) { this.hTLV = null; this.isModified = true; this.s = null; this.hV = newHexString; }; this.getFreshValueHex = function() { return this.hV; }; if (typeof params != "undefined") { if (typeof params['str'] != "undefined") { this.setString(params['str']); } else if (typeof params['hex'] != "undefined") { this.setStringHex(params['hex']); } } }; JSX.extend(KJUR.asn1.DERAbstractString, KJUR.asn1.ASN1Object); // == END DERAbstractString ================================================ // == BEGIN DERAbstractTime ================================================== /** * base class for ASN.1 DER Generalized/UTCTime class * @name KJUR.asn1.DERAbstractTime * @class base class for ASN.1 DER Generalized/UTCTime class * @param {Array} params associative array of parameters (ex. {'str': '130430235959Z'}) * @extends KJUR.asn1.ASN1Object * @description * @see KJUR.asn1.ASN1Object - superclass */ KJUR.asn1.DERAbstractTime = function(params) { KJUR.asn1.DERAbstractTime.superclass.constructor.call(this); var s = null; var date = null; // --- PRIVATE METHODS -------------------- this.localDateToUTC = function(d) { utc = d.getTime() + (d.getTimezoneOffset() * 60000); var utcDate = new Date(utc); return utcDate; }; this.formatDate = function(dateObject, type) { var pad = this.zeroPadding; var d = this.localDateToUTC(dateObject); var year = String(d.getFullYear()); if (type == 'utc') year = year.substr(2, 2); var month = pad(String(d.getMonth() + 1), 2); var day = pad(String(d.getDate()), 2); var hour = pad(String(d.getHours()), 2); var min = pad(String(d.getMinutes()), 2); var sec = pad(String(d.getSeconds()), 2); return year + month + day + hour + min + sec + 'Z'; }; this.zeroPadding = function(s, len) { if (s.length >= len) return s; return new Array(len - s.length + 1).join('0') + s; }; // --- PUBLIC METHODS -------------------- /** * get string value of this string object * @name getString * @memberOf KJUR.asn1.DERAbstractTime * @function * @return {String} string value of this time object */ this.getString = function() { return this.s; }; /** * set value by a string * @name setString * @memberOf KJUR.asn1.DERAbstractTime * @function * @param {String} newS value by a string to set such like "130430235959Z" */ this.setString = function(newS) { this.hTLV = null; this.isModified = true; this.s = newS; this.hV = stohex(this.s); }; /** * set value by a Date object * @name setByDateValue * @memberOf KJUR.asn1.DERAbstractTime * @function * @param {Integer} year year of date (ex. 2013) * @param {Integer} month month of date between 1 and 12 (ex. 12) * @param {Integer} day day of month * @param {Integer} hour hours of date * @param {Integer} min minutes of date * @param {Integer} sec seconds of date */ this.setByDateValue = function(year, month, day, hour, min, sec) { var dateObject = new Date(Date.UTC(year, month - 1, day, hour, min, sec, 0)); this.setByDate(dateObject); }; this.getFreshValueHex = function() { return this.hV; }; }; JSX.extend(KJUR.asn1.DERAbstractTime, KJUR.asn1.ASN1Object); // == END DERAbstractTime ================================================== // == BEGIN DERAbstractStructured ============================================ /** * base class for ASN.1 DER structured class * @name KJUR.asn1.DERAbstractStructured * @class base class for ASN.1 DER structured class * @property {Array} asn1Array internal array of ASN1Object * @extends KJUR.asn1.ASN1Object * @description * @see KJUR.asn1.ASN1Object - superclass */ KJUR.asn1.DERAbstractStructured = function(params) { KJUR.asn1.DERAbstractString.superclass.constructor.call(this); var asn1Array = null; /** * set value by array of ASN1Object * @name setByASN1ObjectArray * @memberOf KJUR.asn1.DERAbstractStructured * @function * @param {array} asn1ObjectArray array of ASN1Object to set */ this.setByASN1ObjectArray = function(asn1ObjectArray) { this.hTLV = null; this.isModified = true; this.asn1Array = asn1ObjectArray; }; /** * append an ASN1Object to internal array * @name appendASN1Object * @memberOf KJUR.asn1.DERAbstractStructured * @function * @param {ASN1Object} asn1Object to add */ this.appendASN1Object = function(asn1Object) { this.hTLV = null; this.isModified = true; this.asn1Array.push(asn1Object); }; this.asn1Array = new Array(); if (typeof params != "undefined") { if (typeof params['array'] != "undefined") { this.asn1Array = params['array']; } } }; JSX.extend(KJUR.asn1.DERAbstractStructured, KJUR.asn1.ASN1Object); // ******************************************************************** // ASN.1 Object Classes // ******************************************************************** // ******************************************************************** /** * class for ASN.1 DER Boolean * @name KJUR.asn1.DERBoolean * @class class for ASN.1 DER Boolean * @extends KJUR.asn1.ASN1Object * @description * @see KJUR.asn1.ASN1Object - superclass */ KJUR.asn1.DERBoolean = function() { KJUR.asn1.DERBoolean.superclass.constructor.call(this); this.hT = "01"; this.hTLV = "0101ff"; }; JSX.extend(KJUR.asn1.DERBoolean, KJUR.asn1.ASN1Object); // ******************************************************************** /** * class for ASN.1 DER Integer * @name KJUR.asn1.DERInteger * @class class for ASN.1 DER Integer * @extends KJUR.asn1.ASN1Object * @description *
* As for argument 'params' for constructor, you can specify one of * following properties: *
    *
  • int - specify initial ASN.1 value(V) by integer value
  • *
  • bigint - specify initial ASN.1 value(V) by BigInteger object
  • *
  • hex - specify initial ASN.1 value(V) by a hexadecimal string
  • *
* NOTE: 'params' can be omitted. */ KJUR.asn1.DERInteger = function(params) { KJUR.asn1.DERInteger.superclass.constructor.call(this); this.hT = "02"; /** * set value by Tom Wu's BigInteger object * @name setByBigInteger * @memberOf KJUR.asn1.DERInteger * @function * @param {BigInteger} bigIntegerValue to set */ this.setByBigInteger = function(bigIntegerValue) { this.hTLV = null; this.isModified = true; this.hV = KJUR.asn1.ASN1Util.bigIntToMinTwosComplementsHex(bigIntegerValue); }; /** * set value by integer value * @name setByInteger * @memberOf KJUR.asn1.DERInteger * @function * @param {Integer} integer value to set */ this.setByInteger = function(intValue) { var bi = new BigInteger(String(intValue), 10); this.setByBigInteger(bi); }; /** * set value by integer value * @name setValueHex * @memberOf KJUR.asn1.DERInteger * @function * @param {String} hexadecimal string of integer value * @description *
* NOTE: Value shall be represented by minimum octet length of * two's complement representation. */ this.setValueHex = function(newHexString) { this.hV = newHexString; }; this.getFreshValueHex = function() { return this.hV; }; if (typeof params != "undefined") { if (typeof params['bigint'] != "undefined") { this.setByBigInteger(params['bigint']); } else if (typeof params['int'] != "undefined") { this.setByInteger(params['int']); } else if (typeof params['hex'] != "undefined") { this.setValueHex(params['hex']); } } }; JSX.extend(KJUR.asn1.DERInteger, KJUR.asn1.ASN1Object); // ******************************************************************** /** * class for ASN.1 DER encoded BitString primitive * @name KJUR.asn1.DERBitString * @class class for ASN.1 DER encoded BitString primitive * @extends KJUR.asn1.ASN1Object * @description *
* As for argument 'params' for constructor, you can specify one of * following properties: *
    *
  • bin - specify binary string (ex. '10111')
  • *
  • array - specify array of boolean (ex. [true,false,true,true])
  • *
  • hex - specify hexadecimal string of ASN.1 value(V) including unused bits
  • *
* NOTE: 'params' can be omitted. */ KJUR.asn1.DERBitString = function(params) { KJUR.asn1.DERBitString.superclass.constructor.call(this); this.hT = "03"; /** * set ASN.1 value(V) by a hexadecimal string including unused bits * @name setHexValueIncludingUnusedBits * @memberOf KJUR.asn1.DERBitString * @function * @param {String} newHexStringIncludingUnusedBits */ this.setHexValueIncludingUnusedBits = function(newHexStringIncludingUnusedBits) { this.hTLV = null; this.isModified = true; this.hV = newHexStringIncludingUnusedBits; }; /** * set ASN.1 value(V) by unused bit and hexadecimal string of value * @name setUnusedBitsAndHexValue * @memberOf KJUR.asn1.DERBitString * @function * @param {Integer} unusedBits * @param {String} hValue */ this.setUnusedBitsAndHexValue = function(unusedBits, hValue) { if (unusedBits < 0 || 7 < unusedBits) { throw "unused bits shall be from 0 to 7: u = " + unusedBits; } var hUnusedBits = "0" + unusedBits; this.hTLV = null; this.isModified = true; this.hV = hUnusedBits + hValue; }; /** * set ASN.1 DER BitString by binary string * @name setByBinaryString * @memberOf KJUR.asn1.DERBitString * @function * @param {String} binaryString binary value string (i.e. '10111') * @description * Its unused bits will be calculated automatically by length of * 'binaryValue'.
* NOTE: Trailing zeros '0' will be ignored. */ this.setByBinaryString = function(binaryString) { binaryString = binaryString.replace(/0+$/, ''); var unusedBits = 8 - binaryString.length % 8; if (unusedBits == 8) unusedBits = 0; for (var i = 0; i <= unusedBits; i++) { binaryString += '0'; } var h = ''; for (var i = 0; i < binaryString.length - 1; i += 8) { var b = binaryString.substr(i, 8); var x = parseInt(b, 2).toString(16); if (x.length == 1) x = '0' + x; h += x; } this.hTLV = null; this.isModified = true; this.hV = '0' + unusedBits + h; }; /** * set ASN.1 TLV value(V) by an array of boolean * @name setByBooleanArray * @memberOf KJUR.asn1.DERBitString * @function * @param {array} booleanArray array of boolean (ex. [true, false, true]) * @description * NOTE: Trailing falses will be ignored. */ this.setByBooleanArray = function(booleanArray) { var s = ''; for (var i = 0; i < booleanArray.length; i++) { if (booleanArray[i] == true) { s += '1'; } else { s += '0'; } } this.setByBinaryString(s); }; /** * generate an array of false with specified length * @name newFalseArray * @memberOf KJUR.asn1.DERBitString * @function * @param {Integer} nLength length of array to generate * @return {array} array of boolean faluse * @description * This static method may be useful to initialize boolean array. */ this.newFalseArray = function(nLength) { var a = new Array(nLength); for (var i = 0; i < nLength; i++) { a[i] = false; } return a; }; this.getFreshValueHex = function() { return this.hV; }; if (typeof params != "undefined") { if (typeof params['hex'] != "undefined") { this.setHexValueIncludingUnusedBits(params['hex']); } else if (typeof params['bin'] != "undefined") { this.setByBinaryString(params['bin']); } else if (typeof params['array'] != "undefined") { this.setByBooleanArray(params['array']); } } }; JSX.extend(KJUR.asn1.DERBitString, KJUR.asn1.ASN1Object); // ******************************************************************** /** * class for ASN.1 DER OctetString * @name KJUR.asn1.DEROctetString * @class class for ASN.1 DER OctetString * @param {Array} params associative array of parameters (ex. {'str': 'aaa'}) * @extends KJUR.asn1.DERAbstractString * @description * @see KJUR.asn1.DERAbstractString - superclass */ KJUR.asn1.DEROctetString = function(params) { KJUR.asn1.DEROctetString.superclass.constructor.call(this, params); this.hT = "04"; }; JSX.extend(KJUR.asn1.DEROctetString, KJUR.asn1.DERAbstractString); // ******************************************************************** /** * class for ASN.1 DER Null * @name KJUR.asn1.DERNull * @class class for ASN.1 DER Null * @extends KJUR.asn1.ASN1Object * @description * @see KJUR.asn1.ASN1Object - superclass */ KJUR.asn1.DERNull = function() { KJUR.asn1.DERNull.superclass.constructor.call(this); this.hT = "05"; this.hTLV = "0500"; }; JSX.extend(KJUR.asn1.DERNull, KJUR.asn1.ASN1Object); // ******************************************************************** /** * class for ASN.1 DER ObjectIdentifier * @name KJUR.asn1.DERObjectIdentifier * @class class for ASN.1 DER ObjectIdentifier * @param {Array} params associative array of parameters (ex. {'oid': '2.5.4.5'}) * @extends KJUR.asn1.ASN1Object * @description *
* As for argument 'params' for constructor, you can specify one of * following properties: *
    *
  • oid - specify initial ASN.1 value(V) by a oid string (ex. 2.5.4.13)
  • *
  • hex - specify initial ASN.1 value(V) by a hexadecimal string
  • *
* NOTE: 'params' can be omitted. */ KJUR.asn1.DERObjectIdentifier = function(params) { var itox = function(i) { var h = i.toString(16); if (h.length == 1) h = '0' + h; return h; }; var roidtox = function(roid) { var h = ''; var bi = new BigInteger(roid, 10); var b = bi.toString(2); var padLen = 7 - b.length % 7; if (padLen == 7) padLen = 0; var bPad = ''; for (var i = 0; i < padLen; i++) bPad += '0'; b = bPad + b; for (var i = 0; i < b.length - 1; i += 7) { var b8 = b.substr(i, 7); if (i != b.length - 7) b8 = '1' + b8; h += itox(parseInt(b8, 2)); } return h; } KJUR.asn1.DERObjectIdentifier.superclass.constructor.call(this); this.hT = "06"; /** * set value by a hexadecimal string * @name setValueHex * @memberOf KJUR.asn1.DERObjectIdentifier * @function * @param {String} newHexString hexadecimal value of OID bytes */ this.setValueHex = function(newHexString) { this.hTLV = null; this.isModified = true; this.s = null; this.hV = newHexString; }; /** * set value by a OID string * @name setValueOidString * @memberOf KJUR.asn1.DERObjectIdentifier * @function * @param {String} oidString OID string (ex. 2.5.4.13) */ this.setValueOidString = function(oidString) { if (! oidString.match(/^[0-9.]+$/)) { throw "malformed oid string: " + oidString; } var h = ''; var a = oidString.split('.'); var i0 = parseInt(a[0]) * 40 + parseInt(a[1]); h += itox(i0); a.splice(0, 2); for (var i = 0; i < a.length; i++) { h += roidtox(a[i]); } this.hTLV = null; this.isModified = true; this.s = null; this.hV = h; }; /** * set value by a OID name * @name setValueName * @memberOf KJUR.asn1.DERObjectIdentifier * @function * @param {String} oidName OID name (ex. 'serverAuth') * @since 1.0.1 * @description * OID name shall be defined in 'KJUR.asn1.x509.OID.name2oidList'. * Otherwise raise error. */ this.setValueName = function(oidName) { if (typeof KJUR.asn1.x509.OID.name2oidList[oidName] != "undefined") { var oid = KJUR.asn1.x509.OID.name2oidList[oidName]; this.setValueOidString(oid); } else { throw "DERObjectIdentifier oidName undefined: " + oidName; } }; this.getFreshValueHex = function() { return this.hV; }; if (typeof params != "undefined") { if (typeof params['oid'] != "undefined") { this.setValueOidString(params['oid']); } else if (typeof params['hex'] != "undefined") { this.setValueHex(params['hex']); } else if (typeof params['name'] != "undefined") { this.setValueName(params['name']); } } }; JSX.extend(KJUR.asn1.DERObjectIdentifier, KJUR.asn1.ASN1Object); // ******************************************************************** /** * class for ASN.1 DER UTF8String * @name KJUR.asn1.DERUTF8String * @class class for ASN.1 DER UTF8String * @param {Array} params associative array of parameters (ex. {'str': 'aaa'}) * @extends KJUR.asn1.DERAbstractString * @description * @see KJUR.asn1.DERAbstractString - superclass */ KJUR.asn1.DERUTF8String = function(params) { KJUR.asn1.DERUTF8String.superclass.constructor.call(this, params); this.hT = "0c"; }; JSX.extend(KJUR.asn1.DERUTF8String, KJUR.asn1.DERAbstractString); // ******************************************************************** /** * class for ASN.1 DER NumericString * @name KJUR.asn1.DERNumericString * @class class for ASN.1 DER NumericString * @param {Array} params associative array of parameters (ex. {'str': 'aaa'}) * @extends KJUR.asn1.DERAbstractString * @description * @see KJUR.asn1.DERAbstractString - superclass */ KJUR.asn1.DERNumericString = function(params) { KJUR.asn1.DERNumericString.superclass.constructor.call(this, params); this.hT = "12"; }; JSX.extend(KJUR.asn1.DERNumericString, KJUR.asn1.DERAbstractString); // ******************************************************************** /** * class for ASN.1 DER PrintableString * @name KJUR.asn1.DERPrintableString * @class class for ASN.1 DER PrintableString * @param {Array} params associative array of parameters (ex. {'str': 'aaa'}) * @extends KJUR.asn1.DERAbstractString * @description * @see KJUR.asn1.DERAbstractString - superclass */ KJUR.asn1.DERPrintableString = function(params) { KJUR.asn1.DERPrintableString.superclass.constructor.call(this, params); this.hT = "13"; }; JSX.extend(KJUR.asn1.DERPrintableString, KJUR.asn1.DERAbstractString); // ******************************************************************** /** * class for ASN.1 DER TeletexString * @name KJUR.asn1.DERTeletexString * @class class for ASN.1 DER TeletexString * @param {Array} params associative array of parameters (ex. {'str': 'aaa'}) * @extends KJUR.asn1.DERAbstractString * @description * @see KJUR.asn1.DERAbstractString - superclass */ KJUR.asn1.DERTeletexString = function(params) { KJUR.asn1.DERTeletexString.superclass.constructor.call(this, params); this.hT = "14"; }; JSX.extend(KJUR.asn1.DERTeletexString, KJUR.asn1.DERAbstractString); // ******************************************************************** /** * class for ASN.1 DER IA5String * @name KJUR.asn1.DERIA5String * @class class for ASN.1 DER IA5String * @param {Array} params associative array of parameters (ex. {'str': 'aaa'}) * @extends KJUR.asn1.DERAbstractString * @description * @see KJUR.asn1.DERAbstractString - superclass */ KJUR.asn1.DERIA5String = function(params) { KJUR.asn1.DERIA5String.superclass.constructor.call(this, params); this.hT = "16"; }; JSX.extend(KJUR.asn1.DERIA5String, KJUR.asn1.DERAbstractString); // ******************************************************************** /** * class for ASN.1 DER UTCTime * @name KJUR.asn1.DERUTCTime * @class class for ASN.1 DER UTCTime * @param {Array} params associative array of parameters (ex. {'str': '130430235959Z'}) * @extends KJUR.asn1.DERAbstractTime * @description *
* As for argument 'params' for constructor, you can specify one of * following properties: *
    *
  • str - specify initial ASN.1 value(V) by a string (ex.'130430235959Z')
  • *
  • hex - specify initial ASN.1 value(V) by a hexadecimal string
  • *
  • date - specify Date object.
  • *
* NOTE: 'params' can be omitted. *

EXAMPLES

* @example * var d1 = new KJUR.asn1.DERUTCTime(); * d1.setString('130430125959Z'); * * var d2 = new KJUR.asn1.DERUTCTime({'str': '130430125959Z'}); * * var d3 = new KJUR.asn1.DERUTCTime({'date': new Date(Date.UTC(2015, 0, 31, 0, 0, 0, 0))}); */ KJUR.asn1.DERUTCTime = function(params) { KJUR.asn1.DERUTCTime.superclass.constructor.call(this, params); this.hT = "17"; /** * set value by a Date object * @name setByDate * @memberOf KJUR.asn1.DERUTCTime * @function * @param {Date} dateObject Date object to set ASN.1 value(V) */ this.setByDate = function(dateObject) { this.hTLV = null; this.isModified = true; this.date = dateObject; this.s = this.formatDate(this.date, 'utc'); this.hV = stohex(this.s); }; if (typeof params != "undefined") { if (typeof params['str'] != "undefined") { this.setString(params['str']); } else if (typeof params['hex'] != "undefined") { this.setStringHex(params['hex']); } else if (typeof params['date'] != "undefined") { this.setByDate(params['date']); } } }; JSX.extend(KJUR.asn1.DERUTCTime, KJUR.asn1.DERAbstractTime); // ******************************************************************** /** * class for ASN.1 DER GeneralizedTime * @name KJUR.asn1.DERGeneralizedTime * @class class for ASN.1 DER GeneralizedTime * @param {Array} params associative array of parameters (ex. {'str': '20130430235959Z'}) * @extends KJUR.asn1.DERAbstractTime * @description *
* As for argument 'params' for constructor, you can specify one of * following properties: *
    *
  • str - specify initial ASN.1 value(V) by a string (ex.'20130430235959Z')
  • *
  • hex - specify initial ASN.1 value(V) by a hexadecimal string
  • *
  • date - specify Date object.
  • *
* NOTE: 'params' can be omitted. */ KJUR.asn1.DERGeneralizedTime = function(params) { KJUR.asn1.DERGeneralizedTime.superclass.constructor.call(this, params); this.hT = "18"; /** * set value by a Date object * @name setByDate * @memberOf KJUR.asn1.DERGeneralizedTime * @function * @param {Date} dateObject Date object to set ASN.1 value(V) * @example * When you specify UTC time, use 'Date.UTC' method like this:
* var o = new DERUTCTime(); * var date = new Date(Date.UTC(2015, 0, 31, 23, 59, 59, 0)); #2015JAN31 23:59:59 * o.setByDate(date); */ this.setByDate = function(dateObject) { this.hTLV = null; this.isModified = true; this.date = dateObject; this.s = this.formatDate(this.date, 'gen'); this.hV = stohex(this.s); }; if (typeof params != "undefined") { if (typeof params['str'] != "undefined") { this.setString(params['str']); } else if (typeof params['hex'] != "undefined") { this.setStringHex(params['hex']); } else if (typeof params['date'] != "undefined") { this.setByDate(params['date']); } } }; JSX.extend(KJUR.asn1.DERGeneralizedTime, KJUR.asn1.DERAbstractTime); // ******************************************************************** /** * class for ASN.1 DER Sequence * @name KJUR.asn1.DERSequence * @class class for ASN.1 DER Sequence * @extends KJUR.asn1.DERAbstractStructured * @description *
* As for argument 'params' for constructor, you can specify one of * following properties: *
    *
  • array - specify array of ASN1Object to set elements of content
  • *
* NOTE: 'params' can be omitted. */ KJUR.asn1.DERSequence = function(params) { KJUR.asn1.DERSequence.superclass.constructor.call(this, params); this.hT = "30"; this.getFreshValueHex = function() { var h = ''; for (var i = 0; i < this.asn1Array.length; i++) { var asn1Obj = this.asn1Array[i]; h += asn1Obj.getEncodedHex(); } this.hV = h; return this.hV; }; }; JSX.extend(KJUR.asn1.DERSequence, KJUR.asn1.DERAbstractStructured); // ******************************************************************** /** * class for ASN.1 DER Set * @name KJUR.asn1.DERSet * @class class for ASN.1 DER Set * @extends KJUR.asn1.DERAbstractStructured * @description *
* As for argument 'params' for constructor, you can specify one of * following properties: *
    *
  • array - specify array of ASN1Object to set elements of content
  • *
* NOTE: 'params' can be omitted. */ KJUR.asn1.DERSet = function(params) { KJUR.asn1.DERSet.superclass.constructor.call(this, params); this.hT = "31"; this.getFreshValueHex = function() { var a = new Array(); for (var i = 0; i < this.asn1Array.length; i++) { var asn1Obj = this.asn1Array[i]; a.push(asn1Obj.getEncodedHex()); } a.sort(); this.hV = a.join(''); return this.hV; }; }; JSX.extend(KJUR.asn1.DERSet, KJUR.asn1.DERAbstractStructured); // ******************************************************************** /** * class for ASN.1 DER TaggedObject * @name KJUR.asn1.DERTaggedObject * @class class for ASN.1 DER TaggedObject * @extends KJUR.asn1.ASN1Object * @description *
* Parameter 'tagNoNex' is ASN.1 tag(T) value for this object. * For example, if you find '[1]' tag in a ASN.1 dump, * 'tagNoHex' will be 'a1'. *
* As for optional argument 'params' for constructor, you can specify *ANY* of * following properties: *
    *
  • explicit - specify true if this is explicit tag otherwise false * (default is 'true').
  • *
  • tag - specify tag (default is 'a0' which means [0])
  • *
  • obj - specify ASN1Object which is tagged
  • *
* @example * d1 = new KJUR.asn1.DERUTF8String({'str':'a'}); * d2 = new KJUR.asn1.DERTaggedObject({'obj': d1}); * hex = d2.getEncodedHex(); */ KJUR.asn1.DERTaggedObject = function(params) { KJUR.asn1.DERTaggedObject.superclass.constructor.call(this); this.hT = "a0"; this.hV = ''; this.isExplicit = true; this.asn1Object = null; /** * set value by an ASN1Object * @name setString * @memberOf KJUR.asn1.DERTaggedObject * @function * @param {Boolean} isExplicitFlag flag for explicit/implicit tag * @param {Integer} tagNoHex hexadecimal string of ASN.1 tag * @param {ASN1Object} asn1Object ASN.1 to encapsulate */ this.setASN1Object = function(isExplicitFlag, tagNoHex, asn1Object) { this.hT = tagNoHex; this.isExplicit = isExplicitFlag; this.asn1Object = asn1Object; if (this.isExplicit) { this.hV = this.asn1Object.getEncodedHex(); this.hTLV = null; this.isModified = true; } else { this.hV = null; this.hTLV = asn1Object.getEncodedHex(); this.hTLV = this.hTLV.replace(/^../, tagNoHex); this.isModified = false; } }; this.getFreshValueHex = function() { return this.hV; }; if (typeof params != "undefined") { if (typeof params['tag'] != "undefined") { this.hT = params['tag']; } if (typeof params['explicit'] != "undefined") { this.isExplicit = params['explicit']; } if (typeof params['obj'] != "undefined") { this.asn1Object = params['obj']; this.setASN1Object(this.isExplicit, this.hT, this.asn1Object); } } }; JSX.extend(KJUR.asn1.DERTaggedObject, KJUR.asn1.ASN1Object);// Hex JavaScript decoder // Copyright (c) 2008-2013 Lapo Luchini // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. /*jshint browser: true, strict: true, immed: true, latedef: true, undef: true, regexdash: false */ (function (undefined) { "use strict"; var Hex = {}, decoder; Hex.decode = function(a) { var i; if (decoder === undefined) { var hex = "0123456789ABCDEF", ignore = " \f\n\r\t\u00A0\u2028\u2029"; decoder = []; for (i = 0; i < 16; ++i) decoder[hex.charAt(i)] = i; hex = hex.toLowerCase(); for (i = 10; i < 16; ++i) decoder[hex.charAt(i)] = i; for (i = 0; i < ignore.length; ++i) decoder[ignore.charAt(i)] = -1; } var out = [], bits = 0, char_count = 0; for (i = 0; i < a.length; ++i) { var c = a.charAt(i); if (c == '=') break; c = decoder[c]; if (c == -1) continue; if (c === undefined) throw 'Illegal character at offset ' + i; bits |= c; if (++char_count >= 2) { out[out.length] = bits; bits = 0; char_count = 0; } else { bits <<= 4; } } if (char_count) throw "Hex encoding incomplete: 4 bits missing"; return out; }; // export globals window.Hex = Hex; })(); // Base64 JavaScript decoder // Copyright (c) 2008-2013 Lapo Luchini // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. /*jshint browser: true, strict: true, immed: true, latedef: true, undef: true, regexdash: false */ (function (undefined) { "use strict"; var Base64 = {}, decoder; Base64.decode = function (a) { var i; if (decoder === undefined) { var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", ignore = "= \f\n\r\t\u00A0\u2028\u2029"; decoder = []; for (i = 0; i < 64; ++i) decoder[b64.charAt(i)] = i; for (i = 0; i < ignore.length; ++i) decoder[ignore.charAt(i)] = -1; } var out = []; var bits = 0, char_count = 0; for (i = 0; i < a.length; ++i) { var c = a.charAt(i); if (c == '=') break; c = decoder[c]; if (c == -1) continue; if (c === undefined) throw 'Illegal character at offset ' + i; bits |= c; if (++char_count >= 4) { out[out.length] = (bits >> 16); out[out.length] = (bits >> 8) & 0xFF; out[out.length] = bits & 0xFF; bits = 0; char_count = 0; } else { bits <<= 6; } } switch (char_count) { case 1: throw "Base64 encoding incomplete: at least 2 bits missing"; case 2: out[out.length] = (bits >> 10); break; case 3: out[out.length] = (bits >> 16); out[out.length] = (bits >> 8) & 0xFF; break; } return out; }; Base64.re = /-----BEGIN [^-]+-----([A-Za-z0-9+\/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+\/=\s]+)====/; Base64.unarmor = function (a) { var m = Base64.re.exec(a); if (m) { if (m[1]) a = m[1]; else if (m[2]) a = m[2]; else throw "RegExp out of sync"; } return Base64.decode(a); }; // export globals window.Base64 = Base64; })(); // ASN.1 JavaScript decoder // Copyright (c) 2008-2013 Lapo Luchini // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. /*jshint browser: true, strict: true, immed: true, latedef: true, undef: true, regexdash: false */ /*global oids */ (function (undefined) { "use strict"; var hardLimit = 100, ellipsis = "\u2026", DOM = { tag: function (tagName, className) { var t = document.createElement(tagName); t.className = className; return t; }, text: function (str) { return document.createTextNode(str); } }; function Stream(enc, pos) { if (enc instanceof Stream) { this.enc = enc.enc; this.pos = enc.pos; } else { this.enc = enc; this.pos = pos; } } Stream.prototype.get = function (pos) { if (pos === undefined) pos = this.pos++; if (pos >= this.enc.length) throw 'Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length; return this.enc[pos]; }; Stream.prototype.hexDigits = "0123456789ABCDEF"; Stream.prototype.hexByte = function (b) { return this.hexDigits.charAt((b >> 4) & 0xF) + this.hexDigits.charAt(b & 0xF); }; Stream.prototype.hexDump = function (start, end, raw) { var s = ""; for (var i = start; i < end; ++i) { s += this.hexByte(this.get(i)); if (raw !== true) switch (i & 0xF) { case 0x7: s += " "; break; case 0xF: s += "\n"; break; default: s += " "; } } return s; }; Stream.prototype.parseStringISO = function (start, end) { var s = ""; for (var i = start; i < end; ++i) s += String.fromCharCode(this.get(i)); return s; }; Stream.prototype.parseStringUTF = function (start, end) { var s = ""; for (var i = start; i < end; ) { var c = this.get(i++); if (c < 128) s += String.fromCharCode(c); else if ((c > 191) && (c < 224)) s += String.fromCharCode(((c & 0x1F) << 6) | (this.get(i++) & 0x3F)); else s += String.fromCharCode(((c & 0x0F) << 12) | ((this.get(i++) & 0x3F) << 6) | (this.get(i++) & 0x3F)); } return s; }; Stream.prototype.parseStringBMP = function (start, end) { var str = "" for (var i = start; i < end; i += 2) { var high_byte = this.get(i); var low_byte = this.get(i + 1); str += String.fromCharCode( (high_byte << 8) + low_byte ); } return str; }; Stream.prototype.reTime = /^((?:1[89]|2\d)?\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/; Stream.prototype.parseTime = function (start, end) { var s = this.parseStringISO(start, end), m = this.reTime.exec(s); if (!m) return "Unrecognized time: " + s; s = m[1] + "-" + m[2] + "-" + m[3] + " " + m[4]; if (m[5]) { s += ":" + m[5]; if (m[6]) { s += ":" + m[6]; if (m[7]) s += "." + m[7]; } } if (m[8]) { s += " UTC"; if (m[8] != 'Z') { s += m[8]; if (m[9]) s += ":" + m[9]; } } return s; }; Stream.prototype.parseInteger = function (start, end) { //TODO support negative numbers var len = end - start; if (len > 4) { len <<= 3; var s = this.get(start); if (s === 0) len -= 8; else while (s < 128) { s <<= 1; --len; } return "(" + len + " bit)"; } var n = 0; for (var i = start; i < end; ++i) n = (n << 8) | this.get(i); return n; }; Stream.prototype.parseBitString = function (start, end) { var unusedBit = this.get(start), lenBit = ((end - start - 1) << 3) - unusedBit, s = "(" + lenBit + " bit)"; if (lenBit <= 20) { var skip = unusedBit; s += " "; for (var i = end - 1; i > start; --i) { var b = this.get(i); for (var j = skip; j < 8; ++j) s += (b >> j) & 1 ? "1" : "0"; skip = 0; } } return s; }; Stream.prototype.parseOctetString = function (start, end) { var len = end - start, s = "(" + len + " byte) "; if (len > hardLimit) end = start + hardLimit; for (var i = start; i < end; ++i) s += this.hexByte(this.get(i)); //TODO: also try Latin1? if (len > hardLimit) s += ellipsis; return s; }; Stream.prototype.parseOID = function (start, end) { var s = '', n = 0, bits = 0; for (var i = start; i < end; ++i) { var v = this.get(i); n = (n << 7) | (v & 0x7F); bits += 7; if (!(v & 0x80)) { // finished if (s === '') { var m = n < 80 ? n < 40 ? 0 : 1 : 2; s = m + "." + (n - m * 40); } else s += "." + ((bits >= 31) ? "bigint" : n); n = bits = 0; } } return s; }; function ASN1(stream, header, length, tag, sub) { this.stream = stream; this.header = header; this.length = length; this.tag = tag; this.sub = sub; } ASN1.prototype.typeName = function () { if (this.tag === undefined) return "unknown"; var tagClass = this.tag >> 6, tagConstructed = (this.tag >> 5) & 1, tagNumber = this.tag & 0x1F; switch (tagClass) { case 0: // universal switch (tagNumber) { case 0x00: return "EOC"; case 0x01: return "BOOLEAN"; case 0x02: return "INTEGER"; case 0x03: return "BIT_STRING"; case 0x04: return "OCTET_STRING"; case 0x05: return "NULL"; case 0x06: return "OBJECT_IDENTIFIER"; case 0x07: return "ObjectDescriptor"; case 0x08: return "EXTERNAL"; case 0x09: return "REAL"; case 0x0A: return "ENUMERATED"; case 0x0B: return "EMBEDDED_PDV"; case 0x0C: return "UTF8String"; case 0x10: return "SEQUENCE"; case 0x11: return "SET"; case 0x12: return "NumericString"; case 0x13: return "PrintableString"; // ASCII subset case 0x14: return "TeletexString"; // aka T61String case 0x15: return "VideotexString"; case 0x16: return "IA5String"; // ASCII case 0x17: return "UTCTime"; case 0x18: return "GeneralizedTime"; case 0x19: return "GraphicString"; case 0x1A: return "VisibleString"; // ASCII subset case 0x1B: return "GeneralString"; case 0x1C: return "UniversalString"; case 0x1E: return "BMPString"; default: return "Universal_" + tagNumber.toString(16); } case 1: return "Application_" + tagNumber.toString(16); case 2: return "[" + tagNumber + "]"; // Context case 3: return "Private_" + tagNumber.toString(16); } }; ASN1.prototype.reSeemsASCII = /^[ -~]+$/; ASN1.prototype.content = function () { if (this.tag === undefined) return null; var tagClass = this.tag >> 6, tagNumber = this.tag & 0x1F, content = this.posContent(), len = Math.abs(this.length); if (tagClass !== 0) { // universal if (this.sub !== null) return "(" + this.sub.length + " elem)"; //TODO: TRY TO PARSE ASCII STRING var s = this.stream.parseStringISO(content, content + Math.min(len, hardLimit)); if (this.reSeemsASCII.test(s)) return s.substring(0, 2 * hardLimit) + ((s.length > 2 * hardLimit) ? ellipsis : ""); else return this.stream.parseOctetString(content, content + len); } switch (tagNumber) { case 0x01: // BOOLEAN return (this.stream.get(content) === 0) ? "false" : "true"; case 0x02: // INTEGER return this.stream.parseInteger(content, content + len); case 0x03: // BIT_STRING return this.sub ? "(" + this.sub.length + " elem)" : this.stream.parseBitString(content, content + len); case 0x04: // OCTET_STRING return this.sub ? "(" + this.sub.length + " elem)" : this.stream.parseOctetString(content, content + len); //case 0x05: // NULL case 0x06: // OBJECT_IDENTIFIER return this.stream.parseOID(content, content + len); //case 0x07: // ObjectDescriptor //case 0x08: // EXTERNAL //case 0x09: // REAL //case 0x0A: // ENUMERATED //case 0x0B: // EMBEDDED_PDV case 0x10: // SEQUENCE case 0x11: // SET return "(" + this.sub.length + " elem)"; case 0x0C: // UTF8String return this.stream.parseStringUTF(content, content + len); case 0x12: // NumericString case 0x13: // PrintableString case 0x14: // TeletexString case 0x15: // VideotexString case 0x16: // IA5String //case 0x19: // GraphicString case 0x1A: // VisibleString //case 0x1B: // GeneralString //case 0x1C: // UniversalString return this.stream.parseStringISO(content, content + len); case 0x1E: // BMPString return this.stream.parseStringBMP(content, content + len); case 0x17: // UTCTime case 0x18: // GeneralizedTime return this.stream.parseTime(content, content + len); } return null; }; ASN1.prototype.toString = function () { return this.typeName() + "@" + this.stream.pos + "[header:" + this.header + ",length:" + this.length + ",sub:" + ((this.sub === null) ? 'null' : this.sub.length) + "]"; }; ASN1.prototype.print = function (indent) { if (indent === undefined) indent = ''; document.writeln(indent + this); if (this.sub !== null) { indent += ' '; for (var i = 0, max = this.sub.length; i < max; ++i) this.sub[i].print(indent); } }; ASN1.prototype.toPrettyString = function (indent) { if (indent === undefined) indent = ''; var s = indent + this.typeName() + " @" + this.stream.pos; if (this.length >= 0) s += "+"; s += this.length; if (this.tag & 0x20) s += " (constructed)"; else if (((this.tag == 0x03) || (this.tag == 0x04)) && (this.sub !== null)) s += " (encapsulates)"; s += "\n"; if (this.sub !== null) { indent += ' '; for (var i = 0, max = this.sub.length; i < max; ++i) s += this.sub[i].toPrettyString(indent); } return s; }; ASN1.prototype.toDOM = function () { var node = DOM.tag("div", "node"); node.asn1 = this; var head = DOM.tag("div", "head"); var s = this.typeName().replace(/_/g, " "); head.innerHTML = s; var content = this.content(); if (content !== null) { content = String(content).replace(/"; s += "Length: " + this.header + "+"; if (this.length >= 0) s += this.length; else s += (-this.length) + " (undefined)"; if (this.tag & 0x20) s += "
(constructed)"; else if (((this.tag == 0x03) || (this.tag == 0x04)) && (this.sub !== null)) s += "
(encapsulates)"; //TODO if (this.tag == 0x03) s += "Unused bits: " if (content !== null) { s += "
Value:
" + content + ""; if ((typeof oids === 'object') && (this.tag == 0x06)) { var oid = oids[content]; if (oid) { if (oid.d) s += "
" + oid.d; if (oid.c) s += "
" + oid.c; if (oid.w) s += "
(warning!)"; } } } value.innerHTML = s; node.appendChild(value); var sub = DOM.tag("div", "sub"); if (this.sub !== null) { for (var i = 0, max = this.sub.length; i < max; ++i) sub.appendChild(this.sub[i].toDOM()); } node.appendChild(sub); head.onclick = function () { node.className = (node.className == "node collapsed") ? "node" : "node collapsed"; }; return node; }; ASN1.prototype.posStart = function () { return this.stream.pos; }; ASN1.prototype.posContent = function () { return this.stream.pos + this.header; }; ASN1.prototype.posEnd = function () { return this.stream.pos + this.header + Math.abs(this.length); }; ASN1.prototype.fakeHover = function (current) { this.node.className += " hover"; if (current) this.head.className += " hover"; }; ASN1.prototype.fakeOut = function (current) { var re = / ?hover/; this.node.className = this.node.className.replace(re, ""); if (current) this.head.className = this.head.className.replace(re, ""); }; ASN1.prototype.toHexDOM_sub = function (node, className, stream, start, end) { if (start >= end) return; var sub = DOM.tag("span", className); sub.appendChild(DOM.text( stream.hexDump(start, end))); node.appendChild(sub); }; ASN1.prototype.toHexDOM = function (root) { var node = DOM.tag("span", "hex"); if (root === undefined) root = node; this.head.hexNode = node; this.head.onmouseover = function () { this.hexNode.className = "hexCurrent"; }; this.head.onmouseout = function () { this.hexNode.className = "hex"; }; node.asn1 = this; node.onmouseover = function () { var current = !root.selected; if (current) { root.selected = this.asn1; this.className = "hexCurrent"; } this.asn1.fakeHover(current); }; node.onmouseout = function () { var current = (root.selected == this.asn1); this.asn1.fakeOut(current); if (current) { root.selected = null; this.className = "hex"; } }; this.toHexDOM_sub(node, "tag", this.stream, this.posStart(), this.posStart() + 1); this.toHexDOM_sub(node, (this.length >= 0) ? "dlen" : "ulen", this.stream, this.posStart() + 1, this.posContent()); if (this.sub === null) node.appendChild(DOM.text( this.stream.hexDump(this.posContent(), this.posEnd()))); else if (this.sub.length > 0) { var first = this.sub[0]; var last = this.sub[this.sub.length - 1]; this.toHexDOM_sub(node, "intro", this.stream, this.posContent(), first.posStart()); for (var i = 0, max = this.sub.length; i < max; ++i) node.appendChild(this.sub[i].toHexDOM(root)); this.toHexDOM_sub(node, "outro", this.stream, last.posEnd(), this.posEnd()); } return node; }; ASN1.prototype.toHexString = function (root) { return this.stream.hexDump(this.posStart(), this.posEnd(), true); }; ASN1.decodeLength = function (stream) { var buf = stream.get(), len = buf & 0x7F; if (len == buf) return len; if (len > 3) throw "Length over 24 bits not supported at position " + (stream.pos - 1); if (len === 0) return -1; // undefined buf = 0; for (var i = 0; i < len; ++i) buf = (buf << 8) | stream.get(); return buf; }; ASN1.hasContent = function (tag, len, stream) { if (tag & 0x20) // constructed return true; if ((tag < 0x03) || (tag > 0x04)) return false; var p = new Stream(stream); if (tag == 0x03) p.get(); // BitString unused bits, must be in [0, 7] var subTag = p.get(); if ((subTag >> 6) & 0x01) // not (universal or context) return false; try { var subLength = ASN1.decodeLength(p); return ((p.pos - stream.pos) + subLength == len); } catch (exception) { return false; } }; ASN1.decode = function (stream) { if (!(stream instanceof Stream)) stream = new Stream(stream, 0); var streamStart = new Stream(stream), tag = stream.get(), len = ASN1.decodeLength(stream), header = stream.pos - streamStart.pos, sub = null; if (ASN1.hasContent(tag, len, stream)) { // it has content, so we decode it var start = stream.pos; if (tag == 0x03) stream.get(); // skip BitString unused bits, must be in [0, 7] sub = []; if (len >= 0) { // definite length var end = start + len; while (stream.pos < end) sub[sub.length] = ASN1.decode(stream); if (stream.pos != end) throw "Content size is not correct for container starting at offset " + start; } else { // undefined length try { for (;;) { var s = ASN1.decode(stream); if (s.tag === 0) break; sub[sub.length] = s; } len = start - stream.pos; } catch (e) { throw "Exception while decoding undefined length content: " + e; } } } else stream.pos += len; // skip content return new ASN1(streamStart, header, len, tag, sub); }; ASN1.test = function () { var test = [ { value: [0x27], expected: 0x27 }, { value: [0x81, 0xC9], expected: 0xC9 }, { value: [0x83, 0xFE, 0xDC, 0xBA], expected: 0xFEDCBA } ]; for (var i = 0, max = test.length; i < max; ++i) { var pos = 0, stream = new Stream(test[i].value, 0), res = ASN1.decodeLength(stream); if (res != test[i].expected) document.write("In test[" + i + "] expected " + test[i].expected + " got " + res + "\n"); } }; // export globals window.ASN1 = ASN1; })(); /** * Retrieve the hexadecimal value (as a string) of the current ASN.1 element * @returns {string} * @public */ ASN1.prototype.getHexStringValue = function () { var hexString = this.toHexString(); var offset = this.header * 2; var length = this.length * 2; return hexString.substr(offset, length); }; /** * Method to parse a pem encoded string containing both a public or private key. * The method will translate the pem encoded string in a der encoded string and * will parse private key and public key parameters. This method accepts public key * in the rsaencryption pkcs #1 format (oid: 1.2.840.113549.1.1.1). * * @todo Check how many rsa formats use the same format of pkcs #1. * * The format is defined as: * PublicKeyInfo ::= SEQUENCE { * algorithm AlgorithmIdentifier, * PublicKey BIT STRING * } * Where AlgorithmIdentifier is: * AlgorithmIdentifier ::= SEQUENCE { * algorithm OBJECT IDENTIFIER, the OID of the enc algorithm * parameters ANY DEFINED BY algorithm OPTIONAL (NULL for PKCS #1) * } * and PublicKey is a SEQUENCE encapsulated in a BIT STRING * RSAPublicKey ::= SEQUENCE { * modulus INTEGER, -- n * publicExponent INTEGER -- e * } * it's possible to examine the structure of the keys obtained from openssl using * an asn.1 dumper as the one used here to parse the components: http://lapo.it/asn1js/ * @argument {string} pem the pem encoded string, can include the BEGIN/END header/footer * @private */ RSAKey.prototype.parseKey = function (pem) { try { var modulus = 0; var public_exponent = 0; var reHex = /^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/; var der = reHex.test(pem) ? Hex.decode(pem) : Base64.unarmor(pem); var asn1 = ASN1.decode(der); //Fixes a bug with OpenSSL 1.0+ private keys if(asn1.sub.length === 3){ asn1 = asn1.sub[2].sub[0]; } if (asn1.sub.length === 9) { // Parse the private key. modulus = asn1.sub[1].getHexStringValue(); //bigint this.n = parseBigInt(modulus, 16); public_exponent = asn1.sub[2].getHexStringValue(); //int this.e = parseInt(public_exponent, 16); var private_exponent = asn1.sub[3].getHexStringValue(); //bigint this.d = parseBigInt(private_exponent, 16); var prime1 = asn1.sub[4].getHexStringValue(); //bigint this.p = parseBigInt(prime1, 16); var prime2 = asn1.sub[5].getHexStringValue(); //bigint this.q = parseBigInt(prime2, 16); var exponent1 = asn1.sub[6].getHexStringValue(); //bigint this.dmp1 = parseBigInt(exponent1, 16); var exponent2 = asn1.sub[7].getHexStringValue(); //bigint this.dmq1 = parseBigInt(exponent2, 16); var coefficient = asn1.sub[8].getHexStringValue(); //bigint this.coeff = parseBigInt(coefficient, 16); } else if (asn1.sub.length === 2) { // Parse the public key. var bit_string = asn1.sub[1]; var sequence = bit_string.sub[0]; modulus = sequence.sub[0].getHexStringValue(); this.n = parseBigInt(modulus, 16); public_exponent = sequence.sub[1].getHexStringValue(); this.e = parseInt(public_exponent, 16); } else { return false; } return true; } catch (ex) { return false; } }; /** * Translate rsa parameters in a hex encoded string representing the rsa key. * * The translation follow the ASN.1 notation : * RSAPrivateKey ::= SEQUENCE { * version Version, * modulus INTEGER, -- n * publicExponent INTEGER, -- e * privateExponent INTEGER, -- d * prime1 INTEGER, -- p * prime2 INTEGER, -- q * exponent1 INTEGER, -- d mod (p1) * exponent2 INTEGER, -- d mod (q-1) * coefficient INTEGER, -- (inverse of q) mod p * } * @returns {string} DER Encoded String representing the rsa private key * @private */ RSAKey.prototype.getPrivateBaseKey = function () { var options = { 'array': [ new KJUR.asn1.DERInteger({'int': 0}), new KJUR.asn1.DERInteger({'bigint': this.n}), new KJUR.asn1.DERInteger({'int': this.e}), new KJUR.asn1.DERInteger({'bigint': this.d}), new KJUR.asn1.DERInteger({'bigint': this.p}), new KJUR.asn1.DERInteger({'bigint': this.q}), new KJUR.asn1.DERInteger({'bigint': this.dmp1}), new KJUR.asn1.DERInteger({'bigint': this.dmq1}), new KJUR.asn1.DERInteger({'bigint': this.coeff}) ] }; var seq = new KJUR.asn1.DERSequence(options); return seq.getEncodedHex(); }; /** * base64 (pem) encoded version of the DER encoded representation * @returns {string} pem encoded representation without header and footer * @public */ RSAKey.prototype.getPrivateBaseKeyB64 = function () { return hex2b64(this.getPrivateBaseKey()); }; /** * Translate rsa parameters in a hex encoded string representing the rsa public key. * The representation follow the ASN.1 notation : * PublicKeyInfo ::= SEQUENCE { * algorithm AlgorithmIdentifier, * PublicKey BIT STRING * } * Where AlgorithmIdentifier is: * AlgorithmIdentifier ::= SEQUENCE { * algorithm OBJECT IDENTIFIER, the OID of the enc algorithm * parameters ANY DEFINED BY algorithm OPTIONAL (NULL for PKCS #1) * } * and PublicKey is a SEQUENCE encapsulated in a BIT STRING * RSAPublicKey ::= SEQUENCE { * modulus INTEGER, -- n * publicExponent INTEGER -- e * } * @returns {string} DER Encoded String representing the rsa public key * @private */ RSAKey.prototype.getPublicBaseKey = function () { var options = { 'array': [ new KJUR.asn1.DERObjectIdentifier({'oid': '1.2.840.113549.1.1.1'}), //RSA Encryption pkcs #1 oid new KJUR.asn1.DERNull() ] }; var first_sequence = new KJUR.asn1.DERSequence(options); options = { 'array': [ new KJUR.asn1.DERInteger({'bigint': this.n}), new KJUR.asn1.DERInteger({'int': this.e}) ] }; var second_sequence = new KJUR.asn1.DERSequence(options); options = { 'hex': '00' + second_sequence.getEncodedHex() }; var bit_string = new KJUR.asn1.DERBitString(options); options = { 'array': [ first_sequence, bit_string ] }; var seq = new KJUR.asn1.DERSequence(options); return seq.getEncodedHex(); }; /** * base64 (pem) encoded version of the DER encoded representation * @returns {string} pem encoded representation without header and footer * @public */ RSAKey.prototype.getPublicBaseKeyB64 = function () { return hex2b64(this.getPublicBaseKey()); }; /** * wrap the string in block of width chars. The default value for rsa keys is 64 * characters. * @param {string} str the pem encoded string without header and footer * @param {Number} [width=64] - the length the string has to be wrapped at * @returns {string} * @private */ RSAKey.prototype.wordwrap = function (str, width) { width = width || 64; if (!str) { return str; } var regex = '(.{1,' + width + '})( +|$\n?)|(.{1,' + width + '})'; return str.match(RegExp(regex, 'g')).join('\n'); }; /** * Retrieve the pem encoded private key * @returns {string} the pem encoded private key with header/footer * @public */ RSAKey.prototype.getPrivateKey = function () { var key = "-----BEGIN RSA PRIVATE KEY-----\n"; key += this.wordwrap(this.getPrivateBaseKeyB64()) + "\n"; key += "-----END RSA PRIVATE KEY-----"; return key; }; /** * Retrieve the pem encoded public key * @returns {string} the pem encoded public key with header/footer * @public */ RSAKey.prototype.getPublicKey = function () { var key = "-----BEGIN PUBLIC KEY-----\n"; key += this.wordwrap(this.getPublicBaseKeyB64()) + "\n"; key += "-----END PUBLIC KEY-----"; return key; }; /** * Check if the object contains the necessary parameters to populate the rsa modulus * and public exponent parameters. * @param {Object} [obj={}] - An object that may contain the two public key * parameters * @returns {boolean} true if the object contains both the modulus and the public exponent * properties (n and e) * @todo check for types of n and e. N should be a parseable bigInt object, E should * be a parseable integer number * @private */ RSAKey.prototype.hasPublicKeyProperty = function (obj) { obj = obj || {}; return ( obj.hasOwnProperty('n') && obj.hasOwnProperty('e') ); }; /** * Check if the object contains ALL the parameters of an RSA key. * @param {Object} [obj={}] - An object that may contain nine rsa key * parameters * @returns {boolean} true if the object contains all the parameters needed * @todo check for types of the parameters all the parameters but the public exponent * should be parseable bigint objects, the public exponent should be a parseable integer number * @private */ RSAKey.prototype.hasPrivateKeyProperty = function (obj) { obj = obj || {}; return ( obj.hasOwnProperty('n') && obj.hasOwnProperty('e') && obj.hasOwnProperty('d') && obj.hasOwnProperty('p') && obj.hasOwnProperty('q') && obj.hasOwnProperty('dmp1') && obj.hasOwnProperty('dmq1') && obj.hasOwnProperty('coeff') ); }; /** * Parse the properties of obj in the current rsa object. Obj should AT LEAST * include the modulus and public exponent (n, e) parameters. * @param {Object} obj - the object containing rsa parameters * @private */ RSAKey.prototype.parsePropertiesFrom = function (obj) { this.n = obj.n; this.e = obj.e; if (obj.hasOwnProperty('d')) { this.d = obj.d; this.p = obj.p; this.q = obj.q; this.dmp1 = obj.dmp1; this.dmq1 = obj.dmq1; this.coeff = obj.coeff; } }; /** * Create a new JSEncryptRSAKey that extends Tom Wu's RSA key object. * This object is just a decorator for parsing the key parameter * @param {string|Object} key - The key in string format, or an object containing * the parameters needed to build a RSAKey object. * @constructor */ var JSEncryptRSAKey = function (key) { // Call the super constructor. RSAKey.call(this); // If a key key was provided. if (key) { // If this is a string... if (typeof key === 'string') { this.parseKey(key); } else if ( this.hasPrivateKeyProperty(key) || this.hasPublicKeyProperty(key) ) { // Set the values for the key. this.parsePropertiesFrom(key); } } }; // Derive from RSAKey. JSEncryptRSAKey.prototype = new RSAKey(); // Reset the contructor. JSEncryptRSAKey.prototype.constructor = JSEncryptRSAKey; /** * * @param {Object} [options = {}] - An object to customize JSEncrypt behaviour * possible parameters are: * - default_key_size {number} default: 1024 the key size in bit * - default_public_exponent {string} default: '010001' the hexadecimal representation of the public exponent * - log {boolean} default: false whether log warn/error or not * @constructor */ var JSEncrypt = function (options) { options = options || {}; this.default_key_size = parseInt(options.default_key_size) || 1024; this.default_public_exponent = options.default_public_exponent || '010001'; //65537 default openssl public exponent for rsa key type this.log = options.log || false; // The private and public key. this.key = null; }; /** * Method to set the rsa key parameter (one method is enough to set both the public * and the private key, since the private key contains the public key paramenters) * Log a warning if logs are enabled * @param {Object|string} key the pem encoded string or an object (with or without header/footer) * @public */ JSEncrypt.prototype.setKey = function (key) { if (this.log && this.key) { console.warn('A key was already set, overriding existing.'); } this.key = new JSEncryptRSAKey(key); }; /** * Proxy method for setKey, for api compatibility * @see setKey * @public */ JSEncrypt.prototype.setPrivateKey = function (privkey) { // Create the key. this.setKey(privkey); }; /** * Proxy method for setKey, for api compatibility * @see setKey * @public */ JSEncrypt.prototype.setPublicKey = function (pubkey) { // Sets the public key. this.setKey(pubkey); }; /** * Proxy method for RSAKey object's decrypt, decrypt the string using the private * components of the rsa key object. Note that if the object was not set will be created * on the fly (by the getKey method) using the parameters passed in the JSEncrypt constructor * @param {string} string base64 encoded crypted string to decrypt * @return {string} the decrypted string * @public */ JSEncrypt.prototype.decrypt = function (string) { // Return the decrypted string. try { return this.getKey().decrypt(b64tohex(string)); } catch (ex) { return false; } }; /** * Proxy method for RSAKey object's encrypt, encrypt the string using the public * components of the rsa key object. Note that if the object was not set will be created * on the fly (by the getKey method) using the parameters passed in the JSEncrypt constructor * @param {string} string the string to encrypt * @return {string} the encrypted string encoded in base64 * @public */ JSEncrypt.prototype.encrypt = function (string) { // Return the encrypted string. try { return hex2b64(this.getKey().encrypt(string)); } catch (ex) { return false; } }; /** * Getter for the current JSEncryptRSAKey object. If it doesn't exists a new object * will be created and returned * @param {callback} [cb] the callback to be called if we want the key to be generated * in an async fashion * @returns {JSEncryptRSAKey} the JSEncryptRSAKey object * @public */ JSEncrypt.prototype.getKey = function (cb) { // Only create new if it does not exist. if (!this.key) { // Get a new private key. this.key = new JSEncryptRSAKey(); if (cb && {}.toString.call(cb) === '[object Function]') { this.key.generateAsync(this.default_key_size, this.default_public_exponent, cb); return; } // Generate the key. this.key.generate(this.default_key_size, this.default_public_exponent); } return this.key; }; /** * Returns the pem encoded representation of the private key * If the key doesn't exists a new key will be created * @returns {string} pem encoded representation of the private key WITH header and footer * @public */ JSEncrypt.prototype.getPrivateKey = function () { // Return the private representation of this key. return this.getKey().getPrivateKey(); }; /** * Returns the pem encoded representation of the private key * If the key doesn't exists a new key will be created * @returns {string} pem encoded representation of the private key WITHOUT header and footer * @public */ JSEncrypt.prototype.getPrivateKeyB64 = function () { // Return the private representation of this key. return this.getKey().getPrivateBaseKeyB64(); }; /** * Returns the pem encoded representation of the public key * If the key doesn't exists a new key will be created * @returns {string} pem encoded representation of the public key WITH header and footer * @public */ JSEncrypt.prototype.getPublicKey = function () { // Return the private representation of this key. return this.getKey().getPublicKey(); }; /** * Returns the pem encoded representation of the public key * If the key doesn't exists a new key will be created * @returns {string} pem encoded representation of the public key WITHOUT header and footer * @public */ JSEncrypt.prototype.getPublicKeyB64 = function () { // Return the private representation of this key. return this.getKey().getPublicBaseKeyB64(); }; exports.JSEncrypt = JSEncrypt; })(ns.JSEncryptExports); ns.JSEncrypt = ns.JSEncryptExports.JSEncrypt; // --- base64.js ------------------------------------------------------------------------------------------------ // ns.b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; ns.b64pad="="; ns.hex2b64 = function(h) { var i; var c; var ret = ""; for(i = 0; i+3 <= h.length; i+=3) { c = parseInt(h.substring(i,i+3),16); ret += ns.b64map.charAt(c >> 6) + ns.b64map.charAt(c & 63); } if(i+1 == h.length) { c = parseInt(h.substring(i,i+1),16); ret += ns.b64map.charAt(c << 2); } else if(i+2 == h.length) { c = parseInt(h.substring(i,i+2),16); ret += ns.b64map.charAt(c >> 2) + ns.b64map.charAt((c & 3) << 4); } while((ret.length & 3) > 0) ret += ns.b64pad; return ret; } // convert a base64 string to hex ns.b64tohex = function(s) { var ret = "" var i; var k = 0; // b64 state, 0-3 var slop; for(i = 0; i < s.length; ++i) { if(s.charAt(i) == ns.b64pad) break; v = ns.b64map.indexOf(s.charAt(i)); if(v < 0) continue; if(k == 0) { ret += ns.int2char(v >> 2); slop = v & 3; k = 1; } else if(k == 1) { ret += ns.int2char((slop << 2) | (v >> 4)); slop = v & 0xf; k = 2; } else if(k == 2) { ret += ns.int2char(slop); ret += ns.int2char(v >> 2); slop = v & 3; k = 3; } else { ret += ns.int2char((slop << 2) | (v >> 4)); ret += ns.int2char(v & 0xf); k = 0; } } if(k == 1) ret += ns.int2char(slop << 2); return ret; } // convert a base64 string to a byte/number array ns.b64toBA = function(s) { //piggyback on b64tohex for now, optimize later var h = ns.b64tohex(s); var i; var a = new Array(); for(i = 0; 2*i < h.length; ++i) { a[i] = parseInt(h.substring(2*i,2*i+2),16); } return a; } // --- hash.js -------------------------------------------------------------------------------------------------- // /** * * Secure Hash Algorithm (SHA256) * http://www.webtoolkit.info/ * * Original code by Angel Marin, Paul Johnston. * **/ ns.SHA256 = function(s){ var chrsz = 8; var hexcase = 0; function safe_add (x, y) { var lsw = (x & 0xFFFF) + (y & 0xFFFF); var msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF); } function S (X, n) { return ( X >>> n ) | (X << (32 - n)); } function R (X, n) { return ( X >>> n ); } function Ch(x, y, z) { return ((x & y) ^ ((~x) & z)); } function Maj(x, y, z) { return ((x & y) ^ (x & z) ^ (y & z)); } function Sigma0256(x) { return (S(x, 2) ^ S(x, 13) ^ S(x, 22)); } function Sigma1256(x) { return (S(x, 6) ^ S(x, 11) ^ S(x, 25)); } function Gamma0256(x) { return (S(x, 7) ^ S(x, 18) ^ R(x, 3)); } function Gamma1256(x) { return (S(x, 17) ^ S(x, 19) ^ R(x, 10)); } function core_sha256 (m, l) { var K = new Array(0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0xFC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x6CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2); var HASH = new Array(0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19); var W = new Array(64); var a, b, c, d, e, f, g, h, i, j; var T1, T2; m[l >> 5] |= 0x80 << (24 - l % 32); m[((l + 64 >> 9) << 4) + 15] = l; for ( var i = 0; i>5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i%32); } return bin; } function Utf8Encode(string) { string = string.replace(/\r\n/g,"\n"); var utftext = ""; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if((c > 127) && (c < 2048)) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); } } return utftext; } function binb2hex (binarray) { var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; var str = ""; for(var i = 0; i < binarray.length * 4; i++) { str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) + hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF); } return str; } s = Utf8Encode(s); return binb2hex(core_sha256(str2binb(s), s.length * chrsz)); } ns.sha256 = {} ns.sha256.hex = function(s) { return ns.SHA256(s); } /** * * Secure Hash Algorithm (SHA1) * http://www.webtoolkit.info/ * **/ ns.SHA1 = function (msg) { function rotate_left(n,s) { var t4 = ( n<>>(32-s)); return t4; }; function lsb_hex(val) { var str=""; var i; var vh; var vl; for( i=0; i<=6; i+=2 ) { vh = (val>>>(i*4+4))&0x0f; vl = (val>>>(i*4))&0x0f; str += vh.toString(16) + vl.toString(16); } return str; }; function cvt_hex(val) { var str=""; var i; var v; for( i=7; i>=0; i-- ) { v = (val>>>(i*4))&0x0f; str += v.toString(16); } return str; }; function Utf8Encode(string) { string = string.replace(/\r\n/g,"\n"); var utftext = ""; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if((c > 127) && (c < 2048)) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); } } return utftext; }; var blockstart; var i, j; var W = new Array(80); var H0 = 0x67452301; var H1 = 0xEFCDAB89; var H2 = 0x98BADCFE; var H3 = 0x10325476; var H4 = 0xC3D2E1F0; var A, B, C, D, E; var temp; msg = Utf8Encode(msg); var msg_len = msg.length; var word_array = new Array(); for( i=0; i>>29 ); word_array.push( (msg_len<<3)&0x0ffffffff ); for ( blockstart=0; blockstart>>(32-iShiftBits)); } function AddUnsigned(lX,lY) { var lX4,lY4,lX8,lY8,lResult; lX8 = (lX & 0x80000000); lY8 = (lY & 0x80000000); lX4 = (lX & 0x40000000); lY4 = (lY & 0x40000000); lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF); if (lX4 & lY4) { return (lResult ^ 0x80000000 ^ lX8 ^ lY8); } if (lX4 | lY4) { if (lResult & 0x40000000) { return (lResult ^ 0xC0000000 ^ lX8 ^ lY8); } else { return (lResult ^ 0x40000000 ^ lX8 ^ lY8); } } else { return (lResult ^ lX8 ^ lY8); } } function F(x,y,z) { return (x & y) | ((~x) & z); } function G(x,y,z) { return (x & z) | (y & (~z)); } function H(x,y,z) { return (x ^ y ^ z); } function I(x,y,z) { return (y ^ (x | (~z))); } function FF(a,b,c,d,x,s,ac) { a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac)); return AddUnsigned(RotateLeft(a, s), b); }; function GG(a,b,c,d,x,s,ac) { a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac)); return AddUnsigned(RotateLeft(a, s), b); }; function HH(a,b,c,d,x,s,ac) { a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac)); return AddUnsigned(RotateLeft(a, s), b); }; function II(a,b,c,d,x,s,ac) { a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac)); return AddUnsigned(RotateLeft(a, s), b); }; function ConvertToWordArray(string) { var lWordCount; var lMessageLength = string.length; var lNumberOfWords_temp1=lMessageLength + 8; var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64; var lNumberOfWords = (lNumberOfWords_temp2+1)*16; var lWordArray=Array(lNumberOfWords-1); var lBytePosition = 0; var lByteCount = 0; while ( lByteCount < lMessageLength ) { lWordCount = (lByteCount-(lByteCount % 4))/4; lBytePosition = (lByteCount % 4)*8; lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount)<>>29; return lWordArray; }; function WordToHex(lValue) { var WordToHexValue="",WordToHexValue_temp="",lByte,lCount; for (lCount = 0;lCount<=3;lCount++) { lByte = (lValue>>>(lCount*8)) & 255; WordToHexValue_temp = "0" + lByte.toString(16); WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2); } return WordToHexValue; }; function Utf8Encode(string) { string = string.replace(/\r\n/g,"\n"); var utftext = ""; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if((c > 127) && (c < 2048)) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); } } return utftext; }; var x=Array(); var k,AA,BB,CC,DD,a,b,c,d; var S11=7, S12=12, S13=17, S14=22; var S21=5, S22=9 , S23=14, S24=20; var S31=4, S32=11, S33=16, S34=23; var S41=6, S42=10, S43=15, S44=21; string = Utf8Encode(string); x = ConvertToWordArray(string); a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476; for (k=0;k= 0) { var v = x * this[i++] + w[j] + c; c = Math.floor(v / 0x4000000); w[j++] = v & 0x3ffffff; } return c; } // am2 avoids a big mult-and-extract completely. // Max digit bits should be <= 30 because we do bitwise ops // on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) ns.am2 = function(i, x, w, j, c, n) { var xl = x & 0x7fff, xh = x >> 15; while (--n >= 0) { var l = this[i] & 0x7fff; var h = this[i++] >> 15; var m = xh * l + h * xl; l = xl * l + ((m & 0x7fff) << 15) + w[j] + (c & 0x3fffffff); c = (l >>> 30) + (m >>> 15) + xh * h + (c >>> 30); w[j++] = l & 0x3fffffff; } return c; } // Alternately, set max digit bits to 28 since some // browsers slow down when dealing with 32-bit numbers. ns.am3 = function(i, x, w, j, c, n) { var xl = x & 0x3fff, xh = x >> 14; while (--n >= 0) { var l = this[i] & 0x3fff; var h = this[i++] >> 14; var m = xh * l + h * xl; l = xl * l + ((m & 0x3fff) << 14) + w[j] + c; c = (l >> 28) + (m >> 14) + xh * h; w[j++] = l & 0xfffffff; } return c; } if (ns.j_lm && (navigator.appName == "Microsoft Internet Explorer")) { ns.BigInteger.prototype.am = ns.am2; ns.dbits = 30; } else if (ns.j_lm && (navigator.appName != "Netscape")) { ns.BigInteger.prototype.am = ns.am1; ns.dbits = 26; } else { // Mozilla/Netscape seems to prefer am3 ns.BigInteger.prototype.am = ns.am3; ns.dbits = 28; } ns.BigInteger.prototype.DB = ns.dbits; ns.BigInteger.prototype.DM = ((1 << ns.dbits) - 1); ns.BigInteger.prototype.DV = (1 << ns.dbits); ns.BI_FP = 52; ns.BigInteger.prototype.FV = Math.pow(2, ns.BI_FP); ns.BigInteger.prototype.F1 = ns.BI_FP - ns.dbits; ns.BigInteger.prototype.F2 = 2 * ns.dbits - ns.BI_FP; // Digit conversions ns.BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"; ns.BI_RC = new Array(); ns.rr; ns.vv; ns.rr = "0".charCodeAt(0); for (ns.vv = 0; ns.vv <= 9; ++ns.vv) ns.BI_RC[ns.rr++] = ns.vv; ns.rr = "a".charCodeAt(0); for (ns.vv = 10; ns.vv < 36; ++ns.vv) ns.BI_RC[ns.rr++] = ns.vv; ns.rr = "A".charCodeAt(0); for (ns.vv = 10; ns.vv < 36; ++ns.vv) ns.BI_RC[ns.rr++] = ns.vv; ns.int2char = function(n) { return ns.BI_RM.charAt(n); } ns.intAt = function(s, i) { var c = ns.BI_RC[s.charCodeAt(i)]; return (c == null) ? -1 : c; } // (protected) copy this to r ns.bnpCopyTo = function(r) { for (var i = this.t - 1; i >= 0; --i) r[i] = this[i]; r.t = this.t; r.s = this.s; } // (protected) set from integer value x, -DV <= x < DV ns.bnpFromInt = function(x) { this.t = 1; this.s = (x < 0) ? -1 : 0; if (x > 0) this[0] = x; else if (x < -1) this[0] = x + DV; else this.t = 0; } // return bigint initialized to value ns.nbv = function(i) { var r = ns.nbi(); r.fromInt(i); return r; } // (protected) set from string and radix ns.bnpFromString = function(s, b) { var k; if (b == 16) k = 4; else if (b == 8) k = 3; else if (b == 256) k = 8; // byte array else if (b == 2) k = 1; else if (b == 32) k = 5; else if (b == 4) k = 2; else { this.fromRadix(s, b); return; } this.t = 0; this.s = 0; var i = s.length, mi = false, sh = 0; while (--i >= 0) { var x = (k == 8) ? s[i] & 0xff : ns.intAt(s, i); if (x < 0) { if (s.charAt(i) == "-") mi = true; continue; } mi = false; if (sh == 0) this[this.t++] = x; else if (sh + k > this.DB) { this[this.t - 1] |= (x & ((1 << (this.DB - sh)) - 1)) << sh; this[this.t++] = (x >> (this.DB - sh)); } else this[this.t - 1] |= x << sh; sh += k; if (sh >= this.DB) sh -= this.DB; } if (k == 8 && (s[0] & 0x80) != 0) { this.s = -1; if (sh > 0) this[this.t - 1] |= ((1 << (this.DB - sh)) - 1) << sh; } this.clamp(); if (mi) ns.BigInteger.ZERO.subTo(this, this); } // (protected) clamp off excess high words ns.bnpClamp = function() { var c = this.s & this.DM; while (this.t > 0 && this[this.t - 1] == c)--this.t; } // (public) return string representation in given radix ns.bnToString = function(b) { if (this.s < 0) return "-" + this.negate().toString(b); var k; if (b == 16) k = 4; else if (b == 8) k = 3; else if (b == 2) k = 1; else if (b == 32) k = 5; else if (b == 64) k = 6; else if (b == 4) k = 2; else return this.toRadix(b); var km = (1 << k) - 1, d, m = false, r = "", i = this.t; var p = this.DB - (i * this.DB) % k; if (i-- > 0) { if (p < this.DB && (d = this[i] >> p) > 0) { m = true; r = ns.int2char(d); } while (i >= 0) { if (p < k) { d = (this[i] & ((1 << p) - 1)) << (k - p); d |= this[--i] >> (p += this.DB - k); } else { d = (this[i] >> (p -= k)) & km; if (p <= 0) { p += this.DB; --i; } } if (d > 0) m = true; if (m) r += ns.int2char(d); } } return m ? r : "0"; } // (public) -this ns.bnNegate = function() { var r = ns.nbi(); ns.BigInteger.ZERO.subTo(this, r); return r; } // (public) |this| ns.bnAbs = function() { return (this.s < 0) ? this.negate() : this; } // (public) return + if this > a, - if this < a, 0 if equal ns.bnCompareTo = function(a) { var r = this.s - a.s; if (r != 0) return r; var i = this.t; r = i - a.t; if (r != 0) return r; while (--i >= 0) if ((r = this[i] - a[i]) != 0) return r; return 0; } // returns bit length of the integer x ns.nbits = function(x) { var r = 1, t; if ((t = x >>> 16) != 0) { x = t; r += 16; } if ((t = x >> 8) != 0) { x = t; r += 8; } if ((t = x >> 4) != 0) { x = t; r += 4; } if ((t = x >> 2) != 0) { x = t; r += 2; } if ((t = x >> 1) != 0) { x = t; r += 1; } return r; } // (public) return the number of bits in "this" ns.bnBitLength = function() { if (this.t <= 0) return 0; return this.DB * (this.t - 1) + ns.nbits(this[this.t - 1] ^ (this.s & this.DM)); } // (protected) r = this << n*DB ns.bnpDLShiftTo = function(n, r) { var i; for (i = this.t - 1; i >= 0; --i) r[i + n] = this[i]; for (i = n - 1; i >= 0; --i) r[i] = 0; r.t = this.t + n; r.s = this.s; } // (protected) r = this >> n*DB ns.bnpDRShiftTo = function(n, r) { for (var i = n; i < this.t; ++i) r[i - n] = this[i]; r.t = Math.max(this.t - n, 0); r.s = this.s; } // (protected) r = this << n ns.bnpLShiftTo = function(n, r) { var bs = n % this.DB; var cbs = this.DB - bs; var bm = (1 << cbs) - 1; var ds = Math.floor(n / this.DB), c = (this.s << bs) & this.DM, i; for (i = this.t - 1; i >= 0; --i) { r[i + ds + 1] = (this[i] >> cbs) | c; c = (this[i] & bm) << bs; } for (i = ds - 1; i >= 0; --i) r[i] = 0; r[ds] = c; r.t = this.t + ds + 1; r.s = this.s; r.clamp(); } // (protected) r = this >> n ns.bnpRShiftTo = function(n, r) { r.s = this.s; var ds = Math.floor(n / this.DB); if (ds >= this.t) { r.t = 0; return; } var bs = n % this.DB; var cbs = this.DB - bs; var bm = (1 << bs) - 1; r[0] = this[ds] >> bs; for (var i = ds + 1; i < this.t; ++i) { r[i - ds - 1] |= (this[i] & bm) << cbs; r[i - ds] = this[i] >> bs; } if (bs > 0) r[this.t - ds - 1] |= (this.s & bm) << cbs; r.t = this.t - ds; r.clamp(); } // (protected) r = this - a ns.bnpSubTo = function(a, r) { var i = 0, c = 0, m = Math.min(a.t, this.t); while (i < m) { c += this[i] - a[i]; r[i++] = c & this.DM; c >>= this.DB; } if (a.t < this.t) { c -= a.s; while (i < this.t) { c += this[i]; r[i++] = c & this.DM; c >>= this.DB; } c += this.s; } else { c += this.s; while (i < a.t) { c -= a[i]; r[i++] = c & this.DM; c >>= this.DB; } c -= a.s; } r.s = (c < 0) ? -1 : 0; if (c < -1) r[i++] = this.DV + c; else if (c > 0) r[i++] = c; r.t = i; r.clamp(); } // (protected) r = this * a, r != this,a (HAC 14.12) // "this" should be the larger one if appropriate. ns.bnpMultiplyTo = function(a, r) { var x = this.abs(), y = a.abs(); var i = x.t; r.t = i + y.t; while (--i >= 0) r[i] = 0; for (i = 0; i < y.t; ++i) r[i + x.t] = x.am(0, y[i], r, i, 0, x.t); r.s = 0; r.clamp(); if (this.s != a.s) ns.BigInteger.ZERO.subTo(r, r); } // (protected) r = this^2, r != this (HAC 14.16) ns.bnpSquareTo = function(r) { var x = this.abs(); var i = r.t = 2 * x.t; while (--i >= 0) r[i] = 0; for (i = 0; i < x.t - 1; ++i) { var c = x.am(i, x[i], r, 2 * i, 0, 1); if ((r[i + x.t] += x.am(i + 1, 2 * x[i], r, 2 * i + 1, c, x.t - i - 1)) >= x.DV) { r[i + x.t] -= x.DV; r[i + x.t + 1] = 1; } } if (r.t > 0) r[r.t - 1] += x.am(i, x[i], r, 2 * i, 0, 1); r.s = 0; r.clamp(); } // (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) // r != q, this != m. q or r may be null. ns.bnpDivRemTo = function(m, q, r) { var pm = m.abs(); if (pm.t <= 0) return; var pt = this.abs(); if (pt.t < pm.t) { if (q != null) q.fromInt(0); if (r != null) this.copyTo(r); return; } if (r == null) r = nbi(); var y = ns.nbi(), ts = this.s, ms = m.s; var nsh = this.DB - ns.nbits(pm[pm.t - 1]); // normalize modulus if (nsh > 0) { pm.lShiftTo(nsh, y); pt.lShiftTo(nsh, r); } else { pm.copyTo(y); pt.copyTo(r); } var ys = y.t; var y0 = y[ys - 1]; if (y0 == 0) return; var yt = y0 * (1 << this.F1) + ((ys > 1) ? y[ys - 2] >> this.F2 : 0); var d1 = this.FV / yt, d2 = (1 << this.F1) / yt, e = 1 << this.F2; var i = r.t, j = i - ys, t = (q == null) ? ns.nbi() : q; y.dlShiftTo(j, t); if (r.compareTo(t) >= 0) { r[r.t++] = 1; r.subTo(t, r); } ns.BigInteger.ONE.dlShiftTo(ys, t); t.subTo(y, y); // "negative" y so we can replace sub with am later while (y.t < ys) y[y.t++] = 0; while (--j >= 0) { // Estimate quotient digit var qd = (r[--i] == y0) ? this.DM : Math.floor(r[i] * d1 + (r[i - 1] + e) * d2); if ((r[i] += y.am(0, qd, r, j, 0, ys)) < qd) { // Try it out y.dlShiftTo(j, t); r.subTo(t, r); while (r[i] < --qd) r.subTo(t, r); } } if (q != null) { r.drShiftTo(ys, q); if (ts != ms) ns.BigInteger.ZERO.subTo(q, q); } r.t = ys; r.clamp(); if (nsh > 0) r.rShiftTo(nsh, r); // Denormalize remainder if (ts < 0) ns.BigInteger.ZERO.subTo(r, r); } // (public) this mod a ns.bnMod = function(a) { var r = ns.nbi(); this.abs().divRemTo(a, null, r); if (this.s < 0 && r.compareTo(ns.BigInteger.ZERO) > 0) a.subTo(r, r); return r; } // Modular reduction using "classic" algorithm ns.Classic = function(m) { this.m = m; } ns.cConvert = function(x) { if (x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); else return x; } ns.cRevert = function(x) { return x; } ns.cReduce = function(x) { x.divRemTo(this.m, null, x); } ns.cMulTo = function(x, y, r) { x.multiplyTo(y, r); this.reduce(r); } ns.cSqrTo = function(x, r) { x.squareTo(r); this.reduce(r); } ns.Classic.prototype.convert = ns.cConvert; ns.Classic.prototype.revert = ns.cRevert; ns.Classic.prototype.reduce = ns.cReduce; ns.Classic.prototype.mulTo = ns.cMulTo; ns.Classic.prototype.sqrTo = ns.cSqrTo; // (protected) return "-1/this % 2^DB"; useful for Mont. reduction // justification: // xy == 1 (mod m) // xy = 1+km // xy(2-xy) = (1+km)(1-km) // x[y(2-xy)] = 1-k^2m^2 // x[y(2-xy)] == 1 (mod m^2) // if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 // should reduce x and y(2-xy) by m^2 at each step to keep size bounded. // JS multiply "overflows" differently from C/C++, so care is needed here. ns.bnpInvDigit = function() { if (this.t < 1) return 0; var x = this[0]; if ((x & 1) == 0) return 0; var y = x & 3; // y == 1/x mod 2^2 y = (y * (2 - (x & 0xf) * y)) & 0xf; // y == 1/x mod 2^4 y = (y * (2 - (x & 0xff) * y)) & 0xff; // y == 1/x mod 2^8 y = (y * (2 - (((x & 0xffff) * y) & 0xffff))) & 0xffff; // y == 1/x mod 2^16 // last step - calculate inverse mod DV directly; // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints y = (y * (2 - x * y % this.DV)) % this.DV; // y == 1/x mod 2^dbits // we really want the negative inverse, and -DV < y < DV return (y > 0) ? this.DV - y : -y; } // Montgomery reduction ns.Montgomery = function(m) { this.m = m; this.mp = m.invDigit(); this.mpl = this.mp & 0x7fff; this.mph = this.mp >> 15; this.um = (1 << (m.DB - 15)) - 1; this.mt2 = 2 * m.t; } // xR mod m ns.montConvert = function(x) { var r = ns.nbi(); x.abs().dlShiftTo(this.m.t, r); r.divRemTo(this.m, null, r); if (x.s < 0 && r.compareTo(ns.BigInteger.ZERO) > 0) this.m.subTo(r, r); return r; } // x/R mod m ns.montRevert = function(x) { var r = ns.nbi(); x.copyTo(r); this.reduce(r); return r; } // x = x/R mod m (HAC 14.32) ns.montReduce = function(x) { while (x.t <= this.mt2) // pad x so am has enough room later x[x.t++] = 0; for (var i = 0; i < this.m.t; ++i) { // faster way of calculating u0 = x[i]*mp mod DV var j = x[i] & 0x7fff; var u0 = (j * this.mpl + (((j * this.mph + (x[i] >> 15) * this.mpl) & this.um) << 15)) & x.DM; // use am to combine the multiply-shift-add into one call j = i + this.m.t; x[j] += this.m.am(0, u0, x, i, 0, this.m.t); // propagate carry while (x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; } } x.clamp(); x.drShiftTo(this.m.t, x); if (x.compareTo(this.m) >= 0) x.subTo(this.m, x); } // r = "x^2/R mod m"; x != r ns.montSqrTo = function(x, r) { x.squareTo(r); this.reduce(r); } // r = "xy/R mod m"; x,y != r ns.montMulTo = function(x, y, r) { x.multiplyTo(y, r); this.reduce(r); } ns.Montgomery.prototype.convert = ns.montConvert; ns.Montgomery.prototype.revert = ns.montRevert; ns.Montgomery.prototype.reduce = ns.montReduce; ns.Montgomery.prototype.mulTo = ns.montMulTo; ns.Montgomery.prototype.sqrTo = ns.montSqrTo; // (protected) true iff this is even ns.bnpIsEven = function() { return ((this.t > 0) ? (this[0] & 1) : this.s) == 0; } // (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) ns.bnpExp = function(e, z) { if (e > 0xffffffff || e < 1) return ns.BigInteger.ONE; var r = ns.nbi(), r2 = ns.nbi(), g = z.convert(this), i = ns.nbits(e) - 1; g.copyTo(r); while (--i >= 0) { z.sqrTo(r, r2); if ((e & (1 << i)) > 0) z.mulTo(r2, g, r); else { var t = r; r = r2; r2 = t; } } return z.revert(r); } // (public) this^e % m, 0 <= e < 2^32 ns.bnModPowInt = function(e, m) { var z; if (e < 256 || m.isEven()) z = new ns.Classic(m); else z = new ns.Montgomery(m); return this.exp(e, z); } // protected ns.BigInteger.prototype.copyTo = ns.bnpCopyTo; ns.BigInteger.prototype.fromInt = ns.bnpFromInt; ns.BigInteger.prototype.fromString = ns.bnpFromString; ns.BigInteger.prototype.clamp = ns.bnpClamp; ns.BigInteger.prototype.dlShiftTo = ns.bnpDLShiftTo; ns.BigInteger.prototype.drShiftTo = ns.bnpDRShiftTo; ns.BigInteger.prototype.lShiftTo = ns.bnpLShiftTo; ns.BigInteger.prototype.rShiftTo = ns.bnpRShiftTo; ns.BigInteger.prototype.subTo = ns.bnpSubTo; ns.BigInteger.prototype.multiplyTo = ns.bnpMultiplyTo; ns.BigInteger.prototype.squareTo = ns.bnpSquareTo; ns.BigInteger.prototype.divRemTo = ns.bnpDivRemTo; ns.BigInteger.prototype.invDigit = ns.bnpInvDigit; ns.BigInteger.prototype.isEven = ns.bnpIsEven; ns.BigInteger.prototype.exp = ns.bnpExp; // public ns.BigInteger.prototype.toString = ns.bnToString; ns.BigInteger.prototype.negate = ns.bnNegate; ns.BigInteger.prototype.abs = ns.bnAbs; ns.BigInteger.prototype.compareTo = ns.bnCompareTo; ns.BigInteger.prototype.bitLength = ns.bnBitLength; ns.BigInteger.prototype.mod = ns.bnMod; ns.BigInteger.prototype.modPowInt = ns.bnModPowInt; // "constants" ns.BigInteger.ZERO = ns.nbv(0); ns.BigInteger.ONE = ns.nbv(1); ns.bnClone = function() { var r = ns.nbi(); this.copyTo(r); return r; } // (public) return value as integer ns.bnIntValue = function() { if (this.s < 0) { if (this.t == 1) return this[0] - this.DV; else if (this.t == 0) return -1; } else if (this.t == 1) return this[0]; else if (this.t == 0) return 0; // assumes 16 < DB < 32 return ((this[1] & ((1 << (32 - this.DB)) - 1)) << this.DB) | this[0]; } // (public) return value as byte ns.bnByteValue = function() { return (this.t == 0) ? this.s : (this[0] << 24) >> 24; } // (public) return value as short (assumes DB>=16) ns.bnShortValue = function() { return (this.t == 0) ? this.s : (this[0] << 16) >> 16; } // (protected) return x s.t. r^x < DV ns.bnpChunkSize = function(r) { return Math.floor(Math.LN2 * this.DB / Math.log(r)); } // (public) 0 if this == 0, 1 if this > 0 ns.bnSigNum = function() { if (this.s < 0) return -1; else if (this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0; else return 1; } // (protected) convert to radix string ns.bnpToRadix = function(b) { if (b == null) b = 10; if (this.signum() == 0 || b < 2 || b > 36) return "0"; var cs = this.chunkSize(b); var a = Math.pow(b, cs); var d = ns.nbv(a), y = ns.nbi(), z = ns.nbi(), r = ""; this.divRemTo(d, y, z); while (y.signum() > 0) { r = (a + z.intValue()).toString(b).substr(1) + r; y.divRemTo(d, y, z); } return z.intValue().toString(b) + r; } // (protected) convert from radix string ns.bnpFromRadix = function(s, b) { this.fromInt(0); if (b == null) b = 10; var cs = this.chunkSize(b); var d = Math.pow(b, cs), mi = false, j = 0, w = 0; for (var i = 0; i < s.length; ++i) { var x = ns.intAt(s, i); if (x < 0) { if (s.charAt(i) == "-" && this.signum() == 0) mi = true; continue; } w = b * w + x; if (++j >= cs) { this.dMultiply(d); this.dAddOffset(w, 0); j = 0; w = 0; } } if (j > 0) { this.dMultiply(Math.pow(b, j)); this.dAddOffset(w, 0); } if (mi) ns.BigInteger.ZERO.subTo(this, this); } // (protected) alternate constructor ns.bnpFromNumber = function(a, b, c) { if ("number" == typeof b) { // new BigInteger(int,int,RNG) if (a < 2) this.fromInt(1); else { this.fromNumber(a, c); if (!this.testBit(a - 1)) // force MSB set this.bitwiseTo(ns.BigInteger.ONE.shiftLeft(a - 1), ns.op_or, this); if (this.isEven()) this.dAddOffset(1, 0); // force odd while (!this.isProbablePrime(b)) { this.dAddOffset(2, 0); if (this.bitLength() > a) this.subTo(ns.BigInteger.ONE.shiftLeft(a - 1), this); } } } else { // new BigInteger(int,RNG) var x = new Array(), t = a & 7; x.length = (a >> 3) + 1; b.nextBytes(x); if (t > 0) x[0] &= ((1 << t) - 1); else x[0] = 0; this.fromString(x, 256); } } // (public) convert to bigendian byte array ns.bnToByteArray = function() { var i = this.t, r = new Array(); r[0] = this.s; var p = this.DB - (i * this.DB) % 8, d, k = 0; if (i-- > 0) { if (p < this.DB && (d = this[i] >> p) != (this.s & this.DM) >> p) r[k++] = d | (this.s << (this.DB - p)); while (i >= 0) { if (p < 8) { d = (this[i] & ((1 << p) - 1)) << (8 - p); d |= this[--i] >> (p += this.DB - 8); } else { d = (this[i] >> (p -= 8)) & 0xff; if (p <= 0) { p += this.DB; --i; } } if ((d & 0x80) != 0) d |= -256; if (k == 0 && (this.s & 0x80) != (d & 0x80))++k; if (k > 0 || d != this.s) r[k++] = d; } } return r; } ns.bnEquals = function(a) { return (this.compareTo(a) == 0); } ns.bnMin = function(a) { return (this.compareTo(a) < 0) ? this : a; } ns.bnMax = function(a) { return (this.compareTo(a) > 0) ? this : a; } // (protected) r = this op a (bitwise) ns.bnpBitwiseTo = function(a, op, r) { var i, f, m = Math.min(a.t, this.t); for (i = 0; i < m; ++i) r[i] = op(this[i], a[i]); if (a.t < this.t) { f = a.s & this.DM; for (i = m; i < this.t; ++i) r[i] = op(this[i], f); r.t = this.t; } else { f = this.s & this.DM; for (i = m; i < a.t; ++i) r[i] = op(f, a[i]); r.t = a.t; } r.s = op(this.s, a.s); r.clamp(); } // (public) this & a ns.op_and = function(x, y) { return x & y; } ns.bnAnd = function(a) { var r = ns.nbi(); this.bitwiseTo(a, op_and, r); return r; } // (public) this | a ns.op_or = function(x, y) { return x | y; } ns.bnOr = function(a) { var r = ns.nbi(); this.bitwiseTo(a, op_or, r); return r; } // (public) this ^ a ns.op_xor = function(x, y) { return x ^ y; } ns.bnXor = function(a) { var r = ns.nbi(); this.bitwiseTo(a, op_xor, r); return r; } // (public) this & ~a ns.op_andnot = function(x, y) { return x & ~y; } ns.bnAndNot = function(a) { var r = ns.nbi(); this.bitwiseTo(a, op_andnot, r); return r; } // (public) ~this ns.bnNot = function() { var r = ns.nbi(); for (var i = 0; i < this.t; ++i) r[i] = this.DM & ~this[i]; r.t = this.t; r.s = ~this.s; return r; } // (public) this << n ns.bnShiftLeft = function(n) { var r = ns.nbi(); if (n < 0) this.rShiftTo(-n, r); else this.lShiftTo(n, r); return r; } // (public) this >> n ns.bnShiftRight = function(n) { var r = ns.nbi(); if (n < 0) this.lShiftTo(-n, r); else this.rShiftTo(n, r); return r; } // return index of lowest 1-bit in x, x < 2^31 ns.lbit = function(x) { if (x == 0) return -1; var r = 0; if ((x & 0xffff) == 0) { x >>= 16; r += 16; } if ((x & 0xff) == 0) { x >>= 8; r += 8; } if ((x & 0xf) == 0) { x >>= 4; r += 4; } if ((x & 3) == 0) { x >>= 2; r += 2; } if ((x & 1) == 0)++r; return r; } // (public) returns index of lowest 1-bit (or -1 if none) ns.bnGetLowestSetBit = function() { for (var i = 0; i < this.t; ++i) if (this[i] != 0) return i * this.DB + ns.lbit(this[i]); if (this.s < 0) return this.t * this.DB; return -1; } // return number of 1 bits in x ns.cbit = function(x) { var r = 0; while (x != 0) { x &= x - 1; ++r; } return r; } // (public) return number of set bits ns.bnBitCount = function() { var r = 0, x = this.s & this.DM; for (var i = 0; i < this.t; ++i) r += ns.cbit(this[i] ^ x); return r; } // (public) true iff nth bit is set ns.bnTestBit = function(n) { var j = Math.floor(n / this.DB); if (j >= this.t) return (this.s != 0); return ((this[j] & (1 << (n % this.DB))) != 0); } // (protected) this op (1<>= this.DB; } if (a.t < this.t) { c += a.s; while (i < this.t) { c += this[i]; r[i++] = c & this.DM; c >>= this.DB; } c += this.s; } else { c += this.s; while (i < a.t) { c += a[i]; r[i++] = c & this.DM; c >>= this.DB; } c += a.s; } r.s = (c < 0) ? -1 : 0; if (c > 0) r[i++] = c; else if (c < -1) r[i++] = this.DV + c; r.t = i; r.clamp(); } // (public) this + a ns.bnAdd = function(a) { var r = ns.nbi(); this.addTo(a, r); return r; } // (public) this - a ns.bnSubtract = function(a) { var r = ns.nbi(); this.subTo(a, r); return r; } // (public) this * a ns.bnMultiply = function(a) { var r = ns.nbi(); this.multiplyTo(a, r); return r; } // (public) this^2 ns.bnSquare = function() { var r = ns.nbi(); this.squareTo(r); return r; } // (public) this / a ns.bnDivide = function(a) { var r = ns.nbi(); this.divRemTo(a, r, null); return r; } // (public) this % a ns.bnRemainder = function(a) { var r = ns.nbi(); this.divRemTo(a, null, r); return r; } // (public) [this/a,this%a] ns.bnDivideAndRemainder = function(a) { var q = ns.nbi(), r = ns.nbi(); this.divRemTo(a, q, r); return new Array(q, r); } // (protected) this *= n, this >= 0, 1 < n < DV ns.bnpDMultiply = function(n) { this[this.t] = this.am(0, n - 1, this, 0, 0, this.t); ++this.t; this.clamp(); } // (protected) this += n << w words, this >= 0 ns.bnpDAddOffset = function(n, w) { if (n == 0) return; while (this.t <= w) this[this.t++] = 0; this[w] += n; while (this[w] >= this.DV) { this[w] -= this.DV; if (++w >= this.t) this[this.t++] = 0; ++this[w]; } } // A "null" reducer ns.NullExp = function() {} ns.nNop = function(x) { return x; } ns.nMulTo = function(x, y, r) { x.multiplyTo(y, r); } ns.nSqrTo = function(x, r) { x.squareTo(r); } ns.NullExp.prototype.convert = ns.nNop; ns.NullExp.prototype.revert = ns.nNop; ns.NullExp.prototype.mulTo = ns.nMulTo; ns.NullExp.prototype.sqrTo = ns.nSqrTo; // (public) this^e ns.bnPow = function(e) { return this.exp(e, new ns.NullExp()); } // (protected) r = lower n words of "this * a", a.t <= n // "this" should be the larger one if appropriate. ns.bnpMultiplyLowerTo = function(a, n, r) { var i = Math.min(this.t + a.t, n); r.s = 0; // assumes a,this >= 0 r.t = i; while (i > 0) r[--i] = 0; var j; for (j = r.t - this.t; i < j; ++i) r[i + this.t] = this.am(0, a[i], r, i, 0, this.t); for (j = Math.min(a.t, n); i < j; ++i) this.am(0, a[i], r, i, 0, n - i); r.clamp(); } // (protected) r = "this * a" without lower n words, n > 0 // "this" should be the larger one if appropriate. ns.bnpMultiplyUpperTo = function(a, n, r) { --n; var i = r.t = this.t + a.t - n; r.s = 0; // assumes a,this >= 0 while (--i >= 0) r[i] = 0; for (i = Math.max(n - this.t, 0); i < a.t; ++i) r[this.t + i - n] = this.am(n - i, a[i], r, 0, 0, this.t + i - n); r.clamp(); r.drShiftTo(1, r); } // Barrett modular reduction ns.Barrett = function(m) { // setup Barrett this.r2 = ns.nbi(); this.q3 = ns.nbi(); ns.BigInteger.ONE.dlShiftTo(2 * m.t, this.r2); this.mu = this.r2.divide(m); this.m = m; } ns.barrettConvert = function(x) { if (x.s < 0 || x.t > 2 * this.m.t) return x.mod(this.m); else if (x.compareTo(this.m) < 0) return x; else { var r = ns.nbi(); x.copyTo(r); this.reduce(r); return r; } } ns.barrettRevert = function(x) { return x; } // x = x mod m (HAC 14.42) ns.barrettReduce = function(x) { x.drShiftTo(this.m.t - 1, this.r2); if (x.t > this.m.t + 1) { x.t = this.m.t + 1; x.clamp(); } this.mu.multiplyUpperTo(this.r2, this.m.t + 1, this.q3); this.m.multiplyLowerTo(this.q3, this.m.t + 1, this.r2); while (x.compareTo(this.r2) < 0) x.dAddOffset(1, this.m.t + 1); x.subTo(this.r2, x); while (x.compareTo(this.m) >= 0) x.subTo(this.m, x); } // r = x^2 mod m; x != r ns.barrettSqrTo = function(x, r) { x.squareTo(r); this.reduce(r); } // r = x*y mod m; x,y != r ns.barrettMulTo = function(x, y, r) { x.multiplyTo(y, r); this.reduce(r); } ns.Barrett.prototype.convert = ns.barrettConvert; ns.Barrett.prototype.revert = ns.barrettRevert; ns.Barrett.prototype.reduce = ns.barrettReduce; ns.Barrett.prototype.mulTo = ns.barrettMulTo; ns.Barrett.prototype.sqrTo = ns.barrettSqrTo; // (public) this^e % m (HAC 14.85) ns.bnModPow = function(e, m) { var i = e.bitLength(), k, r = ns.nbv(1), z; if (i <= 0) return r; else if (i < 18) k = 1; else if (i < 48) k = 3; else if (i < 144) k = 4; else if (i < 768) k = 5; else k = 6; if (i < 8) z = new ns.Classic(m); else if (m.isEven()) z = new ns.Barrett(m); else z = new ns.Montgomery(m); // precomputation var g = new Array(), n = 3, k1 = k - 1, km = (1 << k) - 1; g[1] = z.convert(this); if (k > 1) { var g2 = ns.nbi(); z.sqrTo(g[1], g2); while (n <= km) { g[n] = ns.nbi(); z.mulTo(g2, g[n - 2], g[n]); n += 2; } } var j = e.t - 1, w, is1 = true, r2 = ns.nbi(), t; i = ns.nbits(e[j]) - 1; while (j >= 0) { if (i >= k1) w = (e[j] >> (i - k1)) & km; else { w = (e[j] & ((1 << (i + 1)) - 1)) << (k1 - i); if (j > 0) w |= e[j - 1] >> (this.DB + i - k1); } n = k; while ((w & 1) == 0) { w >>= 1; --n; } if ((i -= n) < 0) { i += this.DB; --j; } if (is1) { // ret == 1, don't bother squaring or multiplying it g[w].copyTo(r); is1 = false; } else { while (n > 1) { z.sqrTo(r, r2); z.sqrTo(r2, r); n -= 2; } if (n > 0) z.sqrTo(r, r2); else { t = r; r = r2; r2 = t; } z.mulTo(r2, g[w], r); } while (j >= 0 && (e[j] & (1 << i)) == 0) { z.sqrTo(r, r2); t = r; r = r2; r2 = t; if (--i < 0) { i = this.DB - 1; --j; } } } return z.revert(r); } // (public) gcd(this,a) (HAC 14.54) ns.bnGCD = function(a) { var x = (this.s < 0) ? this.negate() : this.clone(); var y = (a.s < 0) ? a.negate() : a.clone(); if (x.compareTo(y) < 0) { var t = x; x = y; y = t; } var i = x.getLowestSetBit(), g = y.getLowestSetBit(); if (g < 0) return x; if (i < g) g = i; if (g > 0) { x.rShiftTo(g, x); y.rShiftTo(g, y); } while (x.signum() > 0) { if ((i = x.getLowestSetBit()) > 0) x.rShiftTo(i, x); if ((i = y.getLowestSetBit()) > 0) y.rShiftTo(i, y); if (x.compareTo(y) >= 0) { x.subTo(y, x); x.rShiftTo(1, x); } else { y.subTo(x, y); y.rShiftTo(1, y); } } if (g > 0) y.lShiftTo(g, y); return y; } // (protected) this % n, n < 2^26 ns.bnpModInt = function(n) { if (n <= 0) return 0; var d = this.DV % n, r = (this.s < 0) ? n - 1 : 0; if (this.t > 0) if (d == 0) r = this[0] % n; else for (var i = this.t - 1; i >= 0; --i) r = (d * r + this[i]) % n; return r; } // (public) 1/this % m (HAC 14.61) ns.bnModInverse = function(m) { var ac = m.isEven(); if ((this.isEven() && ac) || m.signum() == 0) return ns.BigInteger.ZERO; var u = m.clone(), v = this.clone(); var a = ns.nbv(1), b = ns.nbv(0), c = ns.nbv(0), d = ns.nbv(1); while (u.signum() != 0) { while (u.isEven()) { u.rShiftTo(1, u); if (ac) { if (!a.isEven() || !b.isEven()) { a.addTo(this, a); b.subTo(m, b); } a.rShiftTo(1, a); } else if (!b.isEven()) b.subTo(m, b); b.rShiftTo(1, b); } while (v.isEven()) { v.rShiftTo(1, v); if (ac) { if (!c.isEven() || !d.isEven()) { c.addTo(this, c); d.subTo(m, d); } c.rShiftTo(1, c); } else if (!d.isEven()) d.subTo(m, d); d.rShiftTo(1, d); } if (u.compareTo(v) >= 0) { u.subTo(v, u); if (ac) a.subTo(c, a); b.subTo(d, b); } else { v.subTo(u, v); if (ac) c.subTo(a, c); d.subTo(b, d); } } if (v.compareTo(ns.BigInteger.ONE) != 0) return ns.BigInteger.ZERO; if (d.compareTo(m) >= 0) return d.subtract(m); if (d.signum() < 0) d.addTo(m, d); else return d; if (d.signum() < 0) return d.add(m); else return d; } ns.lowprimes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997]; ns.lplim = (1 << 26) / ns.lowprimes[ns.lowprimes.length - 1]; // (public) test primality with certainty >= 1-.5^t ns.bnIsProbablePrime = function(t) { var i, x = this.abs(); if (x.t == 1 && x[0] <= ns.lowprimes[ns.lowprimes.length - 1]) { for (i = 0; i < ns.lowprimes.length; ++i) if (x[0] == ns.lowprimes[i]) return true; return false; } if (x.isEven()) return false; i = 1; while (i < ns.lowprimes.length) { var m = ns.lowprimes[i], j = i + 1; while (j < ns.lowprimes.length && m < ns.lplim) m *= ns.lowprimes[j++]; m = x.modInt(m); while (i < j) if (m % ns.lowprimes[i++] == 0) return false; } return x.millerRabin(t); } // (protected) true if probably prime (HAC 4.24, Miller-Rabin) ns.bnpMillerRabin = function(t) { var n1 = this.subtract(ns.BigInteger.ONE); var k = n1.getLowestSetBit(); if (k <= 0) return false; var r = n1.shiftRight(k); t = (t + 1) >> 1; if (t > ns.lowprimes.length) t = ns.lowprimes.length; var a = ns.nbi(); for (var i = 0; i < t; ++i) { //Pick bases at random, instead of starting at 2 a.fromInt(ns.lowprimes[Math.floor(Math.random() * ns.lowprimes.length)]); var y = a.modPow(r, this); if (y.compareTo(ns.BigInteger.ONE) != 0 && y.compareTo(n1) != 0) { var j = 1; while (j++ < k && y.compareTo(n1) != 0) { y = y.modPowInt(2, this); if (y.compareTo(ns.BigInteger.ONE) == 0) return false; } if (y.compareTo(n1) != 0) return false; } } return true; } // protected ns.BigInteger.prototype.chunkSize = ns.bnpChunkSize; ns.BigInteger.prototype.toRadix = ns.bnpToRadix; ns.BigInteger.prototype.fromRadix = ns.bnpFromRadix; ns.BigInteger.prototype.fromNumber = ns.bnpFromNumber; ns.BigInteger.prototype.bitwiseTo = ns.bnpBitwiseTo; ns.BigInteger.prototype.changeBit = ns.bnpChangeBit; ns.BigInteger.prototype.addTo = ns.bnpAddTo; ns.BigInteger.prototype.dMultiply = ns.bnpDMultiply; ns.BigInteger.prototype.dAddOffset = ns.bnpDAddOffset; ns.BigInteger.prototype.multiplyLowerTo = ns.bnpMultiplyLowerTo; ns.BigInteger.prototype.multiplyUpperTo = ns.bnpMultiplyUpperTo; ns.BigInteger.prototype.modInt = ns.bnpModInt; ns.BigInteger.prototype.millerRabin = ns.bnpMillerRabin; // public ns.BigInteger.prototype.clone = ns.bnClone; ns.BigInteger.prototype.intValue = ns.bnIntValue; ns.BigInteger.prototype.byteValue = ns.bnByteValue; ns.BigInteger.prototype.shortValue = ns.bnShortValue; ns.BigInteger.prototype.signum = ns.bnSigNum; ns.BigInteger.prototype.toByteArray = ns.bnToByteArray; ns.BigInteger.prototype.equals = ns.bnEquals; ns.BigInteger.prototype.min = ns.bnMin; ns.BigInteger.prototype.max = ns.bnMax; ns.BigInteger.prototype.and = ns.bnAnd; ns.BigInteger.prototype.or = ns.bnOr; ns.BigInteger.prototype.xor = ns.bnXor; ns.BigInteger.prototype.andNot = ns.bnAndNot; ns.BigInteger.prototype.not = ns.bnNot; ns.BigInteger.prototype.shiftLeft = ns.bnShiftLeft; ns.BigInteger.prototype.shiftRight = ns.bnShiftRight; ns.BigInteger.prototype.getLowestSetBit = ns.bnGetLowestSetBit; ns.BigInteger.prototype.bitCount = ns.bnBitCount; ns.BigInteger.prototype.testBit = ns.bnTestBit; ns.BigInteger.prototype.setBit = ns.bnSetBit; ns.BigInteger.prototype.clearBit = ns.bnClearBit; ns.BigInteger.prototype.flipBit = ns.bnFlipBit; ns.BigInteger.prototype.add = ns.bnAdd; ns.BigInteger.prototype.subtract = ns.bnSubtract; ns.BigInteger.prototype.multiply = ns.bnMultiply; ns.BigInteger.prototype.divide = ns.bnDivide; ns.BigInteger.prototype.remainder = ns.bnRemainder; ns.BigInteger.prototype.divideAndRemainder = ns.bnDivideAndRemainder; ns.BigInteger.prototype.modPow = ns.bnModPow; ns.BigInteger.prototype.modInverse = ns.bnModInverse; ns.BigInteger.prototype.pow = ns.bnPow; ns.BigInteger.prototype.gcd = ns.bnGCD; ns.BigInteger.prototype.isProbablePrime = ns.bnIsProbablePrime; // JSBN-specific extension ns.BigInteger.prototype.square = ns.bnSquare; // --- random.js ------------------------------------------------------------------------------------------------ // // seedrandom.js version 2.0. // Author: David Bau 4/2/2011 // // Defines a method Math.seedrandom() that, when called, substitutes // an explicitly seeded RC4-based algorithm for Math.random(). Also // supports automatic seeding from local or network sources of entropy. // // Usage: // // // // Math.seedrandom('yipee'); Sets Math.random to a function that is // initialized using the given explicit seed. // // Math.seedrandom(); Sets Math.random to a function that is // seeded using the current time, dom state, // and other accumulated local entropy. // The generated seed string is returned. // // Math.seedrandom('yowza', true); // Seeds using the given explicit seed mixed // together with accumulated entropy. // // // Seeds using physical random bits downloaded // from random.org. // // Seeds using urandom bits from call.jsonlib.com, // which is faster than random.org. // // Examples: // // Math.seedrandom("hello"); // Use "hello" as the seed. // document.write(Math.random()); // Always 0.5463663768140734 // document.write(Math.random()); // Always 0.43973793770592234 // var rng1 = Math.random; // Remember the current prng. // // var autoseed = Math.seedrandom(); // New prng with an automatic seed. // document.write(Math.random()); // Pretty much unpredictable. // // Math.random = rng1; // Continue "hello" prng sequence. // document.write(Math.random()); // Always 0.554769432473455 // // Math.seedrandom(autoseed); // Restart at the previous seed. // document.write(Math.random()); // Repeat the 'unpredictable' value. // // Notes: // // Each time seedrandom('arg') is called, entropy from the passed seed // is accumulated in a pool to help generate future seeds for the // zero-argument form of Math.seedrandom, so entropy can be injected over // time by calling seedrandom with explicit data repeatedly. // // On speed - This javascript implementation of Math.random() is about // 3-10x slower than the built-in Math.random() because it is not native // code, but this is typically fast enough anyway. Seeding is more expensive, // especially if you use auto-seeding. Some details (timings on Chrome 4): // // Our Math.random() - avg less than 0.002 milliseconds per call // seedrandom('explicit') - avg less than 0.5 milliseconds per call // seedrandom('explicit', true) - avg less than 2 milliseconds per call // seedrandom() - avg about 38 milliseconds per call // // LICENSE (BSD): // // Copyright 2010 David Bau, all rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // 3. Neither the name of this module nor the names of its contributors may // be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // /** * All code is in an anonymous closure to keep the global namespace clean. * * @param {number=} overflow * @param {number=} startdenom */ (function (pool, math, width, chunks, significance, overflow, startdenom) { // // seedrandom() // This is the seedrandom function described above. // math['seedrandom'] = function seedrandom(seed, use_entropy) { var key = []; var arc4; // Flatten the seed string or build one from local entropy if needed. seed = mixkey(flatten( use_entropy ? [seed, pool] : arguments.length ? seed : [new Date().getTime(), pool, window], 3), key); // Use the seed to initialize an ARC4 generator. arc4 = new ARC4(key); // Mix the randomness into accumulated entropy. mixkey(arc4.S, pool); // Override Math.random // This function returns a random double in [0, 1) that contains // randomness in every bit of the mantissa of the IEEE 754 value. math['random'] = function random() { // Closure to return a random double: var n = arc4.g(chunks); // Start with a numerator n < 2 ^ 48 var d = startdenom; // and denominator d = 2 ^ 48. var x = 0; // and no 'extra last byte'. while (n < significance) { // Fill up all significant digits by n = (n + x) * width; // shifting numerator and d *= width; // denominator and generating a x = arc4.g(1); // new least-significant-byte. } while (n >= overflow) { // To avoid rounding up, before adding n /= 2; // last byte, shift everything d /= 2; // right using integer math until x >>>= 1; // we have exactly the desired bits. } return (n + x) / d; // Form the number within [0, 1). }; // Return the seed that was used return seed; }; // // ARC4 // // An ARC4 implementation. The constructor takes a key in the form of // an array of at most (width) integers that should be 0 <= x < (width). // // The g(count) method returns a pseudorandom integer that concatenates // the next (count) outputs from ARC4. Its return value is a number x // that is in the range 0 <= x < (width ^ count). // /** @constructor */ function ARC4(key) { var t, u, me = this, keylen = key.length; var i = 0, j = me.i = me.j = me.m = 0; me.S = []; me.c = []; // The empty key [] is treated as [0]. if (!keylen) { key = [keylen++]; } // Set up S using the standard key scheduling algorithm. while (i < width) { me.S[i] = i++; } for (i = 0; i < width; i++) { t = me.S[i]; j = lowbits(j + t + key[i % keylen]); u = me.S[j]; me.S[i] = u; me.S[j] = t; } // The "g" method returns the next (count) outputs as one number. me.g = function getnext(count) { var s = me.S; var i = lowbits(me.i + 1); var t = s[i]; var j = lowbits(me.j + t); var u = s[j]; s[i] = u; s[j] = t; var r = s[lowbits(t + u)]; while (--count) { i = lowbits(i + 1); t = s[i]; j = lowbits(j + t); u = s[j]; s[i] = u; s[j] = t; r = r * width + s[lowbits(t + u)]; } me.i = i; me.j = j; return r; }; // For robust unpredictability discard an initial batch of values. // See http://www.rsa.com/rsalabs/node.asp?id=2009 me.g(width); } // // flatten() // Converts an object tree to nested arrays of strings. // /** @param {Object=} result * @param {string=} prop * @param {string=} typ */ function flatten(obj, depth, result, prop, typ) { result = []; typ = typeof (obj); if (depth && typ == 'object') { for (prop in obj) { if (prop.indexOf('S') < 5) { // Avoid FF3 bug (local/sessionStorage) try { result.push(flatten(obj[prop], depth - 1)); } catch (e) {} } } } return (result.length ? result : obj + (typ != 'string' ? '\0' : '')); } // // mixkey() // Mixes a string seed into a key that is an array of integers, and // returns a shortened string seed that is equivalent to the result key. // /** @param {number=} smear * @param {number=} j */ function mixkey(seed, key, smear, j) { seed += ''; // Ensure the seed is a string smear = 0; for (j = 0; j < seed.length; j++) { key[lowbits(j)] = lowbits((smear ^= key[lowbits(j)] * 19) + seed.charCodeAt(j)); } seed = ''; for (j in key) { seed += String.fromCharCode(key[j]); } return seed; } // // lowbits() // A quick "n mod width" for width a power of 2. // function lowbits(n) { return n & (width - 1); } // // The following constants are related to IEEE 754 limits. // startdenom = math.pow(width, chunks); significance = math.pow(2, significance); overflow = significance * 2; // // When seedrandom.js is loaded, we immediately mix a few bits // from the built-in RNG into the entropy pool. Because we do // not want to intefere with determinstic PRNG state later, // seedrandom will not call math.random on its own again after // initialization. // mixkey(math.random(), pool); // End anonymous scope, and pass initial values. })([], // pool: entropy pool starts empty Math, // math: package containing random, pow, and seedrandom 256, // width: each RC4 output is 0 <= x < 256 6, // chunks: at least six RC4 outputs for each double 52 // significance: there are 52 significant digits in a double ); // This is not really a random number generator object, and two SeededRandom // objects will conflict with one another, but it's good enough for generating // the rsa key. ns.SeededRandom = function(){} ns.SRnextBytes = function(ba) { var i; for(i = 0; i < ba.length; i++) { ba[i] = Math.floor(Math.random() * 256); } } ns.SeededRandom.prototype.nextBytes = ns.SRnextBytes; // prng4.js - uses Arcfour as a PRNG ns.Arcfour = function() { this.i = 0; this.j = 0; this.S = new Array(); } // Initialize arcfour context from key, an array of ints, each from [0..255] ns.ARC4init = function(key) { var i, j, t; for(i = 0; i < 256; ++i) this.S[i] = i; j = 0; for(i = 0; i < 256; ++i) { j = (j + this.S[i] + key[i % key.length]) & 255; t = this.S[i]; this.S[i] = this.S[j]; this.S[j] = t; } this.i = 0; this.j = 0; } ns.ARC4next = function() { var t; this.i = (this.i + 1) & 255; this.j = (this.j + this.S[this.i]) & 255; t = this.S[this.i]; this.S[this.i] = this.S[this.j]; this.S[this.j] = t; return this.S[(t + this.S[this.i]) & 255]; } ns.Arcfour.prototype.init = ns.ARC4init; ns.Arcfour.prototype.next = ns.ARC4next; // Plug in your RNG constructor here ns.prng_newstate = function() { return new ns.Arcfour(); } // Pool size must be a multiple of 4 and greater than 32. // An array of bytes the size of the pool will be passed to init() ns.rng_psize = 256; // Random number generator - requires a PRNG backend, e.g. prng4.js // For best results, put code like // // in your main HTML document. ns.rng_state; ns.rng_pool; ns.rng_pptr; // Mix in a 32-bit integer into the pool ns.rng_seed_int = function(x) { ns.rng_pool[ns.rng_pptr++] ^= x & 255; ns.rng_pool[ns.rng_pptr++] ^= (x >> 8) & 255; ns.rng_pool[ns.rng_pptr++] ^= (x >> 16) & 255; ns.rng_pool[ns.rng_pptr++] ^= (x >> 24) & 255; if(ns.rng_pptr >= ns.rng_psize) ns.rng_pptr -= ns.rng_psize; } // Mix in the current time (w/milliseconds) into the pool ns.rng_seed_time = function() { ns.rng_seed_int(new Date().getTime()); } // Initialize the pool with junk if needed. if(ns.rng_pool == null) { ns.rng_pool = new Array(); ns.rng_pptr = 0; var t; if(navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto) { // Extract entropy (256 bits) from NS4 RNG if available var z = window.crypto.random(32); for(t = 0; t < z.length; ++t) ns.rng_pool[ns.rng_pptr++] = z.charCodeAt(t) & 255; } while(ns.rng_pptr < ns.rng_psize) { // extract some randomness from Math.random() t = Math.floor(65536 * Math.random()); ns.rng_pool[ns.rng_pptr++] = t >>> 8; ns.rng_pool[ns.rng_pptr++] = t & 255; } ns.rng_pptr = 0; ns.rng_seed_time(); //ns.rng_seed_int(window.screenX); //ns.rng_seed_int(window.screenY); } ns.rng_get_byte = function() { if(ns.rng_state == null) { ns.rng_seed_time(); ns.rng_state = ns.prng_newstate(); ns.rng_state.init(ns.rng_pool); for(ns.rng_pptr = 0; ns.rng_pptr < ns.rng_pool.length; ++ns.rng_pptr) ns.rng_pool[ns.rng_pptr] = 0; ns.rng_pptr = 0; //ns.rng_pool = null; } // TODO: allow reseeding after first request return ns.rng_state.next(); } ns.rng_get_bytes = function(ba) { var i; for(i = 0; i < ba.length; ++i) ba[i] = ns.rng_get_byte(); } ns.SecureRandom = function() {} ns.SecureRandom.prototype.nextBytes = ns.rng_get_bytes; // --- rsa.js --------------------------------------------------------------------------------------------------- // // Depends on jsbn.js and rng.js // Version 1.1: support utf-8 encoding in pkcs1pad2 // convert a (hex) string to a bignum object ns.parseBigInt = function(str, r) { return new ns.BigInteger(str, r); } ns.linebrk = function(s, n) { var ret = ""; var i = 0; while (i + n < s.length) { ret += s.substring(i, i + n) + "\n"; i += n; } return ret + s.substring(i, s.length); } ns.byte2Hex = function(b) { if (b < 0x10) return "0" + b.toString(16); else return b.toString(16); } // PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint ns.pkcs1pad2 = function(s, n) { if (n < s.length + 11) { // TODO: fix for utf-8 //alert("Message too long for RSA (n=" + n + ", l=" + s.length + ")"); //return null; throw "Message too long for RSA (n=" + n + ", l=" + s.length + ")"; } var ba = new Array(); var i = s.length - 1; while (i >= 0 && n > 0) { var c = s.charCodeAt(i--); if (c < 128) { // encode using utf-8 ba[--n] = c; } else if ((c > 127) && (c < 2048)) { ba[--n] = (c & 63) | 128; ba[--n] = (c >> 6) | 192; } else { ba[--n] = (c & 63) | 128; ba[--n] = ((c >> 6) & 63) | 128; ba[--n] = (c >> 12) | 224; } } ba[--n] = 0; var rng = new ns.SecureRandom(); var x = new Array(); while (n > 2) { // random non-zero pad x[0] = 0; while (x[0] == 0) rng.nextBytes(x); ba[--n] = x[0]; } ba[--n] = 2; ba[--n] = 0; return new ns.BigInteger(ba); } // "empty" RSA key constructor ns.RSAKey = function() { this.n = null; this.e = 0; this.d = null; this.p = null; this.q = null; this.dmp1 = null; this.dmq1 = null; this.coeff = null; } // Set the public key fields N and e from hex strings ns.RSASetPublic = function(N, E) { if (N != null && E != null && N.length > 0 && E.length > 0) { this.n = ns.parseBigInt(N, 16); this.e = parseInt(E, 16); } else alert("Invalid RSA public key"); } // Perform raw public operation on "x": return x^e (mod n) ns.RSADoPublic = function(x) { return x.modPowInt(this.e, this.n); } // Return the PKCS#1 RSA encryption of "text" as an even-length hex string ns.RSAEncrypt = function(text) { var m = ns.pkcs1pad2(text, (this.n.bitLength() + 7) >> 3); if (m == null) return null; var c = this.doPublic(m); if (c == null) return null; var h = c.toString(16); if ((h.length & 1) == 0) return h; else return "0" + h; } // Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string //ns.RSAEncryptB64 = function(text) { // var h = this.encrypt(text); // if(h) return hex2b64(h); else return null; //} // protected ns.RSAKey.prototype.doPublic = ns.RSADoPublic; // public ns.RSAKey.prototype.setPublic = ns.RSASetPublic; ns.RSAKey.prototype.encrypt = ns.RSAEncrypt; // Version 1.1: support utf-8 decoding in pkcs1unpad2 // Undo PKCS#1 (type 2, random) padding and, if valid, return the plaintext ns.pkcs1unpad2 = function(d, n) { var b = d.toByteArray(); var i = 0; while (i < b.length && b[i] == 0)++i; if (b.length - i != n - 1 || b[i] != 2) return null; ++i; while (b[i] != 0) if (++i >= b.length) return null; var ret = ""; while (++i < b.length) { var c = b[i] & 255; if (c < 128) { // utf-8 decode ret += String.fromCharCode(c); } else if ((c > 191) && (c < 224)) { ret += String.fromCharCode(((c & 31) << 6) | (b[i + 1] & 63)); ++i; } else { ret += String.fromCharCode(((c & 15) << 12) | ((b[i + 1] & 63) << 6) | (b[i + 2] & 63)); i += 2; } } return ret; } // Set the private key fields N, e, and d from hex strings ns.RSASetPrivate = function(N, E, D) { if (N != null && E != null && N.length > 0 && E.length > 0) { this.n = ns.parseBigInt(N, 16); this.e = parseInt(E, 16); this.d = ns.parseBigInt(D, 16); } else alert("Invalid RSA private key"); } // Set the private key fields N, e, d and CRT params from hex strings ns.RSASetPrivateEx = function(N, E, D, P, Q, DP, DQ, C) { if (N != null && E != null && N.length > 0 && E.length > 0) { this.n = ns.parseBigInt(N, 16); this.e = parseInt(E, 16); this.d = ns.parseBigInt(D, 16); this.p = ns.parseBigInt(P, 16); this.q = ns.parseBigInt(Q, 16); this.dmp1 = ns.parseBigInt(DP, 16); this.dmq1 = ns.parseBigInt(DQ, 16); this.coeff = ns.parseBigInt(C, 16); } else alert("Invalid RSA private key"); } // Generate a new random private key B bits long, using public expt E ns.RSAGenerate = function(B, E) { var rng = new ns.SeededRandom(); var qs = B >> 1; this.e = parseInt(E, 16); var ee = new ns.BigInteger(E, 16); for (;;) { for (;;) { this.p = new ns.BigInteger(B - qs, 1, rng); if (this.p.subtract(ns.BigInteger.ONE).gcd(ee).compareTo(ns.BigInteger.ONE) == 0 && this.p.isProbablePrime(10)) break; } for (;;) { this.q = new ns.BigInteger(qs, 1, rng); if (this.q.subtract(ns.BigInteger.ONE).gcd(ee).compareTo(ns.BigInteger.ONE) == 0 && this.q.isProbablePrime(10)) break; } if (this.p.compareTo(this.q) <= 0) { var t = this.p; this.p = this.q; this.q = t; } var p1 = this.p.subtract(ns.BigInteger.ONE); var q1 = this.q.subtract(ns.BigInteger.ONE); var phi = p1.multiply(q1); if (phi.gcd(ee).compareTo(ns.BigInteger.ONE) == 0) { this.n = this.p.multiply(this.q); this.d = ee.modInverse(phi); this.dmp1 = this.d.mod(p1); this.dmq1 = this.d.mod(q1); this.coeff = this.q.modInverse(this.p); break; } } } // Perform raw private operation on "x": return x^d (mod n) ns.RSADoPrivate = function(x) { if (this.p == null || this.q == null) return x.modPow(this.d, this.n); // TODO: re-calculate any missing CRT params var xp = x.mod(this.p).modPow(this.dmp1, this.p); var xq = x.mod(this.q).modPow(this.dmq1, this.q); while (xp.compareTo(xq) < 0) xp = xp.add(this.p); return xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq); } // Return the PKCS#1 RSA decryption of "ctext". // "ctext" is an even-length hex string and the output is a plain string. ns.RSADecrypt = function(ctext) { var c = ns.parseBigInt(ctext, 16); var m = this.doPrivate(c); if (m == null) return null; return ns.pkcs1unpad2(m, (this.n.bitLength() + 7) >> 3); } // protected ns.RSAKey.prototype.doPrivate = ns.RSADoPrivate; // public ns.RSAKey.prototype.setPrivate = ns.RSASetPrivate; ns.RSAKey.prototype.setPrivateEx = ns.RSASetPrivateEx; ns.RSAKey.prototype.generate = ns.RSAGenerate; ns.RSAKey.prototype.decrypt = ns.RSADecrypt; // // rsa-sign.js - adding signing functions to RSAKey class. // // // version: 1.0 (2010-Jun-03) // // Copyright (c) 2010 Kenji Urushima (kenji.urushima@gmail.com) // // This software is licensed under the terms of the MIT License. // http://www.opensource.org/licenses/mit-license.php // // The above copyright and license notice shall be // included in all copies or substantial portions of the Software. // // Depends on: // function sha1.hex(s) of sha1.js // jsbn.js // jsbn2.js // rsa.js // rsa2.js // // keysize / pmstrlen // 512 / 128 // 1024 / 256 // 2048 / 512 // 4096 / 1024 // As for _RSASGIN_DIHEAD values for each hash algorithm, see PKCS#1 v2.1 spec (p38). ns._RSASIGN_DIHEAD = []; ns._RSASIGN_DIHEAD['sha1'] = "3021300906052b0e03021a05000414"; ns._RSASIGN_DIHEAD['sha256'] = "3031300d060960864801650304020105000420"; //ns._RSASIGN_DIHEAD['md2'] = "3020300c06082a864886f70d020205000410"; //ns._RSASIGN_DIHEAD['md5'] = "3020300c06082a864886f70d020505000410"; //ns._RSASIGN_DIHEAD['sha384'] = "3041300d060960864801650304020205000430"; //ns._RSASIGN_DIHEAD['sha512'] = "3051300d060960864801650304020305000440"; ns._RSASIGN_HASHHEXFUNC = []; ns._RSASIGN_HASHHEXFUNC['sha1'] = ns.sha1.hex; ns._RSASIGN_HASHHEXFUNC['sha256'] = ns.sha256.hex; // ======================================================================== // Signature Generation // ======================================================================== ns._rsasign_getHexPaddedDigestInfoForString = function(s, keySize, hashAlg) { var pmStrLen = keySize / 4; var hashFunc = ns._RSASIGN_HASHHEXFUNC[hashAlg]; var sHashHex = hashFunc(s); var sHead = "0001"; var sTail = "00" + ns._RSASIGN_DIHEAD[hashAlg] + sHashHex; var sMid = ""; var fLen = pmStrLen - sHead.length - sTail.length; for (var i = 0; i < fLen; i += 2) { sMid += "ff"; } sPaddedMessageHex = sHead + sMid + sTail; return sPaddedMessageHex; } ns._rsasign_signString = function(s, hashAlg) { var hPM = ns._rsasign_getHexPaddedDigestInfoForString(s, this.n.bitLength(), hashAlg); var biPaddedMessage = ns.parseBigInt(hPM, 16); var biSign = this.doPrivate(biPaddedMessage); var hexSign = biSign.toString(16); return hexSign; } ns._rsasign_signStringWithSHA1 = function(s) { var hPM = ns._rsasign_getHexPaddedDigestInfoForString(s, this.n.bitLength(), 'sha1'); var biPaddedMessage = ns.parseBigInt(hPM, 16); var biSign = this.doPrivate(biPaddedMessage); var hexSign = biSign.toString(16); return hexSign; } ns._rsasign_signStringWithSHA256 = function(s) { var hPM = ns._rsasign_getHexPaddedDigestInfoForString(s, this.n.bitLength(), 'sha256'); var biPaddedMessage = ns.parseBigInt(hPM, 16); var biSign = this.doPrivate(biPaddedMessage); var hexSign = biSign.toString(16); return hexSign; } // ======================================================================== // Signature Verification // ======================================================================== ns._rsasign_getDecryptSignatureBI = function(biSig, hN, hE) { var rsa = new ns.RSAKey(); rsa.setPublic(hN, hE); var biDecryptedSig = rsa.doPublic(biSig); return biDecryptedSig; } ns._rsasign_getHexDigestInfoFromSig = function(biSig, hN, hE) { var biDecryptedSig = ns._rsasign_getDecryptSignatureBI(biSig, hN, hE); var hDigestInfo = biDecryptedSig.toString(16).replace(/^1f+00/, ''); return hDigestInfo; } ns._rsasign_getAlgNameAndHashFromHexDisgestInfo = function(hDigestInfo) { for (var algName in ns._RSASIGN_DIHEAD) { var head = ns._RSASIGN_DIHEAD[algName]; var len = head.length; if (hDigestInfo.substring(0, len) == head) { var a = [algName, hDigestInfo.substring(len)]; return a; } } return []; } ns._rsasign_verifySignatureWithArgs = function(sMsg, biSig, hN, hE) { var hDigestInfo = ns._rsasign_getHexDigestInfoFromSig(biSig, hN, hE); var digestInfoAry = ns._rsasign_getAlgNameAndHashFromHexDisgestInfo(hDigestInfo); if (digestInfoAry.length == 0) return false; var algName = digestInfoAry[0]; var diHashValue = digestInfoAry[1]; var ff = ns._RSASIGN_HASHHEXFUNC[algName]; var msgHashValue = ff(sMsg); return (diHashValue == msgHashValue); } ns._rsasign_verifyHexSignatureForMessage = function(hSig, sMsg) { var biSig = ns.parseBigInt(hSig, 16); var result = ns._rsasign_verifySignatureWithArgs(sMsg, biSig, this.n.toString(16), this.e.toString(16)); return result; } ns._rsasign_verifyString = function(sMsg, hSig) { hSig = hSig.replace(/[ \n]+/g, ""); var biSig = ns.parseBigInt(hSig, 16); var biDecryptedSig = this.doPublic(biSig); var hDigestInfo = biDecryptedSig.toString(16).replace(/^1f+00/, ''); var digestInfoAry = ns._rsasign_getAlgNameAndHashFromHexDisgestInfo(hDigestInfo); if (digestInfoAry.length == 0) return false; var algName = digestInfoAry[0]; var diHashValue = digestInfoAry[1]; var ff = ns._RSASIGN_HASHHEXFUNC[algName]; var msgHashValue = ff(sMsg); return (diHashValue == msgHashValue); } ns.RSAKey.prototype.signString = ns._rsasign_signString; ns.RSAKey.prototype.signStringWithSHA1 = ns._rsasign_signStringWithSHA1; ns.RSAKey.prototype.signStringWithSHA256 = ns._rsasign_signStringWithSHA256; ns.RSAKey.prototype.verifyString = ns._rsasign_verifyString; ns.RSAKey.prototype.verifyHexSignatureForMessage = ns._rsasign_verifyHexSignatureForMessage; // --- jscrypto.js ---------------------------------------------------------------------------------------------- // /** * Method to parse a pem encoded string containing both a public or private key. * The method will translate the pem encoded string in a der encoded string and * will parse private key and public key parameters. This method accepts public key * in the rsaencryption pkcs #1 format (oid: 1.2.840.113549.1.1.1). * * @todo Check how many rsa formats use the same format of pkcs #1. * * The format is defined as: * PublicKeyInfo ::= SEQUENCE { * algorithm AlgorithmIdentifier, * PublicKey BIT STRING * } * Where AlgorithmIdentifier is: * AlgorithmIdentifier ::= SEQUENCE { * algorithm OBJECT IDENTIFIER, the OID of the enc algorithm * parameters ANY DEFINED BY algorithm OPTIONAL (NULL for PKCS #1) * } * and PublicKey is a SEQUENCE encapsulated in a BIT STRING * RSAPublicKey ::= SEQUENCE { * modulus INTEGER, -- n * publicExponent INTEGER -- e * } * it's possible to examine the structure of the keys obtained from openssl using * an asn.1 dumper as the one used here to parse the components: http://lapo.it/asn1js/ * @argument {string} pem the pem encoded string, can include the BEGIN/END header/footer * @private */ ns.RSAKey.prototype.parseKey = function (pem) { try { var modulus = 0; var public_exponent = 0; var reHex = /^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/; var der = reHex.test(pem) ? Hex.decode(pem) : Base64.unarmor(pem); var asn1 = ASN1.decode(der); //Fixes a bug with OpenSSL 1.0+ private keys if(asn1.sub.length === 3){ asn1 = asn1.sub[2].sub[0]; } if (asn1.sub.length === 9) { // Parse the private key. modulus = asn1.sub[1].getHexStringValue(); //bigint this.n = ns.parseBigInt(modulus, 16); public_exponent = asn1.sub[2].getHexStringValue(); //int this.e = parseInt(public_exponent, 16); var private_exponent = asn1.sub[3].getHexStringValue(); //bigint this.d = ns.parseBigInt(private_exponent, 16); var prime1 = asn1.sub[4].getHexStringValue(); //bigint this.p = ns.parseBigInt(prime1, 16); var prime2 = asn1.sub[5].getHexStringValue(); //bigint this.q = ns.parseBigInt(prime2, 16); var exponent1 = asn1.sub[6].getHexStringValue(); //bigint this.dmp1 = ns.parseBigInt(exponent1, 16); var exponent2 = asn1.sub[7].getHexStringValue(); //bigint this.dmq1 = ns.parseBigInt(exponent2, 16); var coefficient = asn1.sub[8].getHexStringValue(); //bigint this.coeff = ns.parseBigInt(coefficient, 16); } else if (asn1.sub.length === 2) { // Parse the public key. var bit_string = asn1.sub[1]; var sequence = bit_string.sub[0]; modulus = sequence.sub[0].getHexStringValue(); this.n = ns.parseBigInt(modulus, 16); public_exponent = sequence.sub[1].getHexStringValue(); this.e = parseInt(public_exponent, 16); } else { return false; } return true; } catch (ex) { return false; } }; /** * Translate rsa parameters in a hex encoded string representing the rsa key. * * The translation follow the ASN.1 notation : * RSAPrivateKey ::= SEQUENCE { * version Version, * modulus INTEGER, -- n * publicExponent INTEGER, -- e * privateExponent INTEGER, -- d * prime1 INTEGER, -- p * prime2 INTEGER, -- q * exponent1 INTEGER, -- d mod (p1) * exponent2 INTEGER, -- d mod (q-1) * coefficient INTEGER, -- (inverse of q) mod p * } * @returns {string} DER Encoded String representing the rsa private key * @private */ ns.RSAKey.prototype.getPrivateBaseKey = function () { var options = { 'array': [ new KJUR.asn1.DERInteger({'int': 0}), new KJUR.asn1.DERInteger({'bigint': this.n}), new KJUR.asn1.DERInteger({'int': this.e}), new KJUR.asn1.DERInteger({'bigint': this.d}), new KJUR.asn1.DERInteger({'bigint': this.p}), new KJUR.asn1.DERInteger({'bigint': this.q}), new KJUR.asn1.DERInteger({'bigint': this.dmp1}), new KJUR.asn1.DERInteger({'bigint': this.dmq1}), new KJUR.asn1.DERInteger({'bigint': this.coeff}) ] }; var seq = new KJUR.asn1.DERSequence(options); return seq.getEncodedHex(); }; /** * base64 (pem) encoded version of the DER encoded representation * @returns {string} pem encoded representation without header and footer * @public */ ns.RSAKey.prototype.getPrivateBaseKeyB64 = function () { return ns.hex2b64(this.getPrivateBaseKey()); }; /** * Translate rsa parameters in a hex encoded string representing the rsa public key. * The representation follow the ASN.1 notation : * PublicKeyInfo ::= SEQUENCE { * algorithm AlgorithmIdentifier, * PublicKey BIT STRING * } * Where AlgorithmIdentifier is: * AlgorithmIdentifier ::= SEQUENCE { * algorithm OBJECT IDENTIFIER, the OID of the enc algorithm * parameters ANY DEFINED BY algorithm OPTIONAL (NULL for PKCS #1) * } * and PublicKey is a SEQUENCE encapsulated in a BIT STRING * RSAPublicKey ::= SEQUENCE { * modulus INTEGER, -- n * publicExponent INTEGER -- e * } * @returns {string} DER Encoded String representing the rsa public key * @private */ ns.RSAKey.prototype.getPublicBaseKey = function () { var options = { 'array': [ new KJUR.asn1.DERObjectIdentifier({'oid': '1.2.840.113549.1.1.1'}), //RSA Encryption pkcs #1 oid new KJUR.asn1.DERNull() ] }; var first_sequence = new KJUR.asn1.DERSequence(options); options = { 'array': [ new KJUR.asn1.DERInteger({'bigint': this.n}), new KJUR.asn1.DERInteger({'int': this.e}) ] }; var second_sequence = new KJUR.asn1.DERSequence(options); options = { 'hex': '00' + second_sequence.getEncodedHex() }; var bit_string = new KJUR.asn1.DERBitString(options); options = { 'array': [ first_sequence, bit_string ] }; var seq = new KJUR.asn1.DERSequence(options); return seq.getEncodedHex(); }; /** * base64 (pem) encoded version of the DER encoded representation * @returns {string} pem encoded representation without header and footer * @public */ ns.RSAKey.prototype.getPublicBaseKeyB64 = function () { return ns.hex2b64(this.getPublicBaseKey()); }; /** * wrap the string in block of width chars. The default value for rsa keys is 64 * characters. * @param {string} str the pem encoded string without header and footer * @param {Number} [width=64] - the length the string has to be wrapped at * @returns {string} * @private */ ns.RSAKey.prototype.wordwrap = function (str, width) { width = width || 64; if (!str) { return str; } var regex = '(.{1,' + width + '})( +|$\n?)|(.{1,' + width + '})'; return str.match(RegExp(regex, 'g')).join('\n'); }; /** * Retrieve the pem encoded private key * @returns {string} the pem encoded private key with header/footer * @public */ ns.RSAKey.prototype.getPrivateKey = function () { var key = "-----BEGIN RSA PRIVATE KEY-----\n"; key += this.wordwrap(this.getPrivateBaseKeyB64()) + "\n"; key += "-----END RSA PRIVATE KEY-----"; return key; }; /** * Retrieve the pem encoded public key * @returns {string} the pem encoded public key with header/footer * @public */ ns.RSAKey.prototype.getPublicKey = function () { var key = "-----BEGIN PUBLIC KEY-----\n"; key += this.wordwrap(this.getPublicBaseKeyB64()) + "\n"; key += "-----END PUBLIC KEY-----"; return key; }; /** * Check if the object contains the necessary parameters to populate the rsa modulus * and public exponent parameters. * @param {Object} [obj={}] - An object that may contain the two public key * parameters * @returns {boolean} true if the object contains both the modulus and the public exponent * properties (n and e) * @todo check for types of n and e. N should be a parseable bigInt object, E should * be a parseable integer number * @private */ ns.RSAKey.prototype.hasPublicKeyProperty = function (obj) { obj = obj || {}; return ( obj.hasOwnProperty('n') && obj.hasOwnProperty('e') ); }; /** * Check if the object contains ALL the parameters of an RSA key. * @param {Object} [obj={}] - An object that may contain nine rsa key * parameters * @returns {boolean} true if the object contains all the parameters needed * @todo check for types of the parameters all the parameters but the public exponent * should be parseable bigint objects, the public exponent should be a parseable integer number * @private */ ns.RSAKey.prototype.hasPrivateKeyProperty = function (obj) { obj = obj || {}; return ( obj.hasOwnProperty('n') && obj.hasOwnProperty('e') && obj.hasOwnProperty('d') && obj.hasOwnProperty('p') && obj.hasOwnProperty('q') && obj.hasOwnProperty('dmp1') && obj.hasOwnProperty('dmq1') && obj.hasOwnProperty('coeff') ); }; /** * Parse the properties of obj in the current rsa object. Obj should AT LEAST * include the modulus and public exponent (n, e) parameters. * @param {Object} obj - the object containing rsa parameters * @private */ ns.RSAKey.prototype.parsePropertiesFrom = function (obj) { this.n = obj.n; this.e = obj.e; if (obj.hasOwnProperty('d')) { this.d = obj.d; this.p = obj.p; this.q = obj.q; this.dmp1 = obj.dmp1; this.dmq1 = obj.dmq1; this.coeff = obj.coeff; } }; /** * Create a new JSEncryptRSAKey that extends Tom Wu's RSA key object. * This object is just a decorator for parsing the key parameter * @param {string|Object} key - The key in string format, or an object containing * the parameters needed to build a RSAKey object. * @constructor */ ns.JSEncryptRSAKey = function (key) { // Call the super constructor. ns.RSAKey.call(this); // If a key key was provided. if (key) { // If this is a string... if (typeof key === 'string') { this.parseKey(key); } else if ( this.hasPrivateKeyProperty(key) || this.hasPublicKeyProperty(key) ) { // Set the values for the key. this.parsePropertiesFrom(key); } } }; // Derive from RSAKey. ns.JSEncryptRSAKey.prototype = new ns.RSAKey(); // Reset the contructor. ns.JSEncryptRSAKey.prototype.constructor = ns.JSEncryptRSAKey; })( cryptodeps ); /*©mit************************************************************************** * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the MIT License, * * found in the file license_mit.txt. * * * *****************************************************************************©*/ /******************************************************************************* * * * Friend Crypto (fcrypt) v0.6 * * * * @dependency * * * * cryptojs/rollups/aes.js * * cryptojs/rollups/pbkdf2.js * * jsencrypt.js * * base64.js * * jscrypto.js * * hash.js * * jsbn.js * * random.js * * rsa.js * * * * @example * * * * // generate an object of private and public keys * * var keysObject = fcrypt.generateKeys(); * * * * // get private and public key as string * * var privateKey = fcrypt.getPrivateKey(); * * var publicKey = fcrypt.getPublicKey(); * * * * // get an object with privatekey and publickey * * var keys = fcrypt.getKeys(); * * * * // encrypt message with receivers public key * * var encrypted = fcrypt.encryptString( message, publicKey ); * * var ciphertext = encrypted.cipher; * * * * // decrypt cipertext with receivers privateKey * * var decrypted = fcrypt.decryptString( ciphertext, privateKey ); * * var plaintext = decrypted.plaintext; * * * * // create certificate and encrypt the data with owner keys * * var signed = fcrypt.signCertificate( data, publicKey, privateKey ); * * var certificate = signed.certificate; * * var signed = signed.signature; * * * * // verify certificate with owners private key * * var valid = fcrypt.verifyCertificate( certificate, privateKey, signed ); * * * * // sign a message with senders private key * * var signature = fcrypt.signString( message, privateKey ); * * * * // verify a message with senders signature and senders public key * * var valid = fcrypt.verifyString( message, signature, publicKey ); * * * * // generate a random/passphrased pbkdf2 or hash key * * var key = fcrypt.generateKey( passPhrase, bitLength, keySize, keyType ); * * * * // get the sha256 key that seeded the key generate * * var key = fcrypt.getKey(); * * * * // set a sha256 key as seed to generate lost private and public keys * * fcrypt.setKey( key ); * * * *******************************************************************************/ // TODO: Make support for WebCryptoAPI when there is "ALLOWED TIME FOR IT TO BE IMPLEMENTED" ... // (Also check if generateKey can be seeded with a passphrase, not just random generated keys) // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKey // https://github.com/diafygi/webcrypto-examples#rsassa-pkcs1-v1_5---generatekey var cryptoObj = window.crypto || window.msCrypto; if( cryptoObj !== "undefined" ) { //console.log( 'WebCryptoAPI', cryptoObj ); } var deps = ( typeof cryptodeps !== 'undefined' ? ( window.cryptodeps || cryptodeps ) : '' ); fcrypt = { // Public variables: _______________________________________________________ rsaKeySize: 1024, rsaKeyType: '03', //65537 default openssl public exponent for rsa key type aesKeySize: 256, aesKeyType: 'pbkdf2', aesBlockSize: 256, aesBitLength: 32, key: false, keysObject: false, privKeyObject: false, pubKeyObject: false, debug: false, base64Chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', // Public helper functions: __________________________________________________ bytes2string: function ( bts ) { var str = ''; for ( var i = 0; i < bts.length; i++ ) { str += String.fromCharCode( bts[i] ); } return str; }, string2bytes: function ( str ) { var bytes = new Array(); for ( var i = 0; i < str.length; i++ ) { bytes.push( str.charCodeAt(i) ); } return bytes; }, b16to64: function ( h ) { var i; var c; var ret = ''; if ( h.length % 2 == 1 ) { h = "0" + h; } for ( i = 0; i + 3 <= h.length; i += 3 ) { c = parseInt( h.substring( i, i + 3 ), 16 ); ret += this.base64Chars.charAt( c >> 6 ) + this.base64Chars.charAt( c & 63 ); } if ( i + 1 == h.length ) { c = parseInt( h.substring( i, i + 1 ), 16 ); ret += this.base64Chars.charAt( c << 2 ); } else if ( i + 2 == h.length ) { c = parseInt( h.substring( i, i + 2 ), 16 ); ret += this.base64Chars.charAt( c >> 2 ) + this.base64Chars.charAt( ( c & 3 ) << 4 ); } while ( ( ret.length & 3 ) > 0 ) ret += '='; return ret; }, b64to16: function ( s ) { // Which one to use? var int2ch = deps ? deps.int2char : int2char; var ret = ''; var i; var k = 0; var slop; for ( i = 0; i < s.length; ++i ) { if ( s.charAt(i) == '=' ) break; v = this.base64Chars.indexOf( s.charAt(i) ); if ( v < 0 ) continue; if ( k == 0 ) { ret += int2ch( v >> 2 ); slop = v & 3; k = 1; } else if ( k == 1 ) { ret += int2ch( (slop << 2) | (v >> 4) ); slop = v & 0xf; k = 2; } else if ( k == 2 ) { ret += int2ch( slop ); ret += int2ch( v >> 2 ); slop = v & 3; k = 3; } else { ret += int2ch( (slop << 2) | (v >> 4) ); ret += int2ch( v & 0xf ); k = 0; } } if ( k == 1 ) ret += int2ch( slop << 2 ); return ret; }, base64_encode: function( str ) { try { return btoa( str ); } catch( e ) { return str; } }, base64_decode: function( str ) { try { return atob( str ); } catch( e ) { return str; } }, encodeKeyHeader: function ( key ) { if( key ) { var encoded = ( key.indexOf( '-----BEGIN' ) >= 0 ? this.base64_encode( key ) : key ); if( encoded.indexOf( '-----BEGIN' ) < 0 ) { return encoded; } return key; } return false; }, decodeKeyHeader: function ( key ) { if( key ) { var decoded = ( key.indexOf( '-----BEGIN' ) < 0 ? this.base64_decode( key ) : key ); if( decoded.indexOf( '-----BEGIN' ) >= 0 ) { return decoded; } return key; } return false; }, trimWhitespaceTrail: function( str ) { var l = str.length - 1; var m = 0; for( var a = l; a > 0; a-- ) { if( m == 0 ) { var ch = str.charCodeAt( a ); if( ch == 0 || ch == 11 ) continue; else m = 1; } if( m == 1 ) { break; } } return str.substr( 0, a+1 ); }, linebrk: function ( s, n ) { var ret = ''; var i = 0; while ( i + n < s.length ) { ret += s.substring( i, i + n ) + "\n"; i += n; } return ret + s.substring( i, s.length ); }, stripHeader: function ( str ) { if( !str ) return false; str = str.split( "\r\n" ).join( "" ); str = str.split( "\n" ).join( "" ); str = str.replace( "-----BEGIN RSA PRIVATE KEY-----\n", '' ); str = str.replace( "-----BEGIN RSA PRIVATE KEY-----", '' ); str = str.replace( "-----BEGIN PRIVATE KEY-----\n", '' ); str = str.replace( "-----BEGIN PRIVATE KEY-----", '' ); str = str.replace( "-----END RSA PRIVATE KEY-----", '' ); str = str.replace( "-----END PRIVATE KEY-----", '' ); str = str.replace( "-----BEGIN RSA PUBLIC KEY-----\n", '' ); str = str.replace( "-----BEGIN RSA PUBLIC KEY-----", '' ); str = str.replace( "-----BEGIN PUBLIC KEY-----\n", '' ); str = str.replace( "-----BEGIN PUBLIC KEY-----", '' ); str = str.replace( "-----END RSA PUBLIC KEY-----", '' ); str = str.replace( "-----END PUBLIC KEY-----", '' ); str = str.replace( "-----BEGIN CERTIFICATE-----\n", '' ); str = str.replace( "-----BEGIN CERTIFICATE-----", '' ); str = str.replace( "-----END CERTIFICATE-----", '' ); str = str.replace( "\r\n", '' ); str = str.replace( "\n", '' ); return str; }, // Public functions: _______________________________________________________ generateKeys: function ( passPhrase, keySize, keyType, seedKey ) { keyType = ( keyType ? keyType : this.rsaKeyType ); keySize = ( keySize ? keySize : this.rsaKeySize ); var key = ( seedKey ? seedKey : this.generateKey( passPhrase, 32, 256, 'sha256' ) ); if ( key ) { Math.seedrandom( key ); var keysObject = ( deps ? new deps.RSAKey() : new RSAKey() ); keysObject.generate( keySize, keyType ); if( keysObject && typeof keysObject === 'object' ) { this.key = key; this.keysObject = keysObject; return keysObject; } } return false; }, getPrivateKey: function ( privKeyObject, cleanKey ) { if ( !privKeyObject ) { privKeyObject = ( this.privKeyObject ? this.privKeyObject : this.keysObject ); } if ( privKeyObject && typeof privKeyObject === 'object' ) { if( cleanKey ) { return this.stripHeader( privKeyObject.getPrivateKey() ); } else { return privKeyObject.getPrivateKey(); } } return false; }, getPublicKey: function ( pubKeyObject, cleanKey ) { if ( !pubKeyObject ) { pubKeyObject = ( this.pubKeyObject ? this.pubKeyObject : this.keysObject ); } if ( pubKeyObject && typeof pubKeyObject === 'object' ) { if( cleanKey ) { return this.stripHeader( pubKeyObject.getPublicKey() ); } else { return pubKeyObject.getPublicKey(); } } return false; }, getKeys: function ( privKeyObject, cleanKeys ) { if ( !privKeyObject ) { privKeyObject = ( this.privKeyObject ? this.privKeyObject : this.keysObject ); } if ( privKeyObject && typeof privKeyObject === 'object' ) { if( cleanKeys ) { var keys = { privatekey : this.stripHeader( privKeyObject.getPrivateKey() ), publickey : this.stripHeader( privKeyObject.getPublicKey() ), recoverykey : this.getKey() } } else { var keys = { privatekey : privKeyObject.getPrivateKey(), publickey : privKeyObject.getPublicKey(), recoverykey : this.getKey() } } return keys; } return false; }, getKey: function ( ) { if( this.key ) { return this.key; } return false; }, setKey: function ( key ) { if( key ) { this.key = key; return this.key; } return false; }, setPrivateKeyRSA: function ( str ) { str = this.decodeKeyHeader( str ); str = this.stripHeader( str ); var privKeyObject = ( deps ? new deps.RSAKey() : new RSAKey() ); privKeyObject.parseKey( str ); if ( privKeyObject && typeof privKeyObject === 'object' ) { this.privKeyObject = privKeyObject; return privKeyObject; } return false; }, setPublicKeyRSA: function ( str ) { str = this.decodeKeyHeader( str ); str = this.stripHeader( str ); var pubKeyObject = ( deps ? new deps.RSAKey() : new RSAKey() ); pubKeyObject.parseKey( str ); if ( pubKeyObject && typeof pubKeyObject === 'object' ) { this.pubKeyObject = pubKeyObject; return pubKeyObject; } return false; }, publicKeyID: function ( str ) { if ( !str ) { var pubKeyObject = ( pubKeyObject ? pubKeyObject : keysObject ); str = this.getPublicKey( pubKeyObject ); } if ( str ) { str = this.decodeKeyHeader( str ); return ( deps? deps.MD5( str ) : MD5( str ) ); } return false; }, generateKey: function ( passPhrase, bitLength, keySize, keyType ) { bitLength = ( bitLength ? bitLength : this.aesBitLength ); keySize = ( keySize ? keySize : this.aesKeySize ); keyType = ( keyType ? keyType : this.aesKeyType ); if ( !passPhrase ) { passPhrase = new Array(32); var r = ( deps ? new deps.SecureRandom() : new SecureRandom() ); r.nextBytes( passPhrase ); passPhrase = this.bytes2string( passPhrase ); } if ( keyType == 'sha256' ) { var key = ( deps ? deps.sha256.hex( passPhrase ) : sha256.hex( passPhrase ) ); if( key ) { return key; } } else { var salt = ( deps? deps.CryptoJS.lib.WordArray.random(128/8) : CryptoJS.lib.WordArray.random(128/8) ); var key = ( deps ? deps.CryptoJS.PBKDF2( passPhrase, salt, { keySize: 256/32, iterations: 500 } ) : CryptoJS.PBKDF2( passPhrase, salt, { keySize: 256/32, iterations: 500 } ) ); //var iv = ( deps ? deps.CryptoJS.enc.Hex.parse('101112131415161718191a1b1c1d1e1f') : CryptoJS.enc.Hex.parse('101112131415161718191a1b1c1d1e1f') ); // usually random var iv = salt; var key_base64 = key.toString( deps? deps.CryptoJS.enc.Base64 : CryptoJS.enc.Base64 ); var iv_base64 = iv.toString( deps? deps.CryptoJS.enc.Base64 : CryptoJS.enc.Base64 ); if ( key_base64 && iv_base64 ) { return { key: key_base64, iv: iv_base64 }; } } return false; }, encryptRSA: function ( plaintext, keysObject ) { if ( !plaintext ) return false; if ( keysObject && typeof keysObject === 'string' ) { keysObject = this.decodeKeyHeader( keysObject ); keysObject = this.setPublicKeyRSA( keysObject ); } if ( !keysObject ) { keysObject = ( this.pubKeyObject ? this.pubKeyObject : this.privKeyObject ); if ( keysObject ) { keysObject = this.keysObject; } } if ( keysObject ) { var encrypted = keysObject.encrypt( plaintext ); if ( encrypted ) { return this.b16to64( encrypted ); } } return false; }, decryptRSA: function ( encryptedText, keysObject ) { if ( !encryptedText ) return false; if ( keysObject && typeof keysObject === 'string' ) { keysObject = this.decodeKeyHeader( keysObject ); keysObject = this.setPrivateKeyRSA( keysObject ); } if ( !keysObject ) { keysObject = ( this.privKeyObject ? this.privKeyObject : this.pubKeyObject ); if ( keysObject ) { keysObject = this.keysObject; } } if ( keysObject ) { var decrypted = keysObject.decrypt( this.b64to16( encryptedText ) ); if ( decrypted ) { return decrypted; } } return false; }, encryptAES: function ( plaintext, key, iv ) { if( !plaintext || !key || !iv ) return false; var key_binary = ( deps ? deps.CryptoJS.enc.Base64.parse(key) : CryptoJS.enc.Base64.parse(key) ); var iv_binary = ( deps ? deps.CryptoJS.enc.Base64.parse(iv) : CryptoJS.enc.Base64.parse(iv) ); var encrypted = ( deps ? deps.CryptoJS.AES.encrypt( plaintext, key_binary, { iv: iv_binary } ) : CryptoJS.AES.encrypt( plaintext, key_binary, { iv: iv_binary } ) ); if( encrypted ) { var data_base64 = encrypted.ciphertext.toString( deps ? deps.CryptoJS.enc.Base64 : CryptoJS.enc.Base64 ); var iv_base64 = encrypted.iv.toString( deps ? deps.CryptoJS.enc.Base64 : CryptoJS.enc.Base64 ); var key_base64 = encrypted.key.toString( deps ? deps.CryptoJS.enc.Base64 : CryptoJS.enc.Base64 ); if ( this.debug ) { console.log( 'key: ' + key_binary ); console.log( 'iv: ' + iv_binary ); console.log( 'data(b64): ' + data_base64 ); console.log( 'iv(b64): ' + iv_base64 ); console.log( 'key(b64): ' + key_base64 ); } return { cipher: data_base64, key: key_base64, iv: iv_base64 }; } return false; }, decryptAES: function ( encryptedText, key, iv ) { if( !encryptedText || !key || !iv ) return false; data_binary = ( deps ? deps.CryptoJS.enc.Base64.parse(encryptedText) : CryptoJS.enc.Base64.parse(encryptedText) ); key_binary = ( deps ? deps.CryptoJS.enc.Base64.parse(key) : CryptoJS.enc.Base64.parse(key) ); iv_binary = ( deps ? deps.CryptoJS.enc.Base64.parse(iv) : CryptoJS.enc.Base64.parse(iv) ); var decrypted = ( deps ? deps.CryptoJS.AES.decrypt( { ciphertext: data_binary }, key_binary, { iv: iv_binary } ) : CryptoJS.AES.decrypt( { ciphertext: data_binary }, key_binary, { iv: iv_binary } ) ); if ( this.debug ) { console.log( 'cipher: ' + data_binary ); console.log( 'key: ' + key_binary ); console.log( 'iv: ' + iv_binary ); console.log( 'decrypted text: ' + decrypted ); console.log( 'decrypted text (utf8): ' + decrypted.toString( deps ? deps.CryptoJS.enc.Utf8 : CryptoJS.enc.Utf8 ) ); } if( decrypted ) { var plaintext = decrypted.toString( deps ? deps.CryptoJS.enc.Utf8 : CryptoJS.enc.Utf8 ); plaintext = this.trimWhitespaceTrail( plaintext ); return plaintext; } return false; }, encryptString: function ( plaintext, publicKey, signingKey ) { if ( !plaintext ) return false; publicKey = this.decodeKeyHeader( publicKey ); if ( signingKey ) { if ( signingKey && typeof signingKey === 'string' ) { signingKey = this.decodeKeyHeader( signingKey ); signingKey = this.setPrivateKeyRSA( signingKey ); } var pubkey = this.getPublicKey( signingKey ); pubkey = this.stripHeader( pubkey ); var signString = this.signString( plaintext, signingKey ); plaintext += '::52cee64bb3a38f6403386519a39ac91c::'; plaintext += pubkey; plaintext += '::52cee64bb3a38f6403386519a39ac91c::'; plaintext += signString; } var aeskey = this.generateKey(); if ( aeskey && typeof aeskey === 'object' ) { var AESdata = this.encryptAES( plaintext, aeskey.key, aeskey.iv ); if ( AESdata ) { var pubkey = ( publicKey ? this.setPublicKeyRSA( publicKey ) : this.pubKeyObject ); if ( pubkey ) { var RSAdata = this.encryptRSA( ( AESdata.key + '?' + AESdata.iv ), pubkey ) + '?'; } if ( !RSAdata ) { return { status: 'Invalid public key' }; } var cipherblock = ''; cipherblock += RSAdata; cipherblock += AESdata.cipher; if ( cipherblock ) { return { status: 'success', cipher: this.linebrk( cipherblock, 64 ) }; } } } return false; }, decryptString: function ( ciphertext, privKeyObject ) { if ( !ciphertext ) return false; if ( privKeyObject && typeof privKeyObject === 'string' ) { privKeyObject = this.decodeKeyHeader( privKeyObject ); privKeyObject = this.setPrivateKeyRSA( privKeyObject ); } if ( !privKeyObject ) { privKeyObject = ( this.privKeyObject ? this.privKeyObject : this.keysObject ); } if ( ciphertext && privKeyObject ) { ciphertext = this.stripHeader( ciphertext ); var cipherblock = ciphertext.split( '?' ); var aeskey = this.decryptRSA( cipherblock[0], privKeyObject ); if ( !aeskey ) { return { status: 'failure' }; } if( !cipherblock[1] && aeskey ) { return { status: 'success', plaintext: aeskey, signature: 'unsigned' }; } aeskey = aeskey.split( '?' ); var plaintext = this.decryptAES( cipherblock[1], aeskey[0], aeskey[1] ); if ( plaintext ) { plaintext = plaintext.split( '::52cee64bb3a38f6403386519a39ac91c::' ); var text = plaintext[0]; var cipher = plaintext[2]; if ( plaintext.length == 3 ) { var publickey = this.setPublicKeyRSA( plaintext[1] ); if ( this.verifyString( text, cipher, publickey ) ) { return { status: 'success', plaintext: text, signature: 'verified', publicKeyString: this.getPublicKey( publickey ) }; } else { return { status: 'success', plaintext: text, signature: 'forged', publicKeyString: this.getPublicKey( publickey ) }; } } else { return { status: 'success', plaintext: text, signature: 'unsigned' }; } } } return false; }, signString: function ( plaintext, signingKey ) { if ( !plaintext ) return false; if ( signingKey && typeof signingKey === 'string' ) { signingKey = this.decodeKeyHeader( signingKey ); signingKey = this.setPrivateKeyRSA( signingKey ); } if ( !signingKey ) { signingKey = ( this.privKeyObject ? this.privKeyObject : this.keysObject ); } if ( signingKey ) { var signed = signingKey.signString( plaintext, 'sha1' ); if ( signed ) { return this.b16to64( signed ); } } return false; }, verifyString: function ( plaintext, ciphertext, publicKey ) { if ( !plaintext || !ciphertext ) return false; if ( publicKey && typeof publicKey === 'string' ) { publicKey = this.decodeKeyHeader( publicKey ); publicKey = this.setPublicKeyRSA( publicKey ); } if ( !publicKey ) { publicKey = ( this.pubKeyObject ? this.pubKeyObject : this.keysObject ); } if ( publicKey ) { var signature = this.b64to16( ciphertext ); if ( publicKey.verifyString( plaintext, signature ) ) { return true; } return false; } return false; }, signCertificate: function ( data, publicKey, signingKey ) { if ( !data ) return false; publicKey = this.decodeKeyHeader( publicKey ); if ( signingKey && typeof signingKey === 'string' ) { signingKey = this.decodeKeyHeader( signingKey ); signingKey = this.setPrivateKeyRSA( signingKey ); } if ( !signingKey ) { signingKey = ( this.privKeyObject ? this.privKeyObject : this.keysObject ); } var certificate = this.encryptString( data, publicKey, signingKey ); if ( certificate && certificate.cipher ) { var signCert; signCert = "-----BEGIN CERTIFICATE-----\n"; signCert += certificate.cipher + "\n"; signCert += "-----END CERTIFICATE-----"; return { certificate: signCert, signature: this.publicKeyID( publicKey ) }; } return false; }, verifyCertificate: function ( ciphertext, privKeyObject, signature ) { if ( !ciphertext ) return false; var decrypted = this.decryptString( ciphertext, privKeyObject ); var isValid = ( decrypted.signature && decrypted.signature == 'verified' ? true : false ); if ( signature ) { var publicKeyID = this.publicKeyID( decrypted.publicKeyString ); if ( isValid && signature == publicKeyID ) { return true; } } else if ( isValid ) { return true; } return false; } }; // Clone scope if( !window.MD5 && deps ) { for( var a in deps ) { if( !window[ a ] ) window[ a ] = deps[ a ]; } } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* SHA-256 implementation in JavaScript (c) Chris Veness 2002-2014 / MIT Licence */ /* */ /* - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html */ /* http://csrc.nist.gov/groups/ST/toolkit/examples.html */ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* jshint node:true *//* global define, escape, unescape */ 'use strict'; /** * SHA-256 hash function reference implementation. * * @namespace */ var Sha256 = {}; /** * Generates SHA-256 hash of string. * * @param {string} msg - String to be hashed * @returns {string} Hash of msg as hex character string */ Sha256.hash = function(msg) { // convert string to UTF-8, as SHA only deals with byte-streams msg = msg.utf8Encode(); // constants [§4.2.2] var K = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 ]; // initial hash value [§5.3.1] var H = [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ]; // PREPROCESSING msg += String.fromCharCode(0x80); // add trailing '1' bit (+ 0's padding) to string [§5.1.1] // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1] var l = msg.length/4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length var N = Math.ceil(l/16); // number of 16-integer-blocks required to hold 'l' ints var M = new Array(N); for (var i=0; i>> 32, but since JS converts // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14]); M[N-1][15] = ((msg.length-1)*8) & 0xffffffff; // HASH COMPUTATION [§6.1.2] var W = new Array(64); var a, b, c, d, e, f, g, h; for (var i=0; i>> n) | (x << (32-n)); }; /** * Logical functions [§4.1.2]. * @private */ Sha256.Σ0 = function(x) { return Sha256.ROTR(2, x) ^ Sha256.ROTR(13, x) ^ Sha256.ROTR(22, x); }; Sha256.Σ1 = function(x) { return Sha256.ROTR(6, x) ^ Sha256.ROTR(11, x) ^ Sha256.ROTR(25, x); }; Sha256.σ0 = function(x) { return Sha256.ROTR(7, x) ^ Sha256.ROTR(18, x) ^ (x>>>3); }; Sha256.σ1 = function(x) { return Sha256.ROTR(17, x) ^ Sha256.ROTR(19, x) ^ (x>>>10); }; Sha256.Ch = function(x, y, z) { return (x & y) ^ (~x & z); }; Sha256.Maj = function(x, y, z) { return (x & y) ^ (x & z) ^ (y & z); }; /** * Hexadecimal representation of a number. * @private */ Sha256.toHexStr = function(n) { // note can't use toString(16) as it is implementation-dependant, // and in IE returns signed numbers when used on full words var s="", v; for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); } return s; }; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /** Extend String object with method to encode multi-byte string to utf8 * - monsur.hossa.in/2012/07/20/utf-8-in-javascript.html */ if (typeof String.prototype.utf8Encode == 'undefined') { String.prototype.utf8Encode = function() { return unescape( encodeURIComponent( this ) ); }; } /** Extend String object with method to decode utf8 string to multi-byte */ if (typeof String.prototype.utf8Decode == 'undefined') { String.prototype.utf8Decode = function() { try { return decodeURIComponent( escape( this ) ); } catch (e) { return this; // invalid UTF-8? return as-is } }; } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ if (typeof module != 'undefined' && module.exports) module.exports = Sha256; // CommonJs export if (typeof define == 'function' && define.amd) define([], function() { return Sha256; }); // AMD /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ // Simple abstraction for FriendUP crypto authentication Authenticate = { crypt : {}, path : false, uniqueId : false, username : false, password : false, seed : false, keys : false, methodMap : { 'load' : function( msg, app ) { Authenticate.getItem( msg, app ) }, 'uniqueid' : function( msg, app ) { Authenticate.getUniqueId( msg, app ); }, 'encryptkey' : function( msg, app ) { Authenticate.encryptKey( msg, app ); }, 'decryptkey' : function( msg, app ) { Authenticate.decryptKey( msg, app ); }, 'encrypt' : function( msg, app ) { Authenticate.encrypt( msg, app ); }, 'decrypt' : function( msg, app ) { Authenticate.decrypt( msg, app ); } }, // TODO: Clean up this mess as we finish features and get it to work .... mess is because of time pressure!!! ... // TODO: Add uniqueId and MD5 Hashed Password from somewhere, if not keys will be random // TODO: Recover privatekey from a sha256 recoverykey // TODO: Has to be tested and corrected before it will work. // TODO: Don't run init without checking for uniqueId first if required get it first init : function( conf ) { this.setup( conf ); if( !this.seed && !this.uniqueId && this.username ) { this.getUniqueId(); // TODO: Add some callback thing for getKeys() based on success or failed return this.getKeys(); } else { return this.getKeys(); } }, setup : function( conf ) { this.path = conf.path || false; this.uniqueId = conf.uniqueId || false; this.username = conf.username || false; this.password = conf.password || false; this.seed = conf.seed || false this.keys = conf.keys || false; }, receiveMsg : function( msg, app ) { var handler = this.methodMap[ msg.method ]; if ( !handler ) { msg.data.success = false; msg.data.message = 'no such handler'; this.send( msg, app ); return; } var success = this.checkId( msg ); if ( !success ) return; handler( msg, app ); }, setItem : function( msg, app ) { var bundle = msg.data; var appData = this.load( app ); if( bundle.id ) { appData[ bundle.id ] = bundle.data; } var success = this.save( appData, app ); bundle.success = success; this.send( msg, app ); }, getItem : function( msg, app ) { var bundle = msg.data; var appData = this.load( app ); var data = ( bundle.id ? appData[ bundle.id ] : appData ); bundle.data = data; this.send( msg, app ); }, removeItem : function( msg, app ) { var bundle = msg.data; var id = bundle.id; var appData = ( id ? this.load( app ) : '' ); if( id ) { delete appData[ id ]; } var success = this.save( appData, app ); bundle.success = success; this.send( msg, app ); }, send : function( msg, app ) { msg.command = 'authenticate'; app.contentWindow.postMessage( msg, '*' ); }, load : function( app ) { //console.log( 'Workspace.userId: ', Workspace.userId ); //console.log( 'app.applicationName: ', app.applicationName ); //console.log( 'app.userId: ', app.userId ); var authorized = ( Workspace.userId == app.userId ? true : false ); var appData = ( authorized ? window.localStorage.getItem( 'Workspace' ) : false ); appData = friendUP.tool.parse( appData ); if ( !appData ) appData = {}; return appData; }, save : function( appData, app ) { var authorized = ( Workspace.userId == app.userId ? true : false ); appData = ( authorized ? friendUP.tool.stringify( appData ) : false ); if ( !appData ) return false; window.localStorage.setItem( 'Workspace', appData ); return true; }, checkId : function( msg ) { var bundle = msg.data; if ( !bundle.id ) { return true; } var cleanId = bundle.id.toString().trim(); if ( cleanId !== bundle.id ) { returnError(); return false; } return true; function returnError() { console.log( 'authenticate - invalid msg', msg ); bundle.success = false; bundle.message = 'invalid id'; bundle.cleanId = cleanId || null; this.send( msg, app ); } }, encryptKey : function( msg, app ) { //console.log( 'msg encryptKey: ', msg ); var bundle = msg.data; this.crypt = fcrypt.generateKeys( msg.data.str ); //this.crypt = fcrypt.generateKeys(); this.keys = fcrypt.getKeys( this.crypt, true ); //console.log( 'this.keys: ', this.keys ); var storagekeys = this.load( app ) //console.log( 'storagekeys: ', storagekeys ); if( storagekeys && storagekeys.publickey ) { var encryptedKey = this.encrypt( this.keys.privatekey, storagekeys.publickey ); //console.log( 'encryptedkey: ', encryptedKey ); bundle.data = { publickey : storagekeys.publickey, encrypted : encryptedKey }; } else { bundle.data = false; } if( msg.callbackId && app ) { this.send( msg, app ); return true; } return ( bundle.data ? bundle.data : false ); }, getKeys : function( cleanKeys, conf ) { if( conf ) { this.setup( conf ); } if( this.keys.privatekey ) { this.crypt = fcrypt.setPrivateKeyRSA( this.keys.privatekey ); } else { var seed = this.seed || ''; if( !seed && this.uniqueId && this.password ) { // TODO: Add MD5 check for password, it has to be MD5hashed seed = this.uniqueId + ':' + this.password; } this.crypt = fcrypt.generateKeys( seed ); } this.keys = fcrypt.getKeys( this.crypt, cleanKeys ); if( this.keys ) { this.keys.privatekey = this.base64_encode( this.keys.privatekey ); this.keys.publickey = this.base64_encode( this.keys.publickey ); if( this.keys.recoverykey ) { this.keys.recoverykey = this.base64_encode( this.keys.recoverykey ); } } return this.keys; }, getPrivateKey : function() { var privateKey = this.crypt.getPrivateKey(); if( privateKey ) { return privateKey; } return false; }, getPublicKey : function() { var publicKey = this.crypt.getPublicKey(); if( publicKey ) { return publicKey; } return false; }, setRecoveryKey : function( key ) { // TODO: This has to be set before key generation starts // Perhaps add to this.seed this.crypt = fcrypt.setKey( key ); }, getRecoveryKey : function() { var key = this.crypt.getKey(); if( key ) { return key; } return false; }, encrypt : function( message, publicKey ) { //console.log( '--- fcrypt.encryptString --- : ' + message + ' [] ' + publicKey ); var encrypted = fcrypt.encryptString( message, publicKey ); var ciphertext = encrypted.cipher; if( ciphertext ) { return ciphertext; } return false; }, decrypt : function( ciphertext, privateKey ) { var decrypted = fcrypt.decryptString( ciphertext, privateKey ); var plaintext = decrypted.plaintext; if( plaintext ) { return plaintext; } return false; }, sign : function( message, privateKey ) { var signature = this.crypt.signString( message, privateKey ); if( signature ) { return signature; } return false; }, verify : function( message, signature, publicKey ) { var valid = this.crypt.verifyString( message, signature, publicKey ); if( valid ) { return valid; } return false; }, signCertificate : function( data, publicKey, privateKey ) { var signed = this.crypt.signCertificate( data, publicKey, privateKey ); var certificate = signed.certificate; var signature = signed.signature; if( signed ) { return signed; } return false; }, verifyCertificate : function( certificate, privateKey, signed ) { var valid = this.crypt.verifyCertificate( certificate, privateKey, signed ); if( valid ) { return valid; } return false; }, recoverAccount : function( recoveryKey ) { // Add callback // TODO: Add this more dynamic for other purposes then Treeroot if( recoveryKey ) { var token = this.getToken( recoveryKey ); if( token ) { return token; } } return false; }, getUniqueId : function( msg, app ) { // Add callback // TODO: Add this more dynamic for other purposes then Treeroot //console.log( 'msg: ', msg ); var self = this; var bundle = ( msg ? msg.data : {} ); this.path = ( bundle.path ? bundle.path : this.path ); this.username = ( bundle.username ? bundle.username : this.username ); if( this.path && this.username ) { var m = new Module( 'system' ); m.onExecuted = function( e, d ) { console.log( 'reqBack', { e : e, d : d }); if( e == 'ok' ) { d = JSON.parse( d ); console.log( d ); if( d.uniqueid ) { self.uniqueId = d.uniqueid; bundle.data = self.uniqueId; } else { // Add error messages } } else { // Add error messages } if( msg && msg.callbackId && app ) { self.send( msg, app ); } } m.execute( 'proxyget', { url : this.path, Username : this.username, Source : 'FriendUP', Encoding : 'json' } ); } }, getToken : function( recoveryKey ) { // Add callback // TODO: Add this more dynamic for other purposes then Treeroot if( this.path && this.uniqueId && this.keys.publickey ) { var m = new Module( 'system' ); m.onExecuted = function( e, d ) { console.log( 'reqBack', { e : e, d : d }); if( e == 'ok' ) { d = JSON.parse( d ); console.log( d ); if( d.password ) { var message = this.crypt.decryptRSA( d.password ); var signature = this.crypt.signString( message ); var mm = new Module( 'system' ); mm.onExecuted = function( ee, de ) { console.log( 'reqBack', { ee : ee, dd : dd }); if( ee == 'ok' ) { dd = JSON.parse( dd ); console.log( dd ); if( dd.sessionid ) { this.token = dd.sessionid; } else { // Add error messages } } else { // Add error messages } } mm.execute( 'proxyget', { url : this.path, UniqueID : this.uniqueId, Signature : signature, Source : 'FriendUP', Encoding : 'json' } ); } else { // Add error messages } } else { // Add error messages } } var args = { url : this.path, UniqueID : this.uniqueId, PublicKey : this.keys.publickey, Source : 'FriendUP', Encoding : 'json' } if( recoveryKey ) { args['RecoveryKey'] = recoveryKey; } m.execute( 'proxyget', args ); } }, base64_encode : function( str ) { var b64 = false; if( str ) { try { b64 = btoa( str ); } catch( e ) { return str; } } return b64; }, base64_decode : function( str ) { var b64 = false; if( str ) { try { b64 = atob( str ); } catch( e ) { return str; } } return b64; } }; // TODO: Add Namespacing if needed /* CryptoJS v3.0.2 code.google.com/p/crypto-js (c) 2009-2012 by Jeff Mott. All rights reserved. code.google.com/p/crypto-js/wiki/License */ var CryptoJS=CryptoJS||function(o,q){var l={},m=l.lib={},n=m.Base=function(){function a(){}return{extend:function(e){a.prototype=this;var c=new a;e&&c.mixIn(e);c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.$super.extend(this)}}}(),j=m.WordArray=n.extend({init:function(a,e){a= this.words=a||[];this.sigBytes=e!=q?e:4*a.length},toString:function(a){return(a||r).stringify(this)},concat:function(a){var e=this.words,c=a.words,d=this.sigBytes,a=a.sigBytes;this.clamp();if(d%4)for(var b=0;b>>2]|=(c[b>>>2]>>>24-8*(b%4)&255)<<24-8*((d+b)%4);else if(65535>>2]=c[b>>>2];else e.push.apply(e,c);this.sigBytes+=a;return this},clamp:function(){var a=this.words,e=this.sigBytes;a[e>>>2]&=4294967295<<32-8*(e%4);a.length=o.ceil(e/4)},clone:function(){var a= n.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var e=[],c=0;c>>2]>>>24-8*(d%4)&255;c.push((b>>>4).toString(16));c.push((b&15).toString(16))}return c.join("")},parse:function(a){for(var b=a.length,c=[],d=0;d>>3]|=parseInt(a.substr(d,2),16)<<24-4*(d%8);return j.create(c,b/2)}},p=k.Latin1={stringify:function(a){for(var b= a.words,a=a.sigBytes,c=[],d=0;d>>2]>>>24-8*(d%4)&255));return c.join("")},parse:function(a){for(var b=a.length,c=[],d=0;d>>2]|=(a.charCodeAt(d)&255)<<24-8*(d%4);return j.create(c,b)}},h=k.Utf8={stringify:function(a){try{return decodeURIComponent(escape(p.stringify(a)))}catch(b){throw Error("Malformed UTF-8 data");}},parse:function(a){return p.parse(unescape(encodeURIComponent(a)))}},b=m.BufferedBlockAlgorithm=n.extend({reset:function(){this._data=j.create(); this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=h.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var b=this._data,c=b.words,d=b.sigBytes,f=this.blockSize,i=d/(4*f),i=a?o.ceil(i):o.max((i|0)-this._minBufferSize,0),a=i*f,d=o.min(4*a,d);if(a){for(var h=0;h>>32-d)+f}function l(b,f,a,e,c,d,g){b=b+(f&e|a&~e)+c+g;return(b<>>32-d)+f}function m(b,f,a,e,c,d,g){b=b+(f^a^e)+c+g;return(b<>>32-d)+f}function n(b,f,a,e,c,d,g){b=b+(a^(f|~e))+c+g;return(b<>>32-d)+f}var j=CryptoJS,k=j.lib,r=k.WordArray,k=k.Hasher,p=j.algo,h=[];(function(){for(var b=0;64>b;b++)h[b]=4294967296*o.abs(o.sin(b+1))|0})();p=p.MD5=k.extend({_doReset:function(){this._hash=r.create([1732584193,4023233417, 2562383102,271733878])},_doProcessBlock:function(b,f){for(var a=0;16>a;a++){var e=f+a,c=b[e];b[e]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360}for(var e=this._hash.words,c=e[0],d=e[1],g=e[2],i=e[3],a=0;64>a;a+=4)16>a?(c=q(c,d,g,i,b[f+a],7,h[a]),i=q(i,c,d,g,b[f+a+1],12,h[a+1]),g=q(g,i,c,d,b[f+a+2],17,h[a+2]),d=q(d,g,i,c,b[f+a+3],22,h[a+3])):32>a?(c=l(c,d,g,i,b[f+(a+1)%16],5,h[a]),i=l(i,c,d,g,b[f+(a+6)%16],9,h[a+1]),g=l(g,i,c,d,b[f+(a+11)%16],14,h[a+2]),d=l(d,g,i,c,b[f+a%16],20,h[a+3])):48>a?(c= m(c,d,g,i,b[f+(3*a+5)%16],4,h[a]),i=m(i,c,d,g,b[f+(3*a+8)%16],11,h[a+1]),g=m(g,i,c,d,b[f+(3*a+11)%16],16,h[a+2]),d=m(d,g,i,c,b[f+(3*a+14)%16],23,h[a+3])):(c=n(c,d,g,i,b[f+3*a%16],6,h[a]),i=n(i,c,d,g,b[f+(3*a+7)%16],10,h[a+1]),g=n(g,i,c,d,b[f+(3*a+14)%16],15,h[a+2]),d=n(d,g,i,c,b[f+(3*a+5)%16],21,h[a+3]));e[0]=e[0]+c|0;e[1]=e[1]+d|0;e[2]=e[2]+g|0;e[3]=e[3]+i|0},_doFinalize:function(){var b=this._data,f=b.words,a=8*this._nDataBytes,e=8*b.sigBytes;f[e>>>5]|=128<<24-e%32;f[(e+64>>>9<<4)+14]=(a<<8|a>>> 24)&16711935|(a<<24|a>>>8)&4278255360;b.sigBytes=4*(f.length+1);this._process();b=this._hash.words;for(f=0;4>f;f++)a=b[f],b[f]=(a<<8|a>>>24)&16711935|(a<<24|a>>>8)&4278255360}});j.MD5=k._createHelper(p);j.HmacMD5=k._createHmacHelper(p)})(Math); /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ var friendUP = friendUP || {}; friendUP.tool = friendUP.tool || {}; (function( ns ) { ns.getRandomNumber = function( length ) { length = length || 15; var numString = ''; while ( numString.length <= length ) { var part = getNum(); numString += part; } var slice = numString.slice( 0, length ); return parseInt( slice, 10 ); function getNum() { var decimal = Math.random(); var movedDecimalPoint = decimal * Math.pow( 10, 10 ); var integer = Math.floor( movedDecimalPoint ); return integer.toString(); } } ns.getRandomString = function( length ) { var string = ''; do { var part = ''; var floater = Math.random(); var base36 = floater.toString( 36 ); // .toString is magic, like friendship part = base36.slice( 2 ); // removing the first two characters, they do not please me ( actually, only the 2nd, wich is a period, but thats what you get for being associated with a period ) string += part; } while ( string.length < length ) return string.slice( 0, length ); } ns.getChatTime = function( timestamp ) { var time = new Date( timestamp ); var timeString = ''; if ( moreThanADayAgo( timestamp )) return justDate(); return clockStamp(); function clockStamp() { var timeStr = pad( time.getHours() ) + ':' + pad( time.getMinutes() ) + ':' + pad( time.getSeconds() ); if ( isYesterday()) timeStr = 'yesterday ' + timeStr; return timeStr; function pad( time ) { var str = time.toString(); return str.length !== 1 ? str : '0' + str; } function isYesterday() { var now = new Date(); var today = now.getDate(); var date = time.getDate(); return today !== date; } } function justDate( timestamp ) { var date = time.toLocaleDateString(); return date; } function moreThanADayAgo( timestamp ) { var now = Date.now(); var aDay = 1000 * 60 * 60 * 24; return !!(( now - aDay ) > timestamp ); } } const idCache = {}; // the best solution? possibly not.. >.> ns.getId = function( prefix, length ) { var prefix = startWithAlpha( prefix ) || 'id'; length = length || 36; length = Math.max( length, 11 ); prefix = limit( prefix, ( length / 3 )); var partLength = 8; let id=''; do { id = prefix; id = createId( id ); id = id.slice(0, length ); id = removeTrailingHyphen( id ); } while( idCache[ id ]) idCache[ id ] = id; return id; function createId( str ) { do { var part = ns.getRandomString( partLength ); str = str + '-' + part; } while ( str.length < length ); return str; } function removeTrailingHyphen ( str ) { var lastChar = str[ str.length-1 ]; if ( lastChar == '-' ) return str.slice( 0, str.length-1 ); return str; } function startWithAlpha( prefix ) { if ( typeof( prefix ) !== 'string' ) return false; if ( !( prefix[ 0 ].match( /[a-zA-Z]/ )) ) return 'id-' + prefix; return prefix; } function limit( str, max ) { var len = str.length; var end = Math.min( len, max ); return str.slice( 0, end ); } } ns.uid = ns.getId; ns.stringify = function( obj ) { if ( typeof obj === 'string' ) return obj; try { return JSON.stringify( obj ); } catch (e) { console.log( 'tool.Stringify.exception', e ); console.log( 'tool.Stringifu.exception - returning .toString(): ', obj.toString()); return obj.toString(); // not an object? probably has .toString() then.. #YOLO #360-NO-SCOPE } } ns.parse = function( string ) { if ( typeof string !== 'string' ) return string; try { return JSON.parse( string ); } catch ( e ) { try { return JSON.parse( string.split( '\\"' ).join( '"' ) ); } catch( e ) { console.log( 'could not objectify:', string ); return null; } } } ns.objectify = ns.parse; ns.queryString = function( params ) { if ( typeof( params ) === 'string' ) return params; var pairs = Object.keys( params ).map( pair ) function pair( key ) { var value = params[ key ]; return key + '=' + value; } return pairs.join( '&' ); } ns.getCssValue = function( element, style, pseudo ) { pseudo = pseudo || null; // css pesudo selector if ( element.style[ style ] && !pseudo ) return element.style[ style ]; if ( element.currentStyle ) return element.currentStyle( style ); if ( window.getComputedStyle ) { var styles = window.getComputedStyle( element, pseudo ); return styles[ style ]; } } ns.ucfirst = function( string ) { // yes, it doesnt handle your prepending whitepsace, fix if you like if ( !string.length ) return string; var arr = string.split( '' ); arr[ 0 ] = arr[ 0 ].toUpperCase(); string = arr.join( '' ); return string; } })( friendUP.tool ); /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ var friendUP = friendUP || {}; friendUP.component = friendUP.component || {}; friendUP.gui = friendUP.gui || {}; (function ( ns, undefined ) { ns.HTMLBuilder = function() { if ( !( this instanceof ns.HTMLBuilder )) return new ns.HTMLBuilder(); var self = this; self.cache = {}; } ns.HTMLBuilder.prototype.build = function( tmpl, id, data ) { var self = this; // beware: single quotes in your html ( ' ) will crash the parser. // Single quotes in variables passed to the tempalte is okay // Source : http://ejohn.org/blog/javascript-micro-templating/ self.cache[ id ] = self.cache[ id ] || new Function('obj', "var p = [], print = function() {p.push.apply(p,arguments);};" + "with(obj) {p.push('" + tmpl .replace(/[\r\t\n]/g, " ") .split("<%").join("\t") .replace(/((^|%>)[^\t]*)'/g, "$1\r") .replace(/\t=(.*?)%>/g, "',$1,'") .split("\t").join("');") .split("%>").join("p.push('") .split("\r").join("\\") + "');} return p.join('');" ); return data ? self.cache[id]( data ) : self.cache[ id ]; }; ns.HTMLBuilder.prototype.get = function( tmpl, id, data ) { var self = this; if ( tmpl.trim ) tmpl = tmpl.trim(); if ( !tmpl || !id || !data ) { console.log( 'friendUP.component.HTMLBuilder.get - missing arguments', { tmpl : tmpl, id : id, data : data } ); return null; } return self.build( tmpl, id, data ); } })( friendUP.component ); // html tempalte parsing functions // feed it a string of html elements. It will grab the id and and inner html of every top element. (function( ns, undefined ) { ns.TemplateManager = function ( fragments ) { if ( !( this instanceof ns.TemplateManager )) return new ns.TemplateManager( fragments ); var self = this; self.cache = null; self.init( fragments || '' ); } ns.TemplateManager.prototype.init = function( fragments ) { var self = this; self.fragments = {}; self.cache = new friendUP.component.HTMLBuilder(); if ( fragments ) self.addFragments( fragments ); } ns.TemplateManager.prototype.get = function( id, data ) { var self = this; var fragment = self.fragments[ id ]; if ( !fragment ) throw new Error( 'template.get - invalid template id: ' + id ); data = data || {}; var html = self.cache.get( fragment, id, data ); return html; } ns.TemplateManager.prototype.getFragment = function( id, data ) { var self = this; var tmpl = self.get( id, data ); var fragment = document.createElement( 'div' ); fragment.innerHTML = tmpl; return fragment; } ns.TemplateManager.prototype.getElement = function( id, data ) { var self = this; var fragment = self.getFragment( id, data ); if ( fragment.children.length != 1 ) return fragment; else return fragment.children[ 0 ]; } ns.TemplateManager.prototype.addFragments = function( fragments ) { var self = this; if ( !fragments ) return; if ( 'string' === typeof( fragments )) { var container = document.createElement( 'div' ); container.innerHTML = fragments; fragments = container; } var fragments = fragments.children; if ( !fragments.length ) { console.log( 'TemplateManager.addFragments - no fragments found ', fragments ); return; } Array.prototype.forEach.call( fragments, extractHtml ); function extractHtml( element ) { if ( !element.id ) return; var id = element.id; var fragment = element.textContent; if ( !fragment || !fragment.length ) return; self.fragments[ id ] = fragment.trim(); } } })( friendUP.gui ); /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ /* Some important flags for GUI elements ------------------------------------ */ var DEFAULT_SANDBOX_ATTRIBUTES = 'allow-same-origin allow-forms allow-scripts allow-popups allow-popups-to-escape-sandbox allow-downloads allow-modals allow-top-navigation-by-user-activation allow-presentation'; var FUI_MOUSEDOWN_RESIZE = 2; var FUI_MOUSEDOWN_WINDOW = 1; var FUI_MOUSEDOWN_SCREEN = 3; var FUI_MOUSEDOWN_SCROLLV = 10; var FUI_WINDOW_MARGIN = 3; var FUI_MOUSEDOWN_PICKOBJ = 11; /* Make sure we figure out what the browser fires --------------------------- */ var friendInputMethodOverride = false; window.addEventListener( 'touchstart', function() { if( !friendInputMethodOverride ) friendInputMethodOverride = 'touch'; } ); window.addEventListener( 'click', function() { if( !friendInputMethodOverride ) friendInputMethodOverride = 'click'; } ); /* Done important flags for GUI elements ------------------------------------ */ // Container of settings for virtual workspaces var virtualWorkspaces = []; function setVirtualWorkspaceInformation( num, flag, value ) { if( typeof( num ) == 'undefined' || num < 0 ) return; if( typeof( virtualWorkspaces[ num ] ) == 'undefined' ) { virtualWorkspaces[ num ] = { 'activeWindow': false }; } switch( flag.toLowerCase() ) { case 'activewindow': virtualWorkspaces[ num ].activeWindow = value; break; } } function cleanVirtualWorkspaceInformation() { for( let a in virtualWorkspaces ) { if( virtualWorkspaces[ a ].activeWindow && virtualWorkspaces[ a ].activeWindow.workspace != a ) { virtualWorkspaces[ a ].activeWindow = null; } } } function isTouchDevice() { // We found an override if( friendInputMethodOverride == 'touch' ) { return true; } else if( friendInputMethodOverride == 'click' ) { return false; } return ( ( 'ontouchstart' in window ) || ( navigator.maxTouchPoints > 0 ) || ( navigator.msMaxTouchPoints > 0 ) ); } // Window information var movableHighestZindex = 99; var movableWindowCount = 0; var movableWindows = []; var windowMouseX = -1; var windowMouseY = -1; var mousePointer = { prevMouseX: -1, prevMouseY: -1, elements: [], dom: false, testPointer: function () { if( !ge( 'MousePointer' ) ) { let d = document.createElement( 'div' ); d.id = 'MousePointer'; d.style.position = 'absolute'; d.style.zIndex = 10000; d.style.opacity = 0.7; d.style.whiteSpace = 'nowrap'; document.body.appendChild( d ); this.dom = d; } }, move: function( e ) { if ( !e ) e = window.event; let tar = e.target ? e.target : e.srcElement; // If we have elements, it means we have icons! if ( this.elements.length ) { // Make sure we don't have problems with iframes! CoverWindows(); // Object moved over let mover = false; let moveWin = false; // Get mouse coords if( isTouchDevice() ) { windowMouseX = e.touches[0].pageX; windowMouseY = e.touches[0].pageY; } else { windowMouseX = e.clientX; windowMouseY = e.clientY; } // Skew them if( ge( 'DoorsScreen' ).screenOffsetTop ) windowMouseY -= ge( 'DoorsScreen' ).screenOffsetTop; // Check move on window let z = 0; for( let a in movableWindows ) { let wn = movableWindows[a]; let wnZ = parseInt ( wn.style.zIndex ); if ( wn.offsetTop < windowMouseY && wn.offsetLeft < windowMouseX && wn.offsetTop + wn.offsetHeight > windowMouseY && wn.offsetLeft + wn.offsetWidth > windowMouseX && wnZ >= z ) { moveWin = false; if( wn.content && wn.content.fileBrowser ) { if( !wn.content.fileBrowser.dom.classList.contains( 'Hidden' ) ) { let fb = wn.content.fileBrowser.dom; if( windowMouseX < wn.offsetLeft + fb.offsetWidth && windowMouseY < wn.offsetTop + fb.offsetHeight ) { moveWin = wn.content.fileBrowser; } } } if( !moveWin ) { moveWin = wn; } z = wnZ; } } if( moveWin ) { // Roll over filebrowser if( moveWin && moveWin.dom && moveWin.dom.classList.contains( 'FileBrowser' ) ) { if( moveWin.rollOver ) { moveWin.rollOver( this.elements ); } } // Add mouse actions on window -> if( moveWin.windowObject && moveWin.windowObject.sendMessage ) { moveWin.windowObject.sendMessage( { command: 'inputcoordinates', data: { x: windowMouseX - moveWin.content.offsetLeft - moveWin.offsetLeft, y: windowMouseY - moveWin.content.offsetTop - moveWin.offsetTop } } ); } } // Check screens and view windows let screens = []; let screenl = ge( 'Screens' ); for( let a = 0; a < screenl.childNodes.length; a++ ) { if( screenl.childNodes[a].tagName == 'DIV' && screenl.childNodes[a].classList && screenl.childNodes[a].classList.contains( 'Screen' ) ) { screens.push( screenl.childNodes[a].screen._screen ); } } let ars = []; for( let a in movableWindows ) ars.push( movableWindows[a] ); ars = ars.concat( screens ); for( let c in ars ) { let isListView = false; let isScreen = false; let w = ars[c].icons ? ars[c] : ars[c].content; if( !w || !w.icons ) continue; // No icons? Skip! let sctop = 0; // Listview counts from a different element let iconArea = w; if( w.directoryview && w.directoryview.listMode == 'listview' ) { iconArea = w.querySelector( '.ScrollArea' ); sctop = [ iconArea ]; isListView = true; } else { if( !w.classList.contains( 'ScreenContent' ) ) { sctop = w.getElementsByClassName( 'Scroller' ); } // We're checking screen icons else { isScreen = true; } } let scleft = 0; if( sctop && sctop.length ) { scleft = sctop[0].scrollLeft; sctop = sctop[0].scrollTop; } else sctop = 0; let my = windowMouseY - GetElementTop( iconArea ) + sctop; let mx = windowMouseX - GetElementLeft( iconArea ) + scleft; // Add file browser offset if( w.fileBrowser && !isListView ) { if( !w.fileBrowser.dom.classList.contains( 'Hidden' ) ) { let fb = w.fileBrowser.dom; mx -= fb.offsetWidth; } } let hoverIcon = false; for( let a = 0; a < w.icons.length; a++ ) { let ic = w.icons[a].domNode; let icon = w.icons[a]; // Exclude elements dragged let found = false; for( let b = 0; b < this.dom.childNodes.length; b++ ) { if( ic == this.dom.childNodes[b] ) found = true; } if( found ) { continue; } // Done exclude if( ic ) { if( !hoverIcon && !mover && ( isScreen || ( moveWin && w == moveWin.content ) ) && ic.offsetTop < my && ic.offsetLeft < mx && ic.offsetTop + ic.offsetHeight > my && ic.offsetLeft + ic.offsetWidth > mx ) { hoverIcon = true; ic.classList.add( 'Selected' ); ic.selected = true; if( ic.fileInfo ) ic.fileInfo.selected = true; } else if( !mover || mover != icon ) { ic.classList.remove( 'Selected' ); ic.selected = false; if( ic.fileInfo ) ic.fileInfo.selected = false; } } } } // Register roll out! for( let a in window.movableWindows ) { let wd = window.movableWindows[a]; if( ( !mover && wd.rollOut ) || ( wd != moveWin && wd.rollOut ) ) wd.rollOut( e ); } // Assign! this.mover = mover ? mover : moveWin; if( this.mover.rollOver ) this.mover.rollOver( this.elements ); } // We have a candidate for dragging / etc else if( this.candidate && this.candidate.condition ) { this.candidate.condition( e ); } }, stopMove: function ( e ) { for ( var a in window.movableWindows ) { let wn = window.movableWindows[a]; if ( wn.rollOut ) wn.rollOut ( e ); } }, clear: function() { this.elements = []; this.dom.innerHTML = ''; }, drop: function ( e ) { if ( !e ) e = window.event; let tar = e.targetReplacement ? e.targetReplacement : ( e.target ? e.target : e.srcElement ); if ( this.elements.length ) { let dropper = false; // Check drop on tray icon let titems = ge( 'Tray' ).childNodes; for( let a = 0; a < titems.length; a++ ) { let tr = titems[a]; let l = GetElementLeft( tr ); // left let t = GetElementTop( tr ); // bottom let r = l + tr.offsetWidth; // right let b = t + tr.offsetHeight; // bottom if( windowMouseX >= l && windowMouseX < r && windowMouseY >= t && windowMouseY < b ) { dropper = tr; let objs = []; for( let k = 0; k < this.elements.length; k++ ) { let e = this.elements[k]; if ( e.fileInfo.getDropInfo ) { let info = e.fileInfo.getDropInfo(); objs.push( info ); } else { objs.push( { Path: e.fileInfo.Path, Type: e.fileInfo.Type, Filename: e.fileInfo.Filename ? e.fileInfo.Filename : e.fileInfo.Title, Filesize: e.fileInfo.fileSize, Icon: e.fileInfo.Icon } ); } } if( dropper.ondrop ) { dropper.ondrop( objs ); } break; } } // Check screens and view windows let screens = []; let screenl = ge( 'Screens' ); for( let a = 0; a < screenl.childNodes.length; a++ ) { if( screenl.childNodes[a].tagName == 'DIV' && screenl.childNodes[a].classList && screenl.childNodes[a].classList.contains( 'Screen' ) ) { screens.push( screenl.childNodes[a].screen._screen ); } } let ars = []; for( let a in movableWindows ) { // Don't check minimized windows if( movableWindows[a].parentNode.getAttribute( 'minimized' ) ) continue; // Don't check windows on other workspaces if( globalConfig.workspaceCurrent != movableWindows[a].workspace ) continue; ars.push( movableWindows[a] ); } ars = ars.concat( screens ); let dropped = 0; let dropWin = 0; let skipDropCheck = false; // Check drop on view if( !dropper ) { let z = 0; for( let a in ars ) { let wn = ars[a]; let wnZ = parseInt ( wn.style.zIndex ); if( isNaN( wnZ ) ) wnZ = 0; if ( wn.offsetTop < windowMouseY && wn.offsetLeft < windowMouseX && wn.offsetTop + wn.offsetHeight > windowMouseY && wn.offsetLeft + wn.offsetWidth > windowMouseX && wnZ >= z ) { dropWin = wn; z = wnZ; } } if( dropWin ) { if( dropWin.content && dropWin.content.windowObject && dropWin.content.windowObject.refreshing ) return; // Did we drop on a file browser? if( dropWin.content && dropWin.content.fileBrowser ) { let fb = dropWin.content.fileBrowser.dom; if( windowMouseX < dropWin.offsetLeft + fb.offsetWidth && windowMouseY < dropWin.offsetTop + fb.offsetHeight ) { dropper = dropWin.content.fileBrowser; skipDropCheck = true; } } if( !skipDropCheck && !dropped && ( dropWin.icons || dropWin.content ) ) { dropper = dropWin; } } } let w = null; if( !skipDropCheck ) { // Find what we dropped on for( let c in ars ) { let isListView = false; let isScreen = false; w = ars[c].icons ? ars[c] : ars[c].content; if( !w || !w.icons ) continue; // No icons? Skip! // If we have a dropped on view, skip icons on other views if( dropper && ( w != dropper.content && w != dropper ) ) continue; let sctop = 0; // Listview counts from a different element let iconArea = w; if( w.directoryview && w.directoryview.listMode == 'listview' ) { iconArea = w.querySelector( '.ScrollArea' ); sctop = [ iconArea ]; isListView = true; } else { if( !w.classList.contains( 'ScreenContent' ) ) { sctop = w.getElementsByClassName( 'Scroller' ); } // We're checking screen icons else { isScreen = true; } } let scleft = 0; if( sctop && sctop.length ) { scleft = sctop[0].scrollLeft; sctop = sctop[0].scrollTop; } else sctop = 0; let my = windowMouseY - GetElementTop( iconArea ) + sctop; let mx = windowMouseX - GetElementLeft( iconArea ) + scleft; // Add file browser offset if( w.fileBrowser && !isListView ) { if( !w.fileBrowser.dom.classList.contains( 'Hidden' ) ) { let fb = w.fileBrowser.dom; mx -= fb.offsetWidth; } } // Drop on icon for( let a = 0; a < w.icons.length; a++ ) { let ic = w.icons[a].domNode; if( ic ) { // Exclude elements dragged let found = false; for( var b = 0; b < this.dom.childNodes.length; b++ ) { if( ic == this.dom.childNodes[b] ) found = true; } if( found ) continue; // Done exclude let icon = w.icons[a]; // Hit icon! if( ic.offsetTop < my && ic.offsetLeft < mx && ic.offsetTop + ic.offsetHeight > my && ic.offsetLeft + ic.offsetWidth > mx ) { dropper = icon; break; } } } } // Check drop on desklet if( !dropper || ( dropper.classList && dropper.classList.contains( 'ScreenContent' ) ) ) { let z = 0; let dropWin = 0; for( let a = 0; a < __desklets.length; a++ ) { let wn = __desklets[a].dom; let wnZ = parseInt ( wn.style.zIndex ); if( isNaN( wnZ ) ) wnZ = 0; if ( wn.offsetTop < windowMouseY && wn.offsetLeft < windowMouseX && wn.offsetTop + wn.offsetHeight > windowMouseY && wn.offsetLeft + wn.offsetWidth > windowMouseX && wnZ >= z ) { dropWin = wn; z = wnZ } else { } } if ( dropWin && dropWin.drop ) { dropper = dropWin; } } } let objs = []; if( dropper ) { // Double check that we didn't get to the filebrowser if( dropper.className && dropper.classList.contains( 'View' ) ) { if( e.targetReplacement && e.targetReplacement.classList.contains( 'Bookmarks' ) ) { dropper = dropper.content.fileBrowser; } } // Assume the drop was handled correctly let dropResult = true; // Check if dropper object has a drop method, and execute it // with the supplied elements if( dropper.drop ) { dropResult = dropped = dropper.drop( this.elements, e, dropWin.content ); } else if( dropper.domNode && dropper.domNode.drop ) { dropResult = dropper.domNode.drop( this.elements, e ); } else if( dropper.domNode && dropper.domNode.file && dropper.domNode.file.drop ) { dropResult = dropper.domNode.file.drop( this.elements, e ); } else { for( let k = 0; k < this.elements.length; k++ ) { let e = this.elements[k]; if( e.fileInfo ) { if( e.fileInfo.getDropInfo ) { let info = e.fileInfo.getDropInfo(); objs.push( info ); } else { objs.push( { Path: e.fileInfo.Path, Type: e.fileInfo.Type, Filename: e.fileInfo.Filename ? e.fileInfo.Filename : e.fileInfo.Title, Filesize: e.fileInfo.fileSize, Icon: e.fileInfo.Icon }); } } } if( dropper.windowObject ) { dropper.windowObject.sendMessage( { command: 'drop', data: objs } ); } } } // We didn't drop anything, or there was an error.. if( dropped <= 0 ) { if( window.currentMovable ) { if( window.currentMovable.content && window.currentMovable.content.refresh ) window.currentMovable.content.refresh(); } // We dropped on a screen if( objs && dropper && dropper.classList && dropper.classList.contains( 'ScreenContent' ) ) { // We dropped on the Workspace screen if( dropper == Workspace.screen.contentDiv ) { // Check if we can place desktop shortcuts let files = []; for( let a = 0; a < objs.length; a++ ) { if( objs[ a ].Type == 'Executable' ) { files.push( ':' + objs[ a ].Filename ); } else if( objs[ a ].Type == 'Door' || objs[ a ].Type == 'Dormant' ) continue; else { files.push( objs[ a ].Path ); } } // Create desktop shortcuts let m = new Module( 'system' ); m.onExecuted = function( e, d ) { if( e == 'ok' ) { Workspace.refreshDesktop( false, true ); } } m.execute( 'createdesktopshortcuts', { files: files } ); } } } // Redraw icons Workspace.redrawIcons(); if( window.currentMovable && currentMovable.content && currentMovable.content.refresh ) currentMovable.content.refresh(); // Place back again if( !dropped || !dropper ) { for( let a = 0; a < this.elements.length; a++ ) { if( this.elements[a].ondrop ) this.elements[a].ondrop( dropper ); if( this.elements[a].oldParent ) { let ea = this.elements[a]; ea.oldParent.appendChild( ea ); ea.style.top = ea.oldStyle.top; ea.style.left = ea.oldStyle.left; ea.style.position = ea.oldStyle.position; ea.oldStyle = null; } else this.dom.removeChild( this.elements[a] ); } } // Remove else { for( let a = 0; a < this.elements.length; a++ ) { if( this.elements[a].ondrop ) this.elements[a].ondrop( dropper ); this.dom.removeChild( this.elements[a] ); } } this.elements = []; this.dom.innerHTML = ''; if( w ) { if( w.refreshIcons ) { w.refreshIcons(); } } Workspace.refreshDesktop(); } this.mover = false; }, clone: function ( ele ) { this.testPointer (); }, pickup: function ( ele, e ) { // Do not allow pickup for mobile if( isTouchDevice() ) return; if( !e ) e = window.event; let ctrl = e && ( e.ctrlKey || e.shiftKey || e.command ); let target = false; if( e ) target = e.target || e.srcElement; this.testPointer (); // Check multiple (pickup multiple) let multiple = false; if ( ele.window ) { if( ele.window.windowObject && ele.window.windowObject.refreshing ) return; if( !ele.window.parentNode.classList.contains( 'Active' )) _ActivateWindowOnly( ele.window.parentNode ); if( ele.window && ele.window.directoryview && ele.window.directoryview.filedialog ) { return false; } for( let a = 0; a < ele.window.icons.length; a++ ) { let ic = ele.window.icons[a]; if( !ic.domNode ) continue; if( ic.domNode.className.indexOf ( 'Selected' ) > 0 ) { let el = ic.domNode; multiple = true; el.oldStyle = {}; el.oldStyle.top = el.style.top; el.oldStyle.left = el.style.left; el.oldStyle.position = el.style.position; el.style.top = el.offsetTop + 'px'; el.style.left = el.offsetLeft + 'px'; el.style.position = 'absolute'; el.oldParent = el.parentNode; if( typeof ele.window.icons[a+1] != 'undefined' ) el.sibling = ele.window.icons[a+1].domNode; if( el.parentNode ) { el.parentNode.removeChild( el ); } this.dom.appendChild( el ); this.elements.push( el ); } } // Align with top left corner let maxx = 99999; let maxy = 99999; let elements = this.elements; for( let a = 0; a < elements.length; a++ ) { if( parseInt( elements[ a ].style.left ) < maxx ) maxx = parseInt( elements[ a ].style.left ); if( parseInt( elements[ a ].style.top ) < maxy ) maxy = parseInt( elements[ a ].style.top ); } for( let a = 0; a < elements.length; a++ ) { elements[ a ].style.left = parseInt( elements[ a ].style.left ) - maxx + 'px'; elements[ a ].style.top = parseInt( elements[ a ].style.top ) - maxy + 'px'; } } // Pickup single if ( !multiple ) { ele.oldStyle = {}; ele.oldStyle.top = ele.style.top; ele.oldStyle.left = ele.style.left; ele.oldStyle.position = ele.style.position; ele.style.top = 'auto'; ele.style.left = 'auto'; ele.style.position = 'relative'; if( ele.parentNode ) { if( ele.oldParent ) ele.oldParent.ele = ele.parentNode; ele.parentNode.removeChild( ele ); } this.dom.appendChild( ele ); this.elements.push( ele ); } }, poll: function ( e ) { if( !this.elements || !this.elements.length ) { if( this.dom ) this.dom.parentNode.removeChild ( this.dom ); this.dom = false; } else if( this.dom ) { if( this.dom.firstChild ) { this.dom.style.top = windowMouseY - ( this.dom.firstChild.offsetHeight >> 1 ) + 'px'; this.dom.style.left = windowMouseX - ( this.dom.firstChild.offsetWidth >> 1 ) + 'px'; } window.mouseDown = 5; ClearSelectRegion(); } } }; // Get information about theme var themeInfo = { loaded: false, dynamicClasses: { WindowSnapping: function( e ) { if( !Workspace.screen || !Workspace.screen.contentDiv.directoryview ) return; let hh = Workspace && Workspace.screen ? ( Workspace.screen.getMaxViewHeight() + 'px' ) : '0'; let winw = Workspace.screen.contentDiv.directoryview; if( !winw.scroller ) return; let leftx = 0; let topy = 0; let dockWidth = 0; if( Workspace.mainDock ) { switch( Workspace.mainDock.dom.getAttribute( 'position' ) ) { case 'left_center': case 'left_top': case 'left_bottom': leftx = Workspace.mainDock.dom.offsetWidth + 'px'; dockWidth = Workspace.mainDock.dom.offsetWidth; break; case 'right_center': case 'right_top': case 'right_bottom': dockWidth = Workspace.mainDock.dom.offsetWidth; break; case 'top_left': case 'top_center': case 'top_right': topy = Workspace.mainDock.dom.offsetHeight + 'px'; break; } } winw = window.innerWidth - dockWidth; let ww = Math.floor( winw >> 1 ) + 'px'; return ` html .View.SnapLeft { left: ${leftx} !important; top: ${topy} !important; height: ${hh} !important; width: ${ww} !important; } html .View.SnapRight { left: calc(${leftx} + ${ww}) !important; top: ${topy} !important; height: ${hh} !important; width: ${ww} !important; } `; } } }; // Secure drop widget for apps // TODO: Complete this one once the browser is ready for it. Not used now. // This one is connected to the "securefiledrop" flag on windows function addSecureDropWidget( windowobject, objects ) { let w = new Widget( { top: windowMouseY - 180, left: windowMouseX - 150, width: 300, height: 230, raised: true, rounded: true } ); windowobject.toFront(); let f = new File( 'System:templates/securefiledrop.html' ); f.onLoad = function( data ) { w.setContent( data, function() { for( let a = 0; a < objects.length; a++ ) { let url = getImageUrl( objects[ a ].Path ) let im = new Image(); let o = objects[ a ]; fetch( url ) .then( res => res.blob() ) .then( blob => { let fn = GetFilename( o.Path ); let fil = new File( [ blob ], fn, blob ); let ic = new FileIcon( o, { type: 'A', nativeDraggable: true } ); url = url.split( '/read' ).join( '/read/' + fn ); ic.file.id = 'directoryfile_draggable_' + a; ic.file.setAttribute( 'data-downloadurl', url ); ic.file.href = url; ic.file.style.position = 'relative'; ic.file.style.float = 'left'; ic.file.style.display = 'block'; ic.file.style.marginLeft = '10px'; ic.file.setAttribute( 'download', url ); ic.file.addEventListener( 'dragstart', e => { e.dataTransfer.dropEffect = 'copy'; e.dataTransfer.effectAllowed = 'copy'; let ext = 'bin'; if( fn.indexOf( '.' ) >= 0 ) ext = fn.split( '.' )[1].toUpperCase(); let ctype = 'application/octet-stream'; switch( ctype ) { case 'jpg': case 'jpeg': ctype = 'image/jpeg'; break; case 'png': case 'bmp': case 'gif': ctype = 'image/' + ext; break; default: break; } // TODO: Make items.add work //e.dataTransfer.items.add( [ fil ], ctype, { type: 'custom' } ); e.dataTransfer.setData( 'DownloadURL', [ ctype + ':' + fn + ':' + url ] ); } ); ic.file.addEventListener( 'dragend', function( e ) { w.close(); //console.log( e ); }, false ); w.dom.querySelector( '.Iconlist' ).appendChild( ic.file ); } ); } } ); } f.load(); window.mouseDown = null; } //check if we run inside an app and do some magic function checkForFriendApp() { // just disabled to do not loose compatybility /* //if we dont have a sessionid we will need to wait a bit here... if( !Workspace.sessionId ) { console.log('waiting for valid session...' + Workspace.sessionId ); setTimeout(checkForFriendApp, 500); return; } if( typeof friendApp != 'undefined' && typeof friendApp.exit == 'function') { // if this is mobile app we must register it // if its already registered FC will not do it again let version = null; let platform = null; let appToken = null; let deviceID = null; //var appToken = friendApp.appToken ? friendApp.appToken : false; if( typeof friendApp.get_version == 'function' ) { version = friendApp.get_version(); } if( typeof friendApp.get_platform == 'function' ) { platform = friendApp.get_platform(); } if( typeof friendApp.get_app_token == 'function' ) { appToken = friendApp.get_app_token(); } if( typeof friendApp.get_deviceid == 'function' ) { deviceID = friendApp.get_deviceid(); } console.log('call ' + Workspace.sessionId ); let l = new Library( 'system.library' ); l.onExecuted = function( e, d ) { if( e != 'ok' ) { } } if( appToken != null ) // old applications which do not have appToken will skip this part { l.execute( 'mobile/createuma', { sessionid: Workspace.sessionId, apptoken: appToken, deviceid: deviceID, appversion: version, platform: platform } ); } } */ } // Refresh programmatic classes function RefreshDynamicClasses( e ) { if( !themeInfo.dynamicClasses ) return; let str = ''; for( var a in themeInfo.dynamicClasses ) { str += themeInfo.dynamicClasses[ a ]( e ); } themeInfo.dynCssEle.innerHTML = str; } function InitDynamicClassSystem() { let dynCss = document.createElement( 'style' ); document.body.appendChild( dynCss ); themeInfo.dynCssEle = dynCss; let ls = [ 'resize', 'mousedown', 'mouseup', 'touchstart', 'touchend' ]; for( var a = 0; a < ls.length; a++ ) window.addEventListener( ls[a], RefreshDynamicClasses ); RefreshDynamicClasses( {} ); } function GetThemeInfo( property ) { if( !Workspace.loginUsername ) return false; if( !themeInfo.loaded ) { themeInfo.loaded = true; // Flush old rules let sheet = false; for( let a = 0; a < document.styleSheets.length; a++ ) { if( document.styleSheets[a].href && document.styleSheets[a].href.indexOf( 'theme' ) > 0 ) { sheet = document.styleSheets[a]; break; } } if( sheet ) { for( let a = 0; a < sheet.cssRules.length; a++ ) { let rule = sheet.cssRules[a]; let key = false; let qualifier = false; // What qualifies this as the final rule // TODO: Add all important keys here! switch( rule.selectorText ) { case '.ScreenContentMargins': key = 'ScreenContentMargins'; break; case '.Screen > .TitleBar > .Left .Extra .Offline': case 'html .Screen > .TitleBar > .Left .Extra .Offline': key = 'OfflineIcon'; qualifier = 'backgroundImage'; break; case '.Screen > .TitleBar': key = 'ScreenTitle'; break; case '.View > .LeftBar': key = 'ViewLeftBar'; break; case '.View > .RightBar': key = 'ViewRightBar'; break; case '.View > .Title': key = 'ViewTitle'; break; case '.View > .BottomBar': key = 'ViewBottom'; break; default: //console.log( 'Unhandled css selector: ' + rule.selectorText ); break; } // Test if the theme info property has already qualified, and skip it if so let qualifierTest = themeInfo[ key ] && ( qualifier && typeof( themeInfo[ key ][ qualifier ] ) != 'undefined' && themeInfo[ key ][ qualifier ].length ); if( qualifierTest ) { continue; } if( key ) themeInfo[ key ] = rule.style; } } } if( themeInfo[ property ] ) return themeInfo[ property ]; return false; } // Cover windows with overlay function CoverWindows() { for ( var a in movableWindows ) { if( movableWindows[a].moveoverlay ) { movableWindows[a].moveoverlay.style.height = '100%'; movableWindows[a].moveoverlay.style.pointerEvents = ''; } } } // Expose windows / remove overlay function ExposeWindows() { for ( var a in movableWindows ) { if( movableWindows[a].content.groupMember ) continue; if( movableWindows[a].moveoverlay ) { movableWindows[a].moveoverlay.style.height = '0%'; movableWindows[a].moveoverlay.style.pointerEvents = 'none'; } movableWindows[a].memorize(); } } // Cover screens with overlay function CoverScreens( sticky ) { // Disable all screen overlays let screenc = ge ( 'Screens' ); let screens = screenc.getElementsByTagName( 'div' ); for( let a = 0; a < screens.length; a++ ) { if( !screens[a].className ) continue; if( screens[a].parentNode != screenc ) continue; screens[a]._screenoverlay.style.display = ''; screens[a]._screenoverlay.style.pointerEvents = ''; } } // Cover screens other than current one function CoverOtherScreens() { // Disable all screen overlays let screenc = ge ( 'Screens' ); let screens = screenc.getElementsByTagName ( 'div' ); for( let a = 0; a < screens.length; a++ ) { if( !screens[a].className ) continue; if( screens[a].parentNode != screenc ) continue; if( currentScreen && screens[a] == currentScreen ) continue; screens[a]._screenoverlay.style.display = ''; screens[a]._screenoverlay.style.pointerEvents = 'none'; } } // Expose screens / remove overlay function ExposeScreens() { // Disable all screen overlays let screenc = ge ( 'Screens' ); let screens = screenc.getElementsByTagName ( 'div' ); for( let a = 0; a < screens.length; a++ ) { if( !screens[a].className ) continue; if( screens[a].parentNode != screenc ) continue; screens[a]._screenoverlay.style.display = 'none'; screens[a].moveoverlay.style.pointerEvents = 'none'; screens[a]._screenoverlay.style.display = 'none'; screens[a].moveoverlay.style.pointerEvents = 'none'; } } // Find a movable window by title string function FindWindowById ( id ) { for( let i in movableWindows ) { if ( i == id ) { if ( movableWindows[i].windowObject ) return movableWindows[i].windowObject; return movableWindows[i]; } } return false; } function GuiCreate ( obj ) { let str = ''; for( let a = 0; a < obj.length; a++ ) { switch( typeof ( obj[a] ) ) { case 'function': if ( typeof ( obj[a+1] ) != 'undefined' ) { str += obj[a] ( obj[a+1] ); a++; } break; case 'string': str += obj[a]; break; case 'array': str += GuiCreate ( obj[a], _level+1 ); break; } } return str; } function GuiColumns( data ) { let widths = data[0]; let content = data[1]; let str = ''; for( let a = 0; a < widths.length; a++ ) { if ( widths[a].indexOf ( '%' ) < 0 && widths[a].indexOf ( 'px' ) < 0 ) widths[a] += 'px'; str += ''; } str += '
' + GuiCreate ( [ content[a] ] ) + '
'; return str; } function GuiContainer( obj ) { return '
' + GuiCreate ( obj ) + '
'; } // Init popup box on an element on roll over ----------------------------------- var _epObject = { target : false, l : -1000, t : -1000, datas : new Array () }; function InitElementPopup( pdiv, actionurl, forceupdate, immediateDisplay ) { if ( !pdiv ) return; if ( !immediateDisplay ) immediateDisplay = false; pdiv.triggerElementPopup = true; if ( !pdiv.loadData ) { pdiv.actionData = actionurl; pdiv.checkElementPopup = function () { this.d = ge( 'ElementPopup' ); if ( !this.d ) { this.d = document.createElement ( 'div' ); this.d.className = 'Popup'; this.d.id = 'ElementPopup'; this.d.style.top = '-10000px'; this.d.style.left = '-10000px'; this.d.style.visibility = 'hidden'; document.body.appendChild( this.d ); _epObject.target = this; } return this.d; } pdiv.loadData = function ( showAfterLoad ) { if ( !this.actionData ) return; this.checkElementPopup(); if ( !forceupdate && _epObject.datas[this.actionData] ) { this.activate ( this.actionData ); this.show (); } else { let k = new cAjax (); k.open ( 'get', this.actionData, true ); k.pdiv = pdiv; this.show (); k.onload = function ( e ) { let r = this.responseText (); if ( r.indexOf ( 'login.css' ) < 0 && r.length > 0 ) { this.pdiv.setData ( r, this.pdiv.actionData ); if ( showAfterLoad ) this.pdiv.show (); this.pdiv.activate ( this.pdiv.actionData ); } } k.send (); } } pdiv.setData = function ( data, action, e ) { _epObject.datas[action] = data; } pdiv.activate = function ( action, e ) { if ( !e ) e = window.event; if ( !_epObject.datas ) return; if ( !_epObject.datas[action] ) return; this.d.innerHTML = _epObject.datas[action]; this.d.height = this.d.offsetHeight; RepositionPopup ( e ); } pdiv.onmouseover = function () { this.loadData (); } pdiv.show = function ( e ) { let d = this.checkElementPopup(); d.style.opacity = 1; d.style.filter = 'alpha(opacity=100)'; d.style.visibility = 'visible'; if ( !d.style.top || !d.style.left ) { RepositionPopup ( e ); } } } pdiv.loadData ( immediateDisplay ? true : false ); DelEvent ( ElementPopupMover ); AddEvent ( 'onmousemove', ElementPopupMover ); } var __windowHeightMargin = 25; function RepositionPopup( e, test ) { if ( !e ) e = window.event; if ( !e ) return; let l = 0; var t = 0; let target = document.body; let trg = _epObject.target; // wanted target let mx = windowMouseX; let my = windowMouseY; l = _epObject.l; t = _epObject.t; if ( e ) { let ll = mx; if ( !isNaN ( ll ) ) { l = ll; t = my; _epObject.l = l; _epObject.t = t; // FIXME: Study when we need this one (scroll offset! //t -= document.body.scrollTop; //l -= document.body.scrollLeft; target = e.target ? e.target : e.srcElement; // mouse target } } el = ge( 'ElementPopup' ); if( !el ) return; // Get popup height let height = false; if ( !height ) height = el.height; if ( !height ) return; let wh = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; let ww = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; wh -= __windowHeightMargin; let mdl = GetTitleBarG (); let minTop = mdl ? mdl.offsetHeight : 0; let w = el.offsetWidth + 20; let h = el.offsetHeight + 20; l -= el.offsetWidth >> 1; t -= height + 20; // Constrain to page if ( t + h > wh ) t -= ( t + h ) - wh; if ( l + w > ww ) l -= ( l + w ) - ww; if ( l < 0 ) l = 0; if ( t < minTop ) t = my + 20; let st = document.body.scrollTop; if ( t < minTop && l == 0 ) { t = -1000; l = -1000; } el.style.top = t + st + 'px'; el.style.left = l + 'px'; } // Move popup function ElementPopupMover ( e, init ) { if ( !e ) e = window.event; let el = false; var target; var trg; if ( e ) { target = e.target ? e.target : e.srcElement; // mouse target trg = _epObject.target; // wanted target } if ( ( el = ge( 'ElementPopup' ) ) ) { let isover = false; var test = target; if ( !init ) { while ( test != trg.parentNode && test != document.body ) { if ( test == trg ) { isover = true; break; } if( !test ) return false; test = test.parentNode; } } if ( init || isover == true ) { if ( el.tm ) { clearTimeout ( el.tm ); el.tm = false; } RepositionPopup( e ); } // We're moving over another popup! else if ( target.triggerElementPopup ) { RepositionPopup( e ); } else { RemoveElementPopup( 1 ); // Totally remove } } } function RemoveElementPopup( close ) { let e = ge('ElementPopup' ); if ( !e ) return; if ( e.tm ) clearTimeout ( e.tm ); e.tm = false; if( close ) { e.parentNode.removeChild ( e ); } else { e.style.opacity = 0; e.style.filter = 'alpha(opacity=0)'; } } /* Make select box table ---------------------------------------------------- */ function NewSelectBox ( divobj, height, multiple ) { if ( !divobj ) return false; let cont = document.createElement ( 'div' ); cont.className = 'SelectBox'; let table = document.createElement ( 'table' ); table.className = 'SelectBox ' + ( multiple ? 'Checkboxes' : 'Radioboxes' ); let opts = divobj.getElementsByTagName ( 'div' ); let sw = 1; for( let a = 0; a < opts.length; a++ ) { let tr = document.createElement ( 'tr' ); tr.className = 'sw' + sw + ( opts[a].className ? ( ' ' + opts[a].className ) : '' ); if ( opts[a].title ) tr.title = opts[a].title; let spl = opts[a].innerHTML.split ( "\t" ); let inpid = divobj.id + '_input_'+(a+1); for ( var b = 0; b < spl.length; b++ ) { let td = document.createElement ( 'td' ); td.innerHTML = ''; tr.appendChild ( td ); } let td = document.createElement ( 'td' ); val = opts[a].getAttribute ( 'value' ); if ( multiple ) { td.innerHTML = ''; } else { td.innerHTML = ''; } tr.appendChild ( td ); table.appendChild ( tr ); tr.onselectstart = function () { return false; } sw = sw == 1 ? 2 : 1; } cont.appendChild ( table ); // Replace earlier container cont.id = divobj.id; divobj.parentNode.replaceChild ( cont, divobj ); // Adjust height let rheight = 0; rheight = cont.getElementsByTagName ( 'td' )[0].offsetHeight; cont.style.minHeight = rheight * height + 'px'; } function _NewSelectBoxCheck ( pid, ele ) { let pel if ( typeof ( pid ) == 'string' ) pel = ge ( pid ); else pel = pid; if ( !pel ) return false; let els = pel.getElementsByTagName ( 'tr' ); for( let a = 0; a < els.length; a++ ) { let inp = els[a].getElementsByTagName ( 'input' )[0]; if ( inp.checked ) els[a].className = els[a].className.split ( ' checked' ).join ( '' ) + ' checked'; else els[a].className = els[a].className.split ( ' checked' ).join ( '' ); } } // Rules for forcing screen dimensions function forceScreenMaxHeight() { if( isMobile ) { // Nothing yet } } // Gets values from a SelectBox - multiple select returns array, otherwise string function GetSelectBoxValue( pel ) { if ( !pel ) return false; let inputs = pel.getElementsByTagName ( 'input' ); let table = pel.getElementsByTagName ( 'table' )[0]; if ( table.className.indexOf ( 'Checkboxes' ) > 0 ) { let res = new Array (); for( let a = 0; a < inputs.length; a++ ) { if ( inputs[a].checked ) { res.push ( inputs[a].getAttribute ( 'value' ) ); } } return res; } else if ( table.className.indexOf ( 'Radioboxes' ) > 0 ) { for( let a = 0; a < inputs.length; a++ ) { if( inputs[a].checked ) { return inputs[a].getAttribute ( 'value' ); } } } return false; } function _NewSelectBoxRadio ( pid, ele ) { let pel if ( typeof ( pid ) == 'string' ) pel = ge ( pid ); else pel = pid; if ( !pel ) return false; let els = pel.getElementsByTagName ( 'tr' ); for( let a = 0; a < els.length; a++ ) { let inp = els[a].getElementsByTagName ( 'input' )[0]; if ( inp.checked ) { els[a].className = els[a].className.split ( ' checked' ).join ( '' ) + ' checked'; } else els[a].className = els[a].className.split ( ' checked' ).join ( '' ); } } /* For movable windows ------------------------------------------------------ */ var dragDistance = 0; var dragDistanceX = 0, dragDistanceY = 0; // Moves windows on mouse move movableListener = function( e, data ) { if( !e ) e = window.event; let ww = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; let wh = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; let x, y; if( ( typeof( e.touches ) != 'undefined' && typeof( e.touches[0] ) != 'undefined' ) && ( isTouchDevice() ) ) { x = e.touches[0].pageX; y = e.touches[0].pageY; } else { x = e.clientX ? e.clientX : e.pageX; y = e.clientY ? e.clientY : e.pageY; } let sh = e.shiftKey || e.ctrlKey; // Injection if( data ) { if( data.mouseX ) x = data.mouseX; if( data.mouseY ) y = data.mouseY; } windowMouseX = x; windowMouseY = y; // Keep alive! if( window.Workspace ) { Workspace.updateViewState( 'active' ); } mousePointer.poll(); mousePointer.move( e ); if( window.mouseDown && window.mouseMoveFunc ) { window.mouseMoveFunc( e ); } else if( window.currentMovable ) { // Some defaults let minW = 160; let minH = 100; let maxW = 1000000; let maxH = 1000000; let st = document.body.scrollTop; if ( window.currentMovable.content && window.currentMovable.windowObject.flags ) { if ( window.currentMovable.windowObject.flags['min-width'] >= 100 ) minW = window.currentMovable.windowObject.flags['min-width']; if ( window.currentMovable.windowObject.flags['max-width'] >= 100 ) maxW = window.currentMovable.windowObject.flags['max-width']; if ( window.currentMovable.windowObject.flags['min-height'] >= 100 ) minH = window.currentMovable.windowObject.flags['min-height']; if ( window.currentMovable.windowObject.flags['max-height'] >= 100 ) maxH = window.currentMovable.windowObject.flags['max-height']; } // Clicking on the window title let mx = x - ( window.currentMovable.offx ? window.currentMovable.offx : 0 ); let my = y - ( window.currentMovable.offy ? window.currentMovable.offy : 0 ); let lockY = false; let lockX = false; // 8 px grid + snapping if( e.shiftKey ) { // Mouse coords changed if( window.mouseDown ) { if( mousePointer.prevMouseX != x || mousePointer.prevMouseY != y ) { // Window snapping! Attach to window if( currentMovable ) { // Check current movable intersections let directionY = windowMouseY - mousePointer.prevMouseY; let directionX = windowMouseX - mousePointer.prevMouseX; if( directionX != 0 || directionY != 0 ) { let direction = directionX < 0 ? 'left' : 'right'; if( Math.abs( directionY ) > Math.abs( directionX ) ) direction = directionY < 0 ? 'up' : 'down'; if( currentMovable.shiftX !== false ) { clx = currentMovable.shiftX; cly = currentMovable.shiftY; dragDistance = Math.sqrt( Math.pow( clx - x, 2 ) + Math.pow( cly - y, 2 ) ); dragDistanceX = clx - x; dragDistanceY = cly - y; } let curWinRight = currentMovable.offsetLeft + currentMovable.offsetWidth; let curWinLeft = currentMovable.offsetLeft; let curWinTop = currentMovable.offsetTop; let curWinBottom = currentMovable.offsetTop + currentMovable.offsetHeight; let snapInfo = { view: false, direction: false, z: 0 }; for( let z in movableWindows ) { let mw = movableWindows[ z ]; if( mw.snapObject ) continue; // Can't snap to snapped windows if( mw == currentMovable ) continue; if( mw.parentNode && mw.parentNode && mw.parentNode.getAttribute( 'minimized' ) == 'minimized' ) continue; let mWR = mw.offsetLeft + mw.offsetWidth; let mWL = mw.offsetLeft; let mWT = mw.offsetTop; let mWB = mw.offsetTop + mw.offsetHeight; let doSnap = false; // Snap to the right if( direction == 'right' && ( !currentMovable.snap || currentMovable.snap == 'right' ) ) { if( curWinRight > mWL && curWinRight <= mWL + 20 && ( ( curWinTop >= mWT && curWinTop <= mWB ) || ( curWinBottom >= mWT && curWinBottom <= mWB ) ) ) { if( parseInt( mw.style.zIndex ) > snapInfo.z ) { snapInfo.view = mw; snapInfo.direction = 'right'; snapInfo.extra = mWL; snapInfo.z = parseInt( mw.style.zIndex ); } } } // Snap to the left else if( direction == 'left' && ( !currentMovable.snap || currentMovable.snap == 'left' ) ) { if( curWinLeft < mw.offsetWidth + mWL && curWinLeft >= mw.offsetWidth + mWL - 20 && ( ( curWinTop >= mWT && curWinTop <= mWB ) || ( curWinBottom >= mWT && curWinBottom <= mWB ) ) ) { if( parseInt( mw.style.zIndex ) > snapInfo.z ) { snapInfo.view = mw; snapInfo.direction = 'left'; snapInfo.extra = mWR; snapInfo.z = parseInt( mw.style.zIndex ); } } } else if( direction == 'up' && ( !currentMovable.snap || currentMovable.snap == 'up' ) ) { if( curWinTop >= mWB - 20 && curWinTop < mWB && ( ( curWinLeft >= mWL && curWinLeft <= mWR ) || ( curWinRight >= mWL && curWinRight <= mWR ) ) ) { if( parseInt( mw.style.zIndex ) > snapInfo.z ) { snapInfo.view = mw; snapInfo.direction = 'up'; snapInfo.extra = mWB; snapInfo.z = parseInt( mw.style.zIndex ); } } } else if( direction == 'down' && ( !currentMovable.snap || currentMovable.snap == 'down' ) ) { if( curWinBottom >= mWT && curWinBottom <= mWT + 20 && ( ( curWinLeft >= mWL && curWinLeft <= mWR ) || ( curWinRight >= mWL && curWinRight <= mWR ) ) ) { if( parseInt( mw.style.zIndex ) > snapInfo.z ) { snapInfo.view = mw; snapInfo.direction = 'down'; snapInfo.extra = mWT; snapInfo.z = parseInt( mw.style.zIndex ); } } } } if( snapInfo.view ) { if( currentMovable.shiftX === false ) { currentMovable.shiftX = x; currentMovable.shiftY = y; } let mw = snapInfo.view; if( snapInfo.direction == 'right' ) { if( snapInfo.extra - currentMovable.offsetWidth < 0 ) { currentMovable.style.width = mWL + 'px'; } currentMovable.style.left = snapInfo.extra - currentMovable.offsetWidth + 'px'; currentMovable.snap = 'right'; currentMovable.snapObject = snapInfo.view; doSnap = true; lockX = true; } else if( snapInfo.direction == 'left' ) { if( snapInfo.extra + currentMovable.offsetWidth > Workspace.screen.getMaxViewWidth() ) { currentMovable.style.width = Workspace.screen.getMaxViewWidth() - snapInfo.extra + 'px'; } currentMovable.style.left = snapInfo.extra + 'px'; currentMovable.snap = 'left'; currentMovable.snapObject = snapInfo.view; doSnap = true; lockX = true; } else if( snapInfo.direction == 'up' ) { if( snapInfo.extra + currentMovable.offsetHeight > Workspace.screen.getMaxViewHeight() ) { currentMovable.style.height = Workspace.screen.getMaxViewHeight() + 'px'; } currentMovable.style.top = snapInfo.extra + 'px'; currentMovable.snap = 'up'; currentMovable.snapObject = snapInfo.view; doSnap = true; lockY = true; } else if( snapInfo.direction == 'down' ) { if( snapInfo.extra - currentMovable.offsetHeight < 0 ) { currentMovable.style.height = snapInfo.extra + 'px'; } currentMovable.style.top = snapInfo.extra - currentMovable.offsetHeight + 'px'; currentMovable.snap = 'down'; currentMovable.snapObject = mw; doSnap = true; lockY = true; } // Do we snap? if( doSnap ) { let cx = mw.offsetLeft - currentMovable.offsetLeft; let cy = mw.offsetTop - currentMovable.offsetTop; currentMovable.snapCoords = { x: cx, y: cy, mx: windowMouseX - cx, my: windowMouseY - cy }; if( !mw.attached ) { mw.attached = [ currentMovable ]; } else { let found = false; for( var a in mw.attached ) { if( mw.attached[ a ] == currentMovable ) { found = true; break; } } if( !found ) mw.attached.push( currentMovable ); } mw.setAttribute( 'attach_' + direction, 'attached' ); // Unsnap currentMovable.unsnap = function() { // Unsnap this.snap = null; this.snapDistanceX = 0; this.snapDistanceY = 0; this.snapCoords = null; this.removeAttribute( 'viewsnap' ); // Clean up snap object attached list and detach if( this.snapObject && this.snapObject.attached ) { let o = []; let left = right = up = down = false for( var a = 0; a < this.snapObject.attached.length; a++ ) { let att = this.snapObject.attached[ a ]; if( att != this ) { o.push( att ); if( att.snap == 'right' ) right = true; else if( att.snap == 'left' ) left = true; else if( att.snap == 'up' ) up = true; else if( att.snap == 'down' ) down = true; } } this.snapObject.attached = o; if( !up ) this.snapObject.removeAttribute( 'attach_up' ); if( !down ) this.snapObject.removeAttribute( 'attach_down' ); if( !left ) this.snapObject.removeAttribute( 'attach_left' ); if( !right ) this.snapObject.removeAttribute( 'attach_right' ); this.snapObject = null; } this.unsnap = null; PollTaskbar(); } PollTaskbar(); } } } } } // We are in snapped mode if( currentMovable.snap ) { currentMovable.setAttribute( 'viewsnap', currentMovable.snap ); if( currentMovable.snapObject ) { let mw = currentMovable.snapObject; currentMovable.snapCoords.x = mw.offsetLeft - currentMovable.offsetLeft; currentMovable.snapCoords.y = mw.offsetTop - currentMovable.offsetTop; let dir = currentMovable.snap; if( dir == 'right' || dir == 'left' ) { lockX = true; if( ( dir == 'left' /*&& dragDistanceX > 150*/ ) || ( dir == 'right' /*&& dragDistanceX < -150*/ ) ) { currentMovable.style.top = currentMovable.snapObject.style.top; currentMovable.style.height = currentMovable.snapObject.style.height; lockY = true; currentMovable.setAttribute( 'hardsnap', 'hardsnap' ); } else { currentMovable.removeAttribute( 'hardsnap' ); } } else { lockY = true; if( ( dir == 'up' /*&& dragDistanceY > 150 */) || ( dir == 'down' /*&& dragDistanceY < -150 */) ) { currentMovable.style.left = currentMovable.snapObject.style.left; currentMovable.style.width = currentMovable.snapObject.style.width; lockX = true; currentMovable.setAttribute( 'hardsnap', 'hardsnap' ); } else { currentMovable.removeAttribute( 'hardsnap' ); } } } } } } else { if( window.currentMovable ) { currentMovable.shiftX = false; currentMovable.shiftY = false; } } mousePointer.prevMouseX = windowMouseX; mousePointer.prevMouseY = windowMouseY; // Tell other processes that we're snapping or not if( lockY || lockX ) { currentMovable.snapping = true; } else { currentMovable.snapping = false; } // Moving a window.. if( !isMobile && window.mouseDown == 1 ) { if( ( !lockX && !lockY ) && currentMovable.snap && currentMovable.unsnap && currentMovable.shiftKey ) currentMovable.unsnap(); let w = window.currentMovable; // Make sure the inner overlay is over screens if( window.currentScreen ) { window.currentScreen.moveoverlay.style.display = ''; window.currentScreen.moveoverlay.style.height = '100%'; } // Move the window! if( w && w.style ) { // Move sticky widgets! if( w.windowObject && w.windowObject.widgets ) { let wds = w.windowObject.widgets; for( let z = 0; z < wds.length; z++ ) { let vx = mx + ( isNaN( wds[z].tx ) ? 0 : wds[z].tx ); let vy = my + ( isNaN( wds[z].ty ) ? 0 : wds[z].ty ); if( vx < 0 ) vx = 0; else if( vx + wds[z].dom.offsetWidth >= wds[z].dom.parentNode.offsetWidth ) vx = wds[z].dom.parentNode.offsetWidth - wds[z].dom.offsetWidth; if( vy < 0 ) vy = 0; else if( vy + wds[z].dom.offsetHeight >= wds[z].dom.parentNode.offsetHeight ) vy = wds[z].dom.parentNode.offsetHeight - wds[z].dom.offsetHeight; wds[z].dom.style.right = 'auto'; wds[z].dom.style.bottom = 'auto'; if( !lockX ) wds[z].dom.style.left = vx + 'px'; if( !lockY ) wds[z].dom.style.top = vy + 'px'; } } if( !w.getAttribute( 'moving' ) ) w.setAttribute( 'moving', 'moving' ); // Move the window ConstrainWindow( w, lockX ? currentMovable.offsetLeft : mx, lockY ? currentMovable.offsetTop : my ); // Do the snap! if( !isMobile && currentMovable.windowObject.flags.resize !== false ) { let tsX = w.offsetLeft; let tsY = w.offsetTop; let screenLeftEdge = screenRightEdge = 0; let dv = Workspace.screen.contentDiv.directoryview; if( dv && dv.scroller ) { screenRightEdge = window.innerWidth; screenLeftEdge = 0; let dock = Workspace.mainDock ? Workspace.mainDock.dom : false; if( dock ) { if( dock.classList.contains( 'Left' ) ) { screenLeftEdge += dock.offsetWidth; } else if( dock.classList.contains( 'Right' ) ) { screenRightEdge -= dock.offsetWidth; } } } // Give some space from the title bar if( windowMouseY > 40 ) { let snapOut = false; let hw = 128; let rhw = screenRightEdge - 128; if( windowMouseX > hw && windowMouseX < rhw ) { snapOut = true; } else { if( tsX <= screenLeftEdge ) { w.classList.remove( 'SnapRight' ); if( !w.classList.contains( 'SnapLeft' ) ) { w.classList.add( 'SnapLeft' ); RefreshDynamicClasses(); if( w.content && w.content.refresh ) w.content.refresh(); } } else if( tsX + w.offsetWidth >= screenRightEdge ) { w.classList.remove( 'SnapLeft' ); if( !w.classList.contains( 'SnapRight' ) ) { w.classList.add( 'SnapRight' ); RefreshDynamicClasses(); if( w.content && w.content.refresh ) w.content.refresh(); } } else { snapOut = true; } } if( snapOut ) { let cn = w.classList.contains( 'SnapLeft' ) || w.classList.contains( 'SnapRight' ); if( cn ) { w.classList.remove( 'SnapLeft' ); w.classList.remove( 'SnapRight' ); RefreshDynamicClasses(); if( w.content && w.content.refresh ) w.content.refresh(); } } } } } // Do resize events CoverWindows(); CoverScreens(); if( w.content && w.content.events ) { if( typeof( w.content.events['move'] ) != 'undefined' ) { for( var a = 0; a < w.content.events[ 'move' ].length; a++ ) { w.content.events[ 'move' ][ a ](); } } } return cancelBubble ( e ); } // Mouse down on a resize gadget else if( window.mouseDown == 2 ) { let w = window.currentMovable; let r = w.resize; let t = w.titleBar; let l = w.leftbar; let x = w.rightbar; // Set normal mode r.mode = 'normal'; r.window.removeAttribute( 'maximized' ); _removeWindowTiles( w ); // Done normal mode let rx = ( windowMouseX - r.offx ); // resizex let ry = ( windowMouseY - r.offy ); // resizey // 8 px grid if( e.shiftKey ) { rx = Math.floor( rx / 8 ) << 3; ry = Math.floor( ry / 8 ) << 3; } if( !w.getAttribute( 'moving' ) ) w.setAttribute( 'moving', 'moving' ); let resizeX = r.wwid - w.marginHoriz + rx; let resizeY = r.whei - w.marginVert + ry; if( w.snap ) { if( w.snap == 'up' || w.snap == 'down' ) { resizeX = w.offsetWidth; } if( w.snap == 'left' || w.snap == 'right' ) { resizeY = w.offsetHeight; } } ResizeWindow( w, resizeX, resizeY ); // Do resize events (and make sure we have the overlay to speed things up) CoverWindows(); if( w.content.events ) { if( typeof(w.content.events['resize']) != 'undefined' ) { for( var a = 0; a < w.content.events['resize'].length; a++ ) { w.content.events['resize'][a](); } } } return cancelBubble( e ); } } // Mouse down on desktop (regions) if( !isTouchDevice() && window.mouseDown == 4 && window.regionWindow ) { // Prime if( window.regionWindow.directoryview ) { let scrl = window.regionWindow.directoryview.scroller; if( !scrl.scrolling ) { scrl.scrollTopStart = scrl.scrollTop; scrl.scrollLeftStart = scrl.scrollLeft; scrl.scrolling = true; } // Draw if( DrawRegionSelector( e ) ) { return cancelBubble( e ); } } return false; } } // Draw the region selector with a possible offset! function DrawRegionSelector( e ) { let sh = e.shiftKey || e.ctrlKey; // Create region selector if it doesn't exist! if( !ge( 'RegionSelector' ) ) { let d = document.createElement( 'div' ); d.id = 'RegionSelector'; window.regionWindow.appendChild( d ); } // Extra offset in content window let mx = windowMouseX; let my = windowMouseY; let diffx = 0; let diffy = 0; let ex = 0; var ey = 0; let eh = 0; var ew = 0; let rwc = window.regionWindow.classList; let scrwn = window.regionWindow.directoryview ? window.regionWindow.directoryview.scroller : false; // In icon windows or new screens if ( rwc && ( rwc.contains( 'Content' ) || rwc.contains( 'ScreenContent' ) ) ) { // Window offset ex = -window.regionWindow.parentNode.offsetLeft; ey = -window.regionWindow.parentNode.offsetTop; // Check for other things ex -= ge( 'DoorsScreen' ).offsetLeft; // Some implications per theme accounted for if( rwc.contains( 'Content' ) ) { let top = window.regionWindow.windowObject; if( top ) ey -= window.regionWindow.windowObject._window.parentNode.titleBar.offsetHeight; let bor = GetThemeInfo( 'ScreenContentMargins' ); if( bor ) ey += parseInt( bor.top ); } // Scrolling down / left? Add to mouse scroll eh += window.regionWindow.scrollTop; ew += window.regionWindow.scrollLeft; if( rwc.contains( 'Content' ) ) { _ActivateWindow( window.regionWindow.parentNode, false, e ); } else { _DeactivateWindows(); } // Do we have a scroll area? if( scrwn ) { diffy = scrwn.scrollTopStart - scrwn.scrollTop; diffx = scrwn.scrollLeftStart - scrwn.scrollLeft; // If mouse pointer is far down, do some scrolling let ty = my - window.regionWindow.parentNode.offsetTop; let tx = mx - window.regionWindow.parentNode.offsetLeft; if( ty < 40 ) scrwn.scrollTop -= 10; else if ( ty - 30 > scrwn.offsetHeight - 30 ) scrwn.scrollTop += 10; } } let d = ge( 'RegionSelector' ); if( !d ) return; // Coordinate variables let wx = mx, wy = my; let dw = wx - window.regionX + ew - diffx; let dh = wy - window.regionY + eh - diffy; let ox = diffx, oy = diffy; // If we're selecting leftwards if ( dw < 0 ) { ox = dw + diffx; dw = -dw; } // If we're selecting rightwards if ( dh < 0 ) { oy = dh + diffy; dh = -dh; } if ( !dw || !dh ) return; // Set variables, all things considered! let dx = window.regionX + ox + ex; let dy = window.regionY + oy + ey; let odx = dx, ody = dy; // Some offset in windows or screens dy -= window.regionWindow.offsetTop; dx -= window.regionWindow.offsetLeft; // Check screen offset top if( window.regionWindow.windowObject ) { if( window.regionWindow.windowObject.flags.screen ) { let s = window.regionWindow.windowObject.flags.screen; if( s.div.screenOffsetTop ) { dy -= s.div.screenOffsetTop; } } } // Set dimensions! d.style.width = dw + 'px'; d.style.height = dh + 'px'; d.style.top = dy + 'px'; d.style.left = dx + 'px'; // check icons if ( window.regionWindow ) { let imx = dx; let imy = dy; // Scrolled window.. let scroller = regionWindow.directoryview.scroller; if ( scroller && scroller.style ) { let scr = parseInt ( scroller.scrollTop ); if ( isNaN ( scr )) scr = 0; imy += scr; } let icos = regionWindow.icons; if ( icos ) { let exOffx = 0; let exOffy = 0; if( regionWindow.windowObject ) { if( regionWindow.fileBrowser ) { if( !regionWindow.fileBrowser.dom.classList.contains( 'Hidden' ) ) { imx -= regionWindow.fileBrowser.dom.offsetWidth; } } } for ( let a = 0; a < icos.length; a++ ) { let ics = icos[a].domNode; // Coords on icon if( ics ) { let ix1 = ics.offsetLeft + exOffx; let iy1 = ics.offsetTop + exOffy; let ix2 = ics.offsetWidth+ix1 + exOffx; let iy2 = ics.offsetHeight+iy1 + exOffy; // check overlapping icon let overlapping = ix1 >= imx && iy1 >= imy && ix2 <= imx+dw && iy2 <= imy+dh; // check intersecting icon on a horizontal line let intersecting1 = ix1 >= imx && ix2 <= imx+dw && ( ( imy >= iy1 && imy <= iy2 ) || ( imy+dh >= iy2 && imy <= iy2 ) ); // check intersecting icon on a vertical line let intersecting2 = iy1 >= imy && iy2 <= imy+dh && ( ( imx >= ix1 && imx <= ix2 ) || ( imx+dw >= ix2 && imx <= ix2 ) ); // check top left corner let intersecting3 = ix1 < imx && iy1 < imy && ix2 >= imx && iy2 >= imy; // check top right corner let intersecting4 = ix1 < imx+dw && iy1 < imy+dh && ix2 > imx && iy2 >= imy+dh; // check bottom left corner let intersecting5 = ix1 >= imx && ix2 >= imx+dw && iy2 <= imy+dh && ix1 <= imx+dw && iy2 >= imy; // check bottom right corner let intersecting6 = ix1 >= imx && iy1 >= imy && ix2 >= imx+dw && iy2 >= imy+dh && ix1 < imx+dw && iy1 < imy+dh; // Combine all let intersecting = intersecting1 || intersecting2 || intersecting3 || intersecting4 || intersecting5 || intersecting6; if( overlapping || intersecting ) { ics.classList.add( 'Selected' ); ics.fileInfo.selected = 'multiple'; ics.selected = 'multiple'; icos[a].selected = 'multiple'; } else if( !sh ) { ics.classList.remove( 'Selected' ); ics.fileInfo.selected = false; ics.selected = false; icos[a].selected = false; } } } } } } // Gui modification / editing function AbsolutePosition( div, left, top, width, height ) { div.style.position = 'absolute'; if ( left ) div.style.left = left; if ( top ) div.style.top = top; if ( width ) div.style.width = width; if ( height ) div.style.height = height; } // Make a table list with checkered bgs function MakeTableList( entries, headers ) { let str = ''; let cols = 0; if ( headers ) { str += ''; for( let a = 0; a < headers.length; a++ ) { str += ''; } str += ''; cols = headers.length; } if ( cols <= 0 ) cols = entries[0].length; let sw = 1; for( let a = 0; a < entries.length; a++ ) { str += ''; for( let b = 0; b < cols; b++ ) { str += ''; } sw = sw == 1 ? 2 : 1; str += ''; } return str; } var workbenchMenus = new Array(); function SetMenuEntries( menu, entries ) { if( typeof ( workbenchMenus[menu] ) == 'undefined' ) workbenchMenus[menu] = new Array (); workbenchMenus[menu].push ( entries ); if( typeof ( RefreshWorkspaceMenu ) != 'undefined' ) RefreshWorkspaceMenu (); } // Deep remove events function cancelMouseEvents( e ) { window.mouseMoveFunc = false; window.mouseDown = false; window.fileMenuElement = null; window.mouseReleaseFunc = false; window.mouseDown = 0; return cancelBubble( e ); } // Just register we left the building movableMouseUp = function( e ) { if( !e ) e = window.event; let target = e.target ? e.target : e.srcElement; if( target && ( target.nodeName == 'INPUT' || target.nodeName == 'TEXTAREA' ) ) { return; } // For mobile if( isMobile ) { if( !( target.classList && target.classList.contains( 'View' ) ) && !( target.classList && target.classList.contains( 'ViewIcon' ) ) ) { _removeMobileCloseButtons(); } } window.fileMenuElement = null; window.mouseDown = false; if( window.currentMovable ) currentMovable.snapping = false; window.mouseMoveFunc = false; mousePointer.candidate = null; document.body.style.cursor = ''; // Execute the release function if( window.mouseReleaseFunc ) { window.mouseReleaseFunc(); window.mouseReleaseFunc = false; } // If we have a current movable window, stop "moving" if( window.currentMovable ) { window.currentMovable.removeAttribute( 'moving' ); // Remove where we shiftclicked window.currentMovable.shiftX = false; window.currentMovable.shiftY = false; } if( WorkspaceMenu.open || ( target && target.classList && ( target.classList.contains( 'ScreenContent' ) || target.classList.contains( 'Scroller' ) || target.classList.contains( 'Content' ) || target.classList.contains( 'MoveOverlay' ) || target.classList.contains( 'ScreenOverlay' ) ) ) ) { if( !isMobile ) { WorkspaceMenu.close(); } // Hide start menu if( Workspace.toggleStartMenu ) Workspace.toggleStartMenu( false ); } for( let a in movableWindows ) { let m = movableWindows[a]; m.removeAttribute( 'moving' ); } ExposeScreens(); ExposeWindows(); // Workbench menu is now hidden (only miga style) if( Workspace && Workspace.menuMode == 'miga' ) { WorkspaceMenu.hide( e ); WorkspaceMenu.close(); } // If we selected icons, clear the select region ClearSelectRegion(); // Make sure if( e.target && e.target.classList.contains( 'MoveOverlay' ) ) { e.targetReplacement = document.elementFromPoint( e.clientX, e.clientY ); } // Execute drop function on mousepointer (and stop moving!) mousePointer.drop( e ); mousePointer.stopMove( e ); RemoveDragTargets(); if( e.button == 0 ) { if( Workspace.iconContextMenu ) { let men = Workspace.iconContextMenu.dom.querySelector( '.MenuItems' ); if( men ) { men.classList.add( 'Closing' ); men.classList.remove( 'Open' ); e.preventDefault(); cancelBubble( e ); } setTimeout( function() { Workspace.iconContextMenu.hide(); Workspace.contextMenuShowing = null; }, 150 ); } } if( window.regionWindow && window.regionWindow.directoryview ) { let scrl = window.regionWindow.directoryview.scroller; if( scrl ) { scrl.scrolling = false; } } } // Remove all droptarget states function RemoveDragTargets() { let s = ge( 'DoorsScreen' ); if( s ) { // Make sure this is triggered to roll out of drop target if( s.classList.contains( 'DragTarget' ) ) s.classList.remove( 'DragTarget' ); } } var _screenTitleTimeout = null; // Check the screen title of active window/screen and check menu var prevScreen = prevWindow = false; function CheckScreenTitle( screen, force ) { // Support quickmenu if( Workspace.setQuickMenu ) { // When running with quickmenu, make sure we have an active view! if( !window.currentMovable && window.canSetQuickMenu && window.canSetQuickmenu() ) { let highest = -1; let highestView = false; for( let a in movableWindows ) { let mov = movableWindows[a]; if( mov.windowObject && mov.windowObject.getFlag( 'invisible' ) ) continue; let candidateZ = parseInt( mov.style.zIndex ); if( candidateZ > highest ) { highest = candidateZ; highestView = mov; } } if( highestView ) { if( highestView.windowObject.getFlag( 'invisible' ) ) { return; } _ActivateWindow( highestView ); if( window.hideDashboard ) window.hideDashboard(); return; } } else if( currentMovable && currentMovable.classList.contains( 'Active' ) ) { if( window.hideDashboard ) window.hideDashboard(); } // We do have a current movable, use it let wo = window.currentMovable; // Don't act on invisibles if( wo && wo.windowObject.getFlag( 'invisible' ) ) { return; } if( wo && wo.quickMenu ) { Workspace.setQuickMenu( wo.quickMenu, wo ); } else if( currentScreen && currentScreen.quickMenu ) { Workspace.setQuickMenu( currentScreen.quickMenu, currentScreen ); } // Just make a new one if it does not exist else if( wo ) { wo.quickMenu = { uniqueName: MD5( Math.random() * 1000 + ( Math.random() * 1000 ) + '' ), items: [ { name: i18n( 'i18n_back' ), message: { command: 'back' } } ] }; Workspace.setQuickMenu( wo.quickMenu, wo ); } else { if( Workspace.hideQuickMenu ) Workspace.hideQuickMenu(); } } // Dashboard if( window.Workspace && Workspace.dashboard && Workspace.dashboard !== true ) Workspace.dashboard.refresh( true ); let testObject = screen ? screen : window.currentScreen; if( !testObject && !force ) return; // Orphan node! if( window.currentMovable && !( window.currentMovable.parentNode && window.currentMovable.parentNode.parentNode ) ) { window.currentMovable = null; } // If nothing changed, don't change if( prevScreen && prevWindow && !force ) { if( prevScreen == testObject ) { if( prevWindow == window.currentMovable ) return; } } // Remember current state prevWindow = window.currentMovable; prevScreen = testObject; Friend.GUI.reorganizeResponsiveMinimized(); // Set screen title let csc = testObject.screenObject; if( !csc ) return; // Set the screen title if we have a window with application name let wo = window.currentMovable ? window.currentMovable.windowObject : false; // Fix screen if( wo && !wo.screen && wo.flags.screen ) wo.screen = wo.flags.screen; // Check screen if( wo && wo.screen && wo.screen != csc ) { wo = false; // Only movables on current screen } // Check dom node if( wo && wo.parentNode && !wo.parentNode.parentNode ) wo = false; let isDoorsScreen = testObject.id == 'DoorsScreen'; let hasScreen = ( !csc || ( wo && testObject.screenObject == wo.screen ) || ( wo && !wo.screen && isDoorsScreen ) ); // Clear the delayed action if( _screenTitleTimeout ) { clearTimeout( _screenTitleTimeout ); csc.contentDiv.parentNode.classList.remove( 'ChangingScreenTitle' ); _screenTitleTimeout = null; } // Get app title if( wo && wo.applicationName && hasScreen ) { let wnd = wo.applicationDisplayName ? wo.applicationDisplayName : wo.applicationName; if( !csc.originalTitle ) { csc.originalTitle = csc.getFlag( 'title' ); } // Don't do it twice if( wnd != csc.getFlag( 'title' ) ) { csc.contentDiv.parentNode.classList.add( 'ChangingScreenTitle' ); _screenTitleTimeout = setTimeout( function() { setTitleAndMoveMenu( csc, wnd ); _screenTitleTimeout = setTimeout( function() { csc.contentDiv.parentNode.classList.remove( 'ChangingScreenTitle' ); }, 70 ); }, 70 ); } else { setTitleAndMoveMenu( csc, wnd ); } } // Just use the screen (don't do it twice) else if( csc.originalTitle && csc.getFlag( 'title' ) != csc.originalTitle ) { csc.contentDiv.parentNode.classList.add( 'ChangingScreenTitle' ); let titl = csc.originalTitle; _screenTitleTimeout = setTimeout( function() { setTitleAndMoveMenu( csc, titl ); _screenTitleTimeout = setTimeout( function() { csc.contentDiv.parentNode.classList.remove( 'ChangingScreenTitle' ); }, 70 ); }, 70 ); } else { setTitleAndMoveMenu(); } // Delayed function setTitleAndMoveMenu( obj, tit ) { if( obj && tit ) { obj.setFlag( 'title', tit ); if( Friend.windowBaseStringRules && Friend.windowBaseStringRules == 'replace' ) { document.title = Friend.windowBaseString; } else { if( tit.indexOf( Friend.windowBaseString ) < 0 ) tit += ' - ' + Friend.windowBaseString; document.title = tit; } } // Enable the global menu if( Workspace && Workspace.menuMode == 'pear' ) { if( window.WorkspaceMenu && ( !WorkspaceMenu.generated || WorkspaceMenu.currentView != currentMovable || WorkspaceMenu.currentScreen != currentScreen ) ) { WorkspaceMenu.show(); WorkspaceMenu.currentView = currentMovable; WorkspaceMenu.currentScreen = currentScreen; } // Nudge workspace menu to right side of screen title if( !isMobile && ge( 'WorkspaceMenu' ) ) { let t = currentScreen.screen._titleBar.querySelector( '.Info' ); if( t ) { ge( 'WorkspaceMenu' ).style.left = t.offsetWidth + t.offsetLeft + 10 + 'px'; } } } } } // Indicator that we have a maximized view function CheckMaximizedView() { if( isMobile ) { if( window.currentMovable && currentMovable.classList.contains( 'Active' ) ) { document.body.classList.add( 'ViewMaximized' ); } else { document.body.classList.remove( 'ViewMaximized' ); } } else { if( window.currentMovable ) { if( currentMovable.getAttribute( 'maximized' ) == 'true' ) { document.body.classList.add( 'ViewMaximized' ); } else if( currentMovable.snapObject && currentMovable.snapObject.getAttribute( 'maximized' ) == 'true' ) { document.body.classList.add( 'ViewMaximized' ); } else { document.body.classList.remove( 'ViewMaximized' ); } } else { document.body.classList.remove( 'ViewMaximized' ); } } if( window.pollLiveViews ) pollLiveViews(); } // Get the taskbar element function GetTaskbarElement() { if( ge( 'DockWindowList' ) ) return ge( 'DockWindowList' ); return ge( 'Taskbar' ); } // Let's make gui for movable windows minimize maximize pollingTaskbar = false; function PollTaskbar( curr ) { if( pollingTaskbar ) return; if( !document.body || !document.body.classList.contains( 'Inside' ) ) return; if( ge( 'FriendScreenOverlay' ) && ge( 'FriendScreenOverlay' ).classList.contains( 'Visible' ) ) return; // Do we have dashboard? if( window.Workspace && Workspace.dashboard && Workspace.dashboard !== true && Workspace.dashboard.refreshDashboard ) { Workspace.dashboard.refreshDashboard(); } if( globalConfig.viewList == 'docked' || globalConfig.viewList == 'dockedlist' ) { PollDockedTaskbar(); // <- we are using the dock if( globalConfig.viewList == 'docked' ) return; } // Abort if we have a premature element if( curr && !curr.parentNode ) return; let doorsScreen = ge( 'DoorsScreen' ); if( !doorsScreen ) return; pollingTaskbar = true; let baseElement = ge( 'Taskbar' ); // Placing the apps inside the dock and not using a normal taskbar if( !isMobile ) { if( globalConfig.viewList == 'dockedlist' ) { if( !ge( 'DockWindowList' ) ) { // Find first desklet let dlets = document.getElementsByClassName( 'Desklet' ); if( dlets.length || dlets[ 0 ] ) { ge( 'Statusbar' ).className = 'Docklist'; let dlet = dlets[ 0 ]; let d = document.createElement( 'div' ); d.id = 'DockWindowList'; d.className = 'WindowList'; dlet.appendChild( d ); dlet.classList.add( 'HasWindowlist' ); if( dlet.classList.contains( 'Vertical' ) ) { d.style.bottom = '0px'; } else { d.style.right = '0px'; } baseElement = d; // Add size here let dock = d.parentNode.desklet; if( dock.conf ) d.classList.add( 'Size' + dock.conf.size ); } // Nothing to track else { pollingTaskbar = false; return; } } else { baseElement = ge( 'DockWindowList' ); } if( !baseElement ) { pollingTaskbar = false; return; } // Make into a HasWindowlist element let dock = baseElement.parentNode.desklet; baseElement.parentNode.classList.add( 'HasWindowlist' ); let dlength = Workspace.mainDock.iconListPixelLength; if( baseElement.parentNode.classList.contains( 'Vertical' ) ) { baseElement.style.height = 'calc(100% - ' + dlength + 'px)'; baseElement.style.width = '100%'; } else { let right = '0'; baseElement.style.width = 'calc(100% - ' + dlength + 'px)'; baseElement.style.right = right + 'px'; baseElement.style.height = '100%'; } // Add size here if( dock.conf ) { baseElement.classList.add( 'Size' + dock.conf.size ); } } // Normal taskbar else { if( !baseElement ) { pollingTaskbar = false; return; } if( baseElement.childNodes.length ) { let lastTask = baseElement.childNodes[ baseElement.childNodes.length - 1 ]; // Horizontal let taskWidth = lastTask.offsetLeft + lastTask.offsetWidth; baseElement.style.left = '0px'; baseElement.style.top = 'auto'; baseElement.classList.add( 'Horizontal' ); baseElement.style.width = 'calc(100% - ' + ( ge( 'Tray' ).offsetWidth + 5 ) + 'px)'; if( baseElement.scrollFunc ) baseElement.removeEventListener( 'mousemove', baseElement.scrollFunc ); baseElement.scrollFunc = function( e ) { let l = baseElement.childNodes[ baseElement.childNodes.length - 1 ]; if( !l ) return; let off = e.clientX - baseElement.offsetLeft; let scr = off / baseElement.offsetWidth; if( l.offsetLeft + l.offsetWidth > baseElement.offsetWidth ) { let whole = l.offsetLeft + l.offsetWidth - baseElement.offsetWidth; baseElement.scroll( scr * whole, 0 ); } } baseElement.addEventListener( 'mousemove', baseElement.scrollFunc ); } } let t = null; // If we have the 'Taskbar' if( baseElement ) { let whw = 0; // whole width let swi = baseElement.offsetWidth; t = baseElement; // When activated normally if( !curr ) { // No task array? if( typeof( t.tasks ) == 'undefined' ) t.tasks = []; // Remove tasks on the taskbar that isn't represented by a view let cleaner = []; for( let b = 0; b < t.tasks.length; b++ ) { // Look if this task is registered with a view let f = false; for( let a in movableWindows ) { // Skip snapped windows if( !movableWindows[ a ].snap && movableWindows[ a ].viewId == t.tasks[ b ].viewId ) { f = true; break; } } // If we already registered this task, add to cleaner if( f ) { let tt = t.tasks[ b ]; if( !currentMovable && tt.dom.classList.contains( 'Active' ) ) { // Remove active if there's no movable tt.dom.classList.remove( 'Active' ); } cleaner.push( tt ); } // If the window doesn't exist, remove the DOM element from tasbkar else t.removeChild( t.tasks[ b ].dom ); } t.tasks = cleaner; // Set cleaned task list for( let a in movableWindows ) { let d = false; // Skip snapped windows if( movableWindows[ a ].snap ) continue; // Movable windows let pn = movableWindows[a]; // Skip hidden ones if( pn.windowObject.flags.hidden == true ) { continue; } if( pn.windowObject.flags.invisible == true ) { continue; } if( pn && pn.windowObject.flags.screen != Workspace.screen ) { continue; } // Lets see if the view is a task we manage for( let c = 0; c < t.tasks.length; c++ ) { if ( t.tasks[c].viewId == pn.viewId ) { d = t.tasks[ c ].dom; // don't add twice // Race condition, Update the state ( function( ele, pp ){ setTimeout( function() { if( pp.parentNode.getAttribute( 'minimized' ) ) { ele.classList.add( 'Task', 'Hidden', 'MousePointer' ); } else { ele.classList.add( 'Task', 'MoustPointer' ); ele.classList.remove( 'Hidden' ); } }, 5 ); } )( d, pn ); // Check directoryview if( pn.content.directoryview ) d.classList.add( 'Directory' ); break; } } // Create new tasks if( !d ) { // New view! d = document.createElement( 'div' ); d.viewId = pn.viewId; d.view = pn; d.className = pn.parentNode.getAttribute( 'minimized' ) == 'minimized' ? 'Task Hidden MousePointer' : 'Task MousePointer'; if( pn.content.directoryview ) { d.classList.add( 'Directory' ); } d.window = pn; pn.taskbarTask = d; d.applicationId = d.window.applicationId; d.innerHTML = d.window.titleString; t.tasks.push( { viewId: pn.viewId, dom: d } ); if( pn == currentMovable ) d.classList.add( 'Active' ); // Functions on task element // Activate d.setActive = function( click ) { this.classList.add( 'Active' ); _ActivateWindow( this.window ); if( click ) { let div = this.window; div.viewContainer.setAttribute( 'minimized', '' ); div.windowObject.flags.minimized = false; div.minimized = false; let app = _getAppByAppId( div.applicationId ); if( app ) { app.sendMessage( { 'command': 'notify', 'method': 'setviewflag', 'flag': 'minimized', 'viewId': div.windowObject.viewId, 'value': false } ); } if( div.windowObject.workspace != globalConfig.workspaceCurrent ) { Workspace.switchWorkspace( div.windowObject.workspace ); } if( div.attached ) { for( var a = 0; a < div.attached.length; a++ ) { if( div.attached[ a ].minimize ) { div.attached[ a ].minimized = false; div.attached[ a ].windowObject.flags.minimized = false; div.attached[ a ].viewContainer.removeAttribute( 'minimized' ); let app = _getAppByAppId( div.attached[ a ].applicationId ); if( app ) { app.sendMessage( { 'command': 'notify', 'method': 'setviewflag', 'flag': 'minimized', 'viewId': div.attached[ a ].windowObject.viewId, 'value': false } ); } } } } } } // Deactivate d.setInactive = function() { if( this.window.classList.contains( 'Active' ) ) { _DeactivateWindow( this.window ); } this.classList.remove( 'Active' ); } // Click event d.onmousedown = function() { this.mousedown = true; } d.onmouseout = function() { this.mousedown = false; } d.onmouseup = function( e, extarg ) { if( !this.mousedown ) return; if( e.button != 0 ) return; // Not needed anymore this.mousedown = false; if ( !e ) e = window.event; let targ = e ? ( e.target ? e.target : e.srcElement ) : false; if( extarg ) targ = extarg; for( let n = 0; n < t.childNodes.length; n++ ) { let ch = t.childNodes[ n ]; if( !ch.className ) continue; if( ch.className.indexOf( 'Task' ) < 0 ) continue; if( this == ch ) { if( !this.window.classList.contains( 'Active' ) ) { this.setActive( true ); // with click _WindowToFront( this.window ); this.classList.remove( 'Hidden' ); } else { this.setInactive(); this.window.viewContainer.setAttribute( 'minimized', 'minimized' ); this.window.windowObject.flags.minimized = true; let div = this.window; let app = _getAppByAppId( div.applicationId ); if( app ) { app.sendMessage( { 'command': 'notify', 'method': 'setviewflag', 'flag': 'minimized', 'viewId': div.windowObject.viewId, 'value': true } ); } if( div.attached ) { for( let a = 0; a < div.attached.length; a++ ) { if( !div.attached[ a ].minimized ) { div.attached[ a ].minimized = true; div.attached[ a ].windowObject.minimized = true; div.attached[ a ].viewContainer.setAttribute( 'minimized', 'minimized' ); let app = _getAppByAppId( div.attached[ a ].applicationId ); if( app ) { app.sendMessage( { 'command': 'notify', 'method': 'setviewflag', 'flag': 'minimized', 'viewId': div.attached[ a ].viewId, 'value': true } ); } } } } CheckMaximizedView(); } } else { ch.setInactive(); if( ch.window.classList.contains( 'Active' ) ) _DeactivateWindow( ch.window ); } } } // Need some help? Only show help if parent element is aligned left or right CreateHelpBubble( d, d.window.titleString, false, { getOffsetTop: function(){ return t.scrollTop; }, positions: [ 'Left', 'Right' ] } ); t.appendChild( d ); d.origWidth = d.offsetWidth + 20; // Check if we opened a window with a task image if( d.applicationId ) { let running = ge( 'Tasks' ).getElementsByTagName( 'iframe' ); for( let a = 0; a < running.length; a++ ) { let task = running[a]; // Find the window! if( task.applicationId == d.applicationId ) { // If we have a match! d.style.backgroundImage = 'url(' + task.icon + ')'; } } } } } } else { // Update existing tasks if( curr && curr.taskbarTask && curr.parentNode ) { curr.taskbarTask.setActive(); } for( let c = 0; c < t.tasks.length; c++ ) { let d = t.tasks[ c ].dom; if( d.window != curr ) { d.setInactive(); } } } // Can't if( whw >= swi ) { baseElement.setAttribute( 'full', 'full' ); } // We deleted some else { baseElement.setAttribute( 'full', 'no' ); } } // Manage running apps ------- // Just check if the app represented on the desklet is running for( let a = 0; a < __desklets.length; a++ ) { let desklet = __desklets[a]; // Assume all launchers represent apps that are not running for( let c = 0; c < desklet.dom.childNodes.length; c++ ) { desklet.dom.childNodes[c].running = false; } // Go and check running status for( let b in movableWindows ) { if( movableWindows[b].windowObject ) { let app = movableWindows[b].windowObject.applicationName; let aid = movableWindows[b].windowObject.applicationId; // Try to find the application if it is an application window for( let c = 0; c < desklet.dom.childNodes.length; c++ ) { if( app && desklet.dom.childNodes[c].uniqueId == aid ) { desklet.dom.childNodes[c].classList.add( 'Running' ); desklet.dom.childNodes[c].running = true; } } } } // Just check if the app represented on the desklet is running for( let c = 0; c < desklet.dom.childNodes.length; c++ ) { if( desklet.dom.childNodes[c].running == false ) { desklet.dom.childNodes[c].classList.remove( 'Running' ); desklet.dom.childNodes[c].classList.remove( 'Minimized' ); } } } // Final test, just flush suddenly invisible or hidden view windows let out = []; for( let a = 0; a < t.tasks.length; a++ ) { let v = t.tasks[a].dom; if( v.view.windowObject.flags.hidden || v.view.windowObject.flags.invisible ) { t.tasks[a].dom.parentNode.removeChild( t.tasks[a].dom ); } else out.push( t.tasks[a] ); } t.tasks = out; } PollTray(); pollingTaskbar = false; } // A docked taskbar uses the dock desklet! function PollDockedTaskbar() { if( Workspace.docksReloading ) return; pollingTaskbar = true; PollTray(); for( let a = 0; a < __desklets.length; a++ ) { let desklet = __desklets[a]; let changed = false; if( !desklet.viewList ) { let wl = document.createElement( 'div' ); desklet.viewList = wl; wl.className = 'ViewList'; desklet.dom.appendChild( wl ); } // Clear existing viewlist items that are removed let remove = []; for( let y = 0; y < desklet.viewList.childNodes.length; y++ ) { if( !movableWindows[desklet.viewList.childNodes[y].viewId] ) remove.push( desklet.viewList.childNodes[y] ); } if( remove.length ) { for( let y = 0; y < remove.length; y++ ) desklet.viewList.removeChild( remove[y] ); changed++; } // Clear views that are managed by launchers for( let y = 0; y < desklet.dom.childNodes.length; y++ ) { let dy = desklet.dom.childNodes[y]; if( dy.views ) { let out = []; for( let o in dy.views ) { if( movableWindows[o] ) out[o] = dy.views[o]; } dy.views = out; } } let wl = desklet.viewList; // Just check if the app represented on the desklet is running for( let c = 0; c < desklet.dom.childNodes.length; c++ ) { desklet.dom.childNodes[c].running = false; } // Go through all movable view windows and check! // But only for docked mode. if( globalConfig.viewList == 'docked' ) { for( let b in movableWindows ) { if( movableWindows[ b ].windowObject ) { // Skip hidden and invisible windows if( movableWindows[ b ].windowObject.flags.hidden ) { continue; } if( movableWindows[ b ].windowObject.flags.invisible ) { continue; } let app = movableWindows[ b ].windowObject.applicationName; let aid = movableWindows[ b ].windowObject.applicationId; let win = b; let wino = movableWindows[ b ]; let found = false; // Try to find view in viewlist for( let c = 0; c < desklet.viewList.childNodes.length; c++ ) { let cn = desklet.viewList.childNodes[ c ]; if( cn.viewId == win ) { found = wino; // Check if it is a directory if( found.content.directoryview ) { cn.classList.add( 'Directory' ); } else if( found.applicationId ) { cn.classList.add( 'Running' ); } break; } } // Try to find the application if it is an application window if( !found && app ) { for( let c = 0; c < desklet.dom.childNodes.length; c++ ) { let dof = desklet.dom.childNodes[ c ]; if( dof.uniqueId == aid ) { found = dof.executable; dof.classList.add( 'Running' ); dof.running = true; break; } } } // If it's a found app, check if it isn't a single instance app if( found && typeof( found ) == 'string' ) { // Single instance apps handle themselves if( !Friend.singleInstanceApps[ found ] ) { for( let c = 0; c < desklet.dom.childNodes.length; c++ ) { let d = desklet.dom.childNodes[ c ]; if( !d.classList.contains( 'Launcher' ) ) continue; if( d.executable == found ) { if( !d.views ) d.views = []; if( !d.views[ win ] ) d.views[ win ] = wino; // Clear non existing let out = []; for( let i in d.views ) if( movableWindows[ i ] ) out[ i ] = d.views[ i ]; d.views = out; } } } } // Check for an app icon let labelIcon = false; if( app && ge( 'Tasks' ) ) { let tk = ge( 'Tasks' ).getElementsByTagName( 'iframe' ); for( let a1 = 0; a1 < tk.length; a1++ ) { if( tk[a1].applicationName != app ) continue; let f = tk[ a1 ].parentNode; if( f.className && f.className == 'AppSandbox' ) { let img = f.getElementsByTagName( 'div' ); for( let b1 = 0; b1 < img.length; b1++ ) { if( img[ b1 ].style.backgroundImage ) { labelIcon = document.createElement( 'div' ); labelIcon.style.backgroundImage = img[ b1 ].style.backgroundImage; labelIcon.className = 'LabelIcon'; break; } } } } } // Add the window list item into the desklet if( !found ) { let viewRep = document.createElement( 'div' ); viewRep.className = 'Launcher View MousePointer'; if( labelIcon ) viewRep.appendChild( labelIcon ); if( app ) viewRep.classList.add( app ); viewRep.style.backgroundSize = 'contain'; viewRep.state = 'visible'; viewRep.viewId = win; viewRep.setAttribute( 'title', movableWindows[win].titleString ); viewRep.onclick = function( e ) { // TODO: Make sure we also have touch if( !e || e.button != '0' ) return; let theView = movableWindows[ this.viewId ]; this.state = this.state == 'visible' ? 'hidden' : 'visible'; let wsp = theView.windowObject.workspace; if( wsp != globalConfig.workspaceCurrent ) { Workspace.switchWorkspace( wsp ); this.state = 'visible'; } if( this.state == 'hidden' ) { theView.viewContainer.classList.add( 'Minimized' ); theView.windowObject.flags.minimized = true; } else { theView.viewContainer.classList.remove( 'Minimized' ); theView.windowObject.flags.minimized = false; _WindowToFront( theView ); } let mv = theView; if( mv && mv.windowObject ) { theView.windowObject.setFlag( 'hidden', this.state == 'hidden' ? true : false ); if( this.state == 'hidden' ) { if( !this.elementCount ) { let d = document.createElement( 'div' ); d.className = 'ElementCount'; this.elementCount = d; this.appendChild( d ); } this.elementCount.innerHTML = '' + 1 + ''; } else { if( this.elementCount ) { this.removeChild( this.elementCount ); this.elementCount = null; } } } _WindowToFront( theView ); CheckMaximizedView(); } desklet.viewList.appendChild( viewRep ); changed++; } else { } } } } // Just check if the app represented on the desklet is running for( let c = 0; c < desklet.dom.childNodes.length; c++ ) { if( desklet.dom.childNodes[c].running == false ) { desklet.dom.childNodes[c].classList.remove( 'Running' ); desklet.dom.childNodes[c].classList.remove( 'Minimized' ); } } if( changed ) desklet.render(); } pollingTaskbar = false; } // Make a menu for the current launcher or view list item icon // It's only for items that has a group of views! var _vlMenu = null; function GenerateViewListMenu( win ) { if( _vlMenu ) { _vlMenu.parentNode.removeChild( _vlMenu ); } let found = false; for( var a in movableWindows ) { //if( movableWindows[ } //_vlMenu = document.createElement( 'div' ); } // Notify the user! // format: msg{ title: 'sffs', text: 'fslkjsl' } function CallFriendApp( func, param1, param2, param3 ) { if( typeof friendApp != 'undefined' ) { switch ( func ) { case 'show_notification': if ( typeof friendApp.show_notification == 'function' ) { friendApp.show_notification( param1, param2 ); return ''; } break; case 'onFriendNetworkMessage': if ( typeof friendApp.onFriendNetworkMessage == 'function' ) return friendApp.onFriendNetworkMessage( param1 ); break; } } return false; } function ClearSelectRegion() { if( ge ( 'RegionSelector' ) ) { let s = ge ( 'RegionSelector' ); s.parentNode.removeChild( s ); } // Nullify initial settings if( window.regionWindow ) { if( window.regionWindow.scroller ) { window.regionWindow.scroller.onscroll = null; } } } movableMouseDown = function ( e ) { if ( !e ) e = window.event; if( isTouchDevice() ) { let active = document.activeElement; let anod = ''; if( active ) anod = active.nodeName; if( active && !( anod == 'TEXTAREA' || anod == 'INPUT' || active.getAttribute( 'contenteditable' ) ) ) { active.blur(); } } window.focus(); // Close tray bubble if( ge( 'Tray' ) && ge( 'Tray' ).notificationPopup ) { if( e.target && e.target != ge( 'Tray' ).notificationPopup.parentNode ) { CloseTrayBubble(); } } // Menu trigger let rc = 0; if ( e.which ) rc = ( e.which == 3 ); else if ( e.button ) rc = ( e.button == 2 ); // Get target let tar = e.srcElement ? e.srcElement : e.target; if( isTouchDevice() && Workspace.contextMenuShowing ) { Workspace.iconContextMenu.hide(); Workspace.contextMenuShowing = null; if( !isMobile ) DefaultToWorkspaceScreen( tar ); } // TODO: Allow context menus! if( !isTouchDevice() && ( rc || e.button != 0 ) ) { return; } // Remove menu on calendar slide and menu click if( isMobile && tar.id && tar.id == 'WorkspaceMenu' ) { if( ge( 'CalendarWidget' ) && document.body.classList.contains( 'WidgetSlideDown' ) ) { if( Workspace.widget ) { tar.classList.remove( 'Open' ); Workspace.widget.slideUp(); return; } } } let clickOnMenuItem = tar && tar.classList.contains( 'MenuItem' ) ? true : false; if( !clickOnMenuItem && Workspace.iconContextMenu ) { Workspace.iconContextMenu.hide(); Workspace.contextMenuShowing = null; } let sh = e.shiftKey || e.ctrlKey; // Zero scroll window.regionScrollLeft = 0; window.regionScrollTop = 0; // Clicking inside content (listview or normal) if ( ( tar.classList && tar.classList.contains( 'Scroller' ) && ( tar.parentNode.classList.contains( 'Content' ) || tar.parentNode.classList.contains( 'ScreenContent' ) ) ) || ( tar.classList && tar.classList.contains( 'ScrollArea' ) && tar.parentNode.classList.contains( 'Listview' ) ) ) { tar = tar.parentNode; } // Check if we got a click on desktop let clickonDesktop = tar.classList && ( tar.classList.contains( 'ScreenContent' ) || tar.classList.contains( 'ScreenOverlay' ) ); let clickOnView = tar.classList && tar.classList.contains( 'Content' ) && tar.parentNode.classList.contains ( 'View' ); // Listview if( !clickOnView && tar.classList.contains( 'Listview' ) ) clickOnView = true; // Desktop / view selection if( !isMobile && ( clickonDesktop || clickOnView ) ) { if( !sh && ( e.button === 0 || e.touches ) ) { // Don't count scrollbar let px = e.touches ? e.touches[0].pageX : e.clientX; if( ( ( px - GetElementLeft( tar ) ) < tar.offsetWidth - 16 ) ) { setTimeout( function() { if( tar.directoryview ) { if( tar.directoryview.refreshScrollTimeout ) { return; } } clearRegionIcons( { force: true } ); }, 100 ); } } window.mouseDown = 4; window.regionX = windowMouseX; window.regionY = windowMouseY; if( tar ) window.regionWindow = tar; else window.regionWindow = ge( 'DoorsScreen' ); if( clickOnView ) { // This doesn't seem like it matters? // Before we activated window here } else if( clickonDesktop ) { if( window.currentMovable && tar.classList && tar.classList.contains( 'ScreenOverlay' ) ) { // Check if we clicked active window // TODO: Cycle through all windows and check if we clicked on any, including widgets let wl = GetElementLeft( currentMovable ); let wt = GetElementTop( currentMovable ); if( windowMouseX >= wl && windowMouseX <= wl+currentMovable.offsetWidth && windowMouseY >= wt && windowMouseY <= wt+currentMovable.offsetHeight ) { _ActivateWindow( currentMovable ); } else { DefaultToWorkspaceScreen( tar ); } } else { // Clicking from an active view to screen DefaultToWorkspaceScreen( tar ); } } else { CheckScreenTitle(); } Workspace.toggleStartMenu( false ); // Cover windows if we are clicking on desktop if( clickonDesktop ) CoverWindows(); return cancelBubble( 2 ); } else if ( isMobile && ( clickonDesktop || clickOnView ) ) { // TODO: Perhaps scroll shouldn't deselect if( clickOnView ) { clearRegionIcons( { force: true } ); } } } // Go into standard Workspace user mode (f.ex. clicking on wallpaper) function DefaultToWorkspaceScreen( tar ) // tar = click target { if( isMobile ) return; FocusOnNothing(); WorkspaceMenu.close(); } function convertIconsToMultiple() { if( currentMovable && currentMovable && currentMovable.content && currentMovable.content.icons ) { let ics = currentMovable.content.icons; for( let a = 0; a < ics.length; a++ ) { if( ics[a].selected ) { ics[a].selected = 'multiple'; if( ics[ a ].domNode ) ics[a].domNode.selected = 'multiple'; if( ics[a].fileInfo ) ics[a].fileInfo.selected = 'multiple'; } } } } function clearRegionIcons( flags ) { // No icons selected now.. Friend.iconsSelectedCount = 0; // Exception for icon deselection let exception = null; if( flags && flags.exception ) { exception = flags.exception; } let multipleCheck = flags && flags.force ? 'none' : 'multiple'; // Clear all icons for( let a in movableWindows ) { let w = movableWindows[a]; if( w.content && w.content.icons ) w = w.content; if ( w.icons ) { for ( let b = 0; b < w.icons.length; b++ ) { let ic = w.icons[ b ].domNode; if( ic && ic.className ) { if( exception != ic && ic.selected != multipleCheck ) { ic.classList.remove( 'Selected' ); w.icons[ b ].selected = false; w.icons[ b ].file = false; ic.selected = false; } ic.classList.remove( 'Editing' ); if( ic.input ) { if( ic.input.parentNode ) { ic.input.parentNode.removeChild( ic.input ); } ic.input = null; } } } } } // Clear desktop icons if( window.Doors && Doors.screen && Doors.screen.contentDiv.icons ) { for( let a = 0; a < Doors.screen.contentDiv.icons.length; a++ ) { let icon = Doors.screen.contentDiv.icons[a]; let ic = icon.domNode; if( !ic ) continue; if( exception != ic && ic.selected != multipleCheck ) { ic.classList.remove( 'Selected' ); icon.file.selected = false; icon.selected = false; ic.selected = false; } } } } function contextMenu( e ) { if( e.defaultBehavior ) return; if ( !e ) e = window.event; let tar = e.target ? e.target : e.srcEvent; if ( !tar ) return; let mov, mov2, mov3; let mdl = GetTitleBarG (); if ( tar.parentNode ) mov = tar.parentNode.className && tar.parentNode.className.indexOf ( ' View' ) >= 0; if ( tar.parentNode && tar.parentNode.parentNode && tar.parentNode.parentNode.className ) mov2 = tar.parentNode.parentNode.className.indexOf ( ' View' ) >= 0; if ( tar.parentNode && tar.parentNode.parentNode && tar.parentNode.parentNode.parentNode && tar.parentNode.parentNode.parentNode.className ) mov3 = tar.parentNode.parentNode.parentNode.className.indexOf ( ' View' ) >= 0; if ( tar.classList.contains( 'ScreenContent' ) || windowMouseY < (mdl?mdl.offsetHeight:0) || ( tar.classList.contains( 'Content' ) && mov ) || ( tar.classList.contains( 'Title' ) && mov ) || ( tar.parentNode.classList.contains( 'Title' ) && mov2 ) || ( tar.className == 'MoveOverlay' && mov ) || ( tar.className == 'Resize' && mov ) || ( tar.parentNode.parentNode && tar.parentNode.parentNode.classList.contains( 'Title' ) && mov3 ) ) { window.mouseDown = false; if( isMobile ) { MobileContextMenu.show( tar ); } else { WorkspaceMenu.show(); } } return cancelBubble( e ); } function FixWindowDimensions( mw ) { SetWindowFlag( mw, 'min-height', mw.parentNode.offsetHeight ); SetWindowFlag( mw, 'max-height', mw.parentNode.offsetHeight ); SetWindowFlag( mw, 'min-width', mw.parentNode.offsetWidth ); SetWindowFlag( mw, 'max-width', mw.parentNode.offsetWidth ); } function doReveal() { if( window.friendApp && window.friendApp.reveal ) { if( Workspace.wallpaperImage ) { if( !Workspace.wallpaperLoaded ) { if( Workspace.wallpaperImage == 'color' ) { document.body.classList.add( 'Revealed' ); friendApp.reveal(); } else { let i = new Image(); if( Workspace.wallpaperImageDecoded ) i.src = Workspace.wallpaperImageDecoded; else i.src = getImageUrl( Workspace.wallpaperImage ); i.onload = function() { // Tell app we can show ourselves! document.body.removeChild( i ); document.body.classList.add( 'Revealed' ); if( document.body.classList.contains( 'ThemeRefreshing' ) ) { Workspace.refreshTheme(); } friendApp.reveal(); } i.style.visibility = 'hidden'; document.body.appendChild( i ); if( i.width && i.width > 0 ) { i.onload(); } } } else { // Tell app we can show ourselves! document.body.classList.add( 'Revealed' ); if( document.body.classList.contains( 'ThemeRefreshing' ) ) { Workspace.refreshTheme(); } friendApp.reveal(); } } else { setTimeout( function(){ doReveal(); }, 50 ); } } else { //console.log( 'window.friendApp does not exist, or reveal does not exist on the object.' ); } } function ElementWindow ( ele ) { // Check if this element is in a window while ( ele != document.body ) { ele = ele.parentNode; if ( ele.className && ele.className.indexOf ( ' View' ) >= 0 ) { if ( ele.content ) return ele.content; return ele; } } return false; } function InitGuibaseEvents() { window.addEventListener( 'touchstart', movableMouseDown, false ); window.addEventListener( 'touchmove', movableListener, false ); window.addEventListener( 'touchend', movableMouseUp, false ); if( window.attachEvent ) window.attachEvent( 'onmouseup', movableMouseUp, false ); else window.addEventListener( 'mouseup', movableMouseUp, false ); if( window.attachEvent ) window.attachEvent ( 'onmousemove', movableListener, false ); else window.addEventListener( 'mousemove', movableListener, false ); if( window.attachEvent ) window.attachEvent( 'onmousedown', movableMouseDown, false ); else window.addEventListener( 'mousedown', movableMouseDown, false ); if( window.attachEvent ) window.attachEvent( 'oncontextmenu', contextMenu, false ); else window.addEventListener( 'contextmenu', contextMenu, false ); // On blur, activate current movable (don't put it to front) window.addEventListener( 'blur', function( e ) { // Refresh the tray PollTray(); let viewObject = null; if( document.activeElement ) { viewObject = document.activeElement; } if( window.currentMovable ) { if( window.currentMovable.content == viewObject.view ) { _WindowToFront( window.currentMovable ); } else { _ActivateWindowOnly( window.currentMovable ); } } } ); if( window.attachEvent ) window.attachEvent( 'onresize', movableListener, false ); else window.addEventListener( 'resize', movableListener, false ); document.oncontextmenu = contextMenu; } var __titlebarg = false; function GetTitleBarG () { if ( typeof ( window.currentScreen ) != 'undefined' ) return window.currentScreen.getElementsByTagName ( 'div' )[0]; if ( !__titlebarg ) __titlebarg = ge ( 'Modules' ) ? ge ( 'Modules' ) : ( ge ( 'TitleBar' ) ? ge ( 'TitleBar' ) : false ); return __titlebarg; } function ClearMenuItemStyling( par ) { let lis = par.getElementsByTagName( 'li' ); for( let a = 0; a < lis.length; a++ ) { let sp = lis[a].getElementsByTagName( 'span' ); if( sp && sp[0] ) sp[0].className = ''; } } function FocusOnNothing() { if( !window.currentMovable ) return; if( !isMobile ) _DeactivateWindows(); // Put focus somewhere else than where it is now.. // Blur like hell! :) for( let a in movableWindows ) { if( movableWindows[a].windowObject ) { movableWindows[a].windowObject.sendMessage( { command: 'blur' } ); } } let eles = document.getElementsByTagName( '*' ); for( let a = 0; a < eles.length; a++ ) { eles[a].blur(); } // Why not focus on window!? window.focus(); } // Alert box, blocking a window function AlertBox( title, desc, buttons, win ) { // New view let w = new View( { title: title, width: 380, height: 200, resize: false, dialog: true } ); for( let a in buttons ) buttonml += ''; let ml = '
' + desc + '
' + buttonml + '
'; w.setContent( ml ); // Collect added dom elements let eles = w.content.getElementsByTagName( 'button' ); let dbuttons = []; for( let a = 0; a < eles.length; a++ ) { // TODO: Make safer! if( eles[a].parentNode.className == 'DialogButtons' && eles[a].parentNode.parentNode == 'Dialog' ) { dbuttons.push( eles[a] ); } } // Set onclick actions for( let c = 0; c < buttons.length; c++ ) { dbuttons[c].view = w; dbuttons[c].onclick = buttons.onclick; } // Apply blocker if needed if( win ) w.setBlocker( win ); } // Is it a shared app? function IsSharedApp() { return GetDOMHead().getAttribute( 'sharedapp' ); } function GetDOMHead() { if( !window._domHead ) { window._domHead = document.getElementsByTagName( 'head' )[0]; } return window._domHead; } // COLOR PROCESSOR IS NOT USED AND CAN BE DELETED! ----------------------------- // Setup a color processor _colorProcessor = false; function LockColorProcessor() { if( !_colorProcessor ) { // Make it and get it out of the way _colorProcessor = document.createElement( 'canvas' ); _colorProcessor.style.width = 320; _colorProcessor.style.height = 200; //_colorProcessor.style.visibility = 'hidden'; _colorProcessor.style.pointerEvents = 'none'; //_colorProcessor.style.top = '-320px'; _colorProcessor.style.position = 'absolute'; document.body.appendChild( _colorProcessor ); _colorProcessor.ctx = _colorProcessor.getContext( '2d' ); } if( _colorProcessor.lock ) return false; _colorProcessor.lock = true; return _colorProcessor; } function UnlockColorProcessor() { _colorProcessor.lock = false; } // Take an image and look for average color function FindImageColorProduct( img ) { let c = LockColorProcessor(); if( c ) { c.ctx.drawImage( img, 0, 0, 320, 200 ); let imgd = c.ctx.getImageData( 0, 0, 320, 200 ); let average = { r: 0, g: 0, b: 0 }; let steps = 8; let increments = steps << 2; // * 4 for( let i = 0; i < imgd.data.length; i += increments ) { average.r += imgd.data[ i ]; average.g += imgd.data[ i + 1 ]; average.b += imgd.data[ i + 2 ]; } average.r /= imgd.data.length >> 3; average.g /= imgd.data.length >> 3; // step == 8 (>>3) average.b /= imgd.data.length >> 3; average.r = Math.floor( average.r ); average.g = Math.floor( average.g ); average.b = Math.floor( average.b ); UnlockColorProcessor(); return average; } return false; } // END COLOR PROCESSOR --------------------------------------------------------- // End disposable menu for mobile use ------------------------------------------ /* Bubble emitter for tutorials and help ------------------------------------ */ /* This is bubbles for showing localized help on Workspace scoped elements. */ /* TODO: Support API scoped elements... */ function CreateHelpBubble( element, text, uniqueid, rules ) { if( isTouchDevice() ) return; if( !element ) return; if( !text ) text = ''; if( element.helpBubble ) { element.helpBubble.close(); } let helpBubble = { destroy: function() { if( helpBubble ) { helpBubble.close(); helpBubble.widget = null; } if( element && element.parentNode ) { element.removeEventListener( 'mouseover', element.helpBubble.overListener ); element.removeEventListener( 'mouseover', element.helpBubble.outListener ); } }, close: function() { if( helpBubble.widget ) { helpBubble.widget.close(); helpBubble.widget = null; } }, overListener: function( e ) { if( !element || !element.parentNode ) { helpBubble.destroy(); return; } // Check parent let positionClass = ''; let p = e.target ? e.target.parentNode : false; // Also check parent if( p ) { for( var a = 0; a < 2; a++ ) { let found = false; if( p.getAttribute( 'position' ) ) { switch( p.getAttribute( 'position' ) ) { case 'right_center': case 'right_top': case 'right_bottom': positionClass = 'Right'; break; case 'left_center': case 'left_top': case 'left_bottom': positionClass = 'Left'; break; case 'bottom_left': case 'bottom_center': case 'bottom_right': positionClass = 'Bottom'; break; case 'top_left': case 'top_center': case 'top_right': positionClass = 'Top'; break; default: break; } } if( !!positionClass ) break; p = p.parentNode; } } let mx = windowMouseX; let my = windowMouseY; let mt = GetElementTop( element ) - ( 50 + 10 ); let c = document.createElement( 'canvas' ); let d = c.getContext( '2d' ); d.font = '1em default'; // Dynamic text if( rules && rules.getText ) text = rules.getText(); if( !text ) return; let mw = 0; let mh = 0; if( text.indexOf( "\n" ) > 0 ) { text = text.split( "\n" ); for( let a = 0; a < text.length; a++ ) { if( d.measureText( text[ a ] ).width > mw ) mw = d.measureText( text[ a ] ).width; mh += d.measureText( text[ a ] ).height; } text = text.join ( "
" ); } let textWidth = mw ? mw : d.measureText( text ).width; // Normal operation mx = GetElementLeft( element ) + ( GetElementWidth( element ) >> 1 ) - ( textWidth >> 1 ) - 30; // Check element position if( positionClass ) { if( positionClass == 'Right' ) { mt = GetElementTop( element ) + 5; mx = GetElementLeft( element ) - Math.floor( textWidth + 90 ); posset = true; } else if( positionClass == 'Left' ) { mt = GetElementTop( element ) + 5; mx = GetElementLeft( element ) + GetElementWidth( element.parentNode ) + 10; posset = true; } else if( positionClass == 'Top' ) { mt = GetElementTop( element.parentNode ) + GetElementHeight( element.parentNode ) + 25; } } // Nudge if( rules ) { if( !!rules.offsetTop ) { mt -= rules.offsetTop; } if( !!rules.offsetLeft ) { mx -= rules.offsetLeft; } if( !!rules.getOffsetTop ) { mt -= rules.getOffsetTop(); } if( !!rules.getOffsetLeft ) { mx -= rules.getOffsetLeft(); } } if( mt < 0 ) mt = 0; let lfeeds = text.split( '
' ).length; let v = new Widget( { left: mx, top: mt, width: 200, height: ( lfeeds > 1 ? lfeeds * 20 : 40 ), 'border-radius': 20, above: true, fadeOut: true, fadeIn: true } ); d.innerHTML = text; v.setFlag( 'width', textWidth + 60 ); v.setFlag( 'left', mx ); v.setFlag( 'top', mt ); v.setContent( '
' + text + '
' ); v.dom.addEventListener( 'mouseout', element.helpBubble.outListener ); v.dom.classList.add( 'HelpBubble' ); // Remove all position classes and add right one let pcl = [ 'Left', 'Top', 'Right', 'Bottom' ]; if( rules && rules.positions ) pcl = rules.positions; for( let z = 0; z < pcl.length; z++ ) if( pcl[ a ] != positionClass ) v.dom.classList.remove( pcl[ a ] ); if( v.dom.className.length && positionClass ) v.dom.classList.add( positionClass ); else if( positionClass ) v.dom.className = positionClass; let show = true; if( pcl ) { let f = false; for( let a in pcl ) { if( positionClass == pcl[ a ] ) { f = true; break; } } show = f; } if( show ) v.show(); element.helpBubble.widget = v; }, outListener: function( e ) { if( helpBubble && helpBubble.widget ) helpBubble.widget.hide( function(){ if( helpBubble && helpBubble.widget ) helpBubble.widget.close(); } ); } }; element.helpBubble = helpBubble; element.addEventListener( 'mouseover', element.helpBubble.overListener ); element.addEventListener( 'mouseout', element.helpBubble.outListener ); } /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ /* Make movable box --------------------------------------------------------- */ Friend = window.Friend || {}; // Friend main namespace Friend.io = Friend.io || {}; // Input/output namespace Friend.GUI = Friend.GUI || {}; // GUI namespace Friend.GUI.view = {}; // View window namespace // Lets remember values Friend.GUI.responsiveViewPage = 0; // Current view page mobile Friend.GUI.responsiveViewPageCount = 0; // View page count mobile Friend.GUI.view.windowStorage = []; Friend.GUI.view.viewHistory = []; // History of opened views Friend.GUI.view.windowStorageLoaded = false; Friend.GUI.view.movableViewIdSeed = 0; var _viewType = 'iframe'; //window.friendBook ? 'webview' : 'iframe'; // Get stored data by window id function GetWindowStorage( id ) { if( !id ) { return Friend.GUI.view.windowStorage; } else { if( typeof( Friend.GUI.view.windowStorage[ id ] ) != 'undefined' ) return Friend.GUI.view.windowStorage[ id ]; } return {}; } // Set window data by id function SetWindowStorage( id, data ) { Friend.GUI.view.windowStorage[ id ] = data; } // Get a window by id function GetWindowById( id ) { for( var a in movableWindows ) { if( !movableWindows[ a ].windowObject ) continue; if( movableWindows[ a ].windowObject.viewId == id ) return movableWindows[ a ]; } return false; } // Save window storage to Friend Core function SaveWindowStorage( callback ) { let m = new Module( 'system' ); m.execute( 'setsetting', { setting: 'windowstorage', data: JSON.stringify( jsonSafeObject( Friend.GUI.view.windowStorage ) ) } ); if( callback ) { setTimeout( function() { callback(); }, 500 ); } } // Load window storage from Friend Core function LoadWindowStorage() { if( !Friend.GUI.view.windowStorageLoaded ) { let m = new Module( 'system' ); m.onExecuted = function( e, d ) { if( e == 'ok' ) { try { let dob = JSON.parse( d ); Friend.GUI.view.windowStorage = dob.windowstorage ? dob.windowstorage : []; if( typeof( Friend.GUI.view.windowStorage ) != 'object' ) Friend.GUI.view.windowStorage = []; else { for( var a in Friend.GUI.view.windowStorage ) { if( typeof( Friend.GUI.view.windowStorage[a] ) == 'string' ) { Friend.GUI.view.windowStorage[a] = {}; } } } } catch( e ) { } } } m.execute( 'getsetting', { setting: 'windowstorage' } ); Friend.GUI.view.windowStorageLoaded = true; } } // Find a movable window by title string function FindWindow( titleStr ) { let divs = document.getElementsByTagName ( 'div' ); for( var a = 0; a < divs.length; a++ ) { if( divs[a].className.indexOf( ' View' ) >= 0 ) { if( divs[a].childNodes.length && divs[a].childNodes[0].childNodes[0].childNodes[0].innerHTML == titleStr ) { let divz = divs[a].getElementsByTagName( 'div' ); let cnt = divs[a]; for( var za = 0; za < divz.length; za++ ) { if( divz[za].className == 'Content' ) { cnt = divz[za]; break; } } return cnt; } } } return false; } // Return object with remembered window dimensions function RememberWindowDimensions( div ) { if( isMobile ) return; let wp = GetWindowStorage( div.uniqueId ); if ( wp ) { return wp; } return false; } // Sets innerhtml no a window content and runs any javascript function SetWindowContent( win, data ) { if( !win ) return; if( win.content ) win = win.content; win.innerHTML = Friend.GUI.view.cleanHTMLData( data ); } // Refresh the window and add/remove features function RefreshWindow( div, noresize ) { // We need the content element for the flags if( div.content ) div = div.content; if( div.flags ) { if( div.flags.hidden && div.flags.hidden === true ) return; if( div.flags.invisible && div.flags.invisible === true ) return; let flags = div.flags; let winObj = div.parentNode; if( flags.resize == false ) { winObj.resize.style.display = 'none'; winObj.zoom.style.display = 'none'; div.style.overflow = 'hidden'; } else { winObj.resize.style.display = ''; winObj.zoom.style.display = ''; div.style.overflow = ''; } if( flags.close == false ) { winObj.close.style.display = 'none'; winObj.close.parentNode.setAttribute( 'close', 'false' ); } else { winObj.close.style.display = ''; } } if( !noresize ) { if( d = RememberWindowDimensions( div ) ) { ResizeWindow( div, d.width, d.height ); } else { ResizeWindow( div ); } } } // Set window title on a movable window function SetWindowTitle( div, titleStr ) { if( div.className == 'Content' ) div = div.parentNode; if( !div || !div.getElementsByTagName ) return; let divz = div.getElementsByTagName ( 'div' ); let title = false; for( var a = 0; a < divz.length; a++ ) { if( divz[a].classList.contains( 'Title' ) ) title = divz[a]; } if ( !title ) return false; title.getElementsByTagName ( 'span' )[0].innerHTML = titleStr; div.titleString = titleStr; // Update window if( Friend.windowBaseStringRules && Friend.windowBaseStringRules == 'replace' ) document.title = Friend.windowBaseString; else document.title = titleStr + ' - ' + Friend.windowBaseString; // Viewtitle (for other uses than title) let vTitle = titleStr; if( vTitle.indexOf( ' - ' ) > 0 ) { vTitle = vTitle.split( ' - ' ); vTitle[0] = ''; vTitle = vTitle.join( '' ); } div.viewTitle.innerHTML = vTitle; // Support dashboard let dl = document.querySelector( '.DashboardLabel' ); if( dl ) { dl.innerHTML = vTitle; } // Also check tasks let baseElement = GetTaskbarElement(); if( !baseElement ) return; if( baseElement.tasks ) { for( var a in baseElement.tasks ) { if( div.viewId == baseElement.tasks[ a ].viewId ) { baseElement.tasks[a].dom.innerHTML = titleStr; break; } } } } // Update window content size function UpdateWindowContentSize( div ) { // set the content width if( div.content ) { div.content.style.width = 'calc(100%-' + div.marginHoriz + 'px)'; div.content.style.height = 'calc(100%-' + div.marginVert + 'px)'; } } // Like it says! function ResizeWindow( div, wi, he, mode, depth ) { if( window.isMobile ) return; if( !div ) return; if( !depth ) depth = 0; else if( depth > 4 ) return; if( !mode ) mode = false; if( !wi || wi == 'undefined' || wi == undefined ) { wi = div.windowObject.getFlag( 'width' ); } if( !he || he == 'undefined' || he == undefined ) { he = div.windowObject.getFlag( 'height' ); } // Find window div if ( !div.content ) { while( div && ( !div.classList || ( div.classList && !div.classList.contains( 'View' ) ) ) && div != document.body ) div = div.parentNode; } // If it isn't found, escape! if ( div == document.body ) return; let margins = GetViewDisplayMargins( div ); // Extra width height to calculate with let frameWidth = 0; let frameHeight = 0; let isWorkspaceScreen = div.windowObject.getFlag( 'screen' ) == Workspace.screen; if( div.content && div.windowObject.flags ) { let flags = div.windowObject.flags; let ele = div.windowObject.content.parentNode; // When getting width and height from flags, and not in borderless // mode, check also borders around the content and add those to get // the correct width and height // TODO: leftbar and rightbar does not exist, remove it /*frameWidth = ele.rightbar.offsetWidth + ele.leftbar.offsetWidth; console.log( 'Checking frame ' + frameWidth + ' (' + ele.rightbar.offsetWidth + ', ' + ele.leftbar.offsetWidth + ')' ); console.log( ele.rightbar, ele.leftbar );*/ if( !wi ) { wi = parseInt( flags.width ); if( !flags.borderless && !isNaN( wi ) ) { wi += frameWidth; } } frameHeight = ele.titleBar.offsetHeight; // TODO: Bottom bar does not exist, remove it /*if( isWorkspaceScreen ) { frameHeight += ele.bottombar.offsetHeight; console.log( 'And the bottombar: ' + frameHeight + ' (' + ele.bottombar.offsetHeight + ')' ); }*/ if( !he ) { he = flags.height; if( !flags.borderless && !isNaN( he ) ) { he += frameHeight; } } // Window gauge // TODO: Volume gauge does not exist, remove it /* if( div.windowObject.flags.volume && div.volumeGauge ) { div.content.style.left = GetElementWidth( div.volumeGauge.parentNode ) + 'px'; }*/ } let cl = document.body.classList.contains( 'Inside' ); let maxVWidt, maxVHeig; if( Workspace.mode != 'vr' ) { if( div.windowObject.flags.screen ) { maxVWidt = cl ? div.windowObject.flags.screen.getMaxViewWidth() : GetWindowWidth(); maxVHeig = cl ? div.windowObject.flags.screen.getMaxViewHeight() : GetWindowHeight(); } else { maxVWidt = window.innerWidth; maxVHeig = window.innerHeight; } } else { maxVWidt = window.innerWidth; maxVHeig = window.innerHeight; } let maximized = div.getAttribute( 'maximized' ) == 'true' || div.windowObject.flags.maximized; if ( !wi || wi == 'false' ) wi = div.content ? div.content.offsetWidth : div.offsetWidth; if ( !he || he == 'false' ) he = div.content ? div.content.offsetHeight : div.offsetHeight; wi = parseInt( wi ); he = parseInt( he ); let divs = div.getElementsByTagName ( 'div' ); let cnt = false; for( let a = 0; a < divs.length; a++ ) { if( !cnt && divs[a].classList && divs[a].classList.contains( 'Content' ) ) { cnt = divs[a]; break; } } // TODO: Let a central resize code handle this (this one?) // Maximum dimensions let pheight = div.parentNode ? div.parentNode.offsetHeight : GetWindowHeight(); // TODO: Support parent windows let maxWidth = maxVWidt; let maxHeight = maxVHeig; //console.log( '0) Max height first ' + maxVHeig + ' ' + margins.top + ' ' + margins.bottom ); // Add margins maxWidth -= margins.left + margins.right; maxHeight -= margins.top + margins.bottom; //console.log( '1) Here is wihe: ' + wi + 'x' + he ); if( div.windowObject && maximized ) { wi = maxWidth; he = maxHeight; } // We will not go past max height else { if( he > maxHeight ) he = maxHeight; } //console.log( 'Max height: ' + maxHeight ); //console.log( '2) Here is wihe: ' + wi + 'x' + he ); // Make sure we don't go past screen limits let l = t = 0; if( div.parentNode ) { l = parseInt( div.style.left ); t = parseInt( div.style.top ); if( isNaN( l ) ) l = 0; if( isNaN( t ) ) t = 0; //console.log( 'Style top: ' + t + ' (' + div.style.top + ')' ); } else { l = div.windowObject.flags.left; t = div.windowObject.flags.top; if( !l ) l = isWorkspaceScreen ? div.windowObject.workspace * window.innerWidth : 0; if( !t ) t = 0; } // Maximized if( div.windowObject && maximized ) { l = l = isWorkspaceScreen ? ( div.windowObject.workspace * window.innerWidth ) : 0; t = 0; } //console.log( '3) Here is wihe: ' + wi + 'x' + he ); // Skew for calculating beyond workspace 1 let skewx = div.windowObject.workspace * window.innerWidth; if( !isWorkspaceScreen ) skewx = 0; if( l + wi > maxWidth + skewx + margins.left ) { wi = maxWidth + skewx - l + margins.left; } if( t + he > maxHeight + margins.top ) { he = maxHeight - t + margins.top; } // Done limits //console.log( '2) Here is wihe: ' + wi + 'x' + he ); // Flag constraints let fminw = div.windowObject.flags['min-width'] ? div.windowObject.flags['min-width'] : 0; let fminh = div.windowObject.flags['min-height'] ? div.windowObject.flags['min-height'] : 0; let fmaxw = div.windowObject.flags['max-width'] ? div.windowObject.flags['max-width'] : 999999; let fmaxh = div.windowObject.flags['max-height'] ? div.windowObject.flags['max-height'] : 999999; fminw += frameWidth; fminh += frameHeight; // Constrain if( fmaxh < fminh ) fmaxh = fminh; if( fmaxw < fminw ) fmaxw = fminw; if( fminh > fmaxh ) fminh = fmaxh; if( fminw > fmaxw ) fminw = fmaxw; if( wi < fminw ) wi = fminw; else if( wi >= fmaxw ) wi = fmaxw; if( he < fminh ) he = fminh; else if( he >= fmaxh ) he = fmaxh; // Absolute minimum windows if( wi < 160 ) wi = 160; if( he < 60 ) he = 60; // Set the width and height div.style.width = wi + 'px'; div.style.height = he + 'px'; div.marginHoriz = FUI_WINDOW_MARGIN; div.marginVert = 0; // Constrain ConstrainWindow( div, null, null, depth + 1 ); // Set the content width UpdateWindowContentSize( div ); // Toggle scroll flag function checkScrolling( div ) { if( !div.content && div.parentNode && div.parentNode.content ) div = div.parentNode; if( !div.content ) return; if( !div.content.firstChild ) return; if( div.content.offsetHeight < div.content.firstChild.scrollHeight ) { div.classList.add( 'Scrolling' ); } else { div.classList.remove( 'Scrolling' ); } }; // Check resize event if( div.content.events && div.content.events.resize ) { for( var a = 0; a < div.content.events.resize.length; a++ ) { div.content.events.resize[a]( function(){ checkScrolling( div ) } ); } } // Check now checkScrolling( div ); // refresh // TODO: Is this ever used? Pls check if( div.refreshWindow ) { div.refreshWindow(); } // Recalculate toggle group // It will pop out of view if it's overlapped by other buttons if( div.content.directoryview && !document.body.classList.contains( 'ThemeEngine' ) ) { let t = div.getElementsByClassName( 'ToggleGroup' ); let r = div.getElementsByClassName( 'Reload' ); let m = div.getElementsByClassName( 'Makedir' ); if( t.length > 0 && r.length > 0 ) { let hideCondition = t[0].offsetLeft < r[0].offsetLeft + r[0].offsetWidth || ( m && m[0] && t[0].offsetLeft + t[0].offsetWidth > m[0].offsetLeft ); if( hideCondition ) { t[0].style.visibility = 'hidden'; t[0].style.pointerEvents = 'none'; } else { t[0].style.visibility = 'visible'; t[0].style.pointerEvents = 'all'; } } } let flagDia = div.windowObject.getFlag( 'dialog' ); if( flagDia ) { setTimeout( function() { div.windowObject.setFlag( 'dialog', flagDia ); }, 50 ); } } // Get the statusbar height function GetStatusbarHeight( screen ) { if( screen && screen.div && screen == Workspace.screen ) { if( globalConfig.viewList == 'dockedlist' || globalConfig.viewList == 'docked' ) { if( Workspace.mainDock ) { if( Workspace.mainDock.dom.classList.contains( 'Horizontal' ) ) { return Workspace.mainDock.dom.offsetHeight; } } } // Cache? if( typeof( screen.statusBarHeight ) != 'undefined' && screen.statusBarHeight != null ) return screen.statusBarHeight; // Calculate let eles = screen.div.getElementsByTagName( 'div' ); screen.statusBarHeight = 0; for( var a = 0; a < eles.length; a++ ) { if( eles[a].id == 'Statusbar' ) { screen.statusBarHeight = eles[a].offsetHeight; break; } } return screen.statusBarHeight; } return 0; } // Pop a window up! function PopoutWindow( wind, e ) { let windowObject = wind.windowObject; let ifr = wind.getElementsByTagName( 'iframe' )[0]; let v = window.open( '', '', 'width=900,height=900,status=no,topbar=no' ); let styl = document.createElement( 'style' ); styl.innerHTML = 'iframe{position:absolute;top:0;left:0;width:100%;height:100%;margin:0;border:0}'; v.document.body.appendChild( ifr ); v.document.body.appendChild( styl ); wind.parentNode.parentNode.removeChild( wind.parentNode ); windowObject.setFlag( 'invisible', true ); v.document.title = windowObject.flags.title; setTimeout( function() { let ifr = v.document.getElementsByTagName( 'iframe' )[0]; ifr.contentWindow.document.body.setAttribute( 'style', '' ); ifr.contentWindow.document.body.classList.remove( 'Loading' ); ifr.contentWindow.Application.run(); }, 250 ); } // Make sure we're not overlapping all of the time var _cascadeValue = 0; function CascadeWindowPosition( obj ) { if( !Workspace.screen ) return; if( !isMobile ) { obj.dom.style.top = _cascadeValue + obj.y + 'px'; obj.dom.style.left = _cascadeValue + obj.x + 'px'; _cascadeValue += 20; if( _cascadeValue + obj.x + obj.w > obj.maxw || _cascadeValue + obj.y + obj.h > obj.maxh ) _cascadeValue = 0; } } // Returns the display margins, taking into consideration the screen, dock etc function GetViewDisplayMargins( div ) { let wo = div.windowObject; let sc = wo ? wo.getFlag( 'screen' ) : null; let margins = { top: 0, left: 0, right: 0, bottom: 0 }; if( !sc || ( sc && sc != Workspace.screen ) ) { if( sc && sc.div.screenTitle ) { margins.top += sc.div.screenTitle.offsetHeight; margins.bottom -= margins.top; // TODO: <- this is ugly! to maximize height on screens.. } return margins; } let dockPosition = null; if( Workspace.mainDock ) { let dockDom = Workspace.mainDock.dom; if( !parseInt( dockDom.style.height ) ) return margins; if( dockDom.classList.contains( 'Top' ) ) dockPosition = 'Top'; else if( dockDom.classList.contains( 'Left' ) ) dockPosition = 'Left'; else if( dockDom.classList.contains( 'Right' ) ) dockPosition = 'Right'; else if( dockDom.classList.contains( 'Bottom' ) ) dockPosition = 'Bottom'; switch( dockPosition ) { case 'Top': margins.top += parseInt( dockDom.style.height ); break; case 'Left': margins.left += parseInt( dockDom.style.width ); break; case 'Right': margins.right += parseInt( dockDom.style.width ); break; case 'Bottom': margins.bottom += parseInt( dockDom.style.height ); break; } } if( dockPosition != 'Bottom' && ge( 'Tray' ) && ge( 'Taskbar' ).offsetHeight ) margins.bottom += parseInt( ge( 'Tray' ).style.height ); let inf = GetThemeInfo( 'ScreenContentMargins' ); if( inf && inf.top ) margins.top += parseInt( inf.top ); return margins; } function ConstrainWindows() { if( !window.movableWindows ) return; for( var a in movableWindows ) ConstrainWindow( movableWindows[ a ] ); } // Constrain position (optionally providing left and top) function ConstrainWindow( div, l, t, depth, caller ) { if( window.isMobile ) return; if( !depth ) depth = 0; else if( depth > 4 ) return; div.setAttribute( 'moving', 'moving' ); setTimeout( function() { div.removeAttribute( 'moving' ); }, 250 ); let margins = GetViewDisplayMargins( div ); // Track caller if( !caller ) caller = div; // l and t needs to be numbers if( isNaN( l ) ) l = parseInt( l ); if( isNaN( t ) ) t = parseInt( t ); // Get some information through flags let sc = null; let flagMaxWidth = flagMaxHeight = 0; if( div.windowObject ) { sc = div.windowObject.getFlag( 'screen' ); flagMaxWidth = parseInt( div.windowObject.getFlag( 'max-width' ) ); flagMaxHeight = parseInt( div.windowObject.getFlag( 'max-height' ) ); } if( !sc ) sc = Workspace.screen; let screenMaxWidth = sc ? sc.getMaxViewWidth() : document.body.offsetWidth; let screenMaxHeight = sc ? sc.getMaxViewHeight() : document.body.offsetHeight; // If the view is inside another container (special case) let specialNesting = div.content ? div : div.parentNode; if( div.viewContainer && div.viewContainer.parentNode ) { specialNesting = !div.viewContainer.parentNode.classList.contains( 'ScreenContent' ); } else specialNesting = false; let pn = div.parentWindow; let win = pn ? pn.getWindowElement() : div; let cn = win.content ? win.content : win; // Get maximum width / height let maxWidth = pn ? pn.offsetWidth : ( specialNesting ? div.viewContainer.parentNode.offsetWidth : screenMaxWidth ); let maxHeight = pn ? pn.offsetHeight : ( specialNesting ? div.viewContainer.parentNode.offsetHeight : screenMaxHeight ); // Subtract margins maxWidth -= margins.left + margins.right; maxHeight -= margins.top + margins.bottom; // Max constraint if( !flagMaxWidth || flagMaxWidth > maxWidth ) { div.style.maxWidth = maxWidth + 'px'; } if( !flagMaxHeight || flagMaxHeight > maxHeight ) { div.style.maxHeight = maxHeight + 'px'; div.parentNode.style.maxHeight = maxHeight + 'px'; } let mt = margins.top; let ml = margins.left; // min left let mw = maxWidth; let mh = maxHeight; let ww = div.offsetWidth; let wh = div.offsetHeight; if( ww <= 0 ) ww = div.content.windowObject.getFlag( 'width' ); if( wh <= 0 ) wh = div.content.windowObject.getFlag( 'height' ); let mvw = screenMaxWidth; let mvh = screenMaxHeight; // TODO: See if we can move this dock dimension stuff inside getMax..() if( Workspace.mainDock ) { if( Workspace.mainDock.dom.classList.contains( 'Vertical' ) ) { mvw -= Workspace.mainDock.dom.offsetWidth; } else { mvh -= Workspace.mainDock.dom.offsetHeight; } } // For window cascading, start comparing (isNaN means not set) let doCascade = false; if( !l ) { l = parseInt( div.style.left ); if( isNaN( l ) ) { doCascade = true; l = ( mvw >> 1 ) - ( ww >> 1 ); } } if( !t ) { t = parseInt( div.style.top ); if( isNaN( t ) ) { doCascade = true; t = ( mvh >> 1 ) - ( wh >> 1 ); } } // Add workspace when opening this way if( !div.content.windowObject.flags.screen || div.content.windowObject.flags.screen == Workspace.screen ) { if( typeof( currentScreen ) != 'undefined' && globalConfig.workspaceCurrent >= 0 ) { if( div.windowObject.workspace < 0 ) div.windowObject.workspace = globalConfig.workspaceCurrent; } else if( typeof( div.windowObject ) != 'undefined' ) { if( !div.windowObject.workspace ) div.windowObject.workspace = 0; } } // When cascading, the view is moved if( doCascade ) { return CascadeWindowPosition( { x: l, y: t, w: ww, h: wh, maxw: mw, maxh: mh, dom: div } ); } // Final constraint if( l + ww > ml + mw ) l = ( ml + mw ) - ww; if( l < ml ) l = ml; if( t + wh > mt + mh ) t = ( mt + mh ) - wh; if( t < mt ) t = mt; // Set previous position div.prevPositionLeft = div.windowObject.getFlag( 'left' ); div.prevPositionTop = div.windowObject.getFlag( 'top' ); // Set the actual position div.windowObject.setFlag( 'left', l ); div.windowObject.setFlag( 'top', t ); // Only test if we're currently moving an open window // Skip when we're in snapping mode.. if( window.currentMovable && !currentMovable.snapping ) { // Check attached (snapped) windows if( div.attached ) { for( var a = 0; a < div.attached.length; a++ ) { // Skip if we're the current movable or caller if( div.attached[a] == caller || div.attached[a] == currentMovable ) continue; div.attached[a].setAttribute( 'moving', 'moving' ); ConstrainWindow( div.attached[ a ], div.offsetLeft - div.attached[a].snapCoords.x, div.offsetTop - div.attached[a].snapCoords.y, depth + 1, caller ); } } // Check attached window if we're attached to if( div == caller && div.snapObject && div.snapObject != caller ) { let ll = false, tt = false; if( div.snap == 'up' ) { ll = l + div.snapCoords.x; tt = t - div.snapObject.offsetHeight; } // TODO: Make this work else if( div.snap == 'down' ) { ll = l + div.snapCoords.x; tt = t + div.offsetHeight; } else if( div.snap == 'right' ) { ll = l + div.offsetWidth; tt = t + div.snapCoords.y; } else if( div.snap == 'left' ) { ll = l - div.snapObject.offsetWidth; tt = t + div.snapCoords.y; } if( ll !== false && tt !== false ) ConstrainWindow( div.snapObject, ll, tt, depth + 1, caller ); } } } // Make window autoresize function AutoResizeWindow( div ) { if( !div ) return; if( !div.content ) { while( !div.classList.contains( 'View' ) && div != document.body ) div = div.parentNode; } if( div == document.body ) return; let divs = div.getElementsByTagName( 'div' ); let cnt = false; let title = false; for( var a = 0; a < divs.length; a++ ) { if( !divs[a].classList ) continue; if( divs[a].classList.contains( 'Content' ) ) cnt = divs[a]; if( divs[a].classList.contains( 'Title' ) ) title = divs[a]; } if ( !cnt ) return false; div.autoResize = true; let h = 0; let eles = cnt.getElementsByTagName( '*' ); for( var b = 0; b < eles.length; b++ ) { if( eles[b].parentNode != cnt ) continue; h += eles[b].offsetHeight; } ResizeWindow( div, false, h ); } // Sets the screen by window element function SetScreenByWindowElement( div ) { // Set screen if( !div ) return false; let d = div; while( d != document.body && d.parentNode ) { d = d.parentNode; if( d.className && ( d.className == 'Screen' || d.className.indexOf( ' Screen' ) >= 0 || d.className.indexOf( 'Screen ' ) == 0 ) ) { return ( window.currentScreen = d ); } } return false; } // Just like _ActivateWindow, only without doing anything but activating function _ActivateWindowOnly( div, e ) { if( div.windowObject && div.windowObject.getFlag( 'invisible' ) == true ) return; if( Workspace.contextMenuShowing && Workspace.contextMenuShowing.shown ) { return; } // Blocker if( !isMobile && div.content && div.content.blocker ) { _ActivateWindow( div.content.blocker.getWindowElement().parentNode, false ); return; } // Dialogs here are not activated if( window.Workspace && Workspace.dashboard && div.windowObject && ( div.windowObject.flags[ 'dialog' ] || div.windowObject.flags[ 'standard-dialog' ] ) ) { return _ActivateDialogWindow( div, e ); } // Don't select other fields if( !div.classList.contains( 'Active' ) ) { let ae = document.activeElement; if( ae.parentNode && ae.parentNode.parentNode != div ) { FocusOnNothing(); } } // Special case let delayedDeactivation = true; // Note we're having a current movable currentMovable = div; // we use this one to calculate the max-height of the active window once its switched.... let newOffsetY = 0; for( let a in movableWindows ) { let m = movableWindows[a]; // No div selected or not the div we're looking for - do inactive! if( !div || m != div ) { if( isMobile ) { // Support delayed deactivation ( function( dd ) { function deal() { if( currentMovable && currentMovable.classList && ( currentMovable.parentNode.classList.contains( 'Redrawing' ) || currentMovable.parentNode.classList.contains( 'DoneActivating' ) || currentMovable.parentNode.classList.contains( 'Activated' ) ) ) { return setTimeout( function(){ deal() }, 300 ); } if( dd && dd.parentNode ) { dd.parentNode.classList.remove( 'DelayedDeactivation' ); _DeactivateWindow( dd ); } } if( delayedDeactivation && div.applicationId == dd.applicationId && dd.parentNode ) { dd.parentNode.classList.add( 'DelayedDeactivation' ); setTimeout( function(){ deal() }, 300 ); } else deal(); } )( m ); } else if( m && m.classList.contains( 'Active' ) ) { _DeactivateWindow( m ); } } // This is the div we are looking for! else if( m == div ) { // Record active window for this workspace if( globalConfig.workspaceCurrent == div.workspace ) setVirtualWorkspaceInformation( div.workspace, 'activeWindow', div ); if( div.content ) window.regionWindow = div.content; else window.regionWindow = div; if( div.content ) window.currentMovable = div; else window.currentMovable = div; m.viewContainer.classList.remove( 'OnWorkspace' ); m.classList.add( 'Active' ); m.viewContainer.classList.add( 'Active' ); if( m.windowObject.flags.singletask ) { document.body.classList.add( 'Singletask' ); } else { document.body.classList.remove( 'Singletask' ); } // Set active window if( m.windowObject.getFlag( 'windowActive' ) ) { m.style.backgroundColor = m.windowObject.getFlag( 'windowActive' ); m.titleDiv.style.backgroundColor = m.style.backgroundColor; } let app = _getAppByAppId( div.applicationId ); // Extra force! if( isMobile ) { m.viewContainer.style.display = ''; m.viewContainer.style.top = '0px'; m.viewContainer.style.left = '0px'; m.viewContainer.style.width = '100%'; m.viewContainer.style.height = '100%'; if( window._getAppByAppId ) { if( app ) { if( m.windowObject != app.mainView ) { m.parentNode.setAttribute( 'childview', true ); } else { m.parentNode.removeAttribute( 'childview' ); } } } // Can't be minimized m.viewContainer.removeAttribute( 'minimized' ); m.minimized = false; } else { m.viewContainer.removeAttribute( 'minimized' ); m.minimized = false; } // Notify app if( app ) { app.sendMessage( { 'command': 'notify', 'method': 'setviewflag', 'flag': 'minimized', 'viewId': div.windowObject.viewId, 'value': false } ); } if( div.windowObject ) { if( !div.notifyActivated ) { let iftest = div.getElementsByTagName( _viewType ); let msg = { type: 'system', command: 'notify', method: 'activateview', viewId: div.windowObject.viewId }; if( iftest && iftest[0] ) { msg.applicationId = iftest[0].applicationId; msg.authId = iftest[0].authId; } div.notifyActivated = true; div.windowObject.sendMessage( msg ); } } CheckMaximizedView(); } } // Check window CheckScreenTitle(); } function _ActivateDialogWindow( div, e ) { if( !e ) e = window.event; // TODO: Also for touch! if( !div.windowObject.flags.dockable ) { document.body.classList.add( 'Dialog' ); currentMovable = div; if( e && e.button == 0 ) { if( !div.windowObject.applicationId && !div.classList.contains( 'IconWindow' ) ) { // If we have active windows that already shows, don't deactivate them for the dialog // TODO: Exception is for file views - but not file dialogs let exceptions = []; for( let a in movableWindows ) { if( movableWindows[ a ].classList.contains( 'Active' ) ) exceptions.push( movableWindows[ a ] ); } _DeactivateWindows( exceptions.length ? { exceptions: exceptions } : false ); currentMovable = div; } if( window.hideDashboard ) window.hideDashboard(); } else { if( window.hideDashboard ) window.hideDashboard(); } if( window.Workspace && window.Workspace.showQuickMenu ) { Workspace.showQuickMenu(); } } } // "Private" function to activate a window var _activationTarget = null; function _ActivateWindow( div, nopoll, e ) { let titl = div.windowObject ? div.windowObject.getFlag( 'title' ) : 'unknown'; if( div.windowObject && div.windowObject.getFlag( 'invisible' ) == true ) return; if( div.parentNode && div.parentNode.classList.contains( 'Closing' ) ) return; // Support dashboard let vTitle = div.windowObject.getFlag( 'title' ); if( vTitle ) { let dl = document.querySelector( '.DashboardLabel' ); if( dl ) { dl.innerHTML = vTitle; } } // Add div if it hasn't been added already if( div && div.windowObject && ( window.currentContext && ( typeof( window.currentContext ) == 'string' || div != window.currentContext[ 0 ] ) ) ) { window.currentContext = [ div, window.currentContext ]; } // Dialogs here are not activated if( window.Workspace && Workspace.dashboard && div.windowObject && ( div.windowObject.flags[ 'dialog' ] || div.windowObject.flags[ 'standard-dialog' ] || ( div.content && div.content.classList.contains( 'FileDialog' ) ) ) ) { return _ActivateDialogWindow( div ); } // Remove dialog flag only if it's not a dialog document.body.classList.remove( 'Dialog' ); // Check window color if( div.windowObject.getFlag( 'windowActive' ) ) { div.style.backgroundColor = div.windowObject.getFlag( 'windowActive' ); div.titleDiv.style.backgroundColor = div.style.backgroundColor; } if( Workspace.contextMenuShowing && Workspace.contextMenuShowing.shown ) { return; } if( !e ) e = window.event; // TODO: Also for touch! if( e && e.button == 0 ) { if( window.hideDashboard ) window.hideDashboard(); } if( window.Workspace && window.Workspace.showQuickMenu && !div.windowObject.getFlag( 'sidebarManaged' ) ) { Workspace.showQuickMenu(); } // Already activating if( div.parentNode.classList.contains( 'Activating' ) ) { if( !isMobile && globalConfig.focusMode == 'clicktofront' ) { _WindowToFront( div ); } return; } // And is already active if( div.classList.contains( 'Active' ) ) { if( !isMobile && globalConfig.focusMode == 'clicktofront' ) { _WindowToFront( div ); } return; } // Don't activate a window that is being removed if( div.classList.contains( 'Remove' ) ) return; // Remove flag from window and app div.windowObject.setFlag( 'opensilent', false ); if( div.applicationId && window._getAppByAppId ) { let app = _getAppByAppId( div.applicationId ); app.opensilent = false; } // Remove menu on calendar if( Workspace.calendarWidget ) Workspace.calendarWidget.hide(); if( isMobile && div.windowObject.lastActiveView && isMobile && div.windowObject.lastActiveView.parentNode ) { div.windowObject.lastActiveView.parentNode.classList.remove( 'OnWorkspace' ); _ActivateWindow( div.windowObject.lastActiveView ); div.windowObject.lastActiveView = null; return; } // Set currently displayed view on app if( isMobile ) { if( window._getAppByAppId ) { let app = _getAppByAppId( this.applicationId ); if( app ) { app.displayedView = div; } } } // Check if we're on the right workspace if( !div.windowObject.flags.screen || div.windowObject.flags.screen == Workspace.screen ) { if( div.windowObject.workspace != globalConfig.workspaceCurrent ) { if( window.Workspace && Workspace.switchWorkspace ) { Workspace.switchWorkspace( div.windowObject.workspace ); } } } // Don't reactivate if( div.classList.contains( 'Active' ) ) { if( window.currentMovable ) { if( window.currentMovable != div ) window.currentMovable = div; } if( globalConfig.focusMode == 'clicktofront' ) _WindowToFront( div ); return; } // Reactivate all iframes let fr = div.windowObject.content.getElementsByTagName( 'iframe' ); for( let a = 0; a < fr.length; a++ ) { if( fr[ a ].oldSandbox && typeof( fr[ a ].oldSandbox ) == 'string' ) fr[ a ].setAttribute( 'sandbox', fr[ a ].oldSandbox ); } // Reserve this div for activation _activationTarget = div; // Activate all iframes if( div.windowObject.content ) { let fr = div.windowObject.content.getElementsByTagName( 'iframe' ); for( var a = 0; a < fr.length; a++ ) { if( fr[ a ].oldSandbox ) { if( typeof friendApp == 'undefined' ) fr[ a ].setAttribute( 'sandbox', fr[ a ].oldSandbox ); } else { if( typeof friendApp == 'undefined' ) putSandboxFlags( fr[ a ], getSandboxFlags( div.windowObject, DEFAULT_SANDBOX_ATTRIBUTES ) ); } } } if( isMobile ) { window.focus(); if( Workspace.mainDock ) Workspace.mainDock.closeDesklet(); } // Blur previous window let changedActiveWindow = false; if( window.currentMovable && currentMovable.windowObject ) { if( currentMovable != div ) { changedActiveWindow = true; currentMovable.windowObject.sendMessage( { type: 'view', command: 'blur' } ); } } // Update window title if( Friend.windowBaseStringRules && Friend.windowBaseStringRules == 'replace' ) { document.title = Friend.windowBaseString; } else { document.title = div.windowObject.getFlag( 'title' ) + ' - ' + Friend.windowBaseString; } // If it has a window blocker, activate that instead if ( div && div.content && typeof ( div.content.blocker ) == 'object' ) { _activationTarget = null; // unreserve _ActivateWindow( div.content.blocker.getWindowElement ().parentNode, nopoll, e ); return; } // Calendar is slid if( Workspace && Workspace.widget ) Workspace.widget.slideUp(); if( globalConfig.focusMode == 'clicktofront' ) { _WindowToFront( div ); } // Tell window manager we are activating window let pn = div.parentNode; document.body.classList.add( 'Activating' ); pn.classList.add( 'Activating' ); setTimeout( function() { if( div ) { pn.classList.add( 'Activated' ); pn.classList.remove( 'Activating' ); setTimeout( function() { if( div ) { // Finally pn.classList.add( 'DoneActivating' ); pn.classList.remove( 'Activated' ); setTimeout( function() { if( div && div.parentNode ) { pn.classList.remove( 'DoneActivating' ); pn.classList.remove( 'Activating' ); document.body.classList.remove( 'Activating' ); } }, 300 ); } }, 600 ); } }, 300 ); // Don't do it again, but notify! if( div.classList && div.classList.contains( 'Active' ) ) { if( div.windowObject && !div.notifyActivated ) { let iftest = div.getElementsByTagName( _viewType ); let msg = { type: 'system', command: 'notify', method: 'activateview', viewId: div.windowObject.viewId }; if( iftest && iftest[0] ) { msg.applicationId = iftest[0].applicationId; msg.authId = iftest[0].authId; } div.windowObject.sendMessage( msg ); div.windowObject.sendMessage( { command: 'activate' } ); // Let the app know div.notifyActivated = true; } _activationTarget = null; // Unreserve return; } // Push active view to history if( !div.windowObject.flags.viewGroup ) Friend.GUI.view.viewHistory.push( div ); // Set screen SetScreenByWindowElement( div ); _setWindowTiles( div ); // When activating for the first time, deselect selected icons if( div.classList && !div.classList.contains( 'Screen' ) ) { // Make sure! if( changedActiveWindow ) { let clear = true; let t = e ? e.target : false; if( t ) { while( t && t != document.body && !t.fileInfo ) t = t.parentNode; if( t && t.fileInfo ) clear = false; } if( !clear ) { clearRegionIcons( { exception: t } ); } else { clearRegionIcons(); } } } else if( e && ( !e.shiftKey && !e.ctrlKey ) ) clearRegionIcons(); _ActivateWindowOnly( div ); if( !nopoll ) PollTaskbar( div ); // All done _activationTarget = null; } // Activate tiling system function _setWindowTiles( div ) { if( isMobile ) return; // Check if we have windows attached if( div.attached ) { if( div.className.indexOf( 'TilingMode' ) >= 0 ) { _removeWindowTiles( div ); } let attachedCount = 1; for( var a in div.attached ) { attachedCount++; } div.classList.add( 'TilingMode' + attachedCount ); let tile = 2; for( var a in div.attached ) { div.attached[a].classList.add( 'Tile' + tile++, 'TilingMode' + attachedCount ); } } } // Remove tiling system function _removeWindowTiles( div ) { if( isMobile ) return; // Check if we have windows attached if( div.attached ) { let attachedCount = 1; for( var a in div.attached ) { attachedCount++; } while( div.className.indexOf( 'Til' ) >= 0 ) { let ind = div.className.indexOf( 'Til' ); if( ind >= 0 ) { for( var b = ind; div.className[b] != ' ' && b < div.className.length; b++ ){} div.className = div.className.split( div.className.substr( ind, b - ind ) ).join( ' ' ); } } for( var a in div.attached ) { let d = div.attached[ a ] while( d.className.indexOf( 'Til' ) >= 0 ) { let ind = d.className.indexOf( 'Til' ); if( ind >= 0 ) { for( var b = ind; d.className[b] != ' ' && b < d.className.length; b++ ){} d.className = d.className.split( d.className.substr( ind, b - ind ) ).join( ' ' ); } } } } } function _DeactivateWindow( m, skipCleanUp ) { let ret = false; if( !m ) return; if( !m ) return; // Cannot deactivate singletaskers if( m.windowObject && m.windowObject.getFlag( 'singletask' ) ) { return _ActivateWindow( m ); } if( m.className && m.classList.contains( 'Active' ) ) { m.classList.remove( 'Active' ); m.viewContainer.classList.remove( 'Active' ); // Check inactive window color if( m.windowObject.getFlag( 'windowInactive' ) ) { m.style.backgroundColor = m.windowObject.getFlag( 'windowInactive' ); m.titleDiv.style.backgroundColor = m.style.backgroundColor; } CheckMaximizedView(); if( m.windowObject && m.notifyActivated ) { let iftest = m.getElementsByTagName( _viewType ); let msg = { type: 'system', command: 'notify', method: 'deactivateview', viewId: m.windowObject.viewId }; if( iftest && iftest[0] ) { msg.applicationId = iftest[0].applicationId; } m.windowObject.sendMessage( { command: 'blur' } ); m.windowObject.sendMessage( msg ); m.notifyActivated = false; // Deactivate all iframes let fr = m.windowObject.content.getElementsByTagName( 'iframe' ); for( let a = 0; a < fr.length; a++ ) { fr[ a ].oldSandbox = fr[ a ].getAttribute( 'sandbox' ); fr[ a ].setAttribute( 'sandbox', 'allow-scripts' ); } PollTaskbar(); } // Minimize on mobile if( isMobile ) { m.doMinimize(); } ret = true; } if( window.isMobile && !window.isTablet ) { m.style.height = '35px'; } if( isMobile ) { _removeMobileCloseButtons(); } // If we will not skip cleanup then do this if( !skipCleanUp ) { if( window.currentMovable == m ) window.currentMovable = null; // See if we can activate a mainview if( !currentMovable && !_activationTarget && m.windowObject ) { let app = _getAppByAppId( m.windowObject.applicationId ); let hasActive = false; for( var a in app.windows ) { if( app.windows[ a ]._window.classList.contains( 'Active' ) ) { hasActive = true; break; } } if( !hasActive ) { for( var a in app.windows ) { if( app.windows[ a ].flags.mainView && m.windowObject != app.windows[ a ] ) { if( isMobile ) { app.windows[ a ].flags.minimized = false; app.windows[ a ].activate(); } break; } } } } // For mobiles and tablets hideKeyboard(); // Check window CheckScreenTitle(); } return ret; } function _removeMobileCloseButtons() { for( var a in movableWindows ) { let f = movableWindows[ a ]; if( f.viewIcon ) { f.viewIcon.classList.remove( 'Remove' ); f.classList.remove( 'Remove' ); f.classList.remove( 'Dragging' ); f.parentNode.classList.remove( 'Flipped' ); } } } function _DeactivateWindows( flags = false ) { clearRegionIcons(); let windowsDeactivated = 0; window.currentMovable = null; if( isMobile ) { Friend.GUI.view.viewHistory = []; } let a = null; for( a in movableWindows ) { let m = movableWindows[a]; if( m.classList.contains( 'Active' ) ) { // Check exceptions to deactivation let found = false; if( flags && flags.exceptions ) { for( let b = 0; b < flags.exceptions.length; b++ ) { if( flags.exceptions[ b ] == m ) { found = true; break; } } } if( !found ) windowsDeactivated += _DeactivateWindow( m, true ); } } //if( windowsDeactivated > 0 ) PollTaskbar (); // For mobiles and tablets hideKeyboard(); // Set window title document.title = Friend.windowBaseString; // Check window CheckScreenTitle(); Friend.GUI.reorganizeResponsiveMinimized(); } // Ouch! Use with care! function CloseAllWindows() { for( var a in movableWindows ) { CloseView( movableWindows[a] ); } movableWindows = []; } function _WindowToFront( div, flags ) { // Blocker if( div.content && div.content.blocker ) { _ActivateWindow( div.content.blocker.getWindowElement().parentNode, false ); return; } if( !div || !div.style ) return; if( !flags ) flags = {}; // Could be we did this on content! if( div.parentNode && div.parentNode.content ) div = div.parentNode; // 1. Find highest and lowest zindex let low = 9999999; let high = -1; for( var a in movableWindows ) { let m = movableWindows[a]; let zi = parseInt( m.viewContainer.style.zIndex ); if( zi <= low ) low = zi; if( zi >= high ) high = zi; } // 2. sort windows after zindex let sorted = []; for( var a = low; a <= high; a++ ) { for( var b in movableWindows ) { if( div != movableWindows[ b ] && movableWindows[ b ].viewContainer.style.zIndex == a ) { sorted.push( movableWindows[ b ] ); } } } // 3. sort, and place current window to front let sortedInd = 100; for( var a = 0; a < sorted.length; a++ ) { sorted[ a ].viewContainer.style.zIndex = sortedInd++; sorted[ a ].style.zIndex = sorted[ a ].viewContainer.style.zIndex; } // 4. now apply the one we want to front to the front if( div.viewContainer ) { div.viewContainer.style.zIndex = sortedInd; div.style.zIndex = sortedInd; } // 5. Check if we are snapped if( !flags.sourceElements ) { flags.sourceElements = []; } // Don't check snap objects etc if we're already affected for( var a = 0; a < flags.sourceElements.length; a++ ) { if( flags.sourceElements[ a ] == div ) return; } if( flags.source != 'attachment' ) { if( div.snap && div.snapObject ) { // Tell window to front that we're an object in its attachment list flags.sourceElements.push( div ); _WindowToFront( div.snapObject, { source: 'attachment', sourceElements: flags.sourceElements } ); } } if( flags.source != 'snapobject' ) { if( div.attached ) { for( var a = 0; a < div.attached.length; a++ ) { let found = false; for( var b = 0; b < flags.sourceElements.length; b++ ) { if( flags.sourceElements[b] == div.attached[a] ) { found = true; break; } } if( !found ) { flags.sourceElements.push( div.attached[ a ] ); } } for( var a = 0; a < div.attached.length; a++ ) { if( div.attached[a] == flags.sourceElement ) { continue; } // Tell window to front that we're the snap object _WindowToFront( div.attached[a], { source: 'snapobject', sourceElements: flags.sourceElements } ); } } } } // Sets a window loading animation on content function WindowLoadingAnimation( w ) { if( w.content ) w = w.content; w.innerHTML = '
'; } // Gets a variable on a movable window by event (target element is used, f.x. clicking a button) function GetWindowVariableByEvent( e, vari ) { if( !e ) return; try { let t = e.srcElement ? e.srcElement : e.target; while( t.className.indexOf( 'View' ) < 0 && t != document.body ) { t = t.parentNode; } if( t.className.indexOf ( 'View' ) < 0 ) return; let divs = document.getElementsByTagName ( 'div' ); let cnt = false; for( var a = 0; a < divs.length; a++ ) { if( divs[a].classList && divs[a].classList.contains( 'Content' ) ) { cnt = divs[a]; break; } } if( !cnt ) return; if( cnt[vari] ) return cnt[vari]; } catch( e ){} return false; } // Gets a variable on a movable window function GetWindowVariable( win, vari ) { let t = win; while( t.className.indexOf ( 'View' ) < 0 && t != document.body ) { t = t.parentNode; } if( t.className.indexOf ( 'View' ) < 0 ) return; let divs = document.getElementsByTagName ( 'div' ); let cnt = false; for( var a = 0; a < divs.length; a++ ) { if( divs[a].classList && divs[a].classList.contains( 'Content' ) ) { cnt = divs[a]; break; } } if( !cnt ) return; if( cnt[vari] ) return cnt[vari]; return false; } function HasClassname( div, classname ) { let classes = div.className ? div.className.split( ' ' ) : []; for( var a in classes ) { if( classes[a] == classname ) { return true; } } return false; } // Close a movable window by pointing to the content div // Could one day be moved to the View class... function CloseView( win, delayed ) { if( !win && window.currentMovable ) win = window.currentMovable; let isDialog = false; let title = win.windowObject.getFlag( 'Title' ); if( win ) { // Clean up! if( win == window.regionWindow ) window.regionWindow = null; if( window.currentMovable && window.currentMovable == win ) window.currentMovable = null; if( !win.parentNode.parentNode ) return; if( win.parentNode.classList.contains( 'ViewContainer' ) ) { win.parentNode.classList.add( 'Closing', 'NoEvents' ); } // win == "content" else if( win.parentNode.parentNode.classList.contains( 'ViewContainer' ) ) { win.parentNode.parentNode.classList.add( 'Closing', 'NoEvents' ); } if( win.parentNode.classList.contains( 'Dialog' ) || win.parentNode.parentNode.classList.contains( 'Dialog' ) || win.parentNode.parentNode.classList.contains( 'FileDialog' ) ) { isDialog = true; let qm = null; if( ( qm = win.parentNode.querySelector( '.QuickMenu' ) ) ) { qm.classList.remove( 'Showing' ); ge( 'DoorsScreen' ).appendChild( qm ); } let currentIsDialog = false; if( currentMovable ) { let cr = currentMovable; if( cr.parentNode.classList.contains( 'Dialog' ) || cr.parentNode.parentNode.classList.contains( 'Dialog' ) || cr.parentNode.parentNode.classList.contains( 'FileDialog' ) ) { currentIsDialog = true; } } if( win == currentMovable || currentIsDialog ) document.body.classList.remove( 'Dialog' ); } // Single task if( win.windowObject.getFlag( 'singletask' ) ) { document.body.classList.remove( 'Singletask' ); } // Unassign this if( win.parentNode == Friend.currentWindowHover ) Friend.currentWindowHover = null; // Check virtual workspace information if( win.workspace ) { // Unset if the active window is this to be closed.. if( virtualWorkspaces[ win.workspace ] ) { if( virtualWorkspaces[ win.workspace ].activeWindow == win ) virtualWorkspaces[ win.workspace ].activeWindow = null; } } let count = 0; let isGroupMember = false; if( win.groupMember ) isGroupMember = true; while( !HasClassname( win, 'View' ) && win != document.body ) { win = win.parentNode; } // Clear view that is closed from view history let out = []; for( let a = 0; a < Friend.GUI.view.viewHistory.length; a++ ) { if( Friend.GUI.view.viewHistory[a] != win ) out.push( Friend.GUI.view.viewHistory[a] ); } Friend.GUI.view.viewHistory = out; let div = win; // Unsnap if( win.unsnap ) win.unsnap(); if( win.attached ) { for( var a = 0; a < win.attached.length; a++ ) { if( win.attached[a].unsnap ) win.attached[a].unsnap(); } } let appId = win.windowObject ? win.windowObject.applicationId : false; // Clear reference if ( window.regionWindow == div.content ) window.regionWindow = false; let app = false; if( div.applicationId ) app = _getAppByAppId( div.applicationId ); if( app && div == app.displayedView ) app.displayedView = null; if( !isGroupMember && div.parentNode ) { // Immediately kill child views for mobile! if( isMobile && window._getAppByAppId ) { if( app.mainView == div.windowObject ) { for( let a in app.windows ) { if( app.windows[ a ] != div.windowObject ) { if( app.windows[ a ]._window.parentNode && app.windows[ a ]._window.parentNode.parentNode ) { let elef = app.windows[ a ]._window.parentNode.parentNode; if( elef.classList && elef.classList.contains( 'View' ) || elef.classList.contains( 'ViewContainer' ) ) { app.windows[ a ]._window.parentNode.parentNode.style.display = 'none'; } } } } } } // Animate closing setTimeout( function() { if( div.viewContainer.parentNode ) { div.viewContainer.parentNode.removeChild( div.viewContainer ); } else if( div.parentNode ) { div.parentNode.removeChild( div ); } CheckMaximizedView(); }, isMobile ? 750 : 500 ); if( !isMobile ) { div.style.opacity = 0; if( window.DeepestField ) DeepestField.cleanTasks(); } // Do not click! let ele = document.createElement( 'div' ); ele.style.position = 'absolute'; ele.style.top = '0'; ele.style.left = '0'; ele.style.width = '100%'; ele.style.height = '100%'; ele.style.background = 'rgba(0,0,0,0.0)'; ele.onmousedown = function( e ){ return cancelBubble( e ); } ele.style.zIndex = 7867878; div.appendChild( ele ); } // Context ------------------------------------------------------------- // Check the window recent location exists, and use it instead if( ( currentMovable && currentMovable == win ) || ( !currentMovable && win ) ) { if( win.windowObject && win.windowObject.recentLocation && win.windowObject.recentLocation.substr( 0, 7 ) == 'viewId:' ) { let id = win.windowObject.recentLocation; id = id.substr( 7, id.length - 7 ); let actSet = false; for( let z in movableWindows ) { if( movableWindows[ z ].windowObject && movableWindows[ z ].windowObject.getViewId() == id ) { currentMovable = movableWindows[ z ]; _ActivateWindow( currentMovable ); if( typeof( window.currentContext ) == 'object' && window.currentContext.length > 1 ) window.currentContext = window.currentContext[ 1 ]; else window.currentContext = false; actSet = true; break; } } if( !actSet ) { // Default _DeactivateWindows(); showDashboard(); setTimeout( function(){ showDashboard(); }, 150 ); } } // Check the window context, if it exists //console.log( 'Foo bar', window.currentContext ); if( window.currentContext ) { //console.log( 'Test' ); function handleContext( depth ) { if( !depth ) depth = 1; switch( window.currentContext ) { case 'dashboard': _DeactivateWindows(); showDashboard(); break; case 'sidebar': _DeactivateWindows(); hideDashboard(); break; // We have a different thing for other contexts default: let appCheck = true; //console.log( 'Checking context: ', window.currentContext ); // We got a context array ([ currentWindow, prevContext ]) if( typeof( window.currentContext ) == 'object' ) { // We are referring to self! Fix it if( window.currentContext[0] == win && typeof( window.currentContext[1] ) != 'undefined' ) { //console.log( 'I am self: ', window.currentContext ); window.currentContext = window.currentContext[1]; if( window.currentContext == 'dashboard' ) { return handleContext( depth + 1 ); } } if( window.currentContext[0] && window.currentContext[0].tagName == 'DIV' && window.currentContext[0] != currentMovable ) { currentMovable = window.currentContext[ 0 ]; _ActivateWindow( window.currentContext[ 0 ] ); if( window.currentContext[ 0 ].content && window.currentContext[ 0 ].content.refresh ) window.currentContext[ 0 ].content.refresh(); return; } else if( window.currentContext.length > 1 ) { if( typeof( window.currentContext[ 1 ] ) != 'undefined' ) { window.currentContext = window.currentContext[ 1 ]; return handleContext( depth + 1 ); } } } if( appId && appCheck ) { for( let a = Friend.GUI.view.viewHistory.length - 1; a >= 0; a-- ) { if( Friend.GUI.view.viewHistory[ a ].applicationId == appId ) { // Only activate non minimized views if( Friend.GUI.view.viewHistory[a].viewContainer && !Friend.GUI.view.viewHistory[a].viewContainer.getAttribute( 'minimized' ) ) { let vh = Friend.GUI.view.viewHistory[ a ]; currentMovable = vh; _ActivateWindow( vh ); if( vh.content && vh.content.refresh ) vh.content.refresh(); nextActive = true; } break; } } } break; } } handleContext(); } // Context end --------------------------------------------------------- else { // Activate latest activated view (not on mobile) let nextActive = false; if( div.classList.contains( 'Active' ) || div.windowObject.getFlag( 'dialog' ) ) { if( Friend.GUI.view.viewHistory.length ) { // Only activate last view in the same app if( appId ) { for( let a = Friend.GUI.view.viewHistory.length - 1; a >= 0; a-- ) { if( Friend.GUI.view.viewHistory[ a ].applicationId == appId ) { // Only activate non minimized views if( Friend.GUI.view.viewHistory[a].viewContainer && !Friend.GUI.view.viewHistory[a].viewContainer.getAttribute( 'minimized' ) ) { let vh = Friend.GUI.view.viewHistory[ a ]; currentMovable = vh; _ActivateWindow( vh ); if( vh.content && vh.content.refresh ) vh.content.refresh(); nextActive = true; } break; } } } else { for( let a = Friend.GUI.view.viewHistory.length - 1; a >= 0; a-- ) { if( Friend.GUI.view.viewHistory[ a ].windowObject.workspace == globalConfig.workspaceCurrent ) { if( Friend.GUI.view.viewHistory[ a ].windowObject.getFlag( 'sidebarManaged' ) ) continue; // Only activate non minimized views if( Friend.GUI.view.viewHistory[a].viewContainer && !Friend.GUI.view.viewHistory[a].viewContainer.getAttribute( 'minimized' ) ) { let vh = Friend.GUI.view.viewHistory[ a ]; currentMovable = vh; _ActivateWindow( vh ); if( vh.content && vh.content.refresh ) vh.content.refresh(); nextActive = true; } break; } } } } } } } if( div ) { // Clean up ids let o = []; for( let b in movableWindows ) { if( movableWindows[b] != div && movableWindows[b].parentNode ) { o[b] = movableWindows[b]; } } movableWindows = o; } movableWindowCount--; if( movableWindowCount <= 0 ) { movableWindowCount = 0; movableHighestZindex = 99; } // Check events if( div.content && div.content.events ) { if( typeof( div.content.events['close'] ) != 'undefined' ) { for( var a = 0; a < div.content.events['close'].length; a++ ) { div.content.events['close'][a](); } } } PollTaskbar(); // Remove link to current movable if( win == window.currentMovable ) window.currentMovable = null; // Make sure we count the windows in body if( movableWindowCount > 0 ) { if( window.windowCountTimeout ) { clearTimeout( window.windowCountTimeout ); delete window.windowCountTimeout; } document.body.setAttribute( 'windowcount', movableWindowCount ); } else { // Delay this with 400ms window.windowCountTimeout = setTimeout( function() { document.body.removeAttribute( 'windowcount' ); }, 400 ); } // Dashboard support if( win.windowObject.recentLocation && win.windowObject.recentLocation != 'dashboard' ) { return; } if( !currentMovable || ( currentMovable && currentMovable.windowObject.getFlag( 'dockable' ) && window.showDashboard ) ) { if( window.showDashboard ) { _DeactivateWindows(); showDashboard(); if( window.pollLiveViews ) pollLiveViews(); return; } } if( app && isMobile && app.mainView && app.mainView != win.windowObject ) { app.mainView.activate( 'force' ); } // We have a parent view else if( win.parentView ) { win.parentView.activate( 'force' ); } // Just make sure we have a view on the workspace else if( app && isMobile ) { for( var a in app.windows ) { if( app.windows[ a ].activate ) app.windows[ a ].activate( 'force' ); break; } } } if( !window.currentMovable ) { if( Workspace.screen && Workspace.screen.getFlag ) { document.title = Workspace.screen.getFlag( 'title' ); } } // Check window CheckScreenTitle(); if( isMobile && Workspace.redrawIcons ) Workspace.redrawIcons(); if( !currentMovable ) { // If we have a dashboard if( window.showDashboard ) showDashboard(); if( window.pollLiveViews ) pollLiveViews(); } else { if( window.pollLiveViews ) pollLiveViews(); } } // Obsolete!!! CloseWindow = CloseView; // TODO: Detect if the scrolling is done with the mouse hovering over a window function CancelWindowScrolling( e ) { if ( !e ) e = window.event; let t = e.target ? e.target : e.srcElement; if ( window.currentMovable && window.currentMovable.offsetHeight ) window.scrollTo ( 0, window.lastScrollPosition ? window.lastScrollPosition : 0 ); else window.lastScrollPosition = document.body.scrollTop; return true; } if ( window.addEventListener ) window.addEventListener( 'scroll', CancelWindowScrolling, true ); else window.attachEvent( 'onscroll', CancelWindowScrolling, true ); // Support scrolling in windows function WindowScrolling( e ) { if( !e ) e = window.event; let dlt = e.detail ? (e.detail*-120) : e.wheelDelta; let tr = e.srcElement ? e.srcElement : e.target; let win = false; while( tr != document.body ) { if( tr.className && tr.className.indexOf ( 'View' ) > 0 ) { win = tr; break; } tr = tr.parentNode; } } // The View class begins ------------------------------------------------------- // Attach view class to friend Friend.GUI.view.create = View; Friend.GUI.view.removeScriptsFromData = function( data ) { let r = false; let assets = []; while( r = data.match( /\'; data = data.split( r[0] ).join( '' ); } // Remove scripts data = data.split( /\]*?\>[\w\W]*?\<\/script[^>]*?\>/i ).join ( '' ); // Add assets if( assets.length > 0 ) data += assets.join( "\n" ); return data; }; Friend.GUI.view.cleanHTMLData = function( data ) { // Allow for "script" template assets data = Friend.GUI.view.removeScriptsFromData( data ); data = data.split( /\]*?\>[\w\W]*?\<\/style[^>]*?\>/i ).join ( '' ); return data; }; // View class (the javascript way) // TODO: Fix opening windows with no preset id (right now, window does not appear) // TODO: Fix opening windows with id, to close and kill its application if already running app with unique id /** * View class * * The View class is used to created views/windows in Friend - it is the most used class to build the user interface * * The View class has a sibling, the Screen class that creates a new Friend screen. * * * * @param args - an object containing the initial parameters for the view. As an absolute minium title, width and height should be provided * @return View - a pointer to the new instance that justhas been created * */ var View = function( args ) { let self = this; // Windows on own screen ignores the virtual workspaces if( args.screen && args.screen != Workspace.screen ) { if( globalConfig && typeof( globalConfig.workspaceCurrent ) != 'undefined' ) this.workspace = globalConfig.workspaceCurrent; else this.workspace = 0; if( args.workspace === 0 || args.workspace ) { if( args.workspace < globalConfig.workspacecount - 1 ) { this.workspace = args.workspace; } } } // Special hook for dashboard related workspace if( window.hideDashboard && ( !args || !args.invisible ) ) { let newApp = true; if( args.applicationId ) { for( let a in Workspace.applications ) { let app = Workspace.applications[ a ]; if( app.applicationId == args.applicationId ) { let count = 0; for( let b in app.windows ) { count++; if( count > 1 ) { newApp = false; break; } } } } } if( newApp ) window.hideDashboard(); } // Start off if( !args ) args = {}; this.args = args; this.widgets = []; // Widgets stuck to this view window // Reaffirm workspace this.setWorkspace = function() { // Ignore windows on own screen if( this.flags && this.flags.screen && this.flags.screen != Workspace.screen ) return; if( globalConfig.workspacecount > 1 ) { let ws = this.getFlag( 'left' ); ws = parseInt( ws ) / window.innerWidth; this.workspace = Math.floor( ws ); } } // Clean data this.cleanHTMLData = Friend.GUI.view.cleanHTMLData; this.removeScriptsFromData = Friend.GUI.view.removeScriptsFromData; // Setup the dom elements // div = existing DIV dom element or 'CREATE' // titleStr = title string // width = width // height = height (in pixels) // id = window id // flags = list of constructor flags // applicationId = app id.. this.createDomElements = function( div, titleStr, width, height, id, flags, applicationId ) { if( this.created ) { return; } this.created = true; if ( !div ) return false; // Native mode? Creates a new place for the view this.nativeWindow = false; // If we're making a movable window with a unique id, the make sure // it doesn't exist, in case, just return the existing window let contentscreen = false; let parentWindow = false; if( !titleStr ) titleStr = ''; let transparent = false; let filter = [ 'min-width', 'min-height', 'width', 'height', 'id', 'title', 'screen', 'parentView', 'transparent', 'minimized', 'dialog', 'standard-dialog', 'sidebarManaged' ]; if( !flags.screen ) { flags.screen = Workspace.screen; } // This needs to be set immediately! self.parseFlags( flags, filter ); let app = false; if( window._getAppByAppId ) { let app = _getAppByAppId( div.applicationId ); } // Set a parent relation to main view if( app && app.mainView ) { self.parentView = app.mainView; } // Set initial workspace if( !flags.screen || flags.screen == Workspace.screen ) { if( flags.workspace && flags.workspace > 0 ) { self.workspace = flags.workspace; } else { self.workspace = globalConfig.workspaceCurrent; } } if( !self.getFlag( 'min-width' ) ) this.setFlag( 'min-width', 100 ); if( !self.getFlag( 'min-height' ) ) this.setFlag( 'min-height', 100 ); // Get newly parsed flags width = self.getFlag( 'width' ); height = self.getFlag( 'height' ); id = self.getFlag( 'id' ); titleStr = self.getFlag( 'title' ); if( !titleStr ) titleStr = 'Unnamed window'; contentscreen = self.getFlag( 'screen' ); parentWindow = self.getFlag( 'parentView' ); transparent = self.getFlag( 'transparent' ); // Clean ID if( !id ) { id = titleStr.split( /[^a-z0-9]+/i ).join( '_' ); if( id.substr( 0, 1 ) == '_' ) id = 'win' + id; let tmp = id; let num = 2; while( typeof ( movableWindows[ tmp ] ) != 'undefined' ) { tmp = id + '_' + (num++); } id = tmp; } // Clean ID else { id = id.split( /[^a-z0-9]+/i ).join( '_' ); if( id.substr( 0, 1 ) == '_' ) id = 'win' + id; } // Make a unique id let uniqueId = id; uniqueId = uniqueId.split( /[ |:]/i ).join ( '_' ); // Where to add div.. let divParent = false; let iconSpan; let viewContainer = null; if( id ) { // Existing window! if( typeof( movableWindows[ id ] ) != 'undefined' ) { return false; } // Make a container to put the view div inside of viewContainer = document.createElement( 'div' ); viewContainer.className = 'ViewContainer'; viewContainer.style.display = 'none'; // Set up view title let viewTitle = document.createElement( 'div' ); viewTitle.className = 'ViewTitle'; viewContainer.appendChild( viewTitle ); // Get icon for visualizations if( applicationId ) { for( var a = 0; a < Workspace.applications.length; a++ ) { if( Workspace.applications[a].applicationId == applicationId ) { if( Workspace.applications[a].icon ) { let ic = Workspace.applications[a].icon; iconSpan = document.createElement( 'span' ); iconSpan.classList.add( 'ViewIcon' ); iconSpan.style.backgroundImage = 'url(\'' + ic + '\')'; self.viewIcon = iconSpan; viewContainer.appendChild( iconSpan ); } } } // Add mobile back button if( isMobile ) { let md = document.createElement( 'div' ); md.className = 'MobileBack'; self.mobileBack = md; md.ontouchstart =function( e ) { if( window._getAppByAppId ) { let app = _getAppByAppId( div.applicationId ); if( app.mainView ) { FocusOnNothing(); _ActivateWindow( app.mainView.content.parentNode ); self.close(); return cancelBubble( e ); } } return cancelBubble( e ); }; viewContainer.appendChild( md ); } } else { iconSpan = document.createElement( 'span' ); iconSpan.classList.add( 'ViewIcon' ); self.viewIcon = iconSpan; iconSpan.style.backgroundImage = 'url(/iconthemes/friendup15/Folder.svg)'; viewContainer.appendChild( iconSpan ); // Add mobile back button if( isMobile ) { let md = document.createElement( 'div' ); md.className = 'MobileBack'; self.mobileBack = md; md.ontouchstart =function( e ) { if( window._getAppByAppId ) { let app = _getAppByAppId( div.applicationId ); if( app.mainView ) { FocusOnNothing(); _ActivateWindow( app.mainView.content.parentNode ); self.close(); return cancelBubble( e ); } } return cancelBubble( e ); }; viewContainer.appendChild( md ); } } if( div == 'CREATE' ) { div = document.createElement( 'div' ); if( applicationId ) div.applicationId = applicationId; div.parentWindow = false; if( parentWindow ) { divParent = parentWindow._window; div.parentWindow = parentWindow; } // Defined screen (and we're not in multiple workspaces) else if( contentscreen ) { divParent = contentscreen.contentDiv ? contentscreen.contentDiv : contentscreen.div; } else if( typeof ( window.currentScreen ) != 'undefined' ) { divParent = window.currentScreen; } else { divParent = document.body; } } div.viewTitle = viewTitle; // Designate movableWindows[ id ] = div; div.titleString = titleStr; div.viewId = id; if( !flags.screen || flags.screen == Workspace.screen ) { div.workspace = self.workspace; } else div.workspace = 0; } else if ( div == 'CREATE' ) { div = document.createElement ( 'div' ); if( applicationId ) div.applicationId = applicationId; div.parentWindow = false; if( parentWindow ) { divParent = parentWindow._window; div.parentWindow = parentWindow; } // Defined screen (and we're not in multiple workspaces) else if( contentscreen ) { divParent = contentscreen.contentDiv ? contentscreen.contentDiv : contentscreen.div; } else if( typeof( window.currentScreen ) != 'undefined' ) { divParent = window.currentScreen; } else { divParent = document.body; } if( divParent.object && divParent.object._screen ) { divParent = divParent.object._screen; } // ID must be unique let num = 0; let oid = id; while( ge( id ) ) id = oid + '_' + ++num; div.id = id ? id : ( 'window_' + Friend.GUI.view.movableViewIdSeed++ ); div.viewId = div.id; movableWindows[ div.id ] = div; } // Just register the view else { movableWindows[ div.id ] = div; } if( isMobile ) Workspace.exitMobileMenu(); // Set placeholder quickmenu div.quickMenu = { uniqueName: 'placeholder_' + ( div.id ? div.id : MD5( Math.random() * 1000 + ( Math.random() * 1000 ) + '' ) ), 0: { name: i18n( 'i18n_close' ), icon: 'remove', command: function() { div.windowObject.close(); } } }; // Check to set mainview if( window._getAppByAppId ) { let app = _getAppByAppId( this.applicationId ); if( app ) { let l = 0; for( var k in app.windows ) l++; // If we only have one window - it's probably the main window if( l == 0 ) { this.setFlag( 'mainView', true ); } } } // Tell it's opening (not minimized or invisible ones) if( !flags.minimized && !flags.invisible ) { // Allow initialized if( window.currentMovable ) { viewContainer.classList.add( 'Initialized' ); setTimeout( function() { viewContainer.classList.remove( 'Initialized' ); }, 25 ); } // Allow opening animation viewContainer.classList.add( 'Opening' ); div.classList.add( 'Opening' ); setTimeout( function() { div.classList.add( 'Opened' ); div.classList.remove( 'Opening' ); setTimeout( function() { div.classList.remove( 'Opened' ); // Give last call to port div.classList.add( 'Redrawing' ); setTimeout( function() { viewContainer.classList.remove( 'Opening' ); div.classList.remove( 'Redrawing' ); }, 250 ); }, 250 ); }, 250 ); } if( transparent ) { div.setAttribute( 'transparent', 'transparent' ); } if( !flags.minimized ) { div.style.transform = 'translate3d(0, 0, 0)'; } let zoom; // for use later - zoom gadget let html = div.innerHTML; let contn = document.createElement( 'div' ); contn.windowObject = this; div.windowObject = this; this._window = contn; // Set up view states // TODO: More to come! this.states = { 'input-focus': false }; contn.className = 'Content'; contn.innerHTML = html; // Assign content element to both div and view object div.content = contn; self.content = contn; // Title let titleSpan = document.createElement ( 'span' ); titleSpan.innerHTML = titleStr ? titleStr : '- unnamed -'; contn.applicationId = applicationId; // Virtual window, not for display if( flags.virtual ) { div.classList.add( 'Virtual' ); } div.innerHTML = ''; // Register mouse over and out if( !window.isMobile ) { div.addEventListener( 'mouseover', function( e ) { // Keep track of the previous if( typeof( Friend.currentWindowHover ) != 'undefined' && Friend.currentWindowHover ) Friend.previousWindowHover = Friend.currentWindowHover; Friend.currentWindowHover = div; if( !div.classList.contains( 'Active' ) ) { // Reactivate all iframes let fr = div.windowObject.content.getElementsByTagName( 'iframe' ); for( let a = 0; a < fr.length; a++ ) { if( fr[ a ].oldSandbox && typeof( fr[ a ].oldSandbox ) == 'string' ) fr[ a ].setAttribute( 'sandbox', fr[ a ].oldSandbox ); } } // Focus on desktop if we're not over a window. if( Friend.previousWindowHover && Friend.previousWindowHover != div ) { // Check first if are focused on an input field // If we are, don't focus on nothing! if( !Friend.GUI.checkWindowState( 'input-focus' ) ) { // TODO: If this is an input element, do not lose focus // unless needed. E.g. changing window. //var currentFocus = document.activeElement; window.focus(); } } } ); div.addEventListener( 'mouseout', function() { if( !div.classList.contains( 'Active' ) ) { // Reactivate all iframes let fr = div.windowObject.content.getElementsByTagName( 'iframe' ); for( let a = 0; a < fr.length; a++ ) { fr[ a ].setAttribute( 'sandbox', 'allow-scripts' ); } } // Keep track of the previous if( Friend.currentWindowHover ) Friend.previousWindowHover = Friend.currentWindowHover; Friend.currentWindowHover = null; } ); } if ( !div.id ) { // ID must be unique let num = 0; let oid = id; while( ge( id ) ) id = oid + '_' + ++num; div.id = id; movableWindows[ div.id ] = div; } // Volume gauge if( flags.volume && flags.volume != false ) { let gauge = document.createElement( 'div' ); gauge.className = 'VolumeGauge'; gauge.innerHTML = '
'; div.appendChild( gauge ); div.volumeGauge = gauge.getElementsByTagName( 'div' )[0]; } // Snap elements let snap = document.createElement( 'div' ); snap.className = 'Snap'; snap.innerHTML = '
' + '
'; // Moveoverlay let molay = document.createElement ( 'div' ); molay.className = 'MoveOverlay'; molay.onmouseup = function() { WorkspaceMenu.close(); } // Clean up! Friend.GUI.view.cleanWindowArray( div ); // Title let title = document.createElement ( 'div' ); title.className = 'Title'; if( flags.resize == false ) title.className += ' NoResize'; // Resize let resize = document.createElement ( 'div' ); resize.className = 'Resize'; resize.style.position = 'absolute'; resize.style.width = '14px'; resize.style.height = '14px'; resize.style.zIndex = '100'; let inDiv = document.createElement( 'div' ); title.appendChild( inDiv ); title.onclick = function( e ){ return cancelBubble ( e ); } title.ondragstart = function( e ) { return cancelBubble ( e ); } title.onselectstart = function( e ) { return cancelBubble ( e ); } if( !isMobile ) { title.onmousedown = function( e, mode ) { if ( !e ) e = window.event; div.setAttribute( 'moving', 'moving' ); // Use correct button if( e.button != 0 && !mode ) return cancelBubble( e ); let x, y; if( e.touches && ( isTablet || isTouchDevice() ) ) { x = e.touches[0].pageX; y = e.touches[0].pageY; } else { x = e.clientX ? e.clientX : e.pageXOffset; y = e.clientY ? e.clientY : e.pageYOffset; } window.mouseDown = FUI_MOUSEDOWN_WINDOW; this.parentNode.offx = x - this.parentNode.offsetLeft; this.parentNode.offy = y - this.parentNode.offsetTop; _ActivateWindow( div, false, e ); if( e.shiftKey && div.snapObject ) { div.unsnap(); } // Make sure the tray is updated PollTray(); return cancelBubble( e ); } // Pawel must win! let method = 'ondblclick'; if( document.body.classList.contains( 'ThemeEngine' ) ) { method = 'onclick'; title.style.cursor = 'pointer'; } title[ method ] = function( e ) { if( self.flags.clickableTitle ) { if( !self.titleClickElement ) { let d = document.createElement( 'input' ); d.type = 'text'; d.className = 'BackgroundHeavier NoMargins Absolute'; d.style.position = 'absolute'; d.style.outline = 'none'; d.style.border = '0'; d.style.top = '0px'; d.style.left = '0px'; d.style.width = '100%'; d.style.height = '100%'; d.style.textAlign = 'center'; d.style.pointerEvents = 'all'; d.value = contn.fileInfo.Path; d.onkeydown = function( e ) { self.flags.editing = true; setTimeout( function() { self.flags.editing = false; }, 150 ); } d.onblur = function() { d.parentNode.removeChild( d ); self.titleClickElement = null; } d.onchange = function( e ) { let t = this; let f = ( new Door() ).get( this.value ); if( f ) { f.getIcons( this.value, function( items ) { if( items ) { self.content.fileInfo.Path = t.value; self.content.refresh(); } else { t.value = contn.fileInfo.Path; } } ); } else { t.value = contn.fileInfo.Path; } } this.getElementsByTagName( 'SPAN' )[0].appendChild( d ); self.titleClickElement = d; } self.titleClickElement.focus(); self.titleClickElement.select(); } _WindowToFront( div ); } } // Clicking on window div.onmousedown = function( e ) { if( e.button == 0 ) { if( !this.viewIcon.classList.contains( 'Remove' ) ) { if( isMobile ) { let target = this; if( window._getAppByAppId ) { let app = _getAppByAppId( this.applicationId ); if( app && app.displayedView ) { target = app.displayedView; } } _ActivateWindow( target, false, e ); return; } _ActivateWindow( this, false, e ); } } } // Tablets and mobile div.ontouchstart = function( e ) { let self = this; if( isMobile && !self.parentNode.classList.contains( 'OnWorkspace' ) ) return; else if( e && !div.classList.contains( 'Active' ) ) { this.clickOffset = { x: e.touches[0].clientX, y: e.touches[0].clientY, time: ( new Date() ).getTime() }; } // Start jiggling on longpress // Only removable after 300 ms this.touchInterval = setInterval( function() { let t = ( new Date() ).getTime(); if( self.clickOffset ) { if( t - self.clickOffset.time > 100 ) { // Update time self.clickOffset.removable = true; self.viewIcon.parentNode.classList.add( 'Flipped' ); self.viewIcon.classList.add( 'Dragging' ); clearInterval( self.touchInterval ); self.touchInterval = null; if( !isTablet || isMobile ) { self.viewIcon.classList.add( 'Remove' ); self.classList.add( 'Remove' ); } } } }, 150 ); } // Remove window on drag if( isMobile ) { div.ontouchend = function( e ) { if( this.touchInterval ) { clearInterval( this.touchInterval ); this.touchInterval = null; } // Only cancel bubble if view icon is jiggling on mobile if( this.viewIcon.classList.contains( 'Remove' ) ) { return cancelBubble( e ); } } } // Transparency if( flags.transparent ) { contn.style.background = 'transparent'; contn.style.backgroundColor = 'transparent'; } // Depth gadget let depth = document.createElement( 'div' ); depth.className = 'Depth'; depth.onmousedown = function( e ) { return e.stopPropagation(); } depth.ondragstart = function( e ) { return e.stopPropagation(); } depth.onselectstart = function( e ) { return e.stopPropagation(); } depth.window = div; depth.onclick = function( e ) { if( !window.isTablet && e.button != 0 ) return; // Calculate lowest and highest z-index let low = 99999999; let high = 0; for( var a in movableWindows ) { let maxm = movableWindows[a].getAttribute( 'maximized' ); if( maxm && maxm.length ) continue; let ind = parseInt( movableWindows[a].viewContainer.style.zIndex ); if( ind < low ) low = ind; if( ind > high ) high = ind; } movableHighestZindex = high; // If we are below, get us on top if ( movableHighestZindex > parseInt( this.window.style.zIndex ) ) { this.window.viewContainer.style.zIndex = ++movableHighestZindex; this.window.style.zIndex = this.window.viewContainer.style.zIndex; } // If not, don't else { this.window.viewContainer.style.zIndex = 100; let highest = 0; for( var a in movableWindows ) { if( movableWindows[a] != this.window ) { movableWindows[a].viewContainer.style.zIndex = parseInt( movableWindows[a].viewContainer.style.zIndex ) + 1; movableWindows[a].style.zIndex = movableWindows[a].viewContainer.style.zIndex; if ( parseInt( movableWindows[a].viewContainer.style.zIndex ) > highest ) highest = parseInt( movableWindows[a].viewContainer.style.zIndex ); } } movableHighestZindex = highest; } _ActivateWindow( this.window, false, e ); // Fix it! UpdateWindowContentSize( div ); return cancelBubble ( e ); } // Bottom of the window let bottombar = document.createElement ( 'div' ); bottombar.className = 'BottomBar'; // Left border of window let leftbar = document.createElement ( 'div' ); leftbar.className = 'LeftBar'; // Left border of window let rightbar = document.createElement ( 'div' ); rightbar.className = 'RightBar'; // Zoom gadget if( !isMobile ) { zoom = document.createElement ( 'div' ); zoom.className = 'Zoom'; zoom.onmousedown = function ( e ) { return cancelBubble ( e ); } zoom.ondragstart = function ( e ) { return cancelBubble ( e ); } zoom.onselectstart = function ( e ) { return cancelBubble ( e ); } zoom.mode = 'normal'; zoom.window = div; zoom.onclick = function ( e ) { if( !window.isTablet && e.button != 0 ) return; // Don't animate div.setAttribute( 'moving', 'moving' ); setTimeout( function() { div.removeAttribute( 'moving' ); }, 50 ); if( this.mode == 'normal' ) { if( movableHighestZindex > parseInt( this.window.viewContainer.style.zIndex ) ) { this.window.viewContainer.style.zIndex = ++movableHighestZindex; this.window.style.zIndex = this.window.viewContainer.style.zIndex; } _ActivateWindow( this.window, false, e ); this.window.setAttribute( 'maximized', 'true' ); // Check tiling _setWindowTiles( div ); // Tell app if( window._getAppByAppId ) { let app = _getAppByAppId( div.applicationId ); if( app ) { app.sendMessage( { 'command': 'notify', 'method': 'setviewflag', 'flag': 'maximized', 'viewId': div.windowObject.viewId, 'value': true } ); } } // Store it just in case let d = GetWindowStorage( div.id ); if( !d ) d = {}; d.maximized = true; SetWindowStorage( div.id, d ); if( !window.isMobile ) { this.prevLeft = parseInt ( this.window.style.left ); this.prevTop = parseInt ( this.window.style.top ); this.prevWidth = this.window.offsetWidth; this.prevHeight = this.window.offsetHeight; this.window.style.top = '0px'; this.window.style.left = '0px'; let wid = 0; var hei = 0; if( self.flags.screen ) { let cnt2 = this.window.content; let sbar = 0; if( self.flags.screen.div == Ge( 'DoorsScreen' ) ) sbar = GetStatusbarHeight( self.flags.screen ); wid = self.flags.screen.div.offsetWidth; hei = self.flags.screen.div.offsetHeight - sbar; } ResizeWindow( this.window, wid, hei ); } this.mode = 'maximized'; } else { this.mode = 'normal'; this.window.removeAttribute( 'maximized' ); _removeWindowTiles( div ); // Store it just in case let d = GetWindowStorage( div.id ); if( !d ) d = {}; d.maximized = false; SetWindowStorage( div.id, d ); if( !window.isMobile ) { this.window.style.top = this.prevTop + 'px'; this.window.style.left = this.prevLeft + 'px'; ResizeWindow( this.window, this.prevWidth, this.prevHeight ); } // Tell application if any if( window._getAppByAppId ) { let app = _getAppByAppId( div.applicationId ); if( app ) { app.sendMessage( { 'command': 'notify', 'method': 'setviewflag', 'flag': 'maximized', 'viewId': div.windowObject.viewId, 'value': false } ); } } } // Do resize events if( this.window.content && this.window.content.events ) { if( typeof(this.window.content.events['resize']) != 'undefined' ) { for( var a = 0; a < this.window.content.events['resize'].length; a++ ) { this.window.content.events['resize'][a](); } } } // Check maximized CheckMaximizedView(); return cancelBubble( e ); } } resize.onclick = function( e ) { return cancelBubble ( e ); } resize.ondragstart = function( e ) { return cancelBubble ( e ); } resize.onselectstart = function( e ) { return cancelBubble ( e ); } resize.window = div; resize.onmousedown = function( e ) { if( !window.isTablet && e.button != 0 ) return; // Offset based on the containing window this.offx = windowMouseX; this.offy = windowMouseY; this.wwid = div.offsetWidth; this.whei = div.offsetHeight; // Don't animate div.setAttribute( 'moving', 'moving' ); window.mouseDown = FUI_MOUSEDOWN_RESIZE; if( !div.parentNode.classList.contains( 'Active' ) ) _ActivateWindow( div, false, e ); this.window.zoom.mode = 'normal'; return cancelBubble ( e ); } // remember position div.memorize = function () { if( isMobile ) return; let wenable = this.content && self.flags && self.flags.resize ? true : false; // True if we're to enable memory if ( self.flags && self.flags.memorize ) wenable = true; let wwi = div.offsetWidth; let hhe = div.offsetHeight; // Update information in the window storage object let d = GetWindowStorage( this.uniqueId ); if( !div.getAttribute( 'maximized' ) ) { d.top = this.offsetTop; d.left = this.offsetLeft; d.width = wenable && wwi ? wwi : d.width; d.height = wenable && hhe ? hhe : d.width; } if( div.content.directoryview ) { d.listMode = div.content.directoryview.listMode; } SetWindowStorage( this.uniqueId, d ); } let minimize = document.createElement ( 'div' ); minimize.className = 'Minimize'; minimize.onmousedown = function ( e ) { return cancelBubble ( e ); } minimize.ondragstart = function ( e ) { return cancelBubble ( e ); } minimize.onselectstart = function ( e ) { return cancelBubble ( e ); } div.doMinimize = function ( e ) { // Not single task if( this.windowObject.getFlag( 'singletask' ) ) { return this.windowObject.activate(); } if( div.minimized ) { return; } div.minimized = true; if( !e ) { e = { button: 0, fake: true }; } // Normal desktop applications if( !window.isMobile ) { // Fake events just brute forces if( e.fake ) { div.classList.remove( 'Active' ); div.parentNode.classList.remove( 'Active' ); div.minimized = true; div.windowObject.flags.minimized = true; div.viewContainer.setAttribute( 'minimized', 'minimized' ); PollTray(); PollTaskbar(); } else { // Only on real events _ActivateWindow( div, false, e ); let escapeFlag = 0; if( div.windowObject && ( !globalConfig.viewList || globalConfig.viewList == 'separate' ) && ge( 'Taskbar' ) ) { let t = ge( 'Taskbar' ); for( var tel = 0; tel < t.childNodes.length; tel++ ) { if( t.childNodes[tel].window == div ) { t.childNodes[tel].mousedown = true; e.button = 0; t.childNodes[tel].onmouseup( e, t.childNodes[tel] ); return true; } } } else if( ge( 'DockWindowList' ) ) { let t = ge( 'DockWindowList' ); for( var tel = 0; tel < t.childNodes.length; tel++ ) { if( t.childNodes[tel].window == div ) { t.childNodes[tel].mousedown = true; e.button = 0; t.childNodes[tel].onmouseup( e, t.childNodes[tel] ); escapeFlag++; break; } } } // Try to use the dock if( escapeFlag == 0 ) { if( globalConfig.viewList == 'docked' || globalConfig.viewList == 'dockedlist' ) { for( var u = 0; u < Workspace.mainDock.dom.childNodes.length; u++ ) { let ch = Workspace.mainDock.dom.childNodes[ u ]; // Check the view list if( ch.classList.contains( 'ViewList' ) ) { for( var z = 0; z < ch.childNodes.length; z++ ) { let cj = ch.childNodes[ z ]; if( cj.viewId && movableWindows[ cj.viewId ] == div ) { cj.mousedown = true; cj.onclick( { button: 0 } ); escapeFlag++; break; } } } if( escapeFlag == 0 ) { // Check applications if( ch.executable ) { for( var r = 0; escapeFlag == 0 && r < Workspace.applications.length; r++ ) { let app = Workspace.applications[ r ]; if( app.applicationName == ch.executable ) { if( app.windows ) { for( var t in app.windows ) { if( app.windows[t] == div.windowObject ) { Workspace.mainDock.toggleExecutable( ch.executable ); escapeFlag++; break; } } } } } } } } } } } if( div.attached ) { for( var a = 0; a < div.attached.length; a++ ) { let app = _getAppByAppId( div.attached[ a ].applicationId ); if( app ) { app.sendMessage( { 'command': 'notify', 'method': 'setviewflag', 'flag': 'minimized', 'viewId': div.attached[ a ].windowObject.viewId, 'value': true } ); } div.attached[ a ].minimized = true; div.attached[ a ].parentNode.setAttribute( 'minimized', 'minimized' ); } } } // Reorganize minimized view windows else { if( !isMobile ) { let app = _getAppByAppId( div.applicationId ); if( app.mainView && div != app.mainView ) _ActivateWindow( app.mainView.content.parentNode ); } Friend.GUI.reorganizeResponsiveMinimized(); } if( window._getAppByAppId ) { let app = _getAppByAppId( div.applicationId ); if( app ) { app.sendMessage( { 'command': 'notify', 'method': 'setviewflag', 'flag': 'minimized', 'viewId': div.windowObject.viewId, 'value': true } ); } } } minimize.onclick = div.doMinimize; // Mobile has extra close button let mclose = false; if( isMobile ) { mclose = document.createElement( 'div' ); mclose.className = 'MobileClose'; mclose.ontouchstart = function( e ) { let wo = div.windowObject; for( var a = 0; a < wo.childWindows.length; a++ ) { if( wo.childWindows[a]._window ) { if( wo.childWindows[a]._window.windowObject ) { wo.childWindows[a]._window.windowObject.close(); } else CloseView( wo.childWindows[a]._window ); } else { CloseView( wo.childWindows[a] ); } } wo.close(); return cancelBubble( e ); } } let close = document.createElement( 'div' ); close.className = 'Close'; close.onmousedown = function( e ) { return cancelBubble( e ); } close.ondragstart = function( e ) { return cancelBubble( e ); } close.onselectstart = function( e ) { return cancelBubble( e ); } close.onclick = function( e ) { if( !isMobile && !window.isTablet ) if( e.button != 0 ) return; // On mobile, you get a window menu instead if( window.isMobile && !window.isTablet ) { if( div.classList && div.classList.contains( 'Active' ) ) { _DeactivateWindows(); Workspace.redrawIcons(); return cancelBubble( e ); } } function executeClose() { // Clean movableWindows.. brute force let out = {}; for( let a in movableWindows ) { if( movableWindows[a] != div ) out[ a ] = movableWindows[ a ]; } movableWindows = out; viewContainer.classList.add( 'Closing' ); if( div.windowObject ) { let wo = div.windowObject; for( var a = 0; a < wo.childWindows.length; a++ ) { if( wo.childWindows[a]._window ) { if( wo.childWindows[a]._window.windowObject ) { wo.childWindows[a]._window.windowObject.close(); } else CloseView( wo.childWindows[a]._window ); } else { CloseView( wo.childWindows[a] ); } } wo.close(); } } // Only if we can! if( div.windowObject.close() === true ) { executeClose(); } } // Add all inDiv.appendChild( depth ); inDiv.appendChild( minimize ); if( zoom ) inDiv.appendChild( zoom ); inDiv.appendChild( close ); if( mclose ) inDiv.appendChild( mclose ); inDiv.appendChild( titleSpan ); div.depth = depth; div.zoom = zoom; div.close = close; div.titleBar = title; div.resize = resize; div.bottombar = bottombar; div.leftbar = leftbar; div.rightbar = rightbar; div.minimize = minimize; // For mobile if( iconSpan ) div.viewIcon = iconSpan; div.appendChild( title ); div.titleDiv = title; div.appendChild( resize ); div.appendChild( bottombar ); div.appendChild( leftbar ); div.appendChild( rightbar ); div.appendChild( snap ); // Empty the window menu contn.menu = false; // Null out blocker window (if we have one) contn.blocker = false; div.appendChild( contn ); // Add content div.appendChild( molay ); // Add move overlay // View groups let contentArea = false; self.viewGroups = {}; if( self.flags.viewGroups ) { let validGroups = false; self.viewGroups = []; let groups = self.flags.viewGroups; for( var a = 0; a < groups.length; a++ ) { let group = groups[ a ]; if( group.id ) { let g = document.createElement( 'div' ); g.className = 'ViewGroup'; if( group.mode == 'horizontalTabs' ) { let t = document.createElement( 'div' ); t.className = 'ViewGroupTabsHorizontal'; g.className += ' TabsHorizontal'; g.appendChild( t ); g.tabs = t; } if( group.xposition ) { if( group.xposition == 'left' ) g.classList.add( 'Left' ); else if( group.xposition == 'right' ) g.classList.add( 'Right' ); } if( group.yposition ) { if( group.yposition == 'top' ) g.classList.add( 'Top' ); else if( group.yposition == 'bottom' ) g.classList.add( 'Bottom' ); } if( group.width ) { if( group.width.indexOf && group.width.indexOf( '%' ) > 0 ) { let ex = parseInt( group.xposition == 'left' ? GetThemeInfo( 'ViewLeftBar' ).width : GetThemeInfo( 'ViewRightBar' ).width ); group.width = 'calc(' + group.width + ' - ' + ex + 'px)'; } g.style.width = group.width; } if( group.height ) { if( group.height.indexOf && group.height.indexOf( '%' ) > 0 ) { let ex = parseInt( GetThemeInfo( 'ViewBottom' ).height ) + parseInt( GetThemeInfo( 'ViewTitle' ).height ); group.height = 'calc(' + group.height + ' - ' + ex + 'px)'; } g.style.height = group.height; } self.viewGroups[ group.id ] = g; div.appendChild( g ); validGroups = true; } } if( validGroups ) div.classList.add( 'HasViewGroups' ); } // Max width and height let ww = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; let hh = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; movableWindowCount++; // Iterate global count of view windows // Make sure we count the windows in body if( movableWindowCount > 0 ) { if( window.windowCountTimeout ) { clearTimeout( window.windowCountTimeout ); delete window.windowCountTimeout; } document.body.setAttribute( 'windowcount', movableWindowCount ); } // Create event handler for view window div.content.events = new Array (); div.content.AddEvent = function( event, func ) { if( typeof(this.events[event]) == 'undefined' ) this.events[event] = []; this.events[event].push( func ); return func; } div.content.RemoveEvent = function( event, func ) { if( typeof( this.events[event] ) == 'undefined' ) return false; let o = []; let found = false; for( var a in this.events[event] ) { if( this.events[event][a] != func ) { o.push( this.events[event][a] ); } else found = true; } this.events[event] = o; return found; } // Assign the move layer div.moveoverlay = molay; // Also content must have it div.uniqueId = uniqueId; div.viewId = div.id; div.content.viewId = div.id; div.content.uniqueId = uniqueId; let windowResized = false; let leftSet = false; let topSet = false; let wp = GetWindowStorage( div.uniqueId ); let leftTopSpecial = flags.top == 'center' || flags.left == 'center'; // Check for special flags if( wp && ( wp.top >= 0 || wp.width >= 0 ) && !leftTopSpecial ) { if( window.isMobile ) { if ( self.flags && self.flags.memorize ) { height = wp.height; width = wp.width; ResizeWindow( div, width, height ); } } else { let sw = self.flags.screen && self.flags.screen.div ? self.flags.screen.div.offsetWidth : document.body.offsetWidth; if( wp.maximized ) { div.setAttribute( 'maximized', 'true' ); zoom.mode = 'maximized'; // Tell application if any if( window._getAppByAppId ) { let app = _getAppByAppId( div.applicationId ); if( app ) { app.sendMessage( { 'command': 'notify', 'method': 'setviewflag', 'flag': 'maximized', 'viewId': div.windowObject.viewId, 'value': true } ); } } } if( wp.top >= 0 && wp.top < hh ) { let wt = wp.top; div.style.top = wt + 'px'; topSet = true; } if( wp.left >= 0 && wp.left < ww ) { div.style.left = wp.left + 'px'; leftSet = true; } if ( self.flags && self.flags.memorize ) { height = wp.height; width = wp.width; ResizeWindow( div, width, height ); windowResized = true; } } } // Let's find the center! let mvw = 0, mvh = 0; if( Workspace && Workspace.screen ) { mvw = Workspace.screen.getMaxViewWidth(); mvh = Workspace.screen.getMaxViewHeight(); if( ge( 'desklet_0' ) ) { let att = ge( 'desklet_0' ).getAttribute( 'position' ); if( att && ( att.indexOf( 'bottom' ) >= 0 || att.indexOf( 'top' ) >= 0 ) ) mvh -= ge( 'desklet_0' ).offsetHeight; } } // TODO: See if we can move this dock dimension stuff inside getMax..() if( Workspace.mainDock ) { if( Workspace.mainDock.dom.classList.contains( 'Vertical' ) ) { mvw -= Workspace.mainDock.dom.offsetWidth; } else { mvh -= Workspace.mainDock.dom.offsetHeight; } } if( !isMobile ) { if( !leftSet && self.flags.left ) { leftSet = true; if( self.flags.left == 'center' ) { div.style.left = ( mvh >> 1 - ( height >> 1 ) ) + 'px'; } else { div.style.left = self.flags.left + 'px'; } } if( !topSet && self.flags.top ) { topSet = true; if( self.flags.top == 'center' ) { div.style.left = ( mvw >> 1 - ( width >> 1 ) ) + 'px'; } else { div.style.top = self.flags.top + 'px'; } } } // Only first window on shared apps, do full width and height if( ( window.isMobile && self.flags['mobileMaximised'] ) || ( window.movableWindows.length == 0 && IsSharedApp() ) ) { if( zoom ) { zoom.prevWidth = width; zoom.prevHeight = height; zoom.mode = 'maximized'; } width = window.innerWidth; height = window.innerHeight; div.style.height = height + title.offsetHeight + FUI_WINDOW_MARGIN + 'px'; div.setAttribute( 'maximized', 'true' ); // Tell application if any if( window._getAppByAppId ) { let app = _getAppByAppId( div.applicationId ); if( app ) { app.sendMessage( { 'command': 'notify', 'method': 'setviewflag', 'flag': 'maximized', 'viewId': div.windowObject.viewId, 'value': true } ); } } } // Add div to view container viewContainer.appendChild( div ); div.viewContainer = viewContainer; // Triggers ????? Please review this and add explaining comment if( parentWindow ) { parentWindow.addChildWindow( viewContainer ); } // Make sure it's correctly sized again div.windowObject = this; // Add the borders here if( !windowResized ) { if( !self.flags[ 'borderless' ] && GetThemeInfo( 'ViewTitle' ) ) { width += FUI_WINDOW_MARGIN << 1; height += parseInt( GetThemeInfo( 'ViewTitle' ).height ) + parseInt( GetThemeInfo( 'ViewBottom' ).height ); } ResizeWindow( div, width, height ); } // Add the window after all considerations // TODO: See if the real height is not properly calculated.. div.style.opacity = 0; div.setAttribute( 'created', 'created' ); // Insert into existing viewgroup let inGroup = false; if( self.flags.viewGroup ) { for( let a in movableWindows ) { let w = movableWindows[ a ].windowObject; if( w.viewId == self.flags.viewGroup.view ) { if( w.viewGroups[ self.flags.viewGroup.viewGroup ] ) { divParent = w.viewGroups[ self.flags.viewGroup.viewGroup ]; inGroup = true; self.flags.borderless = true; // Add tab if( divParent.tabs ) { let tab = document.createElement( 'div' ); tab.className = divParent.tabs.childNodes.length > 0 ? 'Tab' : 'TabActive'; if( !divParent.activeTab ) divParent.activeTab = tab; tab.innerHTML = self.flags.title; divParent.tabs.appendChild( tab ); tab.onclick = function( e ) { if( e.button != 0 ) return; _WindowToFront( div ); for( let b = 0; b < divParent.tabs.childNodes.length; b++ ) { if( tab == divParent.tabs.childNodes[ b ] ) { divParent.tabs.childNodes[ b ].className = 'TabActive'; divParent.activeTab = tab; } else { divParent.tabs.childNodes[ b ].className = 'Tab'; } } } } break; } } } } // Append view window to parent if( flags.liveView && Workspace.dashboard && Workspace.dashboard.liveViews ) { Workspace.dashboard.liveViews.appendChild( viewContainer ); } else if( flags.dockable && ge( 'DockableWindowContainer' ) ) ge( 'DockableWindowContainer' ).appendChild( viewContainer ); else { divParent.appendChild( viewContainer ); } if( inGroup ) { ResizeWindow( divParent.parentNode ); setTimeout( function() { divParent.activeTab.onclick(); }, 100 ); } // Don't show the view window if it's hidden if( ( typeof( flags.hidden ) != 'undefined' && flags.hidden ) || ( typeof( flags.invisible ) != 'undefined' && flags.invisible ) ) { div.viewContainer.style.visibility = 'hidden'; div.viewContainer.style.pointerEvents = 'none'; div.viewContainer.classList.add( 'Hidden' ); } setTimeout( function(){ div.style.opacity = 1; } ); // So, dont creating, behave normally now setTimeout( function(){ div.setAttribute( 'created', '' ); }, 300 ); // Turn calculations on viewContainer.style.display = ''; // Once the view appears on screen, again, constrain it ConstrainWindow( div ); // First to generate, second to test PollTaskbar(); // First resize RefreshWindow( div, true ); if( window.isMobile || window.isTablet ) { // window move if( window.isTablet ) { // For mobile and tablets if( !window.isMobile ) { title.addEventListener( 'touchstart', function( e ) { e.clientX = e.touches[0].clientX e.clientY = e.touches[0].clientY; e.button = 0; window.mouseDown = 1; // Window mode if( title.onmousedown ) { title.onmousedown( e ); } _ActivateWindow( div ); } ); } else { title.addEventListener( 'touchmove', function( evt ) { touchMoveWindow( evt ); }); } } // Resize touch events.... ----------------------------------------- let winTouchStart = [ 0, 0 ]; let winTouchDowned = winTouchEnd = 0; resize.addEventListener('touchstart', function( evt ) { cancelBubble( evt ); winTouchStart = [ evt.touches[0].clientX, evt.touches[0].clientY ]; winTouchDowned = evt.timeStamp; } ); resize.addEventListener('touchmove', function( evt ) { cancelBubble( evt ); if( evt.target && evt.target.offsetParent ) evt.target.offH = evt.target.offsetParent.clientHeight; touchResizeWindow(evt); } ); resize.addEventListener( 'touchend', function( evt ) { cancelBubble( evt ); } ); bottombar.addEventListener('touchstart', function( evt ) { cancelBubble( evt ); winTouchStart = [ evt.touches[0].clientX, evt.touches[0].clientY ]; winTouchDowned = evt.timeStamp; } ); bottombar.addEventListener('touchmove', function( evt ) { cancelBubble( evt ); if( evt.target && evt.target.offsetParent ) evt.target.offH = evt.target.offsetParent.clientHeight; touchResizeWindow(evt); } ); bottombar.addEventListener( 'touchend', function( evt ) { cancelBubble( evt ); } ); //close --- ## --- ## --- ## --- ## --- ## --- ## --- ## --- ## --- ## --- ## --- ## --- ## --- ## close.addEventListener( 'touchstart', function( evt ) { cancelBubble( evt ); winTouchStart = [ evt.touches[0].clientX, evt.touches[0].clientY, (evt.target.hasAttribute('class') ? evt.target.getAttribute('class') : '') ]; winTouchEnd = winTouchStart; winTouchDowned = evt.timeStamp; } ); } // Ok, if no window position is remembered.. place it somewhere else if( !wp ) { ConstrainWindow( div ); } /* function shal be called by bottombar or resize... */ // TODO: constrain window please use ResizeWindow() function touchResizeWindow(evt) { if( !evt.target.offH ) evt.target.offH = evt.target.offsetParent.clientHeight; //not too small and not too high... let newHeight = Math.min( Workspace.screenDiv.clientHeight - 72 - evt.target.offsetParent.offsetTop, Math.max(80,evt.touches[0].clientY - evt.target.offsetParent.offsetTop ) ); evt.target.offsetParent.style.height = newHeight + 'px'; evt.target.offsetParent.lastHeight = newHeight; // Only tablets can move if( window.isTablet ) { let newWidth = Math.min( Workspace.screenDiv.clientWidth - evt.target.offsetParent.offsetLeft, evt.touches[0].clientX - evt.target.offsetParent.offsetLeft ); evt.target.offsetParent.style.width = newWidth + 'px'; evt.target.offsetParent.lastWidth = newWidth; } } function touchMoveWindow( evt ) { let nx = evt.touches[0].clientX; let ny = evt.touches[0].clientY; window.mouseDown = 1; movableListener( evt, { mouseX: nx, mouseY: ny } ); } // Remove window borders if( !isMobile ) { if( typeof( flags.borderless ) != 'undefined' && flags.borderless == true ) { title.style.position = 'absolute'; title.style.height = '0px'; title.style.overflow = 'hidden'; resize.style.display = 'none'; div.leftbar.style.display = 'none'; div.rightbar.style.display = 'none'; div.bottombar.style.display = 'none'; div.content.style.left = '0'; div.content.style.right = '0'; div.content.style.top = '0'; div.content.style.right = '0'; div.content.style.bottom = '0'; } } if( typeof( flags.resize ) != 'undefined' && flags.resize == false ) { resize.style.display = 'none'; if( zoom ) zoom.style.display = 'none'; } if( typeof( flags.login ) != 'undefined' && flags.login == true ) { resize.style.display = 'none'; if( zoom ) zoom.style.display = 'none'; depth.style.display = 'none'; } // Start maximized if( window.isMobile ) { if( !flags.minimized ) { this.setFlag( 'maximized', true ); div.setAttribute( 'maximized', 'true' ); // Tell application if any if( window._getAppByAppId ) { let app = _getAppByAppId( div.applicationId ); if( app ) { app.sendMessage( { 'command': 'notify', 'method': 'setviewflag', 'flag': 'maximized', 'viewId': div.windowObject.viewId, 'value': true } ); } } } else { } } // Handle class if ( !div.classList.contains( 'View' ) ) div.classList.add( 'View' ); // (handle z-index) div.viewContainer.style.zIndex = movableHighestZindex++; div.style.zIndex = div.viewContainer.style.zIndex; // If the current window is an app, move it to front.. (unless new window is a child window) if( window.friend && Friend.currentWindowHover ) Friend.currentWindowHover = false; // Reparse! We may have forgotten some things self.parseFlags( flags ); // Only activate if needed if( !flags.minimized && !flags.openSilent && !flags.invisible ) { _ActivateWindow( div ); _WindowToFront( div ); } // Move workspace to designated position if( !flags.screen || flags.screen == Workspace.screen ) { if( self.workspace > 0 ) { self.sendToWorkspace( self.workspace ); } } // Remove menu on calendar if( !flags.minimized && Workspace.calendarWidget ) Workspace.calendarWidget.hide(); if( Workspace.dashboard && typeof( hideDashboard ) != 'undefined' ) { if( !self.flags.dialog ) { hideDashboard(); } } } // Send window to different workspace this.sendToWorkspace = function( wsnum ) { if( isMobile ) return; // Windows on own screen ignores the virtual workspaces if( this.flags && this.flags.screen && this.flags.screen != Workspace.screen ) return; if( wsnum != 0 && ( wsnum < 0 || wsnum > globalConfig.workspacecount - 1 ) ) { return; } let wn = this._window.parentNode; let pn = wn.parentNode; // Move the viewcontainer wn.viewContainer.style.left = ( Workspace.screen.getMaxViewWidth() * wsnum ) + 'px'; wn.workspace = wsnum; cleanVirtualWorkspaceInformation(); // Just clean the workspace info // Done moving if( this.flags && this.flags.screen ) { let maxViewWidth = this.flags.screen.getMaxViewWidth(); this.workspace = wsnum; _DeactivateWindow( this._window.parentNode ); PollTaskbar(); } } // Set content on window this.setContent = function( content, cbk ) { // Safe content without any scripts or styles! SetWindowContent( this._window, this.cleanHTMLData( content ) ); if( cbk ) cbk(); } this.fullscreen = function( val ) { if( val === true || val === false ) { this.setFlag( 'fullscreenenabled', val ); } let fullscreen = this.getFlag( 'fullscreenenabled' ); if( fullscreen ) { if( this.iframe ) { Workspace.fullscreen( this.iframe ); } else { Workspace.fullscreen( this._window ); } } else { document.exitFullscreen(); } } // Set content (securely!) in a sandbox, callback when completed this.setContentIframed = function( content, domain, packet, callback ) { if( !domain ) { domain = document.location.href + ''; domain = domain.split( 'index.html' ).join ( 'sandboxed.html' ); domain = domain.split( 'app.html' ).join( 'sandboxed.html' ); } // Oh we have a conf? if( this.conf ) { if ( Workspace.sessionId ) { domain += '/system.library/module/?module=system&command=sandbox' + '&sessionid=' + Workspace.sessionId + '&conf=' + JSON.stringify( this.conf ); } else { domain += '/system.library/module/?module=system&command=sandbox' + '&authid=' + this.authId + '&conf=' + JSON.stringify( this.conf ); } if( this.getFlag( 'noevents' ) ) domain += '&noevents=true'; } else if( domain.indexOf( 'sandboxed.html' ) <= 0 ) { domain += '/webclient/sandboxed.html'; if( this.getFlag( 'noevents' ) ) domain += '?noevents=true'; } // Make sure scripts can be run after all resources has loaded if( content && content.match ) { let r; while( r = content.match( /\]*?)\>([\w\W]*?)\<\/script\>/i ) ) content = content.split( r[0] ).join( '' + r[2] + '' ); } else { content = ''; } let c = this._window; if( c && c.content ) c = c.content; if( c ) { c.innerHTML = ''; } let ifr = document.createElement( _viewType ); ifr.applicationId = self.applicationId; ifr.authId = self.authId; ifr.applicationName = self.applicationName; ifr.applicationDisplayName = self.applicationDisplayName; putSandboxFlags( ifr, getSandboxFlags( this, DEFAULT_SANDBOX_ATTRIBUTES ) ); ifr.view = this._window; ifr.className = 'Content Loading'; if( this.flags.transparent ) { ifr.setAttribute( 'allowtransparency', 'true' ); ifr.style.backgroundColor = 'transparent'; } ifr.id = 'sandbox_' + this.viewId; ifr.src = domain; let view = this; this.iframe = ifr; ifr.onfocus = function( e ) { if( !ifr.view.parentNode.classList.contains( 'Active' ) ) { // Don't steal focus! ifr.blur(); window.blur(); window.focus(); } } if( packet.applicationId ) this._window.applicationId = packet.applicationId; ifr.onload = function() { // Assign views to each other to allow cross window scripting // TODO: This could be a security hazard! Remember to use security // domains! let parentIframeId = false; let instance = Math.random() % 100; if( ifr.applicationId ) { for( let a = 0; a < Workspace.applications.length; a++ ) { let app = Workspace.applications[a]; if( app.applicationId == ifr.applicationId ) { for( let b in app.windows ) { // Ah we found our parent view if( self.parentViewId == b ) { let win = app.windows[b]; parentIframeId = 'sandbox_' + b; break; } } // Link to application sandbox if( !parentIframeId ) { parentIframeId = 'sandbox_' + app.applicationId; } break; } } } let msg = {}; if( packet ) for( let a in packet ) msg[a] = packet[a]; msg.command = 'setbodycontent'; msg.cachedAppData = _applicationBasics; msg.dosDrivers = Friend.dosDrivers; msg.parentSandboxId = parentIframeId; msg.locale = Workspace.locale; // Override the theme if( view.getFlag( 'theme' ) ) { msg.theme = view.getFlag( 'theme' ); } if( Workspace.themeData ) { msg.themeData = Workspace.themeData; } // Authid is important, should not be left out if it is available if( !msg.authId ) { if( ifr.authId ) msg.authId = ifr.authId; else if( GetUrlVar( 'authid' ) ) msg.authId = GetUrlVar( 'authid' ); } // Use this if the packet has it if( !msg.sessionId ) { if( packet.sessionId ) msg.sessionId = packet.sessionId; } msg.registerCallback = addWrapperCallback( function() { if( callback ) callback(); } ); if( packet.filePath ) { msg.data = content.split( /progdir\:/i ).join( packet.filePath ); } else msg.data = content; if( self.flags.screen ) msg.screenId = self.flags.screen.externScreenId; msg.data = msg.data.split( /system\:/i ).join( '/webclient/' ); if( !msg.origin ) msg.origin = '*'; //TODO: Should be fixed document.location.href; ifr.contentWindow.postMessage( JSON.stringify( msg ), '*' ); } c.appendChild( ifr ); } // Sets content on a safe window (using postmessage), callback when completed this.setContentById = function( content, packet, callback ) { if( this.iframe ) { let msg = {}; if( packet ) for( var a in packet ) msg[a] = packet[a]; msg.command = 'setcontentbyid'; this.iframe.contentWindow.postMessage( JSON.stringify( msg ), Workspace.protocol + '://' + this.iframe.src.split( '//' )[1].split( '/' )[0] ); } if( callback ) callback(); } // old hello function this.setSandboxedUrl = function( conf ) { let self = this; let appName = self.applicationName; let origin = '*'; // TODO: Should be this Doors.runLevels[ 0 ].domain; let domain = Doors.runLevels[ 1 ].domain; domain = domain.split( '://' )[1]; let appBase = '/webclient/apps/' + appName + '/'; let protocol = Workspace.protocol + '://'; let filePath = protocol + domain + appBase + conf.filePath; let container = self._window.content || self._window; let iframe = document.createElement( _viewType ); iframe.applicationId = self.applicationId; iframe.authId = self.authId; iframe.applicationName = self.applicationName; iframe.applicationDisplayName = self.applicationDisplayName; if( typeof friendApp == 'undefined' ) putSandboxFlags( iframe, getSandboxFlags( this, DEFAULT_SANDBOX_ATTRIBUTES ) ); // allow same origin is probably not a good idea, but a bunch other stuff breaks, so for now.. iframe.referrerPolicy = 'origin'; self._window.applicationId = conf.applicationId; // needed for View.close to work self._window.authId = conf.authId; self.iframe = iframe; container.innerHTML = ''; container.appendChild( iframe ); let src = filePath + '?base=' + appBase + '&id=' + self.viewId + '&applicationId=' + self.applicationId + '&authId=' + conf.authId + '&origin=' + origin + '&domain=' + domain + '&locale=' + Workspace.locale + '&theme=' + Doors.theme; if ( conf.viewTheme && conf.viewTheme.length ) src += '&viewTheme=' + conf.viewTheme; iframe.src = src; } // Sets rich content in a safe iframe this.setRichContent = function( content ) { if( !this._window ) return; // Rich content still can't have any scripts! content = this.removeScriptsFromData( content ); if( !this._window ) return; let eles = this._window.getElementsByTagName( _viewType ); let ifr = false; if( eles[0] ) ifr = eles[0]; else { ifr = document.createElement( _viewType ); this._window.appendChild( ifr ); } if( this.flags.transparent ) { ifr.setAttribute( 'allowtransparency', 'true' ); ifr.style.backgroundColor = 'transparent'; } ifr.applicationId = self.applicationId; ifr.applicationName = self.applicationName; ifr.applicationDisplayName = self.applicationDisplayName; putSandboxFlags( ifr, getSandboxFlags( this, DEFAULT_SANDBOX_ATTRIBUTES ) ); ifr.authId = self.authId; ifr.onload = function() { ifr.contentWindow.document.body.innerHTML = content; } if( this.flags.requireDoneLoading ) { ifr.className = 'Loading'; } ifr.onload(); this.isRich = true; this.iframe = ifr; } // Sets rich content in a safe iframe this.setJSXContent = function( content, appName ) { let w = this; if( !this._window ) return; let eles = this._window.getElementsByTagName( _viewType ); let ifr = false; let appended = false; if( eles[0] ) ifr = eles[0]; else { ifr = document.createElement( _viewType ); appended = true; } // Load the sandbox if( this.conf ) { ifr.src = '/system.library/module/?module=system&command=sandbox' + '&sessionid=' + Workspace.sessionId + '&conf=' + JSON.stringify( this.conf ) + ( this.getFlag( 'noevents' ) ? '&noevents=true' : '' ); } // Just give a dumb sandbox else { ifr.src = '/webclient/sandboxed.html' + ( this.getFlag( 'noevents' ) ? '?noevents=true' : '' ); } // Register name and ID ifr.applicationName = appName; ifr.applicationId = appName + '-' + (new Date()).getTime(); Doors.applications.push( ifr ); // Add a loaded script ifr.onload = function() { // Get document let doc = ifr.contentWindow.document; let jsx = doc.createElement( 'script' ); jsx.innerHTML = content; ifr.contentWindow.document.getElementsByTagName( 'head' )[0].appendChild( jsx ); let msg = { command: 'initappframe', base: '/', applicationId: ifr.applicationId, filePath: '/webclient/jsx/', origin: '*', // TODO: Should be this - document.location.href, viewId: w.externViewId ? w.externViewId : w.viewId, clipboard: Friend.clipboard }; // Set theme if( w.getFlag( 'theme' ) ) msg.theme = w.getFlag( 'theme' ); if( Workspace.themeData ) msg.themeData = Workspace.themeData; ifr.contentWindow.postMessage( JSON.stringify( msg ), Workspace.protocol + '://' + ifr.src.split( '//' )[1].split( '/' )[0] ); } // Register some values this.isRich = true; this.iframe = ifr; // If we need to append this one if( appended ) this._window.appendChild( ifr ); } // Sets rich content in a safe iframe this.setRichContentUrl = function( url, base, appId, filePath, callback ) { let view = this; if( !base ) base = '/'; if( !this._window ) return; let eles = this._window.getElementsByTagName( _viewType ); let ifr = false; let w = this; if( eles[0] ) ifr = eles[0]; else { ifr = document.createElement( _viewType ); } // Register the app id so we can talk this._window.applicationId = appId; ifr.applicationId = self.applicationId; ifr.applicationName = self.applicationName; ifr.applicationDisplayName = self.applicationDisplayName; ifr.authId = self.authId; putSandboxFlags( ifr, getSandboxFlags( this, DEFAULT_SANDBOX_ATTRIBUTES ) ); let conf = this.flags || {}; if( this.flags && this.flags.allowScrolling ) { ifr.setAttribute( 'scrolling', 'yes' ); } else { ifr.setAttribute( 'scrolling', 'no' ); } if ( conf.fullscreenenabled ) ifr.setAttribute( 'allowfullscreen', 'true' ); if( this.flags.transparent ) { ifr.setAttribute( 'allowtransparency', 'true' ); ifr.style.backgroundColor = 'transparent'; } ifr.setAttribute( 'seamless', 'true' ); ifr.style.border = '0'; ifr.style.position = 'absolute'; ifr.style.top = '0'; ifr.style.left = '0'; ifr.style.width = '100%'; ifr.style.height = '100%'; if( this.flags.requireDoneLoading ) { ifr.className = 'Loading'; } // Find our friend // TODO: Only send postmessage to friend targets (from our known origin list (security app)) // Fix url if( url.indexOf( 'http' ) != 0 ) { let t = document.location.href.match( /(http[s]{0,1}\:\/\/)(.*?)\//i ); url = t[1] + t[2] + url; } let targetP = url.match( /(http[s]{0,1}\:\/\/.*?)\//i ); let friendU = document.location.href.match( /http[s]{0,1}\:\/\/(.*?)\//i ); let targetU = url.match( /http[s]{0,1}\:\/\/(.*?)\//i ); if( friendU && friendU.length > 1 ) friendU = friendU[1]; if( targetU && targetU.length > 1 ) { targetP = targetP[1]; targetU = targetU[1]; } friendU = Trim( friendU ); if( typeof friendApp == 'undefined' && ( friendU.length || friendU != targetU || !targetU ) ) putSandboxFlags( ifr, getSandboxFlags( this, DEFAULT_SANDBOX_ATTRIBUTES ) ); // Allow sandbox flags let sbx = ifr.getAttribute( 'sandbox' ) ? ifr.getAttribute( 'sandbox' ) : ''; sbx = ('' + sbx).split( ' ' ); if( this.flags && this.flags.allowPopups ) { let found = false; for( var a = 0; a < sbx.length; a++ ) { if( sbx[a] == 'allow-popups' ) { found = true; } } if( !found ) sbx.push( 'allow-popups' ); if( typeof friendApp == 'undefined' ) ifr.setAttribute( 'sandbox', sbx.join( ' ' ) ); } // Special insecure mode (use with caution!) if( this.limitless && this.limitless === true ) { let sb = ifr.getAttribute( 'sandbox' ); if( !sb ) sb = DEFAULT_SANDBOX_ATTRIBUTES; sb += ' allow-top-navigation'; ifr.setAttribute( 'sandbox', sb ); } ifr.onload = function( e ) { if( friendU && ( friendU == targetU || !targetU ) ) { let msg = { command : 'initappframe', base : base, applicationId : appId, filePath : filePath, origin : '*', // TODO: Should be this - document.location.href, viewId : w.externViewId, authId : self.authId, theme : Workspace.theme, fullscreenenabled : conf.fullscreenenabled, clipboard : Friend.clipboard, viewConf : self.args.viewConf }; // Override the theme if( view.getFlag( 'theme' ) ) msg.theme = view.getFlag( 'theme' ); if( Workspace.themeData ) msg.themeData = Workspace.themeData; try { // TODO: Why we used protocol was for security domains - may be deprecated //ifr.contentWindow.postMessage( JSON.stringify( msg ), Workspace.protocol + '://' + ifr.src.split( '//' )[1].split( '/' )[0] ); ifr.contentWindow.postMessage( JSON.stringify( msg ), '*' ); } catch(e) { console.log('could not send postmessage to contentwindow!'); } ifr.loaded = true; } if( callback ) { callback(); } } // Commented out because it stops https sites from being viewed on f.eks localhost without ssl /*if( this.conf && url.indexOf( Workspace.protocol + '://' ) != 0 ) { let cnf = this.conf; if( typeof( this.conf ) == 'object' ) cnf = ''; ifr.src = '/system.library/module/?module=system&command=sandbox' + '&sessionid=' + Workspace.sessionId + '&url=' + encodeURIComponent( url ) + '&conf=' + cnf; } else {*/ ifr.src = url; /*}*/ this.isRich = true; this.iframe = ifr; // Add after options set if( !eles[0] ) this._window.appendChild( ifr ); this.initOnMessageCallback(); } this.showBackButton = function( visible, cbk ) { if( !isMobile ) return; if( visible ) { self.mobileBack.classList.add( 'Showing' ); self.viewIcon.classList.add( 'MobileBackHidesIt' ); if( cbk ) { self.mobileBack.ontouchstart = function( e ) { cbk( e ); } } } else { self.mobileBack.classList.remove( 'Showing' ); self.viewIcon.classList.remove( 'MobileBackHidesIt' ); } } // Send a message this.sendMessage = function( dataObject, event ) { if( !event ) event = window.event; // Check if the iframe is ready to receive a message if( this.iframe && this.iframe.loaded && this.iframe.contentWindow ) { let u = Workspace.protocolUrl + this.iframe.src.split( '//' )[1].split( '/' )[0]; //let origin = event && // event.origin && event.origin != 'null' && event.origin.indexOf( 'wss:' ) != 0 ? event.origin : '*'; // * used to be u; // TODO: Fix this with security let origin = '*'; if( !dataObject.applicationId && this.iframe.applicationId ) { dataObject.applicationId = this.iframe.applicationId; dataObject.authId = this.iframe.authId; dataObject.applicationName = this.iframe.applicationName; dataObject.applicationDisplayName = this.iframe.applicationDisplayName; } if( !dataObject.type ) dataObject.type = 'system'; this.iframe.contentWindow.postMessage( JSON.stringify( dataObject ), origin ); } // No iframe? else if( !this.iframe ) { return false; } else { if( !this.sendQueue ) this.sendQueue = []; this.sendQueue.push( dataObject ); } return true; } // Receive a message specifically for this view. this.initOnMessageCallback = function() { if( self.onMessage && self.iframe && !window.onmessage ) { let b = self.iframe.getAttribute( 'sandbox' ); window.onmessage = function( msg ) { if( msg && msg.isTrusted && msg.data && msg.data.type ) { if( self.iframe.contentWindow == msg.source ) { self.onMessage( msg.data ); // Enforce prevailing sandbox attributes self.iframe.setAttribute( 'sandbox', b ); } } }; } } // Send messages to window that hasn't been sent because iframe was not loaded this.executeSendQueue = function() { if ( !this.sendQueue || !this.sendQueue.length ) return; if( this.executingSendQueue || !this.iframe ) return; this.executingSendQueue = true; for( let a = 0; a < this.sendQueue.length; a++ ) { let msg = this.sendQueue[ a ]; this.sendMessage( msg ); } this.sendQueue = []; this.executingSendQueue = false; } // Get content element this.getContentElement = function() { if( this.isRich && this.iframe ) { return this.iframe.contentWindow.document.body; } else return this._window; return false; } // Focus on an able element this.focusOnElement = function( identifier, flag ) { let ele = this.getSubContent( identifier, flag ); if( ele && ele.focus ) ele.focus(); } // Get some subcontent (.class or #id) this.getContentById = function( identifier, flag ) { let node = this.getContentElement(); if( !node ) return false; let ele = node.getElementsByTagName( '*' ); let cnt = false; let idn = identifier.substr( 0, 1 ); let key = identifier.substr( 1, identifier.length - 1 ); let results = []; if( idn == '.' ) { for( var a = 0; a < ele.length; a++ ) { if( ele[a].className && ele[a].className == key ) { results.push( ele[a] ); } } } else { let fn = key; if( idn != '#' ) fn = identifier; for( var a = 0; a < ele.length; a++ ) { if( ele[a].id == idn ) { results.push( ele[a] ); } } } if( !results.length) return false; if( flag == 'last-child' ) return results[results.length-1]; return results[0]; } // Set content on sub element // TODO: Deprecated! Remove completely! this.setSubContent = function( identifier, flag, content ) { let cnt = this.getSubContent( identifier, flag ); if( !cnt ) return; // Safe content without any scripts or styles! cnt.innerHTML = this.cleanHTMLData( content ); } // Sets a property value this.setAttributeById = function( packet ) { if( this.iframe ) { let msg = {}; if( packet ) for( var a in packet ) msg[a] = packet[a]; msg.command = 'setattributebyid'; this.iframe.contentWindow.postMessage( JSON.stringify( msg ), Workspace.protocol + '://' + this.iframe.src.split( '//' )[1].split( '/' )[0] ); } } // Activate window this.activate = function( force ) { if( isMobile && !force && this.flags.minimized ) { return; } _ActivateWindow( this._window.parentNode ); } // Move window to front this.toFront = function( flags ) { if( this.flags.minimized ) { return; } if( !( flags && flags.activate === false ) ) _ActivateWindow( this._window.parentNode ); _WindowToFront( this._window.parentNode ); } // Close a view window this.close = function ( force ) { if( isMobile ) Workspace.exitMobileMenu(); let c = this._window; if( c && c.content ) c = c.content; // Remember window position if( this.flags.memorize ) { this._window.parentNode.memorize(); } // Close blockers if( this._window && this._window.blocker ) { this._window.blocker.close(); } if( !force && this._window && this._window.applicationId ) { // Send directly to the view let app = this._window.applicationId ? findApplication( this._window.applicationId ) : false; if( c.getElementsByTagName( _viewType ).length ) { let twindow = this; // Notify application let msg = { type: 'system', command: 'notify', method: 'closeview', applicationId: this._window.applicationId, viewId: self.viewId }; // Post directly to the app if( app ) { app.contentWindow.postMessage( JSON.stringify( msg ), '*' ); } // Post directly to the window else { this.sendMessage( msg ); } // Execute any ambient onClose method if( this.onClose ) this.onClose(); if( this.eventSystemClose ) // <- system call { for( let a = 0; a < this.eventSystemClose.length; a++ ) { this.eventSystemClose[a](); } } return; } else if( this.parentViewId ) { let v = GetWindowById( this.parentViewId ); if( v && v.windowObject ) { let msg = { type: 'system', command: 'notify', method: 'closeview', applicationId: this._window.applicationId, viewId: this.viewId }; v.windowObject.sendMessage( msg ); } return false; } else if( app ) { // Notify application let msg = { type: 'system', command: 'notify', method: 'closeview', applicationId: this._window.applicationId, viewId: self.viewId }; app.sendMessage( msg ); return; } } CloseView( this._window ); if( this.onClose ) this.onClose(); if( this.eventSystemClose ) // <- system call { for( let a = 0; a < this.eventSystemClose.length; a++ ) { this.eventSystemClose[a](); } } return true; } // Put a loading animation on window this.loadingAnimation = function () { WindowLoadingAnimation( this._window ); } // Set the main view of app this.setMainView = function( set ) { if( !this.applicationId ) return; if( !window._getAppByAppId ) return; let app = _getAppByAppId( this.applicationId ); if( !app ) return; this.flags.mainView = set; // Set main view if( set ) { app.mainView = this; } // Unset main view (pick the next view if possible else { // Find new main view app.mainView = null; for( var a in app.windows ) { if( app.windows[ a ] != this ) { app.mainView = app.windows[ a ]; app.mainView.flags.mainView = true; } } } // Update other windows with the new main view! if( app.mainView ) { for( var a in app.windows ) { if( app.windows[ a ] != app.mainView ) { app.windows[ a ].mainView = false; app.windows[ a ].flags.mainView = false; app.windows[ a ].parentView = app.mainView; } } } } // Set a window flag this.setFlag = function( flag, value ) { // References to the view window let content = viewdiv = false; if( this._window ) { content = this._window; viewdiv = content.parentNode; } // Set the flag switch( flag ) { case 'context': for( let a in movableWindows ) { if( a == value ) { this.currentContext = [ movableWindows[ a ] ]; this.flags.context = value; break; } } break; case 'mainView': this.setMainView( value ); this.flags.mainView = value; break; case 'noquickmenu': if( value ) this._window.classList.add( 'NoQuickmenu' ); else this._window.classList.remove( 'NoQuickmenu' ); this.flags.noquickmenu = value; break; case 'singletask': this.flags.singletask = value; break; // Standard dialog has preset width and height case 'standard-dialog': // Dialog is treated only like a dialog if( viewdiv.parentNode ) { if( value ) { viewdiv.parentNode.classList.add( 'StandardDialog' ); } else { viewdiv.parentNode.classList.remove( 'StandardDialog' ); } } this.flags[ 'standard-dialog' ] = value; case 'dialog': this.flags[ 'dialog' ] = value; if( viewdiv ) { if( value ) { viewdiv.parentNode.classList.add( 'Dialog' ); if( flag == 'dialog' ) { viewdiv.style.left = 'calc(50% - ' + ( viewdiv.offsetWidth >> 1 ) + 'px)'; viewdiv.style.top = 'calc(50% - ' + ( viewdiv.offsetHeight >> 1 ) + 'px)'; } } else { viewdiv.parentNode.classList.remove( 'Dialog' ); } } break; case 'clickableTitle': this.flags.clickableTitle = value; break; case 'scrollable': if( content ) { if( value == true ) content.className = 'Content IconWindow'; else content.className = 'Content'; if( value == true ) { viewdiv.classList.add( 'Scrolling' ); } else { viewdiv.classList.remove( 'Scrolling' ); } } this.flags.scrollable = value; break; case 'left': this.flags.left = value; if( viewdiv ) { value += ''; value = value.split( 'px' ).join( '' ); if( !isMobile ) { viewdiv.style.left = ( value.indexOf( '%' ) > 0 || value.indexOf( 'vw' ) > 0 ) ? value : ( value + 'px' ); } } break; case 'top': this.flags.top = value; if( viewdiv ) { value += ''; value = value.split( 'px' ).join( '' ); if( !isMobile ) { viewdiv.style.top = ( value.indexOf( '%' ) > 0 || value.indexOf( 'vh' ) > 0 ) ? value : ( value + 'px' ); } } break; case 'max-width': this.flags['max-width'] = value; if( viewdiv ) { viewdiv.style.maxWidth = value; ResizeWindow( viewdiv, ( flag == 'width' ? value : null ), ( flag == 'height' ? value : null ) ); RefreshWindow( viewdiv ); } break; case 'max-height': this.flags['max-height'] = value; if( viewdiv ) { viewdiv.style.maxHeight = value; ResizeWindow( viewdiv, ( flag == 'width' ? value : null ), ( flag == 'height' ? value : null ) ); RefreshWindow( viewdiv ); } break; case 'min-width': this.flags[ 'min-width' ] = value; if( viewdiv ) { viewdiv.style.minWidth = value; ResizeWindow( viewdiv, ( flag == 'width' ? value : null ), ( flag == 'height' ? value : null ) ); RefreshWindow( viewdiv ); } break; case 'min-height': this.flags[ 'min-height' ] = value; if( viewdiv ) { viewdiv.style.minHeight = value; ResizeWindow( viewdiv, ( flag == 'width' ? value : null ), ( flag == 'height' ? value : null ) ); RefreshWindow( viewdiv ); } break; case 'width': case 'height': if( value == null ) value = 0; this.flags[ flag ] = value; if( viewdiv ) { if( value == 'max' ) { if( flag == 'width' ) { value = ( this.flags && this.flags.screen ) ? this.flags.screen.getMaxViewWidth() : window.innerWidth; } else { value = ( this.flags && this.flags.screen ) ? this.flags.screen.getMaxViewHeight() : window.innerHeight; } } ResizeWindow( viewdiv, ( flag == 'width' ? value : null ), ( flag == 'height' ? value : null ) ); RefreshWindow( viewdiv ); } break; case 'resize': this.flags[ flag ] = value; ResizeWindow( viewdiv ); RefreshWindow( viewdiv ); break; case 'loadinganimation': if( value == true ) { this.loadingAnimation(); } break; case 'hidden': case 'invisible': if( viewdiv ) { if( value == 'true' || value == true ) { // Fade out!! if( value === false ) { setTimeout( function(){ viewdiv.viewContainer.style.visibility = 'hidden'; viewdiv.viewContainer.style.pointerEvents = 'none'; }, 500 ); } else { // Don't show the view window if it's hidden viewdiv.viewContainer.style.visibility = 'hidden'; viewdiv.viewContainer.style.pointerEvents = 'none'; } viewdiv.viewContainer.style.opacity = 0; } else { // Fade in!! if( value === true ) { setTimeout( function(){ viewdiv.viewContainer.style.opacity = 1; }, 1 ); } // Don't show the view window if it's hidden else viewdiv.viewContainer.style.opacity = 1; viewdiv.viewContainer.style.visibility = ''; viewdiv.viewContainer.style.pointerEvents = ''; } if( flag == 'invisible' ) { if( value == true || value == 'true' ) { viewdiv.viewContainer.classList.add( 'Invisible' ); } else { viewdiv.viewContainer.classList.remove( 'Invisible' ); } } ResizeWindow( viewdiv ); RefreshWindow( viewdiv ); } this.flags[ flag ] = value; PollTaskbar(); break; case 'liveView': if( value == true || value == 'true' ) { viewdiv.viewContainer.classList.add( 'Liveview' ); } else { viewdiv.viewContainer.classList.remove( 'Liveview' ); } break; case 'screen': this.flags.screen = value; break; case 'minimized': if( viewdiv ) { if( value == 'true' || value == true ) { if( viewdiv.doMinimize ) { viewdiv.doMinimize(); } } else if( value == 'false' || value == false ) { _ActivateWindow( viewdiv ); _WindowToFront( viewdiv ); } } this.flags.minimized = value; break; case 'title': if( !Trim( value ) ) value = i18n( 'i18n_untitled_window' ); SetWindowTitle( viewdiv, value ); this.flags.title = value; break; case 'theme': this.flags.theme = value; break; case 'fullscreenenabled': this.flags.fullscreenenabled = value; break; case 'transparent': this.flags.transparent = value; if( viewdiv ) { viewdiv.setAttribute( 'transparent', value ? 'transparent': '' ); } break; // TODO: Use it when ready // Allow for dropping files in a secure manner case 'securefiledrop': this.flags.securefiledrop = value; break; case 'windowInactive': case 'windowActive': if( isMobile ) return false; // Check window color this.flags[ flag ] = value; break; case 'sidebarManaged': if( viewdiv && viewdiv.parentNode ) { if( value == 'true' || value == true ) viewdiv.parentNode.classList.add( 'SidebarManaged' ); else viewdiv.parentNode.classList.remove( 'SidebarManaged' ); } this.flags[ flag ] = value; break; // Takes all flags default: this.flags[ flag ] = value; return; } // Some values are not set on application if( flag == 'screen' ) return; // Support dashboard if( window.Workspace && Workspace.dashboard && Workspace.dashboard !== true ) Workspace.dashboard.refresh(); // Finally set the value on application // Notify window if possible // TODO: Real value after its evaluated if( !Workspace.applications ) return; for( let a = 0; a < Workspace.applications.length; a++ ) { let app = Workspace.applications[a]; if( app.applicationId == viewdiv.applicationId ) { app.contentWindow.postMessage( JSON.stringify( { command: 'notify', method: 'setviewflag', viewId: viewdiv.windowObject.viewId, applicationId: app.applicationId, flag: flag, value: value } ), '*' ); break; } } } this.parseFlags = function( flags, filter ) { if( !this.flags ) this.flags = {}; for( var a in flags ) { // Just parse by filter if( filter ) { let fnd = false; for( var b in filter ) { if( a == filter[b] ) { fnd = true; break; } } if( !fnd ) continue; } if( a == 'screen' && !flags[a] ) { if( typeof( currentScreen ) != 'undefined' && currentScreen ) flags[a] = currentScreen.screenObject; } this.setFlag( a, flags[a] ); } // We always need a screen if( !this.flags.screen && typeof( currentScreen ) != 'undefined' ) { this.flags.screen = currentScreen.screenObject; } } this.getFlag = function( flag ) { if( typeof( this.flags[flag] ) != 'undefined' ) { switch( flag ) { case 'top': { let fg = this.flags[ flag ]; fg += ''; if( fg.indexOf( '%' ) > 0 ) return fg; else return parseInt( fg ); break; } case 'left': { let fg = this.flags[ flag ]; fg += ''; if( fg.indexOf( '%' ) > 0 ) return fg; else return parseInt( fg ); break; } case 'width': { let fl = this.flags[flag]; if( fl.indexOf && fl.indexOf( '%' ) > 0 ) return fl; if( fl == 'max' && this.flags && this.flags.screen ) { fl = this.flags.screen.getMaxViewWidth(); } return fl; } case 'height': { let fl = this.flags[flag]; if( fl.indexOf && fl.indexOf( '%' ) > 0 ) return fl; if( fl == 'max' && this.flags && this.flags.screen ) { fl = this.flags.screen.getMaxViewHeight(); } return fl; } } return this.flags[flag]; } // No flags set.. just get the raw data if possible, and defaults else if( this._window ) { let w = this._window.parentNode; switch( flag ) { case 'left': return parseInt( w.style.left ); break; case 'top': return parseInt( w.style.top ); break; } return false; } return false; } this.openCamera = function( flags, callback ) { let self = this; // Just get the available devices function getAvailableDevices( cbk ) { if( !navigator.mediaDevices ) { return cbk( { response: -1, message: 'Could not get any devices.' } ); } navigator.mediaDevices.enumerateDevices().then( function( devs ) { cbk( { response: 1, message: 'Success', data: devs } ); } ).catch( function( err ) { return cbk( { response: -1, message: err } ); } ); } function setCameraEvents( ele ) { ele.ontouchstart = function( e ) { this.offX = e.touches[0].clientX; this.timeStamp = ( new Date() ).getTime(); } ele.ontouchend = function( e ) { let diff = e.changedTouches[0].clientX - this.offX; let difftime = ( new Date() ).getTime() - this.timeStamp; if( difftime > 200 ) { return; } // Swipe right if( diff < 127 ) { setCameraMode(); } // Swipe left else if( diff > 127 ) { setCameraMode(); } } } function setCameraMode( e ) { let v = null; if( !self.cameraOptions ) { self.cameraOptions = { devices: e, currentDevice: false }; // Add container v = document.createElement( 'div' ); v.className = 'FriendCameraContainer'; self.content.appendChild( v ); self.content.container = v; self.content.classList.add( 'HasCamera' ); } // Find video devices let initial = false; let devs = []; for( var a in self.cameraOptions.devices ) { let dev = self.cameraOptions.devices[ a ]; if( dev.kind == 'videoinput' ) { //we want back facing camera as default... if( dev.label && dev.label.indexOf('back') > -1 && !self.cameraOptions.currentDevice ) { self.cameraOptions.currentDevice = dev; initial = true; } else { //we overwrite on purpose here! most handsets have backwards facing cameras last... self.cameraOptions.potentialDevice = dev } devs.push( dev ); } } if( !self.cameraOptions.currentDevice && self.cameraOptions.potentialDevice ) { self.cameraOptions.currentDevice = self.cameraOptions.potentialDevice initial = true; } // Initial pass over, now just choose next device if( !initial ) { let found = nextfound = false; for( var a = 0; a < devs.length; a++ ) { if( devs[a].deviceId == self.cameraOptions.currentDevice.deviceId ) { found = true; } // We found stuff else if( found ) { nextfound = true; self.cameraOptions.currentDevice = devs[a]; break; } } // Wrap around if( !nextfound ) { self.cameraOptions.currentDevice = devs[0]; } } let constraints = { video: { deviceId: { exact: self.cameraOptions.currentDevice.deviceId } } }; let ue = navigator.userAgent.toLowerCase(); if( navigator.gm ) { //check if we should stop stuff before we try again... let dd = self.content.container.camera; if(dd && dd.srcObject) { dd.srcObject.getTracks().forEach(track => track.stop()) dd.srcObject = null; } if( self.content.container.camera ) self.content.container.removeChild( self.content.container.camera ); delete self.content.container.camera; delete navigator.gm; } // Shortcut navigator.gm = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia ); if( navigator.gm ) { navigator.gm( constraints, function( localMediaStream ) { // Remove old video object //might be too late here? at least on mobile? moved this check up a couple of lines.. let oldCam = self.content.container.camera; if( oldCam && oldCam.srcObject ) { oldCam.srcObject.getTracks().forEach( track => track.stop() ); oldCam.srcObject = localMediaStream; } else { // New element! let vi = document.createElement( 'video' ); vi.setAttribute( 'autoplay', 'autoplay' ); vi.setAttribute( 'playinline', 'playinline' ); vi.className = 'FriendCameraElement'; vi.srcObject = localMediaStream; self.content.container.appendChild( vi ); self.content.container.camera = vi; setCameraEvents( vi ); } // Create an object URL for the video stream and use this // to set the video source. self.content.container.camera.srcObject = localMediaStream; // Add the record + switch button if( !self.content.container.button ) { let btn = document.createElement( 'button' ); btn.className = 'IconButton IconSmall fa-camera'; btn.onclick = function( e ) { let dd = self.content.container.camera; let canv = document.createElement( 'canvas' ); canv.setAttribute( 'width', dd.videoWidth ); canv.setAttribute( 'height', dd.videoHeight ); v.appendChild( canv ); let ctx = canv.getContext( '2d' ); ctx.drawImage( dd, 0, 0, dd.videoWidth, dd.videoHeight ); let dt = canv.toDataURL( 'image/jpeg', 0.95 ); // Stop taking video dd.srcObject.getTracks().forEach(track => track.stop()) dd.srcObject = null; // FLASH! v.classList.add( 'Flash' ); setTimeout( function() { v.classList.add( 'Flashing' ); setTimeout( function() { v.classList.remove( 'Flashing' ); setTimeout( function() { v.classList.add( 'Closing' ); setTimeout( function() { callback( { response: 1, message: 'Image captured', data: dt } ); v.parentNode.removeChild( v ); }, 250 ); }, 250 ); }, 250 ); }, 5 ); } self.content.container.appendChild( btn ); let switchbtn = document.createElement( 'button' ); switchbtn.className = 'IconButton IconSmall fa-refresh'; switchbtn.onclick = function() { setCameraMode() }; self.content.container.appendChild( switchbtn ); //stop the video if the view is closed! self.addEvent('systemclose', function() { let dd = self.content.container.camera; if(dd && dd.srcObject ) { dd.srcObject.getTracks().forEach(track => track.stop()) dd.srcObject = null; } }); //register our button in the container... to not do this twice self.content.container.button = btn; } }, function( err ) { v = self.content; // Log the error to the console. callback( { response: -2, message: 'Could not access camera. getUserMedia() failed.' + err } ); if( v ) { v.classList.add( 'Closing' ); /*setTimeout( function() { v.parentNode.removeChild( v ); }, 250 );*/ } } ); } else { // TODO: Hogne // Remove video v.removeChild( d ); // Add fallback let fb = document.createElement( 'div' ); fb.className = 'FriendCameraFallback'; v.appendChild( fb ); let mediaElement = document.createElement( 'input' ); mediaElement.type = 'file'; mediaElement.accept = 'image/*'; mediaElement.className = 'FriendCameraInput'; fb.innerHTML = '

' + i18n( 'i18n_camera_action_description' ) + '

'; fb.appendChild( mediaElement ); setTimeout( function() { fb.classList.add( 'Showing' ); }, 5 ); mediaElement.onchange = function( e ) { let reader = new FileReader(); reader.onload = function( e ) { let dataURL = e.target.result; v.classList.remove( 'Showing' ); setTimeout( function() { callback( { response: 1, message: 'Image captured', data: dataURL } ); v.parentNode.removeChild( v ); }, 250 ); } reader.readAsDataURL( mediaElement.files[0] ); } } } // prepare for us to use to external libs. // good quality resize + EXIF data reader // https://github.com/blueimp/JavaScript-Load-Image/blob/master/js/load-image.all.min.js if( !self.cameraIncludesLoaded ) { Include( '/webclient/3rdparty/load-image.all.min.js', function() { // Execute async operation self.cameraIncludesLoaded = true; getAvailableDevices( function( e ){ setCameraMode( e.data ) } ); }); } else { getAvailableDevices( function( e ){ setCameraMode( e.data ) } ); } } // Add a child window to this window this.addChildWindow = function( ele ) { if ( ele.tagName && ele.windowObject ) return this.childWindows.push ( ele.windowObject ); else if ( ele._window ) return this.childWindows.push ( ele ); else if ( ele.content ) return this.childWindows.push ( ele ); return false; } // Get elements by tabname this.getElementsByTagName = function ( tn ) { return this._window.getElementsByTagName ( tn ); } // Get elements by class this.getByClass = function ( classn ) { let el = this._window.getElementsByTagName ( '*' ); let out = []; for( var a = 0; a < el.length; a++ ) { if( el[a].className ) { let cls = el[a].className.split ( ' ' ); for( var b = 0; b < cls.length; b++ ) { if ( cls[b] == classn ) { out.push(el[a]); break; } } } } return out; } // Init gui tabs this.initTabs = function( pel ) { InitTabs ( pel ); } // Get a window by id this.getViewId = function() { return this._window.viewId; } // Add events for stuff this.addEvent = function( event, func ) { if( event == 'systemclose' ) { if( !this.eventSystemClose ) this.eventSystemClose = []; this.eventSystemClose.push( func ); } return this._window.AddEvent( event, func ); } // Remove event this.removeEvent = function( event, func ) { return this._window.RemoveEvent( event, func ); } // Add an event on a sub element this.addEventByClass = function( className, event, func ) { let ele = this.getSubContent( '.' + className ); if( ele ) { if( ele.addEventListener ) ele.addEventListener( event, func, false ); else ele.attachEvent( 'on' + event, func, false ); } } // Set menu items on window this.setMenuItems = function( obj, appid, viewId ) { // Set destination if( viewId ) this._window.menuViewId = viewId; // Set items this._window.menu = obj; WorkspaceMenu.generated = false; if( appid && ( window.isMobile || IsSharedApp() ) ) { this._window.applicationId = appid; } CheckScreenTitle( null, true ); } this.setBlocker = function( blockwin ) { this._window.blocker = blockwin; } this.removeBlocker = function() { this._window.blocker = false; } // Get the window element (dom element) this.getWindowElement = function() { return this._window; } this.focus = function() { this._window.focus(); } // Handle the keys -- dummy function to be overwritten this.handleKeys = function( k, event ) { } this.setSticky = function() { this._window.parentNode.parentNode.setAttribute( 'sticky', 'sticky' ); } // Now set it up! ---------------------------------------------------------> self.viewId = args.viewId; self.applicationId = args.applicationId; self.authId = args.authId; self.applicationName = args.applicationName; self.applicationDisplayName = args.applicationDisplayName; if( !args.id ) args.id = false; // Fullscreen single task if( Workspace.isSingleTask ) { args.width = 'max'; args.height = 'max'; args.left = 0; args.top = 0; args.resize = false; } this.createDomElements( 'CREATE', args.title, args.width, args.height, args.id, args, args.applicationId ); if( !self._window || !self._window.parentNode ) return false; if( !this._window ) { this.ready = false; return; } else this.ready = true; this.isRich = false; this.childWindows = []; CheckScreenTitle(); // Done setting up view ---------------------------------------------------< } Friend.GUI.view.cleanWindowArray = function( ele ) { let out = []; let found = true; for( var a in movableWindows ) { if( a != ele.id && movableWindows[ a ] != ele ) out[ a ] = movableWindows[ a ]; } out[ ele.id ] = ele; movableWindows = out; } // Reorganize view window positions on responsive browser Friend.GUI.reorganizeResponsiveMinimized = function() { if( !isMobile ) return; if( !Workspace.screen || !Workspace.screen.contentDiv ) return; if( currentMovable && currentMovable.classList.contains( 'Active' ) ) return; // Check if we have a maximized window CheckMaximizedView(); if( document.body.classList.contains( 'ViewMaximized' ) ) { // Here is the first screen Workspace.screen.contentDiv.style.transform = 'translate3d(0,0,0)'; return; } let boxWidth = 96; // Window width when minimized let boxHeight = 80; // Window height when minimized let marginX = 12; // Minimum margin let marginY = 42; let pageW = Workspace.screen.contentDiv.parentNode.offsetWidth; let pageH = Workspace.screen.contentDiv.offsetHeight; // Maximum widths in page let maxCount = Math.floor( pageW / ( boxWidth + marginX ) ); // Calculate optimum horiz margin (adds right margin with +1) marginX = ( pageW - ( boxWidth * maxCount ) ) / ( maxCount + 1 ); let startY = 12; let page = 0; let pageX2 = pageW; let iconHeight = false; let pageX1 = 0; let gridX = marginX; let gridY = startY; for( var a in movableWindows ) { let v = movableWindows[ a ]; let c = v.parentNode; // ViewContainer if( c.classList.contains( 'Active' ) ) { // These views are handled by css... c.classList.remove( 'OnWorkspace' ); continue; } // Non-mainview windows are not displayed else if( !v.windowObject.flags.mainView && v.windowObject.applicationId ) { c.style.top = '-200%'; c.classList.remove( 'OnWorkspace' ); continue; } else if( c.style.display == 'none' || v.style.display == 'none' ) { continue; } c.classList.add( 'OnWorkspace' ); // Next row if( gridX + boxWidth >= pageX2 ) { gridX = pageX1 + marginX; gridY += iconHeight; // Next horizontal page if( gridY + boxHeight >= pageH ) { gridY = startY; pageX1 += pageW; pageX2 += pageW; gridX = pageX1 + marginX; page++; } } if( !iconHeight ) { iconHeight = boxHeight + marginY; } // Position and size c.style.top = gridY + 'px'; c.style.left = gridX + 'px'; c.style.width = boxWidth + 'px'; c.style.height = boxHeight + 'px'; c.style.minWidth = boxWidth + 'px'; c.style.minHeight = boxHeight + 'px'; // Next column gridX += boxWidth + marginX; } // Store how many pages we are counting.. Friend.GUI.responsiveViewPageCount = page; // Resize screen content Workspace.screen.contentDiv.style.width = ( pageW * ( page + 1 ) ) + 'px'; // Reposition content div if( Friend.GUI.responsiveViewPage > page ) { Friend.GUI.responsiveViewPage = page; } Workspace.screen.contentDiv.style.transform = 'translate3d(' + ( pageW * ( -Friend.GUI.responsiveViewPage ) ) + 'px,0,0)'; } // Intermediate anchor for code that uses new Window() var Window = View; /* Support functions -------------------------------------------------------- */ var mousewheelevt = (/Firefox/i.test( navigator.userAgent ) ) ? "DOMMouseScroll" : "mousewheel"; if ( document.attachEvent ) //if IE (and Opera depending on user setting) document.attachEvent( "on"+mousewheelevt, function(e){WindowScrolling(e);}) else if ( document.addEventListener ) //WC3 browsers document.addEventListener( mousewheelevt, function(e){WindowScrolling(e);}, false ); // An alert box // TODO: Block other windows! function Ac2Alert ( msg, title ) { let v = new View( { 'title' : !title ? i18n('Alert') : title, 'width' : 400, 'height' : 120, 'standard-dialog': true } ); v.setSticky(); v.setContent( '
' + msg + '
' ); } var __titlebar = false; function GetTitleBar () { if ( typeof ( window.currentScreen ) != 'undefined' ) return window.currentScreen.getElementsByTagName ( 'div' )[0]; if ( !__titlebar ) __titlebar = ge ( 'Modules' ) ? ge ( 'Modules' ) : ( ge ( 'TitleBar' ) ? ge ( 'TitleBar' ) : false ); return __titlebar; } // Handle keys function _kresponse( e ) { if( window.currentMovable ) { let win = window.currentMovable.windowObject; if( !win ) return; win.ctrlKey = false; win.shiftKey = false; if( e.ctrlKey ) win.ctrlKey = true; if( e.shiftKey ) win.shiftKey = true; if( win.handleKeys ) { let abort = false; let k = e.which ? e.which : e.keyCode; if( e.ctrlKey ) { switch ( k ) { // q for quit case 81: abort = true; win.close(); break; // f for fullscreen case 70: win.fullscreen(); break; } } if( win.handleKeys( k, e ) ) return cancelBubble( e ); if( abort ) return cancelBubble( e ); } /*if( e.ctrlKey ) { // Send the message to the window, giving it an opportunity to // respond let k = e.which ? e.which : e.keyCode; win.sendMessage( { command: 'handlekeys', key: k, ctrlKey: true, shiftKey: e.shiftKey } ); return cancelBubble( e ); }*/ } } function _kresponseup( e ) { if ( window.currentMovable ) { let win = window.currentMovable.windowObject; /*if ( ( e.ctrlKey || e.shiftKey ) && typeof ( win.handkeKeys ) ) { if ( e.preventDefault ) e.preventDefault (); return cancelBubble ( e ); }*/ } } // Resize all screens function _kresize( e, depth ) { if( !depth ) depth = 0; checkMobileBrowser(); forceScreenMaxHeight(); // Resize screens if( Workspace && Workspace.screenList ) { //console.log( 'Everything resized.' ); for( let a = 0; a < Workspace.screenList.length; a++ ) { Workspace.screenList[a].resized = true; Workspace.screenList[a].resize(); } if( globalConfig.workspacecount > 1 ) { Workspace.initWorkspaces(); } Workspace.checkWorkspaceWallpapers(); } if( isMobile && depth > 0 ) { return ConstrainWindow( currentMovable ); } // Resize windows for( let a in movableWindows ) { ConstrainWindow( movableWindows[a] ); } if( depth == 0 ) { // ios fix let nav = navigator.userAgent.toLowerCase(); if( nav.indexOf( 'iphone' ) >= 0 || nav.indexOf( 'ipad' ) >= 0 ) { setTimeout( function() { _kresize( e, 1 ); }, 500 ); } } } function Confirm( title, string, okcallback, oktext, canceltext, extrabuttontext, extrabuttonreturn ) { let d = document.createElement( 'div' ); d.style.position = 'absolute'; d.style.left = '-10000px'; d.style.width = '400px'; d.innerHTML = string; document.body.appendChild( d ); let curr = window.currentMovable; let v; if( !window.isMobile || Workspace.dashboard ) { v = new View( { title: title, width: 400, resize: false, height: d.offsetHeight + 75, id: 'confirm_' + title.split( /[\s]+/ ).join( '' ) + ( new Date() ).getTime() + Math.random(), 'standard-dialog': true } ); } else { v = new Widget( { width: 'full', height: 'full', above: true, animate: true, transparent: true, id: 'confirm_' + title.split( /[\s]+/ ).join( '' ) + ( new Date() ).getTime() + Math.random() } ); } v.onClose = function() { if( okcallback ) okcallback( false ) } v.setSticky(); d.parentNode.removeChild( d ); let f = new File( 'System:templates/confirm.html' ); f.type = 'confirm'; let thirdbutton = ''; /* check for third button values */ if( extrabuttontext && extrabuttonreturn ) { thirdbutton = '
' } f.replacements = { 'string' : string, 'okbutton' : ( oktext ? oktext : i18n('i18n_affirmative') ), 'cancelbutton' : ( canceltext ? canceltext : i18n('i18n_cancel') ), 'thirdbutton' : thirdbutton }; f.i18n(); f.onLoad = function( data ) { v.setContent( data ); let eles = v._window.getElementsByTagName( 'button' ); if( !eles && v.dom ) { eles = v.dom.getElementsByTagName( 'button' ); } // FL-6/06/2018: correction so that it does not take the relative position of OK/Cancel in the box // US-792 - 2020: Correction to fix sending the same delete request multiple times for( let el = 0; el < eles.length; el++ ) { ( function( itm ) { if( itm.id == 'ok' ) { itm.onclick = function() { let k = okcallback; okcallback = null; v.close(); k( true ); } itm.focus(); } else if( itm.id == 'cancel' ) { itm.onclick = function() { let k = okcallback; okcallback = null; v.close(); k( false ); } } else { itm.onclick = function( e ) { if( e && e.target && e.target.hasAttribute( 'data-returnvalue' ) ) { okcallback( e.target.getAttribute( 'data-returnvalue' ) ); } else { okcallback( e ); } okcallback = null; v.close(); } } } )( eles[ el ] ); } if( !window.isMobile ) { _ActivateWindow( v._window.parentNode ); _WindowToFront( v._window.parentNode ); } } f.load(); return v; // The window } function Alert( title, string, cancelstring, callback ) { if(!title) title = 'Untitled'; let d = document.createElement( 'div' ); d.style.position = 'absolute'; d.style.left = '-10000px'; d.style.width = '400px'; d.innerHTML = string; document.body.appendChild( d ); // Register current movable let curr = window.currentMovable; let minContentHeight = 100; if( d.offsetHeight > minContentHeight ) minContentHeight = d.offsetHeight; let themeTitle = GetThemeInfo( 'ViewTitle' ).height; let themeBottom = GetThemeInfo( 'ViewBottom' ).height; let v; if( !window.isMobile || Workspace.dashboard ) { v = new View( { title: title, width: 400, resize: false, height: minContentHeight + parseInt( themeTitle ) + parseInt( themeBottom ), id: 'alert_' + title.split( /[\s]+/ ).join( '' ) + ( new Date() ).getTime() + Math.random(), 'standard-dialog': true } ); } else { v = new Widget( { width: 'full', height: 'full', above: true, animate: true, transparent: true, id: 'alert_' + title.split( /[\s]+/ ).join( '' ) + ( new Date() ).getTime() + Math.random() } ); } v.onClose = function() { } v.setSticky(); d.parentNode.removeChild( d ); let f = new File( 'System:templates/alert.html' ); f.replacements = { 'string': string, 'understood': cancelstring ? cancelstring : i18n( 'i18n_understood' ) } f.i18n(); f.onLoad = function( data ) { v.setContent( data ); let eles = v._window.getElementsByTagName( 'button' ); if( eles && eles.length ) { eles[0].onclick = function() { v.close(); if( callback ) callback(); } if( !window.isMobile ) { _ActivateWindow( v._window.parentNode ); _WindowToFront( v._window.parentNode ); } } else { v.close(); console.log( '[Alert] Could not process alert dialog template.' ); } } f.load(); return v; // the window } // Get final sandbox scripts function getSandboxFlags( win, defaultFlags ) { let flags = win.getFlag( 'sandbox' ); if( flags === false && flags !== '' ) { flags = defaultFlags; } if( flags === false ) flags = ''; return flags; } function putSandboxFlags( iframe, flags ) { if( flags != '' && flags ) iframe.setAttribute( 'sandbox', flags ); else iframe.removeAttribute( 'sandbox' ); } // Initialize the events function InitWindowEvents() { if ( window.addEventListener ) { window.addEventListener( 'keydown', _kresponse, false ); window.addEventListener( 'keyup', _kresponseup, false ); window.addEventListener( 'resize', _kresize, false ); } else { window.attachEvent( 'onkeydown', _kresponse, false ); window.attachEvent( 'onkeyup', _kresponseup, false ); window.attachEvent( 'onresize', _kresize, false ); } if( document.getElementById( 'DoorsScreen' ) ) window.currentScreen = 'DoorsWorkbench'; } // GUI functions Friend.GUI.checkWindowState = function( state ) { if( !window.currentMovable ) return false; if( !currentMovable.windowObject ) return false; let wo = window.currentMovable.windowObject; if( wo.states[ state ] ) { return true; } return false; } /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ var _viewType = 'iframe'; //window.friendBook ? 'webview' : 'iframe'; // Screen class to support multiple screens Screen = function ( flags, initObject ) { var self = this; this.resized = 'uninitialized'; this._flags = new Object (); if( typeof( flags ) == 'object' ) { for( var a in flags ) { this._flags[a] = flags[a]; } } // Maximum width available for view windows this.getMaxViewWidth = function() { if( this.div ) { return this.div.offsetWidth; } return 0; } // Maximum height available for view windows this.getMaxViewHeight = function() { if( this.contentDiv ) { if( this.resized || this.resized == 'uninitialized' ) { this.contentDivHeight = window.innerHeight - this.div.screenTitle.offsetHeight; this.resized = false; } //console.log( 'Current content div height: ' + this.contentDivHeight ); return this.contentDivHeight; } return 0; } // Get the flag this.getFlag = function( flag ) { flag = flag.toLowerCase(); if( flag == 'title' ) { var e = this.div.screenTitle.getElementsByClassName( 'Info' )[0]; return e.innerHTML; } if( this._flags[ 'flag' ] ) { return this._flags[ 'flag' ]; } } this.setFlag = function( flag, value ) { switch( flag.toLowerCase() ) { case 'title': this._flags[ flag ] = value; var e = this.div.screenTitle.getElementsByClassName( 'Info' )[0]; e.innerHTML = value; break; case 'vcolumns': this._flags[ flag ] = value; this.resize(); break; case 'vrows': this._flags[ flag ] = value; this.resize(); break; case 'extra': this._flags[ flag ] = value; var e = this.div.screenTitle.getElementsByClassName( 'Extra' )[0]; if( e ) e.innerHTML = '' + value + ''; break; case 'theme': case 'background': this._flags[ flag ] = value; break; } } if ( typeof ( this._flags['title'] ) == 'undefined' ) { this._flags['title'] = 'Unknown screen'; } // TODO: Make dynamic! And it's only for the doors menu!! var statusbar = ''; if( this._flags.taskbar ) { statusbar = "" + "
" + "
" + "
" + "
" + "
" + "
"; } // Fullscreen mode has no statusbar (as well as those who are specific) if ( !this._flags.taskbar || this._flags.fullscreen ) { statusbar = ''; } var div; // When we need it this.hideOverlay = function() { div._screenoverlay.style.display = 'none'; div._screenoverlay.style.pointerEvents = 'none'; } if ( initObject ) div = initObject; else { div = document.createElement ( 'div' ); div.object = this; // We need this or blank if( typeof( this._flags['extra'] ) == 'undefined' ) this._flags['extra'] = ''; // Background support if( typeof( this._flags['background'] ) != 'undefined' ) div.style.backgroundColor = this._flags['background']; div.style.webkitTransform = 'translate3d(0, 0, 0)'; var ex = ''; if( flags.id == 'DoorsScreen' ) { var extra = this._flags['extra']; if( this._flags[ 'extraClickHref' ] ) extra = '' + extra + ''; ex = "\n
" + extra + "
"; } div.innerHTML = "" + "
" + "
" + "
" + "
" + "
" + "
" + this._flags['title'] + "
" + ex + "
" + "
" + "
" + "
" + statusbar + "
"; ge( 'Screens' ).appendChild( div ); // FIXME: Hack - this should be better calculated, and it's not resize friendly let cnt = false; let divs = div.getElementsByTagName( 'div' ); for( var a = 0; a < divs.length; a++ ) { if( divs[a].className == 'ScreenContent' ) { cnt = divs[a]; this._screen = cnt; divs[a].style.minHeight = div.style.minHeight; } else if ( divs[a].className == 'TitleBar' ) { this._titleBar = divs[a]; } } // Screen size aware! let self = this; function resizeScreen() { let cnt = self.contentDiv; if( cnt ) { // Resize view windows for( let a in movableWindows ) { let w = movableWindows[ a ].windowObject; if( w.flags.maximized || w.flags.width == 'max' || ( movableWindows[ a ].zoom && movableWindows[ a ].zoom.mode == 'maximized' ) ) { let v = w._window.parentNode; v.setAttribute( 'moving', 'moving' ); v.style.width = self.getMaxViewWidth() + 'px'; v.style.height = self.getMaxViewHeight() + 'px'; w.setFlag( 'top', 0 ); w.setFlag( 'left', 0 ); } } // Mindful of columns! if( typeof( self._flags['vcolumns'] ) != 'undefined' ) { let columns = parseInt( self._flags['vcolumns'] ); if( columns <= 0 ) columns = 1; // Set width with workaround. let newWidth = GetWindowWidth() * columns; cnt.style.width = newWidth + 'px'; } else { cnt.style.width = '100%'; } // Mindful of rows! let cntTop = parseInt( GetThemeInfo( 'ScreenTitle' ).height ); if( !isNaN( cntTop ) ) { if( typeof( self._flags['vrows'] ) != 'undefined' ) { let rows = parseInt( self._flags['vrows'] ); if( rows <= 0 ) rows = 1; cnt.style.height = '100%'; } else { cnt.style.height = '100%'; } } } } this.resize = function(){ resizeScreen(); } // Do a scroll hack! div.onscroll = function(){ this.scrollLeft = 0; this.scrollTop = 0; }; if( cnt ) cnt.onscroll = function(){ this.scrollLeft = 0; this.scrollTop = 0; } _kresize(); } if( typeof( this._flags['id'] ) != 'undefined' ) { div.id = this._flags['id']; } else if( !div.id ) { div.id = 'unnamed_'; } // Gen unique id and set it if( 1 ) { var idb = div.id; var id = 0; var found; var screens = ge( 'Screens' ).childNodes; do { found = false; for( var a = 0; a < screens.length; a++ ) { if( !screens[a].className ) continue; if( typeof( screens[a].id ) != 'undefined' ) { if( screens[a].id == idb + id ) found = true; } } if( !found ) break; id++; } while( found ); div.id = idb + ( id > 0 ? id : '' ); this._flags['id'] = div.id; } // Register clicks div.onmousedown = function( e ) { var t = e.target ? e.target : e.srcElement; if( !e ) e = window.event; if( e.button != 0 ) return; window.currentScreen = this; CheckScreenTitle(); // Deactivate all windows when clicking on the desktop wallpaper if ( ( t.id && t.id == 'DoorsScreen' ) || ( !t.id && t.parentNode.id == 'DoorsScreen' && t.classList && t.classList.contains( 'ScreenContent' ) ) ) { if( !isMobile ) { _DeactivateWindows(); Workspace.toggleStartMenu( false ); } } } if( this.iframe ) { this.iframe.addEventListener( 'click', function( e ) { div.onmousedown( e ); } ); } // Done registering clicks // Moveoverlay var molay = document.createElement ( 'div' ); molay.className = 'MoveOverlay'; div.moveoverlay = molay; div.appendChild ( molay ); // Slide start on x axis div.startX = -1; div.startY = -1; var divs = div.getElementsByTagName ( 'div' ); var btncycle = false; var scroverl = false; for ( let a = 0; a < divs.length; a++ ) { if ( divs[a].className && divs[a].classList.contains( 'ScreenList' ) ) btncycle = divs[a]; else if ( divs[a].className && divs[a].classList.contains( 'ScreenOverlay' ) ) scroverl = divs[a]; else if( divs[a].className && divs[a].classList.contains( 'ScreenContent' ) ) this.contentDiv = divs[a]; } if ( btncycle ) { var o = this; btncycle.onclick = function( e ) { o.screenCycle(); return cancelBubble( e ); } } if ( scroverl ) { scroverl.style.position = 'absolute'; scroverl.style.top = '0'; scroverl.style.left = '0'; scroverl.style.right = '0'; scroverl.style.bottom = '0'; scroverl.style.display = 'none'; scroverl.style.pointerEvents = 'none'; scroverl.style.zIndex = 2147483647; scroverl.style.webkitTransform = 'translate3d(0, 0, 0)'; div._screenoverlay = scroverl; } div.className = 'Screen'; div.screen = this; div.screenTitle = divs[0]; div.screenTitle.ondragstart = function( e ) { return cancelBubble( e ); } div.screenTitle.onselectstart = function( e ) { return cancelBubble( e ); } div.screenTitle.onmousedown = function ( e ) { if ( !e ) e = window.event; if( e.button != 0 ) return; // Set current screen window.currentScreen = this.parentNode; // Check menu and stuff CheckScreenTitle(); var y = e.clientY ? e.clientY : e.pageYOffset; var x = e.clientX ? e.clientX : e.pageXOffset; var offl = this.parentNode.offsetLeft; var offt = this.parentNode.screenOffsetTop; if( !offt ) offt = 0; this.parentNode.offx = x - offl; this.parentNode.offy = y - offt; window.mouseDown = FUI_MOUSEDOWN_SCREEN; window.mouseReleaseFunc = function( e ) { // Disable all screen overlays var screenc = ge ( 'Screens' ); var screens = screenc.getElementsByTagName ( 'div' ); for( var a = 0; a < screens.length; a++ ) { if( !screens[a].className ) continue; if( screens[a].parentNode != screenc ) continue; screens[a]._screenoverlay.style.display = 'none'; screens[a]._screenoverlay.style.pointerEvents = 'none'; } } // If we have multiple screens, allow screen dragging if( ge( 'Screens' ).getElementsByClassName( 'Screen' ).length > 1 ) { window.mouseMoveFunc = function ( e ) { var my = e.clientY ? e.clientY : e.pageYOffset; var mx = e.clientX ? e.clientX : e.pageXOffset; var ty = my - window.currentScreen.offy; if ( ty < 0 ) ty = 0; if ( ty >= GetWindowHeight () ) ty = GetWindowHeight () - 1; div.style.transform = 'translate3d(0,' + ty + 'px,0)'; div.screenOffsetTop = ty; // Enable all screen overlays var screenc = ge ( 'Screens' ); var screens = screenc.getElementsByTagName ( 'div' ); for( var a = 0; a < screens.length; a++ ) { if( !screens[a].className ) continue; if( screens[a].parentNode != screenc ) continue; screens[a]._screenoverlay.style.display = ''; screens[a]._screenoverlay.style.pointerEvents = 'all'; } } } // Just pop the screen back else { div.style.transition = 'transform 0.25s'; div.style.transform = 'translate3d(0,0px,0)'; div.screenOffsetTop = 0; setTimeout( function() { div.style.transition = ''; }, 250 ); } var t = e.target ? e.target : e.srcElement; // Clicking on the extra widget if( t.classList && t.classList.contains( 'Extra' ) && ( !window.Workspace || !window.Workspace.isSingleTask ) ) { Workspace.calendarClickEvent(); } return cancelBubble ( e ); } div.screenTitle.ontouchstart = function( e ) { // Set current screen window.currentScreen = this.parentNode; CheckScreenTitle(); } // Alias clicking the screen div.onmouseup = function( e ) { // Only left button clicks! if( e.button != 0 ) return false; } var scrn = this; // Check if we have content with no overflow if( this._flags.scrolling === false ) { this.contentDiv.style.overflow = 'hidden'; } // Touch start show menu! scrn.contentDiv.parentNode.addEventListener( 'touchstart', function( e ) { // Set the screen quickly.. window.currentScreen = div; CheckScreenTitle(); // check for other touch start action if( scrn.contentDiv.onTouchStartAction ) if( scrn.contentDiv.onTouchStartAction( e ) ) return cancelBubble( e ); var t = e.target ? e.target : e.srcElement; // We are registering a click inside if( !( t != scrn.contentDiv && t != scrn.contentDiv.parentNode ) ) { if( !isMobile ) { _DeactivateWindows(); } var tp = e.changedTouches[0]; if( !scrn.touch ) scrn.touch = {}; scrn.touch.moving = true; scrn.touch.ox = scrn.contentDiv.parentNode.offsetLeft; scrn.touch.oy = scrn.contentDiv.parentNode.screenOffsetTop; if( !scrn.touch.oy ) scrn.touch.oy = 0; scrn.touch.tx = scrn.touch.ox - tp.clientX; scrn.touch.ty = scrn.touch.oy - tp.clientY; // Click in the corner, or the gadget when pulling screens if( tp.clientX > scrn.contentDiv.parentNode.offsetWidth - 32 && scrn.touch.ty > -32 ) { scrn.screenCycle(); scrn.touchCycled = true; // << prevent the touchmove to occur scrn.touchMoving = true; // return cancelBubble( e ); } // We own this domain.. if( t.classList && ( t.classList.contains( 'ScreenContent' ) || t.classList.contains( 'TitleBar' ) ) ) { if( t.classList.contains( 'ScreenContent' ) ) { if( !isMobile ) { _DeactivateWindows(); } ExposeWindows(); ExposeScreens(); } return cancelBubble( e ); } } // Make clicking work! if( t.onclick ) { t.onclick( e ); return cancelBubble( e ); } }, true ); scrn.touchCycled = false; // we didn't cycle before scrn.touchMoving = false; scrn.contentDiv.parentNode.addEventListener( 'touchmove', function( e ) { var t = e.target ? e.target : e.srcElement; if( t != scrn.contentDiv && t != scrn.contentDiv.parentNode ) return; var ct = scrn.contentDiv.parentNode.screenOffsetTop; if( !ct ) ct = '0px'; var tp = e.changedTouches[0]; scrn.touch.mx = tp.clientX; scrn.touch.my = tp.clientY; var diffy = scrn.touch.ty + ( scrn.touch.my - scrn.touch.oy ); var diffx = scrn.touch.tx + ( scrn.touch.mx - scrn.touch.ox ); if( !scrn.touchCycled && Math.abs( diffx ) > ( window.innerWidth * 0.8 ) ) { scrn.touchCycled = true; scrn.screenCycle(); } else if( isMobile && diffx < -100 && !scrn.moving ) { if( Friend.GUI.responsiveViewPage < Friend.GUI.responsiveViewPageCount ) { scrn.moving = true; setTimeout( function() { scrn.moving = false; }, 500 ); Friend.GUI.responsiveViewPage++; var px = Math.round( scrn.contentDiv.parentNode.offsetWidth * -( Friend.GUI.responsiveViewPage ) ) + 'px'; scrn.contentDiv.style.transform = 'translate3d(' + px + ',0,0)'; } } else if( isMobile && diffx > 100 && !scrn.moving ) { if( Friend.GUI.responsiveViewPage > 0 ) { scrn.moving = true; setTimeout( function() { scrn.moving = false; }, 500 ); Friend.GUI.responsiveViewPage--; var px = Math.round( scrn.contentDiv.parentNode.offsetWidth * -( Friend.GUI.responsiveViewPage ) ) + 'px'; scrn.contentDiv.style.transform = 'translate3d(' + px + ',0,0)'; } } // Show the dock! else if( diffy < 0 && parseInt( ct ) == 0 ) { if( !scrn.touch.moved && diffy < -60 && !Workspace.mainDock.open ) { Workspace.mainDock.openDesklet( e ); } } // Don't do this on mobile else if( !window.isMobile && Math.abs( diffy ) > 5 ) { var top = scrn.touch.oy + diffy; if( top < 0 ) { top = 0; } if( top + 40 > scrn.contentDiv.parentNode.offsetHeight ) { top = scrn.contentDiv.parentNode.offsetHeight - 40; } if( top != 0 ) { scrn.contentDiv.parentNode.setAttribute( 'moved', 'moved' ); } else scrn.contentDiv.parentNode.setAttribute( 'moved', '' ); var nt = top + 'px'; if( ct != nt ) { scrn.contentDiv.parentNode.style.transform = 'translate3d(0,' + nt + ',0)'; scrn.contentDiv.parentNode.screenOffsetTop = top; scrn.touch.moved = true; } // No need for menu here if( scrn.clickTimeout ) { clearTimeout( scrn.clickTimeout ); scrn.clickTimeout = false; } } if( !scrn.touchMoving ) { scrn.touchMoving = true; // You have 0.5 secs to switch screens! scrn.touchCycleTimeout = setTimeout( function() { scrn.touchCycled = true; }, 500 ); } return cancelBubble( e ); } ); scrn.contentDiv.parentNode.addEventListener( 'touchend', function( e ) { if( scrn.clickTimeout ) clearTimeout( scrn.clickTimeout ); if( scrn.touchCycleTimeout ) clearTimeout( scrn.touchCycleTimeout ); if( scrn.menuTimeout ) clearTimeout( scrn.menuTimeout ); scrn.touchCycled = false; // reset scrn.touchMoving = false; scrn.touch = {}; } ); this.div = div; div.screenObject = this; // Prevent scrolling // TODO: Allow it under conditions! if( div.addEventListener ) div.addEventListener( 'scroll', function(){ div.scrollTop = 0; } ); else div.attachEvent( 'onscroll', function(){ div.scrollTop = 0; } ); // Clear on release function clearOverlay() { div._screenoverlay.style.display = 'none'; div._screenoverlay.style.pointerEvents = 'none'; } div.addEventListener( 'touchend', clearOverlay, true ); div.addEventListener( 'mouseup', clearOverlay, true ); // Gets all divs here this.getElementsByTagName = function ( ele ) { return this.div.getElementsByTagName ( ele ); } // Move this screen to front this.screenToFront = function () { var screens = ge ( 'Screens' ); var subs = screens.getElementsByTagName ( 'div' ); var maxz = 0; for ( var a = 0; a < subs.length; a++ ) { if( !subs[a].className ) continue; if( subs[a].parentNode != screens ) continue; if( parseInt ( subs[a].style.zIndex ) <= 0 ) subs[a].style.zIndex = 0; if( parseInt ( subs[a].style.zIndex ) > maxz ) maxz = parseInt ( subs[a].style.zIndex ); } maxz++; this.div.style.zIndex = maxz; window.currentScreen = this.div; } // Next screen please this.screenCycle = function () { var screens = ge ( 'Screens' ); var divz = screens.getElementsByTagName ( 'div' ); // Make sure we get the screen divs in the correct order var rdiv = []; var indexes = []; for( var a = 0; a < divz.length; a++ ) { if( divz[a].className && divz[a].parentNode == screens ) { rdiv.push( divz[a] ); indexes.push( divz[a].style.zIndex ); } } indexes.sort(); var subs = []; for( var z = 0; z < indexes.length; z++ ) { for ( var a = 0; a < rdiv.length; a++ ) { if( rdiv[a].style.zIndex == indexes[z] ) { subs.push( rdiv[a] ); } } } // Normalize z-indexes var max = 1; var highest = false; for( var z = 0; z < subs.length; z++ ) { subs[z].style.zIndex = z+1; highest = subs[z]; max = z+1; } // Flip to front if( this.div.style.zIndex != 0 && this.div.style.zIndex != max ) this.div.style.zIndex = max+1; // Flip to back else this.div.style.zIndex = 0; return highest; // Highest order! } // Set content (securely!) in a sandbox, callback when completed this.setContentIframed = function( content, domain, packet, callback ) { var scrn = this; if( !domain ) { domain = document.location.href + ''; domain = domain.split( 'index.html' ).join ( 'sandboxed.html' ); domain = domain.split( 'app.html' ).join( 'sandboxed.html' ); } // Oh we have a conf? if( this.conf ) { domain += '/system.library/module/?module=system&command=sandbox' + '&sessionid=' + Workspace.sessionId + '&conf=' + JSON.stringify( this.conf ); } else if( domain.indexOf( 'sandboxed.html' ) <= 0 ) { domain += '/webclient/sandboxed.html'; } // Make sure scripts can be run after all resources has loaded if( content && content.match ) { var r; while( r = content.match( /\]*?)\>([\w\W]*?)\<\/script\>/i ) ) content = content.split( r[0] ).join( '' + r[2] + '' ); } else { content = ''; } var c = this._screen; if( c && c.content ) c = c.content; if( c ) { c.innerHTML = ''; } var ifr = document.createElement( _viewType ); ifr.applicationId = self.applicationId; ifr.authId = self.authId; ifr.applicationName = self.applicationName; ifr.applicationDisplayName = self.applicationDisplayName; ifr.className = 'Content'; ifr.setAttribute( 'allowfullscreen', 'true' ) ifr.src = domain; if( packet.applicationId ) this._screen.applicationId = packet.applicationId; if( packet.authId ) this._screen.authId = packet.authId; if( packet.applicationName ) this._screen.applicationName = packet.applicationName; packet.screenId = this.externScreenId; // Register screen id ifr.onload = function() { var msg = {}; if( packet ) for( var a in packet ) msg[a] = packet[a]; msg.command = 'setbodycontent'; msg.locale = Workspace.locale; msg,cachedAppData = _applicationBasics; msg.dosDrivers = Friend.dosDrivers; // Authid is important, should not be left out if it is available if( !msg.authId ) { if( ifr.authId ) msg.authId = ifr.authId; else if( GetUrlVar( 'authid' ) ) msg.authId = GetUrlVar( 'authid' ); } // Override the theme if( scrn.getFlag( 'theme' ) ) msg.theme = scrn.getFlag( 'theme' ); if( Workspace.themeData ) msg.themeData = Workspace.themeData; // Use this if the packet has it if( !msg.sessionId ) { if( packet.sessionId ) msg.sessionId = packet.sessionId; } msg.registerCallback = addWrapperCallback( function() { if( callback ) callback(); } ); if( packet.filePath ) { msg.data = content.split( /progdir\:/i ).join( packet.filePath ); } else msg.data = content; if( msg.data && msg.data.split ) msg.data = msg.data.split( /system\:/i ).join( '/webclient/' ); if( !msg.origin ) msg.origin = '*'; // TODO: should be this - document.location.href; ifr.contentWindow.postMessage( JSON.stringify( msg ), Workspace.protocol + '://' + ifr.src.split( '//' )[1].split( '/' )[0] ); if( callback ) callback(); // Make sure to show! WorkspaceMenu.show(); } this.iframe = ifr; // Position content ifr.style.position = 'absolute'; ifr.style.border = 'none'; ifr.style.height = this._titleBar ? ( 'calc(100% - ' + ( this._titleBar.offsetHeight + 'px' ) + ')' ) : '100%'; ifr.style.width = '100%'; ifr.style.left = '0'; ifr.style.top = this._titleBar.offsetHeight + 'px'; c.appendChild( ifr ); } // Sets rich content in a safe iframe this.setRichContentUrl = function( url, base, appId, filePath, callback ) { if( !base ) base = '/'; if( !this._screen ) return; var eles = this._screen.getElementsByTagName( _viewType ); var ifr = false; var w = this; if( eles[0] ) { ifr = eles[0]; } else { ifr = document.createElement( _viewType ); this._screen.appendChild( ifr ); } // Register the app id so we can talk this._screen.applicationId = appId; ifr.applicationId = self.applicationId; ifr.applicationName = self.applicationName; ifr.authId = self.authId; ifr.setAttribute( 'scrolling', 'no' ); ifr.setAttribute( 'seamless', 'true' ); ifr.setAttribute( 'allowfullscreen', 'true' ); ifr.style.border = '0'; ifr.style.position = 'absolute'; ifr.style.top = this._titleBar ? ( this._titleBar.offsetHeight + 'px' ) : '0'; ifr.style.left = '0'; ifr.style.width = '100%'; ifr.style.height = this._titleBar ? ( 'calc(100% - ' + ( this._titleBar.offsetHeight + 'px' ) + ')' ) : '100%'; // Find our friend // TODO: Only send postmessage to friend targets (from our known origin list (security app)) var targetP = url.match( /(http[s]{0,1}\:\/\/.*?)\//i ); var friendU = document.location.href.match( /http[s]{0,1}\:\/\/(.*?)\//i ); var targetU = url.match( /http[s]{0,1}\:\/\/(.*?)\//i ); if( friendU && friendU.length > 1 ) friendU = friendU[1]; if( targetU && targetU.length > 1 ) { targetP = targetP[1]; targetU = targetU[1]; } // We're on a road trip.. if( !( friendU && ( friendU == targetU || !targetU ) ) ) { ifr.sandbox = DEFAULT_SANDBOX_ATTRIBUTES; } // Allow sandbox flags var sbx = ifr.getAttribute( 'sandbox' ) ? ifr.getAttribute( 'sandbox' ) : ''; sbx = ('' + sbx).split( ' ' ); if( this.flags && this.flags.allowPopups ) { var found = false; for( var a = 0; a < sbx.length; a++ ) { if( sbx[a] == 'allow-popups' ) { found = true; } } if( !found ) sbx.push( 'allow-popups' ); ifr.sandbox = sbx.join( ' ' ); } ifr.onload = function( e ) { if( friendU && ( friendU == targetU || !targetU ) ) { var msg = JSON.stringify( { command: 'initappframe', base: base, applicationId: appId, filePath: filePath, origin: '*', // TODO: should be this - document.location.href, screenId: w.externScreenId, theme: Workspace.theme, clipboard: Friend.clipboard } ); ifr.contentWindow.postMessage( msg, Workspace.protocol + '://' + ifr.src.split( '//' )[1].split( '/' )[0] ); ifr.loaded = true; if( callback ) callback(); } /*else { ifr.contentWindow.window.origin = targetP; }*/ // Make sure to show! WorkspaceMenu.show(); } // Oh we have a conf? if( this.conf && url.indexOf( Workspace.protocol + '://' ) != 0 ) { var cnf = this.conf; if( typeof( this.conf ) == 'object' ) cnf = ''; ifr.src = '/system.library/module/?module=system&command=sandbox' + '&sessionid=' + Workspace.sessionId + '&url=' + encodeURIComponent( url ) + '&conf=' + cnt; } else { ifr.src = url; } this.isRich = true; this.iframe = ifr; } // Set menu items on window this.setMenuItems = function( obj, appid, screenId ) { // Set destination if( screenId ) div.menuScreenId = screenId; // Set items div.menu = obj; if( appid ) { this._screen.applicationId = appid; } } this.close = function () { currentScreen = this.screenCycle(); if( this.div.parentNode ) this.div.parentNode.removeChild( this.div ); delete this; CheckScreenTitle(); } // Send a message this.sendMessage = function( dataObject ) { //dataObject.command = 'message'; if( this.iframe && this.iframe.contentWindow ) { var u = Workspace.protocol + '://' + this.iframe.src.split( '//' )[1].split( '/' )[0]; var origin = u; if( !dataObject.applicationId && this._screen.applicationId ) { dataObject.applicationId = this._screen.applicationId; dataObject.authId = this._screen.authId; dataObject.applicationName = this._screen.applicationName; } if( !dataObject.type ) dataObject.type = 'system'; this.iframe.contentWindow.postMessage( JSON.stringify( dataObject ), origin ); } else { if( !this.sendQueue ) this.sendQueue = []; this.sendQueue.push( dataObject ); } } this.displayOfflineMessage = function() { //console.log('show htat we are offline...'); var offline = this.div.getElementsByClassName( 'Offline' )[0]; if( offline ) { offline.style.display = 'block'; } else { offline = document.createElement( 'div' ); offline.className = 'Offline'; offline.innerHTML = i18n('i18n_server_disconnected'); this.div.appendChild( offline ); } if( window.Workspace && Workspace.notifyAppsOfState ) { Workspace.notifyAppsOfState( { state: 'offline' } ); } } this.hideOfflineMessage = function() { var offline = this.div.getElementsByClassName( 'Offline' )[0]; if( offline ) offline.style.display = 'none'; if( window.Workspace && Workspace.notifyAppsOfState ) { Workspace.notifyAppsOfState( { state: 'online' } ); } } // Go through flags this.checkFlags = function() { for( var a in this._flags ) { switch( a.toLowerCase() ) { case 'fullscreen': if( this._flags[a] == true ) { div.screenTitle.style.display = 'none'; } else { div.screenTitle.style.display = ''; } break; } } } this.checkFlags(); // Init this.screenToFront (); _DeactivateWindows(); // Initial resize this.resize(); this.ready = true; // Let's poll the tray! if( statusbar.length ) PollTray(); if( !Workspace.screenList ) { Workspace.screenList = []; } Workspace.screenList.push( this ); } /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ /** @file * * File browser class * * * @author HT (Hogne Titlestad) */ /** * TODO: There are some race conditions left between 'bookmark' type and * 'volume' type paths jumping around. Make sure that the context * isn't broken (i.e. suddenly a bookmark item triggers a volume item) */ Friend = window.Friend || {}; // File browser ---------------------------------------------------------------- Friend.FileBrowserEntry = function() { }; /* Filebrowser class - creates a recursive file browser! flags: { displayFiles: true | false // File extension filter filedialog: true | false // Is the browser used in a file dialog? justPaths: true | false // Just show the directories? path: true | false // Target path to focus on at start bookmarks: true | false // Show the bookmarks pane? rootPath: true | false // The root from which to display structures } callbacks: { void checkFile( filepath, fileextension ) void loadFile( filepath ) bool permitFiletype( filepath ) } // flags.path is the target path in a query // rootPath is where to start listing files from */ Friend.FileBrowser = function( initElement, flags, callbacks ) { let self = this; // Make ready for alternative to bookmarks this.favorites = []; this.clickType = null; this.prevPath = null; this.dom = initElement; this.dom.addEventListener( 'scroll', function() { // Block autoscrolling self.scrolling = true; setTimeout( function() { self.scrolling = false; }, 50 ); }, false ); this.dom.classList.add( 'FileBrowser' ); this.rootPath = 'Mountlist:'; // The current root path this.callbacks = callbacks; self.flags = { displayFiles: false, filedialog: false, justPaths: false, path: self.rootPath, bookmarks: true, rootPath: false, noContextMenu: false }; if( flags ) { for( let a in flags ) self.flags[ a ] = flags[ a ]; } if( this.flags.rootPath ) this.rootPath = this.flags.rootPath; // If we don't have a rootPath the default is Mountlist: if( !this.rootPath ) this.rootPath = 'Mountlist:'; // Clicking the pane this.dom.onclick = function( e ) { let t = e.target ? e.target : e.srcElement; if( t == this ) { let cb = function() { if( t ) { self.callbacks.folderOpen( self.rootPath, e ); } t = null; }; // Can't set icon listing path to mountlist.. if( self.rootPath != 'Mountlist:' ) { self.setPath( self.rootPath, cb, e ); } } return cancelBubble( e ); } }; Friend.FileBrowser.prototype.clear = function() { this.dom.innerHTML = ''; this.headerDisks = false; this.bookmarksHeader = false; } Friend.FileBrowser.prototype.render = function( force ) { let self = this; if( force && this.dom ) { this.clear(); } this.refresh(); }; Friend.FileBrowser.prototype.drop = function( elements, e, win ) { let drop = 0; let self = this; // Only if we have bookmarks if( self.flags.bookmarks ) { // Element was dropped here for( let a = 0; a < elements.length; a++ ) { if( elements[a].fileInfo.Type == 'Directory' ) { if( elements[a].fileInfo.Path.substr( elements[a].fileInfo.Path - 1, 1 ) != ':' ) { let m = new Module( 'system' ); m.onExecuted = function( e, d ) { if( e == 'ok' ) { self.clear(); self.refresh( 'Mountlist:', null, null, null, { mode: 'poll' } ); } } m.execute( 'addbookmark', { path: elements[a].fileInfo.Path, name: elements[a].fileInfo.Filename } ); drop++; cancelBubble( e ); } } } if( win && drop == 0 ) { if( win.refresh ) { win.refresh(); } } } return drop; }; // Set an active path // Supported flags ( { lockHistory: true|false } ) Friend.FileBrowser.prototype.setPath = function( target, cbk, tempFlags, e ) { let winobj = false; if( this.directoryView.window.windowObject ) winobj = this.directoryView.window.windowObject; else return false; let path = target; if( !Workspace.diskNotificationList[ path ] ) { Workspace.diskNotificationList[ path ] = { type: 'directory', view: winobj }; let f = new Library( 'system.library' ); f.addVar( 'sessionid', Workspace.sessionId ); f.addVar( 'path', path ); f.onExecuted = function( e, d ) { if( e == 'ok' ) { let j = JSON.parse( d ); winobj.addEvent( 'systemclose', function() { winobj.removeEvent( 'systemclose', this ); let ff = new Library( 'system.library' ); ff.addVar( 'sessionid', Workspace.sessionId ); ff.addVar( 'path', path ); ff.addVar( 'id', j.Result ); ff.onExecuted = function( es, ds ) { Workspace.diskNotificationList[ path ] = false; } ff.execute( 'file/notificationremove' ); } ); } //console.log( 'File notification start result: ' + e, d ); } f.execute( 'file/notificationstart' ); //console.log('notification start ' + path); } // Already set if( this.flags.path && this.flags.path == target ) { return; } this.tempFlags = false; this.flags.path = target; // This is the current target path.. if( tempFlags ) this.tempFlags = tempFlags; console.log( '[setPath] Refreshing on this path: (' + this.rootPath + ') ' + target, { context: this.lastContext } ); console.log( '[setPath] --' ); this.refresh( this.rootPath, this.dom, cbk, 0, { context: this.lastContext } ); } Friend.FileBrowser.prototype.rollOver = function( elements ) { // Do some user feedback later }; Friend.FileBrowser.prototype.updateFavorites = function() { let self = this; let rootElement = this.dom; if( self.favoritesDom && !self.favoritesDom.parentNode ) self.favoritesDom = null; // No favorites container? Remove if( !self.favoritesDom && self.favorites.length ) { self.favoritesDom = document.createElement( 'div' ); self.favoritesDom.className = 'Favorites'; let m = document.createElement( 'div' ); m.innerHTML = '

Favorites:

'; self.favoritesDom.appendChild( m ); self.favoritesContainer = document.createElement( 'div' ); self.favoritesContainer.className = 'FileBrowserFavorites'; self.favoritesDom.appendChild( self.favoritesContainer ); rootElement.insertBefore( self.favoritesDom, rootElement.firstChild ); } // No favorites? Clean up if( self.favoritesDom && !self.favorites.length && rootElement && rootElement.parentNode ) { rootElement.removeChild( self.favoritesDom ); self.favoritesDom = false; self.favoritesContainer = false; return; } if( !self.favoritesContainer ) return; let ul = self.favoritesContainer.getElementsByTagName( 'ul' ); if( ul.length ) { ul = ul[0]; } else { ul = document.createElement( 'ul' ); self.favoritesContainer.appendChild( ul ); } let existing = ul.getElementsByTagName( 'li' ); for( let a = 0; a < self.favorites.length; a++ ) { // Check for doubles let found = false; for( let b = 0; b < existing.length; b++ ) { if( self.favorites[ a ].ID == existing[ b ].getAttribute( 'bookmark-id' ) ) { found = true; break; } } if( found ) continue; let item = self.favorites[ a ]; let li = document.createElement( 'li' ); li.setAttribute( 'bookmark-id', item.ID ); let icon = document.createElement( 'span' ); icon.className = 'FileBrowserItemImage'; icon.style.backgroundImage = 'url(/iconthemes/friendup15/DriveLabels/Bookmark.svg)'; let label = document.createElement( 'span' ); label.className = 'FileBrowserItemLabel'; label.innerHTML = item.Title; let rem = document.createElement( 'span' ); rem.className = 'IconSmall fa-remove'; ( function( liElement, ulElement, remmer, path ) { liElement.onclick = function( e ) { // Find active list item let lis = ulElement.getElementsByTagName( 'li' ); for( let a = 0; a < lis.length; a++ ) { if( lis[ a ] == liElement ) { lis[ a ].classList.add( 'Activated' ); self.callbacks.folderOpen( path, e ); } else { lis[ a ].classList.remove( 'Activated' ); } } // Remove active state on disks let disks = rootElement.getElementsByClassName( 'DiskItem' ); for( let a = 0; a < disks.length; a++ ) { let everything = disks[ a ].getElementsByTagName( 'div' ); for( let b = 0; b < everything.length; b++ ) { everything[b].classList.remove( 'Open' ); everything[b].classList.remove( 'Active' ); } } } remmer.onclick = function( ev ) { let mo = new Module( 'system' ); mo.onExecuted = function( eo, od ) { if( eo == 'ok' ) { self.clear(); self.refresh( null, null, null, null, { mode: 'poll' } ); } } mo.execute( 'removebookmark', { name: path } ); return cancelBubble( ev ); } } )( li, ul, rem, item.Path ); li.appendChild( icon ); li.appendChild( label ); li.appendChild( rem ); ul.appendChild( li ); } self.favoritesContainer.appendChild( ul ); } // Create onclick action function for refresh function Friend.FileBrowser.prototype.getOnClickActionFunc = function( data ) { let self = this; let context = refreshMode = path = rootElement = callback = depth = flags = evt = false; if( data ) { if( data.context ) context = data.context; if( data.path ) path = data.path; if( data.rootElement ) rootElement = data.rootElement; if( data.callback ) callback = data.callback; if( data.depth ) depth = data.depth; if( data.flags ) flags = data.flags; if( data.evt ) evt = data.evt; } if( flags ) { if( flags.context ) { context = flags.context; self.lastContext = context; } if( flags.mode ) refreshMode = flags.mode; } function createOnclickAction( ele, ppath, type, depth ) { // Not more than once if( ele.onclick ) return; ele.clickType = context ? context : type; // Parent decides try { if( ele.parentNode.parentNode.clickType ) { ele.clickType = ele.parentNode.parentNode.clickType; } } catch( e ){}; ele.onmouseover = function() { self.lastContext = this.clickType; } ele.onclick = function( e ) { // Remove favorites active state if( self.favoritesDom ) { let lis = self.favoritesContainer.getElementsByTagName( 'li' ); for( let a = 0; a < lis.length; a++ ) { lis[a].classList.remove( 'Activated' ); } } // Real click removes temp flags if( e && ( e.button === 0 || e.button > 0 ) ) self.tempFlags = false; // Don't allow the "simulated click" to trigger different contexts if( !e && this.clickType != self.lastContext ) return; if( !ppath ) { return cancelBubble( e ); } if ( ppath.indexOf( ':' ) < 0 ) ppath += ':'; // Real click or entering target path let doClick = ( ppath == self.flags.path ) || ( e && e.button >= 0 ); if( type == 'File' ) { let eles = self.dom.getElementsByTagName( 'div' ); for( let a = 0; a < eles.length; a++ ) { eles[a].classList.remove( 'Active' ); } let nam = ele.getElementsByClassName( 'Name' ); if( nam.length ) { nam[0].classList.add( 'Active' ); } let treated = false; let ext = ppath.split( '.' ); if( ext && ext.length ) { ext = ext[ ext.length - 1 ].toLowerCase(); if( self.callbacks.checkFile ) { self.callbacks.checkFile( ppath, ext ); treated = true; return cancelBubble( e ); } } if( !treated && self.callbacks.loadFile ) { self.callbacks.loadFile( ppath, e, self.tempFlags ); } } else if( type == 'RootDirectory' ) { // Are we in a file dialog? if( isMobile && ( self.flags.filedialog || self.flags.justPaths ) ) { self.callbacks.folderOpen( ppath, e, self.tempFlags ); return cancelBubble( e ); } if( doClick ) { if( self.callbacks && self.callbacks.folderOpen ) { self.callbacks.folderOpen( ppath, e, self.tempFlags ); } } // Set this to active let eles = self.dom.getElementsByTagName( 'div' ); for( let a = 0; a < eles.length; a++ ) { eles[a].classList.remove( 'Active' ); } let nam = ele.getElementsByClassName( 'Name' ); if( nam.length ) { nam[0].classList.add( 'Active' ); } } else if( type == 'Directory' || type == 'volume' || type == 'bookmark' ) { // Are we in a file dialog? if( isMobile && ( self.flags.filedialog || self.flags.justPaths ) ) { self.callbacks.folderOpen( ppath, e, self.tempFlags ); return cancelBubble( e ); } let nam = ele.getElementsByClassName( 'Name' ); // Normal operation if( !this.classList.contains( 'Open' ) || ( e && e.mode == 'open' ) ) { let subitems = ele.getElementsByClassName( 'SubItems' ); if( subitems.length ) { this.classList.add( 'Open' ); // Only refresh at final destination if( doClick ) { // Register that this is now the path // such that we don't double refresh self.flags.path = ppath; self.refresh( ppath, subitems[0], callback, depth, { context: type == 'volume' || type == 'bookmark' ? type : null }, e ); if( self.callbacks && self.callbacks.folderOpen ) { self.callbacks.folderOpen( ppath, e, self.tempFlags ); } } if( nam.length ) { nam[0].classList.add( 'Open' ); } } } // Only close folders if they are active and clicked else if( nam.length && e && ( ( !isMobile && e.button >= 0 ) || ( isMobile && doClick ) ) ) { // Only close active if( nam[0].classList.contains( 'Active' ) ) { this.classList.remove( 'Open' ); nam[0].classList.remove( 'Open' ); if( self.callbacks && self.callbacks.folderClose ) { self.callbacks.folderClose( ppath, e, self.tempFlags ); } } // Again clicking (like open...) else if( self.callbacks && self.callbacks.folderOpen ) { self.callbacks.folderOpen( ppath, e, self.tempFlags ); } } // Set this to active let eles = self.dom.getElementsByTagName( 'div' ); for( let a = 0; a < eles.length; a++ ) { eles[a].classList.remove( 'Active' ); } nam = ele.getElementsByClassName( 'Name' ); if( nam.length ) { nam[0].classList.add( 'Active' ); // Scroll into view if( !isMobile && !self.scrolling && refreshMode != 'poll' ) { let d = self.dom; let h = d.offsetHeight >> 1; d.scrollTop = nam[0].offsetTop - h; } } fnam = ppath.split( ':' )[1]; if( fnam.indexOf( '/' ) > 0 ) fnam = fnam.split( '/' ).pop(); } return cancelBubble( e ); } ele.oncontextmenu = function( e ) { console.log( 'oncontextmenu', { isMobile : isMobile, noContextMenu : self.flags.noContextMenu, type : type, theme : window?.Workspace?.theme, }); if( isMobile ) return; if( self.flags.noContextMenu ) return cancelBubble( e ); let men = cmd = ''; let cf = false; // create file if( type == 'File' ) { men = 'i18n_open_file'; cmd = 'menuloadfile'; } else if( ele.classList.contains( 'Open' ) ) { // Only show this if it is possible if( i18n( 'i18n_create_file' ) != 'i18n_create_file' ) { if( type == 'Volume' || type == 'Directory' ) { cf = { name: i18n( 'i18n_create_file' ), command: 'createfile', data: { path: ppath } }; } } } let menu = []; if( men && cmd ) { menu.push( { name: i18n( men ), command: cmd, data: { path: ppath } } ); } if( type == 'volume' ) { if ( window?.Workspace?.theme != 'jeanie' ) { menu.push( { name: i18n( 'menu_show_icon_information' ), command: function() { for( let c = 0; c < Workspace.icons.length; c++ ) { if( Workspace.icons[ c ].Volume === ppath ) { Workspace.fileInfo( Workspace.icons[ c ] ); break; } } } } ); } } if( cf ) menu.push( cf ); if ( menu.length < 1 ) return cancelBubble( e ); if( window.ShowContextMenu ) { ShowContextMenu( i18n( 'i18n_file_menu' ), menu ); } else if( window.Workspace ) Workspace.showContextMenu( menu, e ); return cancelBubble( e ); } } return createOnclickAction; } // Refresh the file browser (side bar) Friend.FileBrowser.prototype.refresh = function( path, rootElement, callback, depth, flags, evt ) { let self = this; if( !evt ) evt = {}; if( !rootElement ) rootElement = this.dom; if( !callback ) callback = false; if( !path ) path = this.rootPath; // Use the rootpath if( !depth ) depth = 1; // Fix column problem if ( path.indexOf( ':' ) < 0 ) path += ':'; let refreshMode = 'normal'; let context = null; if( flags ) { if( flags.context ) { if( self.lastContext != flags.context && !evt.button ) { return; } context = flags.context; self.lastContext = context; } if( flags.mode ) refreshMode = flags.mode; } if( !this.headerDisks ) { this.headerDisks = document.createElement( 'div' ); this.headerDisks.innerHTML = '

' + i18n( 'i18n_your_devices' ) + ':

'; rootElement.appendChild( this.headerDisks ); } // What are we looking for at this level? // Keeps the whole target path, but searches on each level recursively.. let targetPath = false; if( this.flags.path ) { targetPath = this.flags.path; } let createOnclickAction = self.getOnClickActionFunc( { context: context, path: path, rootElement: rootElement, callback: callback, depth: depth, flags: flags, evt: evt } ); // A click element for incoming path let clickElement = null; // Check for theme engine (goes into favorites mode) let favoritesMode = document.body.classList.contains( 'ThemeEngine' ); // Just get a list of disks if( path == 'Mountlist:' ) { let func = function( flags, cb ) { // For Workspace scope if( window.Workspace ) { Friend.DOS.getDisks( flags, function( response, msg ) { cb( response ? { list: msg } : false ); } ); } // For application scope else { Friend.DOS.getDisks( flags, cb ); } } // Check from off mountlist func( { sort: true }, function( msg ) { if( !msg || !msg.list ) return; if( callback ) callback(); function done() { // Get existing let eles = rootElement.childNodes; let found = []; let foundElements = []; let foundStructures = []; let removers = []; // Move Home: to the top let temp = []; for( let a = 0; a < msg.list.length; a++ ) { if( msg.list[a].Volume == 'Home:' ) { temp.push( msg.list[a] ); break; } } for( let a = 0; a < msg.list.length; a++ ) { if( msg.list[a].Volume != 'Home:' ) temp.push( msg.list[a] ); } msg.list = temp; // Done moving Home: to the top // Check the existing nodes, if we have items that do not // exist any longer and needs to be removed for( let a = 0; a < eles.length; a++ ) { let elFound = false; if( !eles[a].id ) continue; // Check our list (also contains bookmarks!) for( let b = 0; b < msg.list.length; b++ ) { let vol = msg.list[ b ].Volume; if( vol == 'System:' ) continue; // Ignore shared: TODO: We are trying to remove it if( self.directoryView && self.directoryView.filedialog && vol == 'Shared:' ) { continue; } if( eles[a].id == 'diskitem_' + msg.list[b].Title ) { createOnclickAction( eles[a], vol, 'volume', depth + 1 ); // Don't add twice if( !found.find( function( ele ){ ele == msg.list[b].Title } ) ) { found.push( msg.list[b].Title ); // Found item titles foundElements.push( eles[a] ); // Found dom nodes foundStructures.push( msg.list[ b ] ); // Found dos structures } elFound = true; break; } } // Deleted element if( !elFound && eles[a] != self.headerDisks && eles[a] != self.bookmarksHeader ) { removers.push( eles[a] ); } } if( removers.length ) { for( let a = 0; a < removers.length; a++ ) { rootElement.removeChild( removers[a] ); } delete removers; } // Iterate through the resulting list for( let a = 0; a < msg.list.length; a++ ) { // Skip system drive if( msg.list[a].Volume == 'System:' ) continue; if( self.directoryView && self.directoryView.filedialog && msg.list[ a ].Volume == 'Shared:' ) continue; // Add the bookmark header if it doesn't exist if( self.flags.bookmarks && msg.list[a].Type && msg.list[a].Type == 'header' && !self.bookmarksHeader ) { // Only add bookmarks header when not in favorites mode if( !favoritesMode ) { let d = document.createElement( 'div' ); self.bookmarksHeader = d; d.innerHTML = '

' + i18n( 'i18n_bookmarks' ) + ':

'; rootElement.appendChild( d ); continue; } } // Check if this item already exists let foundItem = foundStructure = false; for( let b = 0; b < found.length; b++ ) { if( found[b] == msg.list[a].Title || msg.list[a].Type == 'header' ) { foundItem = foundElements[ b ]; foundStructure = foundStructures[ b ]; break; } } // Didn't already exist, make it if( !foundItem ) { let d = document.createElement( 'div' ); d.className = 'DiskItem'; d.id = 'diskitem_' + msg.list[a].Title; d.path = msg.list[a].Volume; d.type = msg.list[a].Type; d.clickType = d.type; let nm = document.createElement( 'div' ); nm.style.paddingLeft = ( depth << 3 ) + 'px'; // * 8 nm.className = 'Name IconSmall IconDisk'; nm.innerHTML = ' ' + msg.list[a].Title + ''; // We have an incoming path if( !clickElement && self.flags.path && targetPath == d.path ) { clickElement = d; if( context && clickElement.clickType != context ) clickElement = false; } if( Friend.dosDrivers && !( msg.list[a].Type && msg.list[a].Type == 'bookmark' ) ) { let driver = msg.list[a].Driver; // Find correct image let img = '/iconthemes/friendup15/DriveLabels/FriendDisk.svg'; if( Friend.dosDrivers[ driver ] && Friend.dosDrivers[ driver ].iconLabel ) img = 'data:image/svg+xml;base64,' + Friend.dosDrivers[ driver ].iconLabel; if( msg.list[a].Title == 'Home' ) img = '/iconthemes/friendup15/DriveLabels/Home.svg'; else if( msg.list[a].Title == 'Shared' ) img = '/iconthemes/friendup15/DriveLabels/Shared.svg'; else if( msg.list[a].Title == 'System' ) img = '/iconthemes/friendup15/DriveLabels/SystemDrive.svg'; let i = document.createElement( 'div' ); i.className = 'FileBrowserItemImage'; i.style.backgroundImage = 'url("' + img + '")'; nm.appendChild( i ); nm.classList.remove( 'IconSmall' ); nm.classList.remove( 'IconDisk' ); } d.appendChild( nm ); let ctype = 'volume'; if( msg.list[a].Type && msg.list[a].Type == 'bookmark' ) { if( favoritesMode ) { continue; } else { // Set nice folder icon nm.classList.remove( 'IconSmall' ); nm.classList.remove( 'IconDisk' ); let img = '/iconthemes/friendup15/DriveLabels/Bookmark.svg'; let i = document.createElement( 'div' ); i.className = 'FileBrowserItemImage'; i.style.backgroundImage = 'url("' + img + '")'; nm.appendChild( i ); ( function( ls ){ let ex = document.createElement( 'span' ); ex.className = 'FloatRight IconButton IconSmall fa-remove'; ex.onclick = function( e ) { let m = new Module( 'system' ); m.onExecuted = function( e, d ) { if( e == 'ok' ) { self.clear(); self.refresh( null, null, null, null, { mode: 'poll' } ); } } m.execute( 'removebookmark', { name: ls.Path } ); return cancelBubble( e ); } nm.appendChild( ex ); } )( msg.list[a] ); ctype = 'bookmark'; } } let s = document.createElement( 'div' ); s.className = 'SubItems ' + msg.list[a].Type; d.appendChild( s ); rootElement.appendChild( d ); createOnclickAction( d, d.path, ctype, depth + 1 ); } // Existing items else { // Only refresh child elements if not open if( foundItem.classList.contains( 'Open' ) ) { let s = foundItem.getElementsByClassName( 'SubItems' ); if( s && s.length && msg.list[a].Volume ) { self.refresh( msg.list[a].Volume, s[0], false, depth + 1, { context: foundItem.clickType } ); } } if( !clickElement && self.flags.path && targetPath == foundItem.path ) { clickElement = foundItem; if( context && clickElement.clickType != context ) clickElement = false; } } } // Manage space. if( self.bookmarksHeader ) { self.bookmarksHeader.parentNode.classList.add( 'Bookmarks' ); } // Add checkers classes let sw = 2; for( let a = 0; a < eles.length; a++ ) { sw = sw == 2 ? 1 : 2; if( eles[a].className && eles[a].classList.contains( 'DiskItem' ) ) { eles[a].classList.remove( 'sw1' ); eles[a].classList.remove( 'sw2' ); eles[a].classList.add( 'sw' + sw ); } } // Click the click element for path if( clickElement && !( self.tempFlags && !self.tempFlags.passive ) ) { self.lastClickElement = clickElement; // store it if( !( evt.target && evt.srcElement ) && refreshMode != 'poll' ) { setTimeout( function() { clickElement.onclick( { mode: 'open' } ); }, 5 ); } } if( favoritesMode ) { self.updateFavorites(); } } if( self.flags.bookmarks ) { self.favorites = []; let m = new Module( 'system' ); m.onExecuted = function( e, d ) { let js = false; if( e == 'ok' ) { try { js = JSON.parse( d ); } catch( e ){} console.log( 'Got favorites', js ); } if( !favoritesMode ) { msg.list.push( { Title: i18n( 'i18n_bookmarks' ) + ':', Type: 'header' } ); } self.hasBookmarks = false; if( js ) { for( let a = 0; a < js.length; a++ ) { let ele = { Title: js[a].name, Type: 'bookmark', Path: js[a].path, Volume: js[a].path, ID: js[a].id }; if( !favoritesMode ) { msg.list.push( ele ); } else { self.favorites.push( ele ); } } self.hasBookmarks = true; } done(); } m.execute( 'getbookmarks' ); } else { done(); } } ); } // Get sub directories else { // If we're in favorites mode, skip (we don't care about subdirs here) if( self.favoritesDom ) return; // Support both API scope and Workspace scope let func = function( path, flags, cb ) { // Don't do translations here! flags.translations = false; if( window.Workspace ) { Friend.DOS.getDirectory( path, flags, function( response, msg ) { cb( response ? { list: msg } : false ); } ); } else { Friend.DOS.getDirectory( path, flags, cb ); } } func( path, { sort: true }, function( msg ) { if( !msg || !msg.list ) return; // Create metadirectory "root" if( depth == 1 ) { let itm = [ { Type: 'Directory', Filename: i18n( 'i18n_root_directory' ), Path: Application.browserPath, Title: i18n( 'i18n_root_directory' ), MetaType: 'RootDirectory' } ]; msg.list = itm.concat( msg.list ); } if( callback ) callback(); // Get existing let eles = rootElement.childNodes; let removers = []; let found = []; let foundElements = []; for( let a = 0; a < eles.length; a++ ) { let elFound = false; for( let b = 0; b < msg.list.length; b++ ) { if( eles[a].id == 'fileitem_' + msg.list[b].Filename.split( ' ' ).join( '' ) ) { let fn = msg.list[b].Filename; if( msg.list[b].Type == 'Directory' ) fn += '/'; // Special case - isn't really a directory (uses path without filename) if( msg.list[b].MetaType == 'RootDirectory' ) { createOnclickAction( eles[a], path, 'RootDirectory', depth + 1 ); } else { createOnclickAction( eles[a], path + fn, msg.list[b].Type, depth + 1 ); } // Don't add twice if( !found.find( function( ele ){ ele == msg.list[b].Filename } ) ) { found.push( msg.list[b].Filename ); foundElements.push( eles[a] ); } elFound = true; } } // Deleted item if( !elFound ) { removers.push( eles[a] ); } } let finalRemovers = []; for( let a = 0; a < removers.length; a++ ) { if( removers[ a ].nodeName == 'INPUT' ) { finalRemovers.push( removers[ a ] ); } else { rootElement.removeChild( removers[a] ); } } delete removers; // Precalc let d13 = depth * 13; for( let a = 0; a < msg.list.length; a++ ) { let foundItem = false; if( msg.list[a].Filename.substr( 0, 1 ) == '.' ) continue; // skip hidden files for( let b = 0; b < found.length; b++ ) { if( found[b] == msg.list[a].Filename ) { foundItem = foundElements[b]; break; } } if( !foundItem ) { if( msg.list[a].Type == 'Directory' || ( self.callbacks && self.callbacks.permitFiletype && self.callbacks.permitFiletype( path + msg.list[a].Filename ) ) ) { // Not displaying files? if( msg.list[a].Type != 'Directory' && !self.flags.displayFiles ) { continue; } let d = document.createElement( 'div' ); d.className = msg.list[a].Type == 'Directory' ? 'FolderItem' : 'FileItem'; let ext = msg.list[a].Filename.split( '.' ).pop().toLowerCase(); let icon = d.className == 'FolderItem' ? 'IconFolder' : ( 'IconFile ' + ext ); let title = msg.list[a].Title ? msg.list[a].Title : msg.list[a].Filename; // Translations if( msg.list[a].Path.indexOf( 'Shared:' ) == 0 && title.substr( 0, 5 ) == 'i18n_' ) { title = i18n( title ); } d.id = 'fileitem_' + msg.list[a].Filename.split( ' ' ).join( '' ); d.innerHTML = '
' + title + '
'; rootElement.appendChild( d ); let fn = msg.list[a].Filename; if( msg.list[a].Type == 'Directory' ) fn += '/'; d.path = path + fn; if( msg.list[a].MetaType == 'RootDirectory' ) { d.path = self.rootPath; msg.list[a].Type = 'RootDirectory'; } createOnclickAction( d, d.path, msg.list[a].Type, depth + 1 ); // We have an incoming path if( !clickElement && self.flags.path && targetPath == d.path ) { clickElement = d; if( context && clickElement.clickType != context ) clickElement = false; } } } else if( foundItem && msg.list[a].Type == 'Directory' ) { // Remove active state from inactive items let nam = foundItem.querySelector( '.Name' ); if( nam && nam.classList.contains( 'Active' ) && foundItem.path != self.flags.path ) nam.classList.remove( 'Active' ); // Existing items if( foundItem.classList.contains( 'Open' ) ) { if( msg.list[a].MetaType && msg.list[a].MetaType != 'RootDirectory' ) { let s = foundItem.getElementsByClassName( 'SubItems' ); if( s && s.length ) { let fn = msg.list[a].Filename; if( msg.list[a].Type == 'Directory' ) fn += '/'; self.refresh( path + fn, s[0], false, depth + 1, { context: foundItem.clickType } ); } } } // We have an incoming path if( !clickElement && self.flags.path && targetPath == foundItem.path ) { clickElement = foundItem; if( context && clickElement.clickType != context ) clickElement = false; } } } // Checkers let rootPathLength = self.rootPath.split( '/' ).length; let sw = 2; for( let a = 0; a < eles.length; a++ ) { sw = sw == 2 ? 1 : 2; if( eles[a].className && ( eles[a].classList.contains( 'FolderItem' ) || eles[a].classList.contains( 'FileItem' ) ) ) { eles[a].classList.remove( 'sw1' ); eles[a].classList.remove( 'sw2' ); eles[a].classList.add( 'sw' + sw ); } } // Click the click element for path if( clickElement && !( self.tempFlags && !self.tempFlags.passive ) ) { self.lastClickElement = clickElement; // Store it // Only when clicking if( !( evt.target && evt.srcElement ) && refreshMode != 'poll' ) { setTimeout( function() { clickElement.onclick(); }, 5 ); } } // Cleanup for( let a = 0; a < finalRemovers.length; a++ ) { finalRemovers[ a ].parentNode.removeChild( finalRemovers[ a ] ); } } ); } }; /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ // This is an object! var ScreenOverlay = { visibility: false, mode: false, done: false, debug: false, list: [], // Public methods ---------------------------------------------------------- init: function() { this.div = document.createElement( 'div' ); this.div.id = 'FriendScreenOverlay'; document.body.appendChild( this.div ); }, // Show self show: function() { var self = this; if( this.visibility || !this.div ) return; this.visibility = true; this.div.classList.remove( 'Hidden' ); this.div.classList.add( 'Visible' ); setTimeout( function() { self.div.classList.add( 'Showing' ); }, 5 ); if( this.debug ) { this.enableDebug(); } }, // Trick hide invisible: function() { if( this.debug ) return; var self = this; if( !this.visibility ) return; this.div.classList.add( 'Hiding' ); setTimeout( function() { self.div.classList.remove( 'Showing' ); self.div.classList.remove( 'Hiding' ); setTimeout( function() { self.div.classList.add( 'Hidden' ); self.div.classList.remove( 'Visible' ); self.clearContent(); self.done = true; // Make sure we update screen title and tray/tasks PollTaskbar(); }, 250 ); }, 250 ); }, // Hide self hide: function() { if( this.debug ) return; var self = this; // Reload the docks Workspace.reloadDocks(); if( !this.visibility ) { return; } this.div.classList.add( 'Hiding' ); setTimeout( function() { // Reload the docks Workspace.reloadDocks(); self.div.classList.remove( 'Showing' ); self.div.classList.remove( 'Hiding' ); setTimeout( function() { self.div.classList.add( 'Hidden' ); self.div.classList.remove( 'Visible' ); self.visibility = false; // Done hiding! self.clearContent(); self.done = true; setTimeout( function() { if( Workspace.applications ) { for( var a = 0; a < Workspace.applications.length; a++ ) { Workspace.applications[ a ].startupsequence = false; } } }, 1000 ); // Make sure we update screen title and tray/tasks PollTaskbar(); // Initialize tutorials if( !isMobile ) { if( typeof( TutorialWidget ) != 'undefined' ) { let tuts = new TutorialWidget( { x: 'right', y: 'bottom' } ); } } }, 250 ); }, 250 ); }, setMode: function( mode ) { switch( mode ) { case 'text': case 'list': this.mode = mode; break; default: return false; } }, setTitle: function( tt ) { if( !this.div.stitle ) { // Add box title var title = document.createElement( 'div' ); title.className = 'Title'; this.div.appendChild( title ); this.div.stitle = title; } this.div.stitle.innerHTML = tt; var self = this; setTimeout( function() { self.div.stitle.classList.add( 'Showing' ); }, 5 ); }, enableDebug: function() { this.debug = true; var self = this; if( !this.div.sdebug ) { var deb = document.createElement( 'div' ); deb.className = 'Debug'; this.div.appendChild( deb ); this.div.sdebug = deb; } var transl = i18n( 'i18n_debug_skip' ); if( transl.substr( 0, 5 ) == 'i18n_' ) // translations race cond transl = 'Skip'; this.div.sdebug.innerHTML = transl; this.div.sdebug.onclick = function() { self.debug = false; self.hide(); Workspace.lastListStatus = self.list; self.list = {}; Friend.startupApps = {}; } }, addDebug: function( str ) { if( !this.div.sdebug ) return; var s = document.createElement( 'div' ); s.className = 'DebugLine'; s.innerHTML = str; this.div.sdebug.appendChild( s ); }, addStatus: function( topic, content ) { if( !this.div.status ) { // Add box status var status = document.createElement( 'div' ); status.className = 'StatusBox'; this.div.appendChild( status ); this.div.status = status; } this.list.push( { topic: topic, content: content, status: 'Pending' } ); this.renderStatus(); return this.list.length - 1; }, editStatus: function( slot, status ) { this.list[ slot ].status = status; this.renderStatus(); }, renderStatus: function() { var str = ''; for( var a = 0; a < this.list.length; a++ ) { str += '
'; str += '
' + this.list[a].topic + ':
'; str += '
' + this.list[a].content + '
'; str += '
' + i18n( 'i18n_status_' + this.list[a].status ) + '
'; str += '
'; } str += ''; this.div.status.innerHTML = str; }, clearContent: function() { this.list = []; if( this.div && this.div.status ) this.div.status.innerHTML = ''; } }; /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ // Global window.FUI = window.FUI ? window.FUI : { // Initial built-in classes classTypes: [ 'string' ], guiElements: {}, fragments: {}, callbacks: {}, events: { 'click': [] }, // Create meta markup for a class instance create( data ) { switch( data.type ) { case 'string': { let str = data.value; // Extras are things that prepend the value if( data.extras ) str = data.extras + str; // Additions are things that appear after the value if( data.additions ) str += data.additions; return str; } default: { let classStr = 'FUI' + data.type.substr( 0, 1 ).toUpperCase() + data.type.substr( 1, data.type.length - 1 ); try { let classObj = eval( classStr ); return( new classObj( data ).getMarkup( data ) ); } catch( e ) { console.log( 'No such class type ' + classStr ); } return ''; } } }, // Registers a class to the factory registerClass( type, classDefinition ) { for( let a = 0; a < this.classTypes.length; a++ ) { if( this.classTypes[ a ] == type ) return false; } this.classTypes.push( type ); // Place class definition on window scope if( classDefinition ) { window[ 'FUI' + type.substr( 0, 1 ).toUpperCase() + type.substr( 1, type.length - 1 ) ] = classDefinition; } }, // Checks if a class exists classExists( type ) { for( let a = 0; a < this.classTypes.length; a++ ) { if( this.classTypes[ a ] == type ) return true; } return false; }, // Loads a class and adds it to DOM, supports a callback when loaded loadClass( type, callback = false, skipInit = false ) { if( !this.classExists( type ) ) { let cj = document.createElement( 'script' ); cj.src = '/webclient/js/fui/classes/' + type + '.fui.js'; let cc = document.createElement( 'link' ); cc.rel = 'stylesheet'; cc.href = '/webclient/js/fui/classes/' + type + '.fui.css'; let head = document.getElementsByTagName( 'head' )[0]; cj.onload = function() { if( !skipInit ) FUI.initialize(); if( callback ) callback( true ); } head.appendChild( cj ); head.appendChild( cc ); } else { if( callback ) callback( false ); } }, // Loads multiple classes loadClasses( typeList, callback = false ) { let classLength = typeList.length; function counter( result ){ if( --classLength == 0 ) { // Done! if( callback ) callback( true ); // Initialize the specified types for( let a = 0; a < typeList.length; a++ ) FUI.initialize( typeList[ a ] ); // Initialize all the rest FUI.initialize(); } }; for( let a = 0; a < typeList.length; a++ ) { this.loadClass( typeList[ a ], counter, true ); } }, // Initialize all gui elements on body initialize( type = false ) { let types = this.classTypes; if( type ) types = [ type ]; // Fetch all fragments let ifrags = document.getElementsByTagName( 'fui-fragment' ); if( ifrags.length > 0 ) { let frags = []; for( let a = 0; a < ifrags.length; a++ ) { let id = ifrags[ a ].getAttribute( 'uniqueid' ); if( !id ) continue; frags.push( { id: id, index: a, markup: ifrags[ a ].innerHTML, pnode: ifrags[ a ].parentNode, sourceNode: ifrags[ a ] } ); } for( let a = 0; a < frags.length; a++ ) { let f = frags[ a ]; this.fragments[ f.id ] = f.markup; f.pnode.removeChild( f.sourceNode ); } ifrags = null; } let jailClasses = { 'button': true, 'html': true, 'textarea': true, 'string': true, 'select': true }; // Convert active class placeholders for( let b = 0; b < types.length; b++ ) { ( function( domtype ) { // Convert markup into classes let ch = false; if( !jailClasses[ domtype ] ) { ch = document.getElementsByTagName( domtype ); } // TODO: Extract correct domtype from object // Support fui-* if( !ch || ( ch && !ch.length ) ) { ch = document.getElementsByTagName( 'fui-' + domtype ); } if( ch.length > 0 ) { let out = []; for( let a = 0; a < ch.length; a++ ) { // Prevent system from reinitializing in a race condition if( !ch[a].getAttribute( 'initializing' ) ) { ch[a].setAttribute( 'initializing', 'true' ); out.push( ch[a] ); } } for( let a = 0; a < out.length; a++ ) { let classStr = 'FUI' + domtype.substr( 0, 1 ).toUpperCase() + domtype.substr( 1, domtype.length - 1 ); let classObj = eval( classStr ); let opts = {}; opts.placeholderElement = out[a]; // Transfer innerHTML to options if( opts.placeholderElement.childNodes.length ) { let els = opts.placeholderElement.getElementsByTagName( '*' ); opts.childNodes = []; for( let b = 0; b < els.length; b++ ) { if( els[b].parentNode != opts.placeholderElement ) continue; opts.childNodes.push( els[ b ] ); } opts.innerHTML = opts.placeholderElement.innerHTML; } new classObj( opts ); } } } )( types[b] ); } }, // Get a fragment for processing getFragment( uniqueid, replacements ) { if( this.fragments[ uniqueid ] ) { let frag = this.fragments[ uniqueid ]; if( replacements ) { return this.applyReplacements( frag, replacements ); } return frag; } return false; }, // Apply replacements on string applyReplacements( string, kvchain ) { string = string + ''; // Make sure it is a string for( let a in kvchain ) { string = string.split( '{' + a + '}' ).join( kvchain[ a ] ); } return string; }, // Append child with initializer appendChild( parent, child ) { parent.appendChild( child ); this.initialize(); }, // Insert before element, with initializer insertBefore( newnode, parent ) { parent.insertBefore( newnode, parent ); this.initialize(); }, // Get that element! getElementByUniqueId( id ) { return this.guiElements[ id ] ? this.guiElements[ id ] : false; }, // Add a callback addCallback( callbackId, callbackFunc ) { this.callbacks[ callbackId ] = callbackFunc; }, // Simple system to allow stuff to add click on window addEvent( name, type, func ) { if( this.events[ type ] ) { this.events[ type ][ name ] = func; return true; } return false; }, // Simple system to remove click event by name removeEvent( name, type ) { if( this.events[ type ][ name ] ) { this.events[ type ][ name ] = null; return true; } return false; } }; // Activate click events ( function() { let eventTypes = [ 'click', 'mouseup', 'mousedown', 'mousemove', 'keyup', 'keydown' ]; for( let a in eventTypes ) { let event = eventTypes[ a ]; FUI.events[ event ] = []; window.addEventListener( event, function( e ) { if( FUI.events[ event ] ) { for( let a in FUI.events[ event ] ) if( FUI.events[ event ][ a ] ) FUI.events[ event ][ a ]( e ); } } ); } } )(); // Base class class FUIElement { // Sets default values etc constructor( options ) { this.options = options ? options : false; if( this.options && typeof( options.uniqueid ) != 'undefined' && options.uniqueid ) { if( window.FUI.guiElements[ options.uniqueid ] ) { console.log( 'FUI: Gui element with proposed uniqueId ' + options.uniqueid + ' is taken. Overwriting.' ); } window.FUI.guiElements[ options.uniqueid ] = this; } let d = document.createElement( 'div' ); this.domElement = d; this.attachDomElement(); } // Sets options on gui element setOptions( options ) { for( let a in options ) { this.options[ a ] = options[ a ]; } } // Attaches GUI to dom element if specified attachDomElement() { if( !this.options ) return; if( !this.domElement.parentNode && this.options.containerElement ) { this.grabAttributes( this.options.containerElement ); this.options.containerElement.appendChild( this.domElement ); } else if( !this.domElement.parentNode && this.options.placeholderElement ) { this.grabAttributes( this.options.placeholderElement ); this.options.placeholderElement.parentNode.replaceChild( this.domElement, this.options.placeholderElement ); } } // Grabs attributes from the dom element if they are supported grabAttributes( domElement ) { let uid = domElement.getAttribute( 'uniqueid' ); if( uid ) { // Set directly window.FUI.guiElements[ uid ] = this; } } // Refreshes gui's own dom element refreshDom() { } } /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ class FUIRadiobox extends FUIElement { constructor( options ) { super( options ); if( options.value ) this.value = options.value; if( options.elements ) this.elements = options.elements; } attachDomElement() { super.attachDomElement(); let self = this; this.domElement.classList.add( 'FUI', 'FUIRadiobox' ); this.refreshDom(); } refreshDom() { let self = this; if( this.changed ) { // Gui containers let str = ''; if( this.elements && this.elements.length ) { for( let a in this.elements ) { let e = this.elements[ a ]; str += '
'; str += '
' + e.label + '
'; str += '
'; } } this.domElement.innerHTML = str; // Events let radios = this.domElement.getElementsByClassName( 'FUIRadioElement' ); for( let a = 0; a < radios.length; a++ ) { ( function( r, item, ele ){ r.onclick = function() { self.value = ele.value; if( self.options.onchange ) { // Trigger callback if( window.FUI.callbacks[ self.options.onchange ] ) { // Add structure with current element flags window.FUI.callbacks[ self.options.onchange ]( self.value ); } } self.refreshDom(); } } )( radios[ a ], a, this.elements[ a ] ); } this.changed = false; } let radios = this.domElement.getElementsByClassName( 'FUIRadioElement' ); for( let a = 0; a < this.elements.length; a++ ) { if( this.elements[a].value == this.value ) { radios[a].classList.add( 'FUISelected' ); } else { radios[a].classList.remove( 'FUISelected' ); } } } grabAttributes( domElement ) { let val = domElement.getAttribute( 'value' ); if( val ) this.value = parseFloat( val ); let elements = domElement.getElementsByTagName( 'radioelement' ); if( !elements || !elements.length ) return; this.elements = []; for( let a = 0; a < elements.length; a++ ) { let el = elements[a]; let e = { selected: el.getAttribute( 'selected' ) == 'selected' ? true : false, label: el.getAttribute( 'label' ) ? el.getAttribute( 'label' ): '', value: el.getAttribute( 'value' ) }; if( e.selected ) { this.value = e.value; } this.elements.push( e ); } // Support onchange if( domElement.getAttribute( 'onchange' ) ) { this.setOptions( { onchange: domElement.getAttribute( 'onchange' ) } ); } this.changed = true; this.refreshDom(); } } FUI.registerClass( 'radiobox' ); // Checkbox element class FUICheckbox extends FUIElement { constructor( options ) { super( options ); if( options && options.checked ) this.checked = options.checked; } attachDomElement() { super.attachDomElement(); let self = this; this.domElement.onclick = function() { if( self.checked ) { self.domElement.classList.remove( 'FUIChecked' ); self.checked = false; } else { self.domElement.classList.add( 'FUIChecked' ); self.checked = true; } if( self.options.onchange ) { // Trigger callback if( window.FUI.callbacks[ self.options.onchange ] ) { // Add structure with current element flags window.FUI.callbacks[ self.options.onchange ]( self.checked ); } } } this.domElement.classList.add( 'FUI', 'FUICheckbox' ); this.domElement.innerHTML = '
'; } grabAttributes( domElement ) { super.grabAttributes( domElement ); if( domElement.getAttribute( 'checked' ) ) { this.checked = domElement.getAttribute( 'checked' ) != '' ? true : false; } else { this.checked = false; } // Support onchange if( domElement.getAttribute( 'onchange' ) ) { this.setOptions( { onchange: domElement.getAttribute( 'onchange' ) } ); } this.refreshDom(); } refreshDom() { super.refreshDom(); // Make sure we see checked if( this.checked ) { this.domElement.classList.add( 'FUIChecked' ); } else { this.domElement.classList.remove( 'FUIChecked' ); } } getMarkup( data ) { let str = ''; let opts = []; for( let a in data ) { if( a == 'OnChange' ) { opts.push( 'onchange="' + data[a] + '"' ); } if( a == 'Value' && data[a] ) { opts.push( 'checked="checked"' ); } } if( opts.length ) { str = str.split( '{options}' ).join( opts.join( ' ' ) ); } else { str = str.split( ' {options}' ).join( '' ); } return str; } } FUI.registerClass( 'checkbox' ); class FUIItembox extends FUIElement { constructor( options ) { super( options ); if( options.onsubmit ) this.onsubmit = options.onsubmit; } attachDomElement() { super.attachDomElement(); let self = this; this.domElement.classList.add( 'FUI', 'FUIItembox', 'BordersDefault', 'Padding', 'MarginBottom' ); this.domElement.innerHTML = '
\

Group name:

\
\
\
\

Description:

\
\
\
\
\
'; // Set up events let inp = this.domElement.getElementsByTagName( 'input' )[0]; let txt = this.domElement.getElementsByTagName( 'textarea' )[0]; let btn = this.domElement.getElementsByTagName( 'button' )[0]; btn.onclick = function() { // Trigger callback if( window.FUI.callbacks[ self.options.onsubmit ] ) { // Add structure with current element flags window.FUI.callbacks[ self.options.onsubmit ]( { name: inp.value, description: txt.value } ); } } setTimeout( function() { inp.focus(); }, 5 ); } grabAttributes( domElement ) { super.grabAttributes( domElement ); // Support onchange if( domElement.getAttribute( 'onsubmit' ) ) { this.setOptions( { onsubmit: domElement.getAttribute( 'onsubmit' ) } ); } this.refreshDom(); } refreshDom() { super.refreshDom(); // Make sure we see checked if( this.checked ) { this.domElement.classList.add( 'FUIChecked' ); } else { this.domElement.classList.remove( 'FUIChecked' ); } } } FUI.registerClass( 'itembox' ); /* Image class */ class FUIPicture extends FUIElement { constructor( options ) { super( options ); // Do stuff } attachDomElement() { super.attachDomElement(); let self = this; this.domElement.classList.add( 'FUI', 'FUIPicture' ); } grabAttributes( domElement ) { super.grabAttributes( domElement ); let attrs = [ 'width', 'height', 'icon', 'type', 'shape', 'border-size', 'onclick' ]; for( let a in attrs ) { let op = domElement.getAttribute( attrs[ a ] ); if( op ) this.options[ attrs[ a ] ] = op; } this.refreshDom(); } refreshDom() { super.refreshDom(); let self = this; // Do something with properties on dom if( this.options.width ) { this.domElement.style.width = this.options.width; } if( this.options.height ) { this.domElement.style.height = this.options.height; } let exClasses = ''; if( this.options.shape ) { if( this.options.shape == 'circle' ) { this.domElement.style.borderRadius = '100%'; } else { this.domElement.style.borderRadius = ''; } } if( this.options[ 'border-size' ] ) { this.domElement.style.borderWidth = this.options[ 'border-size' ]; this.domElement.style.borderStyle = 'solid'; } else { this.domElement.style.borderWidth = ''; } if( this.options[ 'onclick' ] ) { this.domElement.style.cursor = 'pointer'; this.domElement.onclick = function( e ) { cancelBubble( e ); if( window.FUI.callbacks[ self.options.onclick ] ) { // Add structure with current element flags window.FUI.callbacks[ self.options.onclick ]( true ); } return; } } // Set stuff on this.domElement.innerHTML if( this.options.type == 'icon' ) { let icon = this.options.icon ? ( 'fa-' + this.options.icon ) : 'fa-info'; this.domElement.innerHTML = '
'; if( this.options[ 'border-size' ] ) { let d = this.domElement.getElementsByTagName( 'div' )[0]; d.style.marginTop = -( this.options[ 'border-size' ] ) + 'px'; } } } getMarkup( data ) { // Return meta-markup for class instantiation later let str = ''; let opts = []; for( let a in data ) { if( a == 'width' ) { opts.push( 'width="' + data[a] + '"' ); } if( a == 'height' && data[a] ) { opts.push( 'height="' + data[a] + '"' ); } if( a == 'type' && data[a] ) { opts.push( 'type="' + data[a] + '"' ); } } if( opts.length ) { str = str.split( '{options}' ).join( opts.join( ' ' ) ); } else { str = str.split( ' {options}' ).join( '' ); } return str; } } FUI.registerClass( 'picture' ); // Textarea element class FUITextarea extends FUIElement { constructor( options ) { super( options ); } attachDomElement() { super.attachDomElement(); let self = this; // Set stuff on this.domElement.innerHTML this.adaptSize(); } grabAttributes( domElement ) { super.grabAttributes( domElement ); let attrs = [ /*'width', 'height', 'icon', 'type', 'shape', 'border-size',*/ 'uniqueid', 'icon', 'onchange' ]; for( let a in attrs ) { let op = domElement.getAttribute( attrs[ a ] ); if( op ) this.options[ attrs[ a ] ] = op; } this.refreshDom(); } refreshDom() { super.refreshDom(); this.domElement.classList.add( 'FUITextareaContainer' ); let self = this; // Class for dom element let cl = ''; if( this.options[ 'icon' ] ) { cl += ' IconSmall fa-' + this.options[ 'icon' ]; } // TODO: Add properties, uniqueId etc this.domElement.innerHTML = '
'; // Sets onchange and onkeyup on input element if( this.options[ 'onchange' ] ) { let inp = this.domElement.getElementsByTagName( 'textarea' )[0]; inp.onchange = function( e ) { cancelBubble( e ); if( window.FUI.callbacks[ self.options.onchange ] ) { // Add structure with current element flags window.FUI.callbacks[ self.options.onchange ]( inp.value ); } return; } inp.onkeyup = function( e ) { cancelBubble( e ); if( window.FUI.callbacks[ self.options.onchange ] ) { // Add structure with current element flags window.FUI.callbacks[ self.options.onchange ]( inp.value ); } return; } } this.adaptSize(); } setValue( string ) { this.domElement.getElementsByTagName( 'textarea' )[0].value = string; } getValue() { return this.domElement.getElementsByTagName( 'textarea' )[0].value; } focus() { return this.domElement.getElementsByTagName( 'textarea' )[0].focus(); } getMarkup( data ) { // Return meta-markup for class instantiation later let attrs = [ /*'width', 'height', 'icon', 'type', 'shape', 'border-size',*/ 'icon', 'uniqueid', 'onchange' ]; let attrStr = []; // Build an array of attributes for( let a in attrs ) { let op = this.options[ attrs[ a ] ] if( op ) { attrStr.push( attrs[ a ] + '="' + op + '"' ); } } if( attrStr.length > 0 ) { attrStr = ' ' + attrStr.join( ' ' ); } else attrStr = ''; return '' + ( this.options.value ? this.options.value : '' ) + ''; } adaptSize() { // TODO: } } FUI.registerClass( 'textarea', FUITextarea ); // String element class FUIString extends FUIElement { constructor( options ) { super( options ); } attachDomElement() { super.attachDomElement(); let self = this; // Set stuff on this.domElement.innerHTML this.adaptSize(); } grabAttributes( domElement ) { super.grabAttributes( domElement ); let attrs = [ /*'width', 'height', 'icon', 'type', 'shape', 'border-size',*/ 'uniqueid', 'icon', 'onchange' ]; for( let a in attrs ) { let op = domElement.getAttribute( attrs[ a ] ); if( op ) this.options[ attrs[ a ] ] = op; } this.refreshDom(); } refreshDom() { super.refreshDom(); let self = this; // Class for dom element let cl = ''; if( this.options[ 'icon' ] ) { cl += ' IconSmall fa-' + this.options[ 'icon' ]; } // TODO: Add properties, uniqueId etc this.domElement.innerHTML = '
'; // Sets onchange and onkeyup on input element if( this.options[ 'onchange' ] ) { let inp = this.domElement.getElementsByTagName( 'input' )[0]; inp.onchange = function( e ) { cancelBubble( e ); if( window.FUI.callbacks[ self.options.onchange ] ) { // Add structure with current element flags window.FUI.callbacks[ self.options.onchange ]( inp.value ); } return; } inp.onkeyup = function( e ) { cancelBubble( e ); if( window.FUI.callbacks[ self.options.onchange ] ) { // Add structure with current element flags window.FUI.callbacks[ self.options.onchange ]( inp.value ); } return; } } this.adaptSize(); } setValue( string ) { this.domElement.getElementsByTagName( 'input' )[0].value = string; } getValue() { return this.domElement.getElementsByTagName( 'input' )[0].value; } focus() { return this.domElement.getElementsByTagName( 'input' )[0].focus(); } getMarkup( data ) { // Return meta-markup for class instantiation later let attrs = [ /*'width', 'height', 'icon', 'type', 'shape', 'border-size',*/ 'icon', 'uniqueid', 'onchange' ]; let attrStr = []; // Build an array of attributes for( let a in attrs ) { let op = this.options[ attrs[ a ] ] if( op ) { attrStr.push( attrs[ a ] + '="' + op + '"' ); } } if( attrStr.length > 0 ) { attrStr = ' ' + attrStr.join( ' ' ); } else attrStr = ''; return '' + ( this.options.value ? this.options.value : '' ) + ''; } adaptSize() { // Adapt size if button is higher! let p = this.domElement.parentNode; if( p ) { let d = this.domElement.querySelector( '.FUIStringElement' ); let h = p.offsetHeight; let styles = getComputedStyle( p ); h -= parseInt( styles.paddingTop ) + parseInt( styles.paddingBottom ); if( d && d.offsetHeight > h ) { d.style.height = h + 'px'; if( h < 20 ) d.style.lineHeight = '0.9'; if( h < 20 ) d.style.fontSize = 'var(--font-size-small)'; else d.style.fontSize = ''; } } } } FUI.registerClass( 'string', FUIString ); // Button element class FUIButton extends FUIElement { constructor( options ) { super( options ); } attachDomElement() { super.attachDomElement(); let self = this; // Set stuff on this.domElement.innerHTML this.adaptSize(); } grabAttributes( domElement ) { super.grabAttributes( domElement ); let attrs = [ /*'width', 'height', 'icon', 'type', 'shape', 'border-size',*/ 'uniqueid', 'icon', 'onclick' ]; for( let a in attrs ) { let op = domElement.getAttribute( attrs[ a ] ); if( op ) this.options[ attrs[ a ] ] = op; } this.refreshDom(); } refreshDom() { super.refreshDom(); let self = this; // Class for dom element let cl = ''; if( this.options[ 'onclick' ] ) { this.domElement.style.cursor = 'pointer'; this.domElement.onclick = function( e ) { cancelBubble( e ); if( window.FUI.callbacks[ self.options.onclick ] ) { // Add structure with current element flags window.FUI.callbacks[ self.options.onclick ]( true ); } return; } cl += ' Clickable'; } if( this.options[ 'icon' ] ) { cl += ' IconSmall fa-' + this.options[ 'icon' ]; } // TODO: Add properties, uniqueId etc this.domElement.innerHTML = '
' + ( this.options.innerHTML ? this.options.innerHTML : '' ) + '
'; this.adaptSize(); } getMarkup( data ) { // Return meta-markup for class instantiation later let attrs = [ /*'width', 'height', 'icon', 'type', 'shape', 'border-size',*/ 'icon', 'onclick' ]; let attrStr = []; // Build an array of attributes for( let a in attrs ) { let op = this.options[ attrs[ a ] ] if( op ) { attrStr.push( attrs[ a ] + '="' + op + '"' ); } } if( attrStr.length > 0 ) { attrStr = ' ' + attrStr.join( ' ' ); } else attrStr = ''; return '' + ( this.options.value ? this.options.value : '' ) + ''; } adaptSize() { // Adapt size if button is higher! let p = this.domElement.parentNode; if( p ) { let d = this.domElement.querySelector( '.FUIButtonElement' ); let h = p.offsetHeight; let styles = getComputedStyle( p ); h -= parseInt( styles.paddingTop ) + parseInt( styles.paddingBottom ); if( d && d.offsetHeight > h ) { d.style.height = h + 'px'; if( h < 20 ) d.style.lineHeight = '0.9'; if( h < 20 ) d.style.fontSize = 'var(--font-size-small)'; else d.style.fontSize = ''; } } } } FUI.registerClass( 'button', FUIButton ); // Button element class FUIText extends FUIElement { constructor( options ) { super( options ); } attachDomElement() { super.attachDomElement(); let self = this; // Set stuff on this.domElement.innerHTML this.adaptSize(); } grabAttributes( domElement ) { super.grabAttributes( domElement ); let attrs = [ /*'width', 'height', 'icon', 'type', 'shape', 'border-size',*/ 'uniqueid', 'size', 'icon', 'onclick' ]; for( let a in attrs ) { let op = domElement.getAttribute( attrs[ a ] ); if( op ) this.options[ attrs[ a ] ] = op; } this.refreshDom(); } refreshDom() { super.refreshDom(); let self = this; // Class for dom element let cl = ''; if( this.options[ 'onclick' ] ) { this.domElement.style.cursor = 'pointer'; this.domElement.onclick = function( e ) { cancelBubble( e ); if( window.FUI.callbacks[ self.options.onclick ] ) { // Add structure with current element flags window.FUI.callbacks[ self.options.onclick ]( true ); } return; } cl += ' Clickable '; } if( this.options[ 'icon' ] ) { cl += ' IconSmall fa-' + this.options[ 'icon' ]; } // TODO: Add properties, uniqueId etc this.domElement.innerHTML = '
' + ( this.options.innerHTML ? this.options.innerHTML : '' ) + '
'; this.adaptSize(); } getMarkup( data ) { // Return meta-markup for class instantiation later let attrs = [ /*'width', 'height', 'icon', 'type', 'shape', 'border-size',*/ 'sze', 'icon', 'onclick' ]; let attrStr = []; // Build an array of attributes for( let a in attrs ) { let op = this.options[ attrs[ a ] ] if( op ) { attrStr.push( attrs[ a ] + '="' + op + '"' ); } } if( attrStr.length > 0 ) { attrStr = ' ' + attrStr.join( ' ' ); } else attrStr = ''; return '' + ( this.options.value ? this.options.value : '' ) + ''; } adaptSize() { // TODO } } FUI.registerClass( 'text', FUIText ); // SimpleHTML element class FUIHTML extends FUIElement { constructor( options ) { super( options ); } attachDomElement() { super.attachDomElement(); } grabAttributes( domElement ) { super.grabAttributes( domElement ); let attrs = [ /*'width', 'height', 'icon', 'type', 'shape', 'border-size',*/ 'uniqueid' ]; for( let a in attrs ) { let op = domElement.getAttribute( attrs[ a ] ); if( op ) this.options[ attrs[ a ] ] = op; } this.refreshDom(); } refreshDom() { super.refreshDom(); let self = this; // Class for dom element let cl = ''; // TODO: Add properties, uniqueId etc this.domElement.innerHTML = '
'; if( this.options.childNodes ) { let fml = this.domElement.getElementsByTagName( 'div' )[0]; let cn = this.options.childNodes; for( let a = 0; a < cn.length; a++ ) fml.appendChild( cn[a] ); } } getMarkup( data ) { // Return meta-markup for class instantiation later let attrs = [ 'uniqueid' ]; let attrStr = []; // Build an array of attributes for( let a in attrs ) { let op = this.options[ attrs[ a ] ] if( op ) { attrStr.push( attrs[ a ] + '="' + op + '"' ); } } if( attrStr.length > 0 ) { attrStr = ' ' + attrStr.join( ' ' ); } else attrStr = ''; return '' + ( this.options.value ? this.options.value : '' ) + ''; } } FUI.registerClass( 'html', FUIHTML ); /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ class FUIGroup extends FUIElement { constructor( options ) { super( options ); } // Sets options on gui element setOptions( options ) { if( options.value ) this.value = options.value; if( options.elements ) this.elements = options.elements; } // Attaches GUI to dom element if specified attachDomElement() { super.attachDomElement(); let self = this; this.domElement.classList.add( 'FUI', 'FUIGroup' ); this.refreshDom(); } // Grabs attributes from the dom element if they are supported grabAttributes( domElement ) { super.grabAttributes( domElement ); let self = this; this.domElement.innerHTML = ''; // Support fixed width let width = domElement.getAttribute( 'width' ); if( width ) { this.domElement.style.width = width; } // Support fixed height let height = domElement.getAttribute( 'height' ); if( height ) { this.domElement.style.height = height; } // Support gap let gap = domElement.getAttribute( 'gap' ); if( gap ) { let int = parseInt( gap ); if( isNaN( int ) ) int = 0; this.domElement.style.gap = int + 'px'; } // Support margins let margin = domElement.getAttribute( 'margin' ); if( margin ) { if( margin == 'normal' ) { this.domElement.style.padding = 'var(--fui-padding-normal)'; } else if( margin == 'small' ) { this.domElement.style.padding = 'var(--fui-padding-row)'; } else if( margin == 'large' ) { this.domElement.style.padding = 'var(--fui-padding-large)'; } else { let int = parseInt( margin ); if( isNaN( int ) ) int = 0; this.domElement.style.margin = int + 'px'; } } // Left margin let marginLeft = domElement.getAttribute( 'margin-left' ); if( marginLeft ) { if( marginLeft == 'normal' ) { this.domElement.style.paddingLeft = 'var(--fui-padding-normal)'; } else if( marginLeft == 'small' ) { this.domElement.style.paddingLeft = 'var(--fui-padding-row)'; } else if( marginLeft == 'large' ) { this.domElement.style.paddingLeft = 'var(--fui-padding-large)'; } else { let int = parseInt( marginLeft ); if( isNaN( int ) ) int = 0; this.domElement.style.marginLeft = int + 'px'; } } // Right margin let marginRight = domElement.getAttribute( 'margin-right' ); if( marginRight ) { if( marginRight == 'normal' ) { this.domElement.style.paddingRight = 'var(--fui-padding-normal)'; } else if( marginRight == 'small' ) { this.domElement.style.paddingRight = 'var(--fui-padding-row)'; } else if( marginRight == 'large' ) { this.domElement.style.paddingRight = 'var(--fui-padding-large)'; } else { let int = parseInt( marginRight ); if( isNaN( int ) ) int = 0; this.domElement.style.marginRight = int + 'px'; } } // Group containers with rows cannot have columns let rowcontainer = domElement.getElementsByTagName( 'rows' ); if( rowcontainer.length ) { if ( rowcontainer[0].parentNode == domElement ) { rowcontainer = rowcontainer[0]; this.setGroupAttributes( rowcontainer ); let rows = rowcontainer.getElementsByTagName( 'row' ); for( let a = 0; a < rows.length; a++ ) { if( rows[a].parentNode != rowcontainer ) continue; let d = document.createElement( 'div' ); d.className = 'FUIRow'; // Is column scrollable? let scrollable = rows[a].getAttribute( 'scrollable' ); if( scrollable ) { d.style.overflow = 'auto'; d.style.scrollBehavior = ''; if( scrollable == 'smooth' ) { d.style.scrollBehavior = 'smooth'; } } // Add padding let padding = rows[a].getAttribute( 'padding' ); if( padding ) { switch( padding ) { case 'row': case 'small': case 'normal': case 'big': d.style.padding = 'var(--fui-padding-' + padding + ')'; break; } } let uniqueid = rows[a].getAttribute( 'uniqueid' ); if( uniqueid ) { d.uniqueid = uniqueid; } let children = rows[a].getElementsByTagName( '*' ); for( let b = 0; b < children.length; b++ ) { if( children[b].parentNode != rows[a] ) continue; d.appendChild( children[b] ); } this.domElement.appendChild( d ); } this.domElement.classList.remove( 'FUIColumns' ); this.domElement.classList.add( 'FUIRows' ); return; } } // Group containers with columns cannot have rows let colcontainer = domElement.getElementsByTagName( 'columns' ); if( colcontainer.length ) { if ( colcontainer[0].parentNode == domElement ) { colcontainer = colcontainer[0]; this.setGroupAttributes( colcontainer ); let columns = colcontainer.getElementsByTagName( 'column' ); for( let a = 0; a < columns.length; a++ ) { if( columns[a].parentNode != colcontainer ) continue; let d = document.createElement( 'div' ); d.className = 'FUIColumn'; // Is column scrollable? let scrollable = columns[a].getAttribute( 'scrollable' ); if( scrollable ) { d.style.overflow = 'auto'; d.style.scrollBehavior = ''; if( scrollable == 'smooth' ) { d.style.scrollBehavior = 'smooth'; } } // Add padding let padding = columns[a].getAttribute( 'padding' ); if( padding ) { switch( padding ) { case 'row': case 'small': case 'normal': case 'big': d.style.padding = 'var(--fui-padding-' + padding + ')'; break; } } if( columns[a].getAttribute( 'id' ) ) { d.id = columns[a].getAttribute( 'id' ); } let children = columns[a].getElementsByTagName( '*' ); for( let b = 0; b < children.length; b++ ) { if( children[b].parentNode != columns[a] ) continue; d.appendChild( children[b] ); } this.domElement.appendChild( d ); } this.domElement.classList.remove( 'FUIRows' ); this.domElement.classList.add( 'FUIColumns' ); return; } } } setGroupAttributes( domElement ) { // Use gap for flex box let gap = domElement.getAttribute( 'gap' ); if( gap ) { let int = parseInt( gap ); if( isNaN( int ) ) int = 0; this.domElement.style.gap = int + 'px'; } // Set height based on parent let height = domElement.getAttribute( 'height' ); if( height ) { this.domElement.style.height = height; } let width = domElement.getAttribute( 'width' ); if( width ) { this.domElement.style.width = width; } } // Get an element by id getElementByUniqueId( id ) { let eles = this.domElement.childNodes; for( let a = 0; a < eles.length; a++ ) { if( eles[ a ].uniqueid == id ) { return { domElement: eles[ a ], setContent: function( cnt ) { this.domElement.innerHTML = cnt; FUI.initialize(); return true; } }; } let eles2 = eles[ a ].childNodes; if( eles2 ) { for( let b = 0; b < eles2.length; b++ ) { if( eles2[ b ].id == id ) { return { domElement: eles[ b ], setContent: function( cnt ) { this.domElement.innerHTML = cnt; FUI.initialize(); return true; } }; } } } } // Fails return false; } // Refreshes gui's own dom element refreshDom() { } } FUI.registerClass( 'group', FUIGroup ); /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ class FUIListview extends FUIElement { constructor( options ) { super( options ); if( !this.options.hasHeader ) this.options.hasHeader = false; if( !this.options.hasHeader ) this.options.hasHeaders = false; if( !this.options.hasRows ) this.options.hasRows = false; } show() { this.domElement.classList.toggle( 'hidden', false ); } hide() { this.domElement.classList.toggle( 'hidden', true ); } attachDomElement() { super.attachDomElement(); this.domElement.classList.add( 'FUI', 'FUIListview' ); } grabAttributes( domElement ) { super.grabAttributes( domElement ); const self = this; let header = domElement.getElementsByTagName( 'listviewhead' ); let headers = domElement.getElementsByTagName( 'listviewheaders' ); let rows = domElement.getElementsByTagName( 'listviewrows' ); if( header && header[0] ) { header = header[0]; this.options.hasHeader = true; let d = document.createElement( 'div' ); d.className = 'FUIListviewHeader'; // check for navigation const headNavigationCollection = header.getElementsByTagName( 'listviewnavigation' ); if ( headNavigationCollection[0] ) { const headNavigationElement = headNavigationCollection[0]; this.options.hasNavigation = true; d.classList.add( 'NavPad' ); const headNavigationContainer = document.createElement( 'div' ); headNavigationContainer.classList.add( 'HeadNav' ); for ( let i = 0; i < headNavigationElement.children.length; i++ ) { const headNavBtn = headNavigationElement.children[ 0 ]; if ( 'listviewbutton' == headNavBtn.localName ) { const navButton = document.createElement( 'div' ); navButton.classList.add( 'HeaderButton', 'IconSmall', 'MousePointer' ); const icon = headNavBtn.getAttribute( 'icon' ); if ( icon ) { navButton.classList.add( 'fa-' + icon ); } const onc = headNavBtn.getAttribute( 'onclick' ) if ( onc ) { navButton.addEventListener( 'click', e => { if ( window.FUI.callbacks[ onc ]) window.FUI.callbacks[ onc ]( self ); }, true ); } headNavigationContainer.appendChild( navButton ); } } d.appendChild( headNavigationContainer ); } // Add the heading let heading = false; if( ( heading = header.getElementsByTagName( 'listviewheading' ) ) ) { heading = heading[0]; let h = document.createElement( 'h2' ); h.innerHTML = heading.innerHTML; d.appendChild( h ); } let toolbar = false; if( ( toolbar = header.getElementsByTagName( 'listviewtoolbar' ) ) ) { toolbar = toolbar[0]; if( toolbar ) { let t = document.createElement( 'div' ); t.className = 'FUIListviewToolbar Left'; let buttons = false; if( ( buttons = toolbar.getElementsByTagName( 'listviewbutton' ) ) ) { for( let a = 0; a < buttons.length; a++ ) { let b = document.createElement( 'div' ); b.classList.add( 'HeaderButton', 'IconSmall', 'MousePointer' ); let icon = ''; if( ( icon = buttons[a].getAttribute( 'icon' ) ) ) { b.classList.add( 'fa-' + icon ); } let cb = false; if( ( cb = buttons[a].getAttribute( 'onclick' ) ) ) { ( function( ele, cbk ) { ele.onclick = function() { // Trigger callback if( window.FUI.callbacks[ cbk ] ) { // Add structure with current element flags window.FUI.callbacks[ cbk ]( self ); } } } )( b, cb ); } t.appendChild( b ); } } self.filters = []; let filterEls = null; if ( ( filterEls = toolbar.getElementsByTagName( 'listviewfilter' ) ) ) { for( let a = 0; a < filterEls.length; a++ ) { const f = document.createElement( 'input' ); //f.classList.add( 'MousePointer' ); self.filters.push( f ); let cb = false; if( ( cb = filterEls[a].getAttribute( 'onchange' ) ) ) { ( function( ele, cbk ) { ele.onchange = function( e ) { // Trigger callback if( window.FUI.callbacks[ cbk ] ) { // Add structure with current element flags window.FUI.callbacks[ cbk ]( self, ele.value ); } } } )( f, cb ); } t.appendChild( f ); } } d.appendChild( t ); } } this.domElement.appendChild( d ); } self.headerElements = []; self.cols = { '_list' : [], '_current' : null, }; if( headers ) { headers = headers[0]; this.options.hasHeaders = true; let d = document.createElement( 'div' ); d.className = 'FUIListviewHeaders BorderTop'; let headerelements = headers.getElementsByTagName( 'listviewheader' ); let row = document.createElement( 'div' ); row.className = 'HRow'; for( let a = 0; a < headerelements.length; a++ ) { self.headerElements[ a ] = {}; self.headerElements[ a ].width = parseInt( headerelements[a].getAttribute( 'width' ) ); self.headerElements[ a ].align = headerelements[a].getAttribute( 'align' ); self.headerElements[ a ].name = headerelements[a].getAttribute( 'name' ) ? headerelements[a].getAttribute( 'name' ) : headerelements[a].innerText; self.headerElements[ a ].text = headerelements[a].innerText; self.headerElements[ a ].id = friendUP.tool.uid( 'h' ); if ( headerelements[ a ].getAttribute( 'sortDefault' ) ) { const so = headerelements[ a ].getAttribute( 'sortOrder' ); if ( 'ZA' == so ) self.sortInvert = true; self.sortDefault = self.headerElements[ a ].name; } self.cols[ self.headerElements[ a ].name ] = []; self.cols._list[ a ] = self.headerElements[ a ].name; if( !self.headerElements[ a ].align ) self.headerElements[ a ].align = 'left'; let h = document.createElement( 'div' ); let alignment = self.headerElements[ a ].align; if( alignment == 'left' ) alignment = ' TextLeft'; else if( alignment == 'right' ) alignment = ' TextRight'; else if( alignment == 'center' ) alignment = ' TextCenter'; h.id = self.headerElements[ a ].id; h.className = 'HContent' + self.headerElements[ a ].width + ' PaddingSmall Ellipsis FloatLeft' + alignment; h.innerHTML = headerelements[ a ].innerHTML; row.appendChild( h ); const hname = self.headerElements[a].name; const hidx = a; h.addEventListener( 'click', e => { let header = null; if ( e.keepCurrent && ( self.cols._current != null )) { const parts = self.cols._current.split( '_' ); header = parts[0]; if ( null != parts[ 1 ]) self.cols._current = header; else self.cols._current = header + '_inverted'; } else { if ( e.sortBy ) header = e.sortBy; else header = hname; if ( self.cols._current == null && self.sortInvert ) self.cols._current = header; } if ( !self.cols[ header ].length ) return; const hIdx = self.cols._list.indexOf( header ); const headId = self.headerElements[ hIdx ].id; const hEl = ge( headId ); if ( 'string' == self.cols[header][0].type ) { self.cols[header].sort(( ra, rb ) => { if ( ra.value == null || rb.value == null ) { if ( self.cols._current == header ) { if ( null == ra.value ) return -1; else return 1; } else { if ( null == ra.value ) return 1; else return -1; } } if ( String( ra.value ).toLowerCase() == String( rb.value ).toLowerCase() ) return 0; if ( self.cols._current == header ) { if ( String( ra.value ).toLowerCase() < String( rb.value ).toLowerCase() ) return 1; else return -1; } else { if ( String( ra.value ).toLowerCase() < String( rb.value ).toLowerCase() ) return -1; else return 1; } }); const p = ge( self.cols[ header ][ 0 ].rowId ).parentNode; for( let i = 0; i < self.cols[ header ].length; i++ ) { p.appendChild( ge( self.cols[ header ][ i ].rowId )); } if ( null == self.cols._current ) { self.cols._current = header; hEl.classList.toggle( 'ListDirectionAZ', true ); } else { const cP = self.cols._current.split( '_' ); const curr = cP[0]; const inv = !!cP[1]; const cIdx = self.cols._list.indexOf( curr ); const cHeadId = self.headerElements[ cIdx ].id; const cHEl = ge( cHeadId ); if ( inv ) cHEl.classList.toggle( 'ListDirectionZA', false ); else cHEl.classList.toggle( 'ListDirectionAZ', false ); if ( self.cols._current == header ) { self.cols._current = header + '_inverted'; hEl.classList.toggle( 'ListDirectionZA', true ); } else { self.cols._current = header; hEl.classList.toggle( 'ListDirectionAZ', true ); } } return; } }, false ); } d.appendChild( row ); this.domElement.appendChild( d ); } if( rows ) { rows = rows[0]; this.options.hasRows = true; let container = document.createElement( 'div' ); container.className = 'FUIListviewContent'; this.rowContainer = container; this.domElement.appendChild( container ); let onload = rows.getAttribute( 'onload' ); if( onload ) { this.onload = function( e ) { // Trigger callback if( window.FUI.callbacks[ onload ] ) { // Add structure with current element flags window.FUI.callbacks[ onload ]( self ); } else console.log( 'no callback found for', { callback_id : onload, FUI_callbacks : JSON.parse( JSON.stringify( FUI.callbacks )), }); } } } if( this.onload ) { this.onload(); } } setRowData( json ) { // Contains references to data this.dataset = {}; this.rowData = json; this.refreshRows(); } refreshRows() { const self = this; let json = this.rowData; this.clearRows(); for( let b = 0; b < json.length; b++ ) { let row = document.createElement( 'div' ); row.className = 'HRow EditRow'; row.id = friendUP.tool.uid( 'r' ); if ( json[b].onclick ) { const oc = json[b].onclick; const rd = json[b]; row.addEventListener( 'click', e => { if ( window.FUI.callbacks[ oc ]) window.FUI.callbacks[ oc ]( rd, self, e ); }, false ); } let baseWidth = parseInt( 100 / json[b].length ); for( let z = 0; z < json[b].length; z++ ) { // Not enough headers for json if( z >= this.headerElements.length ) break; let col = document.createElement( 'div' ); let w = this.headerElements && this.headerElements.length ? this.headerElements[ z ].width : baseWidth; let alignment = this.headerElements[ z ].align; if( alignment == 'left' ) alignment = ' TextLeft'; else if( alignment == 'right' ) alignment = ' TextRight'; else if( alignment == 'center' ) alignment = ' TextCenter'; col.className = 'HContent' + w + ' Ellipsis FloatLeft' + alignment; json[b][z].rowId = row.id; self.cols[ self.cols._list[ z ]][ b ] = json[b][z]; // Identify column dataset if( json[b][z].uniqueid ) { this.dataset[ json[b][z].uniqueid ] = json[b][z]; this.dataset[ json[b][z].uniqueid ].domNode = col; } let str = FUI.create( json[b][z] ); json[b][z].Name = this.headerElements[z].name; let onclick = json[b][z].onclick; if( onclick ) { ( function( data, index, column ) { column.onclick = function( e ) { if( e.target && e.target.nodeName == 'INPUT' ) return; if( window.FUI.callbacks[ onclick ] ) { // Add structure with current element attributes let obj = {}; for( let d = 0; d < data.length; d++ ) { let nam = data[ d ].name; if( !nam ) nam = d; obj[ nam ] = {}; for( let p in data[ d ] ) { if( p == 'name' ) continue; obj[ nam ][ p ] = data[ d ][ p ]; } } window.FUI.callbacks[ onclick ]( obj, data[ index ] ); } } } )( json[b], z, col ); col.classList.add( 'FUIListviewOnclick' ); } const onchange = json[b][z].onchange; if ( onchange ) { const data = json[b]; const cnf = json[b][z]; const el = col; el.onchange = e => { if ( window.FUI.callbacks[ onchange ] ) { let obj = {}; for( let d = 0; d < data.length; d++ ) { obj[ data[ d ].name ] = {}; for( let p in data[ d ] ) { if( p == 'name' ) continue; obj[ data[ d ].name ][ p ] = data[ d ][ p ]; } } if( e.target && e.target.value ) { obj.value = e.target.value; } window.FUI.callbacks[ onchange ]( obj, self, e ); } } } col.innerHTML = str; row.appendChild( col ); } this.rowContainer.appendChild( row ); } FUI.initialize(); } // Edit a row / column by id editColumnById( uid ) { let self = this; let set = this.dataset[ uid ]; // We need to handle editing many different types of columns if( set.type == 'string' ) { if( set.domNode && set.domNode.parentNode ) { set.domNode.innerHTML = ''; let nod = set.domNode.getElementsByTagName( 'input' )[0]; nod.addEventListener( 'blur', function( e ) { set.value = this.value; // If there's an onchange event, execute it and provide the dataset as well as listview object if( set.onchange ) { if( window.FUI.callbacks[ set.onchange ] ) { window.FUI.callbacks[ set.onchange ]( set, self ); return; } } self.refreshRows(); } ); nod.addEventListener( 'change', function( e ) { this.blur(); } ); nod.focus(); nod.select(); } else { console.log( 'FUI: No supported dom node: ', set ); } } else { console.log( 'FUI: Unsupported type: ' + set.type ); } } clearRows() { this.rowContainer.innerHTML = ''; } } FUI.registerClass( 'listview', FUIListview ); /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ /******************************************************************************* * * * Wraps API calls through messages and carries them out * * * * This file calls methods on real objects - not the proxy objects in api.js * * * *******************************************************************************/ // Assumptions window.isTablet = window.isMobile = false; // Main namespace Friend = window.Friend || {} Friend.iconsSelectedCount = 0; Friend.currentMenuItems = false; Friend.singleInstanceApps = []; Friend.windowBaseString = 'Friend Workspace'; // Some global variables var globalConfig = {}; globalConfig.language = 'en-US'; // Defaults to US english // Handle callbacks var apiWrapperCallbacks = []; function addWrapperCallback( f ) { var uniqueId = ( new Date() ).getTime() + '_' + ( Math.random() * 1000 ) + ( Math.random() * 1000 ); while( typeof( apiWrapperCallbacks[uniqueId] ) != 'undefined' ) uniqueId += '_'; apiWrapperCallbacks[uniqueId] = f; return uniqueId; } // Get the wrapper callback function getWrapperCallback( uniqueId ) { if( typeof( apiWrapperCallbacks[uniqueId] ) != 'undefined' ) { var func = apiWrapperCallbacks[uniqueId]; var o = []; for( let a in apiWrapperCallbacks ) { if( a != uniqueId ) o[a] = apiWrapperCallbacks[a]; } apiWrapperCallbacks = o; return func; } return false; } // Run a callback and remove from list function runWrapperCallback( uniqueId, data ) { if( typeof( apiWrapperCallbacks[uniqueId] ) == 'function' ) { apiWrapperCallbacks[uniqueId]( data ); var o = []; for( let a in apiWrapperCallbacks ) { if( a != uniqueId ) o[a] = apiWrapperCallbacks[a]; } apiWrapperCallbacks = o; } } // Make a callback function for an app based on a previous callback function makeAppCallbackFunction( app, data, source ) { if( !app || !data ) return false; var nmsg = {}; for( let a in data ) nmsg[ a ] = data[ a ]; nmsg.type = 'callback'; // Our destination if( source ) { // Just send to app return function(){ source.postMessage( JSON.stringify( nmsg ), '*' ); } } // Just send to app return function(){ app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } } // Native windows var nativeWindows = []; // Api wrapper main handler function apiWrapper( event, force ) { // TODO: Origin if( !event && !event.data ) return false; var edi = -1, d = event.data; try { if( d.match( /^[a-z]*?\' ) ) >= 0 ) d = event.data.substr( 0, edi - 1 ); if( !d.indexOf( '{' ) && !d.indexOf( '[' ) ) return false; } catch( e ) { if( typeof( d ) != 'object' ) { return false; } } var msg = false; try { msg = typeof( d ) == 'object' ? d : JSON.parse( d ); } catch( e ) { console.log( 'Unexpected answer: ' + d, event.data ); } // Check attributes for special types for( let prop in msg ) { if( prop.indexOf( '_format' ) < 0 ) continue; var propName = prop.substring( 0, prop.length - 7 ); if( typeof( msg[ propName ] ) != 'undefined' ) { switch( msg[ prop ] ) { case 'binaryString': case 'base64': msg[ propName ] = ConvertStringToArrayBuffer( msg[ propName ], msg[ prop ] ); //var data = msg[ propName ].split( ',' ); //msg[ propName ] = ( new Uint8Array( data ) ).buffer; break; } } } // Send message back to origin view/application or function function sendItBack( message ) { if ( typeof messageInfo.callback == 'function' ) { messageInfo.callback( message ); return; } if ( messageInfo.view ) { if ( typeof messageInfo.callback == 'string' ) { message.type = 'callback'; message.callback = messageInfo.callback; } message.applicationId = messageInfo.applicationId; message.viewId = messageInfo.viewId; messageInfo.view.postMessage( JSON.stringify( message ), '*' ); } } if( msg.type ) { // Find application iframe var app = findApplication( msg.applicationId ); if( force ) app = force; // <- Run with privileges if( !app ) { // Special case, enter the switch with these conditions if( msg.type != 'friendNetworkRun' ) { //console.log( 'apiwrapper - found no app for ', msg ); return false; } } // For Francois :) if( msg.type.substring( 0, 13 ) == 'friendNetwork' ) { var messageInfo = {}; if ( msg.applicationId ) { messageInfo.view = GetContentWindowByAppMessage( findApplication( msg.applicationId ), msg ); messageInfo.applicationId = msg.applicationId; if ( msg.applicationName ) messageInfo.applicationName = msg.applicationName; messageInfo.viewId = msg.viewId; messageInfo.callback = msg.callback; } } switch( msg.type ) { // Application messaging ------------------------------------------- case 'applicationmessaging': switch( msg.method ) { case 'open': ApplicationMessagingNexus.open( msg.applicationId, function( response ) { event.source.postMessage( { type: 'callback', callback: msg.callback, data: response }, '*' ); } ); break; case 'close': ApplicationMessagingNexus.close( msg.applicationId, function( response ) { event.source.postMessage( { type: 'callback', callback: msg.callback, data: response }, '*' ); } ); break; case 'getapplications': if( msg.callback ) { var out = []; for( let a = 0; a < Workspace.applications.length; a++ ) { var app = Workspace.applications[a]; if( app.applicationId == msg.applicationId ) continue; if( msg.application == '*' || app.applicationName.indexOf( msg.application ) == 0 ) { if( ApplicationMessagingNexus.ports[ app.applicationId ] ) { out.push( { hash: ApplicationMessagingNexus.ports[ app.applicationId ].hash, name: app.applicationName } ); } } } // Respond event.source.postMessage( { type: 'callback', callback: msg.callback, data: out }, '*' ); } break; case 'sendtoapp': var out = []; var responders = []; var sourceHash = ''; if( ApplicationMessagingNexus.ports[ msg.applicationId ] ) { sourceHash = ApplicationMessagingNexus.ports[ msg.applicationId ].hash; } for( let a = 0; a < Workspace.applications.length; a++ ) { var app = Workspace.applications[a]; if( app.applicationId == msg.applicationId ) continue; if( msg.application == '*' || app.applicationName.indexOf( msg.filter ) == 0 ) { if( ApplicationMessagingNexus.ports[ app.applicationId ] ) { out.push( ApplicationMessagingNexus.ports[ app.applicationId ] ); responders.push( { hash: ApplicationMessagingNexus.ports[ app.applicationId ].hash, name: app.applicationName } ); } } } // Check on hash if( !out.length ) { for( let a in ApplicationMessagingNexus.ports ) { if( ApplicationMessagingNexus.ports[ a ].app.applicationId == msg.applicationId ) continue; if( ApplicationMessagingNexus.ports[ a ].hash == msg.filter ) { out.push( ApplicationMessagingNexus.ports[a ] ); responders.push( { hash: ApplicationMessagingNexus.ports[ a ].hash, name: app.applicationName } ); } } } if( out.length ) { for( let a = 0; a < out.length; a++ ) { // Don't send to self if( out[ a ].app.applicationId == msg.applicationId ) continue; ( function( o ) { o.app.sendMessage( { type: 'applicationmessage', message: msg.message, source: sourceHash, callback: addWrapperCallback( function( data ) { event.source.postMessage( { type: 'applicationmessage', message: data }, '*' ); } ) } ); } )( out[ a ] ); } // Respond with responders event.source.postMessage( { type: 'callback', callback: msg.callback, data: responders }, '*' ); } break; } break; // DOS ------------------------------------------------------------- case 'dos': { let win = ( app && app.windows ) ? app.windows[ msg.viewId ] : false; let tar = win ? app.windows[ msg.targetViewId ] : false; // Target for postmessage let cbk = msg.callback; switch ( msg.method ) { case 'getDisks': Friend.DOS.getDisks( msg.flags, function( response, list, extra ) { let nmsg = { viewId: msg.viewId, applicationId: msg.applicationId, callback: cbk, response: response, list: list }; if( tar ) tar.iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); else app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } ); break; case 'getDirectory': Friend.DOS.getDirectory( msg.path, msg.flags, function( response, list, extra ) { let nmsg = { viewId: msg.viewId, applicationId: msg.applicationId, callback: cbk, response: response, list: list, path: msg.path, extra: extra }; if( tar ) tar.iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); else if( app.contentWindow ) app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); }, msg.extra ); break; case 'executeJSX': Friend.DOS.executeJSX( msg.path, msg.args, function( response, message, iframe, extra ) { let nmsg = { viewId: msg.viewId, applicationId: msg.applicationId, callback: cbk, response: response, extra: extra }; if( tar ) { tar.iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } // If we are injecting into an iframe returned from executeJSX! else if( iframe ) { iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } else app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); }, msg.extra ); break; case 'loadHTML': Friend.DOS.loadHTML( msg.applicationId, msg.path, function( response, html, extra ) { let nmsg = { viewId: msg.viewId, applicationId: msg.applicationId, callback: cbk, response: response, html: html, extra: extra }; if( tar ) tar.iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); else app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); }, msg.extra ); break; case 'getDriveInfo': Friend.DOS.getDriveInfo( msg.path, false, function( response, icon, extra ) { let nmsg = { viewId: msg.viewId, applicationId: msg.applicationId, callback: cbk, response: icon ? true : false, info: icon, path: msg.path, extra: msg.extra }; if( tar ) tar.iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); else app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); }, msg.extra ); break; case 'getFileInfo': Friend.DOS.getFileInfo( msg.path, function( response, icon, extra ) { let nmsg = { viewId: msg.viewId, applicationId: msg.applicationId, callback: cbk, response: response, info: icon, path: msg.path, extra: extra }; if( tar ) tar.iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); else app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); }, msg.extra ); break; case 'fileAccess': Friend.DOS.getFileAccess( msg.path, function( response, permissions, extra ) { // Setup the callback message let nmsg = { viewId : msg.viewId, applicationId : msg.applicationId, callback : cbk, response : response, permissions : permissions, path : msg.path, extra : extra }; if( tar ) tar.iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); else app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); }, msg.extra ); break; case 'openWindowByPath': Friend.DOS.getFileAccess( msg.path, {}, ( response, permissions, extra ) => { if ( !response ) return Friend.DOS.getFileInfo( msg.path, {}, ( response, fileInfo, extra ) => { if ( !response ) return if( msg.flags.context && msg.flags.context == '$CURRENTVIEWID' ) { msg.flags.context = currentMovable.windowObject.getViewId(); } fileInfo.flags = msg.flags; if( 'File' == fileInfo.Type ) Friend.DOS.openWindowByFilename( fileInfo ); if( 'Directory' == fileInfo.Type ) { fileInfo.MetaType = 'Directory'; fileInfo.Path = msg.path; console.log( 'open path', fileInfo ); OpenWindowByFileinfo( fileInfo ); } }) }) break; case 'openWindowByFilename': if( msg.args ) { console.log( 'openWindowByFilename', msg ) let appId = false; if( currentMovable && currentMovable.windowObject && currentMovable.windowObject.applicationId ) { appId = currentMovable.windowObject.applicationId; } Friend.DOS.openWindowByFilename( msg.args.fileInfo, msg.args.ext, appId ); } break; } msg.callback = null; break; } // Virtual Reality ------------------------------------------------- case 'friendvr': if( Friend.VRWrapper ) { var data = Friend.VRWrapper( msg ); // Handle callbacks if( msg.callback && app ) { if( msg.viewId && app.windows && app.windows[ msg.viewId ] ) { var nmsg = { applicationId: msg.applicationId, viewId: msg.viewId, type: 'callback', callback: msg.callback, data: data }; app.windows[ msg.viewId ].sendMessage( nmsg ); } else { var nmsg = { applicationId: msg.applicationId, type: 'callback', callback: msg.callback, data: data }; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } } } else { // First time, give an alert if( !Friend.VRError ) { Alert( 'VR mode not available', 'Please run Friend in VR mode to run this application.' ); Friend.VRError = 1; } } break; // Friend Network -------------------------------------------------- case 'friendnet': switch( msg.method ) { case 'isReady': FriendNetwork.isReady( { message: msg, extra: msg.extra, callback: msg.callback } ); break; case 'list': FriendNetwork.listHosts( { message: msg, extra: msg.extra, callback: msg.callback } ); break; case 'listToConsole': FriendNetwork.listHostsToConsole( { message: msg, extra: msg.extra, callback: msg.callback } ); break; case 'subscribeToHostListUpdates': FriendNetwork.subscribeToHostListUpdates( { message: msg, extra: msg.extra, callback: msg.callback } ); break; case 'unsubscribeFromHostListUpdates': FriendNetwork.unsubscribeFromHostListUpdates( { message: msg, extra: msg.extra, identifier: msg.identifier, callback: msg.callback } ); break; case 'subscribeToHostUpdates': FriendNetwork.subscribeToHostUpdates( { message: msg, extra: msg.extra, key: msg.key, callback: msg.callback } ); break; case 'unsubscribeFromHostUpdates': FriendNetwork.unsubscribeFromHostUpdates( { message: msg, extra: msg.extra, key: msg.key, callback: msg.callback } ); break; case 'connect': FriendNetwork.connectToHost( { message: msg, extra: msg.extra, url: msg.url, hostType: msg.hostType, flags: msg.flags, callback: msg.callback } ); break; case 'sendFile': FriendNetwork.sendFile( { message: msg, extra: msg.extra, key: msg.key, file: msg.file, name: msg.name, infos: msg.infos, callback: msg.callback } ); break; case 'disconnect': FriendNetwork.disconnectFromHost( { message: msg, key: msg.key, callback: msg.callback, extra: msg.extra } ); break; case 'disconnectByName': FriendNetwork.disconnectFromHostByName( { message: msg, hostName: msg.hostName, callback: msg.callback, extra: msg.extra } ); break; case 'dispose': FriendNetwork.disposeHosting( { message: msg, key: msg.key, callback: msg.callback, extra: msg.extra } ); break; case 'send': FriendNetwork.send( { message: msg, data: msg.data, key: msg.key, callback: msg.callback, extra: msg.extra } ); break; case 'sendCredentials': FriendNetwork.sendCredentials( { message: msg, password: msg.password, key: msg.key, encrypted: msg.encrypted, extra: msg.extra, callback: msg.callback } ); break; case 'setHostPassword': FriendNetwork.setHostPassword( { message: msg, password: msg.password, key: msg.key, extra: msg.extra, callback: msg.callback } ); break; case 'host': var newmsg = { message: msg, name: msg.name, connectionType: msg.connectionType, applicationName: msg.applicationName, description: msg.description, password: msg.password, data: msg.data, extra: msg.extra, callback: msg.callback }; FriendNetwork.startHosting( newmsg ); break; case 'updateHostPassword': FriendNetwork.updateHostPassword( { message: msg, key: msg.key, password: msg.password, extra: msg.extra, callback: msg.callback } ); break; case 'closeSession': FriendNetwork.closeSession({ message: msg, key: msg.key, extra: msg.extra, callback: msg.callback }); break; case 'closeApplication': FriendNetwork.closeApplication( msg ); break; case 'status': FriendNetwork.getStatus( { message: msg, extra: msg.extra } ) ; break; case 'getUserInformation': FriendNetwork.getUserInformation( { message: msg, extra: msg.extra, callback: msg.callback } ) ; break; } msg.callback = false; // terminate callback break; // Friend Network Share --------------------------------------------- case 'friendNetworkRun': switch ( msg.method ) { case 'start': FriendNetworkDoor.runRemoteApplication( msg, function( response, data, extra ) { var nmsg = { command: 'runRemoteApplicationResponse', response: response, data: data, extra: extra, isFriendAPI: true }; sendItBack( nmsg ); }, msg.extra ); break; } break; // Friend Network Share --------------------------------------------- case 'friendNetworkShare': switch( msg.method ) { case 'activate': FriendNetworkShare.activate( msg.activate ); break; case 'changeFriendNetworkSettings': FriendNetworkShare.changeFriendNetworkSettings( msg.settings ); break; } msg.callback = false; // terminate callback break; // Friend Network Drive --------------------------------------------- case 'friendNetworkDrive': switch( msg.method ) { case 'activate': FriendNetworkDrivecloseFriend.activate( msg.activate ); break; case 'changeFriendNetworkSettings': FriendNetworkDrive.changeFriendNetworkSettings( msg.settings ); break; } msg.callback = false; // terminate callback break; // Friend Network Door --------------------------------------------- case 'friendNetworkDoor': switch( msg.method ) { case 'activate': FriendNetworkDoor.activate( msg.activate ); break; case 'changeFriendNetworkSettings': FriendNetworkDoor.changeFriendNetworkSettings( msg.settings ); break; case 'relocateHTML': FriendNetworkDoor.relocateHTML( msg.html, msg.sourceDrive, msg.linkReplacement, msg.linkFunction, function( response, html ) { var nmsg = { command: 'relocateHTMLReponse', response: response, html: html, extra: msg.extra }; sendItBack( nmsg ); }, msg.extra ); break; case 'connectToDoor': FriendNetworkDoor.connectToDoor( msg.hostName, msg.appName, 'folder', function( response, connection ) { // Remove elements that make JSON.stringify crash //connection.door = false; var nmsg = { command: 'connectToDoorReponse', response: response, connection: connection, extra: msg.extra }; sendItBack( nmsg ); }, msg.extra ); break; case 'disconnectFromDoor': var response = FriendNetworkDoor.disconnectFromDoor( msg.door, msg.parameters ); var nmsg = { command: 'disconnectFromDoorReponse', response: response, extra: msg.extra }; sendItBack( nmsg ); break; case 'shareDoor': FriendNetworkDoor.shareDoor( msg.hostName, msg.appName, function( response, share ) { var nmsg = { command: 'disconnectFromDoorReponse', response: response, share: share, extra: msg.extra }; sendItBack( nmsg ); }, msg.extra ); break; case 'closeSharedDoor': var response = FriendNetworkDoor.closeSharedDoor( msg.name ); var nmsg = { command: 'disconnectFromDoorReponse', response: response, extra: msg.extra }; sendItBack( nmsg ); break; } msg.callback = false; // terminate callback break; // Automatic API System... case 'Friend': Friend.callAPIFunction( msg ); msg.callback = false; break; // Friend Network Friends --------------------------------------------- case 'friendNetworkFriends': switch( msg.method ) { case 'changeFriendNetworkSettings': FriendNetworkFriends.changeFriendNetworkSettings( msg.settings ); break; case 'listCommunities': FriendNetworkFriends.listCommunities( msg.url, function( response, communities, users, extra ) { var nmsg = { command: 'listCommunitiesResponse', response: response, communities: communities, users: users, extra: extra }; sendItBack( nmsg ); }, msg.extra ); break; case 'getUniqueDeviceIdentifier': FriendNetworkFriends.getUniqueDeviceIdentifier( function( identifier, extra ) { var nmsg = { command: 'getUniqueDeviceIdentifierResponse', identifier: identifier, extra: extra }; sendItBack( nmsg ); }, msg.extra ); break; case 'getDeviceInformation': FriendNetworkFriends.getDeviceInformation( msg.flags, function( information, extra ) { var nmsg = { command: 'getDeviceInformationResponse', information: information, extra: extra }; sendItBack( nmsg ); }, msg.extra ); break; default: break; } msg.callback = false; // terminate callback break; // Friend Network Apps --------------------------------------------- case 'friendNetworkApps': switch( msg.method ) { case 'changeFriendNetworkSettings': FriendNetworkApps.changeFriendNetworkSettings( msg.settings ); break; case 'registerApplication': FriendNetworkApps.registerApplication( msg.appInformation, msg.userInformation, msg.password, function( response, data, extra ) { sendItBack( response, data, extra ); }, msg.extra ); break; case 'closeApplication': FriendNetworkApps.closeApplication( msg.appIdentifier, function( response, data, extra ) { sendItBack( response, data, extra ); }, msg.extra ); break; case 'closeConnections': FriendNetworkApps.closeConnections( msg.appIdentifier, function( response, data, extra ) { sendItBack( response, data, extra ); }, msg.extra ); break; case 'closeRunningConnections': FriendNetworkApps.closeRunningConnections( msg.appIdentifier, function( response, data, extra ) { sendItBack( response, data, extra ); }, msg.extra ); break; case 'openHost': FriendNetworkApps.openHost( msg.appIdentifier, function( response, data, extra ) { sendItBack( response, data, extra ); }, msg.extra ); break; case 'closeHost': FriendNetworkApps.closeHost( msg.appIdentifier, function( response, data, extra ) { sendItBack( response, data, extra ); }, msg.extra ) break; case 'connectToUser': FriendNetworkApps.connectToUser( msg.appIdentifier, msg.nameHost, function( response, data, extra ) { sendItBack( response, data, extra ); }, msg.extra ); break; case 'disconnectFromApp': FriendNetworkApps.closeUser( msg.appIdentifier, msg.userIdentifier, function( response, data, extra ) { sendItBack( response, data, extra ); }, msg.extra ); break; case 'establishConnections': FriendNetworkApps.establishConnections( msg.appIdentifier, function( response, data, extra ) { sendItBack( response, data, extra ); }, msg.extra ); break; case 'sendMessageToAll': FriendNetworkApps.sendMessageToAll( msg.appIdentifier, msg.message, function( response, data, extra ) { sendItBack( response, data, extra ); }, msg.extra ); break; case 'startApplication': FriendNetworkApps.startApplication( msg.appIdentifier, function( response, data, extra ) { sendItBack( response, data, extra ); }, msg.extra ); break; case 'getHosts': FriendNetworkApps.getHosts( msg.appIdentifier, msg.filters, msg.registerToUpdates, function( response, data, extra ) { sendItBack( response, data, extra ); }, msg.extra ); break; } msg.callback = false; // terminate callback break; // Dormant --------------------------------------------------------- // TODO : permissions - does the app have persmission to: // 1: expose its own data // 2: pull from other exposed apps // - is it possible for applicationId to leak to other applications? case 'dormantmaster': switch( msg.method ) { case 'execute': //find the door var door = false; for (var a = 0; a < DormantMaster.appDoors.length; a++) { var doorTest = DormantMaster.appDoors[ a ].getDoor(); if( doorTest ) { if( doorTest.Title.split( ':' )[0].toLowerCase() == msg.executable.split( ':' )[ 0 ].toLowerCase() ) { door = DormantMaster.appDoors[ a ]; } } } if( door ) { var path = ''; if( msg.executable.indexOf( '/' ) ) { path = msg.executable.split( '/' ); path.pop(); path = path.join( '/' ) + '/'; } else { path = msg.executable.split( ':' )[ 0 ] + ':'; } door.getDirectory( path, function( data ) { // Callback for (var b = 0; b < data.length; b++) { if ((data[b].Path + data[b].Title).toLowerCase() == msg.executable.toLowerCase()) { data[b].Dormant.execute(data[b], msg.dormantArgs); } } var ret = { applicationId: msg.applicationId, callbackId: msg.callbackId, command: 'dormantmaster', method: 'callback', data: data }; if( msg.callback ) runWrapperCallback(msg.callback, data); } ); } else { if( msg.callback ) runWrapperCallback(msg.callback, false); } break; case 'callback': if ( !msg.callbackId ) return; if( msg.data ) { //find our door let door = false for (var a = 0; a < DormantMaster.appDoors.length; a++ ) { if (DormantMaster.appDoors[a].doorId == msg.doorId ) { door = DormantMaster.appDoors[a]; } } // If we have a viable door, use it if( door ) { for (var a = 0; a < msg.data.length; a++) { msg.data[a].Dormant = door; } runWrapperCallback( msg.callbackId, msg.data ); } } else { runWrapperCallback( msg.callbackId, null ); } break; case 'addevent': DormantMaster.addEvent(msg); break; case 'pollevent': DormantMaster.pollEvent(msg); break; case 'delappevents': DormantMaster.delApplicationEvents(msg.applicationName); break; case 'emit': DormantMaster.handleEvent( msg ); break; // Make proxy object case 'addAppDoor': // Make sure we have a unique name! var num = 0; var nam = msg.title; var namnum = nam; var found; do { found = false; for (var a = 0; a < DormantMaster.appDoors.length; a++) { if (DormantMaster.appDoors[a].title.toLowerCase() == namnum.toLowerCase()) { namnum = nam + '.' + (++num); found = true; break; } } } while (found); var doorObject = { title : namnum, doorId : msg.doorId, applicationId : msg.applicationId, listeners : {}, getDoor: function () { var icon = 'apps/' + msg.title + '/icon.png'; if (app && app.config.IconDoor) icon = app.config.IconDoor; return { MetaType : 'Meta', Title : namnum + ':', /* remove this from all references*/ Filename : namnum + ':', IconFile : icon, Position : 'left', Module : 'files', Command : 'dormant', Filesize : 4096, Flags : '', Type : 'Dormant', Path : namnum + ':', Dormant : this, }; }, addWindow: function( win ) { this.windows.push( win ); }, getDirectory: function( t, callback ) { var id = addWrapperCallback(callback); // Callback var ret = { applicationId : msg.applicationId, doorId : msg.doorId, callbackId : id, command : 'dormantmaster', method : 'getdirectory', path : t }; if (msg.viewId) ret.viewId = msg.viewId; app.contentWindow.postMessage( JSON.stringify(ret), '*' ); }, getFileInformation: function( t, callback ) { var id = addWrapperCallback(callback); // Callback var ret = { applicationId : msg.applicationId, doorId : msg.doorId, callbackId : id, command : 'dormantmaster', method : 'getFileInformation', path : t }; if (msg.viewId) ret.viewId = msg.viewId; app.contentWindow.postMessage( JSON.stringify(ret), '*' ); }, setFileInformation: function( perm, callback ) { var id = addWrapperCallback(callback); // Callback var ret = { applicationId : msg.applicationId, doorId : msg.doorId, callbackId : id, command : 'dormantmaster', method : 'setFileInformation', perm : perm }; if (msg.viewId) ret.viewId = msg.viewId; app.contentWindow.postMessage( JSON.stringify(ret), '*' ); }, // Execute a dormant command! execute: function( fnObj, args, callback ) { var path = fnObj.Path; var command = fnObj.Title || fnObj.Filename; var id = addWrapperCallback( data => { if ( callback ) callback( null, data ); // }); // Callback var ret = { applicationId : msg.applicationId, doorId : msg.doorId, callbackId : id, command : 'dormantmaster', method : 'execute', dormantPath : path, dormantCommand : command, dormantArgs : args }; if (msg.viewId) ret.viewId = msg.viewId; app.contentWindow.postMessage( JSON.stringify(ret), '*' ); }, read: function( path, mode, callback ) { var id = addWrapperCallback( callback ); // Callback var ret = { applicationId : msg.applicationId, doorId : msg.doorId, callbackId : id, command : 'dormantmaster', method : 'read', path : path, mode : mode }; if (msg.viewId) ret.viewId = msg.viewId; app.contentWindow.postMessage( JSON.stringify(ret), '*' ); }, write: function( path, data, callback ) { var id = addWrapperCallback( callback ); // Callback var ret = { applicationId : msg.applicationId, doorId : msg.doorId, callbackId : id, command : 'dormantmaster', method : 'write', path : path, data : data }; if (msg.viewId) ret.viewId = msg.viewId; app.contentWindow.postMessage( JSON.stringify(ret), '*' ); }, listen : function( eventPath, listener ) { const self = this; const listenId = friendUP.tool.uid(); if ( null == self.listeners[ eventPath ] ) { self.listeners[ eventPath ] = []; } self.listeners[ eventPath ].push( listenId ); self.listeners[ listenId ] = listener; return listenId; }, dosAction: function( func, args, callback ) { var id = addWrapperCallback( callback ); // Callback var ret = { applicationId : msg.applicationId, doorId : msg.doorId, callbackId : id, command : 'dormantmaster', method : 'dosAction', func : func, args : args }; if (msg.viewId) ret.viewId = msg.viewId; app.contentWindow.postMessage( JSON.stringify(ret), '*' ); }, windows: [] }; if (msg.viewId) doorObject.viewId = msg.viewId; // Add the appdoor DormantMaster.addAppDoor(doorObject); // Callback var ret = { applicationId: msg.applicationId, doorId: msg.doorId, data: 'success', command: 'dormantmaster', method: 'updatetitle', title: msg.title, realtitle: namnum }; if( msg.viewId ) ret.viewId = msg.viewId; app.contentWindow.postMessage( JSON.stringify(ret), '*' ); break; // Silent deletion case 'deleteAppDoor': DormantMaster.delAppDoor(msg.title); break; // Get a list of doors case 'getDoors': var doors = DormantMaster.getDoors(); // Callback var ret = { applicationId: msg.applicationId, callbackId: msg.callbackId, command: 'dormantmaster', method: 'callback', data: doors }; if (msg.viewId) ret.viewId = msg.viewId; app.contentWindow.postMessage( JSON.stringify(jsonSafeObject(ret)), '*' ); break; case 'getDirectory': //find our door var door = false; for (var a = 0; a < DormantMaster.appDoors.length; a++) { if (DormantMaster.appDoors[a].title == msg.path.split(':')[0]) { door = DormantMaster.appDoors[a]; } } if (door) { door.getDirectory(msg.path, function (data) { // Make sure the "files" have doorid // TODO: Is this safe? Other way of doing it? for (var a in data) data[a].doorId = door.doorId; // Callback var ret = { applicationId: msg.applicationId, callbackId: msg.callbackId, command: 'dormantmaster', method: 'callback', data: data }; if (msg.viewId) ret.viewId = msg.viewId; app.contentWindow.postMessage( JSON.stringify(jsonSafeObject(ret)), '*' ); }); } break; case 'createDrive': var id = Friend.Doors.Dormant.createDrive( msg.options, function( response, data, extra ) { // Callback var ret = { applicationId: msg.applicationId, callbackId: msg.callbackId, command: 'dormantmaster', method: 'callback', response: response, data: data, extra: extra }; if ( msg.viewId ) ret.viewId = msg.viewId; app.contentWindow.postMessage( JSON.stringify( jsonSafeObject( ret ) ), '*' ); }, msg.extra ); break; case 'destroyDrive': Friend.Doors.Dormant.destroyDrive( msg.driveId, msg.options, function( response, data, extra ) { // Callback var ret = { applicationId: msg.applicationId, callbackId: msg.callbackId, command: 'dormantmaster', method: 'callback', response: response, data: response, extra: extra }; if ( msg.viewId ) ret.viewId = msg.viewId; app.contentWindow.postMessage( JSON.stringify( jsonSafeObject( ret ) ), '*' ); }, msg.extra ); break; } break; // Notify ---------------------------------------------------------- // Ok, the iframe was loaded!? Check data case 'notify': if ( app.windows && app.windows[msg.viewId] ) { app.windows[msg.viewId].sendMessage( { command: 'notify' } ); // Execute the loaded function to carry out queued events.. if( app.windows[msg.viewId].iframe ) app.windows[msg.viewId].iframe.loaded = true; app.windows[msg.viewId].executeSendQueue(); // Try to execute register callback function if( msg.registerCallback ) runWrapperCallback( msg.registerCallback, msg.data ); } // We got notify without a window (shell application or main app win no window) else { // Try to execute register callback function if( msg.registerCallback ) runWrapperCallback(msg.registerCallback, msg.data ); } break; // Screens --------------------------------------------------------- case 'screen': var screenId = msg.screenId; // Existing screen if( msg.method && app.screens && app.screens[ msg.screenId ] ) { var scr = app.screens[msg.screenId]; switch (msg.method) { // Pass a message to actual window case 'sendMessage': case 'sendmessage': // inconsistent camel case if (scr) { scr.sendMessage(msg.data); } break; case 'screentofront': if (scr) { scr.screenToFront(); } break; case 'setContent': if( scr ) { // Create a new callback dispatch here.. var cb = false; if( msg.callback ) { cb = makeAppCallbackFunction( app, msg, event.source ); } // Do the setting! var domain = GetDomainFromConf(app.config, msg.applicationId); scr.setContentIframed(msg.data, domain, msg, cb); // Remove callback here - it will be handled by setcontentiframed // as it is asyncronous msg.callback = false; } break; case 'setRichContentUrl': if( scr ) scr.setRichContentUrl(msg.url, msg.base, msg.applicationId, msg.filePath); break; case 'setMenuItems': if (scr) { scr.setMenuItems(msg.data, msg.applicationId, msg.screenId); } CheckScreenTitle(); break; // Receive a close request from below case 'close': if (scr) { scr.close(1); var out = []; for (var c in app.screens) if (c != msg.screenId) out[c] = app.screens[c]; app.screen = out; } break; case 'activate': WorkspaceMenu.close(); break; } } // don't trigger on method else if (!msg.method) { // Try to open a window msg.data.screenId = msg.screenId; msg.data.applicationId = msg.applicationId; msg.data.authId = msg.authId; msg.data.applicationName = app.applicationName; msg.data.applicationDisplayName = app.applicationDisplayName; var v = new Screen(msg.data); if (v.ready) { if (!app.screens) app.screens = []; app.screens[screenId] = v; // Assign conf if it exists on app object if (app.conf) v.conf = app.conf; // This is the external id v.externScreenId = screenId; var nmsg = { applicationId: msg.applicationId, viewId: msg.id ? msg.id : false, type: 'callback', command: 'screenresponse', data: 'ok' }; app.contentWindow.postMessage( JSON.stringify(nmsg), '*' ); } // Call back to say the window was not correctly opened else { var nmsg = { applicationId: msg.applicationId, viewId: msg.id ? msg.id : false, type: 'callback', command: 'screenresponse', data: 'fail' }; app.contentWindow.postMessage( JSON.stringify(nmsg), '*' ); } // Setup window keyboard handler v.handleKeys = function (k) { // If it was only done! } } break; // View ------------------------------------------------------------ case 'view': let viewId = msg.viewId; if( msg.method && app.windows && app.windows[ msg.viewId ] ) { let win = app.windows[ msg.viewId ]; let twin = app.windows[ msg.targetViewId ? msg.targetViewId : msg.viewId ]; switch( msg.method ) { case 'cancelclose': if( win ) { console.log( 'What to do here?', win ); } break; case 'opencamera': if( win ) { let cbk = null; if( msg.callback ) { let cid = msg.callback; cbk = function( data ) { let nmsg = { command: 'callback', callback: cid, data: data }; if( msg.screenId ) nmsg.screenId = msg.screenId; event.source.postMessage( nmsg, '*' ); } msg.callback = null; } win.openCamera( msg.flags, cbk ); } break; case 'showbackbutton': if( win ) { let cbk = null; if( msg.callback ) { let cid = msg.callback; cbk = function( e ) { if( win.viewId == msg.targetViewId ) { win.sendMessage( { command: 'callback', callback: cid, viewId: msg.targetViewId } ); } else { app.sendMessage( { command: 'callback', callback: cid } ); } } msg.callback = null; } win.showBackButton( msg.visible, cbk ); } break; // Set a window state! case 'windowstate': if( win && typeof( win.states[ msg.state ] ) != 'undefined' ) { win.states[ msg.state ] = msg.value; } break; case 'doneloadingbody': if( win && win.iframe ) { win.iframe.classList.remove( 'Loading' ); } // Clean blocker RemoveFromExecutionQueue( app.applicationName ); break; // Pass a message to actual window case 'sendMessage': case 'sendmessage': // inconsistent camel case if( win ) { win.sendMessage( msg.data ); } break; // Receive a close request from below case 'close': if( win ) { let out = []; for( let c in app.windows ) { if( c != msg.viewId ) { if( app.windows[ c ].parentViewId == msg.viewId ) { app.windows[ c ].close( 1 ); continue; } out[ c ] = app.windows[ c ]; } } app.windows = out; win.close(1); } else { console.log( 'can not find window!' ); } break; case 'getWindowElement': if( win ) { let cb = false; msg.data = false; msg.resp = 'fail'; let elev = msg.destination ? app.windows[ msg.destination ] : app; if( elev && elev.iframe ) elev = elev.iframe; if( elev ) { // TODO: Support this in security domains if( win.applicationId == msg.applicationId ) { let i = win.iframe; if( !i ) i = win.content ? win.content.getElementsByTagName( 'iframe' )[0] : false; if( i ) { if( i.contentWindow ) { try { let identifier = 'view_' + win._window.parentNode.id; if( !elev.contentWindow.Application.windowElements ) { elev.contentWindow.Application.windowElements = {}; } elev.contentWindow.Application.windowElements[ identifier ] = i.contentWindow.document; msg.data = identifier; msg.resp = 'ok'; msg.targetViewId = msg.destination; msg.viewId = msg.destination; } catch( e ) { // probably sandboxed } } } } } if( msg.callback ) { cb = makeAppCallbackFunction( app, msg, event.source ); } } break; case 'setFlag': if( win ) { win.setFlag( msg.data.flag, msg.data.value ); } break; case 'setFlags': if( win ) { win.setFlags( msg.data ); } break; case 'setContent': if( win ) { // Create a new callback dispatch here.. let cb = false; if( msg.callback ) cb = makeAppCallbackFunction( app, msg, event.source ); // Do the setting! let domain = GetDomainFromConf( app.config, msg.applicationId ); win.setContentIframed( msg.data, domain, msg, cb ); // Remove callback here - it will be handled by setcontentiframed // as it is asyncronous msg.callback = false; } break; case 'setContentById': if( win ) { // Remember callback let cb = false; if( msg.callback ) cb = makeAppCallbackFunction( app, msg, event.source ); win.setContentById( msg.data, msg, cb ); msg.callback = false; } break; case 'setAttributeById': if( win ) win.setAttributeById( msg ); break; case 'getAttributeById': if( win ) { // Do it, and call back win.getAttributeById( msg, function (c) { // TODO: Implement it (callback to send attribute value back) } ); } break; case 'setSandboxedUrl': if( win ) win.setSandboxedUrl( msg ); break; case 'setRichContent': if( win ) win.setRichContent( msg.data ); break; case 'setRichContentUrl': if( win ) { win.setRichContentUrl( msg.url, msg.base, msg.applicationId, msg.filePath ); } break; case 'getContentById': if( win ) { let c = win.getContentById( msg.identifier, msg.flag ); if( c ) { app.contentWindow.postMessage( JSON.stringify( { command: 'view', method: 'getSubContent', data: c.innerHTML } ), '*'); } } break; // Adds an event by class and runs callback function case 'addEventByClass': if( win ) { win.addEventByClass( msg.className, msg.event, function(e) { app.contentWindow.postMessage( JSON.stringify( { command: 'callback', callback: msg.callback, data: false } ), '*' ); } ); } break; case 'focusOnElement': if( win ) { win.focusOnElement( msg.identifier, msg.flag ); } break; // Set quick menu on window case 'setQuickMenu': msg.data.uniqueName = MD5( msg.applicationId + '-' + msg.viewId ); if( win ) { win._window.parentNode.quickMenu = msg.data; } CheckScreenTitle(); break; // Set menu items on window case 'setMenuItems': if( win ) win.setMenuItems( msg.data, msg.applicationId, msg.viewId ); CheckScreenTitle(); break; case 'popout': if( win ) { PopoutWindow( win._window.parentNode ); } break; case 'toFront': if( win ) { _WindowToFront( win._window.parentNode ); } break; case 'focus': if( win ) { win.focus(); } break; case 'activate': // Silent apps don't activate until clicked! if( app.opensilent ) { return; } // Don't touch moving windows! if( window.isMobile ) { if( win ) { if( !app.startupsequence ) { win.activate(); } } WorkspaceMenu.close(); } else if( !( window.currentMovable && currentMovable.getAttribute( 'moving' ) == 'moving' ) ) { if( !app.startupsequence ) { win.activate(); } } break; } } // don't trigger on method else if( !msg.method ) { // Try to open a window msg.data.viewId = msg.viewId; msg.data.applicationId = msg.applicationId; msg.data.authId = msg.authId; msg.data.applicationName = app.applicationName; msg.data.applicationDisplayName = app.applicationDisplayName; // Add preferred workspace if( app.workspace ) msg.data.workspace = app.workspace; if( app.opensilent ) msg.data.openSilent = app.opensilent; // Redirect to the real screen if( msg.data.screen && app && app.screens[ msg.data.screen ] ) { msg.data.screen = app.screens[ msg.data.screen ]; } else { msg.data.screen = null; } let postTarget = app; // Startup sequence apps need to be deactivated if( app.startupsequence ) { msg.data.minimized = true; // Fake hide when we have a window if( Workspace.applications.length && Workspace.applications[0].windows && window.ScreenOverlay ) { ScreenOverlay.invisible(); } } // Does the view msg override the context? if( msg.context ) { window.currentContext = false; } let v = new View( msg.data ); if( msg.context ) { v.recentLocation = 'viewId:' + msg.context; } else if( msg.applicationId ) { v.recentLocation = window.Workspace && Workspace.dashboard ? 'dashboard' : ''; } let win = msg.parentViewId && app.windows ? app.windows[ msg.parentViewId ] : false; if( win ) { v.parentViewId = msg.parentViewId; postTarget = win.content.getElementsByTagName( 'iframe' )[0]; } if( v.ready ) { if( !app.windows ) app.windows = []; app.windows[ viewId ] = v; // Assign conf if it exists on app object if( app.conf ) v.conf = app.conf; // This is the external id v.externViewId = viewId; let nmsg = { applicationId: msg.applicationId, viewId: msg.id ? msg.id : viewId, type: 'callback', command: 'viewresponse', data: 'ok' }; postTarget.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } // Call back to say the window was not correctly opened else { let nmsg = { applicationId: msg.applicationId, viewId: msg.id ? msg.id : viewId, type: 'callback', command: 'viewresponse', data: 'fail' }; postTarget.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } // Setup window keyboard handler v.handleKeys = function (k) { // } delete msg.data; delete msg; msg = null; } break; // Native view ( mobile app / ios ) -------------------------------------------- case 'native-view': if ( !window.friendApp ) { console.log( 'apiWrapper - native-view event, no friendApp', msg ); return; } const nve = JSON.stringify( msg.data ); window.friendApp.receiveLive( msg.viewId, nve ); break; // Widget --------------------------------------------------------- case 'widget': var widgetId = msg.widgetId; if ( msg.method && app.widgets && app.widgets[ msg.widgetId ] ) { var wid = app.widgets[ msg.widgetId ]; switch ( msg.method ) { // Pass a message to actual window case 'sendMessage': case 'sendmessage': // inconsistent camel case if ( wid ) { wid.sendMessage( msg.data ); } break; case 'setFlag': if( wid ) wid.setFlag( msg.data.flag, msg.data.value ); break; case 'setContent': if ( wid ) { // Create a new callback dispatch here.. var cb = false; if ( msg.callback ) cb = makeAppCallbackFunction( app, msg, event.source ); // Do the setting! var domain = GetDomainFromConf( app.config, msg.applicationId ); wid.setContentIframed( msg.data, domain, msg, cb ); // Remove callback here - it will be handled by setcontent // as it is asyncronous msg.callback = false; } break; case 'raise': if ( wid ) wid.raise(); break; case 'lower': if ( wid ) wid.lower(); break; case 'show': if ( wid ) wid.show(); break; case 'hide': if ( wid ) wid.hide(); break; case 'autosize': if ( wid ) wid.autosize(); break; case 'close': if( wid ) { wid.close(); // Remove widget from list var w = []; for( let a in app.widgets ) if( app.widgets[ a ] != wid ) w.push( app.widgets[a] ); app.widgets = w; } else { } break; } } // don't trigger on method else if ( !msg.method ) { var v = new Widget( msg.data ); // We might need these v.applicationId = msg.applicationId; v.authId = msg.authId; v.applicationDisplayName = msg.applicationDisplayName; v.applicationName = msg.applicationName; if (!app.widgets) app.widgets = []; app.widgets[widgetId] = v; var nmsg = { applicationId: msg.applicationId, widgetId: msg.id ? msg.id : false, type: 'callback', command: 'widgetresponse', data: 'ok' }; app.contentWindow.postMessage( JSON.stringify(nmsg), '*' ); } // Call back to say the window was not correctly opened else { var nmsg = { applicationId: msg.applicationId, widgetId: msg.id ? msg.id : false, type: 'callback', command: 'widgetresponse', data: 'fail' }; app.contentWindow.postMessage( JSON.stringify(nmsg), '*' ); } break; // File ------------------------------------------------------------ // TODO : Permissions - only check local filesystems? // Are there admin only filesystems? case 'file': // Faster way to get javascripts. if( msg.command && msg.command == 'getapidefaultscripts' ) { // Load from cache if( Workspace.apidefaultscripts ) { event.source.postMessage( { type: 'callback', callback: msg.callback, data: Workspace.apidefaultscripts }, '*' ); } // Build else { var n = new XMLHttpRequest(); n.open( 'POST', msg.data ); n.onreadystatechange = function() { if( this.readyState == 4 && this.status == 200 ) { Workspace.apidefaultscripts = this.responseText; event.source.postMessage( { type: 'callback', callback: msg.callback, data: Workspace.apidefaultscripts }, '*' ); } } n.send(); } return true; } // Some paths come as filenames obviously.. if( !msg.data.path && msg.data.filename && msg.data.filename.indexOf( ':' ) > 0 ) msg.data.path = msg.data.filename; // Perhaps do error? if( msg.data.path && msg.data.path.toLowerCase && msg.data.path.toLowerCase().substr( 0, 8 ) != 'progdir:' && msg.data.path.indexOf( ':' ) > 0 ) { // TODO: Clean up "Door Local" which is deprecated if( !checkAppPermission( app.authId, 'Door Local' ) && !checkAppPermission( app.authId, 'Door All' ) ) { console.log( 'Permission denied to local filesystems!' ); return false; } } if( typeof( msg.data.path ) == 'undefined' ) { console.log( 'Empty path..' ); return false; } var fileId = msg.fileId; // Make real file object var f = new File( msg.data.path ); f.application = app; // TODO: This should be in the application from the start if( !f.application.filePath ) f.application.filePath = msg.filePath; // Add variables if( msg.vars ) { for( let a in msg.vars ) { f.addVar( a, msg.vars[a] ); } } // Respond with file contents (uses raw data..) if( msg.method == 'load' ) { f.onLoad = function( data ) { // Fallback if( !data && this.rawData ) data = this.rawData; // File loads should remain in their view context var cw = GetContentWindowByAppMessage( app, msg ); if( app && cw ) { var nmsg = { command: 'fileload', fileId: fileId }; // Pass window id down if( msg.viewId ) { nmsg.viewId = msg.viewId; nmsg.type = 'callback'; } if( msg.screenId ) { nmsg.screenId = msg.screenId; nmsg.type = 'callback'; } if( Workspace.authId ) nmsg.authId = Workspace.authId; // Binary data is sent as string.. if( typeof( data ) == 'object' ) { //var v = new Uint8Array( data ); //nmsg.data = Array.prototype.join.call( v, ',' ); nmsg.dataFormat = 'string'; nmsg.data = ConvertArrayBufferToString( data, 'base64' ); } else { nmsg.data = data; } cw.postMessage( JSON.stringify( nmsg ), '*' ); } } f.load(); } else if( msg.method == 'call' ) { f.onCall = function( data ) { // Fallback if( !data && this.rawdata ) data = this.rawdata; // File loads should remain in their view context var cw = GetContentWindowByAppMessage( app, msg ); if( app && cw ) { var nmsg = { command: 'fileload', fileId: fileId, data: data }; // Pass window id down if( msg.viewId ) { nmsg.viewId = msg.viewId; nmsg.type = 'callback'; } if( msg.screenId ) { nmsg.screenId = msg.screenId; nmsg.type = 'callback'; } if( Workspace.authId ) nmsg.authId = Workspace.authId; cw.postMessage( JSON.stringify( nmsg ), '*' ); } } f.call( msg.vars.query, msg.vars ); } else if( msg.method == 'post' ) { f.onPost = function( result ) { // File posts should remain in their view context var cw = GetContentWindowByAppMessage( app, msg ); if( app && cw ) { var nmsg = { command: 'filepost', fileId: fileId, result: result }; // Pass window id down if( msg.viewId ) { nmsg.viewId = msg.viewId; nmsg.type = 'callback'; } if( msg.screenId ) { nmsg.screenId = msg.screenId; nmsg.type = 'callback'; } cw.postMessage( JSON.stringify( nmsg ), '*' ); } } // Assume base64 encoded data string try { f.post( Base64.decode( msg.data.data ), msg.data.filename ); } // No? Try without.. catch( e ) { f.post( msg.data.data, msg.data.filename ); } } else if( msg.method == 'save' ) { // Respond with save data notification f.onSave = function( resCode, data ) { // File saves should remain in their view context let cw = GetContentWindowByAppMessage( app, msg ); if( app && cw ) { let nmsg = { command: 'filesave', fileId: fileId }; // Pass window id down if( msg.viewId ) { nmsg.viewId = msg.viewId; nmsg.type = 'callback'; } if( msg.screenId ) { nmsg.screenId = msg.screenId; nmsg.type = 'callback'; } nmsg.responseCode = resCode ? resCode : 'fail'; nmsg.responseData = data ? data : ''; cw.postMessage( JSON.stringify( nmsg ), '*' ); } } // We have a binary in string format! // Convert it back into binary! var mode = ''; if( msg.dataFormat == 'string' ) { let data = ConvertStringToArrayBuffer( msg.data.data, 'base64' ); mode = 'wb'; msg.data.data = data; } f.save( msg.data.data, msg.data.path, mode ); } break; // Shell API ------------------------------------------------------- case 'shell': if( msg.command ) { let shell = false; if( msg.shellSession ) { shell = FriendDOS.getSession( msg.shellSession ); } if( !shell ) { return false; } switch( msg.command ) { case 'execute': shell.execute( msg.commandLine, function( rmsg, returnMessage ) { // TODO: Finish the test if rmsg has become safe! if( app && app.contentWindow ) { var nmsg = { command: 'shell', shellId: msg.shellId, shellSession: shell.uniqueId, shellNumber: shell.number, applicationId: msg.applicationId, authId: msg.authId, pipe: shell.applicationPipe, callbackId: msg.callbackId, data: jsonSafeObject( rmsg ) // Make it safe! }; if( returnMessage ) nmsg.returnMessage = returnMessage; // Pass window id down if( msg.viewId ) { nmsg.viewId = msg.viewId; nmsg.type = 'callback'; } app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } else { console.log( 'Nothing to do callback on!' ); } } ); break; case 'evaluate': shell.evaluate( msg.input, function( rmsg, returnMessage ) { // TODO: Finish the test if rmsg has become safe! if( app && app.contentWindow ) { var nmsg = { command: 'shell', shellId: msg.shellId, shellSession: shell.uniqueId, shellNumber: shell.number, applicationId: msg.applicationId, authId: msg.authId, pipe: shell.applicationPipe, callbackId: msg.callbackId, data: jsonSafeObject( rmsg ) // Make it safe! }; if( returnMessage ) nmsg.returnMessage = returnMessage; // Pass window id down if( msg.viewId ) { nmsg.viewId = msg.viewId; nmsg.type = 'callback'; } app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } else { console.log( 'Nothing to do callback on!' ); } }, msg.clientKey, msg.restrictedPath ); break; case 'close': FriendDOS.delSession( msg.shellSession ); break; } } // Handle methods and callbacks else if( msg.method && msg.shellId ) { var shell = FriendDOS.getSession( msg.shellId ); if( shell ) { switch( msg.method ) { case 'callback': var cfunc = runWrapperCallback( msg.callbackId, msg.data ); break; } return; } } else { var shell = FriendDOS.addSession( app, false ); var sh = FriendDOS.getSession( shell ); sh.applicationPipe = msg.pipe; // Pipe to application if( app && app.contentWindow ) { var nmsg = { command: 'shell', shellId: msg.shellId, shellNumber: sh.number, shellSession: shell, applicationId: msg.applicationId, authId: msg.authId }; // Pass window id down if( msg.viewId ) { nmsg.viewId = msg.viewId; nmsg.type = 'callback'; } app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } } break; // Module ---------------------------------------------------------- case 'module': var fileId = msg.fileId; // Perhaps do error? if( msg.module.toLowerCase() != 'system' && msg.module.toLowerCase() != 'files' ) { // TODO: Reenable once we have proper working functionality!! /*if( !checkAppPermission( app.authId, 'Module ' + msg.module.charAt( 0 ).toUpperCase()+msg.module.substr( 1 ).toLowerCase() ) ) { console.log( 'Permission denied!' ); return false; }*/ } // Make real module object var f = new Module( msg.module ); f.application = app; if( msg.forceHTTP ) { f.forceHTTP = msg.forceHTTP; } // Add variables if( msg.vars ) { for( let a in msg.vars ) f.addVar( a, msg.vars[a] ); } // Respond with file contents (uses raw data..) f.onExecuted = function( cod, dat ) { if( app ) { var nmsg = { command: 'fileload', fileId: fileId, data: dat, returnCode: cod }; // Module calls should remain in their view context var cw = GetContentWindowByAppMessage( app, msg ); // Pass window id down if( msg.viewId ) { nmsg.viewId = msg.viewId; nmsg.type = 'callback'; } else if( msg.screenId ) { nmsg.screenId = msg.screenId; nmsg.type = 'callback'; } else if( msg.widgetId ) { nmsg.widgetId = msg.widgetId; nmsg.type = 'callback'; } if( cw ) cw.postMessage( JSON.stringify( nmsg ), '*' ); } } f.execute( msg.method, msg.args ); break; // Doors ----------------------------------------------------------- case 'door': switch( msg.method ) { case 'init': var d = new Door( msg.path ); // Trigger callback var info = d.get( msg.path ); msg.handler = info.handler; app.contentWindow.postMessage( JSON.stringify( msg ), '*' ); // Abort further return; // Get icons from a real Door object case 'geticons': var info = ( new Door() ).get( msg.path ); if( info ) { // Is this info qualified? if( info.path || info.deviceName ) { info.setPath( msg.path ); info.getIcons( false, function( icons ) { if( icons ) msg.data = JSON.stringify( jsonSafeObject( icons ) ); else msg.data = false; if( app && app.contentWindow ) app.contentWindow.postMessage( JSON.stringify( msg ), '*' ); } ); return; } } // Give negative response - works as it should... msg.data = false; if( app && app.contentWindow ) app.contentWindow.postMessage( JSON.stringify( msg ), '*' ); return; } break; // PouchDB --------------------------------------------------------- case 'pouchdb' : console.log( 'pouchdb', msg ); var pdbEvent = msg.data; if ( !Workspace.pouchManager ) { var err = { callback : pdbEvent.data.cid, data : { success : false, errorMessage : 'No pouch manager', }, }; if ( app.contentWindow && app.contentWindow.postMessage ) app.contentWindow.postMessage( err, '*' ); } else Workspace.pouchManager.handle( pdbEvent, app ); break; // ApplicationStorage ---------------------------------------------- // TODO : permissions - should apps be able to store data? how much? // TODO : api for checking how much is stored, total and per app case 'applicationstorage': ApplicationStorage.receiveMsg( msg, app ); break; case 'encryption': switch( msg.command ) { case 'generatekeys': if( msg.algo ) { switch( msg.algo ) { case 'rsa1024': if( typeof msg.args.encoded != 'undefined' ) { Workspace.encryption.encoded = msg.args.encoded; } var passphrase = ( typeof msg.args != 'object' ? msg.args : msg.args.password ); var keys = Workspace.encryption.generateKeys( ( typeof msg.args.username != 'undefined' ? msg.args.username : '' ), passphrase ); var nmsg = {}; for( let b in msg ) { nmsg[b] = msg[b]; } if( keys ) { nmsg.type = 'callback'; nmsg.resp = 'ok'; nmsg.data = keys; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } else { nmsg.type = 'callback'; nmsg.resp = 'fail'; nmsg.data = false; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } break; case 'sha256': var passphrase = ( typeof msg.args != 'object' ? msg.args : msg.args.password ); var key = Workspace.encryption.sha256( passphrase ); var nmsg = {}; for( let b in msg ) { nmsg[b] = msg[b]; } if( key ) { nmsg.type = 'callback'; nmsg.resp = 'ok'; nmsg.data = key; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } else { nmsg.type = 'callback'; nmsg.resp = 'fail'; nmsg.data = false; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } break; case 'md5': var passphrase = ( typeof msg.args != 'object' ? msg.args : msg.args.password ); var key = Workspace.encryption.md5( passphrase ); var nmsg = {}; for( let b in msg ) { nmsg[b] = msg[b]; } if( key ) { nmsg.type = 'callback'; nmsg.resp = 'ok'; nmsg.data = key; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } else { nmsg.type = 'callback'; nmsg.resp = 'fail'; nmsg.data = false; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } break; } } break; case 'encrypt': if( msg.args ) { var key = ( typeof msg.args != 'object' ? msg.args : msg.args.key ); var nmsg = {}; for( let b in msg ) { nmsg[b] = msg[b]; } var encrypted = ( key ? Workspace.encryption.encrypt( key ) : false ); if( encrypted && Workspace.encryption.keys.client.publickey ) { nmsg.type = 'callback'; nmsg.resp = 'ok'; nmsg.data = { encrypted: encrypted, publickey: Workspace.encryption.keys.client.publickey }; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } else { nmsg.type = 'callback'; nmsg.resp = 'fail'; nmsg.data = false; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } } break; case 'decrypt': if( msg.args ) { var key = ( typeof msg.args != 'object' ? msg.args : msg.args.key ); var nmsg = {}; for( let b in msg ) { nmsg[b] = msg[b]; } var decrypted = ( key ? Workspace.encryption.decrypt( key ) : false ); if( decrypted ) { nmsg.type = 'callback'; nmsg.resp = 'ok'; nmsg.data = { decrypted: decrypted }; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } else { nmsg.type = 'callback'; nmsg.resp = 'fail'; nmsg.data = false; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } } break; case 'publickey': if( msg.args && typeof msg.args.encoded != 'undefined' ) { Workspace.encryption.encoded = msg.args.encoded; } var keys = Workspace.encryption.getKeys(); var nmsg = {}; for( let b in msg ) { nmsg[b] = msg[b]; } if( keys && keys.publickey ) { nmsg.type = 'callback'; nmsg.resp = 'ok'; nmsg.data = { publickey: keys.publickey }; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } else { nmsg.type = 'callback'; nmsg.resp = 'fail'; nmsg.data = false; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } break; } break; // Authenticate ---------------------------------------------- case 'authenticate': Authenticate.receiveMsg( msg, app ); break; // Calendar // TODO - Permissions : // 1 can app read events? // 2 can app set events? case 'calendar' : if( msg.method == 'calendarrefresh' ) { Workspace.refreshExtraWidgetContents(); } else { var action = msg.data; if ( 'add' !== action.type ) { done( false, 'unknown type: ' + action.type , action.data.cid ); return; } var data = action.data; var title = app.applicationName + ' wants to add a calendar event'; Confirm( title, data.message, confirmBack ); function confirmBack( accept ) { if ( accept ) addCalendarEvent( data.event ); else { done( false, 'event denied', data.cid ); } } function addCalendarEvent( event ) { var mod = new Module( 'system' ); mod.onExecuted = eventAdded; mod.execute( 'addcalendarevent', { event : event }); function eventAdded( ok, res ) { done( 'ok' === ok, res, data.cid ); } } function done( ok, res, cid ) { if ( !cid ) return; var res = { ok : ok, res : res, callback : cid, }; app.contentWindow.postMessage( res, '*' ); } } break; // post back to whatever tab opened this tab - specialcase for treeroot / friend // to have live chat using hello case 'postout': console.log( 'postout', msg ); if( !window.opener || !window.opener.postMessage ) { console.log( 'window.opener.postMessage not available - dropping:', msg ); return; } window.opener.postMessage( msg.data, '*' ); break; case 'fconn': if( !Workspace.conn ) { Workspace.initWebSocket( function() { apiWrapper( event, force ); } ); console.log( 'Workspace.conn - websocket not enabled, reinitializing' ); return; } if( 'register' === msg.method ) { // hacky check to unregister if it already exists // should be done when an application closes. // - actually, this stuff should be a permission // ## removed by thomas to allow several SAS per application as many apps can have several windows/instances. // ## initial tests showed no negative behaviour. cleanup must be done in a smarter way. //var regId = Workspace.conn.registeredApps[ app.authId ]; //if ( regId ) // Workspace.conn.off( app.authId, regId ); var id = Workspace.conn.on( app.authId, fconnMsg ); Workspace.conn.registeredApps[ app.authId ] = id; requestBack( true ); return; function fconnMsg( msg ) { var wrap = { command: 'fconn', data: msg }; if ( app.contentWindow && app.contentWindow.postMessage ) { //console.log( 'fconnMsg, posting msg to app', wrap ); app.contentWindow.postMessage( wrap, '*' ); } } } if( 'remove' === msg.method ) { var regId = Workspace.conn.registeredApps[ app.authId ]; Workspace.conn.off( app.authId, regId ); return; } var wrap = { path : msg.data.path, authId : app.authId, data : msg.data.data, }; if( 'request' === msg.method ) Workspace.conn.request( wrap, requestBack ); else Workspace.conn.send( wrap ); function requestBack( res ) { msg.data = res; msg.type = 'callback'; msg.callback = msg.callbackId; if ( app.contentWindow && app.contentWindow.postMessage ) app.contentWindow.postMessage( msg, '*' ); } break; // Announcement calls case 'announcement': var app = false; if( msg.applicationId ) app = findApplication( msg.applicationId ); let cbak = null; if( msg.callback ) { cbak = msg.callback; msg.callback = null; } switch( msg.command ) { case 'announcement': let o = { type: msg.announcementType, users: msg.users, workgroups: msg.workgroups, payload: msg.payload }; let m = new Module( 'system' ); m.onExecuted = function( e, d ) { if( cbak ) { if( e == 'ok' ) { let ms = { type: 'callback', resp: 'ok', callback: cbak }; for( let z in msg ) { if( z != 'payload' && z != 'users' && z != 'workgroups' && z != 'theme' && z != 'userLevel' && z != 'username' && z != 'workgroups' && z != 'users' && z != 'type' && z != 'callback' ) { ms[ z ] = msg[ z ]; } } app.contentWindow.postMessage( ms, '*' ); } else { let ms = { type: 'callback', resp: 'fail', callback: cbak }; for( let z in msg ) { if( z != 'payload' && z != 'users' && z != 'workgroups' && z != 'theme' && z != 'userLevel' && z != 'username' && z != 'workgroups' && z != 'users' && z != 'type' && z != 'callback' ) { ms[ z ] = msg[ z ]; } } console.log( 'Failed because: ', e, d ); app.contentWindow.postMessage( ms, '*' ); } } } m.execute( 'announcement', o ); break; } break; // System calls! // TODO: Permissions, not all should be able to do this! case 'system': var app = false; if( msg.applicationId ) app = findApplication( msg.applicationId ); // TODO: Permissions!!! // permission should probably be checked per command? switch( msg.command ) { // Support generic callbacks case 'callback': var df = getWrapperCallback( msg.callbackId ); if( df ) { return df( msg.data ? msg.data : ( msg.error ? msg.error : null ) ); } return false; case 'invite': Workspace.inviteFriend(); break; // Application is asking for Friend credentials case 'friendcredentials': let response = false; let message = 'Could not retrieve Friend credentials.'; // TODO: Investigate different credential types if( msg.credentialType == 'friend' ) { // TODO: Filter application to get access to this! if( msg.callback ) { if( Workspace.storedCredentials ) { let enc = Workspace.encryption; let user = enc.decrypt( Workspace.storedCredentials.username, enc.getKeys().privatekey ); let pass = enc.decrypt( Workspace.storedCredentials.password, enc.getKeys().privatekey ); let logi = enc.decrypt( Workspace.storedCredentials.login, enc.getKeys().privatekey ); if( ( user || logi ) && pass ) { response = { username: user ? user : '', login: logi ? logi : '', password: pass }; message = 'Friend credentials successfully delivered.'; } } let nmsg = {}; for( let xz in msg ) nmsg[ xz ] = msg[ xz ]; nmsg.type = 'callback'; nmsg.response = response; nmsg.message = message; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } } break; case 'addfilesystemevent': if( msg.event && msg.path ) { if( !Workspace.appFilesystemEvents ) { Workspace.appFilesystemEvents = {}; } if( !Workspace.appFilesystemEvents[ msg.event ] ) { Workspace.appFilesystemEvents[ msg.event ] = []; } Workspace.appFilesystemEvents[ msg.event ].push( msg ); } break; case 'removefilesystemevent': if( msg.event && msg.path ) { if( Workspace.appFilesystemEvents ) { if( Workspace.appFilesystemEvents[ msg.event ] ) { var outEvents = []; var evList = Workspace.appFilesystemEvents[ msg.event ]; for( let a = 0; a < evList.length; a++ ) { var found = false; if( evList[a].applicationId == msg.applicationId ) { if( msg.viewId ) { if( evList[a].viewId == msg.viewId ) { found = true; } } else { found = true; } } if( !found ) outEvents.push( evList[a] ); } Workspace.appFilesystemEvents[ msg.event ] = outEvents; } } } break; case 'registermousedown': windowMouseX = msg.x; windowMouseY = msg.y; if( app && app.windows && app.windows[msg.viewId] ) { var div = app.windows[ msg.viewId ]; var x = GetElementLeft( div.content ); var y = GetElementTop( div.content ); windowMouseX += x; windowMouseY += y; // Activate window if it's not the current active one if( !window.currentMovable || window.currentMovable != app.windows[ msg.viewId ]._window.parentNode ) { _ActivateWindow( app.windows[ msg.viewId ]._window.parentNode ); } } // Hide context menu if( Workspace.contextMenuShowing ) { Workspace.contextMenuShowing.hide(); Workspace.contextMenuShowing = false; } break; case 'registermouseup': windowMouseX = msg.x; windowMouseY = msg.y; if( app && app.windows && app.windows[msg.viewId] ) { var div = app.windows[ msg.viewId ]; var x = GetElementLeft( div.content ); var y = GetElementTop( div.content ); windowMouseX += x; windowMouseY += y; } var el = document.elementFromPoint( windowMouseX, windowMouseY ); if( el ) { var clickEvent = document.createEvent( 'MouseEvents' ); clickEvent.initEvent( 'mouseup', true, true ); el.dispatchEvent( clickEvent ); } break; case 'showcontextmenu': Workspace.showContextMenu( msg.menu, window.event, msg ); break; case 'setworkspacemode': var mm = new Module( 'system' ); mm.onExecuted = function( e, d ) { if( e != 'ok' ) { var opts = ge( 'UserMode' ).getElementsByTagName( 'option' ); for( let b = 0; b < opts.length; b++ ) { if( opts[b].value == 'normal' ) { opts[b].selected = 'selected'; } else opts[b].selected = ''; } Workspace.workspacemode = 'normal'; } else { Workspace.workspacemode = msg.mode ? msg.mode : 'normal'; } } mm.execute( 'setsetting', { setting: 'workspacemode', data: msg.mode } ); // TODO: Perhaps we need to do something else as well? break; case 'brutalsignout': document.location.reload(); break; case 'keydown': DoorsKeyDown( msg.data ); break; // Task bar stuff case 'task_add': console.log( 'Received task_add' ); break; case 'task_clear_all': console.log( 'Received task_clear_all' ); break; // End task bar stuff case 'setsingleinstance': // Add to single instances if( app && msg.value == true && !Friend.singleInstanceApps[ app.applicationName ] ) { Friend.singleInstanceApps[ app.applicationName ] = app; } // Remove from single instances else if( app && msg.value == false ) { var out = []; for( let a in Friend.singleInstanceApps ) { if( a != app.applicationName ) out[a] = Friend.singleInstanceApps[a]; } Friend.singleInstanceApps = out; } break; case 'setclipboard': ClipboardSet( ( msg.value ? msg.value : '' ), ( msg.updatesystem ? true : false ) ); break; case 'applicationname': if( app ) { app.applicationDisplayName = msg.applicationname; for( let a = 0; a < Workspace.applications.length; a++ ) { if( app.applicationId == Workspace.applications[a].applicationId ) { Workspace.applications[a].applicationDisplayName = app.applicationDisplayName; break; } } var eles = ge( 'Tasks' ).getElementsByClassName( 'AppSandbox' ); for( let a = 0; a < eles.length; a++ ) { if( eles[a].ifr.applicationId == app.applicationId ) { eles[a].getElementsByClassName( 'Taskname' )[0].innerHTML = app.applicationDisplayName; break; } } } break; case 'getapplicationkey': if( app && ( msg.authId || msg.appPath ) ) { var args = { authId : ( msg.systemWide ? '0' : msg.authId ) }; if( !args.authId && msg.appPath ) { args.appPath = msg.appPath; } var nmsg = {}; for( let a in msg ) nmsg[a] = msg[a]; var m = new Module( 'system' ); m.onExecuted = function( e, d ) { //console.log( { e: e, d: JSON.parse( d ) } ); if( e && e == 'ok' && d ) { try { var data = JSON.parse( d ); if( data && typeof data[0] != 'undefined' ) { for( let k in data ) { // If found data and there is a publickey connected to it, try to decrypt with users privatekey if( data[k].Data && data[k].PublicKey ) { var decrypted = Workspace.encryption.decrypt( data[k].Data ); if( decrypted ) { data[k].Data = decrypted; } } try { if( data[k].Data ) { data[k].Data = JSON.parse( data[k].Data ); } } catch( e ) { } } } } catch(e) { var data = {}; } nmsg.type = 'callback'; nmsg.resp = 'ok'; nmsg.data = data; //console.log( 'app.contentWindow.postMessage: ', nmsg ); app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } else { nmsg.type = 'callback'; nmsg.resp = 'fail'; nmsg.data = { e:e, d:d }; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } } m.execute( 'keys', args ); msg.callback = false; } break; case 'setapplicationkey': //console.log( 'setapplicationkey: ', { msg: msg, app: app } ); // TODO: Add support for saving and getting key data connected to Door. if( app && msg.args && ( msg.authId || msg.appPath ) ) { try { if( msg.args.data && typeof msg.args.data != 'string' ) { msg.args.data = JSON.stringify( msg.args.data ); } } catch( e ) { } // If encrypt is true, try to encrypt with users publickey try { if( !msg.systemWide && msg.appPath && msg.appPath.indexOf( ':' ) >= 0 && Workspace.encryption.keys.server.publickey ) { var encryption_key = Workspace.encryption.keys.server.publickey; } else { var encryption_key = Workspace.encryption.keys.client.publickey; } if( msg.args.encrypt && msg.args.data && encryption_key ) { var encrypted = Workspace.encryption.encrypt( msg.args.data, encryption_key ); if( encrypted ) { msg.args.data = encrypted; msg.args.publickey = encryption_key; } } } catch( e ) { } var args = { type : '', name : msg.args.name, key : msg.args.data, publickey : ( msg.args.publickey ? msg.args.publickey : '' ), signature : '' } // If it's a device ... if( !msg.systemWide && msg.appPath && msg.appPath.indexOf( ':' ) >= 0 ) { args.appPath = msg.appPath; } // Else if it's an app else if( msg.authId || msg.systemWide ) { args.authId = ( msg.systemWide ? '0' : msg.authId ); } var nmsg = {}; for( let a in msg ) nmsg[a] = msg[a]; var m = new Module( 'system' ); m.onExecuted = function( e, d ) { //console.log( { e:e, d:d, o: args } ); if( nmsg.callback ) { if( e && e == 'ok' ) { nmsg.type = 'callback'; nmsg.resp = 'ok'; nmsg.data = d; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } else { nmsg.type = 'callback'; nmsg.resp = 'fail'; nmsg.data = { e:e, d:d }; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } } } m.execute( 'userkeysupdate', args ); msg.callback = false; } break; case 'notification': var nmsg = {}; for( let a in msg ) nmsg[a] = msg[a]; nmsg.locale = Workspace.locale; var cw = GetContentWindowByAppMessage( app, msg ); if( cw ) { var ccb = false; if( nmsg.clickcallback ) { var vmsg = {}; for( let a in nmsg ) vmsg[a] = nmsg[a]; vmsg.method = 'notification'; vmsg.callback = nmsg.clickcallback; vmsg.data = 'clicked'; ccb = function() { cw.postMessage( JSON.stringify( vmsg ), '*' ); } } // Sanitize title and text var title = msg.title ? msg.title.split( /\<[^>]*?\>/ ).join( '' ) : ''; var text = msg.text ? msg.text.split( /\<[^>]*?\>/ ).join( '' ) : ''; // Do the notification Notify( { title: title, text: text, application: app ? app.applicationName : '', applicationIcon: ( app && app.icon ) ? app.icon : false }, function() { cw.postMessage( JSON.stringify( nmsg ), '*' ); }, ccb ); } msg.callback = false; break; case 'getlocale': var nmsg = {}; for( let a in msg ) nmsg[ a ] = msg[ a ]; nmsg.locale = Workspace.locale; var cw = GetContentWindowByAppMessage( app, msg ); if( cw ) { cw.postMessage( JSON.stringify( nmsg ), '*' ); } msg.callback = false; break; case 'alert': let alerv = Alert( msg.title, msg.string ); if( alerv ) { if( alerv && alerv.viewId ) app.windows[ alerv.viewId ] = alerv; if( msg.applicationId ) alerv.applicationId = msg.applicationId; } break; case 'confirm': var nmsg = {}; for( let a in msg ) nmsg[ a ] = msg[ a ]; //console.log('we confirm...',nmsg); let confv = Confirm( msg.title, msg.string, function( data ) { if( app ) { nmsg.type = 'callback'; nmsg.data = data; nmsg.command = 'callback'; // Module calls should remain in their view context var cw = GetContentWindowByAppMessage( app, msg ); // Pass window id down if( nmsg.viewId ) { nmsg.viewId = msg.viewId; } if( cw ) cw.postMessage( JSON.stringify( nmsg ), '*' ); } }, ( nmsg.confirmok ? nmsg.confirmok : false ), ( nmsg.confirmcancel ? nmsg.confirmcancel : false ), ( nmsg.thirdButtonText ? nmsg.thirdButtonText : false ), ( nmsg.thirdButtonReturn ? nmsg.thirdButtonReturn : false ) ); app.windows[ confv.viewId ] = confv; msg.callback = false; if( msg.applicationId ) confv.applicationId = msg.applicationId; break; case 'reload_user_settings': Workspace.refreshUserSettings(); break; case 'change_application_permissions': // TODO: This will bring up the user authentication window first!!!! // TODO: The user must allow, because the app does not have this permission! var m = new Module( 'system' ); m.onExecuted = function( e, d ) { // Tell app! var rmsg = { command: 'updateapppermissions', result: e }; app.contentWindow.postMessage( JSON.stringify( rmsg ), '*' ); } m.execute( 'updateapppermissions', { application: msg.application, data: msg.data, permissions: JSON.stringify( msg.permissions ) } ); break; // Update login, kill old info, and tell apps case 'updatelogin': if( msg.username && msg.password ) { Friend.User.Logout( function() { Friend.User.Login( msg.username, msg.password, true ); for( let a = 0; a < Workspace.applications.length; a++ ) { let nmsg = { command: 'userupdate', applicationId: msg.applicationId }; Workspace.applications[a].contentWindow.postMessage( nmsg, '*' ); } } ); } else { Notify( { title: 'Could not update login', text: 'Missing username and password data to log in.' } ); } break; case 'userupdate': if( msg.reason ) { for( let a = 0; a < Workspace.applications.length; a++ ) { let nmsg = { command: 'userupdate', applicationId: msg.applicationId }; Workspace.applications[a].contentWindow.postMessage( nmsg, '*' ); } } break; case 'reloadmimetypes': Workspace.reloadMimeTypes(); break; // Check which mimetypes are available case 'checkmimetypes': if( app && msg.mimetypes ) { let result = {}; let resultCount = 0; for( let a = 0; a < msg.mimetypes.length; a++ ) { for( let b = 0; b < Workspace.mimeTypes.length; b++ ) { let m = Workspace.mimeTypes[ b ]; for( let c = 0; c < m.types.length; c++ ) { if( m.types[ c ] == msg.mimetypes[ a ] ) { result[ m.types[ c ] ] = m.executable; resultCount++; } } } } let nmsg = {}; for( let a in msg ) nmsg[a] = msg[a]; nmsg.data = resultCount > 0 ? result : false; nmsg.type = 'callback'; delete nmsg.command; const cw = GetContentWindowByAppMessage( app, msg ); cw.postMessage( nmsg, '*' ); //app.contentWindow.postMessage( nmsg, '*' ); msg = null; } break; case 'getopenscreens': if( app ) { var screens = []; var s = ge( 'Screens' ); var sl = s.getElementsByTagName( 'div' ); for( let a = 0; a < sl.length; a++ ) { if( sl[a].parentNode != s ) continue; if( !sl[a].className || sl[a].className.indexOf( 'Screen' ) < 0 ) continue; screens.push( { id: sl[a].id, title: sl[a].screenObject._flags['title'] } ); } var nmsg = {}; for( let a in msg ) nmsg[a] = msg[a]; nmsg.screens = screens; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } break; case 'switchscreens': _globalScreenSwap(); break; case 'nativewindow': if( app && msg.viewId ) { // Prepare response var nmsg = {}; for( let a in msg ) nmsg[a] = msg[a]; nmsg.command = 'nativewindowresponse'; switch( msg.action ) { case 'open': if( !msg.url ) msg.url = ''; if( !msg.specs ) msg.specs = false; nmsg.response = 'fail'; if( nativeWindows[msg.viewId] = window.open( msg.url, msg.viewId, msg.specs ) ) nmsg.response = 'ok'; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); break; case 'close': nmsg.response = 'fail'; if( nativeWindows[msg.viewId] ) { nativeWindows[msg.viewId].close(); // Clean house var nw = []; for( let a in nativeWindows ) { if( a != msg.viewId ) nw[a] = nativeWindows[a]; } nativeWindows = nw; nmsg.response = 'ok'; } app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); break; } } break; case 'refreshwindowbypath': Workspace.refreshWindowByPath( msg.path ); break; case 'wallpaperimage': if( msg.mode == 'doors' ) { Workspace.wallpaperImage = msg.image; } else { Workspace.windowWallpaperImage = msg.image; } Workspace.refreshDesktop(); break; case 'savewallpaperimage': var m = new Module( 'system' ); m.onExecuted = function( e, d ) { if( e == 'ok' ) { if( msg.mode == 'doors' ) { Workspace.wallpaperImage = msg.image ? msg.image : '/webclient/gfx/theme/default_login_screen.jpg'; } else { Workspace.windowWallpaperImage = msg.image; } Workspace.refreshDesktop(); } } m.execute( 'setsetting', { setting: 'wallpaper' + msg.mode, data: msg.image } ); break; // Set the local storage variable case 'setlocalstorage': break; // Get the local storage variable case 'getlocalstorage': break; case 'refreshtheme': Workspace.refreshTheme( msg.theme, true, msg.themeConfig ? msg.themeConfig : false ); break; // Save application state case 'savestate': var m = new Module( 'system' ); m.onExecuted = function( e, d ) { } // <- callback or so?? probably not.. m.execute( 'savestate', { state: msg.state, authId: msg.authId } ); break; case 'quit': // Look if we are allowed to quit if( !canQuitApp( app.applicationName ) ) return; if( app ) app.quit( msg.force ? msg.force : false ); if( PollTray ) PollTray(); break; case 'kill': if( app ) { if( msg.appName ) { KillApplication( msg.appName ); } else if( msg.appId ) { KillApplicationById( msg.appId ); } else app.quit( 1 ); // quit with FORCE == 1! } break; case 'break': if( app ) { var quitit = false; // TODO: Make this happen on num if( msg.appNum ) { for( let a = 0; a < Workspace.applications.length; a++ ) { var theApp = Workspace.applications[a]; if( theApp.applicationNumber == msg.appNum ) { theApp.contentWindow.postMessage( JSON.stringify( { applicationId: theApp.applicationId, command: 'notify', method: 'closewindow' } ), '*' ); setTimeout( function(){ if( theApp.contentWindow ) theApp.quit(); }, 100 ); break; } } } } break; // TODO: Permissions, not all should be able to do this! case 'listapplications': var nmsg = msg; var d = Workspace.listApplications(); if( msg.callbackId ) { var list = 'No running processes.'; if( d.length ) { list = ''; for( let a = 0; a < d.length; a++ ) { list += d[a].applicationNumber + '. ' + d[a].name + '
'; } list = { response: list }; } var df = getWrapperCallback( msg.callbackId ); return df( d ? true : false, list ); } else nmsg.data = d; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); break; case 'refreshdocks': Workspace.reloadDocks(); break; case 'refreshdoors': Workspace.refreshDesktop( false, true ); for( let a in movableWindows ) { if( movableWindows[ a ].content && movableWindows[ a ].content.fileBrowser ) { movableWindows[ a ].content.fileBrowser.refresh(); } } break; case 'executeapplication': // TODO: Make "can run applications" permission if( 1 == 1 ) { var nmsg = msg; function cb( response ) { nmsg.method = response ? 'applicationexecuted' : 'applicationnotexecuted'; if( app && app.contentWindow ) { app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } if( nmsg.callback ) { runWrapperCallback( nmsg.callback, response ); } } // Add flags let flags = false; if( msg.flags ) { flags = msg.flags; } // Special case if( msg.path && msg.path.split( ':' )[0] == 'System' ) { // Special case! var out = WorkspaceDormant.execute( msg.executable, msg.arguments ); cb( out ); } else { ExecuteApplication( msg.executable, msg.args, cb, false, flags ); } msg = null; } break; case 'librarycall': var j = new cAjax(); var ex = ''; if( msg.func ) { ex += msg.func + '/'; if( msg.args ) { if( typeof( msg.args ) == 'string' ) { ex += msg.args; } else if( typeof( msg.args ) == 'object' ) { for( let a in msg.args ) { if( a == 'command' ) ex += msg.args[a]; else { if( typeof( msg.args[a] ) == 'object' ) { j.addVar( a, JSON.stringify( msg.args[a] ) ); } else j.addVar( a, msg.args[a] ); } } } } // Optional vars if( msg.vars ) { for( let a in msg.vars ) j.addVar( a, msg.vars[a] ); } } j.open( 'post', '/' + msg.library + '/' + ex, true, true ); if( !msg.args || ( msg.args && !msg.args.skipsession ) ) j.addVar( 'sessionid', Workspace.sessionId ); j.onload = function( rc, dt ) { var nmsg = msg; nmsg.command = 'libraryresponse'; nmsg.returnCode = rc; nmsg.returnData = dt; var cw = GetContentWindowByAppMessage( app, msg ); if( cw ) cw.postMessage( JSON.stringify( nmsg ), '*' ); else if( app && app.contentWindow ) app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); else console.log('nowhere to take this :( ' + JSON.stringify( nmsg ) ); } j.send(); break; // Color pickers ------------------------------------------- case 'colorpicker': var win = app.windows ? app.windows[ msg.viewId ] : false; var tar = win ? app.windows[ msg.targetViewId ] : false; // Target for postmessage // Create a color picker if( msg.method == 'new' ) { // Success var fs = function( hex ) { if( hex.length ) { if( msg.successCallback ) { var nmsg = { applicationId: msg.applicationId, viewId: msg.viewId, type: 'callback', data: hex, callback: msg.successCallback }; if( tar ) tar.iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); else app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } } } // Cancel var fc = function() { if( msg.failCallback ) { var nmsg = { applicationId: msg.applicationId, viewId: msg.viewId, type: 'callback', callback: msg.failCallback }; if( tar ) tar.iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); else app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } } var c = new Friend.GUI.ColorPicker( fs, fc ); var nmsg = { applicationId: msg.applicationId, viewId: msg.viewId, type: 'callback', callback: msg.failCallback }; } // Just activate the color picker view else if( msg.method == 'activate' ) { var found = false; for( let a = 0; a < Friend.GUI.ColorPickers.length; a++ ) { if( Friend.GUI.ColorPickers[ a ].uniqueId != msg.uniqueId ) { continue; } Friend.GUI.ColorPickers[ a ].view.activate(); found = true; break; } if( msg.callback ) { var nmsg = { applicationId: msg.applicationId, viewId: msg.viewId, type: 'callback', data: found, callback: msg.callback }; if( tar ) tar.iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); else app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } } // Just close the color picker view else if( msg.method == 'close' ) { var found = false; for( let a = 0; a < Friend.GUI.ColorPickers.length; a++ ) { if( Friend.GUI.ColorPickers[ a ].uniqueId != msg.uniqueId ) { continue; } Friend.GUI.ColorPickers[ a ].view.close(); found = true; break; } if( msg.callback ) { var nmsg = { applicationId: msg.applicationId, viewId: msg.viewId, type: 'callback', data: found, callback: msg.callback }; if( tar ) tar.iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); else app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } } break; // Print dialogs ------------------------------------------- case 'printdialog': var win = app.windows ? app.windows[ msg.viewId ] : false; var tar = win ? app.windows[msg.targetViewId] : false; // Target for postmessage var triggerFunc = null; if( msg.callbackId ) { triggerFunc = function( data ) { var nmsg = { command: 'printdialog', applicationId: msg.applicationId, viewId: msg.viewId, targetViewId: msg.targetViewId, callbackId: msg.callbackId, data: data }; if( tar ) tar.iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); else app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } } var d = new Printdialog( msg.flags, triggerFunc ); break; // File dialogs -------------------------------------------- case 'filedialog': console.log( '[test] Filedialog', msg ); var win = app.windows ? app.windows[ msg.viewId ] : false; var tar = win ? app.windows[msg.targetViewId] : false; // Target for postmessage // No targetview id? Then just use the parent view if( !tar && msg.parentViewId ) tar = app.windows[ msg.parentViewId ]; var flags = { mainView: tar ? tar : win, type: msg.method, path: msg.path, title: msg.title, filename: msg.filename, suffix: msg.suffix, multiSelect: msg.multiSelect, keyboardNavigation: msg.keyboardNavigation, rememberPath: msg.rememberPath, applicationId: msg.applicationId, triggerFunction: function( data ) { var nmsg = msg; nmsg.data = data; if( tar && tar.iframe && tar.iframe.contentWindow ) tar.iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); else app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } }; var d = new Filedialog( flags ); break; case 'opencamera': var win = app.windows ? app.windows[ msg.viewId ] : false; var tar = win ? app.windows[msg.targetViewId] : false; // Target for postmessage var vtitle = msg.title ? msg.title : ( msg.flags.title ? msg.flags.title : i18n('i18n_camera') ); var vwidth = msg.width ? msg.width : ( msg.flags.width ? msg.flags.width : 480 ); var vheight= msg.height ? msg.height : ( msg.flags.height ? msg.flags.height : 320 ); var cview = new View({ title: vtitle, width: vwidth, height: vheight }); cview.callback = msg.callback; cview.self = cview; cview.openCamera( false, function( data ) { var nmsg = {'command':'callback'}; nmsg.data = data; nmsg.callback = msg.callback; if( tar ) tar.iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); else if( app && app.contentWindow ) app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); cview.close(); }); cview.onClose = function() { var nmsg = { 'command':'callback','closed':true, 'data':{} }; nmsg.callback = msg.callback; if( tar ) tar.iframe.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); else if(app && app.contentWindow) app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); }; break; } break; } // Check global callback function if( msg && msg.callback ) { var nmsg = {}; for( let b in msg ) nmsg[b] = msg[b]; nmsg.type = 'callback'; app.contentWindow.postMessage( JSON.stringify( nmsg ), '*' ); } } // TODO: Check the flow here, to prevent infinite loops // Reroute message from the deep! else if( msg && msg.applicationId ) { var app = findApplication( msg.applicationId ); if( !app ) return false; // Sometimes we want to send to a pre determined view by id. if( msg.destinationViewId || msg.targetViewId ) { var target = msg.destinationViewId ? msg.destinationViewId : msg.targetViewId; var cw = GetContentWindowById( app, target ); if( cw ) { cw.postMessage( JSON.stringify( msg ), '*' ); } return; } app.contentWindow.postMessage( JSON.stringify( msg ), '*' ); } } /* Magic clipboard code ----------------------------------------------------- */ Friend.clipboard = ''; Friend.macCommand = false; document.addEventListener( 'keydown', function( e ) { var wh = e.which ? e.which : e.keyCode; var t = e.target ? e.target : e.srcElement; if( wh == 91 ) { Friend.macCommand = true; } /*if( e.ctrlKey || Friend.macCommand ) { if( wh == 86 ) { if( Friend.clipboard.length && Friend.clipboard != '' && Friend.clipboard.charCodeAt( 0 ) != 1 ) { if( ClipboardPasteIn( t, Friend.clipboard ) ) { return cancelBubble( e ); } } dispatchEvent( t, 'paste' ); } else if( wh == 67 || wh == 88 ) { ClipboardSet( ClipboardGetSelectedIn( t ) ); } }*/ } ); document.addEventListener( 'keyup', function( e ) { var wh = e.which ? e.which : e.keyCode; if( wh == 91 ) Friend.macCommand = false; } ); /*document.addEventListener( 'paste', function( evt ) { var mimetype = ''; var cpd = ''; if( evt.clipboardData && evt.clipboardData.types.indexOf( 'text/plain' ) > -1 ) { mimetype = 'text/plain'; } //we only do text handling here for now if( mimetype != '' ) { cpd = evt.clipboardData.getData( mimetype ); //console.log('compare old and new in apirwrapper. new data: ',cpd,'friend prev:',Friend.prevClipboard,'friend clipboard:',Friend.clipboard); if( Friend.prevClipboard != cpd ) { Friend.clipboard = cpd; } } if( typeof Application != 'undefined' ) Application.sendMessage( { type: 'system', command: 'setclipboard', value: Friend.clipboard } ); return true; } );*/ // Set the clipboard function ClipboardSet( text, updatesystem ) { if( text == '' ) return; if( Friend.clipboard == text ) return; Friend.prevClipboard = Friend.clipboard; Friend.clipboard = text; for( let a = 0; a < Workspace.applications.length; a++ ) { var app = Workspace.applications[a]; app.contentWindow.postMessage( JSON.stringify( { applicationId: app.applicationId, command: 'updateclipboard', value: Friend.clipboard } ), '*' ); } for( let a in movableWindows ) { var ifr = movableWindows[a].getElementsByTagName( 'iframe' )[0]; if( !ifr ) continue; ifr.contentWindow.postMessage( JSON.stringify( { command: 'updateclipboard', value: Friend.clipboard } ), '*' ); } //ask user if he want to make this clipboard global if( updatesystem ) ClipboardToClientSystem(); } function ClipboardToClientSystem() { success = document.execCommand( 'copy' ); /* if( Friend.clipboardWidget ) { //.... } else { o = { width: 480, height: 200, valign: 'center', halign: 'center', scrolling: false, autosize: true }; Friend.clipboardWidget = new Widget( o ); } Friend.clipboardWidget.dom.innerHTML = '

'+ i18n('i18n_copy_to_system_clipboard_headline') +'

' + i18n('i18n_copy_to_system_clipboard') + '
'; Friend.clipboardWidget.raise(); Friend.clipboardWidget.show(); */ } function CopyClipboardToClientSystem( evt ) { myslave = ge('clipBoardWidgetTA'); myslave.focus(); myslave.select(); var success = false; try { myslave.focus(); success = document.execCommand( 'copy' ); } catch( e ) { console.log( 'failed to copy to clippy', e ); } if( Friend.clipboardWidget ) { myslave.blur(); ge('clipBoardWidgetTA').parentNode.removeChild( ge('clipBoardWidgetTA') ); Friend.clipboardWidget.hide(); } } function CancelCopyClipboardToClientSystem() { if( Friend.clipboardWidget ) { ge('clipBoardWidgetTA').parentNode.removeChild( ge('clipBoardWidgetTA') ); Friend.clipboardWidget.hide(); } } // Copy from select area to clipboard function ClipboardGetSelectedIn( ele ) { var text = ''; if( window.getSelection ) { text = window.getSelection().toString(); } else if( document.selection && document.selection.type != 'Control' ) { text = document.selection.createRange().text; } return text; } // Paste to target from clipboard function ClipboardPasteIn( ele, text ) { if( typeof ele.value != 'undefined' ) { if( document.selection ) { ele.focus(); var sel = document.selection.createRange(); sel.text = text; ele.focus(); } else if( ele.selectionStart || ele.selectionStart === 0 ) { var startPos = ele.selectionStart; var endPos = ele.selectionEnd; var scrollTop = ele.scrollTop; ele.value = ele.value.substring( 0, startPos ) + text + ele.value.substring( endPos, ele.value.length ); ele.focus(); ele.selectionStart = startPos + text.length; ele.selectionEnd = startPos + text.length; ele.scrollTop = scrollTop; } else { ele.value += text; ele.focus(); } dispatchEvent( ele, 'change' ); // Send paste key dispatchEvent( ele, 'input' ); return true; } return false; } /* Done clipboard ----------------------------------------------------------- */ // Find application storage object function findApplication( applicationId ) { for( let a = 0; a < Workspace.applications.length; a++ ) { if( Workspace.applications[a].applicationId == applicationId ) return Workspace.applications[a]; } return false; } /* We need these global keys ------------------------------------------------ */ // Only for our friends if( window.addEventListener ) { window.addEventListener ( 'keydown', function ( e ) { if ( !e ) e = window.event; var k = e.which | e.keyCode; if ( k == 113 && e.shiftKey ) { if ( typeof ( ShowLauncher ) != 'undefined' ) { ShowLauncher (); } return cancelBubble ( e ); } // Switch screens else if( k == 77 && e.ctrlKey ) { _globalScreenSwap(); return cancelBubble( e ); } // Main screen else if( k == 78 && e.ctrlKey ) { return _globalScreenSwap( 'DoorsScreen' ); } }); // Temporary to get message events on page load for login outside using an iframe window.addEventListener( 'message', function ( e ) { if ( !e ) e = window.event; if( e.data && e.data.command == 'login' ) { var args = { 'keys' : false, 'username' : false, 'password' : false, 'sessionid' : false, 'userid' : false, 'fullname' : false, 'remember' : false, 'callback' : false, 'event' : false }; for( key in e.data ) { if( e.data[key] && typeof args[key] !== 'undefined' ) { args[key] = e.data[key]; } } console.log( 'Workspace.login() from window.addEventListener: ', args ); // TODO: This can probably be moved somwhere everything else related to message is ... if( args.username ) { Workspace.loginUsername = args.username; } if( args.sessionid ) { Friend.User.LoginWithSessionId( args.sessionid, args.callbac, args.event ); } if( typeof( args.username ) != 'undefined' ) Workspace.login( args.username, args.password, args.remember, args.callback, args.event ); } }); // Blur events - allows us to track if we're clicking on a frame outside the Friend domain Friend.canActivateWindowOnBlur = true; window.addEventListener( 'blur', function( e ) { // Canactivatewindowonblur is there to prevent loading elements from // being deactivated when they emit a blur event from their iframe if( !window.isMobile ) { if( Friend.currentWindowHover && Friend.canActivateWindowOnBlur ) { _ActivateWindowOnly( Friend.currentWindowHover ); } } } ); } // Global screen swap function _globalScreenSwap( id ) { if( window.currentScreen ) { if( !id ) { return window.currentScreen.screen.screenCycle(); } else { // TODO console.log( 'Swapping a screen by id.' ); } } } // Do the app have the permission? function checkAppPermission( authid, permission, value ) { var eles = ge( 'Tasks' ).getElementsByTagName( 'iframe' ); for( let a = 0; a < eles.length; a++ ) { if( eles[a].authId == authid ) { // JSX apps have all rights.. // TODO: Box down with security! if( eles[a].applicationType && eles[a].applicationType == 'jsx' ) return true; for( let b = 0; b < eles[a].permissions.length; b++ ) { if( eles[a].permissions[b][0] == permission ) { if( value ) { if( eles[a].permissions[b][1] == value ) { return true; } return false; } return true; } } } } return false; } function GetContentWindowByAppMessage( app, msg ) { var cw = app; // Pass window id down if( msg.viewId ) { if( app.windows[msg.viewId] ) { cw = app.windows[msg.viewId].iframe; } /*else { cw = app; console.log( '---------------------------' ); console.log( app, msg ); console.log( '..........' ); }*/ } else if( msg.widgetId ) { if( app.widgets[msg.widgetId] ) { cw = app.widgets[msg.widgetId].iframe; } } if( cw.contentWindow ) return cw.contentWindow; return false; } function GetContentWindowById( app, id ) { var cw = app; if( app.windows[id] ) { cw = app.windows[id].iframe; } if( cw.contentWindow ) return cw.contentWindow; return false; } // Just get the iframe object function _getAppByAppId( appid ) { var t = ge( 'Tasks' ); for( let a = 0; a < t.childNodes.length; a++ ) { if( !t.childNodes[a].ifr ) continue; if( t.childNodes[a].ifr.applicationId == appid ) return t.childNodes[a].ifr; } return false; } // Add Css by url function AddCSSByUrl( csspath, callback ) { if( !window.cssStyles ) window.cssStyles = []; if( typeof( window.cssStyles[csspath] ) != 'undefined' ) { // Remove existing and clean up var pn = window.cssStyles[csspath].parentNode; if( pn ) pn.removeChild( window.cssStyles[csspath] ); var o = []; for( let a in window.cssStyles ) { if( a != csspath ) { o[a] = window.cssStyles[a]; } } window.cssStyles = o; } // Add and register var s = document.createElement( 'link' ); s.rel = 'stylesheet'; s.href = csspath; if( callback ){ s.onload = function() { callback(); } } document.body.appendChild( s ); window.cssStyles[csspath] = s; } /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ /******************************************************************************* * * * The FriendUP Desktop Environment interface. For use on workstations and * * other places. * * * *******************************************************************************/ var _protocol = document.location.href.split( '://' )[0]; Workspace = { receivePush: function() { return false; }, environmentName: 'Workspace', osName: 'FriendOS', staticBranch: 'Hydrogen 4', icons: [], menuMode: 'pear', // 'miga', 'fensters' (alternatives) -> other menu behaviours mode: 'default', initialized: false, protocol: _protocol, protocolUrl: _protocol + '://', menu: [], diskNotificationList: [], notifications: [], notificationEvents: [], applications: [], importWindow: false, menuState: '', themeOverride: false, systemInfo: false, lastfileSystemChangeMessage: false, userSettingsLoaded: false, // Tell when user settings loaded desktopFirstRefresh: false, // Tell when workspace first refreshed serverIsThere: false, runLevels: [ { name: 'root', domain: _protocol + '://' + document.location.href.match( /h[^:]*?\:\/\/([^/]+)/i )[1] }, { name: 'utilities', domain: _protocol + '://' + document.location.href.match( /h[^:]*?\:\/\/([^/]+)/i )[1], /*domain: 'http://utilities.' + document.location.href.match( /h[^:]*?\:\/\/([^/]+)/i )[1],*/ listener: apiWrapper } ], directoryView: false, conn: null, pouchManager: null, deviceid: GetDeviceId(), preinit: function() { // Go ahead and init! ScreenOverlay.init(); Workspace.init(); if( window.friendApp ) { document.body.classList.add( 'friendapp' ); } }, init: function() { // First things first if( this.initialized ) return; // Preload some images var imgs = [ '/webclient/gfx/system/offline_16px.png', '/themes/friendup12/gfx/busy.png' ]; this.imgPreload = []; for( var a = 0; a < imgs.length; a++ ) { var i = new Image(); i.src = imgs[a]; this.imgPreload.push( i ); } // Wait for load if( typeof( InitWindowEvents ) == 'undefined' || typeof( InitGuibaseEvents ) == 'undefined' ) { return setTimeout( 'Workspace.init()', 50 ); } this.initialized = true; checkMobileBrowser(); if( !this.addedMobileCSS && window.isMobile ) { document.body.setAttribute( 'mobile', 'mobile' ); AddCSSByUrl( '/webclient/css/responsive.css' ); this.addedMobileCSS = true; } // Show the login prompt if we're not logged in! Friend.User.Login(); }, // Ready after init // NB: This is where we go towards workspace_inside.js postInit: function() { if( this.postInitialized ) return; let self = this; // Everything must be ready if( typeof( ge ) == 'undefined' ) { if( this.initTimeout ) clearTimeout( this.initTimeout ); this.initTimeout = setTimeout ( 'Workspace.postInit()', 25 ); return; } // We passed! self.postInitialized = true; if( this.loginPrompt ) this.loginPrompt.setFlag( 'hidden', 1 ); // Do the init! window.addEventListener( 'beforeunload', Workspace.leave, true ); InitWindowEvents(); InitWorkspaceEvents(); InitGuibaseEvents(); let dapis = document.createElement( 'script' ); dapis.src = '/system.library/module/?module=system&command=doorsupport&sessionid=' + this.sessionId; document.getElementsByTagName( 'head' )[0].appendChild( dapis ); // Add event listeners for( let a = 0; a < this.runLevels.length; a++ ) { let listener = this.runLevels[a].listener; if ( !listener ) continue; if( window.addEventListener ) window.addEventListener( 'message', listener, true ); else window.attachEvent( 'onmessage', listener, true ); } // Set base url this.baseUrl = document.location.href.split( 'index.html' )[0]; // Setup default Doors screen let wbscreen = new Screen( { title: Workspace.environmentName, id: 'DoorsScreen', extra: Workspace.fullName, taskbar: true, scrolling: false } ); // Make links to screen on this object this.screen = wbscreen; this.screenDiv = wbscreen.div; let tray = document.createElement( 'div' ); tray.id = 'Tray'; this.screenDiv.appendChild( tray ); // Init the deepest field if( !isMobile ) DeepestField.init(); else DeepestField = false; // Key grabber if( !ge( 'InputGrabber' ) ) { let i = document.createElement( 'input' ); i.type = 'text'; i.id = 'InputGrabber'; i.style.position = 'absolute'; i.style.left = '-1000px'; i.style.top = '0'; i.style.opacity = 0; i.style.pointerEvents = 'none'; ge( 'DoorsScreen' ).appendChild( i ); } wbscreen.div.addEventListener( 'mousedown', function( e ) { let wd = wbscreen.div.screenTitle.getElementsByClassName( 'Extra' )[0].widget; if( wd ) { if( wd.shown ) { wd.hideWidget(); } } } ); // Widget for various cool facts! wbscreen.div.screenTitle.getElementsByClassName( 'Extra' )[0].onmouseover = function( e ) { this.classList.add( 'Hover' ); } wbscreen.div.screenTitle.getElementsByClassName( 'Extra' )[0].onmouseout = function( e ) { this.classList.remove( 'Hover' ); } // In desktop mode, show the calendar if( !window.isMobile ) { let ex = wbscreen.div.screenTitle.getElementsByClassName( 'Extra' )[0]; Workspace.calendarClickEvent = function( e ) { if( !ex.widget ) { o = { width: 400, height: 300, top: Workspace.screen.contentDiv.offsetTop, halign: 'right', scrolling: false, autosize: true }; ex.widget = new Widget( o ); ex.widget.dom.style.transition = 'height 0.25s'; ex.widget.showWidget = function() { let sself = this; this.dom.style.height = '0px'; Workspace.refreshExtraWidgetContents(); CoverScreens(); ge( 'DoorsScreen' ).classList.add( 'HasWidget' ); setTimeout( function() { sself.show(); sself.raise(); ExposeScreens(); }, 100 ); } ex.widget.hideWidget = function() { let sself = this; ge( 'DoorsScreen' ).classList.add( 'HidingCalendar' ); setTimeout( function() { ge( 'DoorsScreen' ).classList.remove( 'HasWidget' ); ge( 'DoorsScreen' ).classList.remove( 'HidingCalendar' ); sself.shown = false; sself.hide(); sself.lower(); ExposeScreens(); }, 250 ); } } if( !ex.widget.shown ) ex.widget.showWidget(); else ex.widget.hide(); return cancelBubble( e ); } ex.onclick = Workspace.calendarClickEvent; ex.classList.add( 'MousePointer' ); ex.onmousedown = function( e ) { return cancelBubble( e ); } ex.onmouseup = function( e ) { return cancelBubble( e ); } } // Setup clock if( !isMobile ) { let ex = ge( 'DoorsScreen' ).screenObject._titleBar; ex = ex.getElementsByClassName( 'Extra' )[0]; function clock() { let d = new Date(); if( !ex.time ) { let t = document.createElement( 'div' ); t.className = 'Time'; ex.appendChild( t ); ex.time = t; } if( !Friend.User.ServerIsThere ) { if( !ex.offline ) { let o = document.createElement( 'div' ); o.className = 'Offline'; o.innerHTML = i18n( 'i18n_ws_disconnected' ); if( ex.time ) { ex.insertBefore( o, ex.time ); } else { ex.appendChild( o ); } ex.offline = o; } } else if( ex.offline ) { ex.removeChild( ex.offline ); ex.offline = null; } // Set the clock let etime = ''; etime += StrPad( d.getHours(), 2, '0' ) + ':' + StrPad( d.getMinutes(), 2, '0' ); ex.time.innerHTML = etime; // Realign workspaces Workspace.nudgeWorkspacesWidget(); Workspace.refreshExtraWidgetContents(); // < Screenbar icons } this.clockInterval = setInterval( clock, 30000 ); } // Start the workspace session! this.initializingWorkspaces = true; this.initWorkspaces( function( returnValue ) { // Recall wallpaper from settings self.refreshUserSettings( function(){ // Refresh desktop for the first time Workspace.refreshDesktop( false, true ); } ); // Create desktop self.directoryView = new DirectoryView( wbscreen.contentDiv ); // Create default desklet let mainDesklet = CreateDesklet( self.screenDiv, 64, 480, 'right' ); // Add desklet to dock self.mainDock = mainDesklet; if( !isMobile ) { self.mainDock.dom.oncontextmenu = function( e ) { let tar = e.target ? e.target : e.srcElement; if( tar.classList && tar.classList.contains( 'Task' ) ) { return Workspace.showContextMenu( false, e ); } let men = [ { name: i18n( 'i18n_edit_dock' ), command: function() { ExecuteApplication( 'Dock' ); } } ]; if( tar.classList && tar.classList.contains( 'Launcher' ) ) { men.push( { name: i18n( 'i18n_remove_from_dock' ), command: function() { Workspace.removeFromDock( tar.executable ); } } ); } if( movableWindowCount > 0 ) { men.push( { name: i18n( 'i18n_minimize_all_windows' ), command: function( e ) { let t = GetTaskbarElement(); let lW = null; for( let a = 0; a < t.childNodes.length; a++ ) { if( t.childNodes[a].view && !t.childNodes[a].view.parentNode.getAttribute( 'minimized' ) ) { t.childNodes[a].view.parentNode.setAttribute( 'minimized', 'minimized' ); } } _DeactivateWindows(); } } ); men.push( { name: i18n( 'i18n_show_all_windows' ), command: function( e ) { let t = GetTaskbarElement(); for( let a = 0; a < t.childNodes.length; a++ ) { if( t.childNodes[a].view && t.childNodes[a].view.parentNode.getAttribute( 'minimized' ) == 'minimized' ) { t.childNodes[a].view.parentNode.removeAttribute( 'minimized' ); } } _ActivateWindow( t.childNodes[t.childNodes.length-1].view ); } } ); } Workspace.showContextMenu( men, e ); } } // For mobiles else { self.mainDock.dom.oncontextmenu = function( e ) { let tar = e.target ? e.target : e.srcElement; if( window.MobileContextMenu ) { MobileContextMenu.show( tar ); } } } self.reloadDocks(); } ); // Init security subdomains SubSubDomains.initSubSubDomains(); // console.log( 'Test2: Done post init.' ); }, setLoading: function( isLoading ) { if( isLoading ) { document.body.classList.add( 'Loading' ); } else { if( !this.initializingWorkspaces ) { document.body.classList.add( 'Inside' ); // If not exists document.body.classList.add( 'Loaded' ); document.body.classList.remove( 'Login' ); // If exists document.body.classList.remove( 'Loading' ); } } }, // Just a stub - this isn't used anymore rememberKeys: function() { return false; }, encryption: { fcrypt: fcrypt, keyobject: false, encoded: true, keys: { client: false, server: false }, setKeys: function( u, p ) { if( typeof( this.fcrypt ) != 'undefined' ) { if( u && !Workspace.loginUsername ) Workspace.loginUsername = u; p = ( !p || p.indexOf('HASHED') == 0 ? p : ( 'HASHED' + Sha256.hash( p ) ) ); if( window.ScreenOverlay ) ScreenOverlay.addDebug( 'Generating sha256 keys' ); var seed = ( u && p ? this.fcrypt.generateKey( ( u + ':' + p ), 32, 256, 'sha256' ) : false ); var keys = ApplicationStorage.load( { applicationName : 'Workspace' } ); if( !keys || ( keys && !keys.privatekey ) || ( keys && seed && keys.recoverykey != seed ) ) { if( window.ScreenOverlay ) ScreenOverlay.addDebug( 'Generating encryption keys' ); this.keyobject = this.fcrypt.generateKeys( false, false, false, seed ); keys = this.fcrypt.getKeys( this.keyobject ); } else { if( window.ScreenOverlay ) { ScreenOverlay.addDebug( 'Loaded encryption keys' ); } } if( keys ) { if( this.encoded ) { this.keys.client = { privatekey : this.fcrypt.encodeKeyHeader( keys.privatekey ), publickey : this.fcrypt.encodeKeyHeader( keys.publickey ), recoverykey : keys.recoverykey }; if( window.ScreenOverlay ) ScreenOverlay.addDebug( 'Keys stored encoded' ); } else { this.keys.client = { privatekey : keys.privatekey, publickey : keys.publickey, recoverykey : keys.recoverykey }; if( window.ScreenOverlay ) ScreenOverlay.addDebug( 'Keys stored raw' ); } } return this.keys; } return false; }, generateKeys: function( u, p ) { if( typeof( this.fcrypt ) != 'undefined' ) { if( window.ScreenOverlay ) ScreenOverlay.addDebug( 'Generating keys' ); var pass = ( u && p ? u + ':' : '' ) + ( p ? p : '' ); var keyobject = this.fcrypt.generateKeys( pass ); var keys = this.fcrypt.getKeys( keyobject ); if( keys ) { if( this.encoded ) { return { privatekey : this.fcrypt.encodeKeyHeader( keys.privatekey ), publickey : this.fcrypt.encodeKeyHeader( keys.publickey ), recoverykey : keys.recoverykey }; } else { return { privatekey : keys.privatekey, publickey : keys.publickey, recoverykey : keys.recoverykey }; } } } return false; }, getKeys: function() { if( typeof( this.fcrypt ) != 'undefined' && this.keys.client ) { if( this.encoded ) { return { privatekey : this.fcrypt.encodeKeyHeader( this.keys.client.privatekey ), publickey : this.fcrypt.encodeKeyHeader( this.keys.client.publickey ), recoverykey : this.keys.client.recoverykey }; } else { return { privatekey : this.fcrypt.decodeKeyHeader( this.keys.client.privatekey ), publickey : this.fcrypt.decodeKeyHeader( this.keys.client.publickey ), recoverykey : this.keys.client.recoverykey }; } } return false; }, getServerKey: function( callback ) { var k = new Module( 'system' ); k.onExecuted = function( e, d ) { if( callback ) { if( e == 'ok' && d ) { callback( d ); } else { callback( false ); } } } k.execute( 'getserverkey' ); }, encryptRSA: function( str, publickey ) { if( typeof( this.fcrypt ) != 'undefined' ) { return this.fcrypt.encryptRSA( str, ( publickey ? publickey : this.keys.client.publickey ) ); } return false; }, decryptRSA: function( cipher, privatekey ) { if( typeof( this.fcrypt ) != 'undefined' ) { return this.fcrypt.decryptRSA( cipher, ( privatekey ? privatekey : this.keys.client.privatekey ) ); } return false; }, encryptAES: function( str, publickey ) { if( typeof( this.fcrypt ) != 'undefined' ) { return this.fcrypt.encryptAES( str, ( publickey ? publickey : this.keys.client.publickey ) ); } return false; }, decryptAES: function( cipher, privatekey ) { if( typeof( this.fcrypt ) != 'undefined' ) { return this.fcrypt.decryptAES( cipher, ( privatekey ? privatekey : this.keys.client.privatekey ) ); } return false; }, encrypt: function( str, publickey ) { if( typeof( this.fcrypt ) != 'undefined' ) { var encrypted = this.fcrypt.encryptString( str, ( publickey ? publickey : this.keys.client.publickey ) ); if( encrypted && encrypted.cipher ) { return encrypted.cipher; } } return false; }, decrypt: function( cipher, privatekey ) { if( typeof( this.fcrypt ) != 'undefined' ) { var decrypted = this.fcrypt.decryptString( cipher, ( privatekey ? privatekey : this.keys.client.privatekey ) ); if( decrypted && decrypted.plaintext ) { return decrypted.plaintext; } } return false; }, sha256: function( str ) { if( !str && typeof( this.fcrypt ) != 'undefined' ) { return this.fcrypt.generateKey( '', 32, 256, 'sha256' ); } if( typeof( Sha256 ) != 'undefined' ) { return Sha256.hash( str ); } return false; }, md5: function( str ) { if( !str && typeof( this.fcrypt ) != 'undefined' ) { return MD5( this.fcrypt.generateKey( '', 32, 256, 'sha256' ) ); } if( typeof( MD5 ) != 'undefined' ) { return MD5( str ); } return false; } }, exitMobileMenu: function() { document.body.classList.remove( 'WorkspaceMenuOpen' ); if( ge( 'WorkspaceMenu' ) ) { var eles = ge( 'WorkspaceMenu' ).getElementsByTagName( '*' ); for( var z = 0; z < eles.length; z++ ) { if( eles[z].classList && eles[z].classList.contains( 'Open' ) ) eles[z].classList.remove( 'Open' ); } ge( 'WorkspaceMenu' ).classList.remove( 'Open' ); if( WorkspaceMenu.back ) { WorkspaceMenu.back.parentNode.removeChild( WorkspaceMenu.back ); WorkspaceMenu.back = null; } } }, showLoginPrompt: function() { // No loginprompt when we are inside if( document.body.classList.contains( 'Inside' ) ) return; // Enable friend book mode if( document.body.getAttribute( 'friendbook' ) == 'true' ) window.friendBook = true; // Set body to login state document.body.className = 'Login'; if( Workspace.interfaceMode && Workspace.interfaceMode == 'native' ) return; // Allowed hash vars we can send to loginpromt function allowedHashVars() { let vars = []; let hash = {}; if( window.location.hash && window.location.hash.split( '#' )[1] ) { let allowed = [ 'module', 'verify', 'invite' ]; let url = window.location.hash.split( '#' )[1].split( '&' ); for( let a in url ) { if( url[ a ].indexOf( '=' ) >= 0 && url[ a ].split( '=' )[ 0 ] ) { hash[ url[ a ].split( '=' )[ 0 ] ] = url[ a ].replace( url[ a ].split( '=' )[ 0 ] + '=', '' ); } } for( let b in allowed ) { if( allowed[ b ] in hash ) { vars.push( allowed[ b ] + '=' + hash[ allowed[ b ] ] ); } } // Remove the hash values from the url after window.location.hash = ''; } return ( vars.length > 0 ? ( '?' + vars.join( '&' ) ) : '' ); } let lp = new View( { id: 'Login', width: 432, 'min-width': 290, 'max-width': 432, height: 480, 'min-height': 280, 'resize': false, title: 'Sign into your account', close: false, login: true, theme: 'login' } ); lp.limitless = true; lp.onMessage = function( msg ) { if( msg && msg.type && msg.src && msg.action == 'openWindow' ) { switch( msg.type ) { case 'terms': case 'eula': { let titl = msg.type == 'eula' ? 'EULA' : 'terms'; let v = new View( { title: 'Please verify our ' + titl, width: 432, height: 480, resize: false } ); let f = new XMLHttpRequest(); if( msg.type == 'eula' ) { f.open( 'POST', '/webclient/templates/EULA.html', true, true ); } else { f.open( 'POST', msg.src, true, true ); } f.onload = function() { let t = this.responseText + ''; t += '
\
\ \
'; v.setContent( t ); } f.send(); } break; case 'privacypolicy': { let v = new View( { title: 'Privacy policy', width: 432, height: 480, resize: false } ); let f = new XMLHttpRequest(); f.open( 'POST', '/webclient/templates/PrivacyPolicy.html', true, true ); f.onload = function() { let t = this.responseText + ''; t += '
\
\ \
'; v.setContent( t ); } f.send(); } break; } } } lp.setRichContentUrl( '/loginprompt' + allowedHashVars() ); Workspace.loginPrompt = lp; // Show it this.showDesktop(); }, flushSession: function() { Friend.User.FlushSession(); }, login: function( u, p, r, callback, ev ) { // Use authmodule login if( Workspace.authModuleLogin ) { console.log( 'Using our existing auth module.' ); return Workspace.authModuleLogin( callback, window ); } // Wrap to user object return Friend.User.Login( u, p, r, callback, ev ); }, // TODO: This function should never be used! loginSessionId: function( sessionid, callback, ev ) { return Friend.User.LoginWithSessionId( sessionid, callback, ev ); }, showDesktop: function() { // View desktop // May be deprecated }, // Stubs leave: function() { }, doLeave: function() { }, initUserWorkspace: function( json, callback, ev ) { console.log( 'Test2: Init user workspace.' ); let _this = Workspace; // Once we are done function SetupWorkspaceData( json, cb ) { // console.log( 'Test2: Set it up.', json ); // Ok, we're in _this.sessionId = json.sessionid ? json.sessionid : null; _this.userId = json.userid; _this.fullName = json.fullname; if( json.username ) _this.loginUsername = json.username; // After a user has logged in we want to prepare the workspace for him. // Store user data in localstorage for later verification encrypted let userdata = ApplicationStorage.load( { applicationName : 'Workspace' } ); if( userdata ) { userdata.sessionId = _this.sessionId; userdata.userId = _this.userId; userdata.loginUsername = _this.loginUsername; userdata.fullName = _this.fullName; ApplicationStorage.save( userdata, { applicationName : 'Workspace' } ); } // Only renew session.. if( ge( 'SessionBlock' ) ) { // Could be many while( ge( 'SessionBlock' ) ) { document.body.removeChild( ge( 'SessionBlock' ) ); } // console.log( 'Test2: Renewing all sessions.' ); // We have renewed our session, make sure to set it and run ajax queue Friend.User.RenewAllSessionIds( _this.sessionId ); // Call back! if( cb ) cb(); return; } // Set server key // TODO: Find a better place to set server publickey earlier in the process, temporary ... again time restraints makes delivery fast and sloppy ... if( !_this.encryption.keys.server ) { _this.encryption.getServerKey( function( server ) { _this.encryption.keys.server = ( server ? { publickey: server } : false ); } ); } // Make sure we have a public key for this user (depending on login interface) // TODO: See if we actually need this (and it doesn't work properly) /*if( window.friendApp ) { var credentials = friendApp.getCredentials(); var info = Workspace.generateKeys( credentials.username, credentials.password ); var m = new Module( 'system' ); m.onExecuted = function( e, d ) { // Call back! if( cb ) cb(); } m.execute( 'setuserpublickey', { publickey: info.publickey } ); return; }*/ // Call back! if( cb ) cb(); } // Manipulate screen overlay // (this will only be shown once!) // TODO: Figure out if this is the right behavior in every case // implementation circumvents relogin issue if( !Workspace.screenOverlayShown ) { ScreenOverlay.show(); Workspace.screenOverlayShown = true; } if( !this.userWorkspaceInitialized ) { // console.log( 'Test2: Doing the initialization.' ); this.userWorkspaceInitialized = true; // Loading remaining scripts let s = document.createElement( 'script' ); s.src = '/webclient/js/gui/workspace_inside.js;' + 'webclient/3rdparty/adapter.js;' + 'webclient/3rdparty/pdfjs/build/pdf.js;' + 'webclient/js/utils/speech-input.js;' + 'webclient/js/utils/events.js;' + 'webclient/js/utils/utilities.js;' + 'webclient/js/io/directive.js;' + 'webclient/js/io/door.js;' + 'webclient/js/io/dormant.js;' + 'webclient/js/io/dormantramdisc.js;' + 'webclient/js/io/door_system.js;' + 'webclient/js/io/module.js;' + 'webclient/js/io/file.js;' + 'webclient/js/io/progress.js;' + 'webclient/js/io/friendnetwork.js;' + 'webclient/js/io/friendnetworkshare.js;' + 'webclient/js/io/friendnetworkfriends.js;' + 'webclient/js/io/friendnetworkdrive.js;' + 'webclient/js/io/friendnetworkpower.js;' + 'webclient/js/io/friendnetworkextension.js;' + 'webclient/js/io/friendnetworkdoor.js;' + 'webclient/js/io/friendnetworkapps.js;' + 'webclient/js/io/workspace_fileoperations.js;' + 'webclient/js/io/DOS.js;' + 'webclient/3rdparty/favico.js/favico-0.3.10.min.js;' + 'webclient/js/gui/widget.js;' + 'webclient/js/gui/listview.js;' + 'webclient/js/gui/directoryview.js;' + 'webclient/js/io/directoryview_fileoperations.js;' + 'webclient/js/gui/menufactory.js;' + 'webclient/js/gui/workspace_menu.js;' + 'webclient/js/gui/tabletdashboard.js;' + 'webclient/js/gui/deepestfield.js;' + 'webclient/js/gui/filedialog.js;' + 'webclient/js/gui/printdialog.js;' + 'webclient/js/gui/desklet.js;' + 'webclient/js/gui/calendar.js;' + 'webclient/js/gui/colorpicker.js;' + 'webclient/js/gui/workspace_calendar.js;' + 'webclient/js/gui/workspace_tray.js;' + 'webclient/js/gui/workspace_sharing.js;' + 'webclient/js/gui/tutorial.js;' + 'webclient/js/media/audio.js;' + 'webclient/js/io/p2p.js;' + 'webclient/js/io/request.js;' + 'webclient/js/io/coreSocket.js;' + 'webclient/js/io/networkSocket.js;' + 'webclient/js/io/connection.js;' + 'webclient/js/friendmind.js;' + 'webclient/js/frienddos.js;' + 'webclient/js/oo.js;' + 'webclient/js/api/friendAPIv1_2.js'; s.onload = function() { // Start with expanding the workspace object // TODO: If we have sessionid - verify it through ajax. // TODO: This block is only for already initialized workspace if( _this.sessionId && _this.postInitialized ) { //console.log( 'This is the session.:', _this.sessionId ); if( callback && typeof( callback ) == 'function' ) callback( true ); return true; } if( !json || !json.sessionid ) { // console.log( 'Test2: Got in sessionid error.', json ); return false; } // Just get it done! function doInitInside() { InitWorkspaceNetwork(); // Reset some options if( ev && ev.shiftKey ) { _this.themeOverride = 'friendup12'; } if( GetUrlVar( 'interface' ) ) { switch( GetUrlVar( 'interface' ) ) { case 'native': _this.interfaceMode = 'native'; break; default: break; } } if( GetUrlVar( 'noleavealert' ) ) { _this.noLeaveAlert = true; } SetupWorkspaceData( json ); if( !_this.workspaceHasLoadedOnceBefore ) { Workspace.setLoading( true ); _this.workspaceHasLoadedOnceBefore = true; } // Lets load the stored window positions! LoadWindowStorage(); // Set up a shell instance for the workspace let uid = FriendDOS.addSession( _this ); _this.shell = FriendDOS.getSession( uid ); // We're getting the theme set in an url var let th = ''; if( ( th = GetUrlVar( 'theme' ) ) ) { _this.refreshTheme( th, false ); if( _this.loginPrompt ) { _this.loginPrompt.close(); _this.loginPrompt = false; } _this.init(); } // See if we have some theme settings else { // Check eula let m = new Module( 'system' ); m.onExecuted = function( e, d ) { let m = new Module( 'system' ); m.onExecuted = function( ee, dd ) { if( ee != 'ok' ) { if( dd ) { try { let js = JSON.parse( dd ); if( js.euladocument ) { Workspace.euladocument = js.euladocument; } } catch( e ){}; } ShowEula(); } afterEula( e ); } m.execute( 'checkeula' ); // When eula is displayed or not function afterEula( e ) { // Invites if( json.inviteHash ) { let inv = new Module( 'system' ); inv.onExecuted = function( err, dat ) { // TODO: Make some better error handling ... if( err != 'ok' ) console.log( '[ERROR] verifyinvite: ' + ( dat ? dat : err ) ); } inv.execute( 'verifyinvite', { hash: json.inviteHash } ); } if( e == 'ok' ) { let s = {}; try { s = JSON.parse( d ); } catch( e ) { s = {}; }; if( s && s.Theme && s.Theme.length ) { _this.refreshTheme( s.Theme.toLowerCase(), false ); } else { _this.refreshTheme( false, false ); } //console.log( 'We got a theme: ' + s.Theme ); _this.mimeTypes = s.Mimetypes; } else _this.refreshTheme( false, false ); if( _this.loginPrompt ) { _this.loginPrompt.close(); _this.loginPrompt = false; } _this.init(); } } m.execute( 'usersettings' ); } // Language _this.locale = 'en'; let l = new Module( 'system' ); l.onExecuted = function( e, d ) { // New translations i18n_translations = []; let decoded = false; try { decoded = JSON.parse( d ); } catch( e ) { //console.log( 'This: ', d ); } // Add it! i18nClearLocale(); if( e == 'ok' ) { if( decoded && typeof( decoded.locale ) != 'undefined' ) _this.locale = decoded.locale; //load english first and overwrite with localised values afterwards :) i18nAddPath( 'locale/en.locale', function(){ if( _this.locale != 'en' ) i18nAddPath( 'locale/' + _this.locale + '.locale' ); } ); } else { i18nAddPath( 'locale/en.locale' ); } try { if( decoded.response == 'Failed to load user.' ) { _this.logout(); } } catch( e ){}; // Current stored Friend version if( typeof( decoded.friendversion ) == 'undefined' || decoded.friendversion == 'undefined' ) { Workspace.friendVersion = false; } else { Workspace.friendVersion = decoded.friendversion; } if( callback && typeof( callback ) == 'function' ) callback(); Workspace.postInit(); } l.execute( 'getsetting', { settings: [ 'locale', 'friendversion' ] } ); } if( window.InitWorkspaceNetwork ) doInitInside(); else setTimeout( function(){ doInitInside(); }, 50 ); } document.body.appendChild( s ); } // We've already logged in else { setupWorkspaceData( json, function() { document.body.classList.remove( 'Login' ); document.body.classList.add( 'Inside' ); } ); if( callback && typeof( callback ) == 'function' ) callback(); return 1; } /* done here. workspace is shown. */ }, //set an additional URL to call on logout setLogoutURL: function( logoutURL ) { if( logoutURL ) Workspace.logoutURL = logoutURL; } }; window.onoffline = function() { Friend.User.SetUserConnectionState( 'offline' ); } window.ononline = function() { Friend.User.CheckServerConnection(); } function initBrowser() { if( !document.body ) return setTimeout( initBrowser, 100 ); if( navigator.userAgent.indexOf( 'Safari' ) > 0 ) { document.body.classList.add( 'Safari' ); } } initBrowser(); /*©agpl************************************************************************* * * * This file is part of FRIEND UNIFYING PLATFORM. * * Copyright (c) Friend Software Labs AS. All rights reserved. * * * * Licensed under the Source EULA. Please refer to the copy of the GNU Affero * * General Public License, found in the file license_agpl.txt. * * * *****************************************************************************©*/ /******************************************************************************* * * * User object manages sessions, login and logout * * * *******************************************************************************/ Friend = window.Friend || {}; Friend.User = { // Vars -------------------------------------------------------------------- State: 'online', // online, offline, login ServerIsThere: true, Username: '', // Holds the user's username AccessToken: null, // Holds the user's access token ConnectionAttempts: 0, // How many relogin attempts were made // Methods ----------------------------------------------------------------- // Log into Friend Core Login: function( username, password, remember, callback, event, flags ) { this.State = 'login'; if( !event ) event = window.event; let self = this; // Close conn here - new login regenerates sessionid if( Workspace.conn ) { try { Workspace.conn.ws.close(); } catch( e ) { console.log( 'Could not close conn.' ); } delete Workspace.conn; Workspace.conn = null; } if( username && password ) { Workspace.encryption.setKeys( username, password ); if( flags && flags.hashedPassword ) { //console.log( 'Sending login with hashed password.' ); this.SendLoginCall( { username: username, password: password, remember: remember, hashedPassword: flags.hashedPassword, inviteHash: flags.inviteHash }, callback ); } else { //console.log( 'Sending login with unhashed password' ); this.SendLoginCall( { username: username, password: password, remember: remember, hashedPassword: false, inviteHash: flags && flags.inviteHash ? flags.inviteHash : false }, callback ); } } // Relogin - as we do have an unflushed login else if( Workspace.sessionId ) { return this.ReLogin(); } else { Workspace.showLoginPrompt(); } return 0; }, // Login using a session id LoginWithSessionId: function( sessionid, callback, event ) { if( this.State == 'online' ) return; this.State = 'login'; if( !event ) event = window.event; let self = this; // Close conn here - new login regenerates sessionid if( Workspace.conn ) { try { Workspace.conn.ws.cleanup(); } catch( e ) { console.log( 'Could not close conn.' ); } delete Workspace.conn; } if( sessionid ) { this.SendLoginCall( { sessionid: sessionid }, callback ); } else { Workspace.showLoginPrompt(); } return 0; }, // Send the actual login call SendLoginCall: function( info, callback ) { // Already logging in this.State = 'login'; if( this.lastLogin && this.lastLogin.currentRequest ) { this.lastLogin.currentRequest.destroy(); } let self = this; // Create a new library call object let m = new FriendLibrary( 'system' ); this.lastLogin = m; if( info.username && info.password ) { Workspace.sessionId = ''; if( window.Workspace && !Workspace.originalLogin ) { Workspace.originalLogin = info.password; } // TODO: Fix hash detector by making sure hashing doesn't occur without hashedPassword flag set. let hashDetector = info.password.length > 20 && info.password.substr( 0, 6 ) == 'HASHED' ? true : false; if( !info.hashedPassword && hashDetector ) info.hashedPassword = true; let hashed = info.hashedPassword ? info.password : ( 'HASHED' + Sha256.hash( info.password ) ); m.addVar( 'username', info.username ); m.addVar( 'password', hashed ); try { let enc = parent.Workspace.encryption; //console.log( 'Encrypting password into Workspace.loginPassword: ' + info.password ); parent.Workspace.loginPassword = enc.encrypt( info.password, enc.getKeys().publickey ); parent.Workspace.loginHashed = hashed; } catch( e ) { let enc = Workspace.encryption; //console.log( 'Encrypting(2) password into Workspace.loginPassword: ' + info.password ); Workspace.loginPassword = enc.encrypt( info.password, enc.getKeys().publickey ); Workspace.loginHashed = hashed; } } else if( info.sessionid ) { m.addVar( 'sessionid', info.sessionid ); } else { this.State = 'offline'; this.lastLogin = null; return false; } m.addVar( 'deviceid', GetDeviceId() ); m.onExecuted = function( json, serveranswer ) { Friend.User.lastLogin = null; // We got a real error if( json == null ) { return Friend.User.ReLogin(); } try { let enc = Workspace.encryption; if( json.username || json.loginid ) { Workspace.sessionId = json.sessionid; if( json.username ) Workspace.loginUsername = json.username; Workspace.loginUserId = json.userid; Workspace.loginid = json.loginid; Workspace.userLevel = json.level; Workspace.fullName = json.fullname; // If we have inviteHash, verify and add relationship between the inviter and the invitee. if( info.inviteHash ) json.inviteHash = info.inviteHash; // We are now online! Friend.User.SetUserConnectionState( 'online' ); if( !Workspace.userWorkspaceInitialized ) { // Init workspace Workspace.initUserWorkspace( json, ( callback && typeof( callback ) == 'function' ? callback( true, serveranswer ) : false ), event ); } else { if( typeof( callback ) == 'function' ) callback( true, serveranswer ); // Make sure we didn't lose websocket! } // Remember login info for next login // But removed for security // TODO: Figure out a better way! if( info.remember ) { // Nothing } } else { Friend.User.SetUserConnectionState( 'offline' ); if( typeof( callback ) == 'function' ) callback( false, serveranswer ); } } catch( e ) { console.log( 'Failed to understand server response.', e ); if( callback ) callback( false, serveranswer ); }; } m.forceHTTP = true; m.forceSend = true; m.loginCall = true; m.execute( 'login' ); }, // When session times out, use log in again... ReLogin: function( callback ) { if( this.lastLogin ) return false; this.State = 'login'; if( !event ) event = window.event; let self = this; let info = {}; if( Workspace.loginUsername && Workspace.loginPassword ) { //console.log( 'Trying to log in with: ' + Workspace.loginUsername + ' AND ' + Workspace.loginPassword ); info.username = Workspace.loginUsername; let enc = Workspace.encryption; info.password = enc.decrypt( Workspace.loginPassword, enc.getKeys().privatekey ); //console.log( 'Unhashed, decrypted password (Workspace.loginPassword): ' + info.password ); info.hashedPassword = false; } else if( Workspace.sessionId ) { info.sessionid = Workspace.sessionId; } // Close conn here - new login regenerates sessionid if( Workspace.conn ) { try { Workspace.conn.ws.cleanup(); } catch( e ) { console.log( 'Could not close conn.' ); } delete Workspace.conn; Workspace.conn = null; } // Reset cajax http connections (because we lost connection) _cajax_http_connections = 0; if( info.username || info.sessionid ) { this.SendLoginCall( info, callback, 'relogin' ); } else { Workspace.showLoginPrompt(); } return 0; }, // Log out Logout: function( cbk ) { if( !cbk ) cbk = false; if( GetCookie( 'remcreds' ) ) { DelCookie( 'remcreds' ); } // FIXME: Remove this - it is not used anymore window.localStorage.removeItem( 'WorkspaceUsername' ); window.localStorage.removeItem( 'WorkspacePassword' ); window.localStorage.removeItem( 'WorkspaceSessionID' ); Workspace.loginUsername = null; Workspace.loginPassword = null; let keys = parent.ApplicationStorage.load( { applicationName : 'Workspace' } ); if( keys ) { keys.username = ''; parent.ApplicationStorage.save( keys, { applicationName : 'Workspace' } ); } let dologt = null; SaveWindowStorage( function() { if( dologt != null ) clearTimeout( dologt ); if( !cbk ) { // Do external logout and then our internal one. if( Workspace.logoutURL ) { Workspace.externalLogout(); return; } } if( typeof friendApp != 'undefined' && typeof friendApp.get_app_token == 'function' ) { let ud = new cAjax(); //ud.open( 'get', '/system.library/mobile/deleteuma/?sessionid=' + Workspace.sessionId + '&token=' + window.Base64alt.encode( friendApp.get_app_token() ) , true ); ud.open( 'get', '/system.library/mobile/deleteuma/?sessionid=' + Workspace.sessionId + '&token=' + friendApp.get_app_token() , true ); // ud.forceHTTP = true; ud.onload = function( lmdata ) { console.log('DeleteUma finished: ' + lmdata ); let m = new cAjax(); m.open( 'get', '/system.library/user/logout/?sessionid=' + Workspace.sessionId, true ); m.forceHTTP = true; m.send(); if( !cbk ) { setTimeout( doLogout, 500 ); } else { if( Workspace.conn ) { try { Workspace.conn.ws.close(); } catch( e ) { console.log( 'Could not close conn.' ); } delete Workspace.conn; Workspace.conn = null; } Workspace.sessionId = ''; cbk(); } }; ud.send(); } else { let m = new cAjax(); m.open( 'get', '/system.library/user/logout/?sessionid=' + Workspace.sessionId, true ); m.forceHTTP = true; m.send(); if( !cbk ) { setTimeout( doLogout, 500 ); } else { if( Workspace.conn ) { try { Workspace.conn.ws.close(); } catch( e ) { console.log( 'Could not close conn.' ); } delete Workspace.conn; Workspace.conn = null; } Workspace.sessionId = ''; cbk(); } } } ); // Could be there will be no connection.. function doLogout() { if( typeof friendApp != 'undefined' && typeof friendApp.exit == 'function') { friendApp.exit(); return; } Workspace.sessionId = ''; document.location.href = window.location.href.split( '?' )[0].split( '#' )[0]; //document.location.reload(); } if( !cbk ) { dologt = setTimeout( doLogout, 750 ); } return true; }, // Remember keys RememberKeys: function() { if( Workspace.encryption.keys.client ) { ApplicationStorage.save( { privatekey : Workspace.encryption.keys.client.privatekey, publickey : Workspace.encryption.keys.client.publickey, recoverykey: Workspace.encryption.keys.client.recoverykey }, { applicationName: 'Workspace' } ); if( window.ScreenOverlay ) ScreenOverlay.addDebug( 'Keys remembered' ); return true; } return false; }, // Renews session ids for cajax and executes ajax queue! RenewAllSessionIds: function( session ) { if( session ) Workspace.sessionId = session; // Reset this in this case _cajax_http_connections = 0; // Check if there's a queue of objects waiting to run if( Friend.cajax && Friend.cajax.length ) { for( var a = 0; a < Friend.cajax.length; a++ ) { Friend.cajax[a].send(); } Friend.cajax = []; } }, // Reset the password ResetPassword: function( username, callback ) { var passWordResetURL = '/forgotpassword/username/' + encodeURIComponent( username ); var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { if(typeof callback == 'function') callback( this.responseText ); } }; xhttp.open( 'GET', passWordResetURL, true); xhttp.send(); }, // Flush previous session FlushSession: function() { // Clear Workspace session Workspace.sessionId = ''; }, // Initialize this object Init: function() { this.ServerIsThere = true; }, CheckServerNow: function() { this.CheckServerConnection(); }, // Check if the server is alive CheckServerConnection: function( useAjax ) { if( Workspace && Workspace.loginPrompt ) return; if( typeof( Library ) == 'undefined' ) return; if( typeof( MD5 ) == 'undefined' ) return; let exf = function() { Friend.User.serverCheck = null; if( Friend.User.State != 'offline' ) { let checkTimeo = setTimeout( function() { Friend.User.SetUserConnectionState( 'offline' ); }, 1500 ); let serverCheck = new Library( 'system' ); serverCheck.onExecuted = function( q, s ) { // Dont need this now clearTimeout( checkTimeo ); // Check missing session let missSess = ( s && s.indexOf( 'sessionid or authid parameter is missing' ) > 0 ); if( !missSess && ( s && s.indexOf( 'User session not found' ) > 0 ) ) missSess = true; if( !missSess && q == null && s == null ) missSess = true; if( ( q == 'fail' && !s ) || ( !q && !s ) || ( q == 'error' && !s ) || missSess ) { if( missSess ) { Friend.User.ReLogin(); } Friend.User.SetUserConnectionState( 'offline' ); } else { if( !Friend.User.ServerIsThere ) { Friend.User.SetUserConnectionState( 'online', true ); } Friend.User.ConnectionAttempts = 0; } }; if( !useAjax ) serverCheck.forceHTTP = true; serverCheck.forceSend = true; try { // Cancel previous call if it's still in pipe if( Friend.User.serverCheck && Friend.User.serverCheck.currentRequest ) { Friend.User.serverCheck.currentRequest.destroy(); } serverCheck.execute( 'validate' ); Friend.User.serverCheck = serverCheck; } catch( e ) { Friend.User.SetUserConnectionState( 'offline' ); } } else { Friend.User.ReLogin(); } }; // Check now! exf(); }, // Set the user state (offline / online etc) SetUserConnectionState: function( mode, force ) { if( mode == 'offline' ) { if( this.State != 'offline' ) { Workspace.workspaceIsDisconnected = true; document.body.classList.add( 'Offline' ); if( Workspace.screen ) Workspace.screen.displayOfflineMessage(); Workspace.workspaceIsDisconnected = true; if( Workspace.nudgeWorkspacesWidget ) Workspace.nudgeWorkspacesWidget(); if( this.checkInterval ) clearInterval( this.checkInterval ); this.checkInterval = setInterval( 'Friend.User.CheckServerConnection()', 2500 ); // Try to close the websocket if( Workspace.conn && Workspace.conn.ws ) { try { Workspace.conn.ws.close(); } catch( e ) { console.log( 'Could not close conn.' ); } if( Workspace.conn && Workspace.conn.ws ) { delete Workspace.conn.ws; Workspace.conn.ws = null; } delete Workspace.conn; Workspace.conn = null; } // Remove dirlisting cache! if( window.DoorCache ) { DoorCache.dirListing = {}; } } this.ServerIsThere = false; this.State = 'offline'; } else if( mode == 'online' ) { // We're online again if( this.checkInterval ) { clearInterval( this.checkInterval ); this.checkInterval = null; } if( this.State != 'online' || force || !Workspace.conn ) { this.ServerIsThere = true; this.State = 'online'; document.body.classList.remove( 'Offline' ); if( Workspace.screen ) Workspace.screen.hideOfflineMessage(); Workspace.workspaceIsDisconnected = false; if( Workspace.nudgeWorkspacesWidget ) Workspace.nudgeWorkspacesWidget(); // Just remove this by force document.body.classList.remove( 'Busy' ); // Just refresh it if( Workspace.refreshDesktop ) Workspace.refreshDesktop( true, false ); // Try to reboot the websocket if( ( !Workspace.conn || Workspace.conn == null ) && Workspace.initWebSocket ) { Workspace.initWebSocket(); } else if( !Workspace.initWebSocket ) { return setTimeout( function(){ Friend.User.SetUserConnectionState( mode, force ); }, 25 ); } else { console.log( 'We have a kind of conn: ', Workspace.conn, Workspace.conn ? Workspace.conn.ws : false ); } // Clear execution queue _executionQueue = {}; } } else { this.State = mode; } } };
' + headers[a] + '
' + entries[a][b] + '