diff --git a/app/css/app.css b/app/css/app.css
index 7ac9490c..42e128ef 100644
--- a/app/css/app.css
+++ b/app/css/app.css
@@ -79,7 +79,7 @@ input[type="number"] {
}
.btn-success {
color: #ffffff;
- background-color: #6AC065;
+ background-color: #6ec26d;
}
.btn-success:hover,
@@ -94,7 +94,7 @@ input[type="number"] {
.btn-success:active,
.btn-success.active,
.open .dropdown-toggle.btn-success {
- background: #5aaf54;
+ background: #66b864;
background-image: none;
}
@@ -121,7 +121,7 @@ input[type="number"] {
.btn-primary {
color: #ffffff;
- background-color: #5d8db3;
+ background-color: #6490b1;
border-radius: 3px;
}
.btn-primary:hover,
@@ -425,7 +425,7 @@ input[type="number"] {
.modal-close-button i {
display: inline-block;
background: url(../img/icons/IconsetW.png) -15px -320px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
width: 12px;
height: 12px;
margin: 21px;
@@ -594,73 +594,31 @@ a.tg_radio_on:hover i.icon-radio {
.tg_range_wrap {
line-height: 18px;
}
-input.tg_range {
+.tg_slider_wrap {
+ position: relative;
cursor: pointer;
- outline: none !important;
- -webkit-appearance: none;
- width: 100%;
- max-width: 362px;
- display: inline-block;
- background: #c7c7c7;
- height: 3px;
line-height: 18px;
- vertical-align: top;
- margin: 8px 0;
- border-radius: 2px;
+ height: 18px;
}
-input.tg_range::-moz-range-track {
- cursor: pointer;
- outline: none !important;
- -webkit-appearance: none;
- width: 100%;
- max-width: 362px;
- display: inline-block;
+.tg_slider_track {
+ position: absolute;
background: #c7c7c7;
height: 3px;
- line-height: 18px;
- vertical-align: top;
margin: 8px 0;
border-radius: 2px;
-}
-input.tg_range::-webkit-slider-thumb {
- border: 0;
- -webkit-appearance: none;
- background: #568cb5;
- width: 12px;
- height: 12px;
- border-radius: 6px;
- overflow: hidden;
-}
-input.tg_range::-moz-range-thumb {
- border: 0;
- background: #568cb5;
- width: 12px;
- height: 12px;
- border-radius: 6px;
- overflow: hidden;
-}
-input.tg_range::-ms-track {
- color: transparent;
- border: 0;
- cursor: pointer;
- outline: none !important;
width: 100%;
- max-width: 362px;
- display: inline-block;
- background: #c7c7c7;
- height: 3px;
- line-height: 18px;
- vertical-align: top;
- margin: 8px 0;
- border-radius: 2px;
+ z-index: 2;
}
-input.tg_range::-ms-thumb {
+.tg_slider_thumb {
+ position: absolute;
border: 0;
background: #568cb5;
width: 12px;
height: 12px;
border-radius: 6px;
+ margin-top: 4px;
overflow: hidden;
+ z-index: 3;
}
@@ -910,7 +868,7 @@ img.welcome_logo {
font-size: 12px;
line-height: normal;
background: #F2F2F2 url(../img/icons/IconsetW.png) -6px -205px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
border: 1px solid #F2F2F2;
border-radius: 3px;
padding: 6px 20px 6px 30px;
@@ -938,7 +896,7 @@ img.welcome_logo {
height: 13px;
vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -15px -192px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
opacity: 0.6;
}
.is_1x .im_dialogs_search_clear {
@@ -1070,7 +1028,7 @@ a.im_dialog_selected .im_dialog_message_text {
}
.im_dialog_badge {
- background: #75BB72;
+ background: #6ec26d;
border-radius: 2px;
font-size: 10px;
padding: 3px 4px;
@@ -1144,7 +1102,7 @@ a.im_dialog_selected .im_dialog_date {
margin-left: 6px;
background: url(../img/icons/IconsetW.png) -17px -444px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
}
.is_1x .icon-caret {
background-image: url(../img/icons/IconsetW_1x.png);
@@ -1302,7 +1260,7 @@ div.im_message_video_thumb {
height: 42px;
background: url(../img/icons/IconsetW.png) 0 -590px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
z-index: 1;
}
.is_1x .icon-videoplay {
@@ -1329,26 +1287,37 @@ div.im_message_video_thumb {
height: 19px;
background: url(../img/icons/IconsetW.png) -14px -389px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
}
.is_1x .icon-geo-point {
background-image: url(../img/icons/IconsetW_1x.png);
}
-.im_message_iframe_video {
- position: relative;
- padding-bottom: 56.25%; /* 16/9 ratio */
- height: 0;
- overflow: hidden;
- margin-top: 5px;
+.im_message_media_embed {
+ position: relative;
+ height: 0;
+ overflow: hidden;
+ margin-top: 5px;
}
-.im_message_iframe_video iframe,
-.im_message_iframe_video webview {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
+.im_message_video_embed {
+ padding-bottom: 56.25%; /* 16/9 ratio */
+}
+.im_message_insta_embed {
+ padding-bottom: 122%;
+}
+.im_message_vine_embed {
+ padding-bottom: 100%;
+}
+.im_message_media_embed iframe,
+.im_message_media_embed webview {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+.im_message_twitter_embed > blockquote {
+ visibility: hidden;
}
.im_message_gif_wrap {
@@ -1370,42 +1339,70 @@ div.im_message_video_thumb {
z-index: 1;
}
+.im_message_video,
.im_message_document,
-.im_message_upload_file {
+.im_message_upload_file,
+.im_message_audio {
margin-top: 3px;
- border-radius: 3px;
- display: inline-block;
- width: 340px;
+ width: 317px;
}
.im_message_audio {
margin-top: 3px;
}
-.icon-document,
-.icon-photo,
-.icon-video {
+.im_message_file_button {
display: block;
+ background: rgba(218,228,234,0.50);
float: left;
- width: 38px;
- height: 38px;
- vertical-align: text-top;
-
- background: #F2F2F2 url(../img/icons/IconsetW.png) -2px -229px no-repeat;
- background-size: 42px 971px;
- border-radius: 3px;
+ width: 42px;
+ height: 42px;
+ border-radius: 0;
margin-right: 10px;
}
-.is_1x .icon-document,
-.is_1x .icon-photo,
-.is_1x .icon-video {
+.im_message_file_button_icon {
+ display: inline-block;
+ line-height: 0;
+ /*#dae4ea 50%*/
+ background: url(../img/icons/IconsetW.png) -15px -953px no-repeat;
+ background-size: 42px 1171px;
+ width: 12px;
+ height: 20px;
+ margin: 11px 15px;
+}
+.is_1x .im_message_file_button_icon {
background-image: url(../img/icons/IconsetW_1x.png);
}
+.im_message_file_button_dl_doc .im_message_file_button_icon {
+ background-position: -13px -983px;
+ width: 16px;
+ height: 18px;
+ margin: 12px 13px;
+}
+.im_message_file_button_dl_audio {
+ background: #6490b1;
+ border-radius: 2px;
+}
+.im_message_file_button_dl_audio .im_message_file_button_icon {
+ display: block;
+ width: 15px;
+ height: 18px;
+ background: url(../img/icons/IconsetW.png) -15px -897px no-repeat;
+ background-size: 42px 1171px;
+ margin: 12px 13.5px;
+}
+.is_1x .im_message_file_button_dl_audio .im_message_file_button_icon {
+ background-image: url(../img/icons/IconsetW_1x.png);
+ background-position: -15px -899px;
+}
+.im_message_file_button_dl_audio .audio_player_btn_icon_pause,
+.is_1x .im_message_file_button_dl_audio .audio_player_btn_icon_pause {
+ width: 12px;
+ height: 16px;
+ background-position: -15px -927px;
+ margin: 13px 15px;
+}
.im_message_selected .icon-document,
-.im_message_selected .icon-photo,
-.im_message_selected .icon-video,
-.im_history_selectable .im_message_outer_wrap:hover .icon-document,
-.im_history_selectable .im_message_outer_wrap:hover .icon-photo,
-.im_history_selectable .im_message_outer_wrap:hover .icon-video {
+.im_history_selectable .im_message_outer_wrap:hover .icon-document {
background-color: #dae6f0;
background-position: -2px -542px;
}
@@ -1440,19 +1437,19 @@ img.im_message_document_thumb {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
- width: 290px;
+ width: 265px;
padding: 0 0 1px;
}
.im_message_document_actions {
- width: 290px;
+ width: 265px;
}
.im_message_document_name {
- color: #222;
+ color: #3a6d99;
display: inline-block;
font-weight: bold;
- max-width: 200px;
+ max-width: 170px;
overflow: hidden;
vertical-align: text-top;
white-space: nowrap;
@@ -1461,63 +1458,30 @@ img.im_message_document_thumb {
.im_message_document_size {
color: #999;
padding-left: 2px;
+ vertical-align: text-top;
}
.im_message_document_actions a,
.audio_player_actions a {
margin-right: 10px;
}
-.audio_player_button {
- width: 38px;
- height: 38px;
- padding-left: 12px;
- padding-right: 12px;
- border-radius: 3px;
- margin-right: 12px;
-}
-.audio_player_btn_icon {
- display: block;
- width: 14px;
- height: 17px;
- background: url(../img/icons/IconsetW.png) -15px -897px no-repeat;
- background-size: 42px 971px;
-}
-.is_1x .audio_player_btn_icon {
- background-image: url(../img/icons/IconsetW_1x.png);
- background-position: -15px -898px;
-}
-.audio_player_btn_icon_pause,
-.is_1x .audio_player_btn_icon_pause,
-.audio_player_btn_icon_cancel,
-.is_1x .audio_player_btn_icon_cancel {
- width: 13px;
- height: 15px;
- background-position: -15px -923px;
-}
-.is_1x .audio_player_btn_icon_pause,
-.is_1x .audio_player_btn_icon_cancel {
- background-position: -15px -925px;
-}
.audio_player_title_wrap {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
- padding: 0 0 1px;
- line-height: 18px;
+ padding: 1px 0 1px;
+ line-height: 16px;
height: 19px;
+ width: 200px;
}
.audio_player_title {
- color: #222;
display: inline-block;
font-weight: bold;
- max-width: 200px;
+ max-width: 85px;
overflow: hidden;
vertical-align: text-top;
white-space: nowrap;
text-overflow: ellipsis;
}
-.audio_player_title:hover {
- color: #222;
-}
.audio_player_meta {
overflow: hidden;
vertical-align: text-top;
@@ -1528,78 +1492,138 @@ img.im_message_document_thumb {
color: #999;
padding-left: 2px;
}
+.audio_player_actions {
+ margin-top: 3px;
+}
-.im_message_upload_progress_wrap,
-.im_message_download_progress_wrap {
- margin-top: 5px;
- width: 290px;
+.audio_player_seek_slider {
+ float: left;
+ margin-right: 15px;
+ width: 200px;
+}
+.audio_player_seek_slider .tg_slider_wrap {
+ height: 16px;
+ line-height: 16px;
+}
+.audio_player_seek_slider .tg_slider_thumb {
+ background: #6490b1;
+ width: 4px;
+ height: 16px;
+ line-height: 16px;
+ margin-top: 0;
+ border-radius: 0;
+}
+.audio_player_seek_slider .tg_slider_track {
+ margin: 6px 0;
+ background: rgba(218,228,234,0.50);
+ height: 4px;
+ border-radius: 0;
+}
+.audio_player_seek_slider .tg_slider_track_fill {
+ background: #6490b1;
+ height: 4px;
+ width: 0;
+}
+
+.audio_player_volume_slider {
+ width: 50px;
+ float: left;
+}
+.audio_player_volume_slider .tg_slider_wrap {
+ height: 16px;
+ line-height: 16px;
+}
+.audio_player_volume_slider .tg_slider_thumb {
+ display: none;
+ background: #6490b1;
+ width: 4px;
+ height: 8px;
+ line-height: 16px;
+ margin-top: 4px;
+ border-radius: 0;
+}
+.audio_player_volume_slider:hover .tg_slider_thumb {
+ display: block;
+}
+.audio_player_volume_slider .tg_slider_track {
+ margin: 6px 0;
+ background: rgba(218,228,234,0.50);
+ height: 4px;
+}
+.audio_player_volume_slider .tg_slider_track_fill {
+ background: #6490b1;
+ height: 4px;
+ width: 0;
}
+
.audio_player_progress_wrap {
- margin-top: 5px;
- max-width: 290px;
- border-radius: 2px;
overflow: hidden;
}
+.audio_player_progress_wrap .tg_down_progress {
+ margin-top: 5px;
+}
+
+
+
+
+.im_message_upload_progress_wrap,
+.im_message_download_progress_wrap {
+ margin-top: 5px;
+ width: 200px;
+}
.im_message_document_thumbed .im_message_document_name_wrap,
.im_message_document_thumbed .im_message_upload_progress_wrap,
.im_message_document_thumbed .im_message_download_progress_wrap,
.im_message_document_thumbed .im_message_document_actions {
- width: 230px;
+ width: 207px;
}
.im_message_document_thumbed .im_message_document_name {
- max-width: 150px;
+ max-width: 110px;
}
.im_message_video .im_message_document_name_wrap,
.im_message_video .im_message_download_progress_wrap,
.im_message_video .im_message_document_actions {
- width: 150px;
+ width: 152px;
}
.im_message_video .im_message_document_name_wrap {
margin-top: 5px;
}
+.im_message_cancelable_progress_wrap,
+.im_message_playback_progress_wrap {
+ margin-top: 4px;
+ /*width: 265px;*/
+}
.im_message_media_progress_cancel {
- font-size: 11px;
- margin-left: 10px;
+ margin-left: 15px;
line-height: 100%;
+ width: 50px;
+ display: block;
+ overflow: hidden;
}
.tg_up_progress,
-.tg_down_progress,
-.tg_play_progress {
- height: 5px;
+.tg_down_progress {
+ height: 4px;
margin: 0;
padding: 0;
- background: #F2F2F2;
+ background: rgba(218,228,234,0.50);
border: 0;
border-radius: 0;
-webkit-box-shadow: none;
box-shadow: none;
}
.tg_up_progress .progress-bar,
-.tg_down_progress .progress-bar,
-.tg_play_progress .progress-bar {
- height: 5px;
- line-height: 5px;
+.tg_down_progress .progress-bar {
+ height: 4px;
+ line-height: 4px;
background: #6B9ABD;
border-radius: 0;
overflow: hidden;
-webkit-box-shadow: none;
box-shadow: none;
}
-.tg_play_progress {
- background: #e4e9ed;
- border-radius: 1px;
-}
-.tg_play_progress .progress-bar {
- background: #628fb2;
- border-radius: 1px;
- /*-webkit-transition: width 1s linear;
- transition: width 1s linear;*/
- -webkit-transition: none;
- transition: none;
-}
@@ -1759,7 +1783,7 @@ textarea.im_message_field {
height: 23px;
vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -12px -68px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
opacity: 0.8;
}
.is_1x .icon-paperclip {
@@ -1787,7 +1811,7 @@ textarea.im_message_field {
height: 23px;
vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -10px -4px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
opacity: 0.8;
}
.is_1x .icon-emoji {
@@ -1836,7 +1860,7 @@ textarea.im_message_field {
height: 21px;
vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -9px -132px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
opacity: 0.8;
}
.is_1x .icon-camera {
@@ -1852,7 +1876,7 @@ textarea.im_message_field {
.icon-online {
- background: #6DBF69;
+ background: #6ec26d;
border: 1px solid #FFF;
display: block;
width: 11px;
@@ -1868,13 +1892,41 @@ textarea.im_message_field {
.media_modal_wrap .modal-body {
padding: 19px 18px 17px;
}
-a.img_fullsize {
+a.img_fullsize,
+.img_fullsize_wrap {
display: block;
text-align: center;
}
img.img_fullsize {
margin: 0 auto;
}
+.document_modal_image_wrap {
+ overflow: auto;
+}
+.document_fullsize_wrap {
+ display: none;
+ cursor: zoom-in;
+ text-align: center;
+}
+.document_fullsize_zoomed {
+ cursor: zoom-out;
+}
+.document_fullsize_img {
+ /*max-width: 100%;*/
+ -webkit-user-select: none;
+}
+.document_fullsize_zoomed .document_fullsize_img {
+ /*min-width: 100%;*/
+ -webkit-user-select: none;
+ image-rendering: optimizeSpeed; /* FUCK SMOOTHING, GIVE ME SPEED */
+ image-rendering: -moz-crisp-edges; /* Firefox */
+ image-rendering: -o-crisp-edges; /* Opera */
+ image-rendering: -webkit-optimize-contrast; /* Chrome (and eventually Safari) */
+ image-rendering: optimize-contrast; /* CSS3 Proposed */
+ -ms-interpolation-mode: nearest-neighbor; /* IE8+ */
+
+}
+
.media_modal_info {
color: #999;
margin: 20px 0 0;
@@ -1886,8 +1938,12 @@ img.img_fullsize {
margin-left: 15px;
}
.media_modal_author {
+ color: inherit;
font-weight: bold;
}
+.media_modal_author:hover {
+ color: inherit;
+}
.non_osx .media_modal_author {
font-size: 12px;
}
@@ -1914,7 +1970,7 @@ img.img_fullsize {
overflow: auto;
line-height: 17px;
- border: 1px solid #d9dbde;
+ border: 1px solid #d2dbe3;
border-radius: 2px;
-webkit-box-shadow: none;
box-shadow: none;
@@ -2179,7 +2235,7 @@ a:hover .icon-twitter {
font-size: 12px;
line-height: normal;
background: url(../img/icons/IconsetW.png) -6px -205px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
border: 1px solid #d9dbde;
border-radius: 3px;
padding: 6px 15px 6px 30px;
@@ -2198,7 +2254,7 @@ a:hover .icon-twitter {
height: 13px;
vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -15px -192px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
opacity: 0.6;
}
.is_1x .contacts_modal_search_clear {
@@ -2314,7 +2370,7 @@ img.chat_modal_participant_photo {
width: 25px;
height: 25px;
background: url(../img/icons/IconsetW.png) -9px -516px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
opacity: 0.5;
}
.is_1x .icon-contact-tick {
@@ -2386,7 +2442,7 @@ img.chat_modal_participant_photo {
.im_message_focus .audio_player_duration,
.im_message_focus .audio_player_size,
.im_message_focus .im_message_fwd_date {
- color: #68839c;
+ color: #899daf;
}
.icon-select-tick {
@@ -2395,7 +2451,7 @@ img.chat_modal_participant_photo {
height: 26px;
margin: 13px 0 0 40px;
background: url(../img/icons/IconsetW.png) -9px -516px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
}
.is_1x .icon-select-tick {
background-image: url(../img/icons/IconsetW_1x.png);
@@ -2514,7 +2570,7 @@ ce671b orange
font-size: 12px;
line-height: normal;
background: #F2F2F2 url(../img/icons/IconsetW.png) -6px -205px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
border: 1px solid #F2F2F2;
border-radius: 3px;
padding: 6px 20px 6px 30px;
@@ -2537,7 +2593,7 @@ ce671b orange
height: 13px;
vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -15px -192px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
opacity: 0.6;
}
.is_1x .countries_modal_search_clear {
@@ -2707,4 +2763,9 @@ ce671b orange
font-size: 14px;
line-height: 160%;
margin: 25px 0 30px;
+}
+
+#nacl_listener {
+ position: absolute;
+ left: -10000px;
}
\ No newline at end of file
diff --git a/app/css/desktop.css b/app/css/desktop.css
index ffbcddac..d39a02c9 100644
--- a/app/css/desktop.css
+++ b/app/css/desktop.css
@@ -179,7 +179,7 @@ a.footer_lang_link.active:active {
.im_history_col .nano > .nano-pane,
.contacts_modal_col .nano > .nano-pane,
.im_dialogs_modal_col .nano > .nano-pane {
- background : rgba(3,36,64,0.08);
+ background : rgba(216,223,225,0.45); /*45% d8dfe5*/
width : 9px;
right: 0;
top: 0;
@@ -218,7 +218,7 @@ a.footer_lang_link.active:active {
.im_history_col .nano > .nano-pane > .nano-slider,
.contacts_modal_col .nano > .nano-pane > .nano-slider,
.im_dialogs_modal_col .nano > .nano-pane > .nano-slider {
- background : rgba(3,46,79,0.22);
+ background : rgba(137,160,179,0.50); /*50% 89a0b3*/
margin: 0;
-moz-border-radius : 2px;
-webkit-border-radius : 2px;
@@ -324,7 +324,7 @@ a.footer_lang_link.active:active {
}
.icon-message-status {
- background: #43A4DB;
+ background: #6ba2cb;
border: 0;
display: block;
width: 10px;
@@ -381,6 +381,7 @@ a.footer_lang_link.active:active {
font-size: 13px;
line-height: 17px;
min-width: 60px;
+ border-radius: 2px;
}
.im_message_selected .im_message_date,
@@ -393,7 +394,7 @@ a.footer_lang_link.active:active {
.im_history_selectable .im_message_outer_wrap:hover .im_message_audio_duration,
.im_history_selectable .im_message_outer_wrap:hover .im_message_audio_size,
.im_history_selectable .im_message_outer_wrap:hover .im_message_fwd_date {
- color: #68839c;
+ color: #899daf;
}
.im_content_message_select_area {
@@ -492,7 +493,7 @@ a.footer_lang_link.active:active {
.im_panel_own_photo {
width: 50px;
height: 50px;
- border-radius: 3px;
+ border-radius: 0;
overflow: hidden;
}
div.im_panel_peer_photo {
@@ -505,7 +506,7 @@ div.im_panel_own_photo {
}
.im_panel_peer_online {
- background: #6DBF69;
+ background: #6ec26d;
border: 1px solid #FFF;
display: block;
width: 11px;
@@ -516,6 +517,10 @@ div.im_panel_own_photo {
margin-top: -7px;
margin-left: 43px;
}
+.emoji-wysiwyg-editor,
+.im_message_field {
+ border-radius: 0;
+}
/* Peer modals */
.user_modal_window .modal-dialog {
@@ -787,6 +792,12 @@ div.im_panel_own_photo {
display: none;
}
+.settings_volume_slider {
+ width: 100%;
+ max-width: 362px;
+ display: inline-block;
+}
+
.im_message_selected .im_message_outer_wrap,
.im_message_focus .im_message_outer_wrap {
diff --git a/app/css/mobile.css b/app/css/mobile.css
index f77916b1..77deadb9 100644
--- a/app/css/mobile.css
+++ b/app/css/mobile.css
@@ -130,7 +130,7 @@ html {
vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -15px -835px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
opacity: 0.8;
}
.is_1x .icon-back {
@@ -535,9 +535,7 @@ img.im_message_video_thumb,
color: #93a2ae;
}
-.im_message_out .icon-document,
-.im_message_out .icon-photo,
-.im_message_out .icon-video {
+.im_message_out .icon-document {
background-color: #dae6f0;
background-position: -2px -542px;
}
@@ -961,7 +959,7 @@ a.mobile_modal_action .tg_checkbox_label {
.im_submit:active,
.im_submit:hover {
background: url(../img/icons/IconsetW.png) 2px -860px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
color: transparent;
box-shadow: none;
}
@@ -985,7 +983,7 @@ a.mobile_modal_action .tg_checkbox_label {
height: 23px;
vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -12px -68px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
opacity: 0.8;
}
.is_1x .icon-paperclip {
@@ -1022,7 +1020,7 @@ a.mobile_modal_action .tg_checkbox_label {
opacity: 1;
margin: 0;
background: url(../img/icons/IconsetW.png) -10px -771px no-repeat;
- background-size: 42px 971px;
+ background-size: 42px 1171px;
}
.is_1x .icon-emoji {
background-image: url(../img/icons/IconsetW_1x.png);
@@ -1119,4 +1117,9 @@ a.mobile_modal_action .tg_checkbox_label {
}
.countries_scrollable_wrap a.countries_modal_country {
padding: 8px 8px;
+}
+
+.import_modal_phonebook_wrap {
+ margin-top: 40px;
+ text-align: center;
}
\ No newline at end of file
diff --git a/app/img/icons/IconsetW.png b/app/img/icons/IconsetW.png
index 3b2b0cd4..39e592bf 100644
Binary files a/app/img/icons/IconsetW.png and b/app/img/icons/IconsetW.png differ
diff --git a/app/img/icons/IconsetW_1x.png b/app/img/icons/IconsetW_1x.png
index f1fa3182..92d5ec04 100644
Binary files a/app/img/icons/IconsetW_1x.png and b/app/img/icons/IconsetW_1x.png differ
diff --git a/app/index.html b/app/index.html
index 7d437a38..2e4a48d3 100644
--- a/app/index.html
+++ b/app/index.html
@@ -56,8 +56,10 @@
+
+
diff --git a/app/js/controllers.js b/app/js/controllers.js
index 47ab1d5e..e147cf98 100644
--- a/app/js/controllers.js
+++ b/app/js/controllers.js
@@ -316,7 +316,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
ChangelogNotifyService.checkUpdate();
})
- .controller('AppIMController', function ($scope, $location, $routeParams, $modal, $rootScope, $modalStack, MtpApiManager, AppUsersManager, ContactsSelectService, ChangelogNotifyService, ErrorService, AppRuntimeManager) {
+ .controller('AppIMController', function ($scope, $location, $routeParams, $modal, $rootScope, $modalStack, MtpApiManager, AppUsersManager, AppChatsManager, ContactsSelectService, ChangelogNotifyService, ErrorService, AppRuntimeManager) {
$scope.$on('$routeUpdate', updateCurDialog);
@@ -400,9 +400,9 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.showPeerInfo = function () {
if ($scope.curDialog.peerID > 0) {
- $rootScope.openUser($scope.curDialog.peerID)
+ AppUsersManager.openUser($scope.curDialog.peerID)
} else if ($scope.curDialog.peerID < 0) {
- $rootScope.openChat(-$scope.curDialog.peerID)
+ AppChatsManager.openChat(-$scope.curDialog.peerID)
}
};
@@ -467,7 +467,6 @@ angular.module('myApp.controllers', ['myApp.i18n'])
peersInDialogs = {},
contactsShown;
- MtpApiManager.invokeApi('account.updateStatus', {offline: false});
$scope.$on('dialogs_need_more', function () {
// console.log('on need more');
showMoreDialogs();
@@ -629,7 +628,8 @@ angular.module('myApp.controllers', ['myApp.i18n'])
if (error.code == 401) {
MtpApiManager.logOut()['finally'](function () {
- $location.url('/login');
+ location.hash = '/login';
+ AppRuntimeManager.reload();
});
error.handled = true;
}
@@ -1765,6 +1765,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
})
.controller('VideoModalController', function ($scope, $rootScope, $modalInstance, PeersSelectService, AppMessagesManager, AppVideoManager, AppPeersManager, ErrorService) {
+
$scope.video = AppVideoManager.wrapForFull($scope.videoID);
$scope.progress = {enabled: false};
@@ -1799,6 +1800,38 @@ angular.module('myApp.controllers', ['myApp.i18n'])
});
})
+ .controller('DocumentModalController', function ($scope, $rootScope, $modalInstance, PeersSelectService, AppMessagesManager, AppDocsManager, AppPeersManager, ErrorService) {
+
+ $scope.document = AppDocsManager.wrapForHistory($scope.docID);
+
+ $scope.forward = function () {
+ var messageID = $scope.messageID;
+ PeersSelectService.selectPeer({confirm_type: 'FORWARD_PEER'}).then(function (peerString) {
+ var peerID = AppPeersManager.getPeerID(peerString);
+ AppMessagesManager.forwardMessages(peerID, [messageID]).then(function () {
+ $rootScope.$broadcast('history_focus', {peerString: peerString});
+ });
+ });
+ };
+
+ $scope['delete'] = function () {
+ var messageID = $scope.messageID;
+ ErrorService.confirm({type: 'MESSAGE_DELETE'}).then(function () {
+ AppMessagesManager.deleteMessages([messageID]);
+ });
+ };
+
+ $scope.download = function () {
+ AppDocsManager.saveDocFile($scope.docID);
+ };
+
+ $scope.$on('history_delete', function (e, historyUpdate) {
+ if (historyUpdate.msgs[$scope.messageID]) {
+ $modalInstance.dismiss();
+ }
+ });
+ })
+
.controller('UserModalController', function ($scope, $location, $rootScope, $modal, AppUsersManager, MtpApiManager, NotificationsManager, AppPhotosManager, AppMessagesManager, AppPeersManager, PeersSelectService, ErrorService) {
var peerString = AppUsersManager.getUserString($scope.userID);
@@ -2087,7 +2120,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}
});
- $scope.notify = {};
+ $scope.notify = {volume: 0.5};
$scope.send = {};
$scope.$watch('photo.file', onPhotoSelected);
@@ -2177,31 +2210,30 @@ angular.module('myApp.controllers', ['myApp.i18n'])
if (settings[1]) {
$scope.notify.volume = 0;
} else if (settings[3] !== false) {
- $scope.notify.volume = settings[3] > 0 && Math.ceil(settings[3] * 10) || 0;
+ $scope.notify.volume = settings[3] > 0 && settings[3] <= 1.0 ? settings[3] : 0;
} else {
- $scope.notify.volume = 5;
+ $scope.notify.volume = 0.5;
}
$scope.notify.canVibrate = NotificationsManager.getVibrateSupport();
$scope.notify.vibrate = !settings[4];
$scope.notify.volumeOf4 = function () {
- return 1 + Math.ceil(($scope.notify.volume - 1) / 3.3);
+ return 1 + Math.ceil(($scope.notify.volume - 0.1) / 0.33);
};
$scope.toggleSound = function () {
if ($scope.notify.volume) {
$scope.notify.volume = 0;
} else {
- $scope.notify.volume = 5;
+ $scope.notify.volume = 0.5;
}
}
var testSoundPromise;
$scope.$watch('notify.volume', function (newValue, oldValue) {
if (newValue !== oldValue) {
- var storeVolume = newValue / 10;
- Storage.set({notify_volume: storeVolume});
+ Storage.set({notify_volume: newValue});
Storage.remove('notify_nosound');
NotificationsManager.clear();
@@ -2209,7 +2241,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$timeout.cancel(testSoundPromise);
}
testSoundPromise = $timeout(function () {
- NotificationsManager.testSound(storeVolume);
+ NotificationsManager.testSound(newValue);
}, 500);
}
});
diff --git a/app/js/directives.js b/app/js/directives.js
index 9dff598f..c7761ea7 100644
--- a/app/js/directives.js
+++ b/app/js/directives.js
@@ -177,19 +177,43 @@ angular.module('myApp.directives', ['myApp.filters'])
templateUrl: templateUrl('message_attach_photo')
};
})
- .directive('myMessageVideo', function() {
+ .directive('myMessageVideo', function(AppVideoManager) {
return {
- templateUrl: templateUrl('message_attach_video')
- };
- })
- .directive('myMessageDocument', function() {
- return {
- templateUrl: templateUrl('message_attach_document')
+ scope: {
+ 'video': '=myMessageVideo',
+ 'messageId': '=messageId'
+ },
+ templateUrl: templateUrl('message_attach_video'),
+ link: function ($scope, element, attrs) {
+ AppVideoManager.updateVideoDownloaded($scope.video.id);
+ $scope.videoSave = function () {
+ AppVideoManager.saveVideoFile($scope.video.id);
+ };
+ $scope.videoOpen = function () {
+ AppVideoManager.openVideo($scope.video.id, $scope.messageId);
+ };
+ }
};
})
- .directive('myMessageAudio', function() {
+ .directive('myMessageDocument', function(AppDocsManager) {
return {
- templateUrl: templateUrl('message_attach_audio')
+ scope: {
+ 'document': '=myMessageDocument',
+ 'messageId': '=messageId'
+ },
+ templateUrl: templateUrl('message_attach_document'),
+ link: function ($scope, element, attrs) {
+ AppDocsManager.updateDocDownloaded($scope.document.id);
+ $scope.docSave = function () {
+ AppDocsManager.saveDocFile($scope.document.id);
+ };
+ $scope.docOpen = function () {
+ if (!$scope.document.withPreview) {
+ return $scope.download();
+ }
+ AppDocsManager.openDoc($scope.document.id, $scope.messageId);
+ };
+ }
};
})
.directive('myMessageMap', function() {
@@ -868,7 +892,7 @@ angular.module('myApp.directives', ['myApp.filters'])
})
- .directive('mySendForm', function ($timeout, $modalStack, Storage, ErrorService, $interpolate) {
+ .directive('mySendForm', function ($timeout, $modalStack, $http, $interpolate, Storage, ErrorService) {
return {
link: link,
@@ -1055,10 +1079,12 @@ angular.module('myApp.directives', ['myApp.filters'])
}
function onPastedImageEvent (e) {
- var element = e && e.target;
- var src;
- if (element && (src = element.src) && src.indexOf('data') === 0) {
- element.parentNode.removeChild(element);
+ var element = (e.originalEvent || e).target,
+ src = (element || {}).src || '',
+ remove = false;
+
+ if (src.substr(0, 5) == 'data:') {
+ remove = true;
src = src.substr(5).split(';');
var contentType = src[0];
var base64 = atob(src[1].split(',')[1]);
@@ -1074,6 +1100,15 @@ angular.module('myApp.directives', ['myApp.filters'])
$scope.draftMessage.files = [blob];
$scope.draftMessage.isMedia = true;
});
+ setZeroTimeout(function () {
+ element.parentNode.removeChild(element);
+ })
+ }
+ else if (src && !src.match(/img\/blank\.gif/)) {
+ var replacementNode = document.createTextNode(' ' + src + ' ');
+ setTimeout(function () {
+ element.parentNode.replaceChild(replacementNode, element);
+ }, 100);
}
};
@@ -1316,7 +1351,7 @@ angular.module('myApp.directives', ['myApp.filters'])
})
- .directive('myLoadVideo', function($sce, MtpApiFileManager, _) {
+ .directive('myLoadVideo', function($sce, AppVideoManager, _) {
return {
link: link,
@@ -1329,37 +1364,12 @@ angular.module('myApp.directives', ['myApp.filters'])
function link ($scope, element, attrs) {
- $scope.progress = {enabled: true, percent: 1};
- $scope.player = {};
-
- var inputLocation = {
- _: 'inputVideoFileLocation',
- id: $scope.video.id,
- access_hash: $scope.video.access_hash
- };
+ var downloadPromise = AppVideoManager.downloadVideo($scope.video.id);
- var hasQt = false, i;
- if (navigator.plugins) {
- for (i = 0; i < navigator.plugins.length; i++) {
- if (navigator.plugins[i].name.indexOf('QuickTime') >= 0) {
- hasQt = true;
- }
- }
- }
-
- var downloadPromise = MtpApiFileManager.downloadFile($scope.video.dc_id, inputLocation, $scope.video.size, {mime: 'video/mp4'});
-
- downloadPromise.then(function (url) {
- $scope.progress.enabled = false;
- // $scope.progress = {enabled: true, percent: 50};
- $scope.player.hasQuicktime = hasQt;
- $scope.player.quicktime = false;
- $scope.player.src = $sce.trustAsResourceUrl(url);
+ downloadPromise.then(function () {
$scope.$emit('ui_height');
}, function (e) {
console.log('Download video failed', e, $scope.video);
- $scope.progress.enabled = false;
- $scope.player.src = '';
if (e && e.type == 'FS_BROWSER_UNSUPPORTED') {
$scope.error = {html: _('error_browser_no_local_file_system_video_md', {
@@ -1371,8 +1381,6 @@ angular.module('myApp.directives', ['myApp.filters'])
$scope.error = {text: _('error_video_download_failed'), error: e};
}
- }, function (progress) {
- $scope.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
});
$scope.$emit('ui_height');
@@ -1384,7 +1392,7 @@ angular.module('myApp.directives', ['myApp.filters'])
})
- .directive('myLoadGif', function($rootScope, MtpApiFileManager) {
+ .directive('myLoadGif', function(AppDocsManager) {
return {
link: link,
@@ -1396,21 +1404,13 @@ angular.module('myApp.directives', ['myApp.filters'])
function link ($scope, element, attrs) {
- var downloadPromise = false,
- inputFileLocation = {
- _: 'inputDocumentFileLocation',
- id: $scope.document.id,
- access_hash: $scope.document.access_hash
- };
+ var downloadPromise = false;
$scope.isActive = false;
- $scope.document.url = MtpApiFileManager.getCachedFile(inputFileLocation);
-
- /*return $scope.document.progress = {enabled: true, percent: 30, total: $scope.document.size};*/
$scope.toggle = function (e) {
if (checkClick(e, true)) {
- $rootScope.downloadDoc($scope.document.id);
+ AppDocsManager.saveDocFile($scope.document.id);
return false;
}
@@ -1426,33 +1426,113 @@ angular.module('myApp.directives', ['myApp.filters'])
return;
}
- $scope.document.progress = {enabled: true, percent: 1, total: $scope.document.size};
-
- downloadPromise = MtpApiFileManager.downloadFile(
- $scope.document.dc_id,
- inputFileLocation,
- $scope.document.size,
- null,
- {mime: $scope.document.mime_type}
- );
+ downloadPromise = AppDocsManager.downloadDoc($scope.document.id);
- downloadPromise.then(function (url) {
- $scope.document.url = url;
+ downloadPromise.then(function () {
$scope.isActive = true;
- delete $scope.document.progress;
- console.log('file save done');
$scope.$emit('ui_height');
- }, function () {
- $scope.document.progress.enabled = false;
- }, function (progress) {
- console.log('dl progress', progress);
- $scope.document.progress.done = progress.done;
- $scope.document.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
})
}
}
})
+ .directive('myLoadDocument', function(MtpApiFileManager, AppDocsManager) {
+
+ return {
+ link: link,
+ templateUrl: templateUrl('full_document'),
+ scope: {
+ document: '=myLoadDocument'
+ }
+ };
+
+ function updateModalWidth(element, width) {
+ while (element && !$(element).hasClass('modal-dialog')) {
+ element = element.parentNode;
+ }
+ if (element) {
+ $(element).width(width + (Config.Mobile ? 0 : 36));
+ }
+ }
+
+ function link ($scope, element, attrs) {
+ var loaderWrap = $('.document_fullsize_with_progress_wrap', element);
+ var fullSizeWrap = $('.document_fullsize_wrap', element);
+ var fullSizeImage = $('.document_fullsize_img', element);
+
+ var fullWidth = $(window).width() - (Config.Mobile ? 20 : 36);
+ var fullHeight = $(window).height() - 150;
+
+ $scope.imageWidth = fullWidth;
+ $scope.imageHeight = fullHeight;
+
+ var thumbPhotoSize = $scope.document.thumb;
+
+ if (thumbPhotoSize && thumbPhotoSize._ != 'photoSizeEmpty') {
+ var wh = calcImageInBox(thumbPhotoSize.width, thumbPhotoSize.height, fullWidth, fullHeight);
+ $scope.imageWidth = wh.w;
+ $scope.imageHeight = wh.h;
+
+ $scope.thumbSrc = MtpApiFileManager.getCachedFile(thumbPhotoSize.location);
+ }
+
+ $scope.frameWidth = Math.max($scope.imageWidth, Math.min(600, fullWidth))
+ $scope.frameHeight = $scope.imageHeight;
+
+ onContentLoaded(function () {
+ $scope.$emit('ui_height');
+ });
+
+ updateModalWidth(element[0], $scope.frameWidth);
+
+ var checkSizesInt;
+ var realImageWidth, realImageHeight;
+ AppDocsManager.downloadDoc($scope.document.id).then(function (url) {
+ var image = new Image();
+ var limit = 100; // 2 sec
+ var checkSizes = function (e) {
+ if ((!image.height || !image.width) && --limit) {
+ return;
+ }
+ realImageWidth = image.width;
+ realImageHeight = image.height;
+ clearInterval(checkSizesInt);
+
+ var defaultWh = calcImageInBox(image.width, image.height, fullWidth, fullHeight, true);
+ var zoomedWh = {w: realImageWidth, h: realImageHeight};
+ if (defaultWh.w >= zoomedWh.w && defaultWh.h >= zoomedWh.h) {
+ zoomedWh.w *= 4;
+ zoomedWh.h *= 4;
+ }
+
+ var zoomed = true;
+ $scope.toggleZoom = function () {
+ zoomed = !zoomed;
+ var imageWidth = (zoomed ? zoomedWh : defaultWh).w;
+ var imageHeight = (zoomed ? zoomedWh : defaultWh).h;
+ fullSizeImage.css({
+ width: imageWidth,
+ height: imageHeight,
+ marginTop: $scope.frameHeight > imageHeight ? Math.floor(($scope.frameHeight - imageHeight) / 2) : 0
+ });
+ fullSizeWrap.toggleClass('document_fullsize_zoomed', zoomed);
+ };
+
+ $scope.toggleZoom(false);
+
+ fullSizeImage.attr('src', url);
+ loaderWrap.hide();
+ fullSizeWrap.css({width: $scope.frameWidth, height: $scope.frameHeight}).show();
+
+ };
+ checkSizesInt = setInterval(checkSizes, 20);
+ image.onload = checkSizes;
+ image.src = url;
+ setZeroTimeout(checkSizes);
+ });
+ }
+ })
+
.directive('myMapPoint', function(ExternalResourcesManager) {
return {
@@ -1719,8 +1799,8 @@ angular.module('myApp.directives', ['myApp.filters'])
var updateMargin = function () {
var height = element[0].offsetHeight,
fullHeight = height - (height && usePadding ? 2 * prevMargin : 0),
- contHeight = $($window).height(),
ratio = attrs.myVerticalPosition && parseFloat(attrs.myVerticalPosition) || 0.5,
+ contHeight = attrs.contHeight ? $scope.$eval(attrs.contHeight) : $($window).height(),
margin = fullHeight < contHeight ? parseInt((contHeight - fullHeight) * ratio) : '',
styles = usePadding
? {paddingTop: margin, paddingBottom: margin}
@@ -1736,9 +1816,10 @@ angular.module('myApp.directives', ['myApp.filters'])
prevMargin = margin;
};
+ $($window).on('resize', updateMargin);
+
onContentLoaded(updateMargin);
- $($window).on('resize', updateMargin);
$scope.$on('ui_height', function () {
onContentLoaded(updateMargin);
@@ -1751,7 +1832,7 @@ angular.module('myApp.directives', ['myApp.filters'])
})
- .directive('myUserLink', function ($timeout, $rootScope, AppUsersManager) {
+ .directive('myUserLink', function ($timeout, AppUsersManager) {
return {
link: link
@@ -1767,7 +1848,7 @@ angular.module('myApp.directives', ['myApp.filters'])
if (element[0].tagName == 'A') {
element.on('click', function () {
- $rootScope.openUser(userID, attrs.userOverride && $scope.$eval(attrs.userOverride));
+ AppUsersManager.openUser(userID, attrs.userOverride && $scope.$eval(attrs.userOverride));
});
}
if (attrs.color && $scope.$eval(attrs.color)) {
@@ -1821,7 +1902,7 @@ angular.module('myApp.directives', ['myApp.filters'])
})
- .directive('myUserPhotolink', function ($rootScope, AppUsersManager) {
+ .directive('myUserPhotolink', function (AppUsersManager) {
return {
link: link,
@@ -1840,7 +1921,7 @@ angular.module('myApp.directives', ['myApp.filters'])
if (element[0].tagName == 'A') {
element.on('click', function (e) {
- $rootScope.openUser($scope.userID, attrs.userOverride && $scope.$eval(attrs.userOverride));
+ AppUsersManager.openUser($scope.userID, attrs.userOverride && $scope.$eval(attrs.userOverride));
});
}
@@ -1851,9 +1932,16 @@ angular.module('myApp.directives', ['myApp.filters'])
}
})
- .directive('myAudioPlayer', function ($sce, $timeout, $q, FileManager, MtpApiFileManager) {
+ .directive('myAudioPlayer', function ($timeout, $q, Storage, AppAudioManager, AppDocsManager) {
var currentPlayer = false;
+ var audioVolume = 0.5;
+
+ Storage.get('audio_volume').then(function (newAudioVolume) {
+ if (newAudioVolume >= 0.0 && newAudioVolume <= 1.0) {
+ audioVolume = newAudioVolume;
+ }
+ });
return {
link: link,
@@ -1863,33 +1951,6 @@ angular.module('myApp.directives', ['myApp.filters'])
templateUrl: templateUrl('audio_player')
};
- function downloadAudio (audio) {
- var inputFileLocation = {
- _: audio._ == 'document' ? 'inputDocumentFileLocation' : 'inputAudioFileLocation',
- id: audio.id,
- access_hash: audio.access_hash
- };
-
- audio.progress = {enabled: true, percent: 1, total: audio.size};
-
- var downloadPromise = MtpApiFileManager.downloadFile(audio.dc_id, inputFileLocation, audio.size, {mime: 'audio/ogg'});
-
- audio.progress.cancel = downloadPromise.cancel;
-
- return downloadPromise.then(function (url) {
- delete audio.progress;
- audio.rawUrl = url;
- audio.url = $sce.trustAsResourceUrl(url);
- }, function (e) {
- console.log('audio download failed', e);
- audio.progress.enabled = false;
- }, function (progress) {
- console.log('audio dl progress', progress);
- audio.progress.done = progress.done;
- audio.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
- });
- }
-
function checkPlayer (newPlayer) {
if (newPlayer === currentPlayer) {
return false;
@@ -1901,14 +1962,21 @@ angular.module('myApp.directives', ['myApp.filters'])
}
function link($scope, element, attrs) {
+ if ($scope.audio._ == 'audio') {
+ AppAudioManager.updateAudioDownloaded($scope.audio.id);
+ } else {
+ AppDocsManager.updateDocDownloaded($scope.audio.id);
+ }
+
+ $scope.volume = audioVolume;
$scope.mediaPlayer = {};
$scope.download = function () {
- ($scope.audio.rawUrl ? $q.when() : downloadAudio($scope.audio)).then(
- function () {
- FileManager.download($scope.audio.rawUrl, $scope.audio.mime_type || 'audio/ogg', $scope.audio.file_name || 'audio.ogg');
- }
- );
+ if ($scope.audio._ == 'audio') {
+ AppAudioManager.saveAudioFile($scope.audio.id);
+ } else {
+ AppDocsManager.saveDocFile($scope.audio.id);
+ }
};
$scope.togglePlay = function () {
@@ -1917,16 +1985,139 @@ angular.module('myApp.directives', ['myApp.filters'])
$scope.mediaPlayer.player.playPause();
}
else if ($scope.audio.progress && $scope.audio.progress.enabled) {
- $scope.audio.progress.cancel();
+ return;
}
else {
- downloadAudio($scope.audio).then(function () {
+ var downloadPromise;
+ if ($scope.audio._ == 'audio') {
+ downloadPromise = AppAudioManager.downloadAudio($scope.audio.id);
+ } else {
+ downloadPromise = AppDocsManager.downloadDoc($scope.audio.id);
+ }
+
+ downloadPromise.then(function () {
onContentLoaded(function () {
checkPlayer($scope.mediaPlayer.player);
+ $scope.mediaPlayer.player.setVolume(audioVolume);
$scope.mediaPlayer.player.play();
})
})
}
};
+
+ $scope.seek = function (position) {
+ if ($scope.mediaPlayer && $scope.mediaPlayer.player) {
+ $scope.mediaPlayer.player.seek(position);
+ } else {
+ $scope.togglePlay();
+ }
+ };
+ $scope.setVolume = function (volume) {
+ audioVolume = volume;
+ Storage.set({audio_volume: volume});
+ if ($scope.mediaPlayer && $scope.mediaPlayer.player) {
+ $scope.mediaPlayer.player.setVolume(volume);
+ }
+ };
+ }
+ })
+
+ .directive('mySlider', function ($window) {
+ return {
+ link: link,
+ templateUrl: templateUrl('slider')
+ };
+
+ function link ($scope, element, attrs) {
+ var wrap = $('.tg_slider_wrap', element);
+ var fill = $('.tg_slider_track_fill', element);
+ var thumb = $('.tg_slider_thumb', element);
+ var width = wrap.width();
+ var thumbWidth = Math.ceil(thumb.width());
+ var model = attrs.sliderModel;
+ var sliderCallback = attrs.sliderOnchange;
+ var minValue = 0.0;
+ var maxValue = 1.0;
+ var lastUpdValue = false;
+ var lastMinPageX = false;
+
+ if (attrs.sliderMin) {
+ $scope.$watch(attrs.sliderMin, function (newMinValue) {
+ minValue = newMinValue || 0.0;
+ });
+ }
+ if (attrs.sliderMax) {
+ $scope.$watch(attrs.sliderMax, function (newMaxValue) {
+ maxValue = newMaxValue || 1.0;
+ });
+ }
+
+ var onMouseMove = function (e) {
+ var offsetX = e.pageX - lastMinPageX;
+ offsetX = Math.min(width, Math.max(0 , offsetX));
+ // console.log('mmove', lastMinPageX, e.pageX, offsetX);
+ lastUpdValue = minValue + offsetX / width * (maxValue - minValue);
+ if (sliderCallback) {
+ $scope.$eval(sliderCallback, {value: lastUpdValue});
+ } else {
+ $scope.$eval(model + '=' + lastUpdValue);
+ }
+
+ thumb.css('left', Math.max(0, offsetX - thumbWidth));
+ fill.css('width', offsetX);
+
+ return cancelEvent(e);
+ };
+ var stopMouseTrack = function () {
+ $($window).off('mousemove', onMouseMove);
+ $($window).off('mouseup', stopMouseTrack);
+ };
+
+ $scope.$watch(model, function (newVal) {
+ if (newVal != lastUpdValue && newVal !== undefined) {
+ var percent = Math.max(0, (newVal - minValue) / (maxValue - minValue));
+ if (width) {
+ var offsetX = Math.ceil(width * percent);
+ offsetX = Math.min(width, Math.max(0 , offsetX));
+ thumb.css('left', Math.max(0, offsetX - thumbWidth));
+ fill.css('width', offsetX);
+ } else {
+ thumb.css('left', percent * 100 + '%');
+ fill.css('width', percent * 100 + '%');
+ }
+ lastUpdValue = false;
+ }
+ });
+
+ element.on('dragstart selectstart', cancelEvent);
+
+ element.on('mousedown', function (e) {
+ if (!width) {
+ width = wrap.width();
+ if (!width) {
+ console.error('empty width');
+ return cancelEvent(e);
+ }
+ }
+ stopMouseTrack();
+
+ lastMinPageX = e.pageX - e.offsetX;
+ // console.log('mdown', lastMinPageX, e.pageX, e.offsetX);
+ lastUpdValue = minValue + e.offsetX / width * (maxValue - minValue);
+ if (sliderCallback) {
+ $scope.$eval(sliderCallback, {value: lastUpdValue});
+ } else {
+ $scope.$eval(model + '=' + lastUpdValue);
+ }
+
+ thumb.css('left', Math.max(0, e.offsetX - thumbWidth));
+ fill.css('width', e.offsetX);
+
+ $($window).on('mousemove', onMouseMove);
+ $($window).on('mouseup', stopMouseTrack);
+
+ return cancelEvent(e);
+ });
}
+
})
diff --git a/app/js/filters.js b/app/js/filters.js
index 0ac198c7..8666b6f8 100644
--- a/app/js/filters.js
+++ b/app/js/filters.js
@@ -56,7 +56,7 @@ angular.module('myApp.filters', ['myApp.i18n'])
var cachedDates = {},
dateFilter = $filter('date');
- return function (timestamp) {
+ return function (timestamp, extended) {
if (cachedDates[timestamp]) {
return cachedDates[timestamp];
@@ -67,11 +67,12 @@ angular.module('myApp.filters', ['myApp.i18n'])
format = 'shortTime';
if (diff > 518400000) { // 6 days
- format = 'shortDate';
+ format = extended ? 'mediumDate' : 'shortDate';
}
else if (diff > 43200000) { // 12 hours
- format = 'EEE';
+ format = extended ? 'EEEE' : 'EEE';
}
+
return cachedDates[timestamp] = dateFilter(ticks, format);
}
})
@@ -120,6 +121,14 @@ angular.module('myApp.filters', ['myApp.i18n'])
}
}])
+ .filter('durationRemains', function($filter) {
+ var durationFilter = $filter('duration');
+
+ return function (done, total) {
+ return '-' + durationFilter(total - done);
+ }
+ })
+
.filter('phoneNumber', [function() {
return function (phoneRaw) {
var nbsp = ' ';
@@ -140,13 +149,13 @@ angular.module('myApp.filters', ['myApp.i18n'])
return size + ' b';
}
else if (size < 1048576) {
- return (Math.round(size / 1024 * 10) / 10) + ' Kb';
+ return Math.round(size / 1024) + ' Kb';
}
var mbs = size / 1048576;
if (progressing) {
mbs = mbs.toFixed(1);
} else {
- mbs = (Math.round(mbs * 100) / 100);
+ mbs = (Math.round(mbs * 10) / 10);
}
return mbs + ' Mb';
}
@@ -192,14 +201,14 @@ angular.module('myApp.filters', ['myApp.i18n'])
if (diff < 60000) {
return _('relative_time_just_now');
}
- if (diff < 3000000) {
- var minutes = Math.ceil(diff / 60000);
+ if (diff < 3600000) {
+ var minutes = Math.floor(diff / 60000);
return langMinutesPluralize(minutes);
}
- if (diff < 10000000) {
- var hours = Math.ceil(diff / 3600000);
+ if (diff < 86400000) {
+ var hours = Math.floor(diff / 3600000);
return langHoursPluralize(hours);
}
- return dateOrTimeFilter(timestamp);
+ return dateOrTimeFilter(timestamp, true);
}
})
diff --git a/app/js/i18n.js b/app/js/i18n.js
index 4e02f554..197fc73a 100644
--- a/app/js/i18n.js
+++ b/app/js/i18n.js
@@ -21,16 +21,6 @@ angular.module('myApp.i18n', ['izhukov.utils'])
});
}
- function encodeEntities(value) {
- return value.
- replace(/&/g, '&').
- replace(/([^\#-~| |!\n\*])/g, function (value) { // non-alphanumeric
- return '' + value.charCodeAt(0) + ';';
- }).
- replace(//g, '>');
- }
-
function parseMarkdownString(msgstr, msgid) {
msgstr = msgstr.replace(/\*\*(.+?)\*\*/g, "$1")
.replace(/\n/g, "
");
diff --git a/app/js/lib/bin_utils.js b/app/js/lib/bin_utils.js
index 38bbf77f..d59b2abe 100644
--- a/app/js/lib/bin_utils.js
+++ b/app/js/lib/bin_utils.js
@@ -112,11 +112,13 @@ function bytesXor (bytes1, bytes2) {
}
function bytesToWords (bytes) {
+ if (bytes instanceof ArrayBuffer) {
+ bytes = new Uint8Array(bytes);
+ }
var len = bytes.length,
- words = [];
-
- for (var i = 0; i < len; i++) {
- words[i >>> 2] |= bytes[i] << (24 - (i % 4) * 8);
+ words = [], i;
+ for (i = 0; i < len; i++) {
+ words[i >>> 2] |= bytes[i] << (24 - (i % 4) * 8);
}
return new CryptoJS.lib.WordArray.init(words, len);
@@ -128,7 +130,7 @@ function bytesFromWords (wordArray) {
bytes = [];
for (var i = 0; i < sigBytes; i++) {
- bytes.push((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff);
+ bytes.push((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff);
}
return bytes;
@@ -154,18 +156,59 @@ function bytesToArrayBuffer (b) {
return (new Uint8Array(b)).buffer;
}
+function convertToArrayBuffer(bytes) {
+ // Be careful with converting subarrays!!
+ if (bytes instanceof ArrayBuffer) {
+ return bytes;
+ }
+ if (bytes.buffer !== undefined &&
+ bytes.buffer.byteLength == bytes.length * bytes.BYTES_PER_ELEMENT) {
+ return bytes.buffer;
+ }
+ return bytesToArrayBuffer(bytes);
+}
+
+function convertToUint8Array(bytes) {
+ if (bytes.buffer !== undefined) {
+ return bytes;
+ }
+ return new Uint8Array(bytes);
+}
+
+function convertToByteArray(bytes) {
+ if (Array.isArray(bytes)) {
+ return bytes;
+ }
+ bytes = convertToUint8Array(bytes);
+ var newBytes = [];
+ for (var i = 0, len = bytes.length; i < len; i++) {
+ newBytes.push(bytes[i]);
+ }
+ return newBytes;
+}
+
function bytesFromArrayBuffer (buffer) {
var len = buffer.byteLength,
byteView = new Uint8Array(buffer),
bytes = [];
for (var i = 0; i < len; ++i) {
- bytes[i] = byteView[i];
+ bytes[i] = byteView[i];
}
return bytes;
}
+function bufferConcat(buffer1, buffer2) {
+ var l1 = buffer1.byteLength || buffer1.length,
+ l2 = buffer2.byteLength || buffer2.length;
+ var tmp = new Uint8Array(l1 + l2);
+ tmp.set(buffer1 instanceof ArrayBuffer ? new Uint8Array(buffer1) : buffer1, 0);
+ tmp.set(buffer2 instanceof ArrayBuffer ? new Uint8Array(buffer2) : buffer2, l1);
+
+ return tmp.buffer;
+}
+
function longToInts (sLong) {
var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000));
@@ -195,24 +238,24 @@ function uintToInt (val) {
return val;
}
-function sha1Hash (bytes) {
- // console.log('SHA-1 hash start');
- var hashBytes = sha1.hash(bytes, true);
- // console.log('SHA-1 hash finish');
+function sha1HashSync (bytes) {
+ this.rushaInstance = this.rushaInstance || new Rusha(1024 * 1024);
+
+ // console.log(dT(), 'SHA-1 hash start', bytes.byteLength || bytes.length);
+ var hashBytes = rushaInstance.rawDigest(bytes).buffer;
+ // console.log(dT(), 'SHA-1 hash finish');
return hashBytes;
}
+function sha1BytesSync (bytes) {
+ return bytesFromArrayBuffer(sha1HashSync(bytes));
+}
-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);
- }
+function rsaEncrypt (publicKey, bytes) {
+ bytes = addPadding(bytes, 255);
// console.log('RSA encrypt start');
var N = new BigInteger(publicKey.modulus, 16),
@@ -220,23 +263,35 @@ function rsaEncrypt (publicKey, bytes) {
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) {
+function addPadding(bytes, blockSize) {
+ blockSize = blockSize || 16;
+ var len = bytes.byteLength || bytes.length;
+ var needPadding = blockSize - (len % blockSize);
+ if (needPadding > 0 && needPadding < blockSize) {
var padding = new Array(needPadding);
(new SecureRandom()).nextBytes(padding);
- bytes = bytes.concat(padding);
+ if (bytes instanceof ArrayBuffer) {
+ bytes = bufferConcat(bytes, padding);
+ } else {
+ bytes = bytes.concat(padding);
+ }
}
+ return bytes;
+}
+
+function aesEncryptSync (bytes, keyBytes, ivBytes) {
+ var len = bytes.byteLength || bytes.length;
+
+ // console.log(dT(), 'AES encrypt start', len/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/);
+ bytes = addPadding(bytes);
+
var encryptedWords = CryptoJS.AES.encrypt(bytesToWords(bytes), bytesToWords(keyBytes), {
iv: bytesToWords(ivBytes),
padding: CryptoJS.pad.NoPadding,
@@ -244,15 +299,14 @@ function aesEncrypt (bytes, keyBytes, ivBytes) {
}).ciphertext;
var encryptedBytes = bytesFromWords(encryptedWords);
-
- // console.log('AES encrypt finish');
+ // console.log(dT(), 'AES encrypt finish');
return encryptedBytes;
}
-function aesDecrypt (encryptedBytes, keyBytes, ivBytes) {
- // console.log('AES decrypt start', encryptedBytes.length/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/);
+function aesDecryptSync (encryptedBytes, keyBytes, ivBytes) {
+ // console.log(dT(), 'AES decrypt start', encryptedBytes.length);
var decryptedWords = CryptoJS.AES.decrypt({ciphertext: bytesToWords(encryptedBytes)}, bytesToWords(keyBytes), {
iv: bytesToWords(ivBytes),
padding: CryptoJS.pad.NoPadding,
@@ -260,8 +314,7 @@ function aesDecrypt (encryptedBytes, keyBytes, ivBytes) {
});
var bytes = bytesFromWords(decryptedWords);
-
- // console.log('AES decrypt finish');
+ // console.log(dT(), 'AES decrypt finish');
return bytes;
}
@@ -281,7 +334,7 @@ function pqPrimeFactorization (pqBytes) {
var what = new BigInteger(pqBytes),
result = false;
- console.log('PQ start', pqBytes, what.toString(16), what.bitLength());
+ // console.log(dT(), 'PQ start', pqBytes, what.toString(16), what.bitLength());
try {
result = pqPrimeLeemon(str2bigInt(what.toString(16), 16, Math.ceil(64 / bpe) + 1))
@@ -306,7 +359,7 @@ function pqPrimeFactorization (pqBytes) {
// console.timeEnd('pq BigInt');
}
- console.log('PQ finish');
+ // console.log(dT(), 'PQ finish');
return result;
}
@@ -520,13 +573,15 @@ function pqPrimeLeemon (what) {
function bytesModPow (x, y, m) {
try {
- var xBigInt = str2bigInt(x, 64),
- yBigInt = str2bigInt(y, 64),
+ var xBigInt = str2bigInt(bytesToHex(x), 16),
+ yBigInt = str2bigInt(bytesToHex(y), 16),
mBigInt = str2bigInt(bytesToHex(m), 16, 2),
resBigInt = powMod(xBigInt, yBigInt, mBigInt);
return bytesFromHex(bigInt2str(resBigInt, 16));
- } catch (e) {}
+ } catch (e) {
+ console.error('mod pow error', e);
+ }
return bytesFromBigInt(new BigInteger(x).modPow(new BigInteger(y), new BigInteger(m)));
}
diff --git a/app/js/lib/config.js b/app/js/lib/config.js
index 93bb1001..80f2aa85 100644
--- a/app/js/lib/config.js
+++ b/app/js/lib/config.js
@@ -27,6 +27,7 @@ Config.App = {
Config.Modes = {
test: location.search.indexOf('test=1') > 0,
debug: location.search.indexOf('debug=1') > 0,
+ ssl: location.search.indexOf('ssl=1') > 0 || location.protocol == 'https:',
packed: location.protocol == 'app:' || location.protocol == 'chrome-extension:',
ios_standalone: window.navigator.standalone && navigator.userAgent.match(/iOS|iPhone|iPad/),
chrome_packed: window.chrome && chrome.app && chrome.app.window && true || false
@@ -132,7 +133,7 @@ Config.LangCountries = {"es": "ES", "ru": "RU", "en": "US", "de": "DE", "it": "I
for (i = 0; i < keys.length; i++) {
key = keys[i] = prefix + keys[i];
- if (cache[key] !== undefined) {
+ if (key.substr(0, 3) != 'xt_' && cache[key] !== undefined) {
result.push(cache[key]);
}
else if (useLs) {
diff --git a/app/js/lib/crypto_worker.js b/app/js/lib/crypto_worker.js
index 3340a9d6..96ec3d30 100644
--- a/app/js/lib/crypto_worker.js
+++ b/app/js/lib/crypto_worker.js
@@ -11,7 +11,8 @@ importScripts(
'../../vendor/jsbn/jsbn_combined.js',
'../../vendor/leemon_bigint/bigint.js',
'../../vendor/closure/long.js',
- '../../vendor/cryptoJS/crypto.js'
+ '../../vendor/cryptoJS/crypto.js',
+ '../../vendor/rusha/rusha.js'
);
onmessage = function (e) {
@@ -28,15 +29,15 @@ onmessage = function (e) {
break;
case 'sha1-hash':
- result = sha1Hash(e.data.bytes);
+ result = sha1HashSync(e.data.bytes);
break;
case 'aes-encrypt':
- result = aesEncrypt(e.data.bytes, e.data.keyBytes, e.data.ivBytes);
+ result = aesEncryptSync(e.data.bytes, e.data.keyBytes, e.data.ivBytes);
break;
case 'aes-decrypt':
- result = aesDecrypt(e.data.encryptedBytes, e.data.keyBytes, e.data.ivBytes);
+ result = aesDecryptSync(e.data.encryptedBytes, e.data.keyBytes, e.data.ivBytes);
break;
default:
@@ -45,3 +46,5 @@ onmessage = function (e) {
postMessage({taskID: taskID, result: result});
}
+
+postMessage('ready');
diff --git a/app/js/lib/mtproto.js b/app/js/lib/mtproto.js
index 880e32f3..389b985f 100644
--- a/app/js/lib/mtproto.js
+++ b/app/js/lib/mtproto.js
@@ -8,6 +8,8 @@
angular.module('izhukov.mtproto', ['izhukov.utils'])
.factory('MtpDcConfigurator', function () {
+ var sslSubdomains = ['pluto', 'venus', 'aurora', 'vesta', 'flora'];
+
var dcOptions = Config.Modes.test
? [
{id: 1, host: '173.240.5.253', port: 80},
@@ -24,14 +26,23 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
var chosenServers = {};
- function chooseServer(dcID) {
+ function chooseServer(dcID, upload) {
if (chosenServers[dcID] === undefined) {
var chosenServer = false,
i, dcOption;
+
+ if (Config.Modes.ssl) {
+ var subdomain = sslSubdomains[dcID - 1] + (upload ? '-1' : '');
+ var path = Config.Modes.test ? 'apiw_test1' : 'apiw1';
+ chosenServer = 'https://' + subdomain + '.web.telegram.org/' + path;
+ return chosenServer;
+ }
+
for (i = 0; i < dcOptions.length; i++) {
dcOption = dcOptions[i];
if (dcOption.id == dcID) {
- chosenServer = dcOption.host + ':' + dcOption.port;
+ chosenServer = 'http://' + dcOption.host + (dcOption.port != 80 ? ':' + dcOption.port : '') + '/apiw1';
+ break;
}
}
chosenServers[dcID] = chosenServer;
@@ -82,7 +93,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
var buffer = RSAPublicKey.getBuffer();
- var fingerprintBytes = sha1Hash(buffer).slice(-8);
+ var fingerprintBytes = sha1BytesSync(buffer).slice(-8);
fingerprintBytes.reverse();
publicKeysParsed[bytesToHex(fingerprintBytes)] = {
@@ -114,7 +125,8 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
};
})
-.service('MtpSecureRandom', function () {
+.service('MtpSecureRandom', function ($window) {
+ $($window).on('click keydown', rng_seed_time);
return new SecureRandom();
})
@@ -143,7 +155,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
lastMessageID = messageID;
- // console.log('generated msg id', messageID);
+ // console.log('generated msg id', messageID, timeOffset);
return longFromInts(messageID[0], messageID[1]);
};
@@ -169,7 +181,11 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
.factory('MtpAuthorizer', function (MtpDcConfigurator, MtpRsaKeysManager, MtpSecureRandom, MtpTimeManager, CryptoWorker, $http, $q, $timeout) {
var chromeMatches = navigator.userAgent.match(/Chrome\/(\d+(\.\d+)?)/),
- chromeVersion = chromeMatches && parseFloat(chromeMatches[1]) || false;
+ chromeVersion = chromeMatches && parseFloat(chromeMatches[1]) || false,
+ xhrSendBuffer = !('ArrayBufferView' in window) && (!chromeVersion || chromeVersion < 30);
+
+ delete $http.defaults.headers.post['Content-Type'];
+ delete $http.defaults.headers.common['Accept'];
function mtpSendPlainRequest (dcID, requestBuffer) {
var requestLength = requestBuffer.byteLength,
@@ -190,16 +206,10 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
resultArray.set(headerArray);
resultArray.set(requestArray, headerArray.length);
- delete $http.defaults.headers.post['Content-Type'];
- delete $http.defaults.headers.common['Accept'];
-
- if (!('ArrayBufferView' in window) && (!chromeVersion || chromeVersion < 30)) {
- resultArray = resultArray.buffer;
- }
-
- var requestPromise;
+ var requestData = xhrSendBuffer ? resultBuffer : resultArray,
+ requestPromise;
try {
- requestPromise = $http.post('http://' + MtpDcConfigurator.chooseServer(dcID) + '/apiw1', resultArray, {
+ requestPromise = $http.post(MtpDcConfigurator.chooseServer(dcID), requestData, {
responseType: 'arraybuffer',
transformRequest: null
});
@@ -223,8 +233,6 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
return $q.reject({code: 406, type: 'NETWORK_BAD_RESPONSE', originalError: e});
}
- rng_seed_time();
-
return deserializer;
},
function (error) {
@@ -305,7 +313,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
new_nonce: auth.newNonce
}, 'P_Q_inner_data', 'DECRYPTED_DATA');
- var dataWithHash = sha1Hash(data.getBuffer()).concat(data.getBytes());
+ var dataWithHash = sha1BytesSync(data.getBuffer()).concat(data.getBytes());
var request = new TLSerialization({mtproto: true});
request.storeMethod('req_DH_params', {
@@ -337,7 +345,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
}
if (response._ == 'server_DH_params_fail') {
- var newNonceHash = sha1Hash(auth.newNonce).slice(-16)
+ var newNonceHash = sha1BytesSync(auth.newNonce).slice(-16);
if (!bytesCmp (newNonceHash, response.new_nonce_hash)) {
deferred.reject(new Error('server_DH_params_fail new_nonce_hash mismatch'));
return false;
@@ -362,10 +370,10 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
function mtpDecryptServerDhDataAnswer (auth, encryptedAnswer) {
auth.localTime = tsNow();
- auth.tmpAesKey = sha1Hash(auth.newNonce.concat(auth.serverNonce)).concat(sha1Hash(auth.serverNonce.concat(auth.newNonce)).slice(0, 12));
- auth.tmpAesIv = sha1Hash(auth.serverNonce.concat(auth.newNonce)).slice(12).concat(sha1Hash([].concat(auth.newNonce, auth.newNonce)), auth.newNonce.slice(0, 4));
+ auth.tmpAesKey = sha1BytesSync(auth.newNonce.concat(auth.serverNonce)).concat(sha1BytesSync(auth.serverNonce.concat(auth.newNonce)).slice(0, 12));
+ auth.tmpAesIv = sha1BytesSync(auth.serverNonce.concat(auth.newNonce)).slice(12).concat(sha1BytesSync([].concat(auth.newNonce, auth.newNonce)), auth.newNonce.slice(0, 4));
- var answerWithHash = aesDecrypt(encryptedAnswer, auth.tmpAesKey, auth.tmpAesIv);
+ var answerWithHash = aesDecryptSync(encryptedAnswer, auth.tmpAesKey, auth.tmpAesIv);
var hash = answerWithHash.slice(0, 20);
var answerWithPadding = answerWithHash.slice(20);
@@ -395,7 +403,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
var offset = deserializer.getOffset();
- if (!bytesCmp(hash, sha1Hash(answerWithPadding.slice(0, offset)))) {
+ if (!bytesCmp(hash, sha1BytesSync(answerWithPadding.slice(0, offset)))) {
throw new Error('server_DH_inner_data SHA1-hash mismatch');
}
@@ -420,9 +428,9 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
g_b: gB,
}, 'Client_DH_Inner_Data');
- var dataWithHash = sha1Hash(data.getBuffer()).concat(data.getBytes());
+ var dataWithHash = sha1BytesSync(data.getBuffer()).concat(data.getBytes());
- var encryptedData = aesEncrypt(dataWithHash, auth.tmpAesKey, auth.tmpAesIv);
+ var encryptedData = aesEncryptSync(dataWithHash, auth.tmpAesKey, auth.tmpAesIv);
var request = new TLSerialization({mtproto: true});
request.storeMethod('set_client_DH_params', {
@@ -451,14 +459,14 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
}
CryptoWorker.modPow(auth.gA, auth.b, auth.dhPrime).then(function (authKey) {
- var authKeyHash = sha1Hash(authKey),
+ var authKeyHash = sha1BytesSync(authKey),
authKeyAux = authKeyHash.slice(0, 8),
authKeyID = authKeyHash.slice(-8);
console.log(dT(), 'Got Set_client_DH_params_answer', response._);
switch (response._) {
case 'dh_gen_ok':
- var newNonceHash1 = sha1Hash(auth.newNonce.concat([1], authKeyAux)).slice(-16);
+ var newNonceHash1 = sha1BytesSync(auth.newNonce.concat([1], authKeyAux)).slice(-16);
if (!bytesCmp(newNonceHash1, response.new_nonce_hash1)) {
deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash1 mismatch'));
@@ -476,7 +484,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
break;
case 'dh_gen_retry':
- var newNonceHash2 = sha1Hash(auth.newNonce.concat([2], authKeyAux)).slice(-16);
+ var newNonceHash2 = sha1BytesSync(auth.newNonce.concat([2], authKeyAux)).slice(-16);
if (!bytesCmp(newNonceHash2, response.new_nonce_hash2)) {
deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash2 mismatch'));
return false;
@@ -485,7 +493,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
return mtpSendSetClientDhParams(auth);
case 'dh_gen_fail':
- var newNonceHash3 = sha1Hash(auth.newNonce.concat([3], authKeyAux)).slice(-16);
+ var newNonceHash3 = sha1BytesSync(auth.newNonce.concat([3], authKeyAux)).slice(-16);
if (!bytesCmp(newNonceHash3, response.new_nonce_hash3)) {
deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash3 mismatch'));
return false;
@@ -552,8 +560,13 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
iii = 0,
offline,
offlineInited = false,
+ akStopped = false,
chromeMatches = navigator.userAgent.match(/Chrome\/(\d+(\.\d+)?)/),
- chromeVersion = chromeMatches && parseFloat(chromeMatches[1]) || false;
+ chromeVersion = chromeMatches && parseFloat(chromeMatches[1]) || false,
+ xhrSendBuffer = !('ArrayBufferView' in window) && (!chromeVersion || chromeVersion < 30);
+
+ delete $http.defaults.headers.post['Content-Type'];
+ delete $http.defaults.headers.common['Accept'];
$rootScope.retryOnline = function () {
$(document.body).trigger('online');
@@ -566,7 +579,9 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
this.iii = iii++;
this.authKey = authKey;
- this.authKeyID = sha1Hash(authKey).slice(-8);
+ this.authKeyUint8 = convertToUint8Array(authKey);
+ this.authKeyBuffer = convertToArrayBuffer(authKey);
+ this.authKeyID = sha1BytesSync(authKey).slice(-8);
this.serverSalt = serverSalt;
@@ -738,7 +753,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
message = {
msg_id: messageID,
seq_no: seqNo,
- body: serializer.getBytes(),
+ body: serializer.getBytes(true),
isAPI: true
};
@@ -754,7 +769,9 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
MtpNetworker.prototype.checkLongPoll = function(force) {
var isClean = this.cleanupSent();
// console.log('Check lp', this.longPollPending, tsNow(), this.dcID, isClean);
- if (this.longPollPending && tsNow() < this.longPollPending || this.offline) {
+ if (this.longPollPending && tsNow() < this.longPollPending ||
+ this.offline ||
+ akStopped) {
return false;
}
var self = this;
@@ -787,7 +804,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
longPoll: true
}).then(function () {
delete self.longPollPending;
- $timeout(self.checkLongPoll.bind(self), 0);
+ setZeroTimeout(self.checkLongPoll.bind(self));
}, function () {
console.log('Long-poll failed');
});
@@ -827,19 +844,47 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
};
MtpNetworker.prototype.getMsgKeyIv = function (msgKey, isOut) {
- var authKey = this.authKey,
- x = isOut ? 0 : 8;
-
- var promises = {
- sha1a: CryptoWorker.sha1Hash(msgKey.concat(authKey.slice(x, x + 32))),
- sha1b: CryptoWorker.sha1Hash(authKey.slice(32 + x, 48 + x).concat(msgKey, authKey.slice(48 + x, 64 + x))),
- sha1c: CryptoWorker.sha1Hash(authKey.slice(64 + x, 96 + x).concat(msgKey)),
- sha1d: CryptoWorker.sha1Hash(msgKey.concat(authKey.slice(96 + x, 128 + x)))
- };
+ var authKey = this.authKeyUint8,
+ x = isOut ? 0 : 8,
+ sha1aText = new Uint8Array(48),
+ sha1bText = new Uint8Array(48),
+ sha1cText = new Uint8Array(48),
+ sha1dText = new Uint8Array(48),
+ promises = {};
+
+ sha1aText.set(msgKey, 0);
+ sha1aText.set(authKey.subarray(x, x + 32), 16);
+ promises.sha1a = CryptoWorker.sha1Hash(sha1aText);
+
+ sha1bText.set(authKey.subarray(x + 32, x + 48), 0);
+ sha1bText.set(msgKey, 16);
+ sha1bText.set(authKey.subarray(x + 48, x + 64), 32);
+ promises.sha1b = CryptoWorker.sha1Hash(sha1bText);
+
+ sha1cText.set(authKey.subarray(x + 64, x + 96), 0);
+ sha1cText.set(msgKey, 32);
+ promises.sha1c = CryptoWorker.sha1Hash(sha1cText);
+
+ sha1dText.set(msgKey, 0);
+ sha1dText.set(authKey.subarray(x + 96, x + 128), 16);
+ promises.sha1d = CryptoWorker.sha1Hash(sha1dText);
return $q.all(promises).then(function (result) {
- var aesKey = result.sha1a.slice(0, 8).concat(result.sha1b.slice(8, 20), result.sha1c.slice(4, 16));
- var aesIv = result.sha1a.slice(8, 20).concat(result.sha1b.slice(0, 8), result.sha1c.slice(16, 20), result.sha1d.slice(0, 8));
+ var aesKey = new Uint8Array(32),
+ aesIv = new Uint8Array(32);
+ sha1a = new Uint8Array(result.sha1a),
+ sha1b = new Uint8Array(result.sha1b),
+ sha1c = new Uint8Array(result.sha1c),
+ sha1d = new Uint8Array(result.sha1d);
+
+ aesKey.set(sha1a.subarray(0, 8));
+ aesKey.set(sha1b.subarray(8, 20), 8);
+ aesKey.set(sha1c.subarray(4, 16), 20);
+
+ aesIv.set(sha1a.subarray(8, 20));
+ aesIv.set(sha1b.subarray(0, 8), 12);
+ aesIv.set(sha1c.subarray(16, 20), 20);
+ aesIv.set(sha1d.subarray(0, 8), 24);
return [aesKey, aesIv];
});
@@ -916,8 +961,8 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
MtpNetworker.prototype.performSheduledRequest = function() {
- // console.trace('sheduled', this.dcID, this.iii);
- if (this.offline) {
+ // console.log(dT(), 'sheduled', this.dcID, this.iii);
+ if (this.offline || akStopped) {
console.log(dT(), 'Cancel sheduled');
return false;
}
@@ -948,13 +993,32 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
currentTime = tsNow(),
hasApiCall = false,
hasHttpWait = false,
+ lengthOverflow = false,
+ singlesCount = 0,
self = this;
angular.forEach(this.pendingMessages, function (value, messageID) {
if (!value || value >= currentTime) {
if (message = self.sentMessages[messageID]) {
+ var messageByteLength = (message.body.byteLength || message.body.length) + 32;
+ if (!message.notContentRelated &&
+ lengthOverflow) {
+ return;
+ }
+ if (!message.notContentRelated &&
+ messagesByteLen &&
+ messagesByteLen + messageByteLength > 655360) { // 640 Kb
+ lengthOverflow = true;
+ return;
+ }
+ if (message.singleInRequest) {
+ singlesCount++;
+ if (singlesCount > 1) {
+ return;
+ }
+ }
messages.push(message);
- messagesByteLen += message.body.length + 32;
+ messagesByteLen += messageByteLength;
if (message.isAPI) {
hasApiCall = true;
}
@@ -1009,7 +1073,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
inner: innerMessages
}
- message = angular.extend({body: container.getBytes()}, containerSentMessage);
+ message = angular.extend({body: container.getBytes(true)}, containerSentMessage);
this.sentMessages[message.msg_id] = containerSentMessage;
@@ -1071,15 +1135,23 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
self.toggleOffline(true);
});
+
+ if (lengthOverflow || singlesCount > 1) {
+ this.sheduleRequest()
+ }
};
MtpNetworker.prototype.getEncryptedMessage = function (bytes) {
var self = this;
+ // console.log(dT(), 'Start encrypt', bytes.byteLength);
return CryptoWorker.sha1Hash(bytes).then(function (bytesHash) {
- var msgKey = bytesHash.slice(-16);
+ // console.log(dT(), 'after hash');
+ var msgKey = new Uint8Array(bytesHash).subarray(4, 20);
return self.getMsgKeyIv(msgKey, true).then(function (keyIv) {
+ // console.log(dT(), 'after msg key iv');
return CryptoWorker.aesEncrypt(bytes, keyIv[0], keyIv[1]).then(function (encryptedBytes) {
+ // console.log(dT(), 'Finish encrypt');
return {
bytes: encryptedBytes,
msgKey: msgKey
@@ -1090,7 +1162,9 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
};
MtpNetworker.prototype.getDecryptedMessage = function (msgKey, encryptedData) {
+ // console.log(dT(), 'get decrypted start');
return this.getMsgKeyIv(msgKey, false).then(function (keyIv) {
+ // console.log(dT(), 'after msg key iv');
return CryptoWorker.aesDecrypt(encryptedData, keyIv[0], keyIv[1]);
});
};
@@ -1111,20 +1185,14 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
data.storeInt(message.body.length, 'message_data_length');
data.storeRawBytes(message.body, 'message_data');
- return this.getEncryptedMessage(data.getBytes()).then(function (encryptedResult) {
+ return this.getEncryptedMessage(data.getBuffer()).then(function (encryptedResult) {
// console.log(dT(), 'Got encrypted out message'/*, encryptedResult*/);
- var request = new TLSerialization({startMaxLength: encryptedResult.bytes.length + 256});
+ var request = new TLSerialization({startMaxLength: encryptedResult.bytes.byteLength + 256});
request.storeIntBytes(self.authKeyID, 64, 'auth_key_id');
request.storeIntBytes(encryptedResult.msgKey, 128, 'msg_key');
request.storeRawBytes(encryptedResult.bytes, 'encrypted_data');
- delete $http.defaults.headers.post['Content-Type'];
- delete $http.defaults.headers.common['Accept'];
-
- var resultArray = request.getArray();
- if (!('ArrayBufferView' in window) && (!chromeVersion || chromeVersion < 30)) {
- resultArray = resultArray.buffer;
- }
+ var requestData = xhrSendBuffer ? request.getBuffer() : request.getArray();
var requestPromise;
try {
@@ -1132,7 +1200,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
responseType: 'arraybuffer',
transformRequest: null
});
- requestPromise = $http.post('http://' + MtpDcConfigurator.chooseServer(self.dcID) + '/apiw1', resultArray, options);
+ requestPromise = $http.post(MtpDcConfigurator.chooseServer(self.dcID, self.upload), requestData, options);
} catch (e) {
requestPromise = $q.reject(e);
}
@@ -1168,32 +1236,31 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
var deserializer = new TLDeserialization(responseBuffer);
- var authKeyID = deserializer.fetchIntBytes(64, 'auth_key_id');
+ var authKeyID = deserializer.fetchIntBytes(64, false, 'auth_key_id');
if (!bytesCmp(authKeyID, this.authKeyID)) {
throw new Error('Invalid server auth_key_id: ' + bytesToHex(authKeyID));
}
- var msgKey = deserializer.fetchIntBytes(128, 'msg_key');
-
- var dataLength = responseBuffer.byteLength - deserializer.getOffset();
- var encryptedData = deserializer.fetchRawBytes(dataLength, 'encrypted_data');
+ var msgKey = deserializer.fetchIntBytes(128, true, 'msg_key'),
+ encryptedData = deserializer.fetchRawBytes(responseBuffer.byteLength - deserializer.getOffset(), true, 'encrypted_data');
return this.getDecryptedMessage(msgKey, encryptedData).then(function (dataWithPadding) {
- var buffer = bytesToArrayBuffer(dataWithPadding);
-
- var deserializer = new TLDeserialization(buffer, {mtproto: true});
+ // console.log(dT(), 'after decrypt');
+ var deserializer = new TLDeserialization(dataWithPadding, {mtproto: true});
- var salt = deserializer.fetchIntBytes(64, 'salt');
- var sessionID = deserializer.fetchIntBytes(64, 'session_id');
+ var salt = deserializer.fetchIntBytes(64, false, 'salt');
+ var sessionID = deserializer.fetchIntBytes(64, false, 'session_id');
var messageID = deserializer.fetchLong('message_id');
var seqNo = deserializer.fetchInt('seq_no');
- var messageBody = deserializer.fetchRawBytes(false, 'message_data');
+ var messageBody = deserializer.fetchRawBytes(false, true, 'message_data');
- var offset = deserializer.getOffset();
+ // console.log(dT(), 'before hash');
+ var hashData = convertToUint8Array(dataWithPadding).subarray(0, deserializer.getOffset());
- return CryptoWorker.sha1Hash(dataWithPadding.slice(0, offset)).then(function (dataHashed) {
- if (!bytesCmp(msgKey, dataHashed.slice(-16))) {
+ return CryptoWorker.sha1Hash(hashData).then(function (dataHash) {
+ if (!bytesCmp(msgKey, bytesFromArrayBuffer(dataHash).slice(-16))) {
+ console.warn(msgKey, bytesFromArrayBuffer(dataHash));
throw new Error('server msgKey mismatch');
}
@@ -1216,7 +1283,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
}
if (this.offset != offset + result.bytes) {
console.warn(dT(), 'set offset', this.offset, offset, result.bytes);
- console.log(dT(), result);
+ // console.log(dT(), result);
this.offset = offset + result.bytes;
}
// console.log(dT(), 'override message', result);
@@ -1233,7 +1300,6 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
}
};
var deserializer = new TLDeserialization(buffer, deserializerOptions);
-
var response = deserializer.fetchObject('', 'INPUT');
return {
@@ -1267,19 +1333,19 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
return false;
}
- // console.log('shedule req', delay);
+ // console.log(dT(), 'shedule req', delay);
// console.trace();
$timeout.cancel(this.nextReqPromise);
+ if (delay > 0) {
+ this.nextReqPromise = $timeout(this.performSheduledRequest.bind(this), delay || 0);
+ } else {
+ setZeroTimeout(this.performSheduledRequest.bind(this))
+ }
- this.nextReqPromise = $timeout(this.performSheduledRequest.bind(this), delay || 0);
this.nextReq = nextReq;
};
- MtpNetworker.prototype.onSessionCreate = function (sessionID, messageID) {
- // console.log(dT(), 'New session created', bytesToHex(sessionID));
- };
-
MtpNetworker.prototype.ackMessage = function (msgID) {
// console.log('ack message', msgID);
this.pendingAcks.push(msgID);
@@ -1400,7 +1466,13 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
this.processMessageAck(message.first_msg_id);
this.applyServerSalt(message.server_salt);
- this.onSessionCreate(sessionID, messageID);
+
+ var self = this;
+ Storage.get('dc').then(function (baseDcID) {
+ if (baseDcID == self.dcID && !self.upload && updatesProcessor) {
+ updatesProcessor(message);
+ }
+ });
break;
case 'msgs_ack':
@@ -1486,13 +1558,26 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
}
};
+ function startAll() {
+ if (akStopped) {
+ akStopped = false;
+ updatesProcessor({_: 'new_session_created'});
+ }
+ }
+
+ function stopAll() {
+ akStopped = true;
+ }
+
return {
getNetworker: function (dcID, authKey, serverSalt, options) {
return new MtpNetworker(dcID, authKey, serverSalt, options);
},
setUpdatesProcessor: function (callback) {
updatesProcessor = callback;
- }
+ },
+ stopAll: stopAll,
+ startAll: startAll
};
})
diff --git a/app/js/lib/mtproto_wrapper.js b/app/js/lib/mtproto_wrapper.js
index 2315b8dd..e7363014 100644
--- a/app/js/lib/mtproto_wrapper.js
+++ b/app/js/lib/mtproto_wrapper.js
@@ -7,12 +7,14 @@
angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
-.factory('MtpApiManager', function (Storage, MtpAuthorizer, MtpNetworkerFactory, ErrorService, $q) {
+.factory('MtpApiManager', function (Storage, MtpAuthorizer, MtpNetworkerFactory, MtpSingleInstanceService, ErrorService, $q) {
var cachedNetworkers = {},
cachedUploadNetworkers = {},
cachedExportPromise = {},
baseDcID = false;
+ MtpSingleInstanceService.start();
+
Storage.get('dc').then(function (dcID) {
if (dcID) {
baseDcID = dcID;
@@ -54,7 +56,9 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
}
if (cache[dcID] !== undefined) {
- return $q.when(cache[dcID]);
+ return {then: function (cb) {
+ cb(cache[dcID]);
+ }};
}
var akk = 'dc' + dcID + '_auth_key',
@@ -120,24 +124,17 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
dcID,
networkerPromise;
- if (dcID = options.dcID) {
- networkerPromise = mtpGetNetworker(dcID, options);
- } else {
- networkerPromise = Storage.get('dc').then(function (baseDcID) {
- return mtpGetNetworker(dcID = baseDcID || 2, options);
- });
+ var cachedNetworker;
+ var stack = (new Error()).stack;
+ if (!stack) {
+ try {window.unexistingFunction();} catch (e) {
+ stack = e.stack || '';
+ }
}
-
- var cachedNetworker,
- stack = false;
-
- networkerPromise.then(function (networker) {
+ var performRequest = 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);
@@ -168,12 +165,8 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
cachedExportPromise[dcID].then(function () {
(cachedNetworker = networker).wrapApiCall(method, params, options).then(function (result) {
deferred.resolve(result);
- }, function (error) {
- rejectPromise(error);
- });
- }, function (error) {
- rejectPromise(error);
- });
+ }, rejectPromise);
+ }, rejectPromise);
}
else if (error.code == 303) {
var newDcID = error.type.match(/^(PHONE_MIGRATE_|NETWORK_MIGRATE_|USER_MIGRATE_)(\d+)/)[2];
@@ -187,9 +180,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
mtpGetNetworker(newDcID, options).then(function (networker) {
networker.wrapApiCall(method, params, options).then(function (result) {
deferred.resolve(result);
- }, function (error) {
- rejectPromise(error);
- });
+ }, rejectPromise);
});
}
}
@@ -197,14 +188,14 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
rejectPromise(error);
}
});
- }, function (error) {
- rejectPromise(error);
- });
+ };
- if (!(stack = (stack || (new Error()).stack))) {
- try {window.unexistingFunction();} catch (e) {
- stack = e.stack || '';
- }
+ if (dcID = (options.dcID || baseDcID)) {
+ mtpGetNetworker(dcID, options).then(performRequest, rejectPromise);
+ } else {
+ Storage.get('dc').then(function (baseDcID) {
+ mtpGetNetworker(dcID = baseDcID || 2, options).then(performRequest, rejectPromise);
+ });
}
return deferred.promise;
@@ -234,14 +225,12 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
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) {
@@ -251,7 +240,9 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
var downloadPull = downloadPulls[dcID];
var deferred = $q.defer();
downloadPull.push({cb: cb, deferred: deferred, activeDelta: activeDelta});
- downloadCheck(dcID);
+ setZeroTimeout(function () {
+ downloadCheck(dcID);
+ });
return deferred.promise;
};
@@ -260,6 +251,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
function downloadCheck(dcID) {
var downloadPull = downloadPulls[dcID];
+ var downloadLimit = dcID == 'upload' ? 11 : 5;
if (downloadActives[dcID] >= downloadLimit || !downloadPull || !downloadPull.length) {
return false;
@@ -383,6 +375,13 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
});
}
+ function getDownloadedFile(location, size) {
+ var fileStorage = getFileStorage(),
+ fileName = getFileName(location);
+
+ return fileStorage.getFile(fileName, size);
+ }
+
function downloadFile (dcID, location, size, options) {
if (!FileManager.isAvailable()) {
return $q.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'});
@@ -464,7 +463,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
fileDownload: true,
createNetworker: true
});
- }, 6).then(function (result) {
+ }, 2).then(function (result) {
writeFilePromise.then(function () {
if (canceled) {
return $q.when();
@@ -507,15 +506,23 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
}
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;
+ var fileSize = file.size,
+ isBigFile = fileSize >= 10485760,
+ canceled = false,
+ resolved = false,
+ doneParts = 0,
+ partSize = 262144, // 256 Kb
+ activeDelta = 2;
+
+ if (fileSize > 67108864) {
+ partSize = 524288;
+ activeDelta = 4;
+ }
+ else if (fileSize < 102400) {
+ partSize = 32768;
+ activeDelta = 1;
+ }
+ var totalParts = Math.ceil(fileSize / partSize);
if (totalParts > 1500) {
return $q.reject({type: 'FILE_TOO_BIG'});
@@ -526,6 +533,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
errorHandler = function (error) {
// console.error('Up Error', error);
deferred.reject(error);
+ canceled = true;
errorHandler = angular.noop;
},
part = 0,
@@ -539,35 +547,34 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
};
- var fileReadPromise = $q.when();
-
for (offset = 0; offset < fileSize; offset += partSize) {
(function (offset, part) {
- fileReadPromise = fileReadPromise.then(function () {
- var fileReadDeferred = $q.defer();
+ downloadRequest('upload', function () {
+ var uploadDeferred = $q.defer();
var reader = new FileReader();
var blob = file.slice(offset, offset + partSize);
reader.onloadend = function (e) {
- if (canceled || e.target.readyState != FileReader.DONE) {
+ if (canceled) {
+ uploadDeferred.reject();
return;
}
- var apiCurPromise = apiUploadPromise = apiUploadPromise.then(function () {
- return MtpApiManager.invokeApi(isBigFile ? 'upload.saveBigFilePart' : 'upload.saveFilePart', {
- file_id: fileID,
- file_part: part,
- file_total_parts: totalParts,
- bytes: bytesFromArrayBuffer(e.target.result)
- }, {
- startMaxLength: partSize + 256,
- fileUpload: true
- });
- }, errorHandler);
-
- apiCurPromise.then(function (result) {
+ if (e.target.readyState != FileReader.DONE) {
+ return;
+ }
+ MtpApiManager.invokeApi(isBigFile ? 'upload.saveBigFilePart' : 'upload.saveFilePart', {
+ file_id: fileID,
+ file_part: part,
+ file_total_parts: totalParts,
+ bytes: e.target.result
+ }, {
+ startMaxLength: partSize + 256,
+ fileUpload: true,
+ singleInRequest: true
+ }).then(function (result) {
doneParts++;
- fileReadDeferred.resolve();
+ uploadDeferred.resolve();
if (doneParts >= totalParts) {
deferred.resolve(resultInputFile);
resolved = true;
@@ -580,8 +587,8 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
reader.readAsArrayBuffer(blob);
- return fileReadDeferred.promise;
- });
+ return uploadDeferred.promise;
+ }, activeDelta);
})(offset, part++);
}
@@ -598,9 +605,87 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
return {
getCachedFile: getCachedFile,
+ getDownloadedFile: getDownloadedFile,
downloadFile: downloadFile,
downloadSmallFile: downloadSmallFile,
saveSmallFile: saveSmallFile,
uploadFile: uploadFile
};
})
+
+.service('MtpSingleInstanceService', function (_, $rootScope, $interval, Storage, AppRuntimeManager, IdleManager, ErrorService, MtpNetworkerFactory) {
+
+ var instanceID = nextRandomInt(0xFFFFFFFF);
+ var started = false;
+ var masterInstance = false;
+ var startTime = tsNow();
+ var errorShowTime = 0;
+
+ function start() {
+ if (!started) {
+ started = true;
+
+ IdleManager.start();
+
+ startTime = tsNow();
+ $rootScope.$watch('idle.isIDLE', checkInstance);
+ $interval(checkInstance, 5000);
+ checkInstance();
+
+ try {
+ $($window).on('beforeunload', clearInstance);
+ } catch (e) {};
+ }
+ }
+
+ function clearInstance () {
+ Storage.remove(masterInstance ? 'xt_instance' : 'xt_idle_instance');
+ }
+
+ function checkInstance() {
+ var time = tsNow();
+ var idle = $rootScope.idle && $rootScope.idle.isIDLE;
+ var newInstance = {id: instanceID, idle: idle, time: time};
+
+ Storage.get('xt_instance', 'xt_idle_instance').then(function (result) {
+ var curInstance = result[0],
+ idleInstance = result[1];
+
+ if (!curInstance ||
+ curInstance.time < time - 60000 ||
+ curInstance.id == instanceID ||
+ curInstance.idle ||
+ !idle) {
+
+ if (idleInstance) {
+ if (idleInstance.id == instanceID) {
+ Storage.remove('xt_idle_instance');
+ }
+ else if (idleInstance.time > time - 10000 &&
+ time > errorShowTime) {
+
+ ErrorService.alert(_('error_modal_warning_title'), _('error_modal_multiple_open_tabs'));
+ errorShowTime += tsNow() + 60000;
+ }
+ }
+ Storage.set({xt_instance: newInstance});
+ if (!masterInstance) {
+ MtpNetworkerFactory.startAll();
+ }
+ masterInstance = true;
+ } else {
+ Storage.set({xt_idle_instance: newInstance});
+ if (masterInstance) {
+ MtpNetworkerFactory.stopAll();
+ }
+ masterInstance = false;
+
+ }
+ });
+ }
+
+ return {
+ start: start
+ }
+})
+
diff --git a/app/js/lib/ng_utils.js b/app/js/lib/ng_utils.js
index 82a98c61..61fa441b 100644
--- a/app/js/lib/ng_utils.js
+++ b/app/js/lib/ng_utils.js
@@ -33,7 +33,7 @@ angular.module('izhukov.utils', [])
})
-.service('FileManager', function ($window, $timeout, $q) {
+.service('FileManager', function ($window, $q, $timeout) {
$window.URL = $window.URL || $window.webkitURL;
$window.BlobBuilder = $window.BlobBuilder || $window.WebKitBlobBuilder || $window.MozBlobBuilder;
@@ -98,10 +98,10 @@ angular.module('izhukov.utils', [])
else {
try {
var blob = blobConstruct([bytesToArrayBuffer(bytes)]);
+ fileWriter.write(blob);
} catch (e) {
deferred.reject(e);
}
- fileWriter.write(blob);
}
return deferred.promise;
@@ -150,7 +150,7 @@ angular.module('izhukov.utils', [])
return false;
}
blobParts.push(blob);
- $timeout(function () {
+ setZeroTimeout(function () {
if (fakeFileWriter.onwriteend) {
fakeFileWriter.onwriteend();
}
@@ -484,35 +484,59 @@ angular.module('izhukov.utils', [])
.service('CryptoWorker', function ($timeout, $q) {
- var worker = window.Worker && new Worker('js/lib/crypto_worker.js') || false,
+ var webWorker = false,
+ naClEmbed = false,
taskID = 0,
- awaiting = {};
-
- if (worker) {
- worker.onmessage = function (e) {
- var deferred = awaiting[e.data.taskID];
- if (deferred !== undefined) {
- console.log(dT(), 'CW done');
- deferred.resolve(e.data.result);
- delete awaiting[e.data.taskID];
+ awaiting = {},
+ webCrypto = window.crypto && (window.crypto.subtle || window.crypto.webkitSubtle) || window.msCrypto && window.msCrypto.subtle,
+ useSha1Crypto = webCrypto && webCrypto.digest !== undefined,
+ finalizeTask = function (taskID, result) {
+ var deferred = awaiting[taskID];
+ if (deferred !== undefined) {
+ // console.log(dT(), 'CW done');
+ deferred.resolve(result);
+ delete awaiting[taskID];
+ }
+ };
+
+ if (navigator.mimeTypes['application/x-pnacl'] !== undefined) {
+ var listener = $('