Browse Source

Merge branch 'UI-update'

master
Igor Zhukov 10 years ago
parent
commit
3cbab8cf8e
  1. 411
      app/css/app.css
  2. 23
      app/css/desktop.css
  3. 17
      app/css/mobile.css
  4. BIN
      app/img/icons/IconsetW.png
  5. BIN
      app/img/icons/IconsetW_1x.png
  6. 2
      app/index.html
  7. 58
      app/js/controllers.js
  8. 417
      app/js/directives.js
  9. 29
      app/js/filters.js
  10. 10
      app/js/i18n.js
  11. 117
      app/js/lib/bin_utils.js
  12. 3
      app/js/lib/config.js
  13. 11
      app/js/lib/crypto_worker.js
  14. 253
      app/js/lib/mtproto.js
  15. 197
      app/js/lib/mtproto_wrapper.js
  16. 305
      app/js/lib/ng_utils.js
  17. 33
      app/js/lib/polyfill.js
  18. 70
      app/js/lib/tl_utils.js
  19. 38
      app/js/lib/utils.js
  20. 6
      app/js/locales/en-us.json
  21. 556
      app/js/services.js
  22. 54
      app/nacl/Makefile
  23. 147
      app/nacl/aes.h
  24. 1358
      app/nacl/aes_core.c
  25. 325
      app/nacl/aes_ige.c
  26. 89
      app/nacl/aes_locl.h
  27. 85
      app/nacl/aes_misc.c
  28. BIN
      app/nacl/mtproto_crypto.bc
  29. 267
      app/nacl/mtproto_crypto.cc
  30. 10
      app/nacl/mtproto_crypto.nmf
  31. BIN
      app/nacl/mtproto_crypto.pexe
  32. 36
      app/partials/desktop/audio_player.html
  33. 17
      app/partials/desktop/changelog_modal.html
  34. 21
      app/partials/desktop/document_modal.html
  35. 1
      app/partials/desktop/error_modal.html
  36. 21
      app/partials/desktop/full_document.html
  37. 4
      app/partials/desktop/full_gif.html
  38. 14
      app/partials/desktop/full_video.html
  39. 6
      app/partials/desktop/message.html
  40. 3
      app/partials/desktop/message_attach_audio.html
  41. 37
      app/partials/desktop/message_attach_document.html
  42. 8
      app/partials/desktop/message_attach_pending.html
  43. 25
      app/partials/desktop/message_attach_video.html
  44. 2
      app/partials/desktop/photo_modal.html
  45. 2
      app/partials/desktop/settings_modal.html
  46. 6
      app/partials/desktop/slider.html
  47. 2
      app/partials/desktop/video_modal.html
  48. 6
      app/partials/mobile/full_gif.html
  49. 14
      app/partials/mobile/full_video.html
  50. 11
      app/partials/mobile/import_contact_modal.html
  51. 6
      app/partials/mobile/message.html
  52. 3
      app/partials/mobile/message_attach_audio.html
  53. 37
      app/partials/mobile/message_attach_document.html
  54. 2
      app/partials/mobile/message_attach_pending.html
  55. 6
      app/partials/mobile/message_attach_video.html
  56. 2
      app/partials/mobile/photo_modal.html
  57. 1
      app/partials/mobile/slider.html
  58. 2
      app/partials/mobile/video_modal.html
  59. 392
      app/vendor/rusha/rusha.js
  60. 2
      app/webogram.appcache
  61. 2
      package.json
  62. 4
      server.js

411
app/css/app.css

