/*! * Webogram v0.3.8 - messaging web application for MTProto * https://github.com/zhukov/webogram * Copyright (C) 2014 Igor Zhukov * https://github.com/zhukov/webogram/blob/master/LICENSE */ angular.module('izhukov.utils', []) .provider('Storage', function () { this.setPrefix = function (newPrefix) { ConfigStorage.prefix(newPrefix); }; this.$get = ['$q', function ($q) { var methods = {}; angular.forEach(['get', 'set', 'remove'], function (methodName) { methods[methodName] = function () { var deferred = $q.defer(), args = Array.prototype.slice.call(arguments); args.push(function (result) { deferred.resolve(result); }); ConfigStorage[methodName].apply(ConfigStorage, args); return deferred.promise; }; }); return methods; }]; }) .service('qSync', function () { return { when: function (result) { return {then: function (cb) { return cb(result); }}; } } }) .service('FileManager', function ($window, $q, $timeout, qSync) { $window.URL = $window.URL || $window.webkitURL; $window.BlobBuilder = $window.BlobBuilder || $window.WebKitBlobBuilder || $window.MozBlobBuilder; var buggyUnknownBlob = navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1; var blobSupported = true; try { blobConstruct([], ''); } catch (e) { blobSupported = false; } function isBlobAvailable () { return blobSupported; } function fileCopyTo (fromFileEntry, toFileEntry) { return getFileWriter(toFileEntry).then(function (fileWriter) { return fileWriteData(fileWriter, fromFileEntry).then(function () { return fileWriter; }, function (error) { return $q.reject(error); fileWriter.truncate(0); }); }); } function fileWriteData(fileWriter, bytes) { var deferred = $q.defer(); fileWriter.onwriteend = function(e) { deferred.resolve(); }; fileWriter.onerror = function (e) { deferred.reject(e); }; if (bytes.file) { bytes.file(function (file) { fileWriter.write(file); }, function (error) { deferred.reject(error); }) } else if (bytes instanceof Blob) { // is file bytes fileWriter.write(bytes); } else { try { var blob = blobConstruct([bytesToArrayBuffer(bytes)]); fileWriter.write(blob); } catch (e) { deferred.reject(e); } } return deferred.promise; } function chooseSaveFile (fileName, ext, mimeType) { if (!$window.chrome || !chrome.fileSystem || !chrome.fileSystem.chooseEntry) { return $q.reject(); }; var deferred = $q.defer(); chrome.fileSystem.chooseEntry({ type: 'saveFile', suggestedName: fileName, accepts: [{ mimeTypes: [mimeType], extensions: [ext] }] }, function (writableFileEntry) { deferred.resolve(writableFileEntry); }); return deferred.promise; } function getFileWriter (fileEntry) { var deferred = $q.defer(); fileEntry.createWriter(function (fileWriter) { deferred.resolve(fileWriter); }, function (error) { deferred.reject(error); }); return deferred.promise; } function getFakeFileWriter (mimeType, saveFileCallback) { var blobParts = [], fakeFileWriter = { write: function (blob) { if (!blobSupported) { if (fakeFileWriter.onerror) { fakeFileWriter.onerror(new Error('Blob not supported by browser')); } return false; } blobParts.push(blob); setZeroTimeout(function () { if (fakeFileWriter.onwriteend) { fakeFileWriter.onwriteend(); } }); }, truncate: function () { blobParts = []; }, finalize: function () { var blob = blobConstruct(blobParts, mimeType); if (saveFileCallback) { saveFileCallback(blob); } return blob; } }; return fakeFileWriter; }; function getUrl (fileData, mimeType) { // console.log(dT(), 'get url', fileData, mimeType, fileData.toURL !== undefined, fileData instanceof Blob); if (fileData.toURL !== undefined) { return fileData.toURL(mimeType); } if (fileData instanceof Blob) { return URL.createObjectURL(fileData); } return 'data:' + mimeType + ';base64,' + bytesToBase64(fileData); } function getDataUrl(blob) { var deferred; try { var reader = new FileReader(); reader.onloadend = function() { deferred.resolve(reader.result); } reader.readAsDataURL(blob); } catch (e) { return $q.reject(e); } deferred = $q.defer(); return deferred.promise; } function getFileCorrectUrl(blob, mimeType) { if (buggyUnknownBlob && blob instanceof Blob) { var mimeType = blob.type || blob.mimeType || mimeType || ''; if (!mimeType.match(/image\/(jpeg|gif|png|bmp)|video\/quicktime/)) { return getDataUrl(blob); } } return qSync.when(getUrl(blob, mimeType)); } function downloadFile (blob, mimeType, fileName) { if (window.navigator && navigator.msSaveBlob !== undefined) { window.navigator.msSaveBlob(blob, fileName); return false; } if (window.navigator && navigator.getDeviceStorage) { var storageName = 'sdcard'; switch (mimeType.split('/')[0]) { case 'video': storageName = 'videos'; break; case 'audio': storageName = 'music'; break; case 'image': storageName = 'pictures'; break; } var deviceStorage = navigator.getDeviceStorage(storageName); var request = deviceStorage.addNamed(blob, fileName); request.onsuccess = function () { console.log('Device storage save result', this.result); }; request.onerror = function () { }; return; } getFileCorrectUrl(blob, mimeType).then(function (url) { var anchor = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); anchor.href = url; anchor.target = '_blank'; anchor.download = fileName; if (anchor.dataset) { anchor.dataset.downloadurl = ["video/quicktime", fileName, url].join(':'); } $(anchor).css({position: 'absolute', top: 1, left: 1}).appendTo('body'); try { var clickEvent = document.createEvent('MouseEvents'); clickEvent.initMouseEvent( 'click', true, false, window, 0, 0, 0, 0, 0 , false, false, false, false, 0, null ); anchor.dispatchEvent(clickEvent); } catch (e) { console.error('Download click error', e); try { console.error('Download click error', e); anchor[0].click(); } catch (e) { window.open(url, '_blank'); } } $timeout(function () { $(anchor).remove(); }, 100); }); } return { isAvailable: isBlobAvailable, copy: fileCopyTo, write: fileWriteData, getFileWriter: getFileWriter, getFakeFileWriter: getFakeFileWriter, chooseSave: chooseSaveFile, getUrl: getUrl, getDataUrl: getDataUrl, getFileCorrectUrl: getFileCorrectUrl, download: downloadFile }; }) .service('IdbFileStorage', function ($q, $window, FileManager) { $window.indexedDB = $window.indexedDB || $window.webkitIndexedDB || $window.mozIndexedDB || $window.OIndexedDB || $window.msIndexedDB; $window.IDBTransaction = $window.IDBTransaction || $window.webkitIDBTransaction || $window.OIDBTransaction || $window.msIDBTransaction; var dbName = 'cachedFiles'; var dbStoreName = 'files'; var dbVersion = 1; var openDbPromise; var storageIsAvailable = $window.indexedDB !== undefined && $window.IDBTransaction !== undefined && navigator.userAgent.indexOf('Safari') == -1; // As of Safari 8.0 IndexedDB is REALLY slow, no point in it var storeBlobsAvailable = storageIsAvailable || false; function isAvailable () { return storageIsAvailable; } function openDatabase() { if (openDbPromise) { return openDbPromise; } try { var request = indexedDB.open(dbName, dbVersion), deferred = $q.defer(), createObjectStore = function (db) { db.createObjectStore(dbStoreName); }; if (!request) { throw new Exception(); } } catch (error) { storageIsAvailable = false; return $q.reject(error); } request.onsuccess = function (event) { db = request.result; db.onerror = function (error) { storageIsAvailable = false; console.error('Error creating/accessing IndexedDB database', error); deferred.reject(error); }; // Interim solution for Google Chrome to create an objectStore. Will be deprecated if (db.setVersion) { if (db.version != dbVersion) { db.setVersion(dbVersion).onsuccess = function () { createObjectStore(db); deferred.resolve(db); }; } else { deferred.resolve(db); } } else { deferred.resolve(db); } }; request.onerror = function (event) { storageIsAvailable = false; console.error('Error creating/accessing IndexedDB database', event); deferred.reject(event); } request.onupgradeneeded = function (event) { createObjectStore(event.target.result); }; return openDbPromise = deferred.promise; }; function saveFile (fileName, blob) { return openDatabase().then(function (db) { if (!storeBlobsAvailable) { return saveFileBase64(db, fileName, blob); } try { var objectStore = db.transaction([dbStoreName], IDBTransaction.READ_WRITE || 'readwrite').objectStore(dbStoreName), request = objectStore.put(blob, fileName); } catch (error) { if (storeBlobsAvailable) { storeBlobsAvailable = false; return saveFileBase64(db, fileName, blob); } storageIsAvailable = false; return $q.reject(error); } var deferred = $q.defer(); request.onsuccess = function (event) { deferred.resolve(blob); }; request.onerror = function (error) { deferred.reject(error); }; return deferred.promise; }); }; function saveFileBase64(db, fileName, blob) { try { var reader = new FileReader(); reader.readAsDataURL(blob); } catch (e) { storageIsAvailable = false; return $q.reject(); } var deferred = $q.defer(); reader.onloadend = function() { try { var objectStore = db.transaction([dbStoreName], IDBTransaction.READ_WRITE || 'readwrite').objectStore(dbStoreName), request = objectStore.put(reader.result, fileName); } catch (error) { storageIsAvailable = false; deferred.reject(error); return; }; request.onsuccess = function (event) { deferred.resolve(blob); }; request.onerror = function (error) { deferred.reject(error); }; } return deferred.promise; } function getFile (fileName) { return openDatabase().then(function (db) { var deferred = $q.defer(), objectStore = db.transaction([dbStoreName], IDBTransaction.READ || 'readonly').objectStore(dbStoreName), request = objectStore.get(fileName); request.onsuccess = function (event) { var result = event.target.result; if (result === undefined) { deferred.reject(); } else if (typeof result === 'string' && result.substr(0, 5) === 'data:') { deferred.resolve(dataUrlToBlob(result)); } else { deferred.resolve(result); } }; request.onerror = function (error) { deferred.reject(error); }; return deferred.promise; }); } function getFileWriter (fileName, mimeType) { var fakeWriter = FileManager.getFakeFileWriter(mimeType, function (blob) { saveFile(fileName, blob); }); return $q.when(fakeWriter); } openDatabase(); return { isAvailable: isAvailable, saveFile: saveFile, getFile: getFile, getFileWriter: getFileWriter }; }) .service('TmpfsFileStorage', function ($q, $window, FileManager) { $window.requestFileSystem = $window.requestFileSystem || $window.webkitRequestFileSystem; var reqFsPromise, fileSystem, storageIsAvailable = $window.requestFileSystem !== undefined; function requestFS () { if (reqFsPromise) { return reqFsPromise; } if (!$window.requestFileSystem) { return reqFsPromise = $q.reject({type: 'FS_BROWSER_UNSUPPORTED', description: 'requestFileSystem not present'}); } var deferred = $q.defer(); $window.requestFileSystem($window.TEMPORARY, 500 * 1024 * 1024, function (fs) { cachedFs = fs; deferred.resolve(); }, function (e) { storageIsAvailable = false; deferred.reject(e); }); return reqFsPromise = deferred.promise; }; function isAvailable () { return storageIsAvailable; } function getFile (fileName, size) { size = size || 1; return requestFS().then(function () { // console.log(dT(), 'get file', fileName); var deferred = $q.defer(); cachedFs.root.getFile(fileName, {create: false}, function(fileEntry) { fileEntry.file(function(file) { // console.log(dT(), 'aa', file); if (file.size >= size) { deferred.resolve(fileEntry); } else { deferred.reject(new Error('FILE_NOT_FOUND')); } }, function (error) { console.log(dT(), 'error', error); deferred.reject(error); }); }, function () { deferred.reject(new Error('FILE_NOT_FOUND')); }); return deferred.promise; }); } function saveFile (fileName, blob) { return getFileWriter(fileName).then(function (fileWriter) { return FileManager.write(fileWriter, blob).then(function () { return fileWriter.finalize(); }) }); } function getFileWriter (fileName) { // console.log(dT(), 'get file writer', fileName); return requestFS().then(function () { var deferred = $q.defer(); cachedFs.root.getFile(fileName, {create: true}, function (fileEntry) { FileManager.getFileWriter(fileEntry).then(function (fileWriter) { fileWriter.finalize = function () { return fileEntry; } deferred.resolve(fileWriter); }, function (error) { storageIsAvailable = false; deferred.reject(error); }); }, function (error) { storageIsAvailable = false; deferred.reject(error); }); return deferred.promise; }) } requestFS(); return { isAvailable: isAvailable, saveFile: saveFile, getFile: getFile, getFileWriter: getFileWriter }; }) .service('MemoryFileStorage', function ($q, FileManager) { var storage = {}; function isAvailable () { return true; } function getFile (fileName, size) { if (storage[fileName]) { return $q.when(storage[fileName]); } return $q.reject(new Error('FILE_NOT_FOUND')); } function saveFile (fileName, blob) { return $q.when(storage[fileName] = blob); } function getFileWriter (fileName, mimeType) { var fakeWriter = FileManager.getFakeFileWriter(mimeType, function (blob) { saveFile(fileName, blob); }); return $q.when(fakeWriter); } return { isAvailable: isAvailable, saveFile: saveFile, getFile: getFile, getFileWriter: getFileWriter }; }) .service('CryptoWorker', function ($timeout, $q) { var webWorker = false, naClEmbed = false, taskID = 0, awaiting = {}, webCrypto = Config.Modes.webcrypto && window.crypto && (window.crypto.subtle || window.crypto.webkitSubtle)/* || window.msCrypto && window.msCrypto.subtle*/, useSha1Crypto = webCrypto && webCrypto.digest !== undefined, finalizeTask = function (taskID, result) { var deferred = awaiting[taskID]; if (deferred !== undefined) { // console.log(dT(), 'CW done'); deferred.resolve(result); delete awaiting[taskID]; } }; if (Config.Modes.nacl && navigator.mimeTypes && navigator.mimeTypes['application/x-pnacl'] !== undefined) { var listener = $('
').appendTo($('body'))[0]; listener.addEventListener('load', function (e) { naClEmbed = listener.firstChild; console.log(dT(), 'NaCl ready'); }, true); listener.addEventListener('message', function (e) { finalizeTask(e.data.taskID, e.data.result); }, true); listener.addEventListener('error', function (e) { console.error('NaCl error', e); }, true); } if (window.Worker) { var tmpWorker = new Worker('js/lib/crypto_worker.js'); tmpWorker.onmessage = function (e) { if (!webWorker) { webWorker = tmpWorker; } else { finalizeTask(e.data.taskID, e.data.result); } }; tmpWorker.onerror = function(error) { console.error('CW error', error, error.stack); webWorker = false; }; } function performTaskWorker (task, params, embed) { // console.log(dT(), 'CW start', task); var deferred = $q.defer(); awaiting[taskID] = deferred; params.task = task; params.taskID = taskID; (embed || webWorker).postMessage(params); taskID++; return deferred.promise; } return { sha1Hash: function (bytes) { if (useSha1Crypto) { // We don't use buffer since typedArray.subarray(...).buffer gives the whole buffer and not sliced one. webCrypto.digest supports typed array var deferred = $q.defer(), bytesTyped = Array.isArray(bytes) ? convertToUint8Array(bytes) : bytes; // console.log(dT(), 'Native sha1 start'); webCrypto.digest({name: 'SHA-1'}, bytesTyped).then(function (digest) { // console.log(dT(), 'Native sha1 done'); deferred.resolve(digest); }, function (e) { console.error('Crypto digest error', e); useSha1Crypto = false; deferred.resolve(sha1HashSync(bytes)); }); return deferred.promise; } return $timeout(function () { return sha1HashSync(bytes); }); }, aesEncrypt: function (bytes, keyBytes, ivBytes) { if (naClEmbed) { return performTaskWorker('aes-encrypt', { bytes: addPadding(convertToArrayBuffer(bytes)), keyBytes: convertToArrayBuffer(keyBytes), ivBytes: convertToArrayBuffer(ivBytes) }, naClEmbed); } return $timeout(function () { return convertToArrayBuffer(aesEncryptSync(bytes, keyBytes, ivBytes)); }); }, aesDecrypt: function (encryptedBytes, keyBytes, ivBytes) { if (naClEmbed) { return performTaskWorker('aes-decrypt', { encryptedBytes: addPadding(convertToArrayBuffer(encryptedBytes)), keyBytes: convertToArrayBuffer(keyBytes), ivBytes: convertToArrayBuffer(ivBytes) }, naClEmbed); } return $timeout(function () { return convertToArrayBuffer(aesDecryptSync(encryptedBytes, keyBytes, ivBytes)); }); }, factorize: function (bytes) { bytes = convertToByteArray(bytes); if (naClEmbed && bytes.length <= 8) { return performTaskWorker('factorize', {bytes: bytes}, naClEmbed); } if (webWorker) { return performTaskWorker('factorize', {bytes: bytes}); } return $timeout(function () { return pqPrimeFactorization(bytes); }); }, modPow: function (x, y, m) { if (webWorker) { return performTaskWorker('mod-pow', { x: x, y: y, m: m }); } return $timeout(function () { return bytesModPow(x, y, m); }); }, }; }) .service('SearchIndexManager', function () { var badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<\s]+/g, trimRe = /^\s+|\s$/g, accentsReplace = { a: /[åáâäà]/g, e: /[éêëè]/g, i: /[íîïì]/g, o: /[óôöò]/g, u: /[úûüù]/g, c: /ç/g, ss: /ß/g } return { createIndex: createIndex, indexObject: indexObject, cleanSearchText: cleanSearchText, search: search }; function createIndex () { return { shortIndexes: {}, fullTexts: {} } } function cleanSearchText (text) { text = text.replace(badCharsRe, ' ').replace(trimRe, '').toLowerCase(); for (var key in accentsReplace) { if (accentsReplace.hasOwnProperty(key)) { text = text.replace(accentsReplace[key], key); } } return text; } function indexObject (id, searchText, searchIndex) { if (searchIndex.fullTexts[id] !== undefined) { return false; } searchText = cleanSearchText(searchText); if (!searchText.length) { return false; } var shortIndexes = searchIndex.shortIndexes; searchIndex.fullTexts[id] = searchText; angular.forEach(searchText.split(' '), function(searchWord) { var len = Math.min(searchWord.length, 3), wordPart, i; for (i = 1; i <= len; i++) { wordPart = searchWord.substr(0, i); if (shortIndexes[wordPart] === undefined) { shortIndexes[wordPart] = [id]; } else { shortIndexes[wordPart].push(id); } } }); } function search (query, searchIndex) { var shortIndexes = searchIndex.shortIndexes, fullTexts = searchIndex.fullTexts; query = cleanSearchText(query); var queryWords = query.split(' '), foundObjs = false, newFoundObjs, i, j, searchText, found; for (i = 0; i < queryWords.length; i++) { newFoundObjs = shortIndexes[queryWords[i].substr(0, 3)]; if (!newFoundObjs) { foundObjs = []; break; } if (foundObjs === false || foundObjs.length > newFoundObjs.length) { foundObjs = newFoundObjs; } } newFoundObjs = {}; for (j = 0; j < foundObjs.length; j++) { found = true; searchText = fullTexts[foundObjs[j]]; for (i = 0; i < queryWords.length; i++) { if (searchText.indexOf(queryWords[i]) == -1) { found = false; break; } } if (found) { newFoundObjs[foundObjs[j]] = true; } } return newFoundObjs; } }) .service('ExternalResourcesManager', function ($q, $http) { var urlPromises = {}; function downloadImage (url) { if (urlPromises[url] !== undefined) { return urlPromises[url]; } return urlPromises[url] = $http.get(url, {responseType: 'blob', transformRequest: null}) .then(function (response) { window.URL = window.URL || window.webkitURL; return window.URL.createObjectURL(response.data); }); } return { downloadImage: downloadImage } }) .service('IdleManager', function ($rootScope, $window, $timeout) { $rootScope.idle = {isIDLE: false}; var toPromise, started = false; return { start: start }; function start () { if (!started) { started = true; $($window).on('blur focus keydown mousedown touchstart', onEvent); setTimeout(function () { onEvent({type: 'blur'}); }, 0); } } function onEvent (e) { // console.log('event', e.type); if (e.type == 'mousemove') { $($window).off('mousemove', onEvent); } var isIDLE = e.type == 'blur' || e.type == 'timeout' ? true : false; $timeout.cancel(toPromise); if (!isIDLE) { // console.log('update timeout'); toPromise = $timeout(function () { onEvent({type: 'timeout'}); }, 30000); } if ($rootScope.idle.isIDLE == isIDLE) { return; } // console.log('IDLE changed', isIDLE); $rootScope.$apply(function () { $rootScope.idle.isIDLE = isIDLE; }); if (isIDLE && e.type == 'timeout') { $($window).on('mousemove', onEvent); } } }) .service('AppRuntimeManager', function ($window) { return { reload: function () { try { location.reload(); } catch (e) {}; if ($window.chrome && chrome.runtime && chrome.runtime.reload) { chrome.runtime.reload(); }; }, close: function () { try { $window.close(); } catch (e) {} }, focus: function () { if (window.navigator.mozApps && document.hidden) { // Get app instance and launch it to bring app to foreground window.navigator.mozApps.getSelf().onsuccess = function() { this.result.launch(); }; } else { if (window.chrome && chrome.app && chrome.app.window) { chrome.app.window.current().focus(); } window.focus(); } } } })