/*! * Webogram v0.3.2 - 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('FileManager', function ($window, $q, $timeout) { $window.URL = $window.URL || $window.webkitURL; $window.BlobBuilder = $window.BlobBuilder || $window.WebKitBlobBuilder || $window.MozBlobBuilder; 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 blobConstruct (blobParts, mimeType) { var blob; try { blob = new Blob(blobParts, {type: mimeType}); } catch (e) { var bb = new BlobBuilder; angular.forEach(blobParts, function(blobPart) { bb.append(blobPart); }); blob = bb.getBlob(mimeType); } return blob; } 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 downloadFile (url, mimeType, fileName) { // if (Config.Mobile) { // window.open(url, '_blank'); // return; // } var anchor = $('Download') .css({position: 'absolute', top: 1, left: 1}) .attr('href', url) .attr('target', '_blank') .attr('download', fileName) .appendTo('body'); anchor[0].dataset.downloadurl = [mimeType, fileName, url].join(':'); anchor[0].click(); $timeout(function () { anchor.remove(); }, 100); } return { isAvailable: isBlobAvailable, copy: fileCopyTo, write: fileWriteData, getFileWriter: getFileWriter, getFakeFileWriter: getFakeFileWriter, chooseSave: chooseSaveFile, getUrl: getUrl, 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', dbStoreName = 'files', dbVersion = 1, openDbPromise, storageIsAvailable = $window.indexedDB !== undefined && $window.IDBTransaction !== undefined; 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) { try { var deferred = $q.defer(), objectStore = db.transaction([dbStoreName], IDBTransaction.READ_WRITE || 'readwrite').objectStore(dbStoreName), request = objectStore.put(blob, fileName); } catch (error) { storageIsAvailable = false; return $q.reject(error); } 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) { if (event.target.result === undefined) { deferred.reject(); } else { deferred.resolve(event.target.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 = 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 (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('AppRuntimeManager', function ($window) { return { reload: function () { try { location.reload(); } catch (e) {}; if ($window.chrome && chrome.runtime && chrome.runtime.reload) { chrome.runtime.reload(); }; }, 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(); } } } })