@ -79,7 +79,7 @@ input[type="number"] {
} }
.btn-success { .btn-success {
color: #ffffff; color: #ffffff;
background-color: #6AC065; background-color: #6ec26d;
} }
.btn-success:hover, .btn-success:hover,
@ -94,7 +94,7 @@ input[type="number"] {
.btn-success:active, .btn-success:active,
.btn-success.active, .btn-success.active,
.open .dropdown-toggle.btn-success { .open .dropdown-toggle.btn-success {
background: #5aaf54; background: #66b864;
background-image: none; background-image: none;
} }
@ -121,7 +121,7 @@ input[type="number"] {
.btn-primary { .btn-primary {
color: #ffffff; color: #ffffff;
background-color: #5d8db3; background-color: #6490b1;
border-radius: 3px; border-radius: 3px;
} }
.btn-primary:hover, .btn-primary:hover,
@ -425,7 +425,7 @@ input[type="number"] {
.modal-close-button i { .modal-close-button i {
display: inline-block; display: inline-block;
background: url(../img/icons/IconsetW.png) -15px -320px no-repeat; background: url(../img/icons/IconsetW.png) -15px -320px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
width: 12px; width: 12px;
height: 12px; height: 12px;
margin: 21px; margin: 21px;
@ -594,73 +594,31 @@ a.tg_radio_on:hover i.icon-radio {
.tg_range_wrap { .tg_range_wrap {
line-height: 18px; line-height: 18px;
} }
input.tg_range { .tg_slider_wrap {
position: relative;
cursor: pointer; cursor: pointer;
outline: none !important;
-webkit-appearance: none;
width: 100%;
max-width: 362px;
display: inline-block;
background: #c7c7c7;
height: 3px;
line-height: 18px; line-height: 18px;
vertical-align: top; height: 18px;
margin: 8px 0;
border-radius: 2px;
} }
input.tg_range::-moz-range-track { .tg_slider_track {
cursor: pointer; position: absolute;
outline: none !important;
-webkit-appearance: none;
width: 100%;
max-width: 362px;
display: inline-block;
background: #c7c7c7; background: #c7c7c7;
height: 3px; height: 3px;
line-height: 18px;
vertical-align: top;
margin: 8px 0; margin: 8px 0;
border-radius: 2px; 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%; width: 100%;
max-width: 362px; z-index: 2;
display: inline-block;
background: #c7c7c7;
height: 3px;
line-height: 18px;
vertical-align: top;
margin: 8px 0;
border-radius: 2px;
} }
input.tg_range::-ms-thumb { .tg_slider_thumb {
position: absolute;
border: 0; border: 0;
background: #568cb5; background: #568cb5;
width: 12px; width: 12px;
height: 12px; height: 12px;
border-radius: 6px; border-radius: 6px;
margin-top: 4px;
overflow: hidden; overflow: hidden;
z-index: 3;
} }
@ -910,7 +868,7 @@ img.welcome_logo {
font-size: 12px; font-size: 12px;
line-height: normal; line-height: normal;
background: #F2F2F2 url(../img/icons/IconsetW.png) -6px -205px no-repeat; background: #F2F2F2 url(../img/icons/IconsetW.png) -6px -205px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
border: 1px solid #F2F2F2; border: 1px solid #F2F2F2;
border-radius: 3px; border-radius: 3px;
padding: 6px 20px 6px 30px; padding: 6px 20px 6px 30px;
@ -938,7 +896,7 @@ img.welcome_logo {
height: 13px; height: 13px;
vertical-align: text-top; vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -15px -192px no-repeat; background: url(../img/icons/IconsetW.png) -15px -192px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
opacity: 0.6; opacity: 0.6;
} }
.is_1x .im_dialogs_search_clear { .is_1x .im_dialogs_search_clear {
@ -1070,7 +1028,7 @@ a.im_dialog_selected .im_dialog_message_text {
} }
.im_dialog_badge { .im_dialog_badge {
background: #75BB72; background: #6ec26d;
border-radius: 2px; border-radius: 2px;
font-size: 10px; font-size: 10px;
padding: 3px 4px; padding: 3px 4px;
@ -1144,7 +1102,7 @@ a.im_dialog_selected .im_dialog_date {
margin-left: 6px; margin-left: 6px;
background: url(../img/icons/IconsetW.png) -17px -444px no-repeat; background: url(../img/icons/IconsetW.png) -17px -444px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
} }
.is_1x .icon-caret { .is_1x .icon-caret {
background-image: url(../img/icons/IconsetW_1x.png); background-image: url(../img/icons/IconsetW_1x.png);
@ -1302,7 +1260,7 @@ div.im_message_video_thumb {
height: 42px; height: 42px;
background: url(../img/icons/IconsetW.png) 0 -590px no-repeat; background: url(../img/icons/IconsetW.png) 0 -590px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
z-index: 1; z-index: 1;
} }
.is_1x .icon-videoplay { .is_1x .icon-videoplay {
@ -1329,27 +1287,38 @@ div.im_message_video_thumb {
height: 19px; height: 19px;
background: url(../img/icons/IconsetW.png) -14px -389px no-repeat; background: url(../img/icons/IconsetW.png) -14px -389px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
} }
.is_1x .icon-geo-point { .is_1x .icon-geo-point {
background-image: url(../img/icons/IconsetW_1x.png); background-image: url(../img/icons/IconsetW_1x.png);
} }
.im_message_iframe_video { .im_message_media_embed {
position: relative; position: relative;
padding-bottom: 56.25%; /* 16/9 ratio */
height: 0; height: 0;
overflow: hidden; overflow: hidden;
margin-top: 5px; margin-top: 5px;
} }
.im_message_iframe_video iframe, .im_message_video_embed {
.im_message_iframe_video webview { 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; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.im_message_twitter_embed > blockquote {
visibility: hidden;
}
.im_message_gif_wrap { .im_message_gif_wrap {
position: relative; position: relative;
@ -1370,42 +1339,70 @@ div.im_message_video_thumb {
z-index: 1; z-index: 1;
} }
.im_message_video,
.im_message_document, .im_message_document,
.im_message_upload_file { .im_message_upload_file,
.im_message_audio {
margin-top: 3px; margin-top: 3px;
border-radius: 3px; width: 317px;
display: inline-block;
width: 340px;
} }
.im_message_audio { .im_message_audio {
margin-top: 3px; margin-top: 3px;
} }
.icon-document, .im_message_file_button {
.icon-photo,
.icon-video {
display: block; display: block;
background: rgba(218,228,234,0.50);
float: left; float: left;
width: 38px; width: 42px;
height: 38px; height: 42px;
vertical-align: text-top; border-radius: 0;
background: #F2F2F2 url(../img/icons/IconsetW.png) -2px -229px no-repeat;
background-size: 42px 971px;
border-radius: 3px;
margin-right: 10px; margin-right: 10px;
} }
.is_1x .icon-document, .im_message_file_button_icon {
.is_1x .icon-photo, display: inline-block;
.is_1x .icon-video { 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-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-document,
.im_message_selected .icon-photo, .im_history_selectable .im_message_outer_wrap:hover .icon-document {
.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 {
background-color: #dae6f0; background-color: #dae6f0;
background-position: -2px -542px; background-position: -2px -542px;
} }
@ -1440,19 +1437,19 @@ img.im_message_document_thumb {
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
width: 290px; width: 265px;
padding: 0 0 1px; padding: 0 0 1px;
} }
.im_message_document_actions { .im_message_document_actions {
width: 290px; width: 265px;
} }
.im_message_document_name { .im_message_document_name {
color: #222; color: #3a6d99;
display: inline-block; display: inline-block;
font-weight: bold; font-weight: bold;
max-width: 200px; max-width: 170px;
overflow: hidden; overflow: hidden;
vertical-align: text-top; vertical-align: text-top;
white-space: nowrap; white-space: nowrap;
@ -1461,63 +1458,30 @@ img.im_message_document_thumb {
.im_message_document_size { .im_message_document_size {
color: #999; color: #999;
padding-left: 2px; padding-left: 2px;
vertical-align: text-top;
} }
.im_message_document_actions a, .im_message_document_actions a,
.audio_player_actions a { .audio_player_actions a {
margin-right: 10px; 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 { .audio_player_title_wrap {
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
padding: 0 0 1px; padding: 1px 0 1px;
line-height: 18px; line-height: 16px;
height: 19px; height: 19px;
width: 200px;
} }
.audio_player_title { .audio_player_title {
color: #222;
display: inline-block; display: inline-block;
font-weight: bold; font-weight: bold;
max-width: 200px; max-width: 85px;
overflow: hidden; overflow: hidden;
vertical-align: text-top; vertical-align: text-top;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.audio_player_title:hover {
color: #222;
}
.audio_player_meta { .audio_player_meta {
overflow: hidden; overflow: hidden;
vertical-align: text-top; vertical-align: text-top;
@ -1528,78 +1492,138 @@ img.im_message_document_thumb {
color: #999; color: #999;
padding-left: 2px; padding-left: 2px;
} }
.audio_player_actions {
margin-top: 3px;
}
.im_message_upload_progress_wrap, .audio_player_seek_slider {
.im_message_download_progress_wrap { float: left;
margin-top: 5px; margin-right: 15px;
width: 290px; 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 { .audio_player_progress_wrap {
margin-top: 5px;
max-width: 290px;
border-radius: 2px;
overflow: hidden; 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_document_name_wrap,
.im_message_document_thumbed .im_message_upload_progress_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_download_progress_wrap,
.im_message_document_thumbed .im_message_document_actions { .im_message_document_thumbed .im_message_document_actions {
width: 230px; width: 207px;
} }
.im_message_document_thumbed .im_message_document_name { .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_document_name_wrap,
.im_message_video .im_message_download_progress_wrap, .im_message_video .im_message_download_progress_wrap,
.im_message_video .im_message_document_actions { .im_message_video .im_message_document_actions {
width: 150px; width: 152px;
} }
.im_message_video .im_message_document_name_wrap { .im_message_video .im_message_document_name_wrap {
margin-top: 5px; margin-top: 5px;
} }
.im_message_cancelable_progress_wrap,
.im_message_playback_progress_wrap {
margin-top: 4px;
/*width: 265px;*/
}
.im_message_media_progress_cancel { .im_message_media_progress_cancel {
font-size: 11px; margin-left: 15px;
margin-left: 10px;
line-height: 100%; line-height: 100%;
width: 50px;
display: block;
overflow: hidden;
} }
.tg_up_progress, .tg_up_progress,
.tg_down_progress, .tg_down_progress {
.tg_play_progress { height: 4px;
height: 5px;
margin: 0; margin: 0;
padding: 0; padding: 0;
background: #F2F2F2; background: rgba(218,228,234,0.50);
border: 0; border: 0;
border-radius: 0; border-radius: 0;
-webkit-box-shadow: none; -webkit-box-shadow: none;
box-shadow: none; box-shadow: none;
} }
.tg_up_progress .progress-bar, .tg_up_progress .progress-bar,
.tg_down_progress .progress-bar, .tg_down_progress .progress-bar {
.tg_play_progress .progress-bar { height: 4px;
height: 5px; line-height: 4px;
line-height: 5px;
background: #6B9ABD; background: #6B9ABD;
border-radius: 0; border-radius: 0;
overflow: hidden; overflow: hidden;
-webkit-box-shadow: none; -webkit-box-shadow: none;
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; height: 23px;
vertical-align: text-top; vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -12px -68px no-repeat; background: url(../img/icons/IconsetW.png) -12px -68px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
opacity: 0.8; opacity: 0.8;
} }
.is_1x .icon-paperclip { .is_1x .icon-paperclip {
@ -1787,7 +1811,7 @@ textarea.im_message_field {
height: 23px; height: 23px;
vertical-align: text-top; vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -10px -4px no-repeat; background: url(../img/icons/IconsetW.png) -10px -4px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
opacity: 0.8; opacity: 0.8;
} }
.is_1x .icon-emoji { .is_1x .icon-emoji {
@ -1836,7 +1860,7 @@ textarea.im_message_field {
height: 21px; height: 21px;
vertical-align: text-top; vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -9px -132px no-repeat; background: url(../img/icons/IconsetW.png) -9px -132px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
opacity: 0.8; opacity: 0.8;
} }
.is_1x .icon-camera { .is_1x .icon-camera {
@ -1852,7 +1876,7 @@ textarea.im_message_field {
.icon-online { .icon-online {
background: #6DBF69; background: #6ec26d;
border: 1px solid #FFF; border: 1px solid #FFF;
display: block; display: block;
width: 11px; width: 11px;
@ -1868,13 +1892,41 @@ textarea.im_message_field {
.media_modal_wrap .modal-body { .media_modal_wrap .modal-body {
padding: 19px 18px 17px; padding: 19px 18px 17px;
} }
a.img_fullsize { a.img_fullsize,
.img_fullsize_wrap {
display: block; display: block;
text-align: center; text-align: center;
} }
img.img_fullsize { img.img_fullsize {
margin: 0 auto; 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 { .media_modal_info {
color: #999; color: #999;
margin: 20px 0 0; margin: 20px 0 0;
@ -1886,8 +1938,12 @@ img.img_fullsize {
margin-left: 15px; margin-left: 15px;
} }
.media_modal_author { .media_modal_author {
color: inherit;
font-weight: bold; font-weight: bold;
} }
.media_modal_author:hover {
color: inherit;
}
.non_osx .media_modal_author { .non_osx .media_modal_author {
font-size: 12px; font-size: 12px;
} }
@ -1914,7 +1970,7 @@ img.img_fullsize {
overflow: auto; overflow: auto;
line-height: 17px; line-height: 17px;
border: 1px solid #d9dbde; border: 1px solid #d2dbe3;
border-radius: 2px; border-radius: 2px;
-webkit-box-shadow: none; -webkit-box-shadow: none;
box-shadow: none; box-shadow: none;
@ -2179,7 +2235,7 @@ a:hover .icon-twitter {
font-size: 12px; font-size: 12px;
line-height: normal; line-height: normal;
background: url(../img/icons/IconsetW.png) -6px -205px no-repeat; background: url(../img/icons/IconsetW.png) -6px -205px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
border: 1px solid #d9dbde; border: 1px solid #d9dbde;
border-radius: 3px; border-radius: 3px;
padding: 6px 15px 6px 30px; padding: 6px 15px 6px 30px;
@ -2198,7 +2254,7 @@ a:hover .icon-twitter {
height: 13px; height: 13px;
vertical-align: text-top; vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -15px -192px no-repeat; background: url(../img/icons/IconsetW.png) -15px -192px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
opacity: 0.6; opacity: 0.6;
} }
.is_1x .contacts_modal_search_clear { .is_1x .contacts_modal_search_clear {
@ -2314,7 +2370,7 @@ img.chat_modal_participant_photo {
width: 25px; width: 25px;
height: 25px; height: 25px;
background: url(../img/icons/IconsetW.png) -9px -516px no-repeat; background: url(../img/icons/IconsetW.png) -9px -516px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
opacity: 0.5; opacity: 0.5;
} }
.is_1x .icon-contact-tick { .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_duration,
.im_message_focus .audio_player_size, .im_message_focus .audio_player_size,
.im_message_focus .im_message_fwd_date { .im_message_focus .im_message_fwd_date {
color: #68839c; color: #899daf;
} }
.icon-select-tick { .icon-select-tick {
@ -2395,7 +2451,7 @@ img.chat_modal_participant_photo {
height: 26px; height: 26px;
margin: 13px 0 0 40px; margin: 13px 0 0 40px;
background: url(../img/icons/IconsetW.png) -9px -516px no-repeat; background: url(../img/icons/IconsetW.png) -9px -516px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
} }
.is_1x .icon-select-tick { .is_1x .icon-select-tick {
background-image: url(../img/icons/IconsetW_1x.png); background-image: url(../img/icons/IconsetW_1x.png);
@ -2514,7 +2570,7 @@ ce671b orange
font-size: 12px; font-size: 12px;
line-height: normal; line-height: normal;
background: #F2F2F2 url(../img/icons/IconsetW.png) -6px -205px no-repeat; background: #F2F2F2 url(../img/icons/IconsetW.png) -6px -205px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
border: 1px solid #F2F2F2; border: 1px solid #F2F2F2;
border-radius: 3px; border-radius: 3px;
padding: 6px 20px 6px 30px; padding: 6px 20px 6px 30px;
@ -2537,7 +2593,7 @@ ce671b orange
height: 13px; height: 13px;
vertical-align: text-top; vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -15px -192px no-repeat; background: url(../img/icons/IconsetW.png) -15px -192px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
opacity: 0.6; opacity: 0.6;
} }
.is_1x .countries_modal_search_clear { .is_1x .countries_modal_search_clear {
@ -2708,3 +2764,8 @@ ce671b orange
line-height: 160%; line-height: 160%;
margin: 25px 0 30px; margin: 25px 0 30px;
} }
#nacl_listener {
position: absolute;
left: -10000px;
}

23
app/css/desktop.css

@ -179,7 +179,7 @@ a.footer_lang_link.active:active {
.im_history_col .nano > .nano-pane, .im_history_col .nano > .nano-pane,
.contacts_modal_col .nano > .nano-pane, .contacts_modal_col .nano > .nano-pane,
.im_dialogs_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; width : 9px;
right: 0; right: 0;
top: 0; top: 0;
@ -218,7 +218,7 @@ a.footer_lang_link.active:active {
.im_history_col .nano > .nano-pane > .nano-slider, .im_history_col .nano > .nano-pane > .nano-slider,
.contacts_modal_col .nano > .nano-pane > .nano-slider, .contacts_modal_col .nano > .nano-pane > .nano-slider,
.im_dialogs_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; margin: 0;
-moz-border-radius : 2px; -moz-border-radius : 2px;
-webkit-border-radius : 2px; -webkit-border-radius : 2px;
@ -324,7 +324,7 @@ a.footer_lang_link.active:active {
} }
.icon-message-status { .icon-message-status {
background: #43A4DB; background: #6ba2cb;
border: 0; border: 0;
display: block; display: block;
width: 10px; width: 10px;
@ -381,6 +381,7 @@ a.footer_lang_link.active:active {
font-size: 13px; font-size: 13px;
line-height: 17px; line-height: 17px;
min-width: 60px; min-width: 60px;
border-radius: 2px;
} }
.im_message_selected .im_message_date, .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_duration,
.im_history_selectable .im_message_outer_wrap:hover .im_message_audio_size, .im_history_selectable .im_message_outer_wrap:hover .im_message_audio_size,
.im_history_selectable .im_message_outer_wrap:hover .im_message_fwd_date { .im_history_selectable .im_message_outer_wrap:hover .im_message_fwd_date {
color: #68839c; color: #899daf;
} }
.im_content_message_select_area { .im_content_message_select_area {
@ -492,7 +493,7 @@ a.footer_lang_link.active:active {
.im_panel_own_photo { .im_panel_own_photo {
width: 50px; width: 50px;
height: 50px; height: 50px;
border-radius: 3px; border-radius: 0;
overflow: hidden; overflow: hidden;
} }
div.im_panel_peer_photo { div.im_panel_peer_photo {
@ -505,7 +506,7 @@ div.im_panel_own_photo {
} }
.im_panel_peer_online { .im_panel_peer_online {
background: #6DBF69; background: #6ec26d;
border: 1px solid #FFF; border: 1px solid #FFF;
display: block; display: block;
width: 11px; width: 11px;
@ -516,6 +517,10 @@ div.im_panel_own_photo {
margin-top: -7px; margin-top: -7px;
margin-left: 43px; margin-left: 43px;
} }
.emoji-wysiwyg-editor,
.im_message_field {
border-radius: 0;
}
/* Peer modals */ /* Peer modals */
.user_modal_window .modal-dialog { .user_modal_window .modal-dialog {
@ -787,6 +792,12 @@ div.im_panel_own_photo {
display: none; display: none;
} }
.settings_volume_slider {
width: 100%;
max-width: 362px;
display: inline-block;
}
.im_message_selected .im_message_outer_wrap, .im_message_selected .im_message_outer_wrap,
.im_message_focus .im_message_outer_wrap { .im_message_focus .im_message_outer_wrap {

17
app/css/mobile.css

@ -130,7 +130,7 @@ html {
vertical-align: text-top; vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -15px -835px no-repeat; background: url(../img/icons/IconsetW.png) -15px -835px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
opacity: 0.8; opacity: 0.8;
} }
.is_1x .icon-back { .is_1x .icon-back {
@ -535,9 +535,7 @@ img.im_message_video_thumb,
color: #93a2ae; color: #93a2ae;
} }
.im_message_out .icon-document, .im_message_out .icon-document {
.im_message_out .icon-photo,
.im_message_out .icon-video {
background-color: #dae6f0; background-color: #dae6f0;
background-position: -2px -542px; background-position: -2px -542px;
} }
@ -961,7 +959,7 @@ a.mobile_modal_action .tg_checkbox_label {
.im_submit:active, .im_submit:active,
.im_submit:hover { .im_submit:hover {
background: url(../img/icons/IconsetW.png) 2px -860px no-repeat; background: url(../img/icons/IconsetW.png) 2px -860px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
color: transparent; color: transparent;
box-shadow: none; box-shadow: none;
} }
@ -985,7 +983,7 @@ a.mobile_modal_action .tg_checkbox_label {
height: 23px; height: 23px;
vertical-align: text-top; vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -12px -68px no-repeat; background: url(../img/icons/IconsetW.png) -12px -68px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
opacity: 0.8; opacity: 0.8;
} }
.is_1x .icon-paperclip { .is_1x .icon-paperclip {
@ -1022,7 +1020,7 @@ a.mobile_modal_action .tg_checkbox_label {
opacity: 1; opacity: 1;
margin: 0; margin: 0;
background: url(../img/icons/IconsetW.png) -10px -771px no-repeat; background: url(../img/icons/IconsetW.png) -10px -771px no-repeat;
background-size: 42px 971px; background-size: 42px 1171px;
} }
.is_1x .icon-emoji { .is_1x .icon-emoji {
background-image: url(../img/icons/IconsetW_1x.png); background-image: url(../img/icons/IconsetW_1x.png);
@ -1120,3 +1118,8 @@ a.mobile_modal_action .tg_checkbox_label {
.countries_scrollable_wrap a.countries_modal_country { .countries_scrollable_wrap a.countries_modal_country {
padding: 8px 8px; padding: 8px 8px;
} }
.import_modal_phonebook_wrap {
margin-top: 40px;
text-align: center;
}

BIN
app/img/icons/IconsetW.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 26 KiB

BIN
app/img/icons/IconsetW_1x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

2
app/index.html

@ -56,8 +56,10 @@
<script type="text/javascript" src="vendor/jsbn/jsbn_combined.js"></script> <script type="text/javascript" src="vendor/jsbn/jsbn_combined.js"></script>
<script type="text/javascript" src="vendor/cryptoJS/crypto.js"></script> <script type="text/javascript" src="vendor/cryptoJS/crypto.js"></script>
<script type="text/javascript" src="vendor/rusha/rusha.js"></script>
<script type="text/javascript" src="vendor/zlib/gunzip.min.js"></script> <script type="text/javascript" src="vendor/zlib/gunzip.min.js"></script>
<script type="text/javascript" src="vendor/closure/long.js"></script> <script type="text/javascript" src="vendor/closure/long.js"></script>
<script type="text/javascript" src="vendor/leemon_bigint/bigint.js"></script>
<script type="text/javascript" src="js/lib/utils.js"></script> <script type="text/javascript" src="js/lib/utils.js"></script>

58
app/js/controllers.js

@ -316,7 +316,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
ChangelogNotifyService.checkUpdate(); 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); $scope.$on('$routeUpdate', updateCurDialog);
@ -400,9 +400,9 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.showPeerInfo = function () { $scope.showPeerInfo = function () {
if ($scope.curDialog.peerID > 0) { if ($scope.curDialog.peerID > 0) {
$rootScope.openUser($scope.curDialog.peerID) AppUsersManager.openUser($scope.curDialog.peerID)
} else if ($scope.curDialog.peerID < 0) { } 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 = {}, peersInDialogs = {},
contactsShown; contactsShown;
MtpApiManager.invokeApi('account.updateStatus', {offline: false});
$scope.$on('dialogs_need_more', function () { $scope.$on('dialogs_need_more', function () {
// console.log('on need more'); // console.log('on need more');
showMoreDialogs(); showMoreDialogs();
@ -629,7 +628,8 @@ angular.module('myApp.controllers', ['myApp.i18n'])
if (error.code == 401) { if (error.code == 401) {
MtpApiManager.logOut()['finally'](function () { MtpApiManager.logOut()['finally'](function () {
$location.url('/login'); location.hash = '/login';
AppRuntimeManager.reload();
}); });
error.handled = true; error.handled = true;
} }
@ -1765,6 +1765,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}) })
.controller('VideoModalController', function ($scope, $rootScope, $modalInstance, PeersSelectService, AppMessagesManager, AppVideoManager, AppPeersManager, ErrorService) { .controller('VideoModalController', function ($scope, $rootScope, $modalInstance, PeersSelectService, AppMessagesManager, AppVideoManager, AppPeersManager, ErrorService) {
$scope.video = AppVideoManager.wrapForFull($scope.videoID); $scope.video = AppVideoManager.wrapForFull($scope.videoID);
$scope.progress = {enabled: false}; $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) { .controller('UserModalController', function ($scope, $location, $rootScope, $modal, AppUsersManager, MtpApiManager, NotificationsManager, AppPhotosManager, AppMessagesManager, AppPeersManager, PeersSelectService, ErrorService) {
var peerString = AppUsersManager.getUserString($scope.userID); 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.send = {};
$scope.$watch('photo.file', onPhotoSelected); $scope.$watch('photo.file', onPhotoSelected);
@ -2177,31 +2210,30 @@ angular.module('myApp.controllers', ['myApp.i18n'])
if (settings[1]) { if (settings[1]) {
$scope.notify.volume = 0; $scope.notify.volume = 0;
} else if (settings[3] !== false) { } 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 { } else {
$scope.notify.volume = 5; $scope.notify.volume = 0.5;
} }
$scope.notify.canVibrate = NotificationsManager.getVibrateSupport(); $scope.notify.canVibrate = NotificationsManager.getVibrateSupport();
$scope.notify.vibrate = !settings[4]; $scope.notify.vibrate = !settings[4];
$scope.notify.volumeOf4 = function () { $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 () { $scope.toggleSound = function () {
if ($scope.notify.volume) { if ($scope.notify.volume) {
$scope.notify.volume = 0; $scope.notify.volume = 0;
} else { } else {
$scope.notify.volume = 5; $scope.notify.volume = 0.5;
} }
} }
var testSoundPromise; var testSoundPromise;
$scope.$watch('notify.volume', function (newValue, oldValue) { $scope.$watch('notify.volume', function (newValue, oldValue) {
if (newValue !== oldValue) { if (newValue !== oldValue) {
var storeVolume = newValue / 10; Storage.set({notify_volume: newValue});
Storage.set({notify_volume: storeVolume});
Storage.remove('notify_nosound'); Storage.remove('notify_nosound');
NotificationsManager.clear(); NotificationsManager.clear();
@ -2209,7 +2241,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$timeout.cancel(testSoundPromise); $timeout.cancel(testSoundPromise);
} }
testSoundPromise = $timeout(function () { testSoundPromise = $timeout(function () {
NotificationsManager.testSound(storeVolume); NotificationsManager.testSound(newValue);
}, 500); }, 500);
} }
}); });

417
app/js/directives.js

@ -177,19 +177,43 @@ angular.module('myApp.directives', ['myApp.filters'])
templateUrl: templateUrl('message_attach_photo') templateUrl: templateUrl('message_attach_photo')
}; };
}) })
.directive('myMessageVideo', function() { .directive('myMessageVideo', function(AppVideoManager) {
return { return {
templateUrl: templateUrl('message_attach_video') 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 () {
.directive('myMessageDocument', function() { AppVideoManager.openVideo($scope.video.id, $scope.messageId);
return { };
templateUrl: templateUrl('message_attach_document') }
}; };
}) })
.directive('myMessageAudio', function() { .directive('myMessageDocument', function(AppDocsManager) {
return { 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() { .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 { return {
link: link, link: link,
@ -1055,10 +1079,12 @@ angular.module('myApp.directives', ['myApp.filters'])
} }
function onPastedImageEvent (e) { function onPastedImageEvent (e) {
var element = e && e.target; var element = (e.originalEvent || e).target,
var src; src = (element || {}).src || '',
if (element && (src = element.src) && src.indexOf('data') === 0) { remove = false;
element.parentNode.removeChild(element);
if (src.substr(0, 5) == 'data:') {
remove = true;
src = src.substr(5).split(';'); src = src.substr(5).split(';');
var contentType = src[0]; var contentType = src[0];
var base64 = atob(src[1].split(',')[1]); var base64 = atob(src[1].split(',')[1]);
@ -1074,6 +1100,15 @@ angular.module('myApp.directives', ['myApp.filters'])
$scope.draftMessage.files = [blob]; $scope.draftMessage.files = [blob];
$scope.draftMessage.isMedia = true; $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 { return {
link: link, link: link,
@ -1329,37 +1364,12 @@ angular.module('myApp.directives', ['myApp.filters'])
function link ($scope, element, attrs) { function link ($scope, element, attrs) {
$scope.progress = {enabled: true, percent: 1}; var downloadPromise = AppVideoManager.downloadVideo($scope.video.id);
$scope.player = {};
var inputLocation = {
_: 'inputVideoFileLocation',
id: $scope.video.id,
access_hash: $scope.video.access_hash
};
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) { downloadPromise.then(function () {
$scope.progress.enabled = false;
// $scope.progress = {enabled: true, percent: 50};
$scope.player.hasQuicktime = hasQt;
$scope.player.quicktime = false;
$scope.player.src = $sce.trustAsResourceUrl(url);
$scope.$emit('ui_height'); $scope.$emit('ui_height');
}, function (e) { }, function (e) {
console.log('Download video failed', e, $scope.video); console.log('Download video failed', e, $scope.video);
$scope.progress.enabled = false;
$scope.player.src = '';
if (e && e.type == 'FS_BROWSER_UNSUPPORTED') { if (e && e.type == 'FS_BROWSER_UNSUPPORTED') {
$scope.error = {html: _('error_browser_no_local_file_system_video_md', { $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}; $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'); $scope.$emit('ui_height');
@ -1384,7 +1392,7 @@ angular.module('myApp.directives', ['myApp.filters'])
}) })
.directive('myLoadGif', function($rootScope, MtpApiFileManager) { .directive('myLoadGif', function(AppDocsManager) {
return { return {
link: link, link: link,
@ -1396,21 +1404,13 @@ angular.module('myApp.directives', ['myApp.filters'])
function link ($scope, element, attrs) { function link ($scope, element, attrs) {
var downloadPromise = false, var downloadPromise = false;
inputFileLocation = {
_: 'inputDocumentFileLocation',
id: $scope.document.id,
access_hash: $scope.document.access_hash
};
$scope.isActive = 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) { $scope.toggle = function (e) {
if (checkClick(e, true)) { if (checkClick(e, true)) {
$rootScope.downloadDoc($scope.document.id); AppDocsManager.saveDocFile($scope.document.id);
return false; return false;
} }
@ -1426,33 +1426,113 @@ angular.module('myApp.directives', ['myApp.filters'])
return; return;
} }
$scope.document.progress = {enabled: true, percent: 1, total: $scope.document.size}; downloadPromise = AppDocsManager.downloadDoc($scope.document.id);
downloadPromise = MtpApiFileManager.downloadFile(
$scope.document.dc_id,
inputFileLocation,
$scope.document.size,
null,
{mime: $scope.document.mime_type}
);
downloadPromise.then(function (url) { downloadPromise.then(function () {
$scope.document.url = url;
$scope.isActive = true; $scope.isActive = true;
delete $scope.document.progress;
console.log('file save done');
$scope.$emit('ui_height'); $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) { .directive('myMapPoint', function(ExternalResourcesManager) {
return { return {
@ -1719,8 +1799,8 @@ angular.module('myApp.directives', ['myApp.filters'])
var updateMargin = function () { var updateMargin = function () {
var height = element[0].offsetHeight, var height = element[0].offsetHeight,
fullHeight = height - (height && usePadding ? 2 * prevMargin : 0), fullHeight = height - (height && usePadding ? 2 * prevMargin : 0),
contHeight = $($window).height(),
ratio = attrs.myVerticalPosition && parseFloat(attrs.myVerticalPosition) || 0.5, ratio = attrs.myVerticalPosition && parseFloat(attrs.myVerticalPosition) || 0.5,
contHeight = attrs.contHeight ? $scope.$eval(attrs.contHeight) : $($window).height(),
margin = fullHeight < contHeight ? parseInt((contHeight - fullHeight) * ratio) : '', margin = fullHeight < contHeight ? parseInt((contHeight - fullHeight) * ratio) : '',
styles = usePadding styles = usePadding
? {paddingTop: margin, paddingBottom: margin} ? {paddingTop: margin, paddingBottom: margin}
@ -1736,9 +1816,10 @@ angular.module('myApp.directives', ['myApp.filters'])
prevMargin = margin; prevMargin = margin;
}; };
$($window).on('resize', updateMargin);
onContentLoaded(updateMargin); onContentLoaded(updateMargin);
$($window).on('resize', updateMargin);
$scope.$on('ui_height', function () { $scope.$on('ui_height', function () {
onContentLoaded(updateMargin); onContentLoaded(updateMargin);
@ -1751,7 +1832,7 @@ angular.module('myApp.directives', ['myApp.filters'])
}) })
.directive('myUserLink', function ($timeout, $rootScope, AppUsersManager) { .directive('myUserLink', function ($timeout, AppUsersManager) {
return { return {
link: link link: link
@ -1767,7 +1848,7 @@ angular.module('myApp.directives', ['myApp.filters'])
if (element[0].tagName == 'A') { if (element[0].tagName == 'A') {
element.on('click', function () { 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)) { 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 { return {
link: link, link: link,
@ -1840,7 +1921,7 @@ angular.module('myApp.directives', ['myApp.filters'])
if (element[0].tagName == 'A') { if (element[0].tagName == 'A') {
element.on('click', function (e) { 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 currentPlayer = false;
var audioVolume = 0.5;
Storage.get('audio_volume').then(function (newAudioVolume) {
if (newAudioVolume >= 0.0 && newAudioVolume <= 1.0) {
audioVolume = newAudioVolume;
}
});
return { return {
link: link, link: link,
@ -1863,33 +1951,6 @@ angular.module('myApp.directives', ['myApp.filters'])
templateUrl: templateUrl('audio_player') 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) { function checkPlayer (newPlayer) {
if (newPlayer === currentPlayer) { if (newPlayer === currentPlayer) {
return false; return false;
@ -1901,14 +1962,21 @@ angular.module('myApp.directives', ['myApp.filters'])
} }
function link($scope, element, attrs) { 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.mediaPlayer = {};
$scope.download = function () { $scope.download = function () {
($scope.audio.rawUrl ? $q.when() : downloadAudio($scope.audio)).then( if ($scope.audio._ == 'audio') {
function () { AppAudioManager.saveAudioFile($scope.audio.id);
FileManager.download($scope.audio.rawUrl, $scope.audio.mime_type || 'audio/ogg', $scope.audio.file_name || 'audio.ogg'); } else {
AppDocsManager.saveDocFile($scope.audio.id);
} }
);
}; };
$scope.togglePlay = function () { $scope.togglePlay = function () {
@ -1917,16 +1985,139 @@ angular.module('myApp.directives', ['myApp.filters'])
$scope.mediaPlayer.player.playPause(); $scope.mediaPlayer.player.playPause();
} }
else if ($scope.audio.progress && $scope.audio.progress.enabled) { else if ($scope.audio.progress && $scope.audio.progress.enabled) {
$scope.audio.progress.cancel(); return;
} }
else { 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 () { onContentLoaded(function () {
checkPlayer($scope.mediaPlayer.player); checkPlayer($scope.mediaPlayer.player);
$scope.mediaPlayer.player.setVolume(audioVolume);
$scope.mediaPlayer.player.play(); $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);
});
}
}) })

29
app/js/filters.js

@ -56,7 +56,7 @@ angular.module('myApp.filters', ['myApp.i18n'])
var cachedDates = {}, var cachedDates = {},
dateFilter = $filter('date'); dateFilter = $filter('date');
return function (timestamp) { return function (timestamp, extended) {
if (cachedDates[timestamp]) { if (cachedDates[timestamp]) {
return cachedDates[timestamp]; return cachedDates[timestamp];
@ -67,11 +67,12 @@ angular.module('myApp.filters', ['myApp.i18n'])
format = 'shortTime'; format = 'shortTime';
if (diff > 518400000) { // 6 days if (diff > 518400000) { // 6 days
format = 'shortDate'; format = extended ? 'mediumDate' : 'shortDate';
} }
else if (diff > 43200000) { // 12 hours else if (diff > 43200000) { // 12 hours
format = 'EEE'; format = extended ? 'EEEE' : 'EEE';
} }
return cachedDates[timestamp] = dateFilter(ticks, format); 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() { .filter('phoneNumber', [function() {
return function (phoneRaw) { return function (phoneRaw) {
var nbsp = ' '; var nbsp = ' ';
@ -140,13 +149,13 @@ angular.module('myApp.filters', ['myApp.i18n'])
return size + ' b'; return size + ' b';
} }
else if (size < 1048576) { else if (size < 1048576) {
return (Math.round(size / 1024 * 10) / 10) + ' Kb'; return Math.round(size / 1024) + ' Kb';
} }
var mbs = size / 1048576; var mbs = size / 1048576;
if (progressing) { if (progressing) {
mbs = mbs.toFixed(1); mbs = mbs.toFixed(1);
} else { } else {
mbs = (Math.round(mbs * 100) / 100); mbs = (Math.round(mbs * 10) / 10);
} }
return mbs + ' Mb'; return mbs + ' Mb';
} }
@ -192,14 +201,14 @@ angular.module('myApp.filters', ['myApp.i18n'])
if (diff < 60000) { if (diff < 60000) {
return _('relative_time_just_now'); return _('relative_time_just_now');
} }
if (diff < 3000000) { if (diff < 3600000) {
var minutes = Math.ceil(diff / 60000); var minutes = Math.floor(diff / 60000);
return langMinutesPluralize(minutes); return langMinutesPluralize(minutes);
} }
if (diff < 10000000) { if (diff < 86400000) {
var hours = Math.ceil(diff / 3600000); var hours = Math.floor(diff / 3600000);
return langHoursPluralize(hours); return langHoursPluralize(hours);
} }
return dateOrTimeFilter(timestamp); return dateOrTimeFilter(timestamp, true);
} }
}) })

10
app/js/i18n.js

@ -21,16 +21,6 @@ angular.module('myApp.i18n', ['izhukov.utils'])
}); });
} }
function encodeEntities(value) {
return value.
replace(/&/g, '&amp;').
replace(/([^\#-~| |!\n\*])/g, function (value) { // non-alphanumeric
return '&#' + value.charCodeAt(0) + ';';
}).
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
}
function parseMarkdownString(msgstr, msgid) { function parseMarkdownString(msgstr, msgid) {
msgstr = msgstr.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>") msgstr = msgstr.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
.replace(/\n/g, "<br/>"); .replace(/\n/g, "<br/>");

117
app/js/lib/bin_utils.js

@ -112,10 +112,12 @@ function bytesXor (bytes1, bytes2) {
} }
function bytesToWords (bytes) { function bytesToWords (bytes) {
if (bytes instanceof ArrayBuffer) {
bytes = new Uint8Array(bytes);
}
var len = bytes.length, var len = bytes.length,
words = []; words = [], i;
for (i = 0; i < len; i++) {
for (var i = 0; i < len; i++) {
words[i >>> 2] |= bytes[i] << (24 - (i % 4) * 8); words[i >>> 2] |= bytes[i] << (24 - (i % 4) * 8);
} }
@ -154,6 +156,37 @@ function bytesToArrayBuffer (b) {
return (new Uint8Array(b)).buffer; 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) { function bytesFromArrayBuffer (buffer) {
var len = buffer.byteLength, var len = buffer.byteLength,
byteView = new Uint8Array(buffer), byteView = new Uint8Array(buffer),
@ -166,6 +199,16 @@ function bytesFromArrayBuffer (buffer) {
return bytes; 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) { function longToInts (sLong) {
var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000)); var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000));
@ -195,24 +238,24 @@ function uintToInt (val) {
return val; return val;
} }
function sha1Hash (bytes) { function sha1HashSync (bytes) {
// console.log('SHA-1 hash start'); this.rushaInstance = this.rushaInstance || new Rusha(1024 * 1024);
var hashBytes = sha1.hash(bytes, true);
// console.log('SHA-1 hash finish'); // 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; 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'); // console.log('RSA encrypt start');
var N = new BigInteger(publicKey.modulus, 16), var N = new BigInteger(publicKey.modulus, 16),
@ -220,22 +263,34 @@ function rsaEncrypt (publicKey, bytes) {
X = new BigInteger(bytes), X = new BigInteger(bytes),
encryptedBigInt = X.modPowInt(E, N), encryptedBigInt = X.modPowInt(E, N),
encryptedBytes = bytesFromBigInt(encryptedBigInt, 256); encryptedBytes = bytesFromBigInt(encryptedBigInt, 256);
// console.log('RSA encrypt finish'); // console.log('RSA encrypt finish');
return encryptedBytes; return encryptedBytes;
} }
function aesEncrypt (bytes, keyBytes, ivBytes) { function addPadding(bytes, blockSize) {
// console.log('AES encrypt start', bytes.length/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/); blockSize = blockSize || 16;
var len = bytes.byteLength || bytes.length;
var needPadding = 16 - (bytes.length % 16); var needPadding = blockSize - (len % blockSize);
if (needPadding > 0 && needPadding < 16) { if (needPadding > 0 && needPadding < blockSize) {
var padding = new Array(needPadding); var padding = new Array(needPadding);
(new SecureRandom()).nextBytes(padding); (new SecureRandom()).nextBytes(padding);
if (bytes instanceof ArrayBuffer) {
bytes = bufferConcat(bytes, padding);
} else {
bytes = bytes.concat(padding); 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), { var encryptedWords = CryptoJS.AES.encrypt(bytesToWords(bytes), bytesToWords(keyBytes), {
iv: bytesToWords(ivBytes), iv: bytesToWords(ivBytes),
@ -244,15 +299,14 @@ function aesEncrypt (bytes, keyBytes, ivBytes) {
}).ciphertext; }).ciphertext;
var encryptedBytes = bytesFromWords(encryptedWords); var encryptedBytes = bytesFromWords(encryptedWords);
// console.log(dT(), 'AES encrypt finish');
// console.log('AES encrypt finish');
return encryptedBytes; return encryptedBytes;
} }
function aesDecrypt (encryptedBytes, keyBytes, ivBytes) { function aesDecryptSync (encryptedBytes, keyBytes, ivBytes) {
// console.log('AES decrypt start', encryptedBytes.length/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/);
// console.log(dT(), 'AES decrypt start', encryptedBytes.length);
var decryptedWords = CryptoJS.AES.decrypt({ciphertext: bytesToWords(encryptedBytes)}, bytesToWords(keyBytes), { var decryptedWords = CryptoJS.AES.decrypt({ciphertext: bytesToWords(encryptedBytes)}, bytesToWords(keyBytes), {
iv: bytesToWords(ivBytes), iv: bytesToWords(ivBytes),
padding: CryptoJS.pad.NoPadding, padding: CryptoJS.pad.NoPadding,
@ -260,8 +314,7 @@ function aesDecrypt (encryptedBytes, keyBytes, ivBytes) {
}); });
var bytes = bytesFromWords(decryptedWords); var bytes = bytesFromWords(decryptedWords);
// console.log(dT(), 'AES decrypt finish');
// console.log('AES decrypt finish');
return bytes; return bytes;
} }
@ -281,7 +334,7 @@ function pqPrimeFactorization (pqBytes) {
var what = new BigInteger(pqBytes), var what = new BigInteger(pqBytes),
result = false; result = false;
console.log('PQ start', pqBytes, what.toString(16), what.bitLength()); // console.log(dT(), 'PQ start', pqBytes, what.toString(16), what.bitLength());
try { try {
result = pqPrimeLeemon(str2bigInt(what.toString(16), 16, Math.ceil(64 / bpe) + 1)) result = pqPrimeLeemon(str2bigInt(what.toString(16), 16, Math.ceil(64 / bpe) + 1))
@ -306,7 +359,7 @@ function pqPrimeFactorization (pqBytes) {
// console.timeEnd('pq BigInt'); // console.timeEnd('pq BigInt');
} }
console.log('PQ finish'); // console.log(dT(), 'PQ finish');
return result; return result;
} }
@ -520,13 +573,15 @@ function pqPrimeLeemon (what) {
function bytesModPow (x, y, m) { function bytesModPow (x, y, m) {
try { try {
var xBigInt = str2bigInt(x, 64), var xBigInt = str2bigInt(bytesToHex(x), 16),
yBigInt = str2bigInt(y, 64), yBigInt = str2bigInt(bytesToHex(y), 16),
mBigInt = str2bigInt(bytesToHex(m), 16, 2), mBigInt = str2bigInt(bytesToHex(m), 16, 2),
resBigInt = powMod(xBigInt, yBigInt, mBigInt); resBigInt = powMod(xBigInt, yBigInt, mBigInt);
return bytesFromHex(bigInt2str(resBigInt, 16)); 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))); return bytesFromBigInt(new BigInteger(x).modPow(new BigInteger(y), new BigInteger(m)));
} }

3
app/js/lib/config.js

@ -27,6 +27,7 @@ Config.App = {
Config.Modes = { Config.Modes = {
test: location.search.indexOf('test=1') > 0, test: location.search.indexOf('test=1') > 0,
debug: location.search.indexOf('debug=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:', packed: location.protocol == 'app:' || location.protocol == 'chrome-extension:',
ios_standalone: window.navigator.standalone && navigator.userAgent.match(/iOS|iPhone|iPad/), ios_standalone: window.navigator.standalone && navigator.userAgent.match(/iOS|iPhone|iPad/),
chrome_packed: window.chrome && chrome.app && chrome.app.window && true || false 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++) { for (i = 0; i < keys.length; i++) {
key = keys[i] = prefix + keys[i]; key = keys[i] = prefix + keys[i];
if (cache[key] !== undefined) { if (key.substr(0, 3) != 'xt_' && cache[key] !== undefined) {
result.push(cache[key]); result.push(cache[key]);
} }
else if (useLs) { else if (useLs) {

11
app/js/lib/crypto_worker.js

@ -11,7 +11,8 @@ importScripts(
'../../vendor/jsbn/jsbn_combined.js', '../../vendor/jsbn/jsbn_combined.js',
'../../vendor/leemon_bigint/bigint.js', '../../vendor/leemon_bigint/bigint.js',
'../../vendor/closure/long.js', '../../vendor/closure/long.js',
'../../vendor/cryptoJS/crypto.js' '../../vendor/cryptoJS/crypto.js',
'../../vendor/rusha/rusha.js'
); );
onmessage = function (e) { onmessage = function (e) {
@ -28,15 +29,15 @@ onmessage = function (e) {
break; break;
case 'sha1-hash': case 'sha1-hash':
result = sha1Hash(e.data.bytes); result = sha1HashSync(e.data.bytes);
break; break;
case 'aes-encrypt': 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; break;
case 'aes-decrypt': 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; break;
default: default:
@ -45,3 +46,5 @@ onmessage = function (e) {
postMessage({taskID: taskID, result: result}); postMessage({taskID: taskID, result: result});
} }
postMessage('ready');

253
app/js/lib/mtproto.js

@ -8,6 +8,8 @@
angular.module('izhukov.mtproto', ['izhukov.utils']) angular.module('izhukov.mtproto', ['izhukov.utils'])
.factory('MtpDcConfigurator', function () { .factory('MtpDcConfigurator', function () {
var sslSubdomains = ['pluto', 'venus', 'aurora', 'vesta', 'flora'];
var dcOptions = Config.Modes.test var dcOptions = Config.Modes.test
? [ ? [
{id: 1, host: '173.240.5.253', port: 80}, {id: 1, host: '173.240.5.253', port: 80},
@ -24,14 +26,23 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
var chosenServers = {}; var chosenServers = {};
function chooseServer(dcID) { function chooseServer(dcID, upload) {
if (chosenServers[dcID] === undefined) { if (chosenServers[dcID] === undefined) {
var chosenServer = false, var chosenServer = false,
i, dcOption; 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++) { for (i = 0; i < dcOptions.length; i++) {
dcOption = dcOptions[i]; dcOption = dcOptions[i];
if (dcOption.id == dcID) { if (dcOption.id == dcID) {
chosenServer = dcOption.host + ':' + dcOption.port; chosenServer = 'http://' + dcOption.host + (dcOption.port != 80 ? ':' + dcOption.port : '') + '/apiw1';
break;
} }
} }
chosenServers[dcID] = chosenServer; chosenServers[dcID] = chosenServer;
@ -82,7 +93,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
var buffer = RSAPublicKey.getBuffer(); var buffer = RSAPublicKey.getBuffer();
var fingerprintBytes = sha1Hash(buffer).slice(-8); var fingerprintBytes = sha1BytesSync(buffer).slice(-8);
fingerprintBytes.reverse(); fingerprintBytes.reverse();
publicKeysParsed[bytesToHex(fingerprintBytes)] = { 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(); return new SecureRandom();
}) })
@ -143,7 +155,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
lastMessageID = messageID; lastMessageID = messageID;
// console.log('generated msg id', messageID); // console.log('generated msg id', messageID, timeOffset);
return longFromInts(messageID[0], messageID[1]); 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) { .factory('MtpAuthorizer', function (MtpDcConfigurator, MtpRsaKeysManager, MtpSecureRandom, MtpTimeManager, CryptoWorker, $http, $q, $timeout) {
var chromeMatches = navigator.userAgent.match(/Chrome\/(\d+(\.\d+)?)/), 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) { function mtpSendPlainRequest (dcID, requestBuffer) {
var requestLength = requestBuffer.byteLength, var requestLength = requestBuffer.byteLength,
@ -190,16 +206,10 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
resultArray.set(headerArray); resultArray.set(headerArray);
resultArray.set(requestArray, headerArray.length); resultArray.set(requestArray, headerArray.length);
delete $http.defaults.headers.post['Content-Type']; var requestData = xhrSendBuffer ? resultBuffer : resultArray,
delete $http.defaults.headers.common['Accept']; requestPromise;
if (!('ArrayBufferView' in window) && (!chromeVersion || chromeVersion < 30)) {
resultArray = resultArray.buffer;
}
var requestPromise;
try { try {
requestPromise = $http.post('http://' + MtpDcConfigurator.chooseServer(dcID) + '/apiw1', resultArray, { requestPromise = $http.post(MtpDcConfigurator.chooseServer(dcID), requestData, {
responseType: 'arraybuffer', responseType: 'arraybuffer',
transformRequest: null transformRequest: null
}); });
@ -223,8 +233,6 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
return $q.reject({code: 406, type: 'NETWORK_BAD_RESPONSE', originalError: e}); return $q.reject({code: 406, type: 'NETWORK_BAD_RESPONSE', originalError: e});
} }
rng_seed_time();
return deserializer; return deserializer;
}, },
function (error) { function (error) {
@ -305,7 +313,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
new_nonce: auth.newNonce new_nonce: auth.newNonce
}, 'P_Q_inner_data', 'DECRYPTED_DATA'); }, '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}); var request = new TLSerialization({mtproto: true});
request.storeMethod('req_DH_params', { request.storeMethod('req_DH_params', {
@ -337,7 +345,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
} }
if (response._ == 'server_DH_params_fail') { 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)) { if (!bytesCmp (newNonceHash, response.new_nonce_hash)) {
deferred.reject(new Error('server_DH_params_fail new_nonce_hash mismatch')); deferred.reject(new Error('server_DH_params_fail new_nonce_hash mismatch'));
return false; return false;
@ -362,10 +370,10 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
function mtpDecryptServerDhDataAnswer (auth, encryptedAnswer) { function mtpDecryptServerDhDataAnswer (auth, encryptedAnswer) {
auth.localTime = tsNow(); auth.localTime = tsNow();
auth.tmpAesKey = sha1Hash(auth.newNonce.concat(auth.serverNonce)).concat(sha1Hash(auth.serverNonce.concat(auth.newNonce)).slice(0, 12)); auth.tmpAesKey = sha1BytesSync(auth.newNonce.concat(auth.serverNonce)).concat(sha1BytesSync(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.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 hash = answerWithHash.slice(0, 20);
var answerWithPadding = answerWithHash.slice(20); var answerWithPadding = answerWithHash.slice(20);
@ -395,7 +403,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
var offset = deserializer.getOffset(); 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'); throw new Error('server_DH_inner_data SHA1-hash mismatch');
} }
@ -420,9 +428,9 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
g_b: gB, g_b: gB,
}, 'Client_DH_Inner_Data'); }, '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}); var request = new TLSerialization({mtproto: true});
request.storeMethod('set_client_DH_params', { 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) { CryptoWorker.modPow(auth.gA, auth.b, auth.dhPrime).then(function (authKey) {
var authKeyHash = sha1Hash(authKey), var authKeyHash = sha1BytesSync(authKey),
authKeyAux = authKeyHash.slice(0, 8), authKeyAux = authKeyHash.slice(0, 8),
authKeyID = authKeyHash.slice(-8); authKeyID = authKeyHash.slice(-8);
console.log(dT(), 'Got Set_client_DH_params_answer', response._); console.log(dT(), 'Got Set_client_DH_params_answer', response._);
switch (response._) { switch (response._) {
case 'dh_gen_ok': 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)) { if (!bytesCmp(newNonceHash1, response.new_nonce_hash1)) {
deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash1 mismatch')); deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash1 mismatch'));
@ -476,7 +484,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
break; break;
case 'dh_gen_retry': 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)) { if (!bytesCmp(newNonceHash2, response.new_nonce_hash2)) {
deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash2 mismatch')); deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash2 mismatch'));
return false; return false;
@ -485,7 +493,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
return mtpSendSetClientDhParams(auth); return mtpSendSetClientDhParams(auth);
case 'dh_gen_fail': 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)) { if (!bytesCmp(newNonceHash3, response.new_nonce_hash3)) {
deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash3 mismatch')); deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash3 mismatch'));
return false; return false;
@ -552,8 +560,13 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
iii = 0, iii = 0,
offline, offline,
offlineInited = false, offlineInited = false,
akStopped = false,
chromeMatches = navigator.userAgent.match(/Chrome\/(\d+(\.\d+)?)/), 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 () { $rootScope.retryOnline = function () {
$(document.body).trigger('online'); $(document.body).trigger('online');
@ -566,7 +579,9 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
this.iii = iii++; this.iii = iii++;
this.authKey = authKey; 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; this.serverSalt = serverSalt;
@ -738,7 +753,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
message = { message = {
msg_id: messageID, msg_id: messageID,
seq_no: seqNo, seq_no: seqNo,
body: serializer.getBytes(), body: serializer.getBytes(true),
isAPI: true isAPI: true
}; };
@ -754,7 +769,9 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
MtpNetworker.prototype.checkLongPoll = function(force) { MtpNetworker.prototype.checkLongPoll = function(force) {
var isClean = this.cleanupSent(); var isClean = this.cleanupSent();
// console.log('Check lp', this.longPollPending, tsNow(), this.dcID, isClean); // 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; return false;
} }
var self = this; var self = this;
@ -787,7 +804,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
longPoll: true longPoll: true
}).then(function () { }).then(function () {
delete self.longPollPending; delete self.longPollPending;
$timeout(self.checkLongPoll.bind(self), 0); setZeroTimeout(self.checkLongPoll.bind(self));
}, function () { }, function () {
console.log('Long-poll failed'); console.log('Long-poll failed');
}); });
@ -827,19 +844,47 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
}; };
MtpNetworker.prototype.getMsgKeyIv = function (msgKey, isOut) { MtpNetworker.prototype.getMsgKeyIv = function (msgKey, isOut) {
var authKey = this.authKey, var authKey = this.authKeyUint8,
x = isOut ? 0 : 8; x = isOut ? 0 : 8,
sha1aText = new Uint8Array(48),
var promises = { sha1bText = new Uint8Array(48),
sha1a: CryptoWorker.sha1Hash(msgKey.concat(authKey.slice(x, x + 32))), sha1cText = new Uint8Array(48),
sha1b: CryptoWorker.sha1Hash(authKey.slice(32 + x, 48 + x).concat(msgKey, authKey.slice(48 + x, 64 + x))), sha1dText = new Uint8Array(48),
sha1c: CryptoWorker.sha1Hash(authKey.slice(64 + x, 96 + x).concat(msgKey)), promises = {};
sha1d: CryptoWorker.sha1Hash(msgKey.concat(authKey.slice(96 + x, 128 + x)))
}; 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) { 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 aesKey = new Uint8Array(32),
var aesIv = result.sha1a.slice(8, 20).concat(result.sha1b.slice(0, 8), result.sha1c.slice(16, 20), result.sha1d.slice(0, 8)); 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]; return [aesKey, aesIv];
}); });
@ -916,8 +961,8 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
MtpNetworker.prototype.performSheduledRequest = function() { MtpNetworker.prototype.performSheduledRequest = function() {
// console.trace('sheduled', this.dcID, this.iii); // console.log(dT(), 'sheduled', this.dcID, this.iii);
if (this.offline) { if (this.offline || akStopped) {
console.log(dT(), 'Cancel sheduled'); console.log(dT(), 'Cancel sheduled');
return false; return false;
} }
@ -948,13 +993,32 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
currentTime = tsNow(), currentTime = tsNow(),
hasApiCall = false, hasApiCall = false,
hasHttpWait = false, hasHttpWait = false,
lengthOverflow = false,
singlesCount = 0,
self = this; self = this;
angular.forEach(this.pendingMessages, function (value, messageID) { angular.forEach(this.pendingMessages, function (value, messageID) {
if (!value || value >= currentTime) { if (!value || value >= currentTime) {
if (message = self.sentMessages[messageID]) { 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); messages.push(message);
messagesByteLen += message.body.length + 32; messagesByteLen += messageByteLength;
if (message.isAPI) { if (message.isAPI) {
hasApiCall = true; hasApiCall = true;
} }
@ -1009,7 +1073,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
inner: innerMessages inner: innerMessages
} }
message = angular.extend({body: container.getBytes()}, containerSentMessage); message = angular.extend({body: container.getBytes(true)}, containerSentMessage);
this.sentMessages[message.msg_id] = containerSentMessage; this.sentMessages[message.msg_id] = containerSentMessage;
@ -1071,15 +1135,23 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
self.toggleOffline(true); self.toggleOffline(true);
}); });
if (lengthOverflow || singlesCount > 1) {
this.sheduleRequest()
}
}; };
MtpNetworker.prototype.getEncryptedMessage = function (bytes) { MtpNetworker.prototype.getEncryptedMessage = function (bytes) {
var self = this; var self = this;
// console.log(dT(), 'Start encrypt', bytes.byteLength);
return CryptoWorker.sha1Hash(bytes).then(function (bytesHash) { 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) { 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) { return CryptoWorker.aesEncrypt(bytes, keyIv[0], keyIv[1]).then(function (encryptedBytes) {
// console.log(dT(), 'Finish encrypt');
return { return {
bytes: encryptedBytes, bytes: encryptedBytes,
msgKey: msgKey msgKey: msgKey
@ -1090,7 +1162,9 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
}; };
MtpNetworker.prototype.getDecryptedMessage = function (msgKey, encryptedData) { MtpNetworker.prototype.getDecryptedMessage = function (msgKey, encryptedData) {
// console.log(dT(), 'get decrypted start');
return this.getMsgKeyIv(msgKey, false).then(function (keyIv) { return this.getMsgKeyIv(msgKey, false).then(function (keyIv) {
// console.log(dT(), 'after msg key iv');
return CryptoWorker.aesDecrypt(encryptedData, keyIv[0], keyIv[1]); 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.storeInt(message.body.length, 'message_data_length');
data.storeRawBytes(message.body, 'message_data'); 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*/); // 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(self.authKeyID, 64, 'auth_key_id');
request.storeIntBytes(encryptedResult.msgKey, 128, 'msg_key'); request.storeIntBytes(encryptedResult.msgKey, 128, 'msg_key');
request.storeRawBytes(encryptedResult.bytes, 'encrypted_data'); request.storeRawBytes(encryptedResult.bytes, 'encrypted_data');
delete $http.defaults.headers.post['Content-Type']; var requestData = xhrSendBuffer ? request.getBuffer() : request.getArray();
delete $http.defaults.headers.common['Accept'];
var resultArray = request.getArray();
if (!('ArrayBufferView' in window) && (!chromeVersion || chromeVersion < 30)) {
resultArray = resultArray.buffer;
}
var requestPromise; var requestPromise;
try { try {
@ -1132,7 +1200,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
responseType: 'arraybuffer', responseType: 'arraybuffer',
transformRequest: null 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) { } catch (e) {
requestPromise = $q.reject(e); requestPromise = $q.reject(e);
} }
@ -1168,32 +1236,31 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
var deserializer = new TLDeserialization(responseBuffer); 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)) { if (!bytesCmp(authKeyID, this.authKeyID)) {
throw new Error('Invalid server auth_key_id: ' + bytesToHex(authKeyID)); throw new Error('Invalid server auth_key_id: ' + bytesToHex(authKeyID));
} }
var msgKey = deserializer.fetchIntBytes(128, 'msg_key'); var msgKey = deserializer.fetchIntBytes(128, true, 'msg_key'),
encryptedData = deserializer.fetchRawBytes(responseBuffer.byteLength - deserializer.getOffset(), true, 'encrypted_data');
var dataLength = responseBuffer.byteLength - deserializer.getOffset();
var encryptedData = deserializer.fetchRawBytes(dataLength, 'encrypted_data');
return this.getDecryptedMessage(msgKey, encryptedData).then(function (dataWithPadding) { return this.getDecryptedMessage(msgKey, encryptedData).then(function (dataWithPadding) {
var buffer = bytesToArrayBuffer(dataWithPadding); // console.log(dT(), 'after decrypt');
var deserializer = new TLDeserialization(dataWithPadding, {mtproto: true});
var deserializer = new TLDeserialization(buffer, {mtproto: true});
var salt = deserializer.fetchIntBytes(64, 'salt'); var salt = deserializer.fetchIntBytes(64, false, 'salt');
var sessionID = deserializer.fetchIntBytes(64, 'session_id'); var sessionID = deserializer.fetchIntBytes(64, false, 'session_id');
var messageID = deserializer.fetchLong('message_id'); var messageID = deserializer.fetchLong('message_id');
var seqNo = deserializer.fetchInt('seq_no'); 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) { return CryptoWorker.sha1Hash(hashData).then(function (dataHash) {
if (!bytesCmp(msgKey, dataHashed.slice(-16))) { if (!bytesCmp(msgKey, bytesFromArrayBuffer(dataHash).slice(-16))) {
console.warn(msgKey, bytesFromArrayBuffer(dataHash));
throw new Error('server msgKey mismatch'); throw new Error('server msgKey mismatch');
} }
@ -1216,7 +1283,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
} }
if (this.offset != offset + result.bytes) { if (this.offset != offset + result.bytes) {
console.warn(dT(), 'set offset', 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; this.offset = offset + result.bytes;
} }
// console.log(dT(), 'override message', result); // console.log(dT(), 'override message', result);
@ -1233,7 +1300,6 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
} }
}; };
var deserializer = new TLDeserialization(buffer, deserializerOptions); var deserializer = new TLDeserialization(buffer, deserializerOptions);
var response = deserializer.fetchObject('', 'INPUT'); var response = deserializer.fetchObject('', 'INPUT');
return { return {
@ -1267,17 +1333,17 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
return false; return false;
} }
// console.log('shedule req', delay); // console.log(dT(), 'shedule req', delay);
// console.trace(); // console.trace();
$timeout.cancel(this.nextReqPromise); $timeout.cancel(this.nextReqPromise);
if (delay > 0) {
this.nextReqPromise = $timeout(this.performSheduledRequest.bind(this), delay || 0); this.nextReqPromise = $timeout(this.performSheduledRequest.bind(this), delay || 0);
this.nextReq = nextReq; } else {
}; setZeroTimeout(this.performSheduledRequest.bind(this))
}
MtpNetworker.prototype.onSessionCreate = function (sessionID, messageID) { this.nextReq = nextReq;
// console.log(dT(), 'New session created', bytesToHex(sessionID));
}; };
MtpNetworker.prototype.ackMessage = function (msgID) { MtpNetworker.prototype.ackMessage = function (msgID) {
@ -1400,7 +1466,13 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
this.processMessageAck(message.first_msg_id); this.processMessageAck(message.first_msg_id);
this.applyServerSalt(message.server_salt); 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; break;
case 'msgs_ack': 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 { return {
getNetworker: function (dcID, authKey, serverSalt, options) { getNetworker: function (dcID, authKey, serverSalt, options) {
return new MtpNetworker(dcID, authKey, serverSalt, options); return new MtpNetworker(dcID, authKey, serverSalt, options);
}, },
setUpdatesProcessor: function (callback) { setUpdatesProcessor: function (callback) {
updatesProcessor = callback; updatesProcessor = callback;
} },
stopAll: stopAll,
startAll: startAll
}; };
}) })

197
app/js/lib/mtproto_wrapper.js

@ -7,12 +7,14 @@
angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto']) 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 = {}, var cachedNetworkers = {},
cachedUploadNetworkers = {}, cachedUploadNetworkers = {},
cachedExportPromise = {}, cachedExportPromise = {},
baseDcID = false; baseDcID = false;
MtpSingleInstanceService.start();
Storage.get('dc').then(function (dcID) { Storage.get('dc').then(function (dcID) {
if (dcID) { if (dcID) {
baseDcID = dcID; baseDcID = dcID;
@ -54,7 +56,9 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
} }
if (cache[dcID] !== undefined) { if (cache[dcID] !== undefined) {
return $q.when(cache[dcID]); return {then: function (cb) {
cb(cache[dcID]);
}};
} }
var akk = 'dc' + dcID + '_auth_key', var akk = 'dc' + dcID + '_auth_key',
@ -120,24 +124,17 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
dcID, dcID,
networkerPromise; networkerPromise;
if (dcID = options.dcID) { var cachedNetworker;
networkerPromise = mtpGetNetworker(dcID, options); var stack = (new Error()).stack;
} else { if (!stack) {
networkerPromise = Storage.get('dc').then(function (baseDcID) { try {window.unexistingFunction();} catch (e) {
return mtpGetNetworker(dcID = baseDcID || 2, options); stack = e.stack || '';
});
} }
}
var cachedNetworker, var performRequest = function (networker) {
stack = false;
networkerPromise.then(function (networker) {
return (cachedNetworker = networker).wrapApiCall(method, params, options).then( return (cachedNetworker = networker).wrapApiCall(method, params, options).then(
function (result) { function (result) {
deferred.resolve(result); deferred.resolve(result);
// $timeout(function () {
// deferred.resolve(result);
// }, 1000);
}, },
function (error) { function (error) {
console.error(dT(), 'Error', error.code, error.type, baseDcID, dcID); 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 () { cachedExportPromise[dcID].then(function () {
(cachedNetworker = networker).wrapApiCall(method, params, options).then(function (result) { (cachedNetworker = networker).wrapApiCall(method, params, options).then(function (result) {
deferred.resolve(result); deferred.resolve(result);
}, function (error) { }, rejectPromise);
rejectPromise(error); }, rejectPromise);
});
}, function (error) {
rejectPromise(error);
});
} }
else if (error.code == 303) { else if (error.code == 303) {
var newDcID = error.type.match(/^(PHONE_MIGRATE_|NETWORK_MIGRATE_|USER_MIGRATE_)(\d+)/)[2]; 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) { mtpGetNetworker(newDcID, options).then(function (networker) {
networker.wrapApiCall(method, params, options).then(function (result) { networker.wrapApiCall(method, params, options).then(function (result) {
deferred.resolve(result); deferred.resolve(result);
}, function (error) { }, rejectPromise);
rejectPromise(error);
});
}); });
} }
} }
@ -197,14 +188,14 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
rejectPromise(error); rejectPromise(error);
} }
}); });
}, function (error) { };
rejectPromise(error);
});
if (!(stack = (stack || (new Error()).stack))) { if (dcID = (options.dcID || baseDcID)) {
try {window.unexistingFunction();} catch (e) { mtpGetNetworker(dcID, options).then(performRequest, rejectPromise);
stack = e.stack || ''; } else {
} Storage.get('dc').then(function (baseDcID) {
mtpGetNetworker(dcID = baseDcID || 2, options).then(performRequest, rejectPromise);
});
} }
return deferred.promise; return deferred.promise;
@ -234,14 +225,12 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
var cachedFs = false; var cachedFs = false;
var cachedFsPromise = false; var cachedFsPromise = false;
var apiUploadPromise = $q.when();
var cachedSavePromises = {}; var cachedSavePromises = {};
var cachedDownloadPromises = {}; var cachedDownloadPromises = {};
var cachedDownloads = {}; var cachedDownloads = {};
var downloadPulls = {}; var downloadPulls = {};
var downloadActives = {}; var downloadActives = {};
var downloadLimit = 5;
function downloadRequest(dcID, cb, activeDelta) { function downloadRequest(dcID, cb, activeDelta) {
if (downloadPulls[dcID] === undefined) { if (downloadPulls[dcID] === undefined) {
@ -251,7 +240,9 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
var downloadPull = downloadPulls[dcID]; var downloadPull = downloadPulls[dcID];
var deferred = $q.defer(); var deferred = $q.defer();
downloadPull.push({cb: cb, deferred: deferred, activeDelta: activeDelta}); downloadPull.push({cb: cb, deferred: deferred, activeDelta: activeDelta});
setZeroTimeout(function () {
downloadCheck(dcID); downloadCheck(dcID);
});
return deferred.promise; return deferred.promise;
}; };
@ -260,6 +251,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
function downloadCheck(dcID) { function downloadCheck(dcID) {
var downloadPull = downloadPulls[dcID]; var downloadPull = downloadPulls[dcID];
var downloadLimit = dcID == 'upload' ? 11 : 5;
if (downloadActives[dcID] >= downloadLimit || !downloadPull || !downloadPull.length) { if (downloadActives[dcID] >= downloadLimit || !downloadPull || !downloadPull.length) {
return false; 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) { function downloadFile (dcID, location, size, options) {
if (!FileManager.isAvailable()) { if (!FileManager.isAvailable()) {
return $q.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'}); return $q.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'});
@ -464,7 +463,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
fileDownload: true, fileDownload: true,
createNetworker: true createNetworker: true
}); });
}, 6).then(function (result) { }, 2).then(function (result) {
writeFilePromise.then(function () { writeFilePromise.then(function () {
if (canceled) { if (canceled) {
return $q.when(); return $q.when();
@ -508,14 +507,22 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
function uploadFile (file) { function uploadFile (file) {
var fileSize = file.size, var fileSize = file.size,
// partSize = fileSize > 102400 ? 65536 : 4096,
// partSize = fileSize > 102400 ? 524288 : 4096,
partSize = fileSize > 102400 ? 524288 : 32768,
isBigFile = fileSize >= 10485760, isBigFile = fileSize >= 10485760,
totalParts = Math.ceil(fileSize / partSize),
canceled = false, canceled = false,
resolved = false, resolved = false,
doneParts = 0; 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) { if (totalParts > 1500) {
return $q.reject({type: 'FILE_TOO_BIG'}); return $q.reject({type: 'FILE_TOO_BIG'});
@ -526,6 +533,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
errorHandler = function (error) { errorHandler = function (error) {
// console.error('Up Error', error); // console.error('Up Error', error);
deferred.reject(error); deferred.reject(error);
canceled = true;
errorHandler = angular.noop; errorHandler = angular.noop;
}, },
part = 0, 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) { for (offset = 0; offset < fileSize; offset += partSize) {
(function (offset, part) { (function (offset, part) {
fileReadPromise = fileReadPromise.then(function () { downloadRequest('upload', function () {
var fileReadDeferred = $q.defer(); var uploadDeferred = $q.defer();
var reader = new FileReader(); var reader = new FileReader();
var blob = file.slice(offset, offset + partSize); var blob = file.slice(offset, offset + partSize);
reader.onloadend = function (e) { reader.onloadend = function (e) {
if (canceled || e.target.readyState != FileReader.DONE) { if (canceled) {
uploadDeferred.reject();
return; return;
} }
var apiCurPromise = apiUploadPromise = apiUploadPromise.then(function () { if (e.target.readyState != FileReader.DONE) {
return MtpApiManager.invokeApi(isBigFile ? 'upload.saveBigFilePart' : 'upload.saveFilePart', { return;
}
MtpApiManager.invokeApi(isBigFile ? 'upload.saveBigFilePart' : 'upload.saveFilePart', {
file_id: fileID, file_id: fileID,
file_part: part, file_part: part,
file_total_parts: totalParts, file_total_parts: totalParts,
bytes: bytesFromArrayBuffer(e.target.result) bytes: e.target.result
}, { }, {
startMaxLength: partSize + 256, startMaxLength: partSize + 256,
fileUpload: true fileUpload: true,
}); singleInRequest: true
}, errorHandler); }).then(function (result) {
apiCurPromise.then(function (result) {
doneParts++; doneParts++;
fileReadDeferred.resolve(); uploadDeferred.resolve();
if (doneParts >= totalParts) { if (doneParts >= totalParts) {
deferred.resolve(resultInputFile); deferred.resolve(resultInputFile);
resolved = true; resolved = true;
@ -580,8 +587,8 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
reader.readAsArrayBuffer(blob); reader.readAsArrayBuffer(blob);
return fileReadDeferred.promise; return uploadDeferred.promise;
}); }, activeDelta);
})(offset, part++); })(offset, part++);
} }
@ -598,9 +605,87 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
return { return {
getCachedFile: getCachedFile, getCachedFile: getCachedFile,
getDownloadedFile: getDownloadedFile,
downloadFile: downloadFile, downloadFile: downloadFile,
downloadSmallFile: downloadSmallFile, downloadSmallFile: downloadSmallFile,
saveSmallFile: saveSmallFile, saveSmallFile: saveSmallFile,
uploadFile: uploadFile 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
}
})

305
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.URL = $window.URL || $window.webkitURL;
$window.BlobBuilder = $window.BlobBuilder || $window.WebKitBlobBuilder || $window.MozBlobBuilder; $window.BlobBuilder = $window.BlobBuilder || $window.WebKitBlobBuilder || $window.MozBlobBuilder;
@ -98,10 +98,10 @@ angular.module('izhukov.utils', [])
else { else {
try { try {
var blob = blobConstruct([bytesToArrayBuffer(bytes)]); var blob = blobConstruct([bytesToArrayBuffer(bytes)]);
fileWriter.write(blob);
} catch (e) { } catch (e) {
deferred.reject(e); deferred.reject(e);
} }
fileWriter.write(blob);
} }
return deferred.promise; return deferred.promise;
@ -150,7 +150,7 @@ angular.module('izhukov.utils', [])
return false; return false;
} }
blobParts.push(blob); blobParts.push(blob);
$timeout(function () { setZeroTimeout(function () {
if (fakeFileWriter.onwriteend) { if (fakeFileWriter.onwriteend) {
fakeFileWriter.onwriteend(); fakeFileWriter.onwriteend();
} }
@ -484,35 +484,59 @@ angular.module('izhukov.utils', [])
.service('CryptoWorker', function ($timeout, $q) { .service('CryptoWorker', function ($timeout, $q) {
var worker = window.Worker && new Worker('js/lib/crypto_worker.js') || false, var webWorker = false,
naClEmbed = false,
taskID = 0, taskID = 0,
awaiting = {}; awaiting = {},
webCrypto = window.crypto && (window.crypto.subtle || window.crypto.webkitSubtle) || window.msCrypto && window.msCrypto.subtle,
if (worker) { useSha1Crypto = webCrypto && webCrypto.digest !== undefined,
worker.onmessage = function (e) { finalizeTask = function (taskID, result) {
var deferred = awaiting[e.data.taskID]; var deferred = awaiting[taskID];
if (deferred !== undefined) { if (deferred !== undefined) {
console.log(dT(), 'CW done'); // console.log(dT(), 'CW done');
deferred.resolve(e.data.result); deferred.resolve(result);
delete awaiting[e.data.taskID]; delete awaiting[taskID];
} }
}; };
worker.onerror = function(error) { if (navigator.mimeTypes['application/x-pnacl'] !== undefined) {
var listener = $('<div id="nacl_listener"><embed id="mtproto_crypto" width="0" height="0" src="nacl/mtproto_crypto.nmf?'+Math.random()+'" type="application/x-pnacl" /></div>').appendTo($('body'))[0];
listener.addEventListener('load', function (e) {
naClEmbed = listener.firstChild;
console.log(dT(), 'NaCl ready');
}, true);
listener.addEventListener('message', function (e) {
finalizeTask(e.data.taskID, e.data.result);
}, true);
listener.addEventListener('error', function (e) {
console.error('NaCl error', e);
}, true);
}
if (window.Worker) {
var tmpWorker = new Worker('js/lib/crypto_worker.js');
tmpWorker.onmessage = function (e) {
if (!webWorker) {
webWorker = tmpWorker;
} else {
finalizeTask(e.data.taskID, e.data.result);
}
};
tmpWorker.onerror = function(error) {
console.error('CW error', error, error.stack); console.error('CW error', error, error.stack);
worker = false; webWorker = false;
}; };
} }
function performTaskWorker (task, params) { function performTaskWorker (task, params, embed) {
console.log(dT(), 'CW start', task); // console.log(dT(), 'CW start', task);
var deferred = $q.defer(); var deferred = $q.defer();
awaiting[taskID] = deferred; awaiting[taskID] = deferred;
params.task = task; params.task = task;
params.taskID = taskID; params.taskID = taskID;
worker.postMessage(params); (embed || webWorker).postMessage(params);
taskID++; taskID++;
@ -521,39 +545,56 @@ angular.module('izhukov.utils', [])
return { return {
sha1Hash: function (bytes) { sha1Hash: function (bytes) {
if (worker && false) { // due overhead for data transfer if (useSha1Crypto) {
return performTaskWorker ('sha1-hash', {bytes: bytes}); // We don't use buffer since typedArray.subarray(...).buffer gives the whole buffer and not sliced one. webCrypto.digest supports typed array
var deferred = $q.defer(),
bytesTyped = Array.isArray(bytes) ? convertToUint8Array(bytes) : bytes;
// console.log(dT(), 'Native sha1 start');
webCrypto.digest({name: 'SHA-1'}, bytesTyped).then(function (digest) {
// console.log(dT(), 'Native sha1 done');
deferred.resolve(digest);
}, function (e) {
console.error('Crypto digest error', e);
useSha1Crypto = false;
deferred.resolve(sha1HashSync(bytes));
});
return deferred.promise;
} }
return $timeout(function () { return $timeout(function () {
return sha1Hash(bytes); return sha1HashSync(bytes);
}); });
}, },
aesEncrypt: function (bytes, keyBytes, ivBytes) { aesEncrypt: function (bytes, keyBytes, ivBytes) {
if (worker && false) { // due overhead for data transfer if (naClEmbed) {
return performTaskWorker('aes-encrypt', { return performTaskWorker('aes-encrypt', {
bytes: bytes, bytes: addPadding(convertToArrayBuffer(bytes)),
keyBytes: keyBytes, keyBytes: convertToArrayBuffer(keyBytes),
ivBytes: ivBytes ivBytes: convertToArrayBuffer(ivBytes)
}); }, naClEmbed);
} }
return $timeout(function () { return $timeout(function () {
return aesEncrypt(bytes, keyBytes, ivBytes); return convertToArrayBuffer(aesEncryptSync(bytes, keyBytes, ivBytes));
}); });
}, },
aesDecrypt: function (encryptedBytes, keyBytes, ivBytes) { aesDecrypt: function (encryptedBytes, keyBytes, ivBytes) {
if (worker && false) { // due overhead for data transfer if (naClEmbed) {
return performTaskWorker('aes-decrypt', { return performTaskWorker('aes-decrypt', {
encryptedBytes: encryptedBytes, encryptedBytes: addPadding(convertToArrayBuffer(encryptedBytes)),
keyBytes: keyBytes, keyBytes: convertToArrayBuffer(keyBytes),
ivBytes: ivBytes ivBytes: convertToArrayBuffer(ivBytes)
}); }, naClEmbed);
} }
return $timeout(function () { return $timeout(function () {
return aesDecrypt(encryptedBytes, keyBytes, ivBytes); return convertToArrayBuffer(aesDecryptSync(encryptedBytes, keyBytes, ivBytes));
}); });
}, },
factorize: function (bytes) { factorize: function (bytes) {
if (worker) { bytes = convertToByteArray(bytes);
if (naClEmbed && bytes.length <= 8) {
return performTaskWorker('factorize', {bytes: bytes}, naClEmbed);
}
if (webWorker) {
return performTaskWorker('factorize', {bytes: bytes}); return performTaskWorker('factorize', {bytes: bytes});
} }
return $timeout(function () { return $timeout(function () {
@ -561,7 +602,7 @@ angular.module('izhukov.utils', [])
}); });
}, },
modPow: function (x, y, m) { modPow: function (x, y, m) {
if (worker) { if (webWorker) {
return performTaskWorker('mod-pow', { return performTaskWorker('mod-pow', {
x: x, x: x,
y: y, y: y,
@ -575,6 +616,197 @@ angular.module('izhukov.utils', [])
}; };
}) })
.service('SearchIndexManager', function () {
var badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<\s]+/g,
trimRe = /^\s+|\s$/g,
accentsReplace = {
a: /[åáâäà]/g,
e: /[éêëè]/g,
i: /[íîïì]/g,
o: /[óôöò]/g,
u: /[úûüù]/g,
c: /ç/g,
ss: /ß/g
}
return {
createIndex: createIndex,
indexObject: indexObject,
cleanSearchText: cleanSearchText,
search: search
};
function createIndex () {
return {
shortIndexes: {},
fullTexts: {}
}
}
function cleanSearchText (text) {
text = text.replace(badCharsRe, ' ').replace(trimRe, '').toLowerCase();
for (var key in accentsReplace) {
if (accentsReplace.hasOwnProperty(key)) {
text = text.replace(accentsReplace[key], key);
}
}
return text;
}
function indexObject (id, searchText, searchIndex) {
if (searchIndex.fullTexts[id] !== undefined) {
return false;
}
searchText = cleanSearchText(searchText);
if (!searchText.length) {
return false;
}
var shortIndexes = searchIndex.shortIndexes;
searchIndex.fullTexts[id] = searchText;
angular.forEach(searchText.split(' '), function(searchWord) {
var len = Math.min(searchWord.length, 3),
wordPart, i;
for (i = 1; i <= len; i++) {
wordPart = searchWord.substr(0, i);
if (shortIndexes[wordPart] === undefined) {
shortIndexes[wordPart] = [id];
} else {
shortIndexes[wordPart].push(id);
}
}
});
}
function search (query, searchIndex) {
var shortIndexes = searchIndex.shortIndexes,
fullTexts = searchIndex.fullTexts;
query = cleanSearchText(query);
var queryWords = query.split(' '),
foundObjs = false,
newFoundObjs, i, j, searchText, found;
for (i = 0; i < queryWords.length; i++) {
newFoundObjs = shortIndexes[queryWords[i].substr(0, 3)];
if (!newFoundObjs) {
foundObjs = [];
break;
}
if (foundObjs === false || foundObjs.length > newFoundObjs.length) {
foundObjs = newFoundObjs;
}
}
newFoundObjs = {};
for (j = 0; j < foundObjs.length; j++) {
found = true;
searchText = fullTexts[foundObjs[j]];
for (i = 0; i < queryWords.length; i++) {
if (searchText.indexOf(queryWords[i]) == -1) {
found = false;
break;
}
}
if (found) {
newFoundObjs[foundObjs[j]] = true;
}
}
return newFoundObjs;
}
})
.service('ExternalResourcesManager', function ($q, $http) {
var urlPromises = {};
var twitterAttached = false;
function downloadImage (url) {
if (urlPromises[url] !== undefined) {
return urlPromises[url];
}
return urlPromises[url] = $http.get(url, {responseType: 'blob', transformRequest: null})
.then(function (response) {
window.URL = window.URL || window.webkitURL;
return window.URL.createObjectURL(response.data);
});
}
function attachTwitterScript () {
twitterAttached = true;
$('<script>').appendTo('body')
// .on('load', function() {
// })
.attr('src', '//platform.twitter.com/widgets.js');
}
return {
downloadImage: downloadImage,
attachTwitterScript: attachTwitterScript
}
})
.service('IdleManager', function ($rootScope, $window, $timeout) {
$rootScope.idle = {isIDLE: false};
var toPromise, started = false;
return {
start: start
};
function start () {
if (!started) {
started = true;
$($window).on('blur focus keydown mousedown touchstart', onEvent);
setTimeout(function () {
onEvent({type: 'blur'});
}, 0);
}
}
function onEvent (e) {
// console.log('event', e.type);
if (e.type == 'mousemove') {
$($window).off('mousemove', onEvent);
}
var isIDLE = e.type == 'blur' || e.type == 'timeout' ? true : false;
$timeout.cancel(toPromise);
if (!isIDLE) {
// console.log('update timeout');
toPromise = $timeout(function () {
onEvent({type: 'timeout'});
}, 30000);
}
if ($rootScope.idle.isIDLE == isIDLE) {
return;
}
// console.log('IDLE changed', isIDLE);
$rootScope.$apply(function () {
$rootScope.idle.isIDLE = isIDLE;
});
if (isIDLE && e.type == 'timeout') {
$($window).on('mousemove', onEvent);
}
}
})
.service('AppRuntimeManager', function ($window) { .service('AppRuntimeManager', function ($window) {
return { return {
@ -587,6 +819,11 @@ angular.module('izhukov.utils', [])
chrome.runtime.reload(); chrome.runtime.reload();
}; };
}, },
close: function () {
try {
$window.close();
} catch (e) {}
},
focus: function () { focus: function () {
if (window.navigator.mozApps && document.hidden) { if (window.navigator.mozApps && document.hidden) {
// Get app instance and launch it to bring app to foreground // Get app instance and launch it to bring app to foreground

33
app/js/lib/polyfill.js

@ -96,3 +96,36 @@ if (!Function.prototype.bind) {
return fBound; return fBound;
}; };
} }
/* setZeroTimeout polyfill, from http://dbaron.org/log/20100309-faster-timeouts */
(function(global) {
var timeouts = [];
var messageName = 'zero-timeout-message';
function setZeroTimeout(fn) {
timeouts.push(fn);
global.postMessage(messageName, '*');
}
function handleMessage(event) {
if (event.source == global && event.data == messageName) {
event.stopPropagation();
if (timeouts.length > 0) {
var fn = timeouts.shift();
fn();
}
}
}
global.addEventListener('message', handleMessage, true);
var originalSetTimeout = global.setTimeout;
global.setTimeout = function (callback, delay) {
if (!delay || delay <= 5) {
return setZeroTimeout(callback);
}
return originalSetTimeout(callback, delay);
};
global.setZeroTimeout = setZeroTimeout;
})(this);

70
app/js/lib/tl_utils.js

@ -36,7 +36,16 @@ TLSerialization.prototype.getBuffer = function () {
return this.getArray().buffer; return this.getArray().buffer;
}; };
TLSerialization.prototype.getBytes = function () { TLSerialization.prototype.getBytes = function (typed) {
if (typed) {
var resultBuffer = new ArrayBuffer(this.offset);
var resultArray = new Uint8Array(resultBuffer);
resultArray.set(this.byteView.subarray(0, this.offset));
return resultArray;
}
var bytes = []; var bytes = [];
for (var i = 0; i < this.offset; i++) { for (var i = 0; i < this.offset; i++) {
bytes.push(this.byteView[i]); bytes.push(this.byteView[i]);
@ -139,11 +148,13 @@ TLSerialization.prototype.storeString = function (s, field) {
TLSerialization.prototype.storeBytes = function (bytes, field) { TLSerialization.prototype.storeBytes = function (bytes, field) {
if (bytes instanceof ArrayBuffer) {
bytes = new Uint8Array(bytes);
}
this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':bytes'); this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':bytes');
this.checkLength(bytes.length + 8); var len = bytes.byteLength || bytes.length;
this.checkLength(len + 8);
var len = bytes.length;
if (len <= 253) { if (len <= 253) {
this.byteView[this.offset++] = len; this.byteView[this.offset++] = len;
} else { } else {
@ -152,9 +163,9 @@ TLSerialization.prototype.storeBytes = function (bytes, field) {
this.byteView[this.offset++] = (len & 0xFF00) >> 8; this.byteView[this.offset++] = (len & 0xFF00) >> 8;
this.byteView[this.offset++] = (len & 0xFF0000) >> 16; this.byteView[this.offset++] = (len & 0xFF0000) >> 16;
} }
for (var i = 0; i < len; i++) {
this.byteView[this.offset++] = bytes[i]; this.byteView.set(bytes, this.offset);
} this.offset += len;
// Padding // Padding
while (this.offset % 4) { while (this.offset % 4) {
@ -163,6 +174,9 @@ TLSerialization.prototype.storeBytes = function (bytes, field) {
} }
TLSerialization.prototype.storeIntBytes = function (bytes, bits, field) { TLSerialization.prototype.storeIntBytes = function (bytes, bits, field) {
if (bytes instanceof ArrayBuffer) {
bytes = new Uint8Array(bytes);
}
var len = bytes.length; var len = bytes.length;
if ((bits % 32) || (len * 8) != bits) { if ((bits % 32) || (len * 8) != bits) {
throw new Error('Invalid bits: ' + bits + ', ' + bytes.length); throw new Error('Invalid bits: ' + bits + ', ' + bytes.length);
@ -171,20 +185,21 @@ TLSerialization.prototype.storeIntBytes = function (bytes, bits, field) {
this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':int' + bits); this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':int' + bits);
this.checkLength(len); this.checkLength(len);
for (var i = 0; i < len; i++) { this.byteView.set(bytes, this.offset);
this.byteView[this.offset++] = bytes[i]; this.offset += len;
}
}; };
TLSerialization.prototype.storeRawBytes = function (bytes, field) { TLSerialization.prototype.storeRawBytes = function (bytes, field) {
if (bytes instanceof ArrayBuffer) {
bytes = new Uint8Array(bytes);
}
var len = bytes.length; var len = bytes.length;
this.debug && console.log('>>>', bytesToHex(bytes), (field || '')); this.debug && console.log('>>>', bytesToHex(bytes), (field || ''));
this.checkLength(len); this.checkLength(len);
for (var i = 0; i < len; i++) { this.byteView.set(bytes, this.offset);
this.byteView[this.offset++] = bytes[i]; this.offset += len;
}
}; };
@ -391,10 +406,8 @@ TLDeserialization.prototype.fetchBytes = function (field) {
(this.byteView[this.offset++] << 16); (this.byteView[this.offset++] << 16);
} }
var bytes = []; var bytes = this.byteView.subarray(this.offset, this.offset + len);
for (var i = 0; i < len; i++) { this.offset += len;
bytes.push(this.byteView[this.offset++]);
}
// Padding // Padding
while (this.offset % 4) { while (this.offset % 4) {
@ -406,12 +419,18 @@ TLDeserialization.prototype.fetchBytes = function (field) {
return bytes; return bytes;
} }
TLDeserialization.prototype.fetchIntBytes = function (bits, field) { TLDeserialization.prototype.fetchIntBytes = function (bits, typed, field) {
if (bits % 32) { if (bits % 32) {
throw new Error('Invalid bits: ' + bits); throw new Error('Invalid bits: ' + bits);
} }
var len = bits / 8; var len = bits / 8;
if (typed) {
var result = this.byteView.subarray(this.offset, this.offset + len);
this.offset += len;
return result;
}
var bytes = []; var bytes = [];
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
bytes.push(this.byteView[this.offset++]); bytes.push(this.byteView[this.offset++]);
@ -423,11 +442,18 @@ TLDeserialization.prototype.fetchIntBytes = function (bits, field) {
}; };
TLDeserialization.prototype.fetchRawBytes = function (len, field) { TLDeserialization.prototype.fetchRawBytes = function (len, typed, field) {
if (len === false) { if (len === false) {
len = this.readInt((field || '') + '_length'); len = this.readInt((field || '') + '_length');
} }
if (typed) {
var bytes = new Uint8Array(len);
bytes.set(this.byteView.subarray(this.offset, this.offset + len));
this.offset += len;
return bytes;
}
var bytes = []; var bytes = [];
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
bytes.push(this.byteView[this.offset++]); bytes.push(this.byteView[this.offset++]);
@ -442,9 +468,9 @@ TLDeserialization.prototype.fetchObject = function (type, field) {
switch (type) { switch (type) {
case 'int': return this.fetchInt(field); case 'int': return this.fetchInt(field);
case 'long': return this.fetchLong(field); case 'long': return this.fetchLong(field);
case 'int128': return this.fetchIntBytes(128, field); case 'int128': return this.fetchIntBytes(128, false, field);
case 'int256': return this.fetchIntBytes(256, field); case 'int256': return this.fetchIntBytes(256, false, field);
case 'int512': return this.fetchIntBytes(512, field); case 'int512': return this.fetchIntBytes(512, false, field);
case 'string': return this.fetchString(field); case 'string': return this.fetchString(field);
case 'bytes': return this.fetchBytes(field); case 'bytes': return this.fetchBytes(field);
case 'double': return this.fetchDouble(field); case 'double': return this.fetchDouble(field);

38
app/js/lib/utils.js

@ -39,10 +39,12 @@ function checkDragEvent(e) {
function cancelEvent (event) { function cancelEvent (event) {
event = event || window.event; event = event || window.event;
if (event) event = event.originalEvent || event; if (event) {
event = event.originalEvent || event;
if (event.stopPropagation) event.stopPropagation(); if (event.stopPropagation) event.stopPropagation();
if (event.preventDefault) event.preventDefault(); if (event.preventDefault) event.preventDefault();
}
return false; return false;
} }
@ -61,7 +63,7 @@ function onContentLoaded (cb) {
}; };
function tsNow (seconds) { function tsNow (seconds) {
var t = +new Date(); var t = +new Date() + (window.tsOffset || 0);
return seconds ? Math.floor(t / 1000) : t; return seconds ? Math.floor(t / 1000) : t;
} }
@ -112,3 +114,35 @@ function templateUrl (tplName) {
return 'partials/' + (Config.Mobile ? 'mobile' : 'desktop') + '/' + tplName + '.html'; return 'partials/' + (Config.Mobile ? 'mobile' : 'desktop') + '/' + tplName + '.html';
} }
function encodeEntities(value) {
return value.
replace(/&/g, '&amp;').
replace(/([^\#-~| |!])/g, function (value) { // non-alphanumeric
return '&#' + value.charCodeAt(0) + ';';
}).
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
}
function calcImageInBox(imageW, imageH, boxW, boxH, noZooom) {
var boxedImageW = boxW;
var boxedImageH = boxH;
if ((imageW / imageH) > (boxW / boxH)) {
boxedImageH = parseInt(imageH * boxW / imageW);
}
else {
boxedImageW = parseInt(imageW * boxH / imageH);
if (boxedImageW > boxW) {
boxedImageH = parseInt(boxedImageH * boxW / boxedImageW);
boxedImageW = boxW;
}
}
if (noZooom && boxedImageW >= imageW && boxedImageH >= imageH) {
boxedImageW = imageW;
boxedImageH = imageH;
}
return {w: boxedImageW, h: boxedImageH};
}

6
app/js/locales/en-us.json

@ -195,6 +195,7 @@
"message_service_left_group": "left group", "message_service_left_group": "left group",
"message_service_unsupported_action": "Unsupported action {action}", "message_service_unsupported_action": "Unsupported action {action}",
"error_modal_warning_title": "Warning",
"error_modal_bad_request_title": "Error", "error_modal_bad_request_title": "Error",
"error_modal_unauthorized_title": "Unauthorized", "error_modal_unauthorized_title": "Unauthorized",
"error_modal_forbidden_title": "Access denied", "error_modal_forbidden_title": "Access denied",
@ -215,6 +216,7 @@
"error_modal_no_phone_user_description_md": "Sorry, there is no **Telegram** account with the phone number you provided.", "error_modal_no_phone_user_description_md": "Sorry, there is no **Telegram** account with the phone number you provided.",
"error_modal_no_phone_users_description_md": "Sorry, there are no **Telegram** accounts with the phone numbers you provided.", "error_modal_no_phone_users_description_md": "Sorry, there are no **Telegram** accounts with the phone numbers you provided.",
"error_modal_username_invalid_description": "Sorry, this username is not allowed.", "error_modal_username_invalid_description": "Sorry, this username is not allowed.",
"error_modal_phonebook_required_description": "Telegram needs access to phonebook to import contacts.",
"error_modal_username_occupied_description": "Sorry, this username is already taken.", "error_modal_username_occupied_description": "Sorry, this username is already taken.",
"error_modal_bad_request_description": "One of the params is missing or invalid.", "error_modal_bad_request_description": "One of the params is missing or invalid.",
@ -224,6 +226,8 @@
"error_modal_flood_description": "You are performing too many actions. Please try again later.", "error_modal_flood_description": "You are performing too many actions. Please try again later.",
"error_modal_internal_description": "Internal server error occured. Please try again later.", "error_modal_internal_description": "Internal server error occured. Please try again later.",
"error_modal_tech_details": "Technical details here", "error_modal_tech_details": "Technical details here",
"error_modal_multiple_open_tabs": "Please close other Telegram app tabs.",
"head_new_group": "New Group", "head_new_group": "New Group",
"head_new_contact": "New Contact", "head_new_contact": "New Contact",
@ -325,9 +329,11 @@
"message_attach_document_open": "Open", "message_attach_document_open": "Open",
"message_attach_document_download": "Download", "message_attach_document_download": "Download",
"message_attach_document_save": "Save File",
"message_attach_video_video": "Video", "message_attach_video_video": "Video",
"message_attach_video_download": "Download", "message_attach_video_download": "Download",
"message_attach_video_save": "Save File",
"message_attach_video_play": "Play video", "message_attach_video_play": "Play video",
"conversation_select_modal_title": "Select conversation", "conversation_select_modal_title": "Select conversation",

556
app/js/services.js

@ -9,7 +9,7 @@
/* Services */ /* Services */
angular.module('myApp.services', ['myApp.i18n']) angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
.service('AppUsersManager', function ($rootScope, $modal, $modalStack, $filter, $q, MtpApiFileManager, MtpApiManager, RichTextProcessor, SearchIndexManager, ErrorService, Storage, _) { .service('AppUsersManager', function ($rootScope, $modal, $modalStack, $filter, $q, MtpApiFileManager, MtpApiManager, RichTextProcessor, SearchIndexManager, ErrorService, Storage, _) {
var users = {}, var users = {},
@ -196,7 +196,6 @@ angular.module('myApp.services', ['myApp.i18n'])
windowClass: 'user_modal_window mobile_modal' windowClass: 'user_modal_window mobile_modal'
}); });
}; };
$rootScope.openUser = openUser;
function importContact (phone, firstName, lastName) { function importContact (phone, firstName, lastName) {
return MtpApiManager.invokeApi('contacts.importContacts', { return MtpApiManager.invokeApi('contacts.importContacts', {
@ -401,7 +400,7 @@ angular.module('myApp.services', ['myApp.i18n'])
phones: [] phones: []
}; };
if (this.result.tel !== undefined) { if (this.result.tel != undefined) {
for (var i = 0; i < this.result.tel.length; i++) { for (var i = 0; i < this.result.tel.length; i++) {
contact.phones.push(this.result.tel[i].value); contact.phones.push(this.result.tel[i].value);
} }
@ -528,9 +527,6 @@ angular.module('myApp.services', ['myApp.i18n'])
}); });
} }
$rootScope.openChat = openChat;
return { return {
saveApiChats: saveApiChats, saveApiChats: saveApiChats,
saveApiChat: saveApiChat, saveApiChat: saveApiChat,
@ -606,115 +602,6 @@ angular.module('myApp.services', ['myApp.i18n'])
} }
}) })
.service('SearchIndexManager', function () {
var badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<\s]+/g,
trimRe = /^\s+|\s$/g,
accentsReplace = {
a: /[åáâäà]/g,
e: /[éêëè]/g,
i: /[íîïì]/g,
o: /[óôöò]/g,
u: /[úûüù]/g,
c: /ç/g,
ss: /ß/g
}
return {
createIndex: createIndex,
indexObject: indexObject,
cleanSearchText: cleanSearchText,
search: search
};
function createIndex () {
return {
shortIndexes: {},
fullTexts: {}
}
}
function cleanSearchText (text) {
text = text.replace(badCharsRe, ' ').replace(trimRe, '').toLowerCase();
for (var key in accentsReplace) {
if (accentsReplace.hasOwnProperty(key)) {
text = text.replace(accentsReplace[key], key);
}
}
return text;
}
function indexObject (id, searchText, searchIndex) {
if (searchIndex.fullTexts[id] !== undefined) {
return false;
}
searchText = cleanSearchText(searchText);
if (!searchText.length) {
return false;
}
var shortIndexes = searchIndex.shortIndexes;
searchIndex.fullTexts[id] = searchText;
angular.forEach(searchText.split(' '), function(searchWord) {
var len = Math.min(searchWord.length, 3),
wordPart, i;
for (i = 1; i <= len; i++) {
wordPart = searchWord.substr(0, i);
if (shortIndexes[wordPart] === undefined) {
shortIndexes[wordPart] = [id];
} else {
shortIndexes[wordPart].push(id);
}
}
});
}
function search (query, searchIndex) {
var shortIndexes = searchIndex.shortIndexes,
fullTexts = searchIndex.fullTexts;
query = cleanSearchText(query);
var queryWords = query.split(' '),
foundObjs = false,
newFoundObjs, i, j, searchText, found;
for (i = 0; i < queryWords.length; i++) {
newFoundObjs = shortIndexes[queryWords[i].substr(0, 3)];
if (!newFoundObjs) {
foundObjs = [];
break;
}
if (foundObjs === false || foundObjs.length > newFoundObjs.length) {
foundObjs = newFoundObjs;
}
}
newFoundObjs = {};
for (j = 0; j < foundObjs.length; j++) {
found = true;
searchText = fullTexts[foundObjs[j]];
for (i = 0; i < queryWords.length; i++) {
if (searchText.indexOf(queryWords[i]) == -1) {
found = false;
break;
}
}
if (found) {
newFoundObjs[foundObjs[j]] = true;
}
}
return newFoundObjs;
}
})
.service('AppMessagesManager', function ($q, $rootScope, $location, $filter, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, AppAudioManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, SearchIndexManager, PeersSelectService,Storage, _) { .service('AppMessagesManager', function ($q, $rootScope, $location, $filter, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, AppAudioManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, SearchIndexManager, PeersSelectService,Storage, _) {
var messagesStorage = {}; var messagesStorage = {};
@ -850,7 +737,7 @@ angular.module('myApp.services', ['myApp.i18n'])
offset: offset || 0, offset: offset || 0,
limit: limit || 0, limit: limit || 0,
max_id: maxID || 0 max_id: maxID || 0
}).then(function (historyResult) { }, {noErrorBox: true}).then(function (historyResult) {
AppUsersManager.saveApiUsers(historyResult.users); AppUsersManager.saveApiUsers(historyResult.users);
AppChatsManager.saveApiChats(historyResult.chats); AppChatsManager.saveApiChats(historyResult.chats);
saveMessages(historyResult.messages); saveMessages(historyResult.messages);
@ -1385,7 +1272,7 @@ angular.module('myApp.services', ['myApp.i18n'])
fileName = 'video.mp4'; fileName = 'video.mp4';
} else if (file.type.substr(0, 6) == 'audio/') { } else if (file.type.substr(0, 6) == 'audio/') {
attachType = 'audio'; attachType = 'audio';
fileName = 'audio.' + file.type.split('/')[1] || 'mp3'; fileName = 'audio.' + (file.type.split('/')[1] == 'ogg' ? 'ogg' : 'mp3');
} else { } else {
attachType = 'document'; attachType = 'document';
fileName = 'document.' + file.type.split('/')[1]; fileName = 'document.' + file.type.split('/')[1];
@ -2389,21 +2276,9 @@ angular.module('myApp.services', ['myApp.i18n'])
full.height = fullHeight; full.height = fullHeight;
if (fullPhotoSize && fullPhotoSize._ != 'photoSizeEmpty') { if (fullPhotoSize && fullPhotoSize._ != 'photoSizeEmpty') {
if ((fullPhotoSize.w / fullPhotoSize.h) > (fullWidth / fullHeight)) { var wh = calcImageInBox(fullPhotoSize.w, fullPhotoSize.h, fullWidth, fullHeight, Config.Mobile);
full.height = parseInt(fullPhotoSize.h * fullWidth / fullPhotoSize.w); full.width = wh.w;
} full.height = wh.h;
else {
full.width = parseInt(fullPhotoSize.w * fullHeight / fullPhotoSize.h);
if (full.width > fullWidth) {
full.height = parseInt(full.height * fullWidth / full.width);
full.width = fullWidth;
}
}
if (!Config.Mobile && full.width >= fullPhotoSize.w && full.height >= fullPhotoSize.h) {
full.width = fullPhotoSize.w;
full.height = fullPhotoSize.h;
}
full.modalWidth = Math.max(full.width, Math.min(400, fullWidth)); full.modalWidth = Math.max(full.width, Math.min(400, fullWidth));
@ -2412,7 +2287,6 @@ angular.module('myApp.services', ['myApp.i18n'])
} }
photo.full = full; photo.full = full;
photo.fromUser = AppUsersManager.getUser(photo.user_id);
return photo; return photo;
} }
@ -2444,8 +2318,8 @@ angular.module('myApp.services', ['myApp.i18n'])
ext = 'jpg', ext = 'jpg',
mimeType = 'image/jpeg', mimeType = 'image/jpeg',
fileName = 'photo' + photoID + '.' + ext, fileName = 'photo' + photoID + '.' + ext,
fullWidth = $(window).width() - 36, fullWidth = Math.max(screen.width || 0, $(window).width() - 36, 800),
fullHeight = $($window).height() - 150, fullHeight = Math.max(screen.height || 0, $($window).height() - 150, 800),
fullPhotoSize = choosePhotoSize(photo, fullWidth, fullHeight), fullPhotoSize = choosePhotoSize(photo, fullWidth, fullHeight),
inputFileLocation = { inputFileLocation = {
_: 'inputFileLocation', _: 'inputFileLocation',
@ -2455,19 +2329,17 @@ angular.module('myApp.services', ['myApp.i18n'])
}; };
FileManager.chooseSave(fileName, ext, mimeType).then(function (writableFileEntry) { FileManager.chooseSave(fileName, ext, mimeType).then(function (writableFileEntry) {
if (!writableFileEntry) { if (writableFileEntry) {
return;
}
MtpApiFileManager.downloadFile( MtpApiFileManager.downloadFile(
fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, { fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, {
mime: mimeType, mime: mimeType,
toFileEntry: writableFileEntry toFileEntry: writableFileEntry
}).then(function (url) { }).then(function (url) {
console.log('file save done'); // console.log('file save done');
}, function (e) { }, function (e) {
console.log('photo download failed', e); console.log('photo download failed', e);
}); });
}
}, function () { }, function () {
MtpApiFileManager.downloadFile( MtpApiFileManager.downloadFile(
fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, {mime: mimeType} fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, {mime: mimeType}
@ -2481,7 +2353,6 @@ angular.module('myApp.services', ['myApp.i18n'])
$rootScope.openPhoto = openPhoto; $rootScope.openPhoto = openPhoto;
return { return {
savePhoto: savePhoto, savePhoto: savePhoto,
preloadPhoto: preloadPhoto, preloadPhoto: preloadPhoto,
@ -2495,7 +2366,7 @@ angular.module('myApp.services', ['myApp.i18n'])
}) })
.service('AppVideoManager', function ($rootScope, $modal, $window, $timeout, MtpApiFileManager, AppUsersManager, FileManager) { .service('AppVideoManager', function ($sce, $rootScope, $modal, $window, $timeout, MtpApiFileManager, AppUsersManager, FileManager) {
var videos = {}, var videos = {},
videosForHistory = {}, videosForHistory = {},
windowW = $(window).width(), windowW = $(window).width(),
@ -2520,8 +2391,8 @@ angular.module('myApp.services', ['myApp.i18n'])
} }
var video = angular.copy(videos[videoID]), var video = angular.copy(videos[videoID]),
width = Math.min(windowW - 80, Config.Mobile ? 210 : 200), width = Math.min(windowW - 80, Config.Mobile ? 210 : 150),
height = Math.min(windowH - 100, Config.Mobile ? 210 : 200), height = Math.min(windowH - 100, Config.Mobile ? 210 : 150),
thumbPhotoSize = video.thumb, thumbPhotoSize = video.thumb,
thumb = { thumb = {
placeholder: 'img/placeholders/VideoThumbConversation.gif', placeholder: 'img/placeholders/VideoThumbConversation.gif',
@ -2563,23 +2434,16 @@ angular.module('myApp.services', ['myApp.i18n'])
if (!video.w || !video.h) { if (!video.w || !video.h) {
full.height = full.width = Math.min(fullWidth, fullHeight); full.height = full.width = Math.min(fullWidth, fullHeight);
} } else {
else if (video.w > video.h) { var wh = calcImageInBox(video.w, video.h, fullWidth, fullHeight);
full.height = parseInt(video.h * fullWidth / video.w); full.width = wh.w;
} full.height = wh.h;
else {
full.width = parseInt(video.w * fullHeight / video.h);
if (full.width > fullWidth) {
full.height = parseInt(full.height * fullWidth / full.width);
full.width = fullWidth;
}
} }
video.full = full; video.full = full;
video.fullThumb = angular.copy(video.thumb); video.fullThumb = angular.copy(video.thumb);
video.fullThumb.width = full.width; video.fullThumb.width = full.width;
video.fullThumb.height = full.height; video.fullThumb.height = full.height;
video.fromUser = AppUsersManager.getUser(video.user_id);
return video; return video;
} }
@ -2589,7 +2453,7 @@ angular.module('myApp.services', ['myApp.i18n'])
scope.videoID = videoID; scope.videoID = videoID;
scope.messageID = messageID; scope.messageID = messageID;
var modalInstance = $modal.open({ return $modal.open({
templateUrl: templateUrl('video_modal'), templateUrl: templateUrl('video_modal'),
controller: 'VideoModalController', controller: 'VideoModalController',
scope: scope, scope: scope,
@ -2597,79 +2461,93 @@ angular.module('myApp.services', ['myApp.i18n'])
}); });
} }
function downloadVideo (videoID, accessHash, popup) { function updateVideoDownloaded (videoID) {
var video = videos[videoID], var video = videos[videoID],
historyVideo = videosForHistory[videoID] || video || {}, historyVideo = videosForHistory[videoID] || video || {},
inputFileLocation = { inputFileLocation = {
_: 'inputVideoFileLocation', _: 'inputVideoFileLocation',
id: videoID, id: videoID,
access_hash: accessHash || video.access_hash access_hash: video.access_hash
}; };
historyVideo.progress = {enabled: true, percent: 1, total: video.size}; // historyVideo.progress = {enabled: true, percent: 10, total: video.size};
function updateDownloadProgress (progress) { if (historyVideo.downloaded === undefined) {
console.log('dl progress', progress); MtpApiFileManager.getDownloadedFile(inputFileLocation, video.size).then(function () {
historyVideo.progress.done = progress.done; historyVideo.downloaded = true;
historyVideo.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); }, function () {
$rootScope.$broadcast('history_update'); historyVideo.downloaded = false;
});
}
} }
var ext = 'mp4', function downloadVideo (videoID, toFileEntry) {
mimeType = 'video/mpeg4', var video = videos[videoID],
fileName = 'video' + videoID + '.' + ext; historyVideo = videosForHistory[videoID] || video || {},
inputFileLocation = {
_: 'inputVideoFileLocation',
id: videoID,
access_hash: video.access_hash
};
FileManager.chooseSave(fileName, ext, mimeType).then(function (writableFileEntry) { historyVideo.progress = {enabled: !historyVideo.downloaded, percent: 1, total: video.size};
if (!writableFileEntry) {
return;
}
var downloadPromise = MtpApiFileManager.downloadFile(video.dc_id, inputFileLocation, video.size, { var downloadPromise = MtpApiFileManager.downloadFile(video.dc_id, inputFileLocation, video.size, {
mime: mimeType, mime: video.mime_type || 'video/ogg',
toFileEntry: writableFileEntry toFileEntry: toFileEntry
}); });
downloadPromise.then(function (url) { downloadPromise.then(function (url) {
delete historyVideo.progress; delete historyVideo.progress;
console.log('file save done'); historyVideo.url = $sce.trustAsResourceUrl(url);
historyVideo.downloaded = true;
console.log('video save done');
}, function (e) { }, function (e) {
console.log('video download failed', e); console.log('video download failed', e);
historyVideo.progress.enabled = false; historyVideo.progress.enabled = false;
}, updateDownloadProgress); }, function (progress) {
console.log('dl progress', progress);
historyVideo.progress.enabled = true;
historyVideo.progress.done = progress.done;
historyVideo.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
$rootScope.$broadcast('history_update');
});
historyVideo.progress.cancel = downloadPromise.cancel; historyVideo.progress.cancel = downloadPromise.cancel;
}, function () {
var downloadPromise = MtpApiFileManager.downloadFile(video.dc_id, inputFileLocation, video.size, {mime: mimeType});
downloadPromise.then(function (url) { return downloadPromise;
delete historyVideo.progress;
if (popup) {
window.open(url, '_blank');
return
} }
FileManager.download(url, mimeType, fileName); function saveVideoFile (videoID) {
}, function (e) { var video = videos[videoID],
console.log('video download failed', e); mimeType = video.mime_type || 'video/mpeg4',
historyVideo.progress.enabled = false; fileExt = mimeType.split('.')[1] || 'mp4',
}, updateDownloadProgress); fileName = 't_video' + videoID + '.' + fileExt,
historyVideo = videosForHistory[videoID] || video || {};
historyVideo.progress.cancel = downloadPromise.cancel; FileManager.chooseSave(fileName, fileExt, mimeType).then(function (writableFileEntry) {
if (writableFileEntry) {
downloadVideo(videoID, writableFileEntry);
}
}, function () {
downloadVideo(videoID).then(function (url) {
FileManager.download(url, mimeType, fileName);
}); });
}; });
}
$rootScope.openVideo = openVideo;
$rootScope.downloadVideo = downloadVideo;
return { return {
saveVideo: saveVideo, saveVideo: saveVideo,
wrapForHistory: wrapForHistory, wrapForHistory: wrapForHistory,
wrapForFull: wrapForFull, wrapForFull: wrapForFull,
openVideo: openVideo openVideo: openVideo,
updateVideoDownloaded: updateVideoDownloaded,
downloadVideo: downloadVideo,
saveVideoFile: saveVideoFile
} }
}) })
.service('AppDocsManager', function ($rootScope, $modal, $window, $timeout, MtpApiFileManager, FileManager) { .service('AppDocsManager', function ($sce, $rootScope, $modal, $window, $timeout, $q, MtpApiFileManager, FileManager) {
var docs = {}, var docs = {},
docsForHistory = {}, docsForHistory = {},
windowW = $(window).width(), windowW = $(window).width(),
@ -2737,7 +2615,7 @@ angular.module('myApp.services', ['myApp.i18n'])
return docsForHistory[docID] = doc; return docsForHistory[docID] = doc;
} }
function downloadDoc (docID, action) { function updateDocDownloaded (docID) {
var doc = docs[docID], var doc = docs[docID],
historyDoc = docsForHistory[docID] || doc || {}, historyDoc = docsForHistory[docID] || doc || {},
inputFileLocation = { inputFileLocation = {
@ -2746,72 +2624,94 @@ angular.module('myApp.services', ['myApp.i18n'])
access_hash: doc.access_hash access_hash: doc.access_hash
}; };
function updateDownloadProgress (progress) { // historyDoc.progress = {enabled: true, percent: 10, total: doc.size};
console.log('dl progress', progress);
historyDoc.progress.done = progress.done;
historyDoc.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
$rootScope.$broadcast('history_update');
}
var ext = (doc.file_name.split('.', 2) || [])[1] || ''; if (historyDoc.downloaded === undefined) {
FileManager.chooseSave(doc.file_name, ext, doc.mime_type).then(function (writableFileEntry) { MtpApiFileManager.getDownloadedFile(inputFileLocation, doc.size).then(function () {
if (!writableFileEntry) { historyDoc.downloaded = true;
return; }, function () {
historyDoc.downloaded = false;
});
} }
}
function downloadDoc (docID, toFileEntry) {
var doc = docs[docID],
historyDoc = docsForHistory[docID] || doc || {},
inputFileLocation = {
_: 'inputDocumentFileLocation',
id: docID,
access_hash: doc.access_hash
};
historyDoc.progress = {enabled: true, percent: 1, total: doc.size}; historyDoc.progress = {enabled: !historyDoc.downloaded, percent: 1, total: doc.size};
var downloadPromise = MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, { var downloadPromise = MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, {
mime: doc.mime_type, mime: doc.mime_type,
toFileEntry: writableFileEntry toFileEntry: toFileEntry
}); });
downloadPromise.then(function (url) { downloadPromise.then(function (url) {
delete historyDoc.progress; delete historyDoc.progress;
historyDoc.url = $sce.trustAsResourceUrl(url);
historyDoc.downloaded = true;
console.log('file save done'); console.log('file save done');
}, function (e) { }, function (e) {
console.log('document download failed', e); console.log('document download failed', e);
historyDoc.progress.enabled = false; historyDoc.progress.enabled = false;
}, updateDownloadProgress); }, function (progress) {
console.log('dl progress', progress);
historyDoc.progress.enabled = true;
historyDoc.progress.done = progress.done;
historyDoc.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
$rootScope.$broadcast('history_update');
});
historyDoc.progress.cancel = downloadPromise.cancel; historyDoc.progress.cancel = downloadPromise.cancel;
}, function () {
historyDoc.progress = {enabled: true, percent: 1, total: doc.size};
var downloadPromise = MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, {mime: doc.mime_type}); return downloadPromise;
}
downloadPromise.then(function (url) { function openDoc (docID, messageID) {
delete historyDoc.progress; var scope = $rootScope.$new(true);
scope.docID = docID;
scope.messageID = messageID;
historyDoc.url = url; var modalInstance = $modal.open({
templateUrl: templateUrl('document_modal'),
controller: 'DocumentModalController',
scope: scope,
windowClass: 'document_modal_window'
});
}
switch (action) { function saveDocFile (docID) {
case 1: var doc = docs[docID],
window.open(url, '_blank'); historyDoc = docsForHistory[docID] || doc || {};
break;
default: var ext = (doc.file_name.split('.', 2) || [])[1] || '';
FileManager.download(url, doc.mime_type, doc.file_name); FileManager.chooseSave(doc.file_name, ext, doc.mime_type).then(function (writableFileEntry) {
if (writableFileEntry) {
downloadDoc(docID, writableFileEntry);
} }
}, function (e) { }, function () {
console.log('document download failed', e); downloadDoc(docID).then(function (url) {
historyDoc.progress.enabled = false; FileManager.download(url, doc.mime_type, doc.file_name);
}, updateDownloadProgress); });
historyDoc.progress.cancel = downloadPromise.cancel;
}); });
} }
$rootScope.downloadDoc = downloadDoc;
return { return {
saveDoc: saveDoc, saveDoc: saveDoc,
wrapForHistory: wrapForHistory, wrapForHistory: wrapForHistory,
downloadDoc: downloadDoc updateDocDownloaded: updateDocDownloaded,
downloadDoc: downloadDoc,
openDoc: openDoc,
saveDocFile: saveDocFile
} }
}) })
.service('AppAudioManager', function ($rootScope, $modal, $window, $timeout, $sce, MtpApiFileManager) { .service('AppAudioManager', function ($sce, $rootScope, $modal, $window, $timeout, MtpApiFileManager, FileManager) {
var audios = {}; var audios = {};
var audiosForHistory = {}; var audiosForHistory = {};
@ -2829,75 +2729,90 @@ angular.module('myApp.services', ['myApp.i18n'])
return audiosForHistory[audioID] = audio; return audiosForHistory[audioID] = audio;
} }
function openAudio (audioID, accessHash) { function updateAudioDownloaded (audioID) {
var audio = audios[audioID], var audio = audios[audioID],
historyAudio = audiosForHistory[audioID] || audio || {}, historyAudio = audiosForHistory[audioID] || audio || {},
inputFileLocation = { inputFileLocation = {
_: 'inputAudioFileLocation', _: 'inputAudioFileLocation',
id: audioID, id: audioID,
access_hash: accessHash || audio.access_hash access_hash: audio.access_hash
}; };
historyAudio.progress = {enabled: true, percent: 1, total: audio.size}; // historyAudio.progress = {enabled: !historyAudio.downloaded, percent: 10, total: audio.size};
function updateDownloadProgress (progress) { if (historyAudio.downloaded === undefined) {
console.log('dl progress', progress); MtpApiFileManager.getDownloadedFile(inputFileLocation, audio.size).then(function () {
historyAudio.progress.done = progress.done; historyAudio.downloaded = true;
historyAudio.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); }, function () {
$rootScope.$broadcast('history_update'); historyAudio.downloaded = false;
});
}
} }
var downloadPromise = MtpApiFileManager.downloadFile(audio.dc_id, inputFileLocation, audio.size, {mime: 'audio/ogg'}); function downloadAudio (audioID, toFileEntry) {
var audio = audios[audioID],
historyAudio = audiosForHistory[audioID] || audio || {},
inputFileLocation = {
_: 'inputAudioFileLocation',
id: audioID,
access_hash: audio.access_hash
};
historyAudio.progress = {enabled: !historyAudio.downloaded, percent: 1, total: audio.size};
var downloadPromise = MtpApiFileManager.downloadFile(audio.dc_id, inputFileLocation, audio.size, {
mime: audio.mime_type || 'audio/ogg',
toFileEntry: toFileEntry
});
downloadPromise.then(function (url) { downloadPromise.then(function (url) {
delete historyAudio.progress; delete historyAudio.progress;
historyAudio.url = $sce.trustAsResourceUrl(url); historyAudio.url = $sce.trustAsResourceUrl(url);
historyAudio.autoplay = true; historyAudio.downloaded = true;
$timeout(function () { console.log('audio save done');
console.log('disable autoplay');
delete historyAudio.autoplay;
$rootScope.$broadcast('history_update');
}, 1000);
}, function (e) { }, function (e) {
console.log('audio download failed', e); console.log('audio download failed', e);
historyAudio.progress.enabled = false; historyAudio.progress.enabled = false;
}, updateDownloadProgress); }, function (progress) {
console.log('dl progress', progress);
historyAudio.progress.enabled = true;
historyAudio.progress.done = progress.done;
historyAudio.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
$rootScope.$broadcast('history_update');
});
historyAudio.progress.cancel = downloadPromise.cancel; historyAudio.progress.cancel = downloadPromise.cancel;
return downloadPromise; return downloadPromise;
} }
$rootScope.openAudio = openAudio; function saveAudioFile (audioID) {
var audio = audios[audioID],
return { mimeType = audio.mime_type || 'audio/ogg',
saveAudio: saveAudio, fileExt = mimeType.split('.')[1] || 'ogg',
wrapForHistory: wrapForHistory, fileName = 't_audio' + audioID + '.' + fileExt,
openAudio: openAudio historyAudio = audiosForHistory[audioID] || audio || {};
}
})
.service('ExternalResourcesManager', function ($q, $http) {
var urlPromises = {};
function downloadImage (url) { FileManager.chooseSave(fileName, fileExt, mimeType).then(function (writableFileEntry) {
if (urlPromises[url] !== undefined) { if (writableFileEntry) {
return urlPromises[url]; downloadAudio(audioID, writableFileEntry);
} }
}, function () {
return urlPromises[url] = $http.get(url, {responseType: 'blob', transformRequest: null}) downloadAudio(audioID).then(function (url) {
.then(function (response) { FileManager.download(url, mimeType, fileName);
window.URL = window.URL || window.webkitURL; });
return window.URL.createObjectURL(response.data);
}); });
} }
return { return {
downloadImage: downloadImage saveAudio: saveAudio,
wrapForHistory: wrapForHistory,
updateAudioDownloaded: updateAudioDownloaded,
downloadAudio: downloadAudio,
saveAudioFile: saveAudioFile
} }
}) })
.service('ApiUpdatesManager', function ($rootScope, MtpNetworkerFactory, AppUsersManager, AppChatsManager, AppPeersManager, MtpApiManager) { .service('ApiUpdatesManager', function ($rootScope, MtpNetworkerFactory, AppUsersManager, AppChatsManager, AppPeersManager, MtpApiManager) {
var isSynchronizing = true, var isSynchronizing = true,
@ -2936,6 +2851,7 @@ angular.module('myApp.services', ['myApp.i18n'])
switch (updateMessage._) { switch (updateMessage._) {
case 'updatesTooLong': case 'updatesTooLong':
case 'new_session_created':
forceGetDifference(); forceGetDifference();
break; break;
@ -3129,11 +3045,13 @@ angular.module('myApp.services', ['myApp.i18n'])
function attach () { function attach () {
MtpNetworkerFactory.setUpdatesProcessor(processUpdateMessage); MtpNetworkerFactory.setUpdatesProcessor(processUpdateMessage);
MtpApiManager.invokeApi('updates.getState', {noErrorBox: true}).then(function (stateResult) { MtpApiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then(function (stateResult) {
curState.seq = stateResult.seq; curState.seq = stateResult.seq;
curState.pts = stateResult.pts; curState.pts = stateResult.pts;
curState.date = stateResult.date; curState.date = stateResult.date;
setTimeout(function () {
isSynchronizing = false; isSynchronizing = false;
}, 1000);
}) })
} }
@ -3144,7 +3062,7 @@ angular.module('myApp.services', ['myApp.i18n'])
} }
}) })
.service('RichTextProcessor', function ($sce, $sanitize) { .service('RichTextProcessor', function ($sce, $sanitize, ExternalResourcesManager) {
var emojiUtf = [], var emojiUtf = [],
emojiMap = {}, emojiMap = {},
@ -3187,22 +3105,15 @@ angular.module('myApp.services', ['myApp.i18n'])
var regexAlphaNumericChars = "0-9\.\_" + regexAlphaChars; var regexAlphaNumericChars = "0-9\.\_" + regexAlphaChars;
var regExp = new RegExp('((?:(ftp|https?)://|(?:mailto:)?([A-Za-z0-9._%+-]+@))(\\S*\\.\\S*[^\\s.;,(){}<>"\']))|(\\n)|(' + emojiUtf.join('|') + ')|(^|\\s)(#[' + regexAlphaNumericChars + ']{3,20})', 'i'); var regExp = new RegExp('((?:(ftp|https?)://|(?:mailto:)?([A-Za-z0-9._%+-]+@))(\\S*\\.\\S*[^\\s.;,(){}<>"\']))|(\\n)|(' + emojiUtf.join('|') + ')|(^|\\s)(#[' + regexAlphaNumericChars + ']{3,20})', 'i');
var youtubeRegex = /(?:https?:\/\/)?(?:www\.)?youtu(?:|.be|be.com|.b)(?:\/v\/|\/watch\\?v=|e\/|(?:\/\??#)?\/watch(?:.+)v=)(.{11})(?:\&[^\s]*)?/; var youtubeRegex = /(?:https?:\/\/)?(?:www\.)?youtu(?:|.be|be.com|.b)(?:\/v\/|\/watch\\?v=|e\/|(?:\/\??#)?\/watch(?:.+)v=)(.{11})(?:\&[^\s]*)?/;
var instagramRegex = /https?:\/\/(?:instagr\.am\/p\/|instagram\.com\/p\/)([a-zA-Z0-9\-\_]+)/i;
var vineRegex = /https?:\/\/vine\.co\/v\/([a-zA-Z0-9\-\_]+)/i;
var twitterRegex = /https?:\/\/twitter\.com\/.+?\/status\/\d+/i;
return { return {
wrapRichText: wrapRichText, wrapRichText: wrapRichText,
wrapPlainText: wrapPlainText wrapPlainText: wrapPlainText
}; };
function encodeEntities(value) {
return value.
replace(/&/g, '&amp;').
replace(/([^\#-~| |!])/g, function (value) { // non-alphanumeric
return '&#' + value.charCodeAt(0) + ';';
}).
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
}
function getEmojiSpritesheetCoords(emojiCode) { function getEmojiSpritesheetCoords(emojiCode) {
var i, row, column, totalColumns; var i, row, column, totalColumns;
for (var cat = 0; cat < Config.EmojiCategories.length; cat++) { for (var cat = 0; cat < Config.EmojiCategories.length; cat++) {
@ -3323,15 +3234,30 @@ angular.module('myApp.services', ['myApp.i18n'])
} }
// console.log(4, text, html); // console.log(4, text, html);
if (!options.noLinks) { if (!options.noLinks && !Config.Navigator.mobile) {
var youtubeMatches = text.match(youtubeRegex), var embedUrlMatches,
videoID = youtubeMatches && youtubeMatches[1]; embedTag = Config.Modes.chrome_packed ? 'webview' : 'iframe';
if (videoID) { if (embedUrlMatches = text.match(youtubeRegex)) {
var tag = Config.Modes.chrome_packed ? 'webview' : 'iframe'; var videoID = embedUrlMatches[1]
text = text + '<div class="im_message_iframe_video"><' + tag + ' type="text/html" frameborder="0" ' + text = text + '<div class="im_message_media_embed im_message_video_embed"><' + embedTag + ' type="text/html" frameborder="0" ' +
'src="http://www.youtube.com/embed/' + videoID + 'src="//www.youtube.com/embed/' + videoID +
'?autoplay=0&amp;controls=2"></' + tag + '></div>' '?autoplay=0&amp;controls=2"></' + embedTag + '></div>'
}
else if (embedUrlMatches = text.match(instagramRegex)) {
var instaID = embedUrlMatches[1];
text = text + '<div class="im_message_media_embed im_message_insta_embed"><' + embedTag + ' type="text/html" frameborder="0" ' +
'src="//instagram.com/p/' + instaID +
'/embed/"></' + embedTag + '></div>';
}
else if (embedUrlMatches = text.match(vineRegex)) {
var vineID = embedUrlMatches[1];
text = text + '<div class="im_message_media_embed im_message_vine_embed"><' + embedTag + ' type="text/html" frameborder="0" ' +
'src="//vine.co/v/' + vineID + '/embed/simple"></' + embedTag + '></div>';
}
else if (embedUrlMatches = !Config.Modes.chrome_packed && text.match(twitterRegex)) {
text = text + '<div class="im_message_twitter_embed"><blockquote class="twitter-tweet" lang="en"><a href="' + embedUrlMatches[0] + '"></a></blockquote></div>';
onContentLoaded(ExternalResourcesManager.attachTwitterScript);
} }
} }
@ -3377,58 +3303,6 @@ angular.module('myApp.services', ['myApp.i18n'])
}) })
.service('IdleManager', function ($rootScope, $window, $timeout) {
$rootScope.idle = {isIDLE: false};
var toPromise, started = false;
return {
start: start
};
function start () {
if (!started) {
started = true;
$($window).on('blur focus keydown mousedown touchstart', onEvent);
setTimeout(function () {
onEvent({type: 'blur'});
}, 0);
}
}
function onEvent (e) {
// console.log('event', e.type);
if (e.type == 'mousemove') {
$($window).off('mousemove', onEvent);
}
var isIDLE = e.type == 'blur' || e.type == 'timeout' ? true : false;
$timeout.cancel(toPromise);
if (!isIDLE) {
// console.log('update timeout');
toPromise = $timeout(function () {
onEvent({type: 'timeout'});
}, 30000);
}
if ($rootScope.idle.isIDLE == isIDLE) {
return;
}
// console.log('IDLE changed', isIDLE);
$rootScope.$apply(function () {
$rootScope.idle.isIDLE = isIDLE;
});
if (isIDLE && e.type == 'timeout') {
$($window).on('mousemove', onEvent);
}
}
})
.service('StatusManager', function ($timeout, $rootScope, MtpApiManager, IdleManager) { .service('StatusManager', function ($timeout, $rootScope, MtpApiManager, IdleManager) {
var toPromise, lastOnlineUpdated = 0, started = false; var toPromise, lastOnlineUpdated = 0, started = false;
@ -3607,7 +3481,9 @@ angular.module('myApp.services', ['myApp.i18n'])
try { try {
if ('onbeforeunload' in window) {
$($window).on('beforeunload', notificationsClear); $($window).on('beforeunload', notificationsClear);
}
} catch (e) {} } catch (e) {}
} }

54
app/nacl/Makefile

@ -0,0 +1,54 @@
# Copyright (c) 2013 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# GNU Make based build file.  For details on GNU Make see:
# http://www.gnu.org/software/make/manual/make.html
#
#
# Get pepper directory for toolchain and includes.
#
# If NACL_SDK_ROOT is not set, then assume it can be found three directories up.
#
THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST)))
NACL_SDK_ROOT ?= $(abspath $(dir $(THIS_MAKEFILE))../..)
# Project Build flags
WARNINGS := -Wno-long-long -Wall -Wswitch-enum -pedantic -Werror
CXXFLAGS := -pthread -std=gnu++98 $(WARNINGS)
#
# Compute tool paths
#
GETOS := python $(NACL_SDK_ROOT)/tools/getos.py
OSHELPERS = python $(NACL_SDK_ROOT)/tools/oshelpers.py
OSNAME := $(shell $(GETOS))
RM := $(OSHELPERS) rm
PNACL_TC_PATH := $(abspath $(NACL_SDK_ROOT)/toolchain/$(OSNAME)_pnacl)
PNACL_CXX := $(PNACL_TC_PATH)/bin/pnacl-clang++
PNACL_FINALIZE := $(PNACL_TC_PATH)/bin/pnacl-finalize
CXXFLAGS := -I$(NACL_SDK_ROOT)/include
LDFLAGS := -L$(NACL_SDK_ROOT)/lib/pnacl/Release -lppapi_cpp -lppapi
#
# Disable DOS PATH warning when using Cygwin based tools Windows
#
CYGWIN ?= nodosfilewarning
export CYGWIN
# Declare the ALL target first, to make the 'all' target the default build
all: mtproto_crypto.pexe
clean:
$(RM) mtproto_crypto.pexe mtproto_crypto.bc
mtproto_crypto.bc: mtproto_crypto.cc aes_core.c aes_ige.c aes_misc.c
$(PNACL_CXX) -o $@ $^ -O2 $(CXXFLAGS) $(LDFLAGS)
# $(PNACL_CXX) -g -o $@ $^ $(CXXFLAGS) $(LDFLAGS)
mtproto_crypto.pexe: mtproto_crypto.bc
$(PNACL_FINALIZE) -o $@ $<

147
app/nacl/aes.h

@ -0,0 +1,147 @@
/* crypto/aes/aes.h -*- mode:C; c-file-style: "eay" -*- */
/* ====================================================================
* Copyright (c) 1998-2002 The OpenSSL Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
*
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* openssl-core@openssl.org.
*
* 5. Products derived from this software may not be called "OpenSSL"
* nor may "OpenSSL" appear in their names without prior written
* permission of the OpenSSL Project.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
*
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
*/
#ifndef HEADER_AES_H
#define HEADER_AES_H
//#include <openssl/opensslconf.h>
#ifdef OPENSSL_NO_AES
#error AES is disabled.
#endif
#include <stddef.h>
#define AES_ENCRYPT 1
#define AES_DECRYPT 0
/* Because array size can't be a const in C, the following two are macros.
Both sizes are in bytes. */
#define AES_MAXNR 14
#define AES_BLOCK_SIZE 16
#ifdef __cplusplus
extern "C" {
#endif
/* This should be a hidden type, but EVP requires that the size be known */
struct aes_key_st {
#ifdef AES_LONG
unsigned long rd_key[4 *(AES_MAXNR + 1)];
#else
unsigned int rd_key[4 *(AES_MAXNR + 1)];
#endif
int rounds;
};
typedef struct aes_key_st AES_KEY;
const char *AES_options(void);
int AES_set_encrypt_key(const unsigned char *userKey, const int bits,
AES_KEY *key);
int AES_set_decrypt_key(const unsigned char *userKey, const int bits,
AES_KEY *key);
int private_AES_set_encrypt_key(const unsigned char *userKey, const int bits,
AES_KEY *key);
int private_AES_set_decrypt_key(const unsigned char *userKey, const int bits,
AES_KEY *key);
void AES_encrypt(const unsigned char *in, unsigned char *out,
const AES_KEY *key);
void AES_decrypt(const unsigned char *in, unsigned char *out,
const AES_KEY *key);
void AES_ecb_encrypt(const unsigned char *in, unsigned char *out,
const AES_KEY *key, const int enc);
void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
size_t length, const AES_KEY *key,
unsigned char *ivec, const int enc);
void AES_cfb128_encrypt(const unsigned char *in, unsigned char *out,
size_t length, const AES_KEY *key,
unsigned char *ivec, int *num, const int enc);
void AES_cfb1_encrypt(const unsigned char *in, unsigned char *out,
size_t length, const AES_KEY *key,
unsigned char *ivec, int *num, const int enc);
void AES_cfb8_encrypt(const unsigned char *in, unsigned char *out,
size_t length, const AES_KEY *key,
unsigned char *ivec, int *num, const int enc);
void AES_ofb128_encrypt(const unsigned char *in, unsigned char *out,
size_t length, const AES_KEY *key,
unsigned char *ivec, int *num);
void AES_ctr128_encrypt(const unsigned char *in, unsigned char *out,
size_t length, const AES_KEY *key,
unsigned char ivec[AES_BLOCK_SIZE],
unsigned char ecount_buf[AES_BLOCK_SIZE],
unsigned int *num);
/* NB: the IV is _two_ blocks long */
void AES_ige_encrypt(const unsigned char *in, unsigned char *out,
size_t length, const AES_KEY *key,
unsigned char *ivec, const int enc);
/* NB: the IV is _four_ blocks long */
void AES_bi_ige_encrypt(const unsigned char *in, unsigned char *out,
size_t length, const AES_KEY *key,
const AES_KEY *key2, const unsigned char *ivec,
const int enc);
int AES_wrap_key(AES_KEY *key, const unsigned char *iv,
unsigned char *out,
const unsigned char *in, unsigned int inlen);
int AES_unwrap_key(AES_KEY *key, const unsigned char *iv,
unsigned char *out,
const unsigned char *in, unsigned int inlen);
#ifdef __cplusplus
}
#endif
#endif /* !HEADER_AES_H */

1358
app/nacl/aes_core.c

File diff suppressed because it is too large Load Diff

325
app/nacl/aes_ige.c

@ -0,0 +1,325 @@
/* crypto/aes/aes_ige.c -*- mode:C; c-file-style: "eay" -*- */
/* ====================================================================
* Copyright (c) 2006 The OpenSSL Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
*
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* openssl-core@openssl.org.
*
* 5. Products derived from this software may not be called "OpenSSL"
* nor may "OpenSSL" appear in their names without prior written
* permission of the OpenSSL Project.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
*
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
*/
//#include "cryptlib.h"
#include "aes.h"
#include "aes_locl.h"
#include <assert.h>
#define OPENSSL_assert assert
#define N_WORDS (AES_BLOCK_SIZE / sizeof(unsigned long))
typedef struct {
unsigned long data[N_WORDS];
} aes_block_t;
/* XXX: probably some better way to do this */
#if defined(__i386__) || defined(__x86_64__)
#define UNALIGNED_MEMOPS_ARE_FAST 1
#else
#define UNALIGNED_MEMOPS_ARE_FAST 0
#endif
#if UNALIGNED_MEMOPS_ARE_FAST
#define load_block(d, s) (d) = *(const aes_block_t *)(s)
#define store_block(d, s) *(aes_block_t *)(d) = (s)
#else
#define load_block(d, s) memcpy((d).data, (s), AES_BLOCK_SIZE)
#define store_block(d, s) memcpy((d), (s).data, AES_BLOCK_SIZE)
#endif
/* N.B. The IV for this mode is _twice_ the block size */
void AES_ige_encrypt(const unsigned char *in, unsigned char *out,
size_t length, const AES_KEY *key,
unsigned char *ivec, const int enc)
{
size_t n;
size_t len = length;
OPENSSL_assert(in && out && key && ivec);
OPENSSL_assert((AES_ENCRYPT == enc)||(AES_DECRYPT == enc));
OPENSSL_assert((length%AES_BLOCK_SIZE) == 0);
len = length / AES_BLOCK_SIZE;
if (AES_ENCRYPT == enc)
{
if (in != out &&
(UNALIGNED_MEMOPS_ARE_FAST || ((size_t)in|(size_t)out|(size_t)ivec)%sizeof(long)==0))
{
aes_block_t *ivp = (aes_block_t *)ivec;
aes_block_t *iv2p = (aes_block_t *)(ivec + AES_BLOCK_SIZE);
while (len)
{
aes_block_t *inp = (aes_block_t *)in;
aes_block_t *outp = (aes_block_t *)out;
for(n=0 ; n < N_WORDS; ++n)
outp->data[n] = inp->data[n] ^ ivp->data[n];
AES_encrypt((unsigned char *)outp->data, (unsigned char *)outp->data, key);
for(n=0 ; n < N_WORDS; ++n)
outp->data[n] ^= iv2p->data[n];
ivp = outp;
iv2p = inp;
--len;
in += AES_BLOCK_SIZE;
out += AES_BLOCK_SIZE;
}
memcpy(ivec, ivp->data, AES_BLOCK_SIZE);
memcpy(ivec + AES_BLOCK_SIZE, iv2p->data, AES_BLOCK_SIZE);
}
else
{
aes_block_t tmp, tmp2;
aes_block_t iv;
aes_block_t iv2;
load_block(iv, ivec);
load_block(iv2, ivec + AES_BLOCK_SIZE);
while (len)
{
load_block(tmp, in);
for(n=0 ; n < N_WORDS; ++n)
tmp2.data[n] = tmp.data[n] ^ iv.data[n];
AES_encrypt((unsigned char *)tmp2.data, (unsigned char *)tmp2.data, key);
for(n=0 ; n < N_WORDS; ++n)
tmp2.data[n] ^= iv2.data[n];
store_block(out, tmp2);
iv = tmp2;
iv2 = tmp;
--len;
in += AES_BLOCK_SIZE;
out += AES_BLOCK_SIZE;
}
memcpy(ivec, iv.data, AES_BLOCK_SIZE);
memcpy(ivec + AES_BLOCK_SIZE, iv2.data, AES_BLOCK_SIZE);
}
}
else
{
if (in != out &&
(UNALIGNED_MEMOPS_ARE_FAST || ((size_t)in|(size_t)out|(size_t)ivec)%sizeof(long)==0))
{
aes_block_t *ivp = (aes_block_t *)ivec;
aes_block_t *iv2p = (aes_block_t *)(ivec + AES_BLOCK_SIZE);
while (len)
{
aes_block_t tmp;
aes_block_t *inp = (aes_block_t *)in;
aes_block_t *outp = (aes_block_t *)out;
for(n=0 ; n < N_WORDS; ++n)
tmp.data[n] = inp->data[n] ^ iv2p->data[n];
AES_decrypt((unsigned char *)tmp.data, (unsigned char *)outp->data, key);
for(n=0 ; n < N_WORDS; ++n)
outp->data[n] ^= ivp->data[n];
ivp = inp;
iv2p = outp;
--len;
in += AES_BLOCK_SIZE;
out += AES_BLOCK_SIZE;
}
memcpy(ivec, ivp->data, AES_BLOCK_SIZE);
memcpy(ivec + AES_BLOCK_SIZE, iv2p->data, AES_BLOCK_SIZE);
}
else
{
aes_block_t tmp, tmp2;
aes_block_t iv;
aes_block_t iv2;
load_block(iv, ivec);
load_block(iv2, ivec + AES_BLOCK_SIZE);
while (len)
{
load_block(tmp, in);
tmp2 = tmp;
for(n=0 ; n < N_WORDS; ++n)
tmp.data[n] ^= iv2.data[n];
AES_decrypt((unsigned char *)tmp.data, (unsigned char *)tmp.data, key);
for(n=0 ; n < N_WORDS; ++n)
tmp.data[n] ^= iv.data[n];
store_block(out, tmp);
iv = tmp2;
iv2 = tmp;
--len;
in += AES_BLOCK_SIZE;
out += AES_BLOCK_SIZE;
}
memcpy(ivec, iv.data, AES_BLOCK_SIZE);
memcpy(ivec + AES_BLOCK_SIZE, iv2.data, AES_BLOCK_SIZE);
}
}
}
/*
* Note that its effectively impossible to do biIGE in anything other
* than a single pass, so no provision is made for chaining.
*/
/* N.B. The IV for this mode is _four times_ the block size */
void AES_bi_ige_encrypt(const unsigned char *in, unsigned char *out,
size_t length, const AES_KEY *key,
const AES_KEY *key2, const unsigned char *ivec,
const int enc)
{
size_t n;
size_t len = length;
unsigned char tmp[AES_BLOCK_SIZE];
unsigned char tmp2[AES_BLOCK_SIZE];
unsigned char tmp3[AES_BLOCK_SIZE];
unsigned char prev[AES_BLOCK_SIZE];
const unsigned char *iv;
const unsigned char *iv2;
OPENSSL_assert(in && out && key && ivec);
OPENSSL_assert((AES_ENCRYPT == enc)||(AES_DECRYPT == enc));
OPENSSL_assert((length%AES_BLOCK_SIZE) == 0);
if (AES_ENCRYPT == enc)
{
/* XXX: Do a separate case for when in != out (strictly should
check for overlap, too) */
/* First the forward pass */
iv = ivec;
iv2 = ivec + AES_BLOCK_SIZE;
while (len >= AES_BLOCK_SIZE)
{
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
out[n] = in[n] ^ iv[n];
AES_encrypt(out, out, key);
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
out[n] ^= iv2[n];
iv = out;
memcpy(prev, in, AES_BLOCK_SIZE);
iv2 = prev;
len -= AES_BLOCK_SIZE;
in += AES_BLOCK_SIZE;
out += AES_BLOCK_SIZE;
}
/* And now backwards */
iv = ivec + AES_BLOCK_SIZE*2;
iv2 = ivec + AES_BLOCK_SIZE*3;
len = length;
while(len >= AES_BLOCK_SIZE)
{
out -= AES_BLOCK_SIZE;
/* XXX: reduce copies by alternating between buffers */
memcpy(tmp, out, AES_BLOCK_SIZE);
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
out[n] ^= iv[n];
/* hexdump(stdout, "out ^ iv", out, AES_BLOCK_SIZE); */
AES_encrypt(out, out, key);
/* hexdump(stdout,"enc", out, AES_BLOCK_SIZE); */
/* hexdump(stdout,"iv2", iv2, AES_BLOCK_SIZE); */
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
out[n] ^= iv2[n];
/* hexdump(stdout,"out", out, AES_BLOCK_SIZE); */
iv = out;
memcpy(prev, tmp, AES_BLOCK_SIZE);
iv2 = prev;
len -= AES_BLOCK_SIZE;
}
}
else
{
/* First backwards */
iv = ivec + AES_BLOCK_SIZE*2;
iv2 = ivec + AES_BLOCK_SIZE*3;
in += length;
out += length;
while (len >= AES_BLOCK_SIZE)
{
in -= AES_BLOCK_SIZE;
out -= AES_BLOCK_SIZE;
memcpy(tmp, in, AES_BLOCK_SIZE);
memcpy(tmp2, in, AES_BLOCK_SIZE);
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
tmp[n] ^= iv2[n];
AES_decrypt(tmp, out, key);
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
out[n] ^= iv[n];
memcpy(tmp3, tmp2, AES_BLOCK_SIZE);
iv = tmp3;
iv2 = out;
len -= AES_BLOCK_SIZE;
}
/* And now forwards */
iv = ivec;
iv2 = ivec + AES_BLOCK_SIZE;
len = length;
while (len >= AES_BLOCK_SIZE)
{
memcpy(tmp, out, AES_BLOCK_SIZE);
memcpy(tmp2, out, AES_BLOCK_SIZE);
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
tmp[n] ^= iv2[n];
AES_decrypt(tmp, out, key);
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
out[n] ^= iv[n];
memcpy(tmp3, tmp2, AES_BLOCK_SIZE);
iv = tmp3;
iv2 = out;
len -= AES_BLOCK_SIZE;
in += AES_BLOCK_SIZE;
out += AES_BLOCK_SIZE;
}
}
}

89
app/nacl/aes_locl.h

@ -0,0 +1,89 @@
/* crypto/aes/aes.h -*- mode:C; c-file-style: "eay" -*- */
/* ====================================================================
* Copyright (c) 1998-2002 The OpenSSL Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
*
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* openssl-core@openssl.org.
*
* 5. Products derived from this software may not be called "OpenSSL"
* nor may "OpenSSL" appear in their names without prior written
* permission of the OpenSSL Project.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
*
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
*/
#ifndef HEADER_AES_LOCL_H
#define HEADER_AES_LOCL_H
//#include <openssl/e_os2.h>
#ifdef OPENSSL_NO_AES
#error AES is disabled.
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_X64))
# define SWAP(x) (_lrotl(x, 8) & 0x00ff00ff | _lrotr(x, 8) & 0xff00ff00)
# define GETU32(p) SWAP(*((u32 *)(p)))
# define PUTU32(ct, st) { *((u32 *)(ct)) = SWAP((st)); }
#else
# define GETU32(pt) (((u32)(pt)[0] << 24) ^ ((u32)(pt)[1] << 16) ^ ((u32)(pt)[2] << 8) ^ ((u32)(pt)[3]))
# define PUTU32(ct, st) { (ct)[0] = (u8)((st) >> 24); (ct)[1] = (u8)((st) >> 16); (ct)[2] = (u8)((st) >> 8); (ct)[3] = (u8)(st); }
#endif
#ifdef AES_LONG
typedef unsigned long u32;
#else
typedef unsigned int u32;
#endif
typedef unsigned short u16;
typedef unsigned char u8;
#define MAXKC (256/32)
#define MAXKB (256/8)
#define MAXNR 14
/* This controls loop-unrolling in aes_core.c */
#undef FULL_UNROLL
#endif /* !HEADER_AES_LOCL_H */

85
app/nacl/aes_misc.c

@ -0,0 +1,85 @@
/* crypto/aes/aes_misc.c -*- mode:C; c-file-style: "eay" -*- */
/* ====================================================================
* Copyright (c) 1998-2002 The OpenSSL Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
*
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* openssl-core@openssl.org.
*
* 5. Products derived from this software may not be called "OpenSSL"
* nor may "OpenSSL" appear in their names without prior written
* permission of the OpenSSL Project.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
*
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
*/
//#include <openssl/opensslv.h>
//#include <openssl/crypto.h>
#include "aes.h"
#include "aes_locl.h"
const char AES_version[]="AES" ;//OPENSSL_VERSION_PTEXT;
const char *AES_options(void) {
#ifdef FULL_UNROLL
return "aes(full)";
#else
return "aes(partial)";
#endif
}
/* FIPS wrapper functions to block low level AES calls in FIPS mode */
int AES_set_encrypt_key(const unsigned char *userKey, const int bits,
AES_KEY *key)
{
#ifdef OPENSSL_FIPS
fips_cipher_abort(AES);
#endif
return private_AES_set_encrypt_key(userKey, bits, key);
}
int AES_set_decrypt_key(const unsigned char *userKey, const int bits,
AES_KEY *key)
{
#ifdef OPENSSL_FIPS
fips_cipher_abort(AES);
#endif
return private_AES_set_decrypt_key(userKey, bits, key);
}

BIN
app/nacl/mtproto_crypto.bc

Binary file not shown.

267
app/nacl/mtproto_crypto.cc

@ -0,0 +1,267 @@
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// @file hello_tutorial.cc
/// This example demonstrates loading, running and scripting a very simple NaCl
/// module. To load the NaCl module, the browser first looks for the
/// CreateModule() factory method (at the end of this file). It calls
/// CreateModule() once to load the module code. After the code is loaded,
/// CreateModule() is not called again.
///
/// Once the code is loaded, the browser calls the CreateInstance()
/// method on the object returned by CreateModule(). It calls CreateInstance()
/// each time it encounters an <embed> tag that references your NaCl module.
///
/// The browser can talk to your NaCl module via the postMessage() Javascript
/// function. When you call postMessage() on your NaCl module from the browser,
/// this becomes a call to the HandleMessage() method of your pp::Instance
/// subclass. You can send messages back to the browser by calling the
/// PostMessage() method on your pp::Instance. Note that these two methods
/// (postMessage() in Javascript and PostMessage() in C++) are asynchronous.
/// This means they return immediately - there is no waiting for the message
/// to be handled. This has implications in your program design, particularly
/// when mutating property values that are exposed to both the browser and the
/// NaCl module.
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/var.h"
#include "ppapi/cpp/var_dictionary.h"
#include "ppapi/cpp/var_array_buffer.h"
#include "aes.h"
#include <inttypes.h>
#include <stdio.h>
uint64_t gcd(uint64_t a, uint64_t b) {
while (a != 0 && b != 0) {
while ((b & 1) == 0) {
b >>= 1;
}
while ((a & 1) == 0) {
a >>= 1;
}
if (a > b) {
a -= b;
} else {
b -= a;
}
}
return b == 0 ? a : b;
}
/// The Instance class. One of these exists for each instance of your NaCl
/// module on the web page. The browser will ask the Module object to create
/// a new Instance for each occurrence of the <embed> tag that has these
/// attributes:
/// src="hello_tutorial.nmf"
/// type="application/x-pnacl"
/// To communicate with the browser, you must override HandleMessage() to
/// receive messages from the browser, and use PostMessage() to send messages
/// back to the browser. Note that this interface is asynchronous.
class MtprotoCryptoInstance : public pp::Instance {
public:
/// The constructor creates the plugin-side instance.
/// @param[in] instance the handle to the browser-side plugin instance.
explicit MtprotoCryptoInstance(PP_Instance instance) : pp::Instance(instance)
{}
virtual ~MtprotoCryptoInstance() {}
/// Handler for messages coming in from the browser via postMessage(). The
/// @a var_message can contain be any pp:Var type; for example int, string
/// Array or Dictinary. Please see the pp:Var documentation for more details.
/// @param[in] var_message The message posted by the browser.
virtual void HandleMessage(const pp::Var& var_message) {
if (!var_message.is_dictionary()) {
return;
}
pp::VarDictionary request = pp::VarDictionary::VarDictionary(var_message);
pp::Var varTaskID = request.Get(pp::Var::Var("taskID"));
pp::Var varTask = request.Get(pp::Var::Var("task"));
if (!varTaskID.is_int()) {
return;
}
int32_t intTaskID = varTaskID.AsInt();
std::string strTask = varTask.AsString();
pp::Var varResult;
if (strTask == "aes-encrypt") {
pp::Var varData = request.Get(pp::Var::Var("bytes"));
pp::Var varKey = request.Get(pp::Var::Var("keyBytes"));
pp::Var varIv = request.Get(pp::Var::Var("ivBytes"));
if (!varData.is_array_buffer() || !varKey.is_array_buffer() || !varIv.is_array_buffer()) {
return;
}
pp::VarArrayBuffer abData = pp::VarArrayBuffer::VarArrayBuffer(varData);
pp::VarArrayBuffer abKey = pp::VarArrayBuffer::VarArrayBuffer(varKey);
pp::VarArrayBuffer abIv = pp::VarArrayBuffer::VarArrayBuffer(varIv);
char* what = static_cast<char*>(abData.Map());
char* keyBuff = static_cast<char*>(abKey.Map());
char* ivBuff = static_cast<char*>(abIv.Map());
int length = abData.ByteLength();
AES_KEY akey;
AES_set_encrypt_key((const unsigned char *) keyBuff, 32 * 8, &akey);
AES_ige_encrypt((const unsigned char *)what, (unsigned char *)what, length, &akey, (unsigned char *)ivBuff, AES_ENCRYPT);
varResult = abData;
}
else if (strTask == "aes-decrypt") {
pp::Var varData = request.Get(pp::Var::Var("encryptedBytes"));
pp::Var varKey = request.Get(pp::Var::Var("keyBytes"));
pp::Var varIv = request.Get(pp::Var::Var("ivBytes"));
if (!varData.is_array_buffer() || !varKey.is_array_buffer() || !varIv.is_array_buffer()) {
return;
}
pp::VarArrayBuffer abData = pp::VarArrayBuffer::VarArrayBuffer(varData);
pp::VarArrayBuffer abKey = pp::VarArrayBuffer::VarArrayBuffer(varKey);
pp::VarArrayBuffer abIv = pp::VarArrayBuffer::VarArrayBuffer(varIv);
char* what = static_cast<char*>(abData.Map());
char* keyBuff = static_cast<char*>(abKey.Map());
char* ivBuff = static_cast<char*>(abIv.Map());
int length = abData.ByteLength();
AES_KEY akey;
AES_set_decrypt_key((const unsigned char *) keyBuff, 32 * 8, &akey);
AES_ige_encrypt((const unsigned char *)what, (unsigned char *)what, length, &akey, (unsigned char *)ivBuff, AES_DECRYPT);
varResult = abData;
}
else if (strTask == "factorize") {
pp::Var varBytes = request.Get(pp::Var::Var("bytes"));
if (!varBytes.is_array()) {
return;
}
pp::VarArray aBytes = pp::VarArray::VarArray(varBytes);
int length = aBytes.GetLength();
if (length > 8) {
return;
}
uint64_t what = 0;
for (int i = 0; i < length; i++) {
what += (uint64_t) aBytes.Get(length - i - 1).AsInt() << (i * 8);
}
int it = 0, i, j;
uint64_t g = 0;
for (i = 0; i < 3 || it < 1000; i++) {
int q = ((lrand48() & 15) + 17) % what;
uint64_t x = (long long)lrand48() % (what - 1) + 1, y = x;
int lim = 1 << (i + 18), j;
for(j = 1; j < lim; j++) {
++it;
uint64_t a = x, b = x, c = q;
while (b) {
if (b & 1) {
c += a;
if (c >= what) {
c -= what;
}
}
a += a;
if (a >= what) {
a -= what;
}
b >>= 1;
}
x = c;
uint64_t z = x < y ? what + x - y : x - y;
g = gcd(z, what);
if (g != 1) {
break;
}
if (!(j & (j - 1))) {
y = x;
}
}
if (g > 1 && g < what) {
break;
}
}
uint64_t p = what / g;
pp::VarArray pBytesArray = pp::VarArray::VarArray();
int index = 0;
for (int i = 0; i < 8; i++) {
unsigned char byte = p >> ((8 - i - 1) * 8) & 0xFF;
if (byte > 0 || index > 0) {
pBytesArray.Set(index, pp::Var::Var((int)byte));
index++;
}
}
index = 0;
pp::VarArray gBytesArray = pp::VarArray::VarArray();
for (i = 0; i < 8; i++) {
unsigned char byte = g >> ((8 - i - 1) * 8) & 0xFF;
if (byte > 0 || index > 0) {
gBytesArray.Set(index, pp::Var::Var((int)byte));
index++;
}
}
pp::VarArray varResultArray = pp::VarArray::VarArray();
if (p < g) {
varResultArray.Set(0, pBytesArray);
varResultArray.Set(1, gBytesArray);
} else {
varResultArray.Set(1, pBytesArray);
varResultArray.Set(0, gBytesArray);
}
varResult = varResultArray;
}
else {
varResult = pp::Var::Var();
}
pp::VarDictionary response = pp::VarDictionary::VarDictionary();
response.Set(pp::Var::Var("taskID"), varTaskID);
response.Set(pp::Var::Var("result"), varResult);
PostMessage(response);
}
};
/// The Module class. The browser calls the CreateInstance() method to create
/// an instance of your NaCl module on the web page. The browser creates a new
/// instance for each <embed> tag with type="application/x-pnacl".
class MtprotoCryptoModule : public pp::Module {
public:
MtprotoCryptoModule() : pp::Module() {}
virtual ~MtprotoCryptoModule() {}
/// Create and return a MtprotoCryptoInstance object.
/// @param[in] instance The browser-side instance.
/// @return the plugin-side instance.
virtual pp::Instance* CreateInstance(PP_Instance instance) {
return new MtprotoCryptoInstance(instance);
}
};
namespace pp {
/// Factory function called by the browser when the module is first loaded.
/// The browser keeps a singleton of this module. It calls the
/// CreateInstance() method on the object you return to make instances. There
/// is one instance per <embed> tag on the page. This is the main binding
/// point for your NaCl module with the browser.
Module* CreateModule() {
return new MtprotoCryptoModule();
}
} // namespace pp

10
app/nacl/mtproto_crypto.nmf

@ -0,0 +1,10 @@
{
"program": {
"portable": {
"pnacl-translate": {
"url": "mtproto_crypto.pexe?67",
"optlevel": 2
}
}
}
}

BIN
app/nacl/mtproto_crypto.pexe

Binary file not shown.

36
app/partials/desktop/audio_player.html

@ -1,31 +1,39 @@
<div class="audio_player_wrap clearfix"> <div class="audio_player_wrap clearfix">
<button class="btn btn-primary pull-left audio_player_button" ng-click="togglePlay()"> <a class="im_message_file_button" ng-click="togglePlay()" ng-class="{im_message_file_button_dl_audio: audio.downloaded}">
<i class="icon audio_player_btn_icon" ng-class="{audio_player_btn_icon_pause: mediaPlayer.player.playing, audio_player_btn_icon_cancel: audio.progress.enabled}"></i> <i class="im_message_file_button_icon" ng-class="{audio_player_btn_icon_pause: mediaPlayer.player.playing}"></i>
</button> </a>
<div class="audio_player_title_wrap"> <div class="audio_player_title_wrap">
<div class="audio_player_meta pull-right" ng-if="audio.downloaded &amp;&amp; (mediaPlayer.player.duration || audio.duration)" ng-switch="mediaPlayer.player.playing || mediaPlayer.player.currentTime > 0">
<span ng-switch-when="true" class="audio_player_duration" ng-bind="mediaPlayer.player.currentTime | durationRemains : (mediaPlayer.player.duration || audio.duration)"></span>
<span ng-switch-default class="audio_player_duration" ng-bind="mediaPlayer.player.duration || audio.duration | duration"></span>
</div>
<a ng-click="download()" class="audio_player_title" ng-switch="::audio.file_name.length > 0"> <a ng-click="download()" class="audio_player_title" ng-switch="::audio.file_name.length > 0">
<span ng-switch-when="true" ng-bind="::audio.file_name"></span> <span ng-switch-when="true" ng-bind="::audio.file_name"></span>
<span ng-switch-default my-i18n="message_attach_audio_message"></span> <span ng-switch-default my-i18n="message_attach_audio_message"></span>
</a> </a>
<div class="audio_player_meta" ng-switch="audio.progress.enabled ? 'progress' : (!mediaPlayer.player.duration &amp;&amp; !audio.duration ? 'size' : '')"> <div class="audio_player_meta" ng-if="!audio.downloaded || !(mediaPlayer.player.duration || audio.duration)" ng-switch="audio.progress.enabled">
<span ng-switch-when="progress" class="audio_player_size" ng-bind="audio.progress | formatSizeProgress"></span> <span ng-switch-when="true" class="audio_player_size" ng-bind="audio.progress | formatSizeProgress"></span>
<span ng-switch-when="size" class="audio_player_size" ng-bind="audio.size | formatSize"></span> <span ng-switch-default class="audio_player_size" ng-bind="audio.size | formatSize"></span>
<span ng-switch-default class="audio_player_duration" ng-bind="(mediaPlayer.player.playing || mediaPlayer.player.currentTime > 0) ? mediaPlayer.player.currentTime : (mediaPlayer.player.duration || audio.duration) | duration"></span>
</div> </div>
</div> </div>
<div class="audio_player_actions" ng-if="!audio.progress.enabled &amp;&amp; !audio.url"> <div class="audio_player_actions" ng-if="!audio.progress.enabled &amp;&amp; !audio.downloaded">
<a ng-click="togglePlay()" my-i18n="message_attach_audio_play"></a>
<a ng-if="audio._ == 'document'" ng-click="download()" my-i18n="message_attach_document_download"></a> <a ng-if="audio._ == 'document'" ng-click="download()" my-i18n="message_attach_document_download"></a>
<a ng-click="togglePlay()" my-i18n="message_attach_audio_play"></a>
</div> </div>
<div class="audio_player_progress_wrap" ng-if="audio.progress.enabled || audio.url" ng-switch="audio.progress.enabled"> <div class="audio_player_progress_wrap" ng-if="audio.progress.enabled || audio.downloaded" ng-switch="audio.progress.enabled">
<div ng-switch-when="true" class="progress tg_down_progress"> <div ng-switch-when="true" class="clearfix im_message_cancelable_progress_wrap">
<a class="im_message_media_progress_cancel pull-right" ng-click="audio.progress.cancel()" my-i18n="modal_cancel"></a>
<div class="im_message_download_progress_wrap">
<div class="progress tg_down_progress">
<div class="progress-bar progress-bar-success" ng-style="{width: audio.progress.percent + '%'}"></div> <div class="progress-bar progress-bar-success" ng-style="{width: audio.progress.percent + '%'}"></div>
</div> </div>
<div ng-switch-default class="progress tg_play_progress"> </div>
<div class="progress-bar progress-bar-success" ng-style="{width: mediaPlayer.player.currentTime / (mediaPlayer.player.duration || audio.duration) * 100 + '%'}"></div> </div>
<div ng-switch-default class="im_message_playback_progress_wrap">
<div class="audio_player_seek_slider" my-slider slider-model="mediaPlayer.player.currentTime" slider-max="mediaPlayer.player.duration || audio.duration" slider-onchange="seek(value)"></div>
</div> </div>
</div> </div>
<audio ng-if="audio.url" media-player="mediaPlayer.player"> <audio ng-if="audio.url" media-player="mediaPlayer.player">
<source ng-src="{{::audio.url}}" type="audio/ogg" /> <source ng-src="{{::audio.url}}" type="audio/ogg" volume="{{::volume}}" />
</audio> </audio>
</div> </div>

17
app/partials/desktop/changelog_modal.html

@ -19,9 +19,24 @@
<div class="modal_section changelog_version_wrap"> <div class="modal_section changelog_version_wrap">
<h3 class="modal_section_header changelog_version_title"> <h3 class="modal_section_header changelog_version_title">
Version 0.3.2 Version 0.3.3
<span class="pull-right" my-i18n="changelog_modal_title_current_version"></span> <span class="pull-right" my-i18n="changelog_modal_title_current_version"></span>
</h3> </h3>
<div class="modal_section_body changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Added Native Client module: dramatically improved encryption speed; Download and upload speed is now as high as in native applications.</li>
<li>HTTPS. We recommend you to use <a href="https://web.telegram.org" target="_blank">https://web.telegram.org</a>.</li>
<li>Added multiple open tabs warning. Please note, that only one tab with Telegram Web will work.</li>
<li>Jump to selected spot when playing back audio.</li>
<li>Bugfixes</li>
</ul>
</div>
</div>
<div class="modal_section changelog_version_wrap">
<h3 class="modal_section_header changelog_version_title">
Version 0.3.2
</h3>
<div class="modal_section_body changelog_version_changes"> <div class="modal_section_body changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list"> <ul class="list-unstyled changelog_version_changes_list">
<li>Usernames support: <a href="" ng-click="changeUsername()">Choose a username right now!</a></li> <li>Usernames support: <a href="" ng-click="changeUsername()">Choose a username right now!</a></li>

21
app/partials/desktop/document_modal.html

@ -0,0 +1,21 @@
<div class="media_modal_wrap document_modal_wrap" my-modal-position>
<div class="modal-body">
<div my-load-document="document"></div>
<div class="media_meta_wrap clearfix">
<div class="media_modal_actions pull-right">
<a href="" class="media_modal_action_link" ng-click="download()" my-i18n="media_modal_download"></a>
<a ng-if="messageID" href="" class="media_modal_action_link" ng-click="forward()" my-i18n="media_modal_forward"></a>
<a ng-if="messageID" href="" class="media_modal_action_link" ng-click="delete()" my-i18n="media_modal_delete"></a>
</div>
<p class="media_modal_info">
<a class="media_modal_author" my-user-link="document.user_id"></a>, <span ng-bind="document.date | dateOrTime"></span>
</p>
</div>
</div>
</div>

1
app/partials/desktop/error_modal.html

@ -36,6 +36,7 @@
<span ng-switch-when="USERNAME_INVALID" my-i18n="error_modal_username_invalid_description"></span> <span ng-switch-when="USERNAME_INVALID" my-i18n="error_modal_username_invalid_description"></span>
<span ng-switch-when="USERNAME_OCCUPIED" my-i18n="error_modal_username_occupied_description"></span> <span ng-switch-when="USERNAME_OCCUPIED" my-i18n="error_modal_username_occupied_description"></span>
<div ng-switch-default ng-switch="error.code"> <div ng-switch-default ng-switch="error.code">
<span ng-switch-when="400" my-i18n="error_modal_bad_request_description"></span> <span ng-switch-when="400" my-i18n="error_modal_bad_request_description"></span>
<span ng-switch-when="401" my-i18n="error_modal_unauthorized_description"> <span ng-switch-when="401" my-i18n="error_modal_unauthorized_description">

21
app/partials/desktop/full_document.html

@ -0,0 +1,21 @@
<div class="document_modal_image_wrap">
<div class="img_fullsize_with_progress_wrap document_fullsize_with_progress_wrap" ng-style="{width: frameWidth + 'px', height: frameHeight + 'px'}">
<div class="img_fullsize_progress_overlay">
<div class="img_fullsize_progress_wrap" ng-style="{width: frameWidth + 'px', height: frameHeight + 'px'}">
<div class="img_fullsize_progress progress tg_progress">
<div class="progress-bar progress-bar-success" style="width: {{document.progress.percent}}%"></div>
</div>
</div>
</div>
<div class="img_fullsize_wrap" ng-if="thumbSrc.length > 0">
<img
class="img_fullsize"
ng-src="{{thumbSrc}}"
ng-style="{width: imageWidth + 'px', height: imageHeight + 'px'}"
/>
</div>
</div>
<div class="document_fullsize_wrap" ng-click="toggleZoom(!zoomed)">
<img class="document_fullsize_img" />
</div>
</div>

4
app/partials/desktop/full_gif.html

@ -1,6 +1,6 @@
<a class="img_gif_with_progress_wrap" ng-click="toggle($event)"> <a class="img_gif_with_progress_wrap" ng-click="toggle($event)">
<div class="img_gif_image_wrap" ng-switch="document.url &amp;&amp; isActive"> <div class="img_gif_image_wrap" ng-switch="document.downloaded &amp;&amp; isActive">
<img ng-switch-when="true" class="img_gif_image" ng-src="{{document.url}}" /> <img ng-switch-when="true" class="img_gif_image" ng-src="{{document.url}}" />
<img ng-switch-default class="img_gif_thumb" my-load-thumb thumb="document.thumb" /> <img ng-switch-default class="img_gif_thumb" my-load-thumb thumb="document.thumb" />
@ -17,7 +17,7 @@
<div ng-switch-default class="img_gif_info_wrap"> <div ng-switch-default class="img_gif_info_wrap">
<div class="img_gif_label pull-left">GIF</div> <div class="img_gif_label pull-left">GIF</div>
<div ng-if="!document.url" class="img_gif_size pull-right" ng-bind="::document.size | formatSize"></div> <div ng-if="!document.downloaded" class="img_gif_size pull-right" ng-bind="::document.size | formatSize"></div>
</div> </div>
</div> </div>

14
app/partials/desktop/full_video.html

@ -1,22 +1,22 @@
<div class="img_fullsize_with_progress_wrap" ng-style="{width: video.full.width + 'px', height: video.full.height + 'px'}"> <div class="img_fullsize_with_progress_wrap" ng-style="{width: video.full.width + 'px', height: video.full.height + 'px'}">
<div class="img_fullsize_progress_overlay" ng-show="progress.enabled"> <div class="img_fullsize_progress_overlay" ng-show="video.progress.enabled">
<div class="img_fullsize_progress_wrap" ng-style="{width: video.full.width + 'px', height: video.full.height + 'px'}"> <div class="img_fullsize_progress_wrap" ng-style="{width: video.full.width + 'px', height: video.full.height + 'px'}">
<div class="img_fullsize_progress progress tg_progress"> <div class="img_fullsize_progress progress tg_progress">
<div class="progress-bar progress-bar-success" style="width: {{progress.percent}}%"></div> <div class="progress-bar progress-bar-success" style="width: {{video.progress.percent}}%"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="img_fullsize_wrap" ng-if="!player.src"> <div class="img_fullsize_wrap" ng-if="!video.url">
<img <img
class="img_fullsize" class="img_fullsize"
my-load-thumb my-load-thumb
thumb="video.fullThumb" thumb="video.fullThumb"
/> />
</div> </div>
<div class="video_full_player" ng-if="player.src"> <div class="video_full_player" ng-if="video.url" ng-switch="player.quicktime">
<embed ng-src="{{player.src}}" width="{{video.full.width}}" height="{{video.full.height}}" autoplay="true" CONTROLLER="TRUE" SHOWCONTROLS="TRUE" controller="true" loop="false" pluginspace="http://www.apple.com/quicktime/" ng-if="player.quicktime"/> <embed ng-switch-when="true" ng-src="{{video.url}}" width="{{video.full.width}}" height="{{video.full.height}}" autoplay="true" CONTROLLER="TRUE" SHOWCONTROLS="TRUE" controller="true" loop="false" pluginspace="http://www.apple.com/quicktime/" />
<video width="{{video.full.width}}" height="{{video.full.height}}" controls autoplay ng-if="!player.quicktime"> <video ng-switch-default width="{{video.full.width}}" height="{{video.full.height}}" controls autoplay >
<source ng-src="{{player.src}}" type="video/mp4"> <source ng-src="{{video.url}}" type="video/mp4">
</video> </video>
</div> </div>
<div class="video_full_error_wrap" ng-if="error"> <div class="video_full_error_wrap" ng-if="error">

6
app/partials/desktop/message.html

@ -51,9 +51,9 @@
<div ng-if="::historyMessage.media ? true : false" class="im_message_media" ng-switch="historyMessage.media._"> <div ng-if="::historyMessage.media ? true : false" class="im_message_media" ng-switch="historyMessage.media._">
<div ng-switch-when="messageMediaPhoto" my-message-photo></div> <div ng-switch-when="messageMediaPhoto" my-message-photo></div>
<div ng-switch-when="messageMediaVideo" my-message-video></div> <div ng-switch-when="messageMediaVideo" my-message-video="historyMessage.media.video" message-id="historyMessage.id"></div>
<div ng-switch-when="messageMediaDocument" my-message-document></div> <div ng-switch-when="messageMediaDocument" my-message-document="historyMessage.media.document" message-id="historyMessage.id"></div>
<div ng-switch-when="messageMediaAudio" my-message-audio></div> <div ng-switch-when="messageMediaAudio" class="im_message_audio" my-audio-player audio="historyMessage.media.audio"></div>
<div ng-switch-when="messageMediaGeo" my-message-map></div> <div ng-switch-when="messageMediaGeo" my-message-map></div>
<div ng-switch-when="messageMediaContact" my-message-contact></div> <div ng-switch-when="messageMediaContact" my-message-contact></div>
<div ng-switch-when="messageMediaPending" my-message-pending></div> <div ng-switch-when="messageMediaPending" my-message-pending></div>

3
app/partials/desktop/message_attach_audio.html

@ -1,3 +0,0 @@
<div class="im_message_audio">
<div my-audio-player audio="historyMessage.media.audio"></div>
</div>

37
app/partials/desktop/message_attach_document.html

@ -1,39 +1,42 @@
<div ng-switch="::historyMessage.media.document.isSpecial"> <div ng-switch="::document.isSpecial">
<div ng-switch-when="gif" my-load-gif document="historyMessage.media.document"></div> <div ng-switch-when="gif" my-load-gif document="document"></div>
<div ng-switch-when="audio" class="im_message_audio"> <div ng-switch-when="audio" class="im_message_audio">
<div my-audio-player audio="historyMessage.media.document"></div> <div my-audio-player audio="document"></div>
</div> </div>
<div ng-switch-default class="im_message_document" ng-class="{im_message_document_thumbed: !!historyMessage.media.document.thumb, im_message_document_progress: historyMessage.media.document.progress.enabled}"> <div ng-switch-default class="im_message_document clearfix" ng-class="{im_message_document_thumbed: !!document.thumb, im_message_document_progress: document.progress.enabled}">
<a href="" ng-click="downloadDoc(historyMessage.media.document.id, historyMessage.media.document.withPreview)" ng-class="{im_message_document_link_disabled: historyMessage.media.document.progress.enabled}"> <a ng-if="::!document.thumb" class="im_message_file_button" ng-click="docOpen()" ng-class="{im_message_file_button_dl_doc: document.downloaded}">
<i class="icon icon-document" ng-if="::!historyMessage.media.document.thumb"></i> <i class="im_message_file_button_icon"></i>
<div class="im_message_document_thumb_wrap" ng-if="::historyMessage.media.document.thumb"> </a>
<a ng-if="::document.thumb" ng-click="docOpen()">
<div class="im_message_document_thumb_wrap">
<img <img
class="im_message_document_thumb" class="im_message_document_thumb"
my-load-thumb my-load-thumb
thumb="historyMessage.media.document.thumb" thumb="document.thumb"
/> />
</div> </div>
</a> </a>
<div class="im_message_document_info"> <div class="im_message_document_info">
<div class="im_message_document_name_wrap"> <div class="im_message_document_name_wrap">
<span class="im_message_document_name" ng-bind="::historyMessage.media.document.file_name"></span> <span class="im_message_document_name" ng-bind="::document.file_name"></span>
<span class="im_message_document_size" ng-if="!historyMessage.media.document.progress.enabled" ng-bind="::historyMessage.media.document.size | formatSize"></span> <span class="im_message_document_size" ng-if="!document.progress.enabled" ng-bind="::document.size | formatSize"></span>
<span class="im_message_document_size" ng-if="historyMessage.media.document.progress.enabled" ng-bind="historyMessage.media.document.progress | formatSizeProgress"></span> <span class="im_message_document_size" ng-if="document.progress.enabled" ng-bind="document.progress | formatSizeProgress"></span>
</div> </div>
<div class="im_message_document_actions" ng-if="!historyMessage.media.document.progress.enabled"> <div class="im_message_document_actions" ng-if="!document.progress.enabled" ng-switch="document.downloaded">
<a href="" ng-click="downloadDoc(historyMessage.media.document.id)" my-i18n="message_attach_document_download"></a> <a ng-switch-when="true" href="" ng-click="docSave()" my-i18n="message_attach_document_save"></a>
<a href="" ng-click="downloadDoc(historyMessage.media.document.id, 1)" ng-if="::historyMessage.media.document.withPreview" my-i18n="message_attach_document_open"></a> <a ng-switch-default href="" ng-click="docSave()" my-i18n="message_attach_document_download"></a>
<a ng-if="::document.withPreview" href="" ng-click="docOpen()" my-i18n="message_attach_document_open"></a>
</div> </div>
<div class="clearfix cancelable_progress_wrap" ng-if="historyMessage.media.document.progress.enabled"> <div class="clearfix im_message_cancelable_progress_wrap" ng-if="document.progress.enabled">
<a class="im_message_media_progress_cancel pull-right" ng-click="historyMessage.media.document.progress.cancel()" my-i18n="modal_cancel"></a> <a class="im_message_media_progress_cancel pull-right" ng-click="document.progress.cancel()" my-i18n="modal_cancel"></a>
<div class="im_message_download_progress_wrap"> <div class="im_message_download_progress_wrap">
<div class="progress tg_down_progress"> <div class="progress tg_down_progress">
<div class="progress-bar progress-bar-success" ng-style="{width: historyMessage.media.document.progress.percent + '%'}"></div> <div class="progress-bar progress-bar-success" ng-style="{width: document.progress.percent + '%'}"></div>
</div> </div>
</div> </div>
</div> </div>

8
app/partials/desktop/message_attach_pending.html

@ -1,14 +1,16 @@
<div class="im_message_document im_message_upload_file" ng-class="::'im_message_upload_' + historyMessage.media.type"> <div class="im_message_document im_message_upload_file" ng-class="::'im_message_upload_' + historyMessage.media.type">
<i class="icon" ng-class="::'icon-' + historyMessage.media.type"></i> <div class="im_message_file_button im_message_file_button_upload">
<i class="im_message_file_button_icon"></i>
</div>
<div class="im_message_document_info"> <div class="im_message_document_info">
<div class="im_message_document_name_wrap"> <div class="im_message_document_name_wrap">
<span class="im_message_document_name" ng-bind="::historyMessage.media.file_name"></span> <span class="im_message_document_name" ng-bind="::historyMessage.media.file_name"></span>
<span class="im_message_document_size" ng-if="historyMessage.media.progress" ng-bind="historyMessage.media.progress | formatSizeProgress"></span> <span class="im_message_document_size" ng-if="historyMessage.media.progress" ng-bind="historyMessage.media.progress | formatSizeProgress"></span>
</div> </div>
<div class="clearfix cancelable_progress_wrap"> <div class="clearfix im_message_cancelable_progress_wrap">
<a class="im_message_media_progress_cancel pull-right" ng-click="historyMessage.media.progress.cancel()" my-i18n="modal_cancel"></a> <a class="im_message_media_progress_cancel pull-right" ng-click="historyMessage.media.progress.cancel()" my-i18n="modal_cancel"></a>
<div class="im_message_download_progress_wrap"> <div class="im_message_download_progress_wrap">
<div class="progress tg_down_progress"> <div class="progress tg_up_progress">
<div class="progress-bar progress-bar-success" role="progressbar" ng-style="{width: historyMessage.media.progress.percent + '%'}"></div> <div class="progress-bar progress-bar-success" role="progressbar" ng-style="{width: historyMessage.media.progress.percent + '%'}"></div>
</div> </div>
</div> </div>

25
app/partials/desktop/message_attach_video.html

@ -1,29 +1,32 @@
<div class="im_message_video im_message_document_thumbed"> <div class="im_message_video im_message_document_thumbed">
<a class="im_message_video_thumb" href="" ng-click="openVideo(historyMessage.media.video.id, historyMessage.id)" ng-style="::{width: historyMessage.media.video.thumb.width + 'px'}"> <a class="im_message_video_thumb" href="" ng-click="videoOpen()" ng-style="::{width: video.thumb.width + 'px'}">
<span class="im_message_video_duration" ng-bind="::historyMessage.media.video.duration | duration"></span> <span class="im_message_video_duration" ng-bind="::video.duration | duration"></span>
<i class="icon icon-videoplay"></i> <i class="icon icon-videoplay"></i>
<img <img
class="im_message_video_thumb" class="im_message_video_thumb"
my-load-thumb my-load-thumb
thumb="historyMessage.media.video.thumb" thumb="video.thumb"
/> />
</a> </a>
<div class="im_message_document_info"> <div class="im_message_document_info">
<div class="im_message_document_name_wrap"> <div class="im_message_document_name_wrap">
<span class="im_message_document_name" my-i18n="message_attach_video_video"></span> <span class="im_message_document_name" my-i18n="message_attach_video_video"></span>
<span class="im_message_document_size" ng-if="!historyMessage.media.video.progress.enabled" ng-bind="::historyMessage.media.video.size | formatSize"></span> <span class="im_message_document_size" ng-if="!video.progress.enabled" ng-bind="::video.size | formatSize"></span>
<span class="im_message_document_size" ng-if="historyMessage.media.video.progress.enabled" ng-bind="historyMessage.media.video.progress | formatSizeProgress"></span> <span class="im_message_document_size" ng-if="video.progress.enabled" ng-bind="video.progress | formatSizeProgress"></span>
</div> </div>
<div class="im_message_document_actions" ng-if="!historyMessage.media.video.progress.enabled"> <div class="im_message_document_actions" ng-if="!video.progress.enabled">
<a href="" ng-click="downloadVideo(historyMessage.media.video.id)" my-i18n="message_attach_video_download"></a> <a href="" ng-click="videoSave()" ng-switch="video.downloaded">
<a href="" ng-click="openVideo(historyMessage.media.video.id, historyMessage.id)" my-i18n="message_attach_video_play"></a> <span ng-switch-when="true" my-i18n="message_attach_video_save"></span>
<span ng-switch-default my-i18n="message_attach_video_download"></span>
</a>
<a href="" ng-click="videoOpen()" my-i18n="message_attach_video_play"></a>
</div> </div>
<div class="clearfix cancelable_progress_wrap" ng-if="historyMessage.media.video.progress.enabled"> <div class="clearfix im_message_cancelable_progress_wrap" ng-if="video.progress.enabled">
<a class="im_message_media_progress_cancel pull-right" ng-click="historyMessage.media.video.progress.cancel()" my-i18n="modal_cancel"></a> <a class="im_message_media_progress_cancel pull-right" ng-click="video.progress.cancel()" my-i18n="modal_cancel"></a>
<div class="im_message_download_progress_wrap"> <div class="im_message_download_progress_wrap">
<div class="progress tg_down_progress"> <div class="progress tg_down_progress">
<div class="progress-bar progress-bar-success" ng-style="{width: historyMessage.media.video.progress.percent + '%'}"></div> <div class="progress-bar progress-bar-success" ng-style="{width: video.progress.percent + '%'}"></div>
</div> </div>
</div> </div>
</div> </div>

2
app/partials/desktop/photo_modal.html

@ -12,7 +12,7 @@
</div> </div>
<p class="media_modal_info"> <p class="media_modal_info">
<span class="media_modal_author" ng-bind-html="::photo.fromUser.rFullName"></span>, <span ng-bind="photo.date | dateOrTime"></span> <a class="media_modal_author" my-user-link="photo.user_id"></a>, <span ng-bind="photo.date | dateOrTime"></span>
</p> </p>
</div> </div>

2
app/partials/desktop/settings_modal.html

@ -97,7 +97,7 @@
<i class="icon-volume-inner icon-volume-inner3"></i> <i class="icon-volume-inner icon-volume-inner3"></i>
<i class="icon-volume-inner icon-volume-inner4"></i> <i class="icon-volume-inner icon-volume-inner4"></i>
</span> </span>
<input type="range" class="tg_range" ng-model="notify.volume" min="1" max="10"> <div class="settings_volume_slider" my-slider slider-model="notify.volume"></div>
</div> </div>
</div> </div>

6
app/partials/desktop/slider.html

@ -0,0 +1,6 @@
<div class="tg_slider_wrap">
<div class="tg_slider_thumb"></div>
<div class="tg_slider_track">
<div class="tg_slider_track_fill"></div>
</div>
</div>

2
app/partials/desktop/video_modal.html

@ -12,7 +12,7 @@
</div> </div>
<p class="media_modal_info"> <p class="media_modal_info">
<span class="media_modal_author" ng-bind-html="video.fromUser.rFullName"></span>, <span ng-bind="video.date | dateOrTime"></span> <a class="media_modal_author" my-user-link="video.user_id"></a>, <span ng-bind="video.date | dateOrTime"></span>
</p> </p>
</div> </div>

6
app/partials/mobile/full_gif.html

@ -1,6 +1,6 @@
<a class="img_gif_with_progress_wrap" ng-click="toggle()"> <a class="img_gif_with_progress_wrap" ng-click="toggle($event)">
<div class="img_gif_image_wrap" ng-switch="document.url &amp;&amp; isActive"> <div class="img_gif_image_wrap" ng-switch="document.downloaded &amp;&amp; isActive">
<img ng-switch-when="true" class="img_gif_image" ng-src="{{document.url}}" /> <img ng-switch-when="true" class="img_gif_image" ng-src="{{document.url}}" />
<img ng-switch-default class="img_gif_thumb" my-load-thumb thumb="document.thumb" /> <img ng-switch-default class="img_gif_thumb" my-load-thumb thumb="document.thumb" />
@ -17,7 +17,7 @@
<div ng-switch-default class="img_gif_info_wrap"> <div ng-switch-default class="img_gif_info_wrap">
<div class="img_gif_label pull-left">GIF</div> <div class="img_gif_label pull-left">GIF</div>
<div ng-if="!document.url" class="img_gif_size pull-right" ng-bind="::document.size | formatSize"></div> <div ng-if="!document.downloaded" class="img_gif_size pull-right" ng-bind="::document.size | formatSize"></div>
</div> </div>
</div> </div>

14
app/partials/mobile/full_video.html

@ -1,22 +1,22 @@
<div class="img_fullsize_with_progress_wrap" ng-style="{width: video.full.width + 'px', height: video.full.height + 'px'}"> <div class="img_fullsize_with_progress_wrap" ng-style="{width: video.full.width + 'px', height: video.full.height + 'px'}">
<div class="img_fullsize_progress_overlay" ng-show="progress.enabled"> <div class="img_fullsize_progress_overlay" ng-show="video.progress.enabled">
<div class="img_fullsize_progress_wrap" ng-style="{width: video.full.width + 'px', height: video.full.height + 'px'}"> <div class="img_fullsize_progress_wrap" ng-style="{width: video.full.width + 'px', height: video.full.height + 'px'}">
<div class="img_fullsize_progress progress tg_progress"> <div class="img_fullsize_progress progress tg_progress">
<div class="progress-bar progress-bar-success" style="width: {{progress.percent}}%"></div> <div class="progress-bar progress-bar-success" style="width: {{video.progress.percent}}%"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="img_fullsize_wrap" ng-if="!player.src"> <div class="img_fullsize_wrap" ng-if="!video.url">
<img <img
class="img_fullsize" class="img_fullsize"
my-load-thumb my-load-thumb
thumb="video.fullThumb" thumb="video.fullThumb"
/> />
</div> </div>
<div class="video_full_player" ng-if="player.src"> <div class="video_full_player" ng-if="video.url" ng-switch="player.quicktime">
<embed ng-src="{{player.src}}" width="{{video.full.width}}" height="{{video.full.height}}" autoplay="true" CONTROLLER="TRUE" SHOWCONTROLS="TRUE" controller="true" loop="false" pluginspace="http://www.apple.com/quicktime/" ng-if="player.quicktime"/> <embed ng-switch-when="true" ng-src="{{video.url}}" width="{{video.full.width}}" height="{{video.full.height}}" autoplay="true" CONTROLLER="TRUE" SHOWCONTROLS="TRUE" controller="true" loop="false" pluginspace="http://www.apple.com/quicktime/" />
<video width="{{video.full.width}}" height="{{video.full.height}}" controls autoplay ng-if="!player.quicktime"> <video ng-switch-default width="{{video.full.width}}" height="{{video.full.height}}" controls autoplay >
<source ng-src="{{player.src}}" type="video/mp4"> <source ng-src="{{video.url}}" type="video/mp4">
</video> </video>
</div> </div>
<div class="video_full_error_wrap" ng-if="error"> <div class="video_full_error_wrap" ng-if="error">

11
app/partials/mobile/import_contact_modal.html

@ -28,7 +28,7 @@
<div class="modal-body"> <div class="modal-body">
<form class="modal_simple_form" ng-submit="doImport()" my-vertical-position="0.3"> <form class="modal_simple_form" ng-submit="doImport()" my-vertical-position="0.2">
<div class="form-group"> <div class="form-group">
<input class="form-control input-lg" my-focused type="text" placeholder="{{'contact_import_modal_phone' | i18n}}" ng-model="importContact.phone"/> <input class="form-control input-lg" my-focused type="text" placeholder="{{'contact_import_modal_phone' | i18n}}" ng-model="importContact.phone"/>
@ -40,12 +40,11 @@
<input class="form-control input-lg" type="text" placeholder="{{'contact_edit_modal_last_name' | i18n}}" ng-model="importContact.last_name"/> <input class="form-control input-lg" type="text" placeholder="{{'contact_edit_modal_last_name' | i18n}}" ng-model="importContact.last_name"/>
</div> </div>
</form> <div class="import_modal_phonebook_wrap" ng-if="phonebookAvailable">
</div>
<div class="modal-footer" ng-if="phonebookAvailable">
<a class="btn btn-link" ng-click="importPhonebook()" my-i18n="contact_import_modal_phonebook"></a> <a class="btn btn-link" ng-click="importPhonebook()" my-i18n="contact_import_modal_phonebook"></a>
</div> </div>
</form>
</div>
</div> </div>

6
app/partials/mobile/message.html

@ -41,9 +41,9 @@
<div ng-if="::historyMessage.media || false" class="im_message_media" ng-switch="historyMessage.media._"> <div ng-if="::historyMessage.media || false" class="im_message_media" ng-switch="historyMessage.media._">
<div ng-switch-when="messageMediaPhoto" my-message-photo></div> <div ng-switch-when="messageMediaPhoto" my-message-photo></div>
<div ng-switch-when="messageMediaVideo" my-message-video></div> <div ng-switch-when="messageMediaVideo" my-message-video="historyMessage.media.video" message-id="historyMessage.id"></div>
<div ng-switch-when="messageMediaDocument" my-message-document></div> <div ng-switch-when="messageMediaDocument" my-message-document="historyMessage.media.document" message-id="historyMessage.id"></div>
<div ng-switch-when="messageMediaAudio" my-message-audio></div> <div ng-switch-when="messageMediaAudio" class="im_message_audio" my-audio-player audio="historyMessage.media.audio"></div>
<div ng-switch-when="messageMediaGeo" my-message-map></div> <div ng-switch-when="messageMediaGeo" my-message-map></div>
<div ng-switch-when="messageMediaContact" my-message-contact></div> <div ng-switch-when="messageMediaContact" my-message-contact></div>
<div ng-switch-when="messageMediaPending" my-message-pending></div> <div ng-switch-when="messageMediaPending" my-message-pending></div>

3
app/partials/mobile/message_attach_audio.html

@ -1,3 +0,0 @@
<div class="im_message_audio">
<div my-audio-player audio="historyMessage.media.audio"></div>
</div>

37
app/partials/mobile/message_attach_document.html

@ -1,39 +1,42 @@
<div ng-switch="::historyMessage.media.document.isSpecial"> <div ng-switch="::document.isSpecial">
<div ng-switch-when="gif" my-load-gif document="historyMessage.media.document"></div> <div ng-switch-when="gif" my-load-gif document="document"></div>
<div ng-switch-when="audio" class="im_message_audio"> <div ng-switch-when="audio" class="im_message_audio">
<div my-audio-player audio="historyMessage.media.document"></div> <div my-audio-player audio="document"></div>
</div> </div>
<div ng-switch-default class="im_message_document" ng-class="{im_message_document_thumbed: !!historyMessage.media.document.thumb, im_message_document_progress: historyMessage.media.document.progress.enabled}"> <div ng-switch-default class="im_message_document clearfix" ng-class="{im_message_document_thumbed: !!document.thumb, im_message_document_progress: document.progress.enabled}">
<a href="" ng-click="downloadDoc(historyMessage.media.document.id, historyMessage.media.document.withPreview)" ng-class="{im_message_document_link_disabled: historyMessage.media.document.progress.enabled}"> <a ng-if="::!document.thumb" class="im_message_file_button" ng-click="docOpen()" ng-class="{im_message_file_button_dl_doc: document.downloaded}">
<i class="icon icon-document" ng-if="::!historyMessage.media.document.thumb"></i> <i class="im_message_file_button_icon"></i>
<div class="im_message_document_thumb_wrap" ng-if="::historyMessage.media.document.thumb"> </a>
<a ng-if="::document.thumb" ng-click="docOpen()">
<div class="im_message_document_thumb_wrap">
<img <img
class="im_message_document_thumb" class="im_message_document_thumb"
my-load-thumb my-load-thumb
thumb="historyMessage.media.document.thumb" thumb="document.thumb"
/> />
</div> </div>
</a> </a>
<div class="im_message_document_info"> <div class="im_message_document_info">
<div class="im_message_document_name_wrap"> <div class="im_message_document_name_wrap">
<span class="im_message_document_name" ng-bind="::historyMessage.media.document.file_name"></span> <span class="im_message_document_name" ng-bind="::document.file_name"></span>
<span class="im_message_document_size" ng-if="!historyMessage.media.document.progress.enabled" ng-bind="::historyMessage.media.document.size | formatSize"></span> <span class="im_message_document_size" ng-if="!document.progress.enabled" ng-bind="::document.size | formatSize"></span>
<span class="im_message_document_size" ng-if="historyMessage.media.document.progress.enabled" ng-bind="historyMessage.media.document.progress | formatSizeProgress"></span> <span class="im_message_document_size" ng-if="document.progress.enabled" ng-bind="document.progress | formatSizeProgress"></span>
</div> </div>
<div class="im_message_document_actions" ng-if="!historyMessage.media.document.progress.enabled"> <div class="im_message_document_actions" ng-if="!document.progress.enabled" ng-switch="document.downloaded">
<a href="" ng-click="downloadDoc(historyMessage.media.document.id)" my-i18n="message_attach_document_download"></a> <a ng-switch-when="true" href="" ng-click="docSave()" my-i18n="message_attach_document_save"></a>
<a href="" ng-click="downloadDoc(historyMessage.media.document.id, 1)" ng-if="::historyMessage.media.document.withPreview" my-i18n="message_attach_document_open"></a> <a ng-switch-default href="" ng-click="docSave()" my-i18n="message_attach_document_download"></a>
<a ng-if="::document.withPreview" href="" ng-click="docOpen()" my-i18n="message_attach_document_open"></a>
</div> </div>
<div class="clearfix cancelable_progress_wrap" ng-if="historyMessage.media.document.progress.enabled"> <div class="clearfix im_message_cancelable_progress_wrap" ng-if="document.progress.enabled">
<a class="im_message_media_progress_cancel pull-right" ng-click="historyMessage.media.document.progress.cancel()" my-i18n="modal_cancel"></a> <a class="im_message_media_progress_cancel pull-right" ng-click="document.progress.cancel()" my-i18n="modal_cancel"></a>
<div class="im_message_download_progress_wrap"> <div class="im_message_download_progress_wrap">
<div class="progress tg_down_progress"> <div class="progress tg_down_progress">
<div class="progress-bar progress-bar-success" ng-style="{width: historyMessage.media.document.progress.percent + '%'}"></div> <div class="progress-bar progress-bar-success" ng-style="{width: document.progress.percent + '%'}"></div>
</div> </div>
</div> </div>
</div> </div>

2
app/partials/mobile/message_attach_pending.html

@ -5,7 +5,7 @@
<span class="im_message_document_name" ng-bind="::historyMessage.media.file_name"></span> <span class="im_message_document_name" ng-bind="::historyMessage.media.file_name"></span>
<span class="im_message_document_size" ng-if="historyMessage.media.progress" ng-bind="historyMessage.media.progress | formatSizeProgress"></span> <span class="im_message_document_size" ng-if="historyMessage.media.progress" ng-bind="historyMessage.media.progress | formatSizeProgress"></span>
</div> </div>
<div class="clearfix cancelable_progress_wrap"> <div class="clearfix im_message_cancelable_progress_wrap">
<a class="im_message_media_progress_cancel pull-right" ng-click="historyMessage.media.progress.cancel()" my-i18n="modal_cancel"></a> <a class="im_message_media_progress_cancel pull-right" ng-click="historyMessage.media.progress.cancel()" my-i18n="modal_cancel"></a>
<div class="im_message_download_progress_wrap"> <div class="im_message_download_progress_wrap">
<div class="progress tg_down_progress"> <div class="progress tg_down_progress">

6
app/partials/mobile/message_attach_video.html

@ -1,11 +1,11 @@
<div class="im_message_video im_message_document_thumbed"> <div class="im_message_video im_message_document_thumbed">
<a class="im_message_video_thumb" href="" ng-click="openVideo(historyMessage.media.video.id, historyMessage.id)" ng-style="::{width: historyMessage.media.video.thumb.width + 'px'}"> <a class="im_message_video_thumb" href="" ng-click="videoOpen()" ng-style="::{width: video.thumb.width + 'px'}">
<span class="im_message_video_duration" ng-bind="::historyMessage.media.video.duration | duration"></span> <span class="im_message_video_duration" ng-bind="::video.duration | duration"></span>
<i class="icon icon-videoplay"></i> <i class="icon icon-videoplay"></i>
<img <img
class="im_message_video_thumb" class="im_message_video_thumb"
my-load-thumb my-load-thumb
thumb="historyMessage.media.video.thumb" thumb="video.thumb"
/> />
</a> </a>
</div> </div>

2
app/partials/mobile/photo_modal.html

@ -12,7 +12,7 @@
</div> </div>
<p class="media_modal_info"> <p class="media_modal_info">
<span class="media_modal_author" ng-bind-html="::photo.fromUser.rFullName"></span>, <span ng-bind="photo.date | dateOrTime"></span> <a class="media_modal_author" my-user-link="photo.user_id"></a>, <span ng-bind="photo.date | dateOrTime"></span>
</p> </p>
</div> </div>

1
app/partials/mobile/slider.html

@ -0,0 +1 @@
../desktop/slider.html

2
app/partials/mobile/video_modal.html

@ -12,7 +12,7 @@
</div> </div>
<p class="media_modal_info"> <p class="media_modal_info">
<span class="media_modal_author" ng-bind-html="video.fromUser.rFullName"></span>, <span ng-bind="video.date | dateOrTime"></span> <a class="media_modal_author" my-user-link="video.user_id"></a>, <span ng-bind="video.date | dateOrTime"></span>
</p> </p>
</div> </div>

392
app/vendor/rusha/rusha.js vendored

@ -0,0 +1,392 @@
/*
* Rusha, a JavaScript implementation of the Secure Hash Algorithm, SHA-1,
* as defined in FIPS PUB 180-1, tuned for high performance with large inputs.
* (http://github.com/srijs/rusha)
*
* Inspired by Paul Johnstons implementation (http://pajhome.org.uk/crypt/md5).
*
* Copyright (c) 2013 Sam Rijs (http://awesam.de).
* Released under the terms of the MIT license as follows:
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
(function (global) {
// If we'e running in Node.JS, export a module.
if (typeof module !== 'undefined') {
module.exports = Rusha;
}
// If we're running in a DOM context, export
// the Rusha object to toplevel.
if (typeof global !== 'undefined') {
global.Rusha = Rusha;
}
// If we're running in a webworker, accept
// messages containing a jobid and a buffer
// or blob object, and return the hash result.
if (typeof FileReaderSync !== 'undefined') {
var reader = new FileReaderSync(), hasher = new Rusha(4 * 1024 * 1024);
self.onmessage = function onMessage(event) {
var hash, data = event.data.data;
if (data instanceof Blob) {
try {
data = reader.readAsBinaryString(data);
} catch (e) {
self.postMessage({
id: event.data.id,
error: e.name
});
return;
}
}
hash = hasher.digest(data);
self.postMessage({
id: event.data.id,
hash: hash
});
};
}
var util = {
getDataType: function (data) {
if (typeof data === 'string') {
return 'string';
}
if (data instanceof Array) {
return 'array';
}
if (typeof global !== 'undefined' && global.Buffer && global.Buffer.isBuffer(data)) {
return 'buffer';
}
if (data instanceof ArrayBuffer) {
return 'arraybuffer';
}
if (data.buffer instanceof ArrayBuffer) {
return 'view';
}
throw new Error('Unsupported data type.');
}
};
// The Rusha object is a wrapper around the low-level RushaCore.
// It provides means of converting different inputs to the
// format accepted by RushaCore as well as other utility methods.
function Rusha(chunkSize) {
'use strict';
// Private object structure.
var self$2 = { fill: 0 };
// Calculate the length of buffer that the sha1 routine uses
// including the padding.
var padlen = function (len) {
for (len += 9; len % 64 > 0; len += 1);
return len;
};
var padZeroes = function (bin, len) {
for (var i = len >> 2; i < bin.length; i++)
bin[i] = 0;
};
var padData = function (bin, chunkLen, msgLen) {
bin[chunkLen >> 2] |= 128 << 24 - (chunkLen % 4 << 3);
bin[((chunkLen >> 2) + 2 & ~15) + 15] = msgLen << 3;
};
// Convert a binary string and write it to the heap.
// A binary string is expected to only contain char codes < 256.
var convStr = function (H8, H32, start, len, off) {
var str = this, i, om = off % 4, lm = len % 4, j = len - lm;
if (j > 0) {
switch (om) {
case 0:
H8[off + 3 | 0] = str.charCodeAt(start);
case 1:
H8[off + 2 | 0] = str.charCodeAt(start + 1);
case 2:
H8[off + 1 | 0] = str.charCodeAt(start + 2);
case 3:
H8[off | 0] = str.charCodeAt(start + 3);
}
}
for (i = om; i < j; i = i + 4 | 0) {
H32[off + i >> 2] = str.charCodeAt(start + i) << 24 | str.charCodeAt(start + i + 1) << 16 | str.charCodeAt(start + i + 2) << 8 | str.charCodeAt(start + i + 3);
}
switch (lm) {
case 3:
H8[off + j + 1 | 0] = str.charCodeAt(start + j + 2);
case 2:
H8[off + j + 2 | 0] = str.charCodeAt(start + j + 1);
case 1:
H8[off + j + 3 | 0] = str.charCodeAt(start + j);
}
};
// Convert a buffer or array and write it to the heap.
// The buffer or array is expected to only contain elements < 256.
var convBuf = function (H8, H32, start, len, off) {
var buf = this, i, om = off % 4, lm = len % 4, j = len - lm;
if (j > 0) {
switch (om) {
case 0:
H8[off + 3 | 0] = buf[start];
case 1:
H8[off + 2 | 0] = buf[start + 1];
case 2:
H8[off + 1 | 0] = buf[start + 2];
case 3:
H8[off | 0] = buf[start + 3];
}
}
for (i = 4 - om; i < j; i = i += 4 | 0) {
H32[off + i >> 2] = buf[start + i] << 24 | buf[start + i + 1] << 16 | buf[start + i + 2] << 8 | buf[start + i + 3];
}
switch (lm) {
case 3:
H8[off + j + 1 | 0] = buf[start + j + 2];
case 2:
H8[off + j + 2 | 0] = buf[start + j + 1];
case 1:
H8[off + j + 3 | 0] = buf[start + j];
}
};
var convFn = function (data) {
switch (util.getDataType(data)) {
case 'string':
return convStr.bind(data);
case 'array':
return convBuf.bind(data);
case 'buffer':
return convBuf.bind(data);
case 'arraybuffer':
return convBuf.bind(new Uint8Array(data));
case 'view':
return convBuf.bind(new Uint8Array(data.buffer));
}
};
var slice = function (data, offset) {
switch (util.getDataType(data)) {
case 'string':
return data.slice(offset);
case 'array':
return data.slice(offset);
case 'buffer':
return data.slice(offset);
case 'arraybuffer':
return data.slice(offset);
case 'view':
return data.buffer.slice(offset);
}
};
// Convert an ArrayBuffer into its hexadecimal string representation.
var hex = function (arrayBuffer) {
var i, x, hex_tab = '0123456789abcdef', res = [], binarray = new Uint8Array(arrayBuffer);
for (i = 0; i < binarray.length; i++) {
x = binarray[i];
res[i] = hex_tab.charAt(x >> 4 & 15) + hex_tab.charAt(x >> 0 & 15);
}
return res.join('');
};
var ceilHeapSize = function (v) {
// The asm.js spec says:
// The heap object's byteLength must be either
// 2^n for n in [12, 24) or 2^24 * n for n ≥ 1.
// Also, byteLengths smaller than 2^16 are deprecated.
var p;
// If v is smaller than 2^16, the smallest possible solution
// is 2^16.
if (v <= 65536)
return 65536;
// If v < 2^24, we round up to 2^n,
// otherwise we round up to 2^24 * n.
if (v < 16777216) {
for (p = 1; p < v; p = p << 1);
} else {
for (p = 16777216; p < v; p += 16777216);
}
return p;
};
// Initialize the internal data structures to a new capacity.
var init = function (size) {
if (size % 64 > 0) {
throw new Error('Chunk size must be a multiple of 128 bit');
}
self$2.maxChunkLen = size;
self$2.padMaxChunkLen = padlen(size);
// The size of the heap is the sum of:
// 1. The padded input message size
// 2. The extended space the algorithm needs (320 byte)
// 3. The 160 bit state the algoritm uses
self$2.heap = new ArrayBuffer(ceilHeapSize(self$2.padMaxChunkLen + 320 + 20));
self$2.h32 = new Int32Array(self$2.heap);
self$2.h8 = new Int8Array(self$2.heap);
self$2.core = RushaCore({
Int32Array: Int32Array,
DataView: DataView
}, {}, self$2.heap);
self$2.buffer = null;
};
// Iinitializethe datastructures according
// to a chunk siyze.
init(chunkSize || 64 * 1024);
var initState = function (heap, padMsgLen) {
var io = new Int32Array(heap, padMsgLen + 320, 5);
io[0] = 1732584193;
io[1] = -271733879;
io[2] = -1732584194;
io[3] = 271733878;
io[4] = -1009589776;
};
var padChunk = function (chunkLen, msgLen) {
var padChunkLen = padlen(chunkLen);
var view = new Int32Array(self$2.heap, 0, padChunkLen >> 2);
padZeroes(view, chunkLen);
padData(view, chunkLen, msgLen);
return padChunkLen;
};
// Write data to the heap.
var write = function (data, chunkOffset, chunkLen) {
convFn(data)(self$2.h8, self$2.h32, chunkOffset, chunkLen, 0);
};
// Initialize and call the RushaCore,
// assuming an input buffer of length len * 4.
var coreCall = function (data, chunkOffset, chunkLen, msgLen, finalize) {
var padChunkLen = chunkLen;
if (finalize) {
padChunkLen = padChunk(chunkLen, msgLen);
}
write(data, chunkOffset, chunkLen);
self$2.core.hash(padChunkLen, self$2.padMaxChunkLen);
};
var getRawDigest = function (heap, padMaxChunkLen) {
var io = new Int32Array(heap, padMaxChunkLen + 320, 5);
var out = new Int32Array(5);
var arr = new DataView(out.buffer);
arr.setInt32(0, io[0], false);
arr.setInt32(4, io[1], false);
arr.setInt32(8, io[2], false);
arr.setInt32(12, io[3], false);
arr.setInt32(16, io[4], false);
return out;
};
// Calculate the hash digest as an array of 5 32bit integers.
var rawDigest = this.rawDigest = function (str) {
var msgLen = str.byteLength || str.length;
initState(self$2.heap, self$2.padMaxChunkLen);
var chunkOffset = 0, chunkLen = self$2.maxChunkLen, last;
for (chunkOffset = 0; msgLen > chunkOffset + chunkLen; chunkOffset += chunkLen) {
coreCall(str, chunkOffset, chunkLen, msgLen, false);
}
coreCall(str, chunkOffset, msgLen - chunkOffset, msgLen, true);
return getRawDigest(self$2.heap, self$2.padMaxChunkLen);
};
// The digest and digestFrom* interface returns the hash digest
// as a hex string.
this.digest = this.digestFromString = this.digestFromBuffer = this.digestFromArrayBuffer = function (str) {
return hex(rawDigest(str).buffer);
};
}
;
// The low-level RushCore module provides the heart of Rusha,
// a high-speed sha1 implementation working on an Int32Array heap.
// At first glance, the implementation seems complicated, however
// with the SHA1 spec at hand, it is obvious this almost a textbook
// implementation that has a few functions hand-inlined and a few loops
// hand-unrolled.
function RushaCore(stdlib, foreign, heap) {
'use asm';
var H = new stdlib.Int32Array(heap);
function hash(k, x) {
// k in bytes
k = k | 0;
x = x | 0;
var i = 0, j = 0, y0 = 0, z0 = 0, y1 = 0, z1 = 0, y2 = 0, z2 = 0, y3 = 0, z3 = 0, y4 = 0, z4 = 0, t0 = 0, t1 = 0;
y0 = H[x + 320 >> 2] | 0;
y1 = H[x + 324 >> 2] | 0;
y2 = H[x + 328 >> 2] | 0;
y3 = H[x + 332 >> 2] | 0;
y4 = H[x + 336 >> 2] | 0;
for (i = 0; (i | 0) < (k | 0); i = i + 64 | 0) {
z0 = y0;
z1 = y1;
z2 = y2;
z3 = y3;
z4 = y4;
for (j = 0; (j | 0) < 64; j = j + 4 | 0) {
t1 = H[i + j >> 2] | 0;
t0 = ((y0 << 5 | y0 >>> 27) + (y1 & y2 | ~y1 & y3) | 0) + ((t1 + y4 | 0) + 1518500249 | 0) | 0;
y4 = y3;
y3 = y2;
y2 = y1 << 30 | y1 >>> 2;
y1 = y0;
y0 = t0;
;
H[k + j >> 2] = t1;
}
for (j = k + 64 | 0; (j | 0) < (k + 80 | 0); j = j + 4 | 0) {
t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31;
t0 = ((y0 << 5 | y0 >>> 27) + (y1 & y2 | ~y1 & y3) | 0) + ((t1 + y4 | 0) + 1518500249 | 0) | 0;
y4 = y3;
y3 = y2;
y2 = y1 << 30 | y1 >>> 2;
y1 = y0;
y0 = t0;
;
H[j >> 2] = t1;
}
for (j = k + 80 | 0; (j | 0) < (k + 160 | 0); j = j + 4 | 0) {
t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31;
t0 = ((y0 << 5 | y0 >>> 27) + (y1 ^ y2 ^ y3) | 0) + ((t1 + y4 | 0) + 1859775393 | 0) | 0;
y4 = y3;
y3 = y2;
y2 = y1 << 30 | y1 >>> 2;
y1 = y0;
y0 = t0;
;
H[j >> 2] = t1;
}
for (j = k + 160 | 0; (j | 0) < (k + 240 | 0); j = j + 4 | 0) {
t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31;
t0 = ((y0 << 5 | y0 >>> 27) + (y1 & y2 | y1 & y3 | y2 & y3) | 0) + ((t1 + y4 | 0) - 1894007588 | 0) | 0;
y4 = y3;
y3 = y2;
y2 = y1 << 30 | y1 >>> 2;
y1 = y0;
y0 = t0;
;
H[j >> 2] = t1;
}
for (j = k + 240 | 0; (j | 0) < (k + 320 | 0); j = j + 4 | 0) {
t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31;
t0 = ((y0 << 5 | y0 >>> 27) + (y1 ^ y2 ^ y3) | 0) + ((t1 + y4 | 0) - 899497514 | 0) | 0;
y4 = y3;
y3 = y2;
y2 = y1 << 30 | y1 >>> 2;
y1 = y0;
y0 = t0;
;
H[j >> 2] = t1;
}
y0 = y0 + z0 | 0;
y1 = y1 + z1 | 0;
y2 = y2 + z2 | 0;
y3 = y3 + z3 | 0;
y4 = y4 + z4 | 0;
}
H[x + 320 >> 2] = y0;
H[x + 324 >> 2] = y1;
H[x + 328 >> 2] = y2;
H[x + 332 >> 2] = y3;
H[x + 336 >> 2] = y4;
}
return { hash: hash };
}
}(this));

2
app/webogram.appcache

@ -1,6 +1,6 @@
CACHE MANIFEST CACHE MANIFEST
# 39 # 41
NETWORK: NETWORK:
* *

2
package.json

@ -1,7 +1,7 @@
{ {
"name": "Telegram", "name": "Telegram",
"description": "Telegram Web App.\nMore info & source code here: https://github.com/zhukov/webogram", "description": "Telegram Web App.\nMore info & source code here: https://github.com/zhukov/webogram",
"version": "0.3.2", "version": "0.3.3",
"main": "app/index.html", "main": "app/index.html",
"single-instance": true, "single-instance": true,
"dom_storage_quota": 40, "dom_storage_quota": 40,

4
server.js

@ -88,7 +88,9 @@ StaticServlet.MimeMap = {
'png': 'image/png', 'png': 'image/png',
  'svg': 'image/svg+xml',   'svg': 'image/svg+xml',
  'wav': 'audio/wav',   'wav': 'audio/wav',
'ico': 'image/vnd.microsoft.icon' 'ico': 'image/vnd.microsoft.icon',
'pexe': 'application/x-pnacl',
'bc': 'application/x-pnacl'
}; };
StaticServlet.prototype.handleRequest = function(req, res) { StaticServlet.prototype.handleRequest = function(req, res) {

Loading…
Cancel
Save