diff --git a/app/index.html b/app/index.html
index ade74629..7b7b2dee 100644
--- a/app/index.html
+++ b/app/index.html
@@ -47,8 +47,12 @@
+
+
+
+
-
+
diff --git a/app/js/app.js b/app/js/app.js
index 2cbb4093..10992620 100644
--- a/app/js/app.js
+++ b/app/js/app.js
@@ -24,7 +24,9 @@ angular.module('myApp', [
'ngSanitize',
'ui.bootstrap',
'pasvaz.bindonce',
- 'mtproto.services',
+ 'izhukov.utils',
+ 'izhukov.mtproto',
+ 'izhukov.mtproto.wrapper',
'myApp.filters',
'myApp.services',
/*PRODUCTION_ONLY_BEGIN
@@ -33,7 +35,7 @@ angular.module('myApp', [
'myApp.directives',
'myApp.controllers'
]).
-config(['$locationProvider', '$routeProvider', '$compileProvider', function($locationProvider, $routeProvider, $compileProvider) {
+config(['$locationProvider', '$routeProvider', '$compileProvider', 'StorageProvider', function($locationProvider, $routeProvider, $compileProvider, StorageProvider) {
var icons = {}, reverseIcons = {}, i, j, hex, name, dataItem, row, column, totalColumns;
@@ -49,6 +51,10 @@ config(['$locationProvider', '$routeProvider', '$compileProvider', function($loc
}
}
+ if (Config.Modes.test) {
+ StorageProvider.setPrefix('t_');
+ }
+
$.emojiarea.spritesheetPath = 'img/emojisprite_!.png';
$.emojiarea.spritesheetDimens = Config.EmojiCategorySpritesheetDimens;
$.emojiarea.iconSize = 20;
@@ -59,7 +65,6 @@ config(['$locationProvider', '$routeProvider', '$compileProvider', function($loc
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|file|mailto|blob|filesystem|chrome-extension|app):|data:image\//);
- // $locationProvider.html5Mode(true);
$routeProvider.when('/', {templateUrl: 'partials/welcome.html', controller: 'AppWelcomeController'});
$routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'AppLoginController'});
$routeProvider.when('/im', {templateUrl: 'partials/im.html', controller: 'AppIMController', reloadOnSearch: false});
diff --git a/app/js/controllers.js b/app/js/controllers.js
index 226aa0e5..56b63e79 100644
--- a/app/js/controllers.js
+++ b/app/js/controllers.js
@@ -1125,7 +1125,7 @@ angular.module('myApp.controllers', [])
$scope.$on('user_update', angular.noop);
})
- .controller('AppImSendController', function ($scope, $timeout, MtpApiManager, AppConfigManager, AppPeersManager, AppMessagesManager, ApiUpdatesManager, MtpApiFileManager) {
+ .controller('AppImSendController', function ($scope, $timeout, MtpApiManager, Storage, AppPeersManager, AppMessagesManager, ApiUpdatesManager, MtpApiFileManager) {
$scope.$watch('curDialog.peer', resetDraft);
$scope.$on('user_update', angular.noop);
@@ -1180,7 +1180,7 @@ angular.module('myApp.controllers', [])
function resetDraft (newPeer) {
if (newPeer) {
- AppConfigManager.get('draft' + $scope.curDialog.peerID).then(function (draftText) {
+ Storage.get('draft' + $scope.curDialog.peerID).then(function (draftText) {
// console.log('Restore draft', 'draft' + $scope.curDialog.peerID, draftText);
$scope.draftMessage.text = draftText || '';
// console.log('send broadcast', $scope.draftMessage);
@@ -1204,10 +1204,10 @@ angular.module('myApp.controllers', [])
var backupDraftObj = {};
backupDraftObj['draft' + $scope.curDialog.peerID] = newVal;
- AppConfigManager.set(backupDraftObj);
+ Storage.set(backupDraftObj);
// console.log('draft save', backupDraftObj);
} else {
- AppConfigManager.remove('draft' + $scope.curDialog.peerID);
+ Storage.remove('draft' + $scope.curDialog.peerID);
// console.log('draft delete', 'draft' + $scope.curDialog.peerID);
}
}
@@ -1780,7 +1780,7 @@ angular.module('myApp.controllers', [])
})
- .controller('SettingsModalController', function ($rootScope, $scope, $timeout, $modal, AppUsersManager, AppChatsManager, AppPhotosManager, MtpApiManager, AppConfigManager, NotificationsManager, MtpApiFileManager, ApiUpdatesManager, ChangelogNotifyService, ErrorService) {
+ .controller('SettingsModalController', function ($rootScope, $scope, $timeout, $modal, AppUsersManager, AppChatsManager, AppPhotosManager, MtpApiManager, Storage, NotificationsManager, MtpApiFileManager, ApiUpdatesManager, ChangelogNotifyService, ErrorService) {
$scope.profile = {};
$scope.photo = {};
@@ -1876,7 +1876,7 @@ angular.module('myApp.controllers', [])
});
};
- AppConfigManager.get('notify_nodesktop', 'notify_nosound', 'send_ctrlenter', 'notify_volume').then(function (settings) {
+ Storage.get('notify_nodesktop', 'notify_nosound', 'send_ctrlenter', 'notify_volume').then(function (settings) {
$scope.notify.desktop = !settings[0];
$scope.send.enter = settings[2] ? '' : '1';
@@ -1904,8 +1904,8 @@ angular.module('myApp.controllers', [])
$scope.$watch('notify.volume', function (newValue, oldValue) {
if (newValue !== oldValue) {
var storeVolume = newValue / 10;
- AppConfigManager.set({notify_volume: storeVolume});
- AppConfigManager.remove('notify_nosound');
+ Storage.set({notify_volume: storeVolume});
+ Storage.remove('notify_nosound');
NotificationsManager.clear();
if (testSoundPromise) {
@@ -1921,9 +1921,9 @@ angular.module('myApp.controllers', [])
$scope.notify.desktop = !$scope.notify.desktop;
if ($scope.notify.desktop) {
- AppConfigManager.remove('notify_nodesktop');
+ Storage.remove('notify_nodesktop');
} else {
- AppConfigManager.set({notify_nodesktop: true});
+ Storage.set({notify_nodesktop: true});
}
}
@@ -1931,9 +1931,9 @@ angular.module('myApp.controllers', [])
$scope.send.enter = newValue;
if ($scope.send.enter) {
- AppConfigManager.remove('send_ctrlenter');
+ Storage.remove('send_ctrlenter');
} else {
- AppConfigManager.set({send_ctrlenter: true});
+ Storage.set({send_ctrlenter: true});
}
$rootScope.$broadcast('settings_changed');
}
@@ -1944,7 +1944,7 @@ angular.module('myApp.controllers', [])
}
})
- .controller('ProfileEditModalController', function ($rootScope, $scope, $timeout, $modal, $modalInstance, AppUsersManager, AppChatsManager, MtpApiManager, AppConfigManager, NotificationsManager, MtpApiFileManager, ApiUpdatesManager) {
+ .controller('ProfileEditModalController', function ($rootScope, $scope, $timeout, $modal, $modalInstance, AppUsersManager, AppChatsManager, MtpApiManager, Storage, NotificationsManager, MtpApiFileManager, ApiUpdatesManager) {
$scope.profile = {};
$scope.error = {};
diff --git a/app/js/directives.js b/app/js/directives.js
index 64c49af0..d17e8a66 100644
--- a/app/js/directives.js
+++ b/app/js/directives.js
@@ -609,7 +609,7 @@ angular.module('myApp.directives', ['myApp.filters'])
})
- .directive('mySendForm', function ($timeout, $modalStack, AppConfigManager, ErrorService) {
+ .directive('mySendForm', function ($timeout, $modalStack, Storage, ErrorService) {
return {
link: link,
@@ -665,7 +665,7 @@ angular.module('myApp.directives', ['myApp.filters'])
var sendOnEnter = true,
updateSendSettings = function () {
- AppConfigManager.get('send_ctrlenter').then(function (sendOnCtrl) {
+ Storage.get('send_ctrlenter').then(function (sendOnCtrl) {
sendOnEnter = !sendOnCtrl;
});
};
diff --git a/app/js/lib/bin_utils.js b/app/js/lib/bin_utils.js
new file mode 100644
index 00000000..945cc4db
--- /dev/null
+++ b/app/js/lib/bin_utils.js
@@ -0,0 +1,435 @@
+/*!
+ * Webogram v0.1.6 - messaging web application for MTProto
+ * https://github.com/zhukov/webogram
+ * Copyright (C) 2014 Igor Zhukov
+ * https://github.com/zhukov/webogram/blob/master/LICENSE
+ */
+
+function bigint (num) {
+ return new BigInteger(num.toString(16), 16);
+}
+
+function bigStringInt (strNum) {
+ return new BigInteger(strNum, 10);
+}
+
+function dHexDump (bytes) {
+ var arr = [];
+ for (var i = 0; i < bytes.length; i++) {
+ if (i && !(i % 2)) {
+ if (!(i % 16)) {
+ arr.push("\n");
+ } else if (!(i % 4)) {
+ arr.push(' ');
+ } else {
+ arr.push(' ');
+ }
+ }
+ arr.push((bytes[i] < 16 ? '0' : '') + bytes[i].toString(16));
+ }
+
+ console.log(arr.join(''));
+}
+
+function bytesToHex (bytes) {
+ bytes = bytes || [];
+ var arr = [];
+ for (var i = 0; i < bytes.length; i++) {
+ arr.push((bytes[i] < 16 ? '0' : '') + (bytes[i] || 0).toString(16));
+ }
+ return arr.join('');
+}
+
+function bytesFromHex (hexString) {
+ var len = hexString.length,
+ i,
+ bytes = [];
+
+ for (i = 0; i < len; i += 2) {
+ bytes.push(parseInt(hexString.substr(i, 2), 16));
+ }
+
+ return bytes;
+}
+
+function bytesToBase64 (bytes) {
+ var mod3, result = '';
+
+ for (var nLen = bytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) {
+ mod3 = nIdx % 3;
+ nUint24 |= bytes[nIdx] << (16 >>> mod3 & 24);
+ if (mod3 === 2 || nLen - nIdx === 1) {
+ result += String.fromCharCode(
+ uint6ToBase64(nUint24 >>> 18 & 63),
+ uint6ToBase64(nUint24 >>> 12 & 63),
+ uint6ToBase64(nUint24 >>> 6 & 63),
+ uint6ToBase64(nUint24 & 63)
+ );
+ nUint24 = 0;
+ }
+ }
+
+ return result.replace(/A(?=A$|$)/g, '=');
+}
+
+function uint6ToBase64 (nUint6) {
+ return nUint6 < 26
+ ? nUint6 + 65
+ : nUint6 < 52
+ ? nUint6 + 71
+ : nUint6 < 62
+ ? nUint6 - 4
+ : nUint6 === 62
+ ? 43
+ : nUint6 === 63
+ ? 47
+ : 65;
+}
+
+function bytesCmp (bytes1, bytes2) {
+ var len = bytes1.length;
+ if (len != bytes2.length) {
+ return false;
+ }
+
+ for (var i = 0; i < len; i++) {
+ if (bytes1[i] != bytes2[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function bytesXor (bytes1, bytes2) {
+ var len = bytes1.length,
+ bytes = [];
+
+ for (var i = 0; i < len; ++i) {
+ bytes[i] = bytes1[i] ^ bytes2[i];
+ }
+
+ return bytes;
+}
+
+function bytesToWords (bytes) {
+ var len = bytes.length,
+ words = [];
+
+ for (var i = 0; i < len; i++) {
+ words[i >>> 2] |= bytes[i] << (24 - (i % 4) * 8);
+ }
+
+ return new CryptoJS.lib.WordArray.init(words, len);
+}
+
+function bytesFromWords (wordArray) {
+ var words = wordArray.words,
+ sigBytes = wordArray.sigBytes,
+ bytes = [];
+
+ for (var i = 0; i < sigBytes; i++) {
+ bytes.push((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff);
+ }
+
+ return bytes;
+}
+
+function bytesFromBigInt (bigInt, len) {
+ var bytes = bigInt.toByteArray();
+
+ while (!bytes[0] && (!len || bytes.length > len)) {
+ bytes = bytes.slice(1);
+ }
+
+ return bytes;
+}
+
+function bytesToArrayBuffer (b) {
+ return (new Uint8Array(b)).buffer;
+}
+
+function bytesFromArrayBuffer (buffer) {
+ var len = buffer.byteLength,
+ byteView = new Uint8Array(buffer),
+ bytes = [];
+
+ for (var i = 0; i < len; ++i) {
+ bytes[i] = byteView[i];
+ }
+
+ return bytes;
+}
+
+function longToInts (sLong) {
+ var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000));
+
+ return [divRem[0].intValue(), divRem[1].intValue()];
+}
+
+function longToBytes (sLong) {
+ return bytesFromWords({words: longToInts(sLong), sigBytes: 8}).reverse();
+}
+
+function longFromInts (high, low) {
+ return bigint(high).shiftLeft(32).add(bigint(low)).toString(10);
+}
+
+function intToUint (val) {
+ val = parseInt(val);
+ if (val < 0) {
+ val = val + 4294967296;
+ }
+ return val;
+}
+
+function uintToInt (val) {
+ if (val > 2147483647) {
+ val = val - 4294967296;
+ }
+ return val;
+}
+
+function sha1Hash (bytes) {
+ // console.log('SHA-1 hash start');
+ var hashBytes = sha1.hash(bytes, true);
+ // console.log('SHA-1 hash finish');
+
+ return hashBytes;
+}
+
+
+
+function rsaEncrypt (publicKey, bytes) {
+ var needPadding = 255 - bytes.length;
+ if (needPadding > 0) {
+ var padding = new Array(needPadding);
+ (new SecureRandom()).nextBytes(padding);
+
+ bytes = bytes.concat(padding);
+ }
+
+ // console.log('RSA encrypt start');
+ var N = new BigInteger(publicKey.modulus, 16),
+ E = new BigInteger(publicKey.exponent, 16),
+ X = new BigInteger(bytes),
+ encryptedBigInt = X.modPowInt(E, N),
+ encryptedBytes = bytesFromBigInt(encryptedBigInt, 256);
+
+ // console.log('RSA encrypt finish');
+
+ return encryptedBytes;
+}
+
+function aesEncrypt (bytes, keyBytes, ivBytes) {
+ // console.log('AES encrypt start', bytes.length/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/);
+
+ var needPadding = 16 - (bytes.length % 16);
+ if (needPadding > 0 && needPadding < 16) {
+ var padding = new Array(needPadding);
+ (new SecureRandom()).nextBytes(padding);
+
+ bytes = bytes.concat(padding);
+ }
+
+ var encryptedWords = CryptoJS.AES.encrypt(bytesToWords(bytes), bytesToWords(keyBytes), {
+ iv: bytesToWords(ivBytes),
+ padding: CryptoJS.pad.NoPadding,
+ mode: CryptoJS.mode.IGE
+ }).ciphertext;
+
+ var encryptedBytes = bytesFromWords(encryptedWords);
+
+ // console.log('AES encrypt finish');
+
+ return encryptedBytes;
+}
+
+function aesDecrypt (encryptedBytes, keyBytes, ivBytes) {
+ // console.log('AES decrypt start', encryptedBytes.length/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/);
+
+ var decryptedWords = CryptoJS.AES.decrypt({ciphertext: bytesToWords(encryptedBytes)}, bytesToWords(keyBytes), {
+ iv: bytesToWords(ivBytes),
+ padding: CryptoJS.pad.NoPadding,
+ mode: CryptoJS.mode.IGE
+ });
+
+ var bytes = bytesFromWords(decryptedWords);
+
+ // console.log('AES decrypt finish');
+
+ return bytes;
+}
+
+function gzipUncompress (bytes) {
+ // console.log('Gzip uncompress start');
+ var result = (new Zlib.Gunzip(bytes)).decompress();
+ // console.log('Gzip uncompress finish');
+ return result;
+}
+
+function nextRandomInt (maxValue) {
+ return Math.floor(Math.random() * maxValue);
+};
+
+function pqPrimeFactorization (pqBytes) {
+ var what = new BigInteger(pqBytes),
+ result = false;
+
+ console.log('PQ start', pqBytes, what.bitLength());
+
+ if (what.bitLength() <= 64) {
+ // console.time('PQ long');
+ try {
+ result = pqPrimeLong(goog.math.Long.fromString(what.toString(16), 16));
+ } catch (e) {
+ console.error('Pq long Exception', e);
+ };
+ // console.timeEnd('PQ long');
+ }
+ // console.log(result);
+
+ if (result === false) {
+ // console.time('pq BigInt');
+ result = pqPrimeBigInteger(what);
+ // console.timeEnd('pq BigInt');
+ }
+
+ console.log('PQ finish');
+
+ return result;
+}
+
+function pqPrimeBigInteger (what) {
+ var it = 0,
+ g;
+ for (var i = 0; i < 3; i++) {
+ var q = (nextRandomInt(128) & 15) + 17,
+ x = bigint(nextRandomInt(1000000000) + 1),
+ y = x.clone(),
+ lim = 1 << (i + 18);
+
+ for (var j = 1; j < lim; j++) {
+ ++it;
+ var a = x.clone(),
+ b = x.clone(),
+ c = bigint(q);
+
+ while (!b.equals(BigInteger.ZERO)) {
+ if (!b.and(BigInteger.ONE).equals(BigInteger.ZERO)) {
+ c = c.add(a);
+ if (c.compareTo(what) > 0) {
+ c = c.subtract(what);
+ }
+ }
+ a = a.add(a);
+ if (a.compareTo(what) > 0) {
+ a = a.subtract(what);
+ }
+ b = b.shiftRight(1);
+ }
+
+ x = c.clone();
+ var z = x.compareTo(y) < 0 ? y.subtract(x) : x.subtract(y);
+ g = z.gcd(what);
+ if (!g.equals(BigInteger.ONE)) {
+ break;
+ }
+ if ((j & (j - 1)) == 0) {
+ y = x.clone();
+ }
+ }
+ if (g.compareTo(BigInteger.ONE) > 0) {
+ break;
+ }
+ }
+
+ var f = what.divide(g), P, Q;
+
+ if (g.compareTo(f) > 0) {
+ P = f;
+ Q = g;
+ } else {
+ P = g;
+ Q = f;
+ }
+
+ return [bytesFromBigInt(P), bytesFromBigInt(Q)];
+}
+
+function gcdLong(a, b) {
+ while (a.notEquals(goog.math.Long.ZERO) && b.notEquals(goog.math.Long.ZERO)) {
+ while (b.and(goog.math.Long.ONE).equals(goog.math.Long.ZERO)) {
+ b = b.shiftRight(1);
+ }
+ while (a.and(goog.math.Long.ONE).equals(goog.math.Long.ZERO)) {
+ a = a.shiftRight(1);
+ }
+ if (a.compare(b) > 0) {
+ a = a.subtract(b);
+ } else {
+ b = b.subtract(a);
+ }
+ }
+ return b.equals(goog.math.Long.ZERO) ? a : b;
+}
+
+function pqPrimeLong(what) {
+ // console.log('start long');
+ var it = 0,
+ g;
+ for (var i = 0; i < 3; i++) {
+ var q = goog.math.Long.fromInt((nextRandomInt(128) & 15) + 17),
+ x = goog.math.Long.fromInt(nextRandomInt(1000000000) + 1),
+ y = x,
+ lim = 1 << (i + 18);
+
+ for (var j = 1; j < lim; j++) {
+ ++it;
+ // if (!(it % 100)) {
+ // console.log(dT(), 'it', it, i, j, x.toString());
+ // }
+ var a = x,
+ b = x,
+ c = q;
+
+ while (b.notEquals(goog.math.Long.ZERO)) {
+ if (b.and(goog.math.Long.ONE).notEquals(goog.math.Long.ZERO)) {
+ c = c.add(a);
+ if (c.compare(what) > 0) {
+ c = c.subtract(what);
+ }
+ }
+ a = a.add(a);
+ if (a.compare(what) > 0) {
+ a = a.subtract(what);
+ }
+ b = b.shiftRight(1);
+ }
+
+ x = c;
+ var z = x.compare(y) < 0 ? y.subtract(x) : x.subtract(y);
+ g = gcdLong(z, what);
+ if (g.notEquals(goog.math.Long.ONE)) {
+ break;
+ }
+ if ((j & (j - 1)) == 0) {
+ y = x;
+ }
+ }
+ if (g.compare(goog.math.Long.ONE) > 0) {
+ break;
+ }
+ }
+
+ var f = what.div(g), P, Q;
+
+ if (g.compare(f) > 0) {
+ P = f;
+ Q = g;
+ } else {
+ P = g;
+ Q = f;
+ }
+
+ return [bytesFromHex(P.toString(16)), bytesFromHex(Q.toString(16))];
+}
diff --git a/app/js/lib/mtproto.js b/app/js/lib/mtproto.js
index 245a1da9..762ebade 100644
--- a/app/js/lib/mtproto.js
+++ b/app/js/lib/mtproto.js
@@ -5,1004 +5,9 @@
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
-function bigint (num) {
- return new BigInteger(num.toString(16), 16);
-}
-
-function bigStringInt (strNum) {
- return new BigInteger(strNum, 10);
-}
-
-function dHexDump (bytes) {
- var arr = [];
- for (var i = 0; i < bytes.length; i++) {
- if (i && !(i % 2)) {
- if (!(i % 16)) {
- arr.push("\n");
- } else if (!(i % 4)) {
- arr.push(' ');
- } else {
- arr.push(' ');
- }
- }
- arr.push((bytes[i] < 16 ? '0' : '') + bytes[i].toString(16));
- }
-
- console.log(arr.join(''));
-}
-
-function bytesToHex (bytes) {
- bytes = bytes || [];
- var arr = [];
- for (var i = 0; i < bytes.length; i++) {
- arr.push((bytes[i] < 16 ? '0' : '') + (bytes[i] || 0).toString(16));
- }
- return arr.join('');
-}
-
-function bytesFromHex (hexString) {
- var len = hexString.length,
- i,
- bytes = [];
-
- for (i = 0; i < len; i += 2) {
- bytes.push(parseInt(hexString.substr(i, 2), 16));
- }
-
- return bytes;
-}
-
-function bytesToBase64 (bytes) {
- var mod3, result = '';
-
- for (var nLen = bytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) {
- mod3 = nIdx % 3;
- nUint24 |= bytes[nIdx] << (16 >>> mod3 & 24);
- if (mod3 === 2 || nLen - nIdx === 1) {
- result += String.fromCharCode(
- uint6ToBase64(nUint24 >>> 18 & 63),
- uint6ToBase64(nUint24 >>> 12 & 63),
- uint6ToBase64(nUint24 >>> 6 & 63),
- uint6ToBase64(nUint24 & 63)
- );
- nUint24 = 0;
- }
- }
-
- return result.replace(/A(?=A$|$)/g, '=');
-}
-
-function uint6ToBase64 (nUint6) {
- return nUint6 < 26
- ? nUint6 + 65
- : nUint6 < 52
- ? nUint6 + 71
- : nUint6 < 62
- ? nUint6 - 4
- : nUint6 === 62
- ? 43
- : nUint6 === 63
- ? 47
- : 65;
-}
-
-function bytesCmp (bytes1, bytes2) {
- var len = bytes1.length;
- if (len != bytes2.length) {
- return false;
- }
-
- for (var i = 0; i < len; i++) {
- if (bytes1[i] != bytes2[i]) {
- return false;
- }
- }
- return true;
-}
-
-function bytesXor (bytes1, bytes2) {
- var len = bytes1.length,
- bytes = [];
-
- for (var i = 0; i < len; ++i) {
- bytes[i] = bytes1[i] ^ bytes2[i];
- }
-
- return bytes;
-}
-
-function bytesToWords (bytes) {
- var len = bytes.length,
- words = [];
-
- for (var i = 0; i < len; i++) {
- words[i >>> 2] |= bytes[i] << (24 - (i % 4) * 8);
- }
-
- return new CryptoJS.lib.WordArray.init(words, len);
-}
-
-function bytesFromWords (wordArray) {
- var words = wordArray.words,
- sigBytes = wordArray.sigBytes,
- bytes = [];
-
- for (var i = 0; i < sigBytes; i++) {
- bytes.push((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff);
- }
-
- return bytes;
-}
-
-function bytesFromBigInt (bigInt, len) {
- var bytes = bigInt.toByteArray();
-
- while (!bytes[0] && (!len || bytes.length > len)) {
- bytes = bytes.slice(1);
- }
-
- return bytes;
-}
-
-function bytesToArrayBuffer (b) {
- return (new Uint8Array(b)).buffer;
-}
-
-function bytesFromArrayBuffer (buffer) {
- var len = buffer.byteLength,
- byteView = new Uint8Array(buffer),
- bytes = [];
-
- for (var i = 0; i < len; ++i) {
- bytes[i] = byteView[i];
- }
-
- return bytes;
-}
-
-function longToInts (sLong) {
- var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000));
-
- return [divRem[0].intValue(), divRem[1].intValue()];
-}
-
-function longToBytes (sLong) {
- return bytesFromWords({words: longToInts(sLong), sigBytes: 8}).reverse();
-}
-
-function longFromInts (high, low) {
- return bigint(high).shiftLeft(32).add(bigint(low)).toString(10);
-}
-
-function intToUint (val) {
- val = parseInt(val);
- if (val < 0) {
- val = val + 4294967296;
- }
- return val;
-}
-
-function uintToInt (val) {
- if (val > 2147483647) {
- val = val - 4294967296;
- }
- return val;
-}
-
-function sha1Hash (bytes) {
- // console.log('SHA-1 hash start');
- var hashBytes = sha1.hash(bytes, true);
- // console.log('SHA-1 hash finish');
-
- return hashBytes;
-}
-
-
-
-function rsaEncrypt (publicKey, bytes) {
- var needPadding = 255 - bytes.length;
- if (needPadding > 0) {
- var padding = new Array(needPadding);
- (new SecureRandom()).nextBytes(padding);
-
- bytes = bytes.concat(padding);
- }
-
- // console.log('RSA encrypt start');
- var N = new BigInteger(publicKey.modulus, 16),
- E = new BigInteger(publicKey.exponent, 16),
- X = new BigInteger(bytes),
- encryptedBigInt = X.modPowInt(E, N),
- encryptedBytes = bytesFromBigInt(encryptedBigInt, 256);
-
- // console.log('RSA encrypt finish');
-
- return encryptedBytes;
-}
-
-function aesEncrypt (bytes, keyBytes, ivBytes) {
- // console.log('AES encrypt start', bytes.length/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/);
-
- var needPadding = 16 - (bytes.length % 16);
- if (needPadding > 0 && needPadding < 16) {
- var padding = new Array(needPadding);
- (new SecureRandom()).nextBytes(padding);
-
- bytes = bytes.concat(padding);
- }
-
- var encryptedWords = CryptoJS.AES.encrypt(bytesToWords(bytes), bytesToWords(keyBytes), {
- iv: bytesToWords(ivBytes),
- padding: CryptoJS.pad.NoPadding,
- mode: CryptoJS.mode.IGE
- }).ciphertext;
-
- var encryptedBytes = bytesFromWords(encryptedWords);
-
- // console.log('AES encrypt finish');
-
- return encryptedBytes;
-}
-
-function aesDecrypt (encryptedBytes, keyBytes, ivBytes) {
- // console.log('AES decrypt start', encryptedBytes.length/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/);
-
- var decryptedWords = CryptoJS.AES.decrypt({ciphertext: bytesToWords(encryptedBytes)}, bytesToWords(keyBytes), {
- iv: bytesToWords(ivBytes),
- padding: CryptoJS.pad.NoPadding,
- mode: CryptoJS.mode.IGE
- });
-
- var bytes = bytesFromWords(decryptedWords);
-
- // console.log('AES decrypt finish');
-
- return bytes;
-}
-
-function gzipUncompress (bytes) {
- // console.log('Gzip uncompress start');
- var result = (new Zlib.Gunzip(bytes)).decompress();
- // console.log('Gzip uncompress finish');
- return result;
-}
-
-function nextRandomInt (maxValue) {
- return Math.floor(Math.random() * maxValue);
-};
-
-function pqPrimeFactorization (pqBytes) {
- var what = new BigInteger(pqBytes),
- result = false;
-
- console.log('PQ start', pqBytes, what.bitLength());
-
- if (what.bitLength() <= 64) {
- // console.time('PQ long');
- try {
- result = pqPrimeLong(goog.math.Long.fromString(what.toString(16), 16));
- } catch (e) {
- console.error('Pq long Exception', e);
- };
- // console.timeEnd('PQ long');
- }
- // console.log(result);
-
- if (result === false) {
- // console.time('pq BigInt');
- result = pqPrimeBigInteger(what);
- // console.timeEnd('pq BigInt');
- }
-
- console.log('PQ finish');
-
- return result;
-}
-
-function pqPrimeBigInteger (what) {
- var it = 0,
- g;
- for (var i = 0; i < 3; i++) {
- var q = (nextRandomInt(128) & 15) + 17,
- x = bigint(nextRandomInt(1000000000) + 1),
- y = x.clone(),
- lim = 1 << (i + 18);
-
- for (var j = 1; j < lim; j++) {
- ++it;
- var a = x.clone(),
- b = x.clone(),
- c = bigint(q);
-
- while (!b.equals(BigInteger.ZERO)) {
- if (!b.and(BigInteger.ONE).equals(BigInteger.ZERO)) {
- c = c.add(a);
- if (c.compareTo(what) > 0) {
- c = c.subtract(what);
- }
- }
- a = a.add(a);
- if (a.compareTo(what) > 0) {
- a = a.subtract(what);
- }
- b = b.shiftRight(1);
- }
-
- x = c.clone();
- var z = x.compareTo(y) < 0 ? y.subtract(x) : x.subtract(y);
- g = z.gcd(what);
- if (!g.equals(BigInteger.ONE)) {
- break;
- }
- if ((j & (j - 1)) == 0) {
- y = x.clone();
- }
- }
- if (g.compareTo(BigInteger.ONE) > 0) {
- break;
- }
- }
-
- var f = what.divide(g), P, Q;
-
- if (g.compareTo(f) > 0) {
- P = f;
- Q = g;
- } else {
- P = g;
- Q = f;
- }
-
- return [bytesFromBigInt(P), bytesFromBigInt(Q)];
-}
-
-function gcdLong(a, b) {
- while (a.notEquals(goog.math.Long.ZERO) && b.notEquals(goog.math.Long.ZERO)) {
- while (b.and(goog.math.Long.ONE).equals(goog.math.Long.ZERO)) {
- b = b.shiftRight(1);
- }
- while (a.and(goog.math.Long.ONE).equals(goog.math.Long.ZERO)) {
- a = a.shiftRight(1);
- }
- if (a.compare(b) > 0) {
- a = a.subtract(b);
- } else {
- b = b.subtract(a);
- }
- }
- return b.equals(goog.math.Long.ZERO) ? a : b;
-}
-
-function pqPrimeLong(what) {
- // console.log('start long');
- var it = 0,
- g;
- for (var i = 0; i < 3; i++) {
- var q = goog.math.Long.fromInt((nextRandomInt(128) & 15) + 17),
- x = goog.math.Long.fromInt(nextRandomInt(1000000000) + 1),
- y = x,
- lim = 1 << (i + 18);
-
- for (var j = 1; j < lim; j++) {
- ++it;
- // if (!(it % 100)) {
- // console.log(dT(), 'it', it, i, j, x.toString());
- // }
- var a = x,
- b = x,
- c = q;
-
- while (b.notEquals(goog.math.Long.ZERO)) {
- if (b.and(goog.math.Long.ONE).notEquals(goog.math.Long.ZERO)) {
- c = c.add(a);
- if (c.compare(what) > 0) {
- c = c.subtract(what);
- }
- }
- a = a.add(a);
- if (a.compare(what) > 0) {
- a = a.subtract(what);
- }
- b = b.shiftRight(1);
- }
-
- x = c;
- var z = x.compare(y) < 0 ? y.subtract(x) : x.subtract(y);
- g = gcdLong(z, what);
- if (g.notEquals(goog.math.Long.ONE)) {
- break;
- }
- if ((j & (j - 1)) == 0) {
- y = x;
- }
- }
- if (g.compare(goog.math.Long.ONE) > 0) {
- break;
- }
- }
-
- var f = what.div(g), P, Q;
-
- if (g.compare(f) > 0) {
- P = f;
- Q = g;
- } else {
- P = g;
- Q = f;
- }
-
- return [bytesFromHex(P.toString(16)), bytesFromHex(Q.toString(16))];
-}
-
-
-function TLSerialization (options) {
- options = options || {};
- this.maxLength = options.startMaxLength || 2048; // 2Kb
- this.offset = 0; // in bytes
-
- this.createBuffer();
-
- // this.debug = options.debug !== undefined ? options.debug : Config.Modes.debug;
- this.mtproto = options.mtproto || false;
- return this;
-}
-
-TLSerialization.prototype.createBuffer = function () {
- this.buffer = new ArrayBuffer(this.maxLength);
- this.intView = new Int32Array(this.buffer);
- this.byteView = new Uint8Array(this.buffer);
-};
-
-TLSerialization.prototype.getArray = function () {
- var resultBuffer = new ArrayBuffer(this.offset);
- var resultArray = new Int32Array(resultBuffer);
-
- resultArray.set(this.intView.subarray(0, this.offset / 4));
-
- return resultArray;
-};
-
-TLSerialization.prototype.getBuffer = function () {
- return this.getArray().buffer;
-};
-
-TLSerialization.prototype.getBytes = function () {
- var bytes = [];
- for (var i = 0; i < this.offset; i++) {
- bytes.push(this.byteView[i]);
- }
- return bytes;
-};
-
-TLSerialization.prototype.checkLength = function (needBytes) {
- if (this.offset + needBytes < this.maxLength) {
- return;
- }
-
- console.trace('Increase buffer', this.offset, needBytes, this.maxLength);
- this.maxLength = Math.ceil(Math.max(this.maxLength * 2, this.offset + needBytes + 16) / 4) * 4;
- var previousBuffer = this.buffer,
- previousArray = new Int32Array(previousBuffer);
-
- this.createBuffer();
-
- new Int32Array(this.buffer).set(previousArray);
-};
-
-TLSerialization.prototype.writeInt = function (i, field) {
- this.debug && console.log('>>>', i.toString(16), i, field);
-
- this.checkLength(4);
- this.intView[this.offset / 4] = i;
- this.offset += 4;
-};
-
-TLSerialization.prototype.storeInt = function (i, field) {
- this.writeInt(i, (field || '') + ':int');
-};
-
-TLSerialization.prototype.storeBool = function (i, field) {
- if (i) {
- this.writeInt(0x997275b5, (field || '') + ':bool');
- } else {
- this.writeInt(0xbc799737, (field || '') + ':bool');
- }
-};
-
-TLSerialization.prototype.storeLongP = function (iHigh, iLow, field) {
- this.writeInt(iLow, (field || '') + ':long[low]');
- this.writeInt(iHigh, (field || '') + ':long[high]');
-};
-
-TLSerialization.prototype.storeLong = function (sLong, field) {
- if (angular.isArray(sLong)) {
- if (sLong.length == 2) {
- return this.storeLongP(sLong[0], sLong[1], field);
- } else {
- return this.storeIntBytes(sLong, 64, field);
- }
- }
-
- var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000));
-
- this.writeInt(intToUint(divRem[1].intValue()), (field || '') + ':long[low]');
- this.writeInt(intToUint(divRem[0].intValue()), (field || '') + ':long[high]');
-};
-
-TLSerialization.prototype.storeDouble = function (f) {
- var buffer = new ArrayBuffer(8);
- var intView = new Int32Array(buffer);
- var doubleView = new Float64Array(buffer);
-
- doubleView[0] = f;
-
- this.writeInt(intView[0], (field || '') + ':double[low]');
- this.writeInt(intView[1], (field || '') + ':double[high]');
-};
-
-TLSerialization.prototype.storeString = function (s, field) {
- this.debug && console.log('>>>', s, (field || '') + ':string');
-
- var sUTF8 = unescape(encodeURIComponent(s));
-
- this.checkLength(sUTF8.length + 8);
-
-
- var len = sUTF8.length;
- if (len <= 253) {
- this.byteView[this.offset++] = len;
- } else {
- this.byteView[this.offset++] = 254;
- this.byteView[this.offset++] = len & 0xFF;
- this.byteView[this.offset++] = (len & 0xFF00) >> 8;
- this.byteView[this.offset++] = (len & 0xFF0000) >> 16;
- }
- for (var i = 0; i < len; i++) {
- this.byteView[this.offset++] = sUTF8.charCodeAt(i);
- }
-
- // Padding
- while (this.offset % 4) {
- this.byteView[this.offset++] = 0;
- }
-}
-
-
-TLSerialization.prototype.storeBytes = function (bytes, field) {
- this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':bytes');
-
- this.checkLength(bytes.length + 8);
-
- var len = bytes.length;
- if (len <= 253) {
- this.byteView[this.offset++] = len;
- } else {
- this.byteView[this.offset++] = 254;
- this.byteView[this.offset++] = len & 0xFF;
- this.byteView[this.offset++] = (len & 0xFF00) >> 8;
- this.byteView[this.offset++] = (len & 0xFF0000) >> 16;
- }
- for (var i = 0; i < len; i++) {
- this.byteView[this.offset++] = bytes[i];
- }
-
- // Padding
- while (this.offset % 4) {
- this.byteView[this.offset++] = 0;
- }
-}
-
-TLSerialization.prototype.storeIntBytes = function (bytes, bits, field) {
- var len = bytes.length;
- if ((bits % 32) || (len * 8) != bits) {
- throw new Error('Invalid bits: ' + bits + ', ' + bytes.length);
- }
-
- this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':int' + bits);
- this.checkLength(len);
-
- for (var i = 0; i < len; i++) {
- this.byteView[this.offset++] = bytes[i];
- }
-};
-
-TLSerialization.prototype.storeRawBytes = function (bytes, field) {
- var len = bytes.length;
-
- this.debug && console.log('>>>', bytesToHex(bytes), (field || ''));
- this.checkLength(len);
-
- for (var i = 0; i < len; i++) {
- this.byteView[this.offset++] = bytes[i];
- }
-};
-
-
-TLSerialization.prototype.storeMethod = function (methodName, params) {
- var schema = this.mtproto ? Config.Schema.MTProto : Config.Schema.API,
- methodData = false,
- i;
-
- for (i = 0; i < schema.methods.length; i++) {
- if (schema.methods[i].method == methodName) {
- methodData = schema.methods[i];
- break
- }
- }
- if (!methodData) {
- throw new Error('No method ' + methodName + ' found');
- }
-
- this.storeInt(intToUint(methodData.id), methodName + '[id]');
-
- var self = this;
- angular.forEach(methodData.params, function (param) {
- self.storeObject(params[param.name], param.type, methodName + '[' + param.name + ']');
- });
-
- return methodData.type;
-};
-
-TLSerialization.prototype.storeObject = function (obj, type, field) {
- switch (type) {
- case 'int': return this.storeInt(obj, field);
- case 'long': return this.storeLong(obj, field);
- case 'int128': return this.storeIntBytes(obj, 128, field);
- case 'int256': return this.storeIntBytes(obj, 256, field);
- case 'int512': return this.storeIntBytes(obj, 512, field);
- case 'string': return this.storeString(obj, field);
- case 'bytes': return this.storeBytes(obj, field);
- case 'double': return this.storeDouble(obj, field);
- case 'Bool': return this.storeBool(obj, field);
- }
-
- if (angular.isArray(obj)) {
- if (type.substr(0, 6) == 'Vector') {
- this.writeInt(0x1cb5c415, field + '[id]');
- }
- else if (type.substr(0, 6) != 'vector') {
- throw new Error('Invalid vector type ' + type);
- }
- var itemType = type.substr(7, type.length - 8); // for "Vector"
- this.writeInt(obj.length, field + '[count]');
- for (var i = 0; i < obj.length; i++) {
- this.storeObject(obj[i], itemType, field + '[' + i + ']');
- }
- return true;
- }
- else if (type.substr(0, 6).toLowerCase() == 'vector') {
- throw new Error('Invalid vector object');
- }
-
- if (!angular.isObject(obj)) {
- throw new Error('Invalid object for type ' + type);
- }
-
- var schema = this.mtproto ? Config.Schema.MTProto : Config.Schema.API,
- predicate = obj['_'],
- isBare = false,
- constructorData = false,
- i;
-
- if (isBare = (type.charAt(0) == '%')) {
- type = type.substr(1);
- }
-
- for (i = 0; i < schema.constructors.length; i++) {
- if (schema.constructors[i].predicate == predicate) {
- constructorData = schema.constructors[i];
- break
- }
- }
- if (!constructorData) {
- throw new Error('No predicate ' + predicate + ' found');
- }
-
- if (predicate == type) {
- isBare = true;
- }
-
- if (!isBare) {
- this.writeInt(intToUint(constructorData.id), field + '[' + predicate + '][id]');
- }
-
- var self = this;
- angular.forEach(constructorData.params, function (param) {
- self.storeObject(obj[param.name], param.type, field + '[' + predicate + '][' + param.name + ']');
- });
-
- return constructorData.type;
-};
-
-
-
-function TLDeserialization (buffer, options) {
- options = options || {};
-
- this.offset = 0; // in bytes
- this.override = options.override || {};
-
- this.buffer = buffer;
- this.intView = new Uint32Array(this.buffer);
- this.byteView = new Uint8Array(this.buffer);
-
- // this.debug = options.debug !== undefined ? options.debug : Config.Modes.debug;
- this.mtproto = options.mtproto || false;
- return this;
-}
-
-TLDeserialization.prototype.readInt = function (field) {
- if (this.offset >= this.intView.length * 4) {
- throw new Error('Nothing to fetch: ' + field);
- }
-
- var i = this.intView[this.offset / 4];
-
- this.debug && console.log('<<<', i.toString(16), i, field);
-
- this.offset += 4;
-
- return i;
-};
-
-TLDeserialization.prototype.fetchInt = function (field) {
- return this.readInt((field || '') + ':int');
-}
-
-TLDeserialization.prototype.fetchDouble = function (field) {
- var buffer = new ArrayBuffer(8);
- var intView = new Int32Array(buffer);
- var doubleView = new Float64Array(buffer);
-
- intView[0] = this.readInt((field || '') + ':double[low]'),
- intView[1] = this.readInt((field || '') + ':double[high]');
-
- return doubleView[0];
-};
-
-TLDeserialization.prototype.fetchLong = function (field) {
- var iLow = this.readInt((field || '') + ':long[low]'),
- iHigh = this.readInt((field || '') + ':long[high]');
-
- var longDec = bigint(iHigh).shiftLeft(32).add(bigint(iLow)).toString();
-
- return longDec;
-}
-
-TLDeserialization.prototype.fetchBool = function (field) {
- var i = this.readInt((field || '') + ':bool');
- if (i == 0x997275b5) {
- return true;
- } else if (i == 0xbc799737) {
- return false
- }
-
- this.offset -= 4;
- return this.fetchObject('Object', field);
-}
-
-TLDeserialization.prototype.fetchString = function (field) {
- var len = this.byteView[this.offset++];
-
- if (len == 254) {
- var len = this.byteView[this.offset++] |
- (this.byteView[this.offset++] << 8) |
- (this.byteView[this.offset++] << 16);
- }
-
- var sUTF8 = '';
- for (var i = 0; i < len; i++) {
- sUTF8 += String.fromCharCode(this.byteView[this.offset++]);
- }
-
- // Padding
- while (this.offset % 4) {
- this.offset++;
- }
-
- try {
- var s = decodeURIComponent(escape(sUTF8));
- } catch (e) {
- var s = sUTF8;
- }
-
- this.debug && console.log('<<<', s, (field || '') + ':string');
-
- return s;
-}
-
-
-TLDeserialization.prototype.fetchBytes = function (field) {
- var len = this.byteView[this.offset++];
-
- if (len == 254) {
- var len = this.byteView[this.offset++] |
- (this.byteView[this.offset++] << 8) |
- (this.byteView[this.offset++] << 16);
- }
-
- var bytes = [];
- for (var i = 0; i < len; i++) {
- bytes.push(this.byteView[this.offset++]);
- }
-
- // Padding
- while (this.offset % 4) {
- this.offset++;
- }
-
- this.debug && console.log('<<<', bytesToHex(bytes), (field || '') + ':bytes');
-
- return bytes;
-}
-
-TLDeserialization.prototype.fetchIntBytes = function (bits, field) {
- if (bits % 32) {
- throw new Error('Invalid bits: ' + bits);
- }
-
- var len = bits / 8;
- var bytes = [];
- for (var i = 0; i < len; i++) {
- bytes.push(this.byteView[this.offset++]);
- }
-
- this.debug && console.log('<<<', bytesToHex(bytes), (field || '') + ':int' + bits);
-
- return bytes;
-};
-
-
-TLDeserialization.prototype.fetchRawBytes = function (len, field) {
- if (len === false) {
- len = this.readInt((field || '') + '_length');
- }
-
- var bytes = [];
- for (var i = 0; i < len; i++) {
- bytes.push(this.byteView[this.offset++]);
- }
-
- this.debug && console.log('<<<', bytesToHex(bytes), (field || ''));
-
- return bytes;
-};
-
-TLDeserialization.prototype.fetchObject = function (type, field) {
- switch (type) {
- case 'int': return this.fetchInt(field);
- case 'long': return this.fetchLong(field);
- case 'int128': return this.fetchIntBytes(128, field);
- case 'int256': return this.fetchIntBytes(256, field);
- case 'int512': return this.fetchIntBytes(512, field);
- case 'string': return this.fetchString(field);
- case 'bytes': return this.fetchBytes(field);
- case 'double': return this.fetchDouble(field);
- case 'Bool': return this.fetchBool(field);
- }
-
- field = field || type || 'Object';
-
- if (type.substr(0, 6) == 'Vector' || type.substr(0, 6) == 'vector') {
- if (type.charAt(0) == 'V') {
- var constructor = this.readInt(field + '[id]');
- if (constructor != 0x1cb5c415) {
- throw new Error('Invalid vector constructor ' + constructor);
- }
- }
- var len = this.readInt(field + '[count]');
- var result = [];
- if (len > 0) {
- var itemType = type.substr(7, type.length - 8); // for "Vector"
- for (var i = 0; i < len; i++) {
- result.push(this.fetchObject(itemType, field + '[' + i + ']'))
- }
- }
-
- return result;
- }
-
- var schema = this.mtproto ? Config.Schema.MTProto : Config.Schema.API,
- predicate = false,
- constructorData = false;
-
- if (type.charAt(0) == '%') {
- var checkType = type.substr(1);
- for (i = 0; i < schema.constructors.length; i++) {
- if (schema.constructors[i].type == checkType) {
- constructorData = schema.constructors[i];
- break
- }
- }
- if (!constructorData) {
- throw new Error('Constructor not found for type: ' + type);
- }
- }
- else if (type.charAt(0) >= 97 && type.charAt(0) <= 122) {
- for (i = 0; i < schema.constructors.length; i++) {
- if (schema.constructors[i].predicate == type) {
- constructorData = schema.constructors[i];
- break
- }
- }
- if (!constructorData) {
- throw new Error('Constructor not found for predicate: ' + type);
- }
- }
- else {
- var constructor = this.readInt(field + '[id]'),
- constructorCmp = uintToInt(constructor);
-
- if (constructorCmp == 0x3072cfa1) { // Gzip packed
- var compressed = this.fetchBytes(field + '[packed_string]'),
- uncompressed = gzipUncompress(compressed),
- buffer = bytesToArrayBuffer(uncompressed),
- newDeserializer = (new TLDeserialization(buffer));
-
- return newDeserializer.fetchObject(type, field);
- }
-
- for (i = 0; i < schema.constructors.length; i++) {
- if (schema.constructors[i].id == constructorCmp) {
- constructorData = schema.constructors[i];
- break;
- }
- }
-
- var fallback = false;
- if (!constructorData && this.mtproto) {
- var schemaFallback = Config.Schema.API;
- for (i = 0; i < schemaFallback.constructors.length; i++) {
- if (schemaFallback.constructors[i].id == constructorCmp) {
- constructorData = schemaFallback.constructors[i];
-
- delete this.mtproto;
- fallback = true;
- break;
- }
- }
- }
- if (!constructorData) {
- throw new Error('Constructor not found: ' + constructor);
- }
- }
-
- predicate = constructorData.predicate;
-
- var result = {'_': predicate},
- overrideKey = (this.mtproto ? 'mt_' : '') + predicate,
- self = this;
-
-
- if (this.override[overrideKey]) {
- this.override[overrideKey].apply(this, [result, field + '[' + predicate + ']']);
- } else {
- angular.forEach(constructorData.params, function (param) {
- result[param.name] = self.fetchObject(param.type, field + '[' + predicate + '][' + param.name + ']');
- });
- }
-
- if (fallback) {
- this.mtproto = true;
- }
-
- return result;
-};
-
-TLDeserialization.prototype.getOffset = function () {
- return this.offset;
-};
-
-TLDeserialization.prototype.fetchEnd = function () {
- if (this.offset != this.byteView.length) {
- throw new Error('Fetch end with non-empty buffer');
- }
- return true;
-};
+angular.module('izhukov.mtproto', ['izhukov.utils'])
-if (typeof angular != 'undefined') angular.module('mtproto.services', ['myApp.services']).
-
-factory('MtpDcConfigurator', function () {
+.factory('MtpDcConfigurator', function () {
var dcOptions = Config.Modes.test
? [
{id: 1, host: '173.240.5.253', port: 80},
@@ -1038,9 +43,9 @@ factory('MtpDcConfigurator', function () {
return {
chooseServer: chooseServer
};
-}).
+})
-factory('MtpRsaKeysManager', function () {
+.factory('MtpRsaKeysManager', function () {
/**
* Server public key, obtained from here: https://core.telegram.org/api/obtaining_api_id
@@ -1109,17 +114,17 @@ factory('MtpRsaKeysManager', function () {
prepare: prepareRsaKeys,
select: selectRsaKeyByFingerPrint
};
-}).
+})
-service('MtpSecureRandom', function () {
+.service('MtpSecureRandom', function () {
return new SecureRandom();
-}).
+})
-factory('MtpMessageIdGenerator', function (AppConfigManager) {
+.factory('MtpMessageIdGenerator', function (Storage) {
var lastMessageID = [0, 0],
timeOffset = 0;
- AppConfigManager.get('server_time_offset').then(function (to) {
+ Storage.get('server_time_offset').then(function (to) {
if (to) {
timeOffset = to;
}
@@ -1148,7 +153,7 @@ factory('MtpMessageIdGenerator', function (AppConfigManager) {
function applyServerTime (serverTime, localTime) {
var newTimeOffset = serverTime - Math.floor((localTime || tsNow()) / 1000),
changed = Math.abs(timeOffset - newTimeOffset) > 10;
- AppConfigManager.set({server_time_offset: newTimeOffset});
+ Storage.set({server_time_offset: newTimeOffset});
lastMessageID = [0, 0];
timeOffset = newTimeOffset;
@@ -1161,9 +166,9 @@ factory('MtpMessageIdGenerator', function (AppConfigManager) {
generateID: generateMessageID,
applyServerTime: applyServerTime
};
-}).
+})
-factory('MtpAuthorizer', function (MtpDcConfigurator, MtpRsaKeysManager, MtpSecureRandom, MtpMessageIdGenerator, $http, $q, $timeout) {
+.factory('MtpAuthorizer', function (MtpDcConfigurator, MtpRsaKeysManager, MtpSecureRandom, MtpMessageIdGenerator, $http, $q, $timeout) {
function mtpSendPlainRequest (dcID, requestBuffer) {
var requestLength = requestBuffer.byteLength,
@@ -1536,9 +541,9 @@ factory('MtpAuthorizer', function (MtpDcConfigurator, MtpRsaKeysManager, MtpSecu
auth: mtpAuth
};
-}).
+})
-factory('MtpAesService', function ($q) {
+.factory('MtpAesService', function ($q) {
if (!window.Worker || true) {
return {
encrypt: function (bytes, keyBytes, ivBytes) {
@@ -1575,7 +580,7 @@ factory('MtpAesService', function ($q) {
// console.log('AES post message', {taskID: taskID, task: 'encrypt', bytes: bytes, keyBytes: keyBytes, ivBytes: ivBytes})
worker.postMessage({taskID: taskID, task: 'encrypt', bytes: bytes, keyBytes: keyBytes, ivBytes: ivBytes});
- taskID++
+ taskID++;
return deferred.promise;
},
@@ -1590,10 +595,9 @@ factory('MtpAesService', function ($q) {
return deferred.promise;
}
}
-}).
-
+})
-factory('MtpSha1Service', function ($q) {
+.factory('MtpSha1Service', function ($q) {
if (!window.Worker || true) {
return {
hash: function (bytes) {
@@ -1637,9 +641,9 @@ factory('MtpSha1Service', function ($q) {
return deferred.promise;
}
}
-}).
+})
-factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerator, MtpSecureRandom, MtpSha1Service, MtpAesService, AppConfigManager, $http, $q, $timeout, $interval, $rootScope) {
+.factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerator, MtpSecureRandom, MtpSha1Service, MtpAesService, Storage, $http, $q, $timeout, $interval, $rootScope) {
var updatesProcessor,
iii = 0,
@@ -1813,7 +817,7 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato
if (Config.Modes.debug) {
console.log(dT(), 'Api call', method, params, messageID, seqNo, options);
} else {
- console.log(dT(), 'Api call', method, messageID, seqNo);
+ console.log(dT(), 'Api call', method);
}
return this.pushMessage(message, options);
@@ -1826,7 +830,7 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato
return false;
}
var self = this;
- AppConfigManager.get('dc').then(function (baseDcID) {
+ Storage.get('dc').then(function (baseDcID) {
if (isClean && (baseDcID != self.dcID || self.upload)) {
// console.warn('send long-poll for guest DC is delayed', self.dcID);
return;
@@ -2299,7 +1303,7 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato
var storeObj = {};
storeObj['dc' + this.dcID + '_server_salt'] = bytesToHex(serverSalt);
- AppConfigManager.set(storeObj);
+ Storage.set(storeObj);
this.serverSalt = serverSalt;
return true;
@@ -2520,793 +1524,4 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato
}
};
-}).
-
-factory('MtpApiManager', function (AppConfigManager, MtpAuthorizer, MtpNetworkerFactory, ErrorService, $q) {
- var cachedNetworkers = {},
- cachedUploadNetworkers = {},
- cachedExportPromise = {},
- baseDcID = false;
-
- AppConfigManager.get('dc').then(function (dcID) {
- if (dcID) {
- baseDcID = dcID;
- }
- });
-
- function mtpSetUserAuth (dcID, userAuth) {
- AppConfigManager.set({
- dc: dcID,
- user_auth: angular.extend({dcID: dcID}, userAuth)
- });
-
- baseDcID = dcID;
- }
-
- function mtpLogOut () {
- return mtpInvokeApi('auth.logOut').then(function () {
- AppConfigManager.remove('dc', 'user_auth');
-
- baseDcID = false;
- }, function (error) {
- AppConfigManager.remove('dc', 'user_auth');
- if (error && error.code != 401) {
- AppConfigManager.remove('dc' + baseDcID + '_auth_key');
- }
- baseDcID = false;
- error.handled = true;
- });
- }
-
- function mtpGetNetworker (dcID, options) {
- options = options || {};
-
- var cache = (options.fileUpload || options.fileDownload)
- ? cachedUploadNetworkers
- : cachedNetworkers;
- if (!dcID) {
- throw new Exception('get Networker without dcID');
- }
-
- if (cache[dcID] !== undefined) {
- return $q.when(cache[dcID]);
- }
-
- var akk = 'dc' + dcID + '_auth_key',
- ssk = 'dc' + dcID + '_server_salt';
-
- return AppConfigManager.get(akk, ssk).then(function (result) {
-
- if (cache[dcID] !== undefined) {
- return cache[dcID];
- }
-
- var authKeyHex = result[0],
- serverSaltHex = result[1];
- // console.log('ass', dcID, authKeyHex, serverSaltHex);
- if (authKeyHex && authKeyHex.length == 512) {
- var authKey = bytesFromHex(authKeyHex);
- var serverSalt = bytesFromHex(serverSaltHex);
-
- return cache[dcID] = MtpNetworkerFactory.getNetworker(dcID, authKey, serverSalt, options);
- }
-
- if (!options.createNetworker) {
- return $q.reject({type: 'AUTH_KEY_EMPTY', code: 401});
- }
-
- return MtpAuthorizer.auth(dcID).then(function (auth) {
- var storeObj = {};
- storeObj[akk] = bytesToHex(auth.authKey);
- storeObj[ssk] = bytesToHex(auth.serverSalt);
- AppConfigManager.set(storeObj);
-
- return cache[dcID] = MtpNetworkerFactory.getNetworker(dcID, auth.authKey, auth.serverSalt, options);
- }, function (error) {
- console.log('Get networker error', error, error.stack);
- return $q.reject(error);
- });
- });
- };
-
- function mtpInvokeApi (method, params, options) {
- options = options || {};
-
- var deferred = $q.defer(),
- rejectPromise = function (error) {
- if (!error) {
- error = {type: 'ERROR_EMPTY'};
- } else if (!angular.isObject(error)) {
- error = {message: error};
- }
- deferred.reject(error);
-
- if (!options.noErrorBox) {
- error.input = method;
- error.stack = error.stack || (new Error()).stack;
- setTimeout(function () {
- if (!error.handled) {
- ErrorService.show({error: error});
- error.handled = true;
- }
- }, 100);
- }
- },
- dcID,
- networkerPromise;
-
- if (dcID = options.dcID) {
- networkerPromise = mtpGetNetworker(dcID, options);
- } else {
- networkerPromise = AppConfigManager.get('dc').then(function (baseDcID) {
- return mtpGetNetworker(dcID = baseDcID || 1, options);
- });
- }
-
- var cachedNetworker,
- stack = false;
-
- networkerPromise.then(function (networker) {
- return (cachedNetworker = networker).wrapApiCall(method, params, options).then(
- function (result) {
- deferred.resolve(result);
- // $timeout(function () {
- // deferred.resolve(result);
- // }, 1000);
- },
- function (error) {
- console.error(dT(), 'Error', error.code, error.type, baseDcID, dcID);
- if (error.code == 401 && baseDcID == dcID) {
- AppConfigManager.remove('dc', 'user_auth');
- }
- else if (error.code == 401 && baseDcID && dcID != baseDcID) {
- if (cachedExportPromise[dcID] === undefined) {
- var exportDeferred = $q.defer();
-
- mtpInvokeApi('auth.exportAuthorization', {dc_id: dcID}, {noErrorBox: true}).then(function (exportedAuth) {
- mtpInvokeApi('auth.importAuthorization', {
- id: exportedAuth.id,
- bytes: exportedAuth.bytes
- }, {dcID: dcID, noErrorBox: true}).then(function () {
- exportDeferred.resolve();
- }, function (e) {
- exportDeferred.reject(e);
- })
- }, function (e) {
- exportDeferred.reject(e)
- });
-
- cachedExportPromise[dcID] = exportDeferred.promise;
- }
-
- cachedExportPromise[dcID].then(function () {
- (cachedNetworker = networker).wrapApiCall(method, params, options).then(function (result) {
- deferred.resolve(result);
- }, function (error) {
- rejectPromise(error);
- });
- }, function (error) {
- rejectPromise(error);
- });
- }
- else if (error.code == 303) {
- var newDcID = error.type.match(/^(PHONE_MIGRATE_|NETWORK_MIGRATE_)(\d+)/)[2];
- if (newDcID != dcID) {
- if (options.dcID) {
- options.dcID = newDcID;
- } else {
- AppConfigManager.set({dc: baseDcID = newDcID});
- }
-
- mtpGetNetworker(newDcID, options).then(function (networker) {
- networker.wrapApiCall(method, params, options).then(function (result) {
- deferred.resolve(result);
- }, function (error) {
- rejectPromise(error);
- });
- });
- }
- }
- else {
- rejectPromise(error);
- }
- });
- }, function (error) {
- rejectPromise(error);
- });
-
- if (!(stack = (stack || (new Error()).stack))) {
- try {window.unexistingFunction();} catch (e) {
- stack = e.stack || '';
- }
- }
-
- return deferred.promise;
- };
-
- function mtpGetUserID () {
- return AppConfigManager.get('user_auth').then(function (auth) {
- return auth.id || 0;
- });
- }
-
- function getBaseDcID () {
- return baseDcID || false;
- }
-
- return {
- getBaseDcID: getBaseDcID,
- getUserID: mtpGetUserID,
- invokeApi: mtpInvokeApi,
- setUserAuth: mtpSetUserAuth,
- logOut: mtpLogOut
- }
-}).
-
-
-factory('MtpApiFileManager', function (MtpApiManager, $q, $window) {
-
- var cachedFs = false;
- var cachedFsPromise = false;
- var apiUploadPromise = $q.when();
- var cachedSavePromises = {};
- var cachedDownloadPromises = {};
- var cachedDownloads = {};
-
- var downloadPulls = {};
- var downloadActives = {};
- var downloadLimit = 5;
-
- function downloadRequest(dcID, cb, activeDelta) {
- if (downloadPulls[dcID] === undefined) {
- downloadPulls[dcID] = [];
- downloadActives[dcID] = 0
- }
- var downloadPull = downloadPulls[dcID];
- var deferred = $q.defer();
- downloadPull.push({cb: cb, deferred: deferred, activeDelta: activeDelta});
- downloadCheck(dcID);
-
- return deferred.promise;
- };
-
- var index = 0;
-
- function downloadCheck(dcID) {
- var downloadPull = downloadPulls[dcID];
-
- if (downloadActives[dcID] >= downloadLimit || !downloadPull || !downloadPull.length) {
- return false;
- }
-
- var data = downloadPull.shift(),
- activeDelta = data.activeDelta || 1;
-
- downloadActives[dcID] += activeDelta;
-
- var a = index++;
- data.cb()
- .then(function (result) {
- downloadActives[dcID] -= activeDelta;
- downloadCheck(dcID);
-
- data.deferred.resolve(result);
-
- }, function (error) {
- downloadActives[dcID] -= activeDelta;
- downloadCheck(dcID);
-
- data.deferred.reject(error);
- })
- };
-
- function requestFS () {
- if (cachedFsPromise) {
- return cachedFsPromise;
- }
-
- $window.requestFileSystem = $window.requestFileSystem || $window.webkitRequestFileSystem;
-
- if (!$window.requestFileSystem) {
- return cachedFsPromise = $q.reject({type: 'FS_BROWSER_UNSUPPORTED', description: 'requestFileSystem not present'});
- }
-
- var deferred = $q.defer();
-
- $window.requestFileSystem($window.TEMPORARY, 5*1024*1024, function (fs) {
- cachedFs = fs;
- deferred.resolve();
- }, function (e) {
- deferred.reject(e);
- });
-
- return cachedFsPromise = deferred.promise;
- };
-
- function fileWriteBytes(fileWriter, bytes) {
- var deferred = $q.defer();
-
- fileWriter.onwriteend = function(e) {
- deferred.resolve();
- };
- fileWriter.onerror = function (e) {
- deferred.reject();
- };
-
- if (bytes instanceof Blob) { // is file bytes
- fileWriter.write(bytes);
- } else {
- fileWriter.write(new Blob([bytesToArrayBuffer(bytes)]));
- }
-
- return deferred.promise;
- }
-
- function fileCopyTo (fromFileEntry, toFileEntry) {
- var deferred = $q.defer();
-
- toFileEntry.createWriter(function (fileWriter) {
- fileWriteBytes(fileWriter, fromFileEntry).then(function () {
- deferred.resolve(fileWriter);
- }, function (e) {
- deferred.reject(e);
- fileWriter.truncate(0);
- });
- }, function (e) {
- deferred.reject(e);
- });
-
- return deferred.promise;
- }
-
- function getFileName(location) {
- switch (location._) {
- case 'inputVideoFileLocation':
- return 'video' + location.id + '.mp4';
-
- case 'inputDocumentFileLocation':
- return 'doc' + location.id;
-
- case 'inputAudioFileLocation':
- return 'audio' + location.id;
- }
-
- if (!location.volume_id) {
- console.trace('Empty location', location);
- }
-
- return location.volume_id + '_' + location.local_id + '_' + location.secret + '.jpg';
- };
-
- function getTempFileName(file) {
- var size = file.size || -1;
- var random = nextRandomInt(0xFFFFFFFF);
- return '_temp' + random + '_' + size;
- };
-
- function getCachedFile (location) {
- if (!location) {
- return false;
- }
- var fileName = getFileName(location);
-
- return cachedDownloads[fileName] || false;
- }
-
- function saveSmallFile (location, bytes) {
- var fileName = getFileName(location);
-
- if (cachedSavePromises[fileName]) {
- return cachedSavePromises[fileName];
- }
- var deferred = $q.defer(),
- cacheFileWriter,
- errorHandler = function (error) {
- deferred.reject(error);
- errorHandler = angular.noop;
- if (cacheFileWriter) cacheFileWriter.truncate(0);
- };
-
- requestFS().then(function () {
- cachedFs.root.getFile(fileName, {create: false}, function(fileEntry) {
- deferred.resolve(cachedDownloads[fileName] = fileEntry.toURL());
- }, function () {
- cachedFs.root.getFile(fileName, {create: true}, function(fileEntry) {
- fileEntry.createWriter(function (fileWriter) {
- cacheFileWriter = fileWriter;
- fileWriteBytes(fileWriter, bytes).then(function () {
- deferred.resolve(cachedDownloads[fileName] = fileEntry.toURL());
- }, errorHandler);
- }, errorHandler);
- }, errorHandler);
- });
- }, function () {
- deferred.resolve('data:image/jpeg;base64,' + bytesToBase64(bytes))
- });
-
- return cachedSavePromises[fileName] = deferred.promise;
- }
-
- function downloadSmallFile(location) {
- // console.log('dload small', location);
- var fileName = getFileName(location),
- cachedPromise = cachedSavePromises[fileName] || cachedDownloadPromises[fileName];
-
- if (cachedPromise) {
- return cachedPromise;
- }
-
- var deferred = $q.defer(),
- cacheFileWriter,
- errorHandler = function (error) {
- deferred.reject(error);
- errorHandler = angular.noop;
- if (cacheFileWriter) cacheFileWriter.truncate(0);
- },
- doDownload = function () {
- cachedFs.root.getFile(fileName, {create: true}, function(fileEntry) {
- var downloadPromise = downloadRequest(location.dc_id, function () {
- // console.log('next small promise');
- return MtpApiManager.invokeApi('upload.getFile', {
- location: angular.extend({}, location, {_: 'inputFileLocation'}),
- offset: 0,
- limit: 0
- }, {
- dcID: location.dc_id,
- fileDownload: true,
- createNetworker: true
- });
- });
-
- fileEntry.createWriter(function (fileWriter) {
- cacheFileWriter = fileWriter;
- downloadPromise.then(function (result) {
- fileWriteBytes(fileWriter, result.bytes).then(function () {
- // console.log('Success', location, fileEntry.toURL());
- deferred.resolve(cachedDownloads[fileName] = fileEntry.toURL());
- }, errorHandler);
- }, errorHandler);
- }, errorHandler);
- }, errorHandler);
- };
-
- requestFS().then(function () {
- cachedFs.root.getFile(fileName, {create: false}, function(fileEntry) {
- fileEntry.file(function(file) {
- if (file.size) {
- deferred.resolve(cachedDownloads[fileName] = fileEntry.toURL());
- } else {
- console.log('Small file empty', file);
- doDownload();
- }
- }, errorHandler);
- }, doDownload);
- }, function (error) {
- downloadRequest(location.dc_id, function () {
- return MtpApiManager.invokeApi('upload.getFile', {
- location: angular.extend({}, location, {_: 'inputFileLocation'}),
- offset: 0,
- limit: 0
- }, {
- dcID: location.dc_id,
- fileDownload: true,
- createNetworker: true
- });
- }).then(function (result) {
- deferred.resolve(cachedDownloads[fileName] = 'data:image/jpeg;base64,' + bytesToBase64(result.bytes))
- }, errorHandler);
- });
-
- return cachedDownloadPromises[fileName] = deferred.promise;
- }
-
- function downloadFile (dcID, location, size, options) {
- options = options || {};
-
- console.log(dT(), 'Dload file', dcID, location, size);
- var fileName = getFileName(location),
- toFileEntry = options.toFileEntry || null,
- cachedPromise = cachedSavePromises[fileName] || cachedDownloadPromises[fileName];
-
- if (!toFileEntry && cachedPromise) {
- return cachedPromise;
- }
-
- var deferred = $q.defer(),
- canceled = false,
- resolved = false,
- cacheFileWriter,
- errorHandler = function (error) {
- // console.error('Dl Error', error);
- deferred.reject(error);
- errorHandler = angular.noop;
- if (cacheFileWriter) cacheFileWriter.truncate(0);
- },
- saveToFileEntry = function (fileEntry) {
- fileEntry.createWriter(function (fileWriter) {
- cacheFileWriter = fileWriter;
- // console.time(fileName + ' ' + (size / 1024));
-
- var limit = 524288;
- // var limit = size > 16384 ? 524288 : 51200;
- var writeFilePromise = $q.when(),
- writeFileDeferred;
- for (var offset = 0; offset < size; offset += limit) {
- writeFileDeferred = $q.defer();
- (function (isFinal, offset, writeFileDeferred, writeFilePromise) {
- return downloadRequest(dcID, function () {
- // console.log('next big promise');
- if (canceled) {
- return $q.when();
- }
- return MtpApiManager.invokeApi('upload.getFile', {
- location: location,
- offset: offset,
- limit: limit
- }, {
- dcID: dcID,
- fileDownload: true,
- createNetworker: true
- });
-
- }, 6).then(function (result) {
-
- // console.log('waiting for file promise', offset);
- writeFilePromise.then(function () {
- // console.log('resolved file promise', offset);
- if (canceled) {
- return $q.when();
- }
-
- return fileWriteBytes(fileWriter, result.bytes).then(function () {
-
- // console.log('resolve file promise', offset);
- writeFileDeferred.resolve();
-
- }, errorHandler).then(function () {
-
- if (isFinal) {
- // console.timeEnd(fileName + ' ' + (size / 1024));
- resolved = true;
- if (toFileEntry) {
- deferred.resolve();
- } else {
- deferred.resolve(cachedDownloads[fileName] = fileEntry.toURL(options.mime || 'image/jpeg'));
- }
- } else {
- // console.log('notify', {done: offset + limit, total: size});
- deferred.notify({done: offset + limit, total: size});
- };
-
- });
-
- });
-
- });
-
- })(offset + limit >= size, offset, writeFileDeferred, writeFilePromise);
-
- writeFilePromise = writeFileDeferred.promise;
-
- }
- }, errorHandler);
-
- };
-
- requestFS().then(function () {
- cachedFs.root.getFile(fileName, {create: false}, function(fileEntry) {
- fileEntry.file(function(file) {
- // console.log(dT(), 'Check size', file.size, size);
- if (file.size >= size/* && false*/) {
- resolved = true;
- if (toFileEntry) {
- fileCopyTo(file, toFileEntry).then(function () {
- deferred.resolve();
- })
- } else {
- deferred.resolve(cachedDownloads[fileName] = fileEntry.toURL());
- }
- } else {
- // setTimeout(function () {
- console.log('File bad size', file, size);
- if (toFileEntry) {
- saveToFileEntry(toFileEntry);
- } else {
- cachedFs.root.getFile(fileName, {create: true}, saveToFileEntry, errorHandler)
- }
- // }, 10000);
- }
- }, errorHandler);
- }, function () {
- if (toFileEntry) {
- saveToFileEntry(toFileEntry);
- } else {
- cachedFs.root.getFile(fileName, {create: true}, saveToFileEntry, errorHandler)
- }
- });
- }, function () {
-
- if (toFileEntry) {
- return saveToFileEntry(toFileEntry);
- }
-
- var blobParts = [];
- var limit = size > 30400 ? 524288 : 4096;
- var writeBlobPromise = $q.when(),
- writeBlobDeferred;
- for (var offset = 0; offset < size; offset += limit) {
- writeBlobDeferred = $q.defer();
- (function (isFinal, offset, writeBlobDeferred, writeBlobPromise) {
- return downloadRequest(dcID, function () {
- if (canceled) {
- return $q.when();
- }
- return MtpApiManager.invokeApi('upload.getFile', {
- location: location,
- offset: offset,
- limit: limit
- }, {
- dcID: dcID,
- fileDownload: true,
- createNetworker: true
- });
- }, 6).then(function (result) {
- writeBlobPromise.then(function () {
- if (canceled) {
- return $q.when();
- }
- try {
- blobParts.push(bytesToArrayBuffer(result.bytes));
- writeBlobDeferred.resolve();
-
- if (isFinal) {
- try {
- var blob = new Blob(blobParts, {type: options.mime || 'image/jpeg'});
- } catch (e) {
- window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
- var bb = new BlobBuilder;
- angular.forEach(blobParts, function(blobPart) {
- bb.append(blobPart);
- });
- var blob = bb.getBlob(options.mime || 'image/jpeg');
- }
-
- window.URL = window.URL || window.webkitURL;
- resolved = true;
- deferred.resolve(cachedDownloads[fileName] = URL.createObjectURL(blob));
- } else {
- deferred.notify({done: offset + limit, total: size});
- };
- } catch (e) {
- errorHandler(e);
- }
- }, errorHandler);
-
- });
-
- })(offset + limit >= size, offset, writeBlobDeferred, writeBlobPromise);
-
- writeBlobPromise = writeBlobDeferred.promise;
-
- }
-
- });
-
- deferred.promise.cancel = function () {
- if (!canceled && !resolved) {
- canceled = true;
- delete cachedDownloadPromises[fileName];
- errorHandler({type: 'DOWNLOAD_CANCELED'});
- }
- }
-
- if (!toFileEntry) {
- cachedDownloadPromises[fileName] = deferred.promise;
- }
-
- return deferred.promise;
- }
-
- function uploadFile (file) {
- var fileSize = file.size,
- // partSize = fileSize > 102400 ? 65536 : 4096,
- // partSize = fileSize > 102400 ? 524288 : 4096,
- partSize = fileSize > 102400 ? 524288 : 30720,
- totalParts = Math.ceil(fileSize / partSize),
- canceled = false,
- resolved = false,
- doneParts = 0;
-
- if (totalParts > 1500) {
- return $q.reject({type: 'FILE_TOO_BIG'});
- }
-
- var fileID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)],
- deferred = $q.defer(),
- errorHandler = function (error) {
- // console.error('Up Error', error);
- deferred.reject(error);
- errorHandler = angular.noop;
- },
- part = 0,
- offset,
- resultInputFile = {
- _: 'inputFile',
- id:fileID,
- parts: totalParts,
- name: file.name,
- md5_checksum: ''
- };
-
-
- var fileReadPromise = $q.when();
-
- for (offset = 0; offset < fileSize; offset += partSize) {
- (function (offset, part) {
- fileReadPromise = fileReadPromise.then(function () {
- var fileReadDeferred = $q.defer();
-
- var reader = new FileReader();
- var blob = file.slice(offset, offset + partSize);
-
- reader.onloadend = function (e) {
- if (canceled || e.target.readyState != FileReader.DONE) {
- return;
- }
- var apiCurPromise = apiUploadPromise = apiUploadPromise.then(function () {
- return MtpApiManager.invokeApi('upload.saveFilePart', {
- file_id: fileID,
- file_part: part,
- bytes: bytesFromArrayBuffer(e.target.result)
- }, {
- startMaxLength: partSize + 256,
- fileUpload: true
- });
- }, errorHandler);
-
- apiCurPromise.then(function (result) {
- doneParts++;
- fileReadDeferred.resolve();
- if (doneParts >= totalParts) {
- deferred.resolve(resultInputFile);
- resolved = true;
- } else {
- console.log(dT(), 'Progress', doneParts * partSize / fileSize);
- deferred.notify({done: doneParts * partSize, total: fileSize});
- }
- }, errorHandler);
- };
-
- reader.readAsArrayBuffer(blob);
-
- return fileReadDeferred.promise;
- });
- })(offset, part++);
- }
-
- deferred.promise.cancel = function () {
- console.log('cancel upload', canceled, resolved);
- if (!canceled && !resolved) {
- canceled = true;
- errorHandler({type: 'UPLOAD_CANCELED'});
- }
- }
-
- return deferred.promise;
- }
-
-
- return {
- getCachedFile: getCachedFile,
- downloadFile: downloadFile,
- downloadSmallFile: downloadSmallFile,
- saveSmallFile: saveSmallFile,
- uploadFile: uploadFile
- };
})
-
-
-
-
-
-
diff --git a/app/js/lib/mtproto_wrapper.js b/app/js/lib/mtproto_wrapper.js
new file mode 100644
index 00000000..a53809c5
--- /dev/null
+++ b/app/js/lib/mtproto_wrapper.js
@@ -0,0 +1,586 @@
+/*!
+ * Webogram v0.1.6 - 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.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
+
+.factory('MtpApiManager', function (Storage, MtpAuthorizer, MtpNetworkerFactory, ErrorService, $q) {
+ var cachedNetworkers = {},
+ cachedUploadNetworkers = {},
+ cachedExportPromise = {},
+ baseDcID = false;
+
+ Storage.get('dc').then(function (dcID) {
+ if (dcID) {
+ baseDcID = dcID;
+ }
+ });
+
+ function mtpSetUserAuth (dcID, userAuth) {
+ Storage.set({
+ dc: dcID,
+ user_auth: angular.extend({dcID: dcID}, userAuth)
+ });
+
+ baseDcID = dcID;
+ }
+
+ function mtpLogOut () {
+ return mtpInvokeApi('auth.logOut').then(function () {
+ Storage.remove('dc', 'user_auth');
+
+ baseDcID = false;
+ }, function (error) {
+ Storage.remove('dc', 'user_auth');
+ if (error && error.code != 401) {
+ Storage.remove('dc' + baseDcID + '_auth_key');
+ }
+ baseDcID = false;
+ error.handled = true;
+ });
+ }
+
+ function mtpGetNetworker (dcID, options) {
+ options = options || {};
+
+ var cache = (options.fileUpload || options.fileDownload)
+ ? cachedUploadNetworkers
+ : cachedNetworkers;
+ if (!dcID) {
+ throw new Exception('get Networker without dcID');
+ }
+
+ if (cache[dcID] !== undefined) {
+ return $q.when(cache[dcID]);
+ }
+
+ var akk = 'dc' + dcID + '_auth_key',
+ ssk = 'dc' + dcID + '_server_salt';
+
+ return Storage.get(akk, ssk).then(function (result) {
+
+ if (cache[dcID] !== undefined) {
+ return cache[dcID];
+ }
+
+ var authKeyHex = result[0],
+ serverSaltHex = result[1];
+ // console.log('ass', dcID, authKeyHex, serverSaltHex);
+ if (authKeyHex && authKeyHex.length == 512) {
+ var authKey = bytesFromHex(authKeyHex);
+ var serverSalt = bytesFromHex(serverSaltHex);
+
+ return cache[dcID] = MtpNetworkerFactory.getNetworker(dcID, authKey, serverSalt, options);
+ }
+
+ if (!options.createNetworker) {
+ return $q.reject({type: 'AUTH_KEY_EMPTY', code: 401});
+ }
+
+ return MtpAuthorizer.auth(dcID).then(function (auth) {
+ var storeObj = {};
+ storeObj[akk] = bytesToHex(auth.authKey);
+ storeObj[ssk] = bytesToHex(auth.serverSalt);
+ Storage.set(storeObj);
+
+ return cache[dcID] = MtpNetworkerFactory.getNetworker(dcID, auth.authKey, auth.serverSalt, options);
+ }, function (error) {
+ console.log('Get networker error', error, error.stack);
+ return $q.reject(error);
+ });
+ });
+ };
+
+ function mtpInvokeApi (method, params, options) {
+ options = options || {};
+
+ var deferred = $q.defer(),
+ rejectPromise = function (error) {
+ if (!error) {
+ error = {type: 'ERROR_EMPTY'};
+ } else if (!angular.isObject(error)) {
+ error = {message: error};
+ }
+ deferred.reject(error);
+
+ if (!options.noErrorBox) {
+ error.input = method;
+ error.stack = error.stack || (new Error()).stack;
+ setTimeout(function () {
+ if (!error.handled) {
+ ErrorService.show({error: error});
+ error.handled = true;
+ }
+ }, 100);
+ }
+ },
+ dcID,
+ networkerPromise;
+
+ if (dcID = options.dcID) {
+ networkerPromise = mtpGetNetworker(dcID, options);
+ } else {
+ networkerPromise = Storage.get('dc').then(function (baseDcID) {
+ return mtpGetNetworker(dcID = baseDcID || 1, options);
+ });
+ }
+
+ var cachedNetworker,
+ stack = false;
+
+ networkerPromise.then(function (networker) {
+ return (cachedNetworker = networker).wrapApiCall(method, params, options).then(
+ function (result) {
+ deferred.resolve(result);
+ // $timeout(function () {
+ // deferred.resolve(result);
+ // }, 1000);
+ },
+ function (error) {
+ console.error(dT(), 'Error', error.code, error.type, baseDcID, dcID);
+ if (error.code == 401 && baseDcID == dcID) {
+ Storage.remove('dc', 'user_auth');
+ }
+ else if (error.code == 401 && baseDcID && dcID != baseDcID) {
+ if (cachedExportPromise[dcID] === undefined) {
+ var exportDeferred = $q.defer();
+
+ mtpInvokeApi('auth.exportAuthorization', {dc_id: dcID}, {noErrorBox: true}).then(function (exportedAuth) {
+ mtpInvokeApi('auth.importAuthorization', {
+ id: exportedAuth.id,
+ bytes: exportedAuth.bytes
+ }, {dcID: dcID, noErrorBox: true}).then(function () {
+ exportDeferred.resolve();
+ }, function (e) {
+ exportDeferred.reject(e);
+ })
+ }, function (e) {
+ exportDeferred.reject(e)
+ });
+
+ cachedExportPromise[dcID] = exportDeferred.promise;
+ }
+
+ cachedExportPromise[dcID].then(function () {
+ (cachedNetworker = networker).wrapApiCall(method, params, options).then(function (result) {
+ deferred.resolve(result);
+ }, function (error) {
+ rejectPromise(error);
+ });
+ }, function (error) {
+ rejectPromise(error);
+ });
+ }
+ else if (error.code == 303) {
+ var newDcID = error.type.match(/^(PHONE_MIGRATE_|NETWORK_MIGRATE_)(\d+)/)[2];
+ if (newDcID != dcID) {
+ if (options.dcID) {
+ options.dcID = newDcID;
+ } else {
+ Storage.set({dc: baseDcID = newDcID});
+ }
+
+ mtpGetNetworker(newDcID, options).then(function (networker) {
+ networker.wrapApiCall(method, params, options).then(function (result) {
+ deferred.resolve(result);
+ }, function (error) {
+ rejectPromise(error);
+ });
+ });
+ }
+ }
+ else {
+ rejectPromise(error);
+ }
+ });
+ }, function (error) {
+ rejectPromise(error);
+ });
+
+ if (!(stack = (stack || (new Error()).stack))) {
+ try {window.unexistingFunction();} catch (e) {
+ stack = e.stack || '';
+ }
+ }
+
+ return deferred.promise;
+ };
+
+ function mtpGetUserID () {
+ return Storage.get('user_auth').then(function (auth) {
+ return auth.id || 0;
+ });
+ }
+
+ function getBaseDcID () {
+ return baseDcID || false;
+ }
+
+ return {
+ getBaseDcID: getBaseDcID,
+ getUserID: mtpGetUserID,
+ invokeApi: mtpInvokeApi,
+ setUserAuth: mtpSetUserAuth,
+ logOut: mtpLogOut
+ }
+})
+
+.factory('MtpApiFileManager', function (MtpApiManager, $q, FileManager, IdbFileStorage, TmpfsFileStorage, MemoryFileStorage) {
+
+ var cachedFs = false;
+ var cachedFsPromise = false;
+ var apiUploadPromise = $q.when();
+ var cachedSavePromises = {};
+ var cachedDownloadPromises = {};
+ var cachedDownloads = {};
+
+ var downloadPulls = {};
+ var downloadActives = {};
+ var downloadLimit = 5;
+
+ function downloadRequest(dcID, cb, activeDelta) {
+ if (downloadPulls[dcID] === undefined) {
+ downloadPulls[dcID] = [];
+ downloadActives[dcID] = 0
+ }
+ var downloadPull = downloadPulls[dcID];
+ var deferred = $q.defer();
+ downloadPull.push({cb: cb, deferred: deferred, activeDelta: activeDelta});
+ downloadCheck(dcID);
+
+ return deferred.promise;
+ };
+
+ var index = 0;
+
+ function downloadCheck(dcID) {
+ var downloadPull = downloadPulls[dcID];
+
+ if (downloadActives[dcID] >= downloadLimit || !downloadPull || !downloadPull.length) {
+ return false;
+ }
+
+ var data = downloadPull.shift(),
+ activeDelta = data.activeDelta || 1;
+
+ downloadActives[dcID] += activeDelta;
+
+ var a = index++;
+ data.cb()
+ .then(function (result) {
+ downloadActives[dcID] -= activeDelta;
+ downloadCheck(dcID);
+
+ data.deferred.resolve(result);
+
+ }, function (error) {
+ downloadActives[dcID] -= activeDelta;
+ downloadCheck(dcID);
+
+ data.deferred.reject(error);
+ })
+ };
+
+ function getFileName(location) {
+ switch (location._) {
+ case 'inputVideoFileLocation':
+ return 'video' + location.id + '.mp4';
+
+ case 'inputDocumentFileLocation':
+ return 'doc' + location.id;
+
+ case 'inputAudioFileLocation':
+ return 'audio' + location.id;
+ }
+
+ if (!location.volume_id) {
+ console.trace('Empty location', location);
+ }
+
+ return location.volume_id + '_' + location.local_id + '_' + location.secret + '.jpg';
+ };
+
+ function getTempFileName(file) {
+ var size = file.size || -1;
+ var random = nextRandomInt(0xFFFFFFFF);
+ return '_temp' + random + '_' + size;
+ };
+
+ function getCachedFile (location) {
+ if (!location) {
+ return false;
+ }
+ var fileName = getFileName(location);
+
+ return cachedDownloads[fileName] || false;
+ }
+
+ function getFileStorage () {
+ if (TmpfsFileStorage.isAvailable()) {
+ return TmpfsFileStorage;
+ }
+ if (IdbFileStorage.isAvailable()) {
+ return IdbFileStorage;
+ }
+ return MemoryFileStorage;
+ }
+
+ function saveSmallFile (location, bytes) {
+ var fileName = getFileName(location),
+ mimeType = 'image/jpeg';
+
+ if (!cachedSavePromises[fileName]) {
+ cachedSavePromises[fileName] = getFileStorage().saveFile(fileName, bytes).then(function (blob) {
+ return cachedDownloads[fileName] = FileManager.getUrl(blob, mimeType);
+ });
+ }
+ return cachedSavePromises[fileName];
+ }
+
+ function downloadSmallFile(location) {
+ // console.log('dload small', location);
+ var fileName = getFileName(location),
+ mimeType = 'image/jpeg',
+ cachedPromise = cachedSavePromises[fileName] || cachedDownloadPromises[fileName];
+
+ if (cachedPromise) {
+ return cachedPromise;
+ }
+
+ var fileStorage = getFileStorage();
+
+ return cachedDownloadPromises[fileName] = fileStorage.getFile(fileName).then(function (blob) {
+ return cachedDownloads[fileName] = FileManager.getUrl(blob, mimeType);
+ }, function () {
+ var downloadPromise = downloadRequest(location.dc_id, function () {
+ // console.log('next small promise');
+ return MtpApiManager.invokeApi('upload.getFile', {
+ location: angular.extend({}, location, {_: 'inputFileLocation'}),
+ offset: 0,
+ limit: 0
+ }, {
+ dcID: location.dc_id,
+ fileDownload: true,
+ createNetworker: true
+ });
+ });
+
+ return fileStorage.getFileWriter(fileName, mimeType).then(function (fileWriter) {
+ return downloadPromise.then(function (result) {
+ return FileManager.write(fileWriter, result.bytes).then(function () {
+ return cachedDownloads[fileName] = FileManager.getUrl(fileWriter.finalize(), mimeType);
+ });
+ });
+ });
+ });
+ }
+
+ function downloadFile (dcID, location, size, options) {
+ options = options || {};
+
+ // console.log(dT(), 'Dload file', dcID, location, size);
+ var fileName = getFileName(location),
+ toFileEntry = options.toFileEntry || null,
+ cachedPromise = cachedSavePromises[fileName] || cachedDownloadPromises[fileName];
+
+ var fileStorage = getFileStorage();
+
+ // console.log(dT(), 'fs', fileStorage, fileName, cachedPromise);
+
+ if (cachedPromise) {
+ if (toFileEntry) {
+ return cachedPromise.then(function (url) {
+ return fileStorage.getFile(fileName).then(function (blob) {
+ return FileManager.copy(blob, toFileEntry);
+ });
+ })
+ }
+ return cachedPromise;
+ }
+
+ var deferred = $q.defer(),
+ canceled = false,
+ resolved = false,
+ mimeType = options.mime || 'image/jpeg',
+ cacheFileWriter,
+ errorHandler = function (error) {
+ deferred.reject(error);
+ errorHandler = angular.noop;
+ if (cacheFileWriter) cacheFileWriter.truncate(0);
+ };
+
+
+ fileStorage.getFile(fileName).then(function (blob) {
+ if (toFileEntry) {
+ FileManager.copy(blob, toFileEntry).then(function () {
+ deferred.resolve();
+ }, errorHandler);
+ } else {
+ deferred.resolve(cachedDownloads[fileName] = FileManager.getUrl(blob, mimeType));
+ }
+ }, function () {
+ var fileWriterPromise = toFileEntry ? $q.when(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType);
+
+ fileWriterPromise.then(function (fileWriter) {
+ cacheFileWriter = fileWriter;
+ var limit = 524288,
+ writeFilePromise = $q.when(),
+ writeFileDeferred;
+ for (var offset = 0; offset < size; offset += limit) {
+ writeFileDeferred = $q.defer();
+ (function (isFinal, offset, writeFileDeferred, writeFilePromise) {
+ return downloadRequest(dcID, function () {
+ if (canceled) {
+ return $q.when();
+ }
+ return MtpApiManager.invokeApi('upload.getFile', {
+ location: location,
+ offset: offset,
+ limit: limit
+ }, {
+ dcID: dcID,
+ fileDownload: true,
+ createNetworker: true
+ });
+ }, 6).then(function (result) {
+ writeFilePromise.then(function () {
+ if (canceled) {
+ return $q.when();
+ }
+ return FileManager.write(fileWriter, result.bytes).then(function () {
+ writeFileDeferred.resolve();
+ }, errorHandler).then(function () {
+ if (isFinal) {
+ resolved = true;
+ if (toFileEntry) {
+ deferred.resolve();
+ } else {
+ deferred.resolve(cachedDownloads[fileName] = FileManager.getUrl(fileWriter.finalize(), mimeType));
+ }
+ } else {
+ deferred.notify({done: offset + limit, total: size});
+ };
+ });
+ });
+ });
+ })(offset + limit >= size, offset, writeFileDeferred, writeFilePromise);
+ writeFilePromise = writeFileDeferred.promise;
+ }
+ });
+ })
+
+ deferred.promise.cancel = function () {
+ if (!canceled && !resolved) {
+ canceled = true;
+ delete cachedDownloadPromises[fileName];
+ errorHandler({type: 'DOWNLOAD_CANCELED'});
+ }
+ }
+
+ if (!toFileEntry) {
+ cachedDownloadPromises[fileName] = deferred.promise;
+ }
+
+ return deferred.promise;
+ }
+
+ function uploadFile (file) {
+ var fileSize = file.size,
+ // partSize = fileSize > 102400 ? 65536 : 4096,
+ // partSize = fileSize > 102400 ? 524288 : 4096,
+ partSize = fileSize > 102400 ? 524288 : 32768,
+ isBigFile = fileSize >= 10485760,
+ totalParts = Math.ceil(fileSize / partSize),
+ canceled = false,
+ resolved = false,
+ doneParts = 0;
+
+ if (totalParts > 1500) {
+ return $q.reject({type: 'FILE_TOO_BIG'});
+ }
+
+ var fileID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)],
+ deferred = $q.defer(),
+ errorHandler = function (error) {
+ // console.error('Up Error', error);
+ deferred.reject(error);
+ errorHandler = angular.noop;
+ },
+ part = 0,
+ offset,
+ resultInputFile = {
+ _: isBigFile ? 'inputFileBig' : 'inputFile',
+ id: fileID,
+ parts: totalParts,
+ name: file.name,
+ md5_checksum: ''
+ };
+
+
+ var fileReadPromise = $q.when();
+
+ for (offset = 0; offset < fileSize; offset += partSize) {
+ (function (offset, part) {
+ fileReadPromise = fileReadPromise.then(function () {
+ var fileReadDeferred = $q.defer();
+
+ var reader = new FileReader();
+ var blob = file.slice(offset, offset + partSize);
+
+ reader.onloadend = function (e) {
+ if (canceled || e.target.readyState != FileReader.DONE) {
+ return;
+ }
+ var apiCurPromise = apiUploadPromise = apiUploadPromise.then(function () {
+ return MtpApiManager.invokeApi('upload.saveFilePart', {
+ file_id: fileID,
+ file_part: part,
+ bytes: bytesFromArrayBuffer(e.target.result)
+ }, {
+ startMaxLength: partSize + 256,
+ fileUpload: true
+ });
+ }, errorHandler);
+
+ apiCurPromise.then(function (result) {
+ doneParts++;
+ fileReadDeferred.resolve();
+ if (doneParts >= totalParts) {
+ deferred.resolve(resultInputFile);
+ resolved = true;
+ } else {
+ console.log(dT(), 'Progress', doneParts * partSize / fileSize);
+ deferred.notify({done: doneParts * partSize, total: fileSize});
+ }
+ }, errorHandler);
+ };
+
+ reader.readAsArrayBuffer(blob);
+
+ return fileReadDeferred.promise;
+ });
+ })(offset, part++);
+ }
+
+ deferred.promise.cancel = function () {
+ console.log('cancel upload', canceled, resolved);
+ if (!canceled && !resolved) {
+ canceled = true;
+ errorHandler({type: 'UPLOAD_CANCELED'});
+ }
+ }
+
+ return deferred.promise;
+ }
+
+ return {
+ getCachedFile: getCachedFile,
+ downloadFile: downloadFile,
+ downloadSmallFile: downloadSmallFile,
+ saveSmallFile: saveSmallFile,
+ uploadFile: uploadFile
+ };
+})
diff --git a/app/js/lib/ng_utils.js b/app/js/lib/ng_utils.js
new file mode 100644
index 00000000..be46726b
--- /dev/null
+++ b/app/js/lib/ng_utils.js
@@ -0,0 +1,518 @@
+/*!
+ * Webogram v0.1.6 - 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 () {
+
+ var keyPrefix = '';
+ var cache = {};
+ var useCs = !!(window.chrome && chrome.storage && chrome.storage.local);
+ var useLs = !useCs && !!window.localStorage;
+
+ this.setPrefix = function (newPrefix) {
+ keyPrefix = newPrefix
+ };
+
+ this.$get = ['$q', function ($q) {
+ function getValue() {
+ var keys = Array.prototype.slice.call(arguments),
+ result = [],
+ single = keys.length == 1,
+ allFound = true;
+
+ for (var i = 0; i < keys.length; i++) {
+ keys[i] = keyPrefix + keys[i];
+ }
+
+ angular.forEach(keys, function (key) {
+ if (cache[key] !== undefined) {
+ result.push(cache[key]);
+ }
+ else if (useLs) {
+ var value = localStorage.getItem(key);
+ value = (value === undefined || value === null) ? false : JSON.parse(value);
+ result.push(cache[key] = value);
+ }
+ else if (!useCs) {
+ result.push(cache[key] = false);
+ }
+ else {
+ allFound = false;
+ }
+ });
+
+ if (allFound) {
+ return $q.when(single ? result[0] : result);
+ }
+
+ var deferred = $q.defer();
+
+ chrome.storage.local.get(keys, function (resultObj) {
+ result = [];
+ angular.forEach(keys, function (key) {
+ var value = resultObj[key];
+ value = value === undefined || value === null ? false : JSON.parse(value);
+ result.push(cache[key] = value);
+ });
+
+ deferred.resolve(single ? result[0] : result);
+ });
+
+ return deferred.promise;
+ };
+
+ function setValue(obj) {
+ var keyValues = {};
+ angular.forEach(obj, function (value, key) {
+ keyValues[keyPrefix + key] = JSON.stringify(value);
+ cache[keyPrefix + key] = value;
+ });
+
+ if (useLs) {
+ angular.forEach(keyValues, function (value, key) {
+ localStorage.setItem(key, value);
+ });
+ return $q.when();
+ }
+
+ if (!useCs) {
+ return $q.when();
+ }
+
+ var deferred = $q.defer();
+
+ chrome.storage.local.set(keyValues, function () {
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+ };
+
+ function removeValue () {
+ var keys = Array.prototype.slice.call(arguments);
+
+ for (var i = 0; i < keys.length; i++) {
+ keys[i] = keyPrefix + keys[i];
+ }
+
+ angular.forEach(keys, function(key){
+ delete cache[key];
+ });
+
+ if (useLs) {
+ angular.forEach(keys, function(key){
+ localStorage.removeItem(key);
+ });
+
+ return $q.when();
+ }
+
+ if (!useCs) {
+ return $q.when();
+ }
+
+ var deferred = $q.defer();
+
+ chrome.storage.local.remove(keys, function () {
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+ };
+
+ return {
+ get: getValue,
+ set: setValue,
+ remove: removeValue
+ };
+ }];
+
+})
+
+.service('FileManager', function ($window, $timeout, $q) {
+
+ $window.URL = $window.URL || $window.webkitURL;
+ $window.BlobBuilder = $window.BlobBuilder || $window.WebKitBlobBuilder || $window.MozBlobBuilder;
+
+ function fileCopyTo (fromFileEntry, toFileEntry) {
+ var deferred = $q.defer();
+
+ toFileEntry.createWriter(function (fileWriter) {
+ fileWriteData(fileWriter, fromFileEntry).then(function () {
+ deferred.resolve(fileWriter);
+ }, function (e) {
+ deferred.reject(e);
+ fileWriter.truncate(0);
+ });
+ }, function (e) {
+ deferred.reject(e);
+ });
+
+ return deferred.promise;
+ }
+
+ function fileWriteData(fileWriter, bytes) {
+ var deferred = $q.defer();
+
+ fileWriter.onwriteend = function(e) {
+ deferred.resolve();
+ };
+ fileWriter.onerror = function (e) {
+ deferred.reject();
+ };
+
+ if (bytes instanceof Blob) { // is file bytes
+ fileWriter.write(bytes);
+ } else {
+ fileWriter.write(new Blob([bytesToArrayBuffer(bytes)]));
+ }
+
+ 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 getFakeFileWriter (mimeType, saveFileCallback) {
+ var blobParts = [],
+ fakeFileWriter = {
+ write: function (blob) {
+ blobParts.push(blob);
+ $timeout(function () {
+ if (fakeFileWriter.onwriteend) {
+ fakeFileWriter.onwriteend();
+ }
+ });
+ },
+ truncate: function () {
+ blobParts = [];
+ },
+ finalize: function () {
+ 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);
+ }
+ 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) {
+ 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 {
+ copy: fileCopyTo,
+ write: fileWriteData,
+ 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 (argument) {
+ return storageIsAvailable;
+ }
+
+ function openDatabase() {
+ if (openDbPromise) {
+ return openDbPromise;
+ }
+
+ var request = indexedDB.open(dbName, dbVersion),
+ deferred = $q.defer(),
+ createObjectStore = function (db) {
+ db.createObjectStore(dbStoreName);
+ };
+
+ request.onsuccess = function (event) {
+ db = request.result;
+
+ db.onerror = function (event) {
+ storageIsAvailable = false;
+ console.error("Error creating/accessing IndexedDB database", event);
+ deferred.reject(event);
+ };
+
+ // 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.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);
+ }
+
+ 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, 5*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) {
+ fileEntry.createWriter(function (fileWriter) {
+ fileWriter.finalize = function () {
+ return fileEntry;
+ }
+ // console.log(dT(), 'got writer');
+ deferred.resolve(fileWriter);
+ }, function (error) {
+ deferred.reject(error);
+ });
+ }, function (error) {
+ deferred.reject(error);
+ });
+
+ return deferred.promise;
+ })
+ }
+
+ 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
+ };
+})
+
diff --git a/app/js/lib/tl_utils.js b/app/js/lib/tl_utils.js
new file mode 100644
index 00000000..0dd629fd
--- /dev/null
+++ b/app/js/lib/tl_utils.js
@@ -0,0 +1,571 @@
+/*!
+ * Webogram v0.1.6 - messaging web application for MTProto
+ * https://github.com/zhukov/webogram
+ * Copyright (C) 2014 Igor Zhukov
+ * https://github.com/zhukov/webogram/blob/master/LICENSE
+ */
+
+ function TLSerialization (options) {
+ options = options || {};
+ this.maxLength = options.startMaxLength || 2048; // 2Kb
+ this.offset = 0; // in bytes
+
+ this.createBuffer();
+
+ // this.debug = options.debug !== undefined ? options.debug : Config.Modes.debug;
+ this.mtproto = options.mtproto || false;
+ return this;
+}
+
+TLSerialization.prototype.createBuffer = function () {
+ this.buffer = new ArrayBuffer(this.maxLength);
+ this.intView = new Int32Array(this.buffer);
+ this.byteView = new Uint8Array(this.buffer);
+};
+
+TLSerialization.prototype.getArray = function () {
+ var resultBuffer = new ArrayBuffer(this.offset);
+ var resultArray = new Int32Array(resultBuffer);
+
+ resultArray.set(this.intView.subarray(0, this.offset / 4));
+
+ return resultArray;
+};
+
+TLSerialization.prototype.getBuffer = function () {
+ return this.getArray().buffer;
+};
+
+TLSerialization.prototype.getBytes = function () {
+ var bytes = [];
+ for (var i = 0; i < this.offset; i++) {
+ bytes.push(this.byteView[i]);
+ }
+ return bytes;
+};
+
+TLSerialization.prototype.checkLength = function (needBytes) {
+ if (this.offset + needBytes < this.maxLength) {
+ return;
+ }
+
+ console.trace('Increase buffer', this.offset, needBytes, this.maxLength);
+ this.maxLength = Math.ceil(Math.max(this.maxLength * 2, this.offset + needBytes + 16) / 4) * 4;
+ var previousBuffer = this.buffer,
+ previousArray = new Int32Array(previousBuffer);
+
+ this.createBuffer();
+
+ new Int32Array(this.buffer).set(previousArray);
+};
+
+TLSerialization.prototype.writeInt = function (i, field) {
+ this.debug && console.log('>>>', i.toString(16), i, field);
+
+ this.checkLength(4);
+ this.intView[this.offset / 4] = i;
+ this.offset += 4;
+};
+
+TLSerialization.prototype.storeInt = function (i, field) {
+ this.writeInt(i, (field || '') + ':int');
+};
+
+TLSerialization.prototype.storeBool = function (i, field) {
+ if (i) {
+ this.writeInt(0x997275b5, (field || '') + ':bool');
+ } else {
+ this.writeInt(0xbc799737, (field || '') + ':bool');
+ }
+};
+
+TLSerialization.prototype.storeLongP = function (iHigh, iLow, field) {
+ this.writeInt(iLow, (field || '') + ':long[low]');
+ this.writeInt(iHigh, (field || '') + ':long[high]');
+};
+
+TLSerialization.prototype.storeLong = function (sLong, field) {
+ if (angular.isArray(sLong)) {
+ if (sLong.length == 2) {
+ return this.storeLongP(sLong[0], sLong[1], field);
+ } else {
+ return this.storeIntBytes(sLong, 64, field);
+ }
+ }
+
+ var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000));
+
+ this.writeInt(intToUint(divRem[1].intValue()), (field || '') + ':long[low]');
+ this.writeInt(intToUint(divRem[0].intValue()), (field || '') + ':long[high]');
+};
+
+TLSerialization.prototype.storeDouble = function (f) {
+ var buffer = new ArrayBuffer(8);
+ var intView = new Int32Array(buffer);
+ var doubleView = new Float64Array(buffer);
+
+ doubleView[0] = f;
+
+ this.writeInt(intView[0], (field || '') + ':double[low]');
+ this.writeInt(intView[1], (field || '') + ':double[high]');
+};
+
+TLSerialization.prototype.storeString = function (s, field) {
+ this.debug && console.log('>>>', s, (field || '') + ':string');
+
+ var sUTF8 = unescape(encodeURIComponent(s));
+
+ this.checkLength(sUTF8.length + 8);
+
+
+ var len = sUTF8.length;
+ if (len <= 253) {
+ this.byteView[this.offset++] = len;
+ } else {
+ this.byteView[this.offset++] = 254;
+ this.byteView[this.offset++] = len & 0xFF;
+ this.byteView[this.offset++] = (len & 0xFF00) >> 8;
+ this.byteView[this.offset++] = (len & 0xFF0000) >> 16;
+ }
+ for (var i = 0; i < len; i++) {
+ this.byteView[this.offset++] = sUTF8.charCodeAt(i);
+ }
+
+ // Padding
+ while (this.offset % 4) {
+ this.byteView[this.offset++] = 0;
+ }
+}
+
+
+TLSerialization.prototype.storeBytes = function (bytes, field) {
+ this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':bytes');
+
+ this.checkLength(bytes.length + 8);
+
+ var len = bytes.length;
+ if (len <= 253) {
+ this.byteView[this.offset++] = len;
+ } else {
+ this.byteView[this.offset++] = 254;
+ this.byteView[this.offset++] = len & 0xFF;
+ this.byteView[this.offset++] = (len & 0xFF00) >> 8;
+ this.byteView[this.offset++] = (len & 0xFF0000) >> 16;
+ }
+ for (var i = 0; i < len; i++) {
+ this.byteView[this.offset++] = bytes[i];
+ }
+
+ // Padding
+ while (this.offset % 4) {
+ this.byteView[this.offset++] = 0;
+ }
+}
+
+TLSerialization.prototype.storeIntBytes = function (bytes, bits, field) {
+ var len = bytes.length;
+ if ((bits % 32) || (len * 8) != bits) {
+ throw new Error('Invalid bits: ' + bits + ', ' + bytes.length);
+ }
+
+ this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':int' + bits);
+ this.checkLength(len);
+
+ for (var i = 0; i < len; i++) {
+ this.byteView[this.offset++] = bytes[i];
+ }
+};
+
+TLSerialization.prototype.storeRawBytes = function (bytes, field) {
+ var len = bytes.length;
+
+ this.debug && console.log('>>>', bytesToHex(bytes), (field || ''));
+ this.checkLength(len);
+
+ for (var i = 0; i < len; i++) {
+ this.byteView[this.offset++] = bytes[i];
+ }
+};
+
+
+TLSerialization.prototype.storeMethod = function (methodName, params) {
+ var schema = this.mtproto ? Config.Schema.MTProto : Config.Schema.API,
+ methodData = false,
+ i;
+
+ for (i = 0; i < schema.methods.length; i++) {
+ if (schema.methods[i].method == methodName) {
+ methodData = schema.methods[i];
+ break
+ }
+ }
+ if (!methodData) {
+ throw new Error('No method ' + methodName + ' found');
+ }
+
+ this.storeInt(intToUint(methodData.id), methodName + '[id]');
+
+ var self = this;
+ angular.forEach(methodData.params, function (param) {
+ self.storeObject(params[param.name], param.type, methodName + '[' + param.name + ']');
+ });
+
+ return methodData.type;
+};
+
+TLSerialization.prototype.storeObject = function (obj, type, field) {
+ switch (type) {
+ case 'int': return this.storeInt(obj, field);
+ case 'long': return this.storeLong(obj, field);
+ case 'int128': return this.storeIntBytes(obj, 128, field);
+ case 'int256': return this.storeIntBytes(obj, 256, field);
+ case 'int512': return this.storeIntBytes(obj, 512, field);
+ case 'string': return this.storeString(obj, field);
+ case 'bytes': return this.storeBytes(obj, field);
+ case 'double': return this.storeDouble(obj, field);
+ case 'Bool': return this.storeBool(obj, field);
+ }
+
+ if (angular.isArray(obj)) {
+ if (type.substr(0, 6) == 'Vector') {
+ this.writeInt(0x1cb5c415, field + '[id]');
+ }
+ else if (type.substr(0, 6) != 'vector') {
+ throw new Error('Invalid vector type ' + type);
+ }
+ var itemType = type.substr(7, type.length - 8); // for "Vector"
+ this.writeInt(obj.length, field + '[count]');
+ for (var i = 0; i < obj.length; i++) {
+ this.storeObject(obj[i], itemType, field + '[' + i + ']');
+ }
+ return true;
+ }
+ else if (type.substr(0, 6).toLowerCase() == 'vector') {
+ throw new Error('Invalid vector object');
+ }
+
+ if (!angular.isObject(obj)) {
+ throw new Error('Invalid object for type ' + type);
+ }
+
+ var schema = this.mtproto ? Config.Schema.MTProto : Config.Schema.API,
+ predicate = obj['_'],
+ isBare = false,
+ constructorData = false,
+ i;
+
+ if (isBare = (type.charAt(0) == '%')) {
+ type = type.substr(1);
+ }
+
+ for (i = 0; i < schema.constructors.length; i++) {
+ if (schema.constructors[i].predicate == predicate) {
+ constructorData = schema.constructors[i];
+ break
+ }
+ }
+ if (!constructorData) {
+ throw new Error('No predicate ' + predicate + ' found');
+ }
+
+ if (predicate == type) {
+ isBare = true;
+ }
+
+ if (!isBare) {
+ this.writeInt(intToUint(constructorData.id), field + '[' + predicate + '][id]');
+ }
+
+ var self = this;
+ angular.forEach(constructorData.params, function (param) {
+ self.storeObject(obj[param.name], param.type, field + '[' + predicate + '][' + param.name + ']');
+ });
+
+ return constructorData.type;
+};
+
+
+
+function TLDeserialization (buffer, options) {
+ options = options || {};
+
+ this.offset = 0; // in bytes
+ this.override = options.override || {};
+
+ this.buffer = buffer;
+ this.intView = new Uint32Array(this.buffer);
+ this.byteView = new Uint8Array(this.buffer);
+
+ // this.debug = options.debug !== undefined ? options.debug : Config.Modes.debug;
+ this.mtproto = options.mtproto || false;
+ return this;
+}
+
+TLDeserialization.prototype.readInt = function (field) {
+ if (this.offset >= this.intView.length * 4) {
+ throw new Error('Nothing to fetch: ' + field);
+ }
+
+ var i = this.intView[this.offset / 4];
+
+ this.debug && console.log('<<<', i.toString(16), i, field);
+
+ this.offset += 4;
+
+ return i;
+};
+
+TLDeserialization.prototype.fetchInt = function (field) {
+ return this.readInt((field || '') + ':int');
+}
+
+TLDeserialization.prototype.fetchDouble = function (field) {
+ var buffer = new ArrayBuffer(8);
+ var intView = new Int32Array(buffer);
+ var doubleView = new Float64Array(buffer);
+
+ intView[0] = this.readInt((field || '') + ':double[low]'),
+ intView[1] = this.readInt((field || '') + ':double[high]');
+
+ return doubleView[0];
+};
+
+TLDeserialization.prototype.fetchLong = function (field) {
+ var iLow = this.readInt((field || '') + ':long[low]'),
+ iHigh = this.readInt((field || '') + ':long[high]');
+
+ var longDec = bigint(iHigh).shiftLeft(32).add(bigint(iLow)).toString();
+
+ return longDec;
+}
+
+TLDeserialization.prototype.fetchBool = function (field) {
+ var i = this.readInt((field || '') + ':bool');
+ if (i == 0x997275b5) {
+ return true;
+ } else if (i == 0xbc799737) {
+ return false
+ }
+
+ this.offset -= 4;
+ return this.fetchObject('Object', field);
+}
+
+TLDeserialization.prototype.fetchString = function (field) {
+ var len = this.byteView[this.offset++];
+
+ if (len == 254) {
+ var len = this.byteView[this.offset++] |
+ (this.byteView[this.offset++] << 8) |
+ (this.byteView[this.offset++] << 16);
+ }
+
+ var sUTF8 = '';
+ for (var i = 0; i < len; i++) {
+ sUTF8 += String.fromCharCode(this.byteView[this.offset++]);
+ }
+
+ // Padding
+ while (this.offset % 4) {
+ this.offset++;
+ }
+
+ try {
+ var s = decodeURIComponent(escape(sUTF8));
+ } catch (e) {
+ var s = sUTF8;
+ }
+
+ this.debug && console.log('<<<', s, (field || '') + ':string');
+
+ return s;
+}
+
+
+TLDeserialization.prototype.fetchBytes = function (field) {
+ var len = this.byteView[this.offset++];
+
+ if (len == 254) {
+ var len = this.byteView[this.offset++] |
+ (this.byteView[this.offset++] << 8) |
+ (this.byteView[this.offset++] << 16);
+ }
+
+ var bytes = [];
+ for (var i = 0; i < len; i++) {
+ bytes.push(this.byteView[this.offset++]);
+ }
+
+ // Padding
+ while (this.offset % 4) {
+ this.offset++;
+ }
+
+ this.debug && console.log('<<<', bytesToHex(bytes), (field || '') + ':bytes');
+
+ return bytes;
+}
+
+TLDeserialization.prototype.fetchIntBytes = function (bits, field) {
+ if (bits % 32) {
+ throw new Error('Invalid bits: ' + bits);
+ }
+
+ var len = bits / 8;
+ var bytes = [];
+ for (var i = 0; i < len; i++) {
+ bytes.push(this.byteView[this.offset++]);
+ }
+
+ this.debug && console.log('<<<', bytesToHex(bytes), (field || '') + ':int' + bits);
+
+ return bytes;
+};
+
+
+TLDeserialization.prototype.fetchRawBytes = function (len, field) {
+ if (len === false) {
+ len = this.readInt((field || '') + '_length');
+ }
+
+ var bytes = [];
+ for (var i = 0; i < len; i++) {
+ bytes.push(this.byteView[this.offset++]);
+ }
+
+ this.debug && console.log('<<<', bytesToHex(bytes), (field || ''));
+
+ return bytes;
+};
+
+TLDeserialization.prototype.fetchObject = function (type, field) {
+ switch (type) {
+ case 'int': return this.fetchInt(field);
+ case 'long': return this.fetchLong(field);
+ case 'int128': return this.fetchIntBytes(128, field);
+ case 'int256': return this.fetchIntBytes(256, field);
+ case 'int512': return this.fetchIntBytes(512, field);
+ case 'string': return this.fetchString(field);
+ case 'bytes': return this.fetchBytes(field);
+ case 'double': return this.fetchDouble(field);
+ case 'Bool': return this.fetchBool(field);
+ }
+
+ field = field || type || 'Object';
+
+ if (type.substr(0, 6) == 'Vector' || type.substr(0, 6) == 'vector') {
+ if (type.charAt(0) == 'V') {
+ var constructor = this.readInt(field + '[id]');
+ if (constructor != 0x1cb5c415) {
+ throw new Error('Invalid vector constructor ' + constructor);
+ }
+ }
+ var len = this.readInt(field + '[count]');
+ var result = [];
+ if (len > 0) {
+ var itemType = type.substr(7, type.length - 8); // for "Vector"
+ for (var i = 0; i < len; i++) {
+ result.push(this.fetchObject(itemType, field + '[' + i + ']'))
+ }
+ }
+
+ return result;
+ }
+
+ var schema = this.mtproto ? Config.Schema.MTProto : Config.Schema.API,
+ predicate = false,
+ constructorData = false;
+
+ if (type.charAt(0) == '%') {
+ var checkType = type.substr(1);
+ for (i = 0; i < schema.constructors.length; i++) {
+ if (schema.constructors[i].type == checkType) {
+ constructorData = schema.constructors[i];
+ break
+ }
+ }
+ if (!constructorData) {
+ throw new Error('Constructor not found for type: ' + type);
+ }
+ }
+ else if (type.charAt(0) >= 97 && type.charAt(0) <= 122) {
+ for (i = 0; i < schema.constructors.length; i++) {
+ if (schema.constructors[i].predicate == type) {
+ constructorData = schema.constructors[i];
+ break
+ }
+ }
+ if (!constructorData) {
+ throw new Error('Constructor not found for predicate: ' + type);
+ }
+ }
+ else {
+ var constructor = this.readInt(field + '[id]'),
+ constructorCmp = uintToInt(constructor);
+
+ if (constructorCmp == 0x3072cfa1) { // Gzip packed
+ var compressed = this.fetchBytes(field + '[packed_string]'),
+ uncompressed = gzipUncompress(compressed),
+ buffer = bytesToArrayBuffer(uncompressed),
+ newDeserializer = (new TLDeserialization(buffer));
+
+ return newDeserializer.fetchObject(type, field);
+ }
+
+ for (i = 0; i < schema.constructors.length; i++) {
+ if (schema.constructors[i].id == constructorCmp) {
+ constructorData = schema.constructors[i];
+ break;
+ }
+ }
+
+ var fallback = false;
+ if (!constructorData && this.mtproto) {
+ var schemaFallback = Config.Schema.API;
+ for (i = 0; i < schemaFallback.constructors.length; i++) {
+ if (schemaFallback.constructors[i].id == constructorCmp) {
+ constructorData = schemaFallback.constructors[i];
+
+ delete this.mtproto;
+ fallback = true;
+ break;
+ }
+ }
+ }
+ if (!constructorData) {
+ throw new Error('Constructor not found: ' + constructor);
+ }
+ }
+
+ predicate = constructorData.predicate;
+
+ var result = {'_': predicate},
+ overrideKey = (this.mtproto ? 'mt_' : '') + predicate,
+ self = this;
+
+
+ if (this.override[overrideKey]) {
+ this.override[overrideKey].apply(this, [result, field + '[' + predicate + ']']);
+ } else {
+ angular.forEach(constructorData.params, function (param) {
+ result[param.name] = self.fetchObject(param.type, field + '[' + predicate + '][' + param.name + ']');
+ });
+ }
+
+ if (fallback) {
+ this.mtproto = true;
+ }
+
+ return result;
+};
+
+TLDeserialization.prototype.getOffset = function () {
+ return this.offset;
+};
+
+TLDeserialization.prototype.fetchEnd = function () {
+ if (this.offset != this.byteView.length) {
+ throw new Error('Fetch end with non-empty buffer');
+ }
+ return true;
+};
diff --git a/app/js/util.js b/app/js/lib/utils.js
similarity index 100%
rename from app/js/util.js
rename to app/js/lib/utils.js
diff --git a/app/js/services.js b/app/js/services.js
index 987d70c4..4baed141 100644
--- a/app/js/services.js
+++ b/app/js/services.js
@@ -11,125 +11,6 @@
angular.module('myApp.services', [])
-.service('AppConfigManager', function ($q) {
- var testPrefix = Config.Modes.test ? 't_' : '';
- var cache = {};
- var useCs = !!(window.chrome && chrome.storage && chrome.storage.local);
- var useLs = !useCs && !!window.localStorage;
-
- function getValue() {
- var keys = Array.prototype.slice.call(arguments),
- result = [],
- single = keys.length == 1,
- allFound = true;
-
- for (var i = 0; i < keys.length; i++) {
- keys[i] = testPrefix + keys[i];
- }
-
- angular.forEach(keys, function (key) {
- if (cache[key] !== undefined) {
- result.push(cache[key]);
- }
- else if (useLs) {
- var value = localStorage.getItem(key);
- value = (value === undefined || value === null) ? false : JSON.parse(value);
- result.push(cache[key] = value);
- }
- else if (!useCs) {
- result.push(cache[key] = false);
- }
- else {
- allFound = false;
- }
- });
-
- if (allFound) {
- return $q.when(single ? result[0] : result);
- }
-
- var deferred = $q.defer();
-
- chrome.storage.local.get(keys, function (resultObj) {
- result = [];
- angular.forEach(keys, function (key) {
- var value = resultObj[key];
- value = value === undefined || value === null ? false : JSON.parse(value);
- result.push(cache[key] = value);
- });
-
- deferred.resolve(single ? result[0] : result);
- });
-
- return deferred.promise;
- };
-
- function setValue(obj) {
- var keyValues = {};
- angular.forEach(obj, function (value, key) {
- keyValues[testPrefix + key] = JSON.stringify(value);
- cache[testPrefix + key] = value;
- });
-
- if (useLs) {
- angular.forEach(keyValues, function (value, key) {
- localStorage.setItem(key, value);
- });
- return $q.when();
- }
-
- if (!useCs) {
- return $q.when();
- }
-
- var deferred = $q.defer();
-
- chrome.storage.local.set(keyValues, function () {
- deferred.resolve();
- });
-
- return deferred.promise;
- };
-
- function removeValue () {
- var keys = Array.prototype.slice.call(arguments);
-
- for (var i = 0; i < keys.length; i++) {
- keys[i] = testPrefix + keys[i];
- }
-
- angular.forEach(keys, function(key){
- delete cache[key];
- });
-
- if (useLs) {
- angular.forEach(keys, function(key){
- localStorage.removeItem(key);
- });
-
- return $q.when();
- }
-
- if (!useCs) {
- return $q.when();
- }
-
- var deferred = $q.defer();
-
- chrome.storage.local.remove(keys, function () {
- deferred.resolve();
- });
-
- return deferred.promise;
- };
-
- return {
- get: getValue,
- set: setValue,
- remove: removeValue
- };
-})
-
.service('AppUsersManager', function ($rootScope, $modal, $modalStack, $filter, $q, MtpApiFileManager, MtpApiManager, RichTextProcessor, SearchIndexManager, ErrorService) {
var users = {},
cachedPhotoLocations = {},
@@ -2176,7 +2057,7 @@ angular.module('myApp.services', [])
}
})
-.service('AppPhotosManager', function ($modal, $window, $timeout, $rootScope, MtpApiManager, MtpApiFileManager, AppUsersManager) {
+.service('AppPhotosManager', function ($modal, $window, $timeout, $rootScope, MtpApiManager, MtpApiFileManager, AppUsersManager, FileManager) {
var photos = {};
function savePhoto (apiPhoto) {
@@ -2373,48 +2254,25 @@ angular.module('myApp.services', [])
secret: fullPhotoSize.location.secret
};
- if (window.chrome && chrome.fileSystem && chrome.fileSystem.chooseEntry) {
-
- chrome.fileSystem.chooseEntry({
- type: 'saveFile',
- suggestedName: fileName,
- accepts: [{
- mimeTypes: [mimeType],
- extensions: [ext]
- }]
- }, function (writableFileEntry) {
- var downloadPromise = MtpApiFileManager.downloadFile(fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, {
- mime: mimeType,
- toFileEntry: writableFileEntry
- });
- downloadPromise.then(function (url) {
- console.log('file save done');
- }, function (e) {
- console.log('photo download failed', e);
- });
-
+ FileManager.chooseSave(fileName, ext, mimeType).then(function (writableFileEntry) {
+ MtpApiFileManager.downloadFile(
+ fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, {
+ mime: mimeType,
+ toFileEntry: writableFileEntry
+ }).then(function (url) {
+ console.log('file save done');
+ }, function (e) {
+ console.log('photo download failed', e);
});
- } else {
- var downloadPromise = MtpApiFileManager.downloadFile(fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, {mime: mimeType});
-
- downloadPromise.then(function (url) {
-
- var a = $('Download')
- .css({position: 'absolute', top: 1, left: 1})
- .attr('href', url)
- .attr('target', '_blank')
- .attr('download', fileName)
- .appendTo('body');
-
- a[0].dataset.downloadurl = [mimeType, fileName, url].join(':');
- a[0].click();
- $timeout(function () {
- a.remove();
- }, 100);
+ }, function () {
+ MtpApiFileManager.downloadFile(
+ fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, {mime: mimeType}
+ ).then(function (url) {
+ FileManager.download(url, mimeType, fileName);
}, function (e) {
console.log('photo download failed', e);
});
- }
+ });
};
$rootScope.openPhoto = openPhoto;
@@ -2433,7 +2291,7 @@ angular.module('myApp.services', [])
})
-.service('AppVideoManager', function ($rootScope, $modal, $window, $timeout, MtpApiFileManager, AppUsersManager) {
+.service('AppVideoManager', function ($rootScope, $modal, $window, $timeout, MtpApiFileManager, AppUsersManager, FileManager) {
var videos = {};
var videosForHistory = {};
@@ -2551,31 +2409,21 @@ angular.module('myApp.services', [])
mimeType = 'video/mpeg4',
fileName = 'video' + videoID + '.' + ext;
- if (window.chrome && chrome.fileSystem && chrome.fileSystem.chooseEntry) {
-
- chrome.fileSystem.chooseEntry({
- type: 'saveFile',
- suggestedName: fileName,
- accepts: [{
- mimeTypes: [mimeType],
- extensions: [ext]
- }]
- }, function (writableFileEntry) {
- var downloadPromise = MtpApiFileManager.downloadFile(video.dc_id, inputFileLocation, video.size, {
- mime: mimeType,
- toFileEntry: writableFileEntry
- });
- downloadPromise.then(function (url) {
- delete historyVideo.progress;
- console.log('file save done');
- }, function (e) {
- console.log('video download failed', e);
- historyVideo.progress.enabled = false;
- }, updateDownloadProgress);
-
- historyVideo.progress.cancel = downloadPromise.cancel;
+ FileManager.chooseSave(fileName, ext, mimeType).then(function (writableFileEntry) {
+ var downloadPromise = MtpApiFileManager.downloadFile(video.dc_id, inputFileLocation, video.size, {
+ mime: mimeType,
+ toFileEntry: writableFileEntry
});
- } else {
+ downloadPromise.then(function (url) {
+ delete historyVideo.progress;
+ console.log('file save done');
+ }, function (e) {
+ console.log('video download failed', e);
+ historyVideo.progress.enabled = false;
+ }, updateDownloadProgress);
+
+ historyVideo.progress.cancel = downloadPromise.cancel;
+ }, function () {
var downloadPromise = MtpApiFileManager.downloadFile(video.dc_id, inputFileLocation, video.size, {mime: mimeType});
downloadPromise.then(function (url) {
@@ -2586,25 +2434,14 @@ angular.module('myApp.services', [])
return
}
- var a = $('Download')
- .css({position: 'absolute', top: 1, left: 1})
- .attr('href', url)
- .attr('target', '_blank')
- .attr('download', fileName)
- .appendTo('body');
-
- a[0].dataset.downloadurl = [mimeType, fileName, url].join(':');
- a[0].click();
- $timeout(function () {
- a.remove();
- }, 100);
+ FileManager.download(url, mimeType, fileName);
}, function (e) {
console.log('video download failed', e);
historyVideo.progress.enabled = false;
}, updateDownloadProgress);
historyVideo.progress.cancel = downloadPromise.cancel;
- }
+ });
};
$rootScope.openVideo = openVideo;
@@ -2618,7 +2455,7 @@ angular.module('myApp.services', [])
}
})
-.service('AppDocsManager', function ($rootScope, $modal, $window, $timeout, MtpApiFileManager) {
+.service('AppDocsManager', function ($rootScope, $modal, $window, $timeout, MtpApiFileManager, FileManager) {
var docs = {};
var docsForHistory = {};
@@ -2693,34 +2530,23 @@ angular.module('myApp.services', [])
$rootScope.$broadcast('history_update');
}
+ var ext = (doc.file_name.split('.', 2) || [])[1] || '';
+ FileManager.chooseSave(doc.file_name, ext, doc.mime_type).then(function (writableFileEntry) {
+ var downloadPromise = MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, {
+ mime: doc.mime_type,
+ toFileEntry: writableFileEntry
+ });
- if (window.chrome && chrome.fileSystem && chrome.fileSystem.chooseEntry) {
- var ext = (doc.file_name.split('.', 2) || [])[1] || '';
-
- chrome.fileSystem.chooseEntry({
- type: 'saveFile',
- suggestedName: doc.file_name,
- accepts: [{
- mimeTypes: [doc.mime_type],
- extensions: [ext]
- }]
- }, function (writableFileEntry) {
- var downloadPromise = MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, {
- mime: doc.mime_type,
- toFileEntry: writableFileEntry
- });
-
- downloadPromise.then(function (url) {
- delete historyDoc.progress;
- console.log('file save done');
- }, function (e) {
- console.log('document download failed', e);
- historyDoc.progress.enabled = false;
- }, updateDownloadProgress);
+ downloadPromise.then(function (url) {
+ delete historyDoc.progress;
+ console.log('file save done');
+ }, function (e) {
+ console.log('document download failed', e);
+ historyDoc.progress.enabled = false;
+ }, updateDownloadProgress);
- historyDoc.progress.cancel = downloadPromise.cancel;
- });
- } else {
+ historyDoc.progress.cancel = downloadPromise.cancel;
+ }, function () {
var downloadPromise = MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, {mime: doc.mime_type});
downloadPromise.then(function (url) {
@@ -2734,18 +2560,7 @@ angular.module('myApp.services', [])
break;
default:
- var a = $('Download')
- .css({position: 'absolute', top: 1, left: 1})
- .attr('href', url)
- .attr('target', '_blank')
- .attr('download', doc.file_name)
- .appendTo('body');
-
- a[0].dataset.downloadurl = [doc.mime_type, doc.file_name, url].join(':');
- a[0].click();
- $timeout(function () {
- a.remove();
- }, 100);
+ FileManager.download(url, doc.mime_type, doc.file_name);
}
}, function (e) {
console.log('document download failed', e);
@@ -2753,7 +2568,7 @@ angular.module('myApp.services', [])
}, updateDownloadProgress);
historyDoc.progress.cancel = downloadPromise.cancel;
- }
+ });
}
$rootScope.downloadDoc = downloadDoc;
@@ -3354,7 +3169,7 @@ angular.module('myApp.services', [])
})
-.service('NotificationsManager', function ($rootScope, $window, $timeout, $interval, $q, MtpApiManager, AppPeersManager, IdleManager, AppConfigManager) {
+.service('NotificationsManager', function ($rootScope, $window, $timeout, $interval, $q, MtpApiManager, AppPeersManager, IdleManager, Storage) {
var notificationsUiSupport = 'Notification' in window;
var notificationsShown = {};
@@ -3493,13 +3308,13 @@ angular.module('myApp.services', [])
return false;
}
- AppConfigManager.get('notify_nosound', 'notify_volume').then(function (settings) {
+ Storage.get('notify_nosound', 'notify_volume').then(function (settings) {
if (!settings[0] && settings[1] === false || settings[1] > 0) {
playSound(settings[1] || 0.5);
}
})
- AppConfigManager.get('notify_nodesktop').then(function (noShow) {
+ Storage.get('notify_nodesktop').then(function (noShow) {
if (noShow) {
return;
}
@@ -3688,7 +3503,7 @@ angular.module('myApp.services', [])
})
-.service('ChangelogNotifyService', function (AppConfigManager, $rootScope, $http, $modal) {
+.service('ChangelogNotifyService', function (Storage, $rootScope, $http, $modal) {
function versionCompare (ver1, ver2) {
if (typeof ver1 !== 'string') {
@@ -3718,12 +3533,12 @@ angular.module('myApp.services', [])
}
function checkUpdate () {
- AppConfigManager.get('last_version').then(function (lastVersion) {
+ Storage.get('last_version').then(function (lastVersion) {
if (lastVersion != Config.App.version) {
if (lastVersion) {
showChangelog(lastVersion);
}
- AppConfigManager.set({last_version: Config.App.version});
+ Storage.set({last_version: Config.App.version});
}
})
}
diff --git a/app/webogram.appcache b/app/webogram.appcache
index dd794be2..70b57f2a 100644
--- a/app/webogram.appcache
+++ b/app/webogram.appcache
@@ -1,6 +1,6 @@
CACHE MANIFEST
-# 12
+# 15
NETWORK:
*