New media permission, show dialog when not enabled for voice msg
UI now in separate renderer: - the permissions popup - settings dialog - debug log dialog - about window Couple bug fixes: - About Window: Fix 'escape' to close window - Remove outdated dist/copy tasks from Gruntfile Eslintified settings_view.js
This commit is contained in:
		
					parent
					
						
							
								9d9a797bda
							
						
					
				
			
			
				commit
				
					
						ad4387803b
					
				
			
		
					 33 changed files with 1192 additions and 383 deletions
				
			
		| 
						 | 
				
			
			@ -40,6 +40,7 @@ ts/**/*.js
 | 
			
		|||
!js/expiring_messages.js
 | 
			
		||||
!js/views/attachment_view.js
 | 
			
		||||
!js/views/backbone_wrapper_view.js
 | 
			
		||||
!js/views/clear_data_view.js
 | 
			
		||||
!js/views/conversation_search_view.js
 | 
			
		||||
!js/views/conversation_view.js
 | 
			
		||||
!js/views/debug_log_view.js
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -105,12 +105,15 @@ module.exports = function(grunt) {
 | 
			
		|||
        '!js/expiring_messages.js',
 | 
			
		||||
        '!js/modules/**/*.js',
 | 
			
		||||
        '!js/Mp3LameEncoder.min.js',
 | 
			
		||||
        '!js/settings_start.js',
 | 
			
		||||
        '!js/signal_protocol_store.js',
 | 
			
		||||
        '!js/views/clear_data_view.js',
 | 
			
		||||
        '!js/views/conversation_search_view.js',
 | 
			
		||||
        '!js/views/conversation_view.js',
 | 
			
		||||
        '!js/views/debug_log_view.js',
 | 
			
		||||
        '!js/views/file_input_view.js',
 | 
			
		||||
        '!js/views/message_view.js',
 | 
			
		||||
        '!js/views/settings_view.js',
 | 
			
		||||
        '!js/models/conversations.js',
 | 
			
		||||
        '!js/models/messages.js',
 | 
			
		||||
        '!js/WebAudioRecorderMp3.js',
 | 
			
		||||
| 
						 | 
				
			
			@ -134,10 +137,6 @@ module.exports = function(grunt) {
 | 
			
		|||
      },
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
      dist: {
 | 
			
		||||
        files: ['<%= dist.src %>', '<%= dist.res %>'],
 | 
			
		||||
        tasks: ['copy_dist'],
 | 
			
		||||
      },
 | 
			
		||||
      libtextsecure: {
 | 
			
		||||
        files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'],
 | 
			
		||||
        tasks: ['concat:libtextsecure'],
 | 
			
		||||
| 
						 | 
				
			
			@ -461,7 +460,6 @@ module.exports = function(grunt) {
 | 
			
		|||
  grunt.registerTask('dev', ['default', 'watch']);
 | 
			
		||||
  grunt.registerTask('lint', ['jshint']);
 | 
			
		||||
  grunt.registerTask('test', ['unit-tests', 'lib-unit-tests']);
 | 
			
		||||
  grunt.registerTask('copy_dist', ['gitinfo', 'copy:res', 'copy:src']);
 | 
			
		||||
  grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
 | 
			
		||||
  grunt.registerTask('default', [
 | 
			
		||||
    'exec:build-protobuf',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -514,6 +514,10 @@
 | 
			
		|||
      "message": "Report an Issue",
 | 
			
		||||
      "description": "Item under the Help menu, takes you to GitHub new issue form (title case)"
 | 
			
		||||
    },
 | 
			
		||||
    "signalDesktopPreferences": {
 | 
			
		||||
      "message": "Signal Desktop Preferences",
 | 
			
		||||
      "description": "Title of the window that pops up with Signal Desktop preferences in it"
 | 
			
		||||
    },
 | 
			
		||||
    "aboutSignalDesktop": {
 | 
			
		||||
      "message": "About Signal Desktop",
 | 
			
		||||
      "description": "Item under the Help menu, which opens a small about window"
 | 
			
		||||
| 
						 | 
				
			
			@ -610,6 +614,18 @@
 | 
			
		|||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "audioPermissionNeeded": {
 | 
			
		||||
      "message": "To send audio messages, allow Signal Desktop to access your microphone.",
 | 
			
		||||
      "description": "Shown if the user attempts to send an audio message without audio permssions turned on"
 | 
			
		||||
    },
 | 
			
		||||
    "allowAccess": {
 | 
			
		||||
      "message": "Allow Access",
 | 
			
		||||
      "description": "Button shown in popup asking to enable microphon/video permissions to send audio messages"
 | 
			
		||||
    },
 | 
			
		||||
    "showSettings": {
 | 
			
		||||
      "message": "Show Settings",
 | 
			
		||||
      "description": "A button shown in dialog requesting the user to turn on audio permissions"
 | 
			
		||||
    },
 | 
			
		||||
    "audio": {
 | 
			
		||||
      "message": "Audio",
 | 
			
		||||
      "description": "Shown in a quotation of a message containing an audio attachment if no text was originally provided with that attachment"
 | 
			
		||||
| 
						 | 
				
			
			@ -795,6 +811,14 @@
 | 
			
		|||
        "message": "Theme",
 | 
			
		||||
        "description": "Header for theme settings"
 | 
			
		||||
    },
 | 
			
		||||
    "permissions": {
 | 
			
		||||
      "message": "Permissions",
 | 
			
		||||
      "description": "Header for permissions section of settings"
 | 
			
		||||
    },
 | 
			
		||||
    "mediaPermissionsDescription": {
 | 
			
		||||
      "message": "Allow access to camera and microphone",
 | 
			
		||||
      "description": "Description of the media permission description"
 | 
			
		||||
    },
 | 
			
		||||
    "clearDataHeader": {
 | 
			
		||||
        "message": "Clear Data",
 | 
			
		||||
        "description": "Header in the settings dialog for the section dealing with data deletion"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										82
									
								
								about.html
									
										
									
									
									
								
							
							
						
						
									
										82
									
								
								about.html
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,68 +1,50 @@
 | 
			
		|||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
 | 
			
		||||
<style>
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
  <meta http-equiv="Content-Security-Policy"
 | 
			
		||||
    content="default-src 'none';
 | 
			
		||||
            child-src 'self';
 | 
			
		||||
            connect-src 'self' https: wss:;
 | 
			
		||||
            font-src 'self';
 | 
			
		||||
            form-action 'self';
 | 
			
		||||
            frame-src 'none';
 | 
			
		||||
            img-src 'self' blob: data:;
 | 
			
		||||
            media-src 'self' blob:;
 | 
			
		||||
            object-src 'none';
 | 
			
		||||
            script-src 'self';
 | 
			
		||||
            style-src 'self' 'unsafe-inline';"
 | 
			
		||||
  >
 | 
			
		||||
  <link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
 | 
			
		||||
  <style>
 | 
			
		||||
    body {
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      background-color: #2090EA;
 | 
			
		||||
      color: white;
 | 
			
		||||
      font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
img {
 | 
			
		||||
    img {
 | 
			
		||||
      margin-top: 1em;
 | 
			
		||||
}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
a {
 | 
			
		||||
    a {
 | 
			
		||||
      color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
    }
 | 
			
		||||
  </style>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
  <img src='images/icon_250.png'>
 | 
			
		||||
 | 
			
		||||
<img src='images/icon_250.png'>
 | 
			
		||||
 | 
			
		||||
<div>
 | 
			
		||||
<script>
 | 
			
		||||
  document.write('v', window.getVersion());
 | 
			
		||||
</script>
 | 
			
		||||
</div>
 | 
			
		||||
<div>
 | 
			
		||||
<script>
 | 
			
		||||
  const states = [];
 | 
			
		||||
 | 
			
		||||
  if (window.getEnvironment() !== 'production') {
 | 
			
		||||
    states.push(window.getEnvironment());
 | 
			
		||||
  }
 | 
			
		||||
  if (window.getAppInstance()) {
 | 
			
		||||
    states.push(window.getAppInstance());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  document.write(states.join(' - '));
 | 
			
		||||
</script>
 | 
			
		||||
</div>
 | 
			
		||||
<div>
 | 
			
		||||
  <div class='version'></div>
 | 
			
		||||
  <div class='environment'></div>
 | 
			
		||||
  <div>
 | 
			
		||||
    <a href="https://signal.org">signal.org</a>
 | 
			
		||||
</div>
 | 
			
		||||
<br>
 | 
			
		||||
<div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <br>
 | 
			
		||||
  <div>
 | 
			
		||||
    <a class="privacy" href="https://signal.org/legal">Terms & Privacy Policy</a>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script type='text/javascript' src='node_modules/jquery/dist/jquery.js'></script>
 | 
			
		||||
<script>
 | 
			
		||||
$(document).on('keyup', function(e) {
 | 
			
		||||
  if (e.keyCode === 27) {
 | 
			
		||||
    window.closeAbout();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$('.privacy').text(window.i18n('privacyPolicy'));
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <script type='text/javascript' src='node_modules/jquery/dist/jquery.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/about_start.js'></script>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,8 @@ window.getEnvironment = () => config.environment;
 | 
			
		|||
window.getVersion = () => config.version;
 | 
			
		||||
window.getAppInstance = () => config.appInstance;
 | 
			
		||||
 | 
			
		||||
window.closeAbout = () => ipc.send('close-about');
 | 
			
		||||
window.closeAbout = () => ipcRenderer.send('close-about');
 | 
			
		||||
 | 
			
		||||
window.i18n = i18n.setup(locale, localeMessages);
 | 
			
		||||
 | 
			
		||||
require('./js/logging');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,9 +4,11 @@
 | 
			
		|||
const PERMISSIONS = {
 | 
			
		||||
  // Allowed
 | 
			
		||||
  fullscreen: true, // required to show videos in full-screen
 | 
			
		||||
  media: true, // required for access to microphone, used for voice notes
 | 
			
		||||
  notifications: true, // required to show OS notifications for new messages
 | 
			
		||||
 | 
			
		||||
  // Off by default, can be enabled by user
 | 
			
		||||
  media: false, // required for access to microphone, used for voice notes
 | 
			
		||||
 | 
			
		||||
  // Not allowed
 | 
			
		||||
  geolocation: false,
 | 
			
		||||
  midiSysex: false,
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +16,13 @@ const PERMISSIONS = {
 | 
			
		|||
  pointerLock: false,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function _permissionHandler(webContents, permission, callback) {
 | 
			
		||||
function _createPermissionHandler(userConfig) {
 | 
			
		||||
  return (webContents, permission, callback) => {
 | 
			
		||||
    // We default 'media' permission to false, but the user can override that
 | 
			
		||||
    if (permission === 'media' && userConfig.get('mediaPermissions')) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (PERMISSIONS[permission]) {
 | 
			
		||||
      console.log(`Approving request for permission '${permission}'`);
 | 
			
		||||
      return callback(true);
 | 
			
		||||
| 
						 | 
				
			
			@ -22,10 +30,18 @@ function _permissionHandler(webContents, permission, callback) {
 | 
			
		|||
 | 
			
		||||
    console.log(`Denying request for permission '${permission}'`);
 | 
			
		||||
    return callback(false);
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function installPermissionsHandler({ session }) {
 | 
			
		||||
  session.defaultSession.setPermissionRequestHandler(_permissionHandler);
 | 
			
		||||
function installPermissionsHandler({ session, userConfig }) {
 | 
			
		||||
  // Setting the permission request handler to null first forces any permissions to be
 | 
			
		||||
  //   requested again. Without this, revoked permissions might still be available if
 | 
			
		||||
  //   they've already been used successfully.
 | 
			
		||||
  session.defaultSession.setPermissionRequestHandler(null);
 | 
			
		||||
 | 
			
		||||
  session.defaultSession.setPermissionRequestHandler(
 | 
			
		||||
    _createPermissionHandler(userConfig)
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										111
									
								
								background.html
									
										
									
									
									
								
							
							
						
						
									
										111
									
								
								background.html
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -519,114 +519,6 @@
 | 
			
		|||
      {{ learnMore }}
 | 
			
		||||
    </a>
 | 
			
		||||
  </script>
 | 
			
		||||
  <script type='text/x-tmpl-mustache' id='debug-log'>
 | 
			
		||||
    <div class='content'>
 | 
			
		||||
      <div>
 | 
			
		||||
        <a class='x close' alt='close debug log' href='#'></a>
 | 
			
		||||
        <h1> {{ title }} </h1>
 | 
			
		||||
        <p> {{ debugLogExplanation }}</p>
 | 
			
		||||
      </div>
 | 
			
		||||
      <textarea spellcheck='false' rows='5'></textarea>
 | 
			
		||||
      <div class='buttons'>
 | 
			
		||||
        <button class='grey submit'>{{ submit }}</button>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class='result'>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </script>
 | 
			
		||||
  <script type='text/x-tmpl-mustache' id='debug-log-link'>
 | 
			
		||||
    <div class='input-group clearfix'>
 | 
			
		||||
      <input type='text' class='link' readonly value='{{ url }}' />
 | 
			
		||||
      <a class='open' alt='open in a new browser tab' target='_blank' href='{{ url }}'></a>
 | 
			
		||||
    </div>
 | 
			
		||||
    <p>
 | 
			
		||||
      <a class='report-link' target='_blank'
 | 
			
		||||
          href='https://github.com/signalapp/Signal-Desktop/issues/new/'>
 | 
			
		||||
          {{ reportIssue }}
 | 
			
		||||
      </a>
 | 
			
		||||
    </p>
 | 
			
		||||
  </script>
 | 
			
		||||
  <script type='text/x-tmpl-mustache' id='settings'>
 | 
			
		||||
    <div class='content'>
 | 
			
		||||
      <a class='x close' alt='close settings' href='#'></a>
 | 
			
		||||
      <h2>{{ settings }}</h2>
 | 
			
		||||
      <div class='device-name-settings'>
 | 
			
		||||
        <b>{{ deviceNameLabel }}:</b> {{ deviceName }}
 | 
			
		||||
      </div>
 | 
			
		||||
      <hr>
 | 
			
		||||
      <div class='theme-settings'>
 | 
			
		||||
        <h3>{{ theme }}</h3>
 | 
			
		||||
        <div>
 | 
			
		||||
          <input type='radio' name='theme' id='theme-setting-android' value='android'>
 | 
			
		||||
          <label for='theme-setting-android'>Android</label>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
          <input type='radio' name='theme' id='theme-setting-android-dark' value='android-dark'>
 | 
			
		||||
          <label for='theme-setting-android-dark'>{{ themeAndroidDark }}</label>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
          <input type='radio' name='theme' id='theme-setting-ios'  value='ios'/>
 | 
			
		||||
          <label for='theme-setting-ios'>iOS</label>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <br />
 | 
			
		||||
      <div class='menu-bar-setting'>
 | 
			
		||||
        <input type='checkbox' name='hide-menu-bar' id='hide-menu-bar'/>
 | 
			
		||||
        <label for='hide-menu-bar'>{{ hideMenuBar }}</label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <hr>
 | 
			
		||||
      <div class='notification-settings'>
 | 
			
		||||
        <h3>{{ notifications }}</h3>
 | 
			
		||||
        <p>{{ notificationSettingsDialog }}</p>
 | 
			
		||||
        <div>
 | 
			
		||||
          <input type='radio' name='notifications' id='notification-setting-message' value='message'>
 | 
			
		||||
          <label for='notification-setting-message'>{{ nameAndMessage }} </label>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
          <input type='radio' name='notifications' id='notification-setting-name'  value='name'/>
 | 
			
		||||
          <label for='notification-setting-name'>{{ nameOnly }} </label>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
          <input type='radio' name='notifications' id='notification-setting-count' value='count'/>
 | 
			
		||||
          <label for='notification-setting-count'>{{ noNameOrMessage }} </label>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
          <input type='radio' name='notifications' id='notification-setting-off' value='off'/>
 | 
			
		||||
          <label for='notification-setting-off'>{{ disableNotifications }} </label>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <br />
 | 
			
		||||
      {{ #isAudioNotificationSupported }}
 | 
			
		||||
      <div class='audio-notification-setting'>
 | 
			
		||||
        <input type='checkbox' name='audio-notification' id='audio-notification'/>
 | 
			
		||||
        <label for='audio-notification'>{{ audioNotificationDescription }}</label>
 | 
			
		||||
      </div>
 | 
			
		||||
      {{ /isAudioNotificationSupported }}
 | 
			
		||||
      <div class='sync-setting'></div>
 | 
			
		||||
      <hr>
 | 
			
		||||
      <div class='clear-data-settings'>
 | 
			
		||||
        <h3>{{ clearDataHeader }}</h3>
 | 
			
		||||
        <div>
 | 
			
		||||
          <button class='grey destructive clear-data'>{{ clearDataButton }}</button>
 | 
			
		||||
          <p>{{ clearDataExplanation }}</p>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </script>
 | 
			
		||||
  <script type='text/x-tmpl-mustache' id='syncSettings'>
 | 
			
		||||
      <hr>
 | 
			
		||||
      <h3>{{ sync }}</h3>
 | 
			
		||||
      <div>
 | 
			
		||||
        <button class='grey sync'>{{ syncNow }}</button>
 | 
			
		||||
        <p>
 | 
			
		||||
          {{ syncExplanation }}
 | 
			
		||||
          <div class='synced_at'>
 | 
			
		||||
              {{ lastSynced }} {{ syncDate }} {{ syncTime }}
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class='sync_failed'>{{ syncFailed }}</div>
 | 
			
		||||
        </p>
 | 
			
		||||
      </div>
 | 
			
		||||
  </script>
 | 
			
		||||
  <script type='text/x-tmpl-mustache' id='clear-data'>
 | 
			
		||||
    {{#isStep1}}
 | 
			
		||||
    <div class='step'>
 | 
			
		||||
| 
						 | 
				
			
			@ -904,7 +796,6 @@
 | 
			
		|||
  <script type='text/javascript' src='js/views/whisper_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/views/last_seen_indicator_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/views/scroll_down_button_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/views/debug_log_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/views/toast_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/views/attachment_preview_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/views/file_input_view.js'></script>
 | 
			
		||||
| 
						 | 
				
			
			@ -929,7 +820,6 @@
 | 
			
		|||
  <script type='text/javascript' src='js/views/network_status_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/views/confirmation_dialog_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/views/identicon_svg_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/views/settings_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/views/install_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/views/banner_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/views/identity_key_send_error_view.js'></script>
 | 
			
		||||
| 
						 | 
				
			
			@ -937,6 +827,7 @@
 | 
			
		|||
  <script type='text/javascript' src='js/views/standalone_registration_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/views/app_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/views/import_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/views/clear_data_view.js'></script>
 | 
			
		||||
 | 
			
		||||
  <script type='text/javascript' src='js/wall_clock_listener.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='js/rotate_signed_prekey_listener.js'></script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										53
									
								
								debug_log.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								debug_log.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
  <meta http-equiv="Content-Security-Policy"
 | 
			
		||||
    content="default-src 'none';
 | 
			
		||||
            child-src 'self';
 | 
			
		||||
            connect-src 'self' https: wss:;
 | 
			
		||||
            font-src 'self';
 | 
			
		||||
            form-action 'self';
 | 
			
		||||
            frame-src 'none';
 | 
			
		||||
            img-src 'self' blob: data:;
 | 
			
		||||
            media-src 'self' blob:;
 | 
			
		||||
            object-src 'none';
 | 
			
		||||
            script-src 'self';
 | 
			
		||||
            style-src 'self' 'unsafe-inline';"
 | 
			
		||||
  >
 | 
			
		||||
  <link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
 | 
			
		||||
<style>
 | 
			
		||||
</style>
 | 
			
		||||
</head>
 | 
			
		||||
<body class='debug-log-window'>
 | 
			
		||||
</body>
 | 
			
		||||
<script type='text/x-tmpl-mustache' id='debug-log'>
 | 
			
		||||
  <div class='content'>
 | 
			
		||||
    <div>
 | 
			
		||||
      <a class='x close' alt='close debug log' href='#'></a>
 | 
			
		||||
      <h1> {{ title }} </h1>
 | 
			
		||||
      <p> {{ debugLogExplanation }}</p>
 | 
			
		||||
    </div>
 | 
			
		||||
    <textarea spellcheck='false' rows='5'></textarea>
 | 
			
		||||
    <div class='buttons'>
 | 
			
		||||
      <button class='grey submit'>{{ submit }}</button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='result'>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</script>
 | 
			
		||||
<script type='text/x-tmpl-mustache' id='debug-log-link'>
 | 
			
		||||
  <div class='input-group clearfix'>
 | 
			
		||||
    <input type='text' class='link' readonly value='{{ url }}' />
 | 
			
		||||
    <a class='open' alt='open in a new browser tab' target='_blank' href='{{ url }}'></a>
 | 
			
		||||
  </div>
 | 
			
		||||
  <p>
 | 
			
		||||
    <a class='report-link' target='_blank'
 | 
			
		||||
        href='https://github.com/signalapp/Signal-Desktop/issues/new/'>
 | 
			
		||||
        {{ reportIssue }}
 | 
			
		||||
    </a>
 | 
			
		||||
  </p>
 | 
			
		||||
</script>
 | 
			
		||||
<script type='text/javascript' src='js/components.js'></script>
 | 
			
		||||
<script type='text/javascript' src='js/views/whisper_view.js'></script>
 | 
			
		||||
<script type='text/javascript' src='js/views/debug_log_view.js'></script>
 | 
			
		||||
<script type='text/javascript' src='js/debug_log_start.js'></script>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										19
									
								
								debug_log_preload.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								debug_log_preload.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
const { ipcRenderer } = require('electron');
 | 
			
		||||
const url = require('url');
 | 
			
		||||
const i18n = require('./js/modules/i18n');
 | 
			
		||||
 | 
			
		||||
const config = url.parse(window.location.toString(), true).query;
 | 
			
		||||
const { locale } = config;
 | 
			
		||||
const localeMessages = ipcRenderer.sendSync('locale-data');
 | 
			
		||||
 | 
			
		||||
window.i18n = i18n.setup(locale, localeMessages);
 | 
			
		||||
 | 
			
		||||
// got.js appears to need this to successfully submit debug logs to the cloud
 | 
			
		||||
window.nodeSetImmediate = setImmediate;
 | 
			
		||||
 | 
			
		||||
window.getNodeVersion = () => config.node_version;
 | 
			
		||||
window.getEnvironment = () => config.environment;
 | 
			
		||||
 | 
			
		||||
require('./js/logging');
 | 
			
		||||
 | 
			
		||||
window.closeDebugLog = () => ipcRenderer.send('close-debug-log');
 | 
			
		||||
							
								
								
									
										24
									
								
								js/about_start.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								js/about_start.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
// Add version
 | 
			
		||||
$('.version').text(`v${window.getVersion()}`);
 | 
			
		||||
 | 
			
		||||
// Add debugging metadata - environment if not production, app instance name
 | 
			
		||||
const states = [];
 | 
			
		||||
 | 
			
		||||
if (window.getEnvironment() !== 'production') {
 | 
			
		||||
  states.push(window.getEnvironment());
 | 
			
		||||
}
 | 
			
		||||
if (window.getAppInstance()) {
 | 
			
		||||
  states.push(window.getAppInstance());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$('.environment').text(states.join(' - '));
 | 
			
		||||
 | 
			
		||||
// Install the 'dismiss with escape key' handler
 | 
			
		||||
$(document).on('keyup', function(e) {
 | 
			
		||||
  if (e.keyCode === 27) {
 | 
			
		||||
    window.closeAbout();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Localize the privacy string
 | 
			
		||||
$('.privacy').text(window.i18n('privacyPolicy'));
 | 
			
		||||
| 
						 | 
				
			
			@ -123,6 +123,54 @@
 | 
			
		|||
    }
 | 
			
		||||
    first = false;
 | 
			
		||||
 | 
			
		||||
    // These make key operations available to IPC handlers created in preload.js
 | 
			
		||||
    window.Events = {
 | 
			
		||||
      getDeviceName: () => textsecure.storage.user.getDeviceName(),
 | 
			
		||||
 | 
			
		||||
      getThemeSetting: () => storage.get('theme-setting'),
 | 
			
		||||
      setThemeSetting: value => {
 | 
			
		||||
        storage.put('theme-setting', value);
 | 
			
		||||
        onChangeTheme();
 | 
			
		||||
      },
 | 
			
		||||
      getHideMenuBar: () => storage.get('hide-menu-bar'),
 | 
			
		||||
      setHideMenuBar: value => {
 | 
			
		||||
        storage.get('hide-menu-bar', value);
 | 
			
		||||
        window.setAutoHideMenuBar(value);
 | 
			
		||||
        window.setMenuBarVisibility(!value);
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      getNotificationSetting: () =>
 | 
			
		||||
        storage.get('notification-setting', 'message'),
 | 
			
		||||
      setNotificationSetting: value =>
 | 
			
		||||
        storage.get('notification-setting', value),
 | 
			
		||||
      getAudioNotification: () => storage.get('audio-notification'),
 | 
			
		||||
      setAudioNotification: value => storage.put('audio-notification', value),
 | 
			
		||||
 | 
			
		||||
      // eslint-disable-next-line eqeqeq
 | 
			
		||||
      isPrimary: () => textsecure.storage.user.getDeviceId() == '1',
 | 
			
		||||
      getSyncRequest: () =>
 | 
			
		||||
        new Promise((resolve, reject) => {
 | 
			
		||||
          const syncRequest = window.getSyncRequest();
 | 
			
		||||
          syncRequest.addEventListener('success', resolve);
 | 
			
		||||
          syncRequest.addEventListener('timeout', reject);
 | 
			
		||||
        }),
 | 
			
		||||
      getLastSyncTime: () => storage.get('synced_at'),
 | 
			
		||||
      setLastSyncTime: value => storage.put('synced_at', value),
 | 
			
		||||
 | 
			
		||||
      addDarkOverlay: () => {
 | 
			
		||||
        if ($('.dark-overlay').length) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        $(document.body).prepend('<div class="dark-overlay"></div>');
 | 
			
		||||
        $('.dark-overlay').on('click', () => $('.dark-overlay').remove());
 | 
			
		||||
      },
 | 
			
		||||
      removeDarkOverlay: () => $('.dark-overlay').remove(),
 | 
			
		||||
      deleteAllData: () => {
 | 
			
		||||
        const clearDataView = new window.Whisper.ClearDataView().render();
 | 
			
		||||
        $('body').append(clearDataView.el);
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      await ConversationController.load();
 | 
			
		||||
    } finally {
 | 
			
		||||
| 
						 | 
				
			
			@ -208,16 +256,6 @@
 | 
			
		|||
    Whisper.events.on('showDebugLog', () => {
 | 
			
		||||
      appView.openDebugLog();
 | 
			
		||||
    });
 | 
			
		||||
    Whisper.events.on('showSettings', () => {
 | 
			
		||||
      if (!appView || !appView.inboxView) {
 | 
			
		||||
        console.log(
 | 
			
		||||
          "background: Event: 'showSettings':" +
 | 
			
		||||
            ' Expected `appView.inboxView` to exist.'
 | 
			
		||||
        );
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      appView.inboxView.showSettings();
 | 
			
		||||
    });
 | 
			
		||||
    Whisper.events.on('unauthorized', () => {
 | 
			
		||||
      appView.inboxView.networkStatusView.update();
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										13
									
								
								js/debug_log_start.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								js/debug_log_start.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
$(document).on('keyup', function(e) {
 | 
			
		||||
  if (e.keyCode === 27) {
 | 
			
		||||
    window.closeDebugLog();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const $body = $(document.body);
 | 
			
		||||
 | 
			
		||||
// got.js appears to need this to successfully submit debug logs to the cloud
 | 
			
		||||
window.setImmediate = window.nodeSetImmediate;
 | 
			
		||||
 | 
			
		||||
window.view = new Whisper.DebugLogView();
 | 
			
		||||
window.view.$el.appendTo($body);
 | 
			
		||||
| 
						 | 
				
			
			@ -55,32 +55,23 @@ const Initialization = require('./views/initialization');
 | 
			
		|||
const { IdleDetector } = require('./idle_detector');
 | 
			
		||||
const MessageDataMigrator = require('./messages_data_migrator');
 | 
			
		||||
 | 
			
		||||
exports.setup = (options = {}) => {
 | 
			
		||||
  const { Attachments, userDataPath, getRegionCode } = options;
 | 
			
		||||
 | 
			
		||||
  const Components = {
 | 
			
		||||
    ContactDetail,
 | 
			
		||||
    ContactName,
 | 
			
		||||
    ConversationTitle,
 | 
			
		||||
    EmbeddedContact,
 | 
			
		||||
    Emojify,
 | 
			
		||||
    Lightbox,
 | 
			
		||||
    LightboxGallery,
 | 
			
		||||
    MediaGallery,
 | 
			
		||||
    MessageBody,
 | 
			
		||||
    Types: {
 | 
			
		||||
      Message: MediaGalleryMessage,
 | 
			
		||||
    },
 | 
			
		||||
    Quote,
 | 
			
		||||
  };
 | 
			
		||||
function initializeMigrations({
 | 
			
		||||
  Attachments,
 | 
			
		||||
  userDataPath,
 | 
			
		||||
  Type,
 | 
			
		||||
  getRegionCode,
 | 
			
		||||
}) {
 | 
			
		||||
  if (!Attachments) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const attachmentsPath = Attachments.getPath(userDataPath);
 | 
			
		||||
  const readAttachmentData = Attachments.createReader(attachmentsPath);
 | 
			
		||||
  const loadAttachmentData = AttachmentType.loadData(readAttachmentData);
 | 
			
		||||
  const loadAttachmentData = Type.loadData(readAttachmentData);
 | 
			
		||||
 | 
			
		||||
  const Migrations = {
 | 
			
		||||
  return {
 | 
			
		||||
    attachmentsPath,
 | 
			
		||||
    deleteAttachmentData: AttachmentType.deleteData(
 | 
			
		||||
    deleteAttachmentData: Type.deleteData(
 | 
			
		||||
      Attachments.createDeleter(attachmentsPath)
 | 
			
		||||
    ),
 | 
			
		||||
    getAbsoluteAttachmentPath: Attachments.createAbsolutePathGetter(
 | 
			
		||||
| 
						 | 
				
			
			@ -100,6 +91,33 @@ exports.setup = (options = {}) => {
 | 
			
		|||
      Attachments.createWriterForExisting(attachmentsPath)
 | 
			
		||||
    ),
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.setup = (options = {}) => {
 | 
			
		||||
  const { Attachments, userDataPath, getRegionCode } = options;
 | 
			
		||||
 | 
			
		||||
  const Migrations = initializeMigrations({
 | 
			
		||||
    Attachments,
 | 
			
		||||
    userDataPath,
 | 
			
		||||
    Type: AttachmentType,
 | 
			
		||||
    getRegionCode,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const Components = {
 | 
			
		||||
    ContactDetail,
 | 
			
		||||
    ContactName,
 | 
			
		||||
    ConversationTitle,
 | 
			
		||||
    EmbeddedContact,
 | 
			
		||||
    Emojify,
 | 
			
		||||
    Lightbox,
 | 
			
		||||
    LightboxGallery,
 | 
			
		||||
    MediaGallery,
 | 
			
		||||
    MessageBody,
 | 
			
		||||
    Types: {
 | 
			
		||||
      Message: MediaGalleryMessage,
 | 
			
		||||
    },
 | 
			
		||||
    Quote,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const Types = {
 | 
			
		||||
    Attachment: AttachmentType,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -134,6 +134,8 @@
 | 
			
		|||
          break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      console.log({ title, message, iconUrl });
 | 
			
		||||
 | 
			
		||||
      const shouldHideExpiringMessageBody =
 | 
			
		||||
        last.isExpiringMessage && Signal.OS.isMacOS();
 | 
			
		||||
      if (shouldHideExpiringMessageBody) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										19
									
								
								js/permissions_popup_start.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								js/permissions_popup_start.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
$(document).on('keyup', function(e) {
 | 
			
		||||
  if (e.keyCode === 27) {
 | 
			
		||||
    window.closePermissionsPopup();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const $body = $(document.body);
 | 
			
		||||
 | 
			
		||||
window.view = new Whisper.ConfirmationDialogView({
 | 
			
		||||
  message: i18n('audioPermissionNeeded'),
 | 
			
		||||
  okText: i18n('allowAccess'),
 | 
			
		||||
  resolve: () => {
 | 
			
		||||
    window.setMediaPermissions(true);
 | 
			
		||||
    window.closePermissionsPopup();
 | 
			
		||||
  },
 | 
			
		||||
  reject: window.closePermissionsPopup,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
window.view.$el.appendTo($body);
 | 
			
		||||
							
								
								
									
										29
									
								
								js/settings_start.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								js/settings_start.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
$(document).on('keyup', function(e) {
 | 
			
		||||
  if (e.keyCode === 27) {
 | 
			
		||||
    window.closeSettings();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const $body = $(document.body);
 | 
			
		||||
 | 
			
		||||
const getInitialData = async () => ({
 | 
			
		||||
  deviceName: await window.getDeviceName(),
 | 
			
		||||
 | 
			
		||||
  themeSetting: await window.getThemeSetting(),
 | 
			
		||||
  hideMenuBar: await window.getHideMenuBar(),
 | 
			
		||||
 | 
			
		||||
  notificationSetting: await window.getNotificationSetting(),
 | 
			
		||||
  audioNotification: await window.getAudioNotification(),
 | 
			
		||||
 | 
			
		||||
  mediaPermissions: await window.getMediaPermissions(),
 | 
			
		||||
 | 
			
		||||
  isPrimary: await window.isPrimary(),
 | 
			
		||||
  lastSyncTime: await window.getLastSyncTime(),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
window.initialRequest = getInitialData();
 | 
			
		||||
window.initialRequest.then(data => {
 | 
			
		||||
  window.initialData = data;
 | 
			
		||||
  window.view = new Whisper.SettingsView();
 | 
			
		||||
  window.view.$el.appendTo($body);
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -14,8 +14,6 @@
 | 
			
		|||
    events: {
 | 
			
		||||
      'click .openInstaller': 'openInstaller', // NetworkStatusView has this button
 | 
			
		||||
      openInbox: 'openInbox',
 | 
			
		||||
      'change-theme': 'applyTheme',
 | 
			
		||||
      'change-hide-menu': 'applyHideMenu',
 | 
			
		||||
    },
 | 
			
		||||
    applyTheme: function() {
 | 
			
		||||
      var theme = storage.get('theme-setting') || 'android';
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										69
									
								
								js/views/clear_data_view.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								js/views/clear_data_view.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,69 @@
 | 
			
		|||
/* global i18n: false */
 | 
			
		||||
/* global Whisper: false */
 | 
			
		||||
 | 
			
		||||
/* eslint-disable no-new */
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line func-names
 | 
			
		||||
(function() {
 | 
			
		||||
  'use strict';
 | 
			
		||||
 | 
			
		||||
  window.Whisper = window.Whisper || {};
 | 
			
		||||
  const { Database } = window.Whisper;
 | 
			
		||||
  const { Logs } = window.Signal;
 | 
			
		||||
 | 
			
		||||
  const CLEAR_DATA_STEPS = {
 | 
			
		||||
    CHOICE: 1,
 | 
			
		||||
    DELETING: 2,
 | 
			
		||||
  };
 | 
			
		||||
  window.Whisper.ClearDataView = Whisper.View.extend({
 | 
			
		||||
    templateName: 'clear-data',
 | 
			
		||||
    className: 'full-screen-flow overlay',
 | 
			
		||||
    events: {
 | 
			
		||||
      'click .cancel': 'onCancel',
 | 
			
		||||
      'click .delete-all-data': 'onDeleteAllData',
 | 
			
		||||
    },
 | 
			
		||||
    initialize() {
 | 
			
		||||
      this.step = CLEAR_DATA_STEPS.CHOICE;
 | 
			
		||||
    },
 | 
			
		||||
    onCancel() {
 | 
			
		||||
      this.remove();
 | 
			
		||||
    },
 | 
			
		||||
    async onDeleteAllData() {
 | 
			
		||||
      console.log('Deleting everything!');
 | 
			
		||||
      this.step = CLEAR_DATA_STEPS.DELETING;
 | 
			
		||||
      this.render();
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        await Database.close();
 | 
			
		||||
        console.log('All database connections closed. Starting delete.');
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.log('Something went wrong closing all database connections.');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.clearAllData();
 | 
			
		||||
    },
 | 
			
		||||
    async clearAllData() {
 | 
			
		||||
      try {
 | 
			
		||||
        await Promise.all([Logs.deleteAll(), Database.drop()]);
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.log(
 | 
			
		||||
          'Something went wrong deleting all data:',
 | 
			
		||||
          error && error.stack ? error.stack : error
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      window.restart();
 | 
			
		||||
    },
 | 
			
		||||
    render_attributes() {
 | 
			
		||||
      return {
 | 
			
		||||
        isStep1: this.step === CLEAR_DATA_STEPS.CHOICE,
 | 
			
		||||
        header: i18n('deleteAllDataHeader'),
 | 
			
		||||
        body: i18n('deleteAllDataBody'),
 | 
			
		||||
        cancelButton: i18n('cancel'),
 | 
			
		||||
        deleteButton: i18n('deleteAllDataButton'),
 | 
			
		||||
 | 
			
		||||
        isStep2: this.step === CLEAR_DATA_STEPS.DELETING,
 | 
			
		||||
        deleting: i18n('deleteAllDataProgress'),
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
})();
 | 
			
		||||
| 
						 | 
				
			
			@ -42,9 +42,8 @@
 | 
			
		|||
      close: i18n('gotIt'),
 | 
			
		||||
      debugLogExplanation: i18n('debugLogExplanation'),
 | 
			
		||||
    },
 | 
			
		||||
    close(e) {
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
      this.remove();
 | 
			
		||||
    close() {
 | 
			
		||||
      window.closeDebugLog();
 | 
			
		||||
    },
 | 
			
		||||
    async submit(e) {
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -233,13 +233,6 @@
 | 
			
		|||
    reloadBackgroundPage() {
 | 
			
		||||
      window.location.reload();
 | 
			
		||||
    },
 | 
			
		||||
    showSettings() {
 | 
			
		||||
      if (this.$el.find('.settings').length) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      const view = new Whisper.SettingsView();
 | 
			
		||||
      view.$el.appendTo(this.el);
 | 
			
		||||
    },
 | 
			
		||||
    filterContacts(e) {
 | 
			
		||||
      this.searchView.filterContacts(e);
 | 
			
		||||
      const input = this.$('input.search');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,17 +31,25 @@
 | 
			
		|||
      if (this.recorder.isRecording()) {
 | 
			
		||||
        this.recorder.cancelRecording();
 | 
			
		||||
      }
 | 
			
		||||
      this.recorder = null;
 | 
			
		||||
 | 
			
		||||
      if (this.interval) {
 | 
			
		||||
        clearInterval(this.interval);
 | 
			
		||||
      }
 | 
			
		||||
      this.interval = null;
 | 
			
		||||
 | 
			
		||||
      if (this.source) {
 | 
			
		||||
        this.source.disconnect();
 | 
			
		||||
      }
 | 
			
		||||
      this.source = null;
 | 
			
		||||
 | 
			
		||||
      if (this.context) {
 | 
			
		||||
        this.context.close().then(function() {
 | 
			
		||||
          console.log('audio context closed');
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      this.context = null;
 | 
			
		||||
 | 
			
		||||
      this.remove();
 | 
			
		||||
      this.trigger('closed');
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -74,8 +82,23 @@
 | 
			
		|||
      this.recorder.startRecording();
 | 
			
		||||
    },
 | 
			
		||||
    onError: function(error) {
 | 
			
		||||
      console.log(error.stack);
 | 
			
		||||
      // Protect against out-of-band errors, which can happen if the user revokes media
 | 
			
		||||
      //   permissions after successfully accessing the microphone.
 | 
			
		||||
      if (!this.recorder) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.close();
 | 
			
		||||
 | 
			
		||||
      if (error && error.name === 'PermissionDeniedError') {
 | 
			
		||||
        console.log('RecorderView.onError: Microphone access is not allowed!');
 | 
			
		||||
        window.showPermissionsPopup();
 | 
			
		||||
      } else {
 | 
			
		||||
        console.log(
 | 
			
		||||
          'RecorderView.onError:',
 | 
			
		||||
          error && error.stack ? error.stack : error
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
})();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,106 +1,119 @@
 | 
			
		|||
/* global storage: false */
 | 
			
		||||
/* global textsecure: false */
 | 
			
		||||
/* global i18n: false */
 | 
			
		||||
/* global Whisper: false */
 | 
			
		||||
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
/* eslint-disable no-new */
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line func-names
 | 
			
		||||
(function() {
 | 
			
		||||
  'use strict';
 | 
			
		||||
 | 
			
		||||
  window.Whisper = window.Whisper || {};
 | 
			
		||||
  const { Database } = window.Whisper;
 | 
			
		||||
  const { OS, Logs } = window.Signal;
 | 
			
		||||
  const { Settings } = window.Signal.Types;
 | 
			
		||||
 | 
			
		||||
  var CheckboxView = Whisper.View.extend({
 | 
			
		||||
    initialize: function(options) {
 | 
			
		||||
      this.name = options.name;
 | 
			
		||||
      this.defaultValue = options.defaultValue;
 | 
			
		||||
      this.event = options.event;
 | 
			
		||||
  const CheckboxView = Whisper.View.extend({
 | 
			
		||||
    initialize(options) {
 | 
			
		||||
      this.setFn = options.setFn;
 | 
			
		||||
      this.value = options.value;
 | 
			
		||||
      this.populate();
 | 
			
		||||
    },
 | 
			
		||||
    events: {
 | 
			
		||||
      change: 'change',
 | 
			
		||||
    },
 | 
			
		||||
    change: function(e) {
 | 
			
		||||
      var value = e.target.checked;
 | 
			
		||||
      storage.put(this.name, value);
 | 
			
		||||
    change(e) {
 | 
			
		||||
      const value = e.target.checked;
 | 
			
		||||
      this.setFn(value);
 | 
			
		||||
      console.log(this.name, 'changed to', value);
 | 
			
		||||
      if (this.event) {
 | 
			
		||||
        this.$el.trigger(this.event);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    populate: function() {
 | 
			
		||||
      var value = storage.get(this.name, this.defaultValue);
 | 
			
		||||
      this.$('input').prop('checked', !!value);
 | 
			
		||||
    populate() {
 | 
			
		||||
      this.$('input').prop('checked', !!this.value);
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  var RadioButtonGroupView = Whisper.View.extend({
 | 
			
		||||
    initialize: function(options) {
 | 
			
		||||
      this.name = options.name;
 | 
			
		||||
      this.defaultValue = options.defaultValue;
 | 
			
		||||
      this.event = options.event;
 | 
			
		||||
 | 
			
		||||
  const MediaPermissionsSettingView = Whisper.View.extend({
 | 
			
		||||
    initialize(options) {
 | 
			
		||||
      this.value = options.value;
 | 
			
		||||
      this.setFn = options.setFn;
 | 
			
		||||
      this.populate();
 | 
			
		||||
    },
 | 
			
		||||
    events: {
 | 
			
		||||
      change: 'change',
 | 
			
		||||
    },
 | 
			
		||||
    change: function(e) {
 | 
			
		||||
      var value = this.$(e.target).val();
 | 
			
		||||
      storage.put(this.name, value);
 | 
			
		||||
      console.log(this.name, 'changed to', value);
 | 
			
		||||
      if (this.event) {
 | 
			
		||||
        this.$el.trigger(this.event);
 | 
			
		||||
      }
 | 
			
		||||
    change(e) {
 | 
			
		||||
      this.value = e.target.checked;
 | 
			
		||||
      this.setFn(this.value);
 | 
			
		||||
      console.log('media-permissions changed to', this.value);
 | 
			
		||||
    },
 | 
			
		||||
    populate: function() {
 | 
			
		||||
      var value = storage.get(this.name, this.defaultValue);
 | 
			
		||||
      this.$('#' + this.name + '-' + value).attr('checked', 'checked');
 | 
			
		||||
    populate() {
 | 
			
		||||
      this.$('input').prop('checked', Boolean(this.value));
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const RadioButtonGroupView = Whisper.View.extend({
 | 
			
		||||
    initialize(options) {
 | 
			
		||||
      this.name = options.name;
 | 
			
		||||
      this.setFn = options.setFn;
 | 
			
		||||
      this.value = options.value;
 | 
			
		||||
      this.populate();
 | 
			
		||||
    },
 | 
			
		||||
    events: {
 | 
			
		||||
      change: 'change',
 | 
			
		||||
    },
 | 
			
		||||
    change(e) {
 | 
			
		||||
      const value = this.$(e.target).val();
 | 
			
		||||
      this.setFn(value);
 | 
			
		||||
      console.log(this.name, 'changed to', value);
 | 
			
		||||
    },
 | 
			
		||||
    populate() {
 | 
			
		||||
      this.$(`#${this.name}-${this.value}`).attr('checked', 'checked');
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  Whisper.SettingsView = Whisper.View.extend({
 | 
			
		||||
    className: 'settings modal expand',
 | 
			
		||||
    templateName: 'settings',
 | 
			
		||||
    initialize: function() {
 | 
			
		||||
      this.deviceName = textsecure.storage.user.getDeviceName();
 | 
			
		||||
    initialize() {
 | 
			
		||||
      this.render();
 | 
			
		||||
      new RadioButtonGroupView({
 | 
			
		||||
        el: this.$('.notification-settings'),
 | 
			
		||||
        defaultValue: 'message',
 | 
			
		||||
        name: 'notification-setting',
 | 
			
		||||
        value: window.initialData.notificationSetting,
 | 
			
		||||
        setFn: window.setNotificationSetting,
 | 
			
		||||
      });
 | 
			
		||||
      new RadioButtonGroupView({
 | 
			
		||||
        el: this.$('.theme-settings'),
 | 
			
		||||
        defaultValue: 'android',
 | 
			
		||||
        name: 'theme-setting',
 | 
			
		||||
        event: 'change-theme',
 | 
			
		||||
        value: window.initialData.themeSetting,
 | 
			
		||||
        setFn: window.setThemeSetting,
 | 
			
		||||
      });
 | 
			
		||||
      if (Settings.isAudioNotificationSupported()) {
 | 
			
		||||
        new CheckboxView({
 | 
			
		||||
          el: this.$('.audio-notification-setting'),
 | 
			
		||||
          defaultValue: false,
 | 
			
		||||
          name: 'audio-notification',
 | 
			
		||||
          value: window.initialData.audioNotification,
 | 
			
		||||
          setFn: window.setAudioNotification,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      new CheckboxView({
 | 
			
		||||
        el: this.$('.menu-bar-setting'),
 | 
			
		||||
        defaultValue: false,
 | 
			
		||||
        name: 'hide-menu-bar',
 | 
			
		||||
        event: 'change-hide-menu',
 | 
			
		||||
        value: window.initialData.hideMenuBar,
 | 
			
		||||
        setFn: window.setHideMenuBar,
 | 
			
		||||
      });
 | 
			
		||||
      if (textsecure.storage.user.getDeviceId() != '1') {
 | 
			
		||||
        var syncView = new SyncView().render();
 | 
			
		||||
      new MediaPermissionsSettingView({
 | 
			
		||||
        el: this.$('.media-permissions'),
 | 
			
		||||
        value: window.initialData.mediaPermissions,
 | 
			
		||||
        setFn: window.setMediaPermissions,
 | 
			
		||||
      });
 | 
			
		||||
      if (!window.initialData.isPrimary) {
 | 
			
		||||
        const syncView = new SyncView().render();
 | 
			
		||||
        this.$('.sync-setting').append(syncView.el);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    events: {
 | 
			
		||||
      'click .close': 'remove',
 | 
			
		||||
      'click .close': 'onClose',
 | 
			
		||||
      'click .clear-data': 'onClearData',
 | 
			
		||||
    },
 | 
			
		||||
    render_attributes: function() {
 | 
			
		||||
    render_attributes() {
 | 
			
		||||
      return {
 | 
			
		||||
        deviceNameLabel: i18n('deviceName'),
 | 
			
		||||
        deviceName: this.deviceName,
 | 
			
		||||
        deviceName: window.initialData.deviceName,
 | 
			
		||||
        theme: i18n('theme'),
 | 
			
		||||
        notifications: i18n('notifications'),
 | 
			
		||||
        notificationSettingsDialog: i18n('notificationSettingsDialog'),
 | 
			
		||||
| 
						 | 
				
			
			@ -116,121 +129,72 @@
 | 
			
		|||
        clearDataHeader: i18n('clearDataHeader'),
 | 
			
		||||
        clearDataButton: i18n('clearDataButton'),
 | 
			
		||||
        clearDataExplanation: i18n('clearDataExplanation'),
 | 
			
		||||
        permissions: i18n('permissions'),
 | 
			
		||||
        mediaPermissionsDescription: i18n('mediaPermissionsDescription'),
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
    onClearData: function() {
 | 
			
		||||
      var clearDataView = new ClearDataView().render();
 | 
			
		||||
      $('body').append(clearDataView.el);
 | 
			
		||||
    onClose() {
 | 
			
		||||
      window.closeSettings();
 | 
			
		||||
    },
 | 
			
		||||
    onClearData() {
 | 
			
		||||
      window.deleteAllData();
 | 
			
		||||
      window.closeSettings();
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  /* jshint ignore:start */
 | 
			
		||||
  /* eslint-enable */
 | 
			
		||||
 | 
			
		||||
  const CLEAR_DATA_STEPS = {
 | 
			
		||||
    CHOICE: 1,
 | 
			
		||||
    DELETING: 2,
 | 
			
		||||
  };
 | 
			
		||||
  const ClearDataView = Whisper.View.extend({
 | 
			
		||||
    templateName: 'clear-data',
 | 
			
		||||
    className: 'full-screen-flow overlay',
 | 
			
		||||
    events: {
 | 
			
		||||
      'click .cancel': 'onCancel',
 | 
			
		||||
      'click .delete-all-data': 'onDeleteAllData',
 | 
			
		||||
    },
 | 
			
		||||
    initialize() {
 | 
			
		||||
      this.step = CLEAR_DATA_STEPS.CHOICE;
 | 
			
		||||
    },
 | 
			
		||||
    onCancel() {
 | 
			
		||||
      this.remove();
 | 
			
		||||
    },
 | 
			
		||||
    async onDeleteAllData() {
 | 
			
		||||
      console.log('Deleting everything!');
 | 
			
		||||
      this.step = CLEAR_DATA_STEPS.DELETING;
 | 
			
		||||
      this.render();
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        await Database.close();
 | 
			
		||||
        console.log('All database connections closed. Starting delete.');
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.log('Something went wrong closing all database connections.');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.clearAllData();
 | 
			
		||||
    },
 | 
			
		||||
    async clearAllData() {
 | 
			
		||||
      try {
 | 
			
		||||
        await Promise.all([Logs.deleteAll(), Database.drop()]);
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.log(
 | 
			
		||||
          'Something went wrong deleting all data:',
 | 
			
		||||
          error && error.stack ? error.stack : error
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      window.restart();
 | 
			
		||||
    },
 | 
			
		||||
    render_attributes() {
 | 
			
		||||
      return {
 | 
			
		||||
        isStep1: this.step === CLEAR_DATA_STEPS.CHOICE,
 | 
			
		||||
        header: i18n('deleteAllDataHeader'),
 | 
			
		||||
        body: i18n('deleteAllDataBody'),
 | 
			
		||||
        cancelButton: i18n('cancel'),
 | 
			
		||||
        deleteButton: i18n('deleteAllDataButton'),
 | 
			
		||||
 | 
			
		||||
        isStep2: this.step === CLEAR_DATA_STEPS.DELETING,
 | 
			
		||||
        deleting: i18n('deleteAllDataProgress'),
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  /* eslint-disable */
 | 
			
		||||
  /* jshint ignore:end */
 | 
			
		||||
 | 
			
		||||
  var SyncView = Whisper.View.extend({
 | 
			
		||||
  const SyncView = Whisper.View.extend({
 | 
			
		||||
    templateName: 'syncSettings',
 | 
			
		||||
    className: 'syncSettings',
 | 
			
		||||
    events: {
 | 
			
		||||
      'click .sync': 'sync',
 | 
			
		||||
    },
 | 
			
		||||
    enable: function() {
 | 
			
		||||
    initialize() {
 | 
			
		||||
      this.lastSyncTime = window.initialData.lastSyncTime;
 | 
			
		||||
    },
 | 
			
		||||
    enable() {
 | 
			
		||||
      this.$('.sync').text(i18n('syncNow'));
 | 
			
		||||
      this.$('.sync').removeAttr('disabled');
 | 
			
		||||
    },
 | 
			
		||||
    disable: function() {
 | 
			
		||||
    disable() {
 | 
			
		||||
      this.$('.sync').attr('disabled', 'disabled');
 | 
			
		||||
      this.$('.sync').text(i18n('syncing'));
 | 
			
		||||
    },
 | 
			
		||||
    onsuccess: function() {
 | 
			
		||||
      storage.put('synced_at', Date.now());
 | 
			
		||||
    onsuccess() {
 | 
			
		||||
      window.setLastSyncTime(Date.now());
 | 
			
		||||
      this.lastSyncTime = Date.now();
 | 
			
		||||
      console.log('sync successful');
 | 
			
		||||
      this.enable();
 | 
			
		||||
      this.render();
 | 
			
		||||
    },
 | 
			
		||||
    ontimeout: function() {
 | 
			
		||||
    ontimeout() {
 | 
			
		||||
      console.log('sync timed out');
 | 
			
		||||
      this.$('.synced_at').hide();
 | 
			
		||||
      this.$('.sync_failed').show();
 | 
			
		||||
      this.enable();
 | 
			
		||||
    },
 | 
			
		||||
    sync: function() {
 | 
			
		||||
    async sync() {
 | 
			
		||||
      this.$('.sync_failed').hide();
 | 
			
		||||
      if (textsecure.storage.user.getDeviceId() != '1') {
 | 
			
		||||
        this.disable();
 | 
			
		||||
        var syncRequest = window.getSyncRequest();
 | 
			
		||||
        syncRequest.addEventListener('success', this.onsuccess.bind(this));
 | 
			
		||||
        syncRequest.addEventListener('timeout', this.ontimeout.bind(this));
 | 
			
		||||
      } else {
 | 
			
		||||
      if (window.initialData.isPrimary) {
 | 
			
		||||
        console.log('Tried to sync from device 1');
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.disable();
 | 
			
		||||
      try {
 | 
			
		||||
        await window.makeSyncRequest();
 | 
			
		||||
        this.onsuccess();
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        this.ontimeout();
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    render_attributes: function() {
 | 
			
		||||
      var attrs = {
 | 
			
		||||
    render_attributes() {
 | 
			
		||||
      const attrs = {
 | 
			
		||||
        sync: i18n('sync'),
 | 
			
		||||
        syncNow: i18n('syncNow'),
 | 
			
		||||
        syncExplanation: i18n('syncExplanation'),
 | 
			
		||||
        syncFailed: i18n('syncFailed'),
 | 
			
		||||
      };
 | 
			
		||||
      var date = storage.get('synced_at');
 | 
			
		||||
      let date = this.lastSyncTime;
 | 
			
		||||
      if (date) {
 | 
			
		||||
        date = new Date(date);
 | 
			
		||||
        attrs.lastSynced = i18n('lastSynced');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										268
									
								
								main.js
									
										
									
									
									
								
							
							
						
						
									
										268
									
								
								main.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -349,20 +349,6 @@ function createWindow() {
 | 
			
		|||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function showDebugLog() {
 | 
			
		||||
  if (mainWindow) {
 | 
			
		||||
    mainWindow.webContents.send('debug-log');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function showSettings() {
 | 
			
		||||
  if (!mainWindow) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mainWindow.webContents.send('show-settings');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function openReleaseNotes() {
 | 
			
		||||
  shell.openExternal(
 | 
			
		||||
    `https://github.com/signalapp/Signal-Desktop/releases/tag/v${app.getVersion()}`
 | 
			
		||||
| 
						 | 
				
			
			@ -441,6 +427,146 @@ function showAbout() {
 | 
			
		|||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let settingsWindow;
 | 
			
		||||
function showSettingsWindow() {
 | 
			
		||||
  if (settingsWindow) {
 | 
			
		||||
    settingsWindow.show();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!mainWindow) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const size = mainWindow.getSize();
 | 
			
		||||
  const options = {
 | 
			
		||||
    width: Math.min(500, size[0]),
 | 
			
		||||
    height: Math.max(size[1] - 100, MIN_HEIGHT),
 | 
			
		||||
    resizable: false,
 | 
			
		||||
    title: locale.messages.signalDesktopPreferences.message,
 | 
			
		||||
    autoHideMenuBar: true,
 | 
			
		||||
    backgroundColor: '#FFFFFF',
 | 
			
		||||
    show: false,
 | 
			
		||||
    modal: true,
 | 
			
		||||
    webPreferences: {
 | 
			
		||||
      nodeIntegration: false,
 | 
			
		||||
      nodeIntegrationInWorker: false,
 | 
			
		||||
      preload: path.join(__dirname, 'settings_preload.js'),
 | 
			
		||||
      // sandbox: true,
 | 
			
		||||
      nativeWindowOpen: true,
 | 
			
		||||
    },
 | 
			
		||||
    parent: mainWindow,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  settingsWindow = new BrowserWindow(options);
 | 
			
		||||
 | 
			
		||||
  captureClicks(settingsWindow);
 | 
			
		||||
 | 
			
		||||
  settingsWindow.loadURL(prepareURL([__dirname, 'settings.html']));
 | 
			
		||||
 | 
			
		||||
  settingsWindow.on('closed', () => {
 | 
			
		||||
    removeDarkOverlay();
 | 
			
		||||
    settingsWindow = null;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  settingsWindow.once('ready-to-show', () => {
 | 
			
		||||
    addDarkOverlay();
 | 
			
		||||
    settingsWindow.show();
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let debugLogWindow;
 | 
			
		||||
function showDebugLogWindow() {
 | 
			
		||||
  if (debugLogWindow) {
 | 
			
		||||
    debugLogWindow.show();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const size = mainWindow.getSize();
 | 
			
		||||
  const options = {
 | 
			
		||||
    width: Math.max(size[0] - 100, MIN_WIDTH),
 | 
			
		||||
    height: Math.max(size[1] - 100, MIN_HEIGHT),
 | 
			
		||||
    resizable: false,
 | 
			
		||||
    title: locale.messages.signalDesktopPreferences.message,
 | 
			
		||||
    autoHideMenuBar: true,
 | 
			
		||||
    backgroundColor: '#FFFFFF',
 | 
			
		||||
    show: false,
 | 
			
		||||
    modal: true,
 | 
			
		||||
    webPreferences: {
 | 
			
		||||
      nodeIntegration: false,
 | 
			
		||||
      nodeIntegrationInWorker: false,
 | 
			
		||||
      preload: path.join(__dirname, 'debug_log_preload.js'),
 | 
			
		||||
      // sandbox: true,
 | 
			
		||||
      nativeWindowOpen: true,
 | 
			
		||||
    },
 | 
			
		||||
    parent: mainWindow,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  debugLogWindow = new BrowserWindow(options);
 | 
			
		||||
 | 
			
		||||
  captureClicks(debugLogWindow);
 | 
			
		||||
 | 
			
		||||
  debugLogWindow.loadURL(prepareURL([__dirname, 'debug_log.html']));
 | 
			
		||||
 | 
			
		||||
  debugLogWindow.on('closed', () => {
 | 
			
		||||
    removeDarkOverlay();
 | 
			
		||||
    debugLogWindow = null;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  debugLogWindow.once('ready-to-show', () => {
 | 
			
		||||
    addDarkOverlay();
 | 
			
		||||
    debugLogWindow.show();
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let permissionsPopupWindow;
 | 
			
		||||
function showPermissionsPopupWindow() {
 | 
			
		||||
  if (permissionsPopupWindow) {
 | 
			
		||||
    permissionsPopupWindow.show();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!mainWindow) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const size = mainWindow.getSize();
 | 
			
		||||
  const options = {
 | 
			
		||||
    width: Math.min(400, size[0]),
 | 
			
		||||
    height: Math.min(150, size[1]),
 | 
			
		||||
    resizable: false,
 | 
			
		||||
    title: locale.messages.signalDesktopPreferences.message,
 | 
			
		||||
    autoHideMenuBar: true,
 | 
			
		||||
    backgroundColor: '#FFFFFF',
 | 
			
		||||
    show: false,
 | 
			
		||||
    modal: true,
 | 
			
		||||
    webPreferences: {
 | 
			
		||||
      nodeIntegration: false,
 | 
			
		||||
      nodeIntegrationInWorker: false,
 | 
			
		||||
      preload: path.join(__dirname, 'permissions_popup_preload.js'),
 | 
			
		||||
      // sandbox: true,
 | 
			
		||||
      nativeWindowOpen: true,
 | 
			
		||||
    },
 | 
			
		||||
    parent: mainWindow,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  permissionsPopupWindow = new BrowserWindow(options);
 | 
			
		||||
 | 
			
		||||
  captureClicks(permissionsPopupWindow);
 | 
			
		||||
 | 
			
		||||
  permissionsPopupWindow.loadURL(
 | 
			
		||||
    prepareURL([__dirname, 'permissions_popup.html'])
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  permissionsPopupWindow.on('closed', () => {
 | 
			
		||||
    removeDarkOverlay();
 | 
			
		||||
    permissionsPopupWindow = null;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  permissionsPopupWindow.once('ready-to-show', () => {
 | 
			
		||||
    addDarkOverlay();
 | 
			
		||||
    permissionsPopupWindow.show();
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This method will be called when Electron has finished
 | 
			
		||||
// initialization and is ready to create browser windows.
 | 
			
		||||
// Some APIs can only be used after this event occurs.
 | 
			
		||||
| 
						 | 
				
			
			@ -462,7 +588,7 @@ app.on('ready', async () => {
 | 
			
		|||
    protocol: electronProtocol,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  installPermissionsHandler({ session });
 | 
			
		||||
  installPermissionsHandler({ session, userConfig });
 | 
			
		||||
 | 
			
		||||
  // NOTE: Temporarily allow `then` until we convert the entire file to `async` / `await`:
 | 
			
		||||
  /* eslint-disable more/no-then */
 | 
			
		||||
| 
						 | 
				
			
			@ -508,9 +634,10 @@ function setupMenu(options) {
 | 
			
		|||
  const { platform } = process;
 | 
			
		||||
  const menuOptions = Object.assign({}, options, {
 | 
			
		||||
    development,
 | 
			
		||||
    showDebugLog,
 | 
			
		||||
    showDebugLog: showDebugLogWindow,
 | 
			
		||||
    showWindow,
 | 
			
		||||
    showAbout,
 | 
			
		||||
    showSettings: showSettingsWindow,
 | 
			
		||||
    openReleaseNotes,
 | 
			
		||||
    openNewBugForm,
 | 
			
		||||
    openSupportPage,
 | 
			
		||||
| 
						 | 
				
			
			@ -519,7 +646,6 @@ function setupMenu(options) {
 | 
			
		|||
    setupWithImport,
 | 
			
		||||
    setupAsNewDevice,
 | 
			
		||||
    setupAsStandalone,
 | 
			
		||||
    showSettings,
 | 
			
		||||
  });
 | 
			
		||||
  const template = createTemplate(menuOptions, locale.messages);
 | 
			
		||||
  const menu = Menu.buildFromTemplate(template);
 | 
			
		||||
| 
						 | 
				
			
			@ -591,6 +717,14 @@ ipc.on('draw-attention', () => {
 | 
			
		|||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
ipc.on('set-media-permissions', (event, enabled) => {
 | 
			
		||||
  userConfig.set('mediaPermissions', enabled);
 | 
			
		||||
});
 | 
			
		||||
ipc.on('get-media-permissions', event => {
 | 
			
		||||
  // eslint-disable-next-line no-param-reassign
 | 
			
		||||
  event.returnValue = userConfig.get('mediaPermissions') || false;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
ipc.on('restart', () => {
 | 
			
		||||
  app.relaunch();
 | 
			
		||||
  app.quit();
 | 
			
		||||
| 
						 | 
				
			
			@ -619,3 +753,103 @@ ipc.on('update-tray-icon', (event, unreadCount) => {
 | 
			
		|||
    tray.updateIcon(unreadCount);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Debug Log-related IPC calls
 | 
			
		||||
 | 
			
		||||
ipc.on('show-debug-log', showDebugLogWindow);
 | 
			
		||||
ipc.on('close-debug-log', () => {
 | 
			
		||||
  if (debugLogWindow) {
 | 
			
		||||
    debugLogWindow.close();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Permissions Popup-related IPC calls
 | 
			
		||||
 | 
			
		||||
ipc.on('show-permissions-popup', showPermissionsPopupWindow);
 | 
			
		||||
ipc.on('close-permissions-popup', () => {
 | 
			
		||||
  if (permissionsPopupWindow) {
 | 
			
		||||
    permissionsPopupWindow.close();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Settings-related IPC calls
 | 
			
		||||
 | 
			
		||||
function addDarkOverlay() {
 | 
			
		||||
  if (mainWindow && mainWindow.webContents) {
 | 
			
		||||
    mainWindow.webContents.send('add-dark-overlay');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function removeDarkOverlay() {
 | 
			
		||||
  if (mainWindow && mainWindow.webContents) {
 | 
			
		||||
    mainWindow.webContents.send('remove-dark-overlay');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ipc.on('show-settings', showSettingsWindow);
 | 
			
		||||
ipc.on('close-settings', () => {
 | 
			
		||||
  if (settingsWindow) {
 | 
			
		||||
    settingsWindow.close();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
installSettingsGetter('device-name');
 | 
			
		||||
 | 
			
		||||
installSettingsGetter('theme-setting');
 | 
			
		||||
installSettingsSetter('theme-setting');
 | 
			
		||||
installSettingsGetter('hide-menu-bar');
 | 
			
		||||
installSettingsSetter('hide-menu-bar');
 | 
			
		||||
 | 
			
		||||
installSettingsGetter('notification-setting');
 | 
			
		||||
installSettingsSetter('notification-setting');
 | 
			
		||||
installSettingsGetter('audio-notification');
 | 
			
		||||
installSettingsSetter('audio-notification');
 | 
			
		||||
 | 
			
		||||
// This one is different because its single source of truth is userConfig, not IndexedDB
 | 
			
		||||
ipc.on('get-media-permissions', event => {
 | 
			
		||||
  event.sender.send(
 | 
			
		||||
    'get-success-media-permissions',
 | 
			
		||||
    null,
 | 
			
		||||
    userConfig.get('mediaPermissions') || false
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
ipc.on('set-media-permissions', (event, value) => {
 | 
			
		||||
  userConfig.set('mediaPermissions', value);
 | 
			
		||||
 | 
			
		||||
  // We reinstall permissions handler to ensure that a revoked permission takes effect
 | 
			
		||||
  installPermissionsHandler({ session, userConfig });
 | 
			
		||||
 | 
			
		||||
  event.sender.send('set-success-media-permissions', null);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
installSettingsGetter('is-primary');
 | 
			
		||||
installSettingsGetter('sync-request');
 | 
			
		||||
installSettingsGetter('sync-time');
 | 
			
		||||
installSettingsSetter('sync-time');
 | 
			
		||||
 | 
			
		||||
ipc.on('delete-all-data', () => {
 | 
			
		||||
  if (mainWindow && mainWindow.webContents) {
 | 
			
		||||
    mainWindow.webContents.send('delete-all-data');
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function installSettingsGetter(name) {
 | 
			
		||||
  ipc.on(`get-${name}`, event => {
 | 
			
		||||
    if (mainWindow && mainWindow.webContents) {
 | 
			
		||||
      ipc.once(`get-success-${name}`, (_event, error, value) =>
 | 
			
		||||
        event.sender.send(`get-success-${name}`, error, value)
 | 
			
		||||
      );
 | 
			
		||||
      mainWindow.webContents.send(`get-${name}`);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function installSettingsSetter(name) {
 | 
			
		||||
  ipc.on(`set-${name}`, (event, value) => {
 | 
			
		||||
    if (mainWindow && mainWindow.webContents) {
 | 
			
		||||
      ipc.once(`set-success-${name}`, (_event, error) =>
 | 
			
		||||
        event.sender.send(`set-success-${name}`, error)
 | 
			
		||||
      );
 | 
			
		||||
      mainWindow.webContents.send(`set-${name}`, value);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -214,6 +214,9 @@
 | 
			
		|||
      "config/local-${env.SIGNAL_ENV}.json",
 | 
			
		||||
      "background.html",
 | 
			
		||||
      "about.html",
 | 
			
		||||
      "settings.html",
 | 
			
		||||
      "permissions_popup.html",
 | 
			
		||||
      "debug_log.html",
 | 
			
		||||
      "_locales/**",
 | 
			
		||||
      "protos/*",
 | 
			
		||||
      "js/**",
 | 
			
		||||
| 
						 | 
				
			
			@ -225,6 +228,9 @@
 | 
			
		|||
      "app/*",
 | 
			
		||||
      "preload.js",
 | 
			
		||||
      "about_preload.js",
 | 
			
		||||
      "settings_preload.js",
 | 
			
		||||
      "permissions_preload.js",
 | 
			
		||||
      "debug_log_preload.js",
 | 
			
		||||
      "main.js",
 | 
			
		||||
      "images/**",
 | 
			
		||||
      "fonts/*",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										38
									
								
								permissions_popup.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								permissions_popup.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
  <meta http-equiv="Content-Security-Policy"
 | 
			
		||||
    content="default-src 'none';
 | 
			
		||||
            child-src 'self';
 | 
			
		||||
            connect-src 'self' https: wss:;
 | 
			
		||||
            font-src 'self';
 | 
			
		||||
            form-action 'self';
 | 
			
		||||
            frame-src 'none';
 | 
			
		||||
            img-src 'self' blob: data:;
 | 
			
		||||
            media-src 'self' blob:;
 | 
			
		||||
            object-src 'none';
 | 
			
		||||
            script-src 'self';
 | 
			
		||||
            style-src 'self' 'unsafe-inline';"
 | 
			
		||||
  >
 | 
			
		||||
  <link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
 | 
			
		||||
<style>
 | 
			
		||||
</style>
 | 
			
		||||
</head>
 | 
			
		||||
<body class='permissions-popup'>
 | 
			
		||||
</body>
 | 
			
		||||
<script type='text/x-tmpl-mustache' id='confirmation-dialog'>
 | 
			
		||||
  <div class="content">
 | 
			
		||||
    <div class='message'>{{ message }}</div>
 | 
			
		||||
    <div class='buttons'>
 | 
			
		||||
      {{ #showCancel }}
 | 
			
		||||
        <button class='cancel' tabindex='2'>{{ cancel }}</button>
 | 
			
		||||
      {{ /showCancel }}
 | 
			
		||||
      <button class='ok' tabindex='1'>{{ ok }}</button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</script>
 | 
			
		||||
</script>
 | 
			
		||||
<script type='text/javascript' src='js/components.js'></script>
 | 
			
		||||
<script type='text/javascript' src='js/views/whisper_view.js'></script>
 | 
			
		||||
<script type='text/javascript' src='js/views/confirmation_dialog_view.js'></script>
 | 
			
		||||
<script type='text/javascript' src='js/permissions_popup_start.js'></script>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										45
									
								
								permissions_popup_preload.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								permissions_popup_preload.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
const { ipcRenderer } = require('electron');
 | 
			
		||||
const url = require('url');
 | 
			
		||||
const i18n = require('./js/modules/i18n');
 | 
			
		||||
 | 
			
		||||
const config = url.parse(window.location.toString(), true).query;
 | 
			
		||||
const { locale } = config;
 | 
			
		||||
const localeMessages = ipcRenderer.sendSync('locale-data');
 | 
			
		||||
 | 
			
		||||
window.i18n = i18n.setup(locale, localeMessages);
 | 
			
		||||
 | 
			
		||||
require('./js/logging');
 | 
			
		||||
 | 
			
		||||
window.closePermissionsPopup = () =>
 | 
			
		||||
  ipcRenderer.send('close-permissions-popup');
 | 
			
		||||
 | 
			
		||||
window.getMediaPermissions = makeGetter('media-permissions');
 | 
			
		||||
window.setMediaPermissions = makeSetter('media-permissions');
 | 
			
		||||
 | 
			
		||||
function makeGetter(name) {
 | 
			
		||||
  return () =>
 | 
			
		||||
    new Promise((resolve, reject) => {
 | 
			
		||||
      ipcRenderer.once(`get-success-${name}`, (event, error, value) => {
 | 
			
		||||
        if (error) {
 | 
			
		||||
          return reject(error);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return resolve(value);
 | 
			
		||||
      });
 | 
			
		||||
      ipcRenderer.send(`get-${name}`);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function makeSetter(name) {
 | 
			
		||||
  return value =>
 | 
			
		||||
    new Promise((resolve, reject) => {
 | 
			
		||||
      ipcRenderer.once(`set-success-${name}`, (event, error) => {
 | 
			
		||||
        if (error) {
 | 
			
		||||
          return reject(error);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return resolve();
 | 
			
		||||
      });
 | 
			
		||||
      ipcRenderer.send(`set-${name}`, value);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										87
									
								
								preload.js
									
										
									
									
									
								
							
							
						
						
									
										87
									
								
								preload.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -61,6 +61,10 @@ window.restart = () => {
 | 
			
		|||
  ipc.send('restart');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
window.setMediaPermissions = enabled =>
 | 
			
		||||
  ipc.send('set-media-permissions', enabled);
 | 
			
		||||
window.getMediaPermissions = () => ipc.sendSync('get-media-permissions');
 | 
			
		||||
 | 
			
		||||
window.closeAbout = () => ipc.send('close-about');
 | 
			
		||||
 | 
			
		||||
window.updateTrayIcon = unreadCount =>
 | 
			
		||||
| 
						 | 
				
			
			@ -82,12 +86,89 @@ ipc.on('set-up-as-standalone', () => {
 | 
			
		|||
  Whisper.events.trigger('setupAsStandalone');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
ipc.on('show-settings', () => {
 | 
			
		||||
  Whisper.events.trigger('showSettings');
 | 
			
		||||
// Settings-related events
 | 
			
		||||
 | 
			
		||||
window.showSettings = () => ipc.send('show-settings');
 | 
			
		||||
window.showPermissionsPopup = () => ipc.send('show-permissions-popup');
 | 
			
		||||
 | 
			
		||||
ipc.on('add-dark-overlay', () => {
 | 
			
		||||
  const { addDarkOverlay } = window.Events;
 | 
			
		||||
  if (addDarkOverlay) {
 | 
			
		||||
    addDarkOverlay();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
ipc.on('remove-dark-overlay', () => {
 | 
			
		||||
  const { removeDarkOverlay } = window.Events;
 | 
			
		||||
  if (removeDarkOverlay) {
 | 
			
		||||
    removeDarkOverlay();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
window.addSetupMenuItems = () => ipc.send('add-setup-menu-items');
 | 
			
		||||
installGetter('device-name', 'getDeviceName');
 | 
			
		||||
 | 
			
		||||
installGetter('theme-setting', 'getThemeSetting');
 | 
			
		||||
installSetter('theme-setting', 'setThemeSetting');
 | 
			
		||||
installGetter('hide-menu-bar', 'getHideMenuBar');
 | 
			
		||||
installSetter('hide-menu-bar', 'setHideMenuBar');
 | 
			
		||||
 | 
			
		||||
installGetter('notification-setting', 'getNotificationSetting');
 | 
			
		||||
installSetter('notification-setting', 'setNotificationSetting');
 | 
			
		||||
installGetter('audio-notification', 'getAudioNotification');
 | 
			
		||||
installSetter('audio-notification', 'setAudioNotification');
 | 
			
		||||
 | 
			
		||||
window.getMediaPermissions = () =>
 | 
			
		||||
  new Promise((resolve, reject) => {
 | 
			
		||||
    ipc.once('get-success-media-permissions', (_event, error, value) => {
 | 
			
		||||
      if (error) {
 | 
			
		||||
        return reject(error);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return resolve(value);
 | 
			
		||||
    });
 | 
			
		||||
    ipc.send('get-media-permissions');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
installGetter('is-primary', 'isPrimary');
 | 
			
		||||
installGetter('sync-request', 'getSyncRequest');
 | 
			
		||||
installGetter('sync-time', 'getLastSyncTime');
 | 
			
		||||
installSetter('sync-time', 'setLastSyncTime');
 | 
			
		||||
 | 
			
		||||
ipc.on('delete-all-data', () => {
 | 
			
		||||
  const { deleteAllData } = window.Events;
 | 
			
		||||
  if (deleteAllData) {
 | 
			
		||||
    deleteAllData();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function installGetter(name, functionName) {
 | 
			
		||||
  ipc.on(`get-${name}`, async () => {
 | 
			
		||||
    const getFn = window.Events[functionName];
 | 
			
		||||
    if (getFn) {
 | 
			
		||||
      // eslint-disable-next-line no-param-reassign
 | 
			
		||||
      try {
 | 
			
		||||
        ipc.send(`get-success-${name}`, null, await getFn());
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        ipc.send(`get-success-${name}`, error);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function installSetter(name, functionName) {
 | 
			
		||||
  ipc.on(`set-${name}`, async (_event, value) => {
 | 
			
		||||
    const setFn = window.Events[functionName];
 | 
			
		||||
    if (setFn) {
 | 
			
		||||
      try {
 | 
			
		||||
        await setFn(value);
 | 
			
		||||
        ipc.send(`set-success-${name}`);
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        ipc.send(`set-success-${name}`, error);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
window.addSetupMenuItems = () => ipc.send('add-setup-menu-items');
 | 
			
		||||
window.removeSetupMenuItems = () => ipc.send('remove-setup-menu-items');
 | 
			
		||||
 | 
			
		||||
// We pull these dependencies in now, from here, because they have Node.js dependencies
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										116
									
								
								settings.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								settings.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,116 @@
 | 
			
		|||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
  <meta http-equiv="Content-Security-Policy"
 | 
			
		||||
    content="default-src 'none';
 | 
			
		||||
            child-src 'self';
 | 
			
		||||
            connect-src 'self' https: wss:;
 | 
			
		||||
            font-src 'self';
 | 
			
		||||
            form-action 'self';
 | 
			
		||||
            frame-src 'none';
 | 
			
		||||
            img-src 'self' blob: data:;
 | 
			
		||||
            media-src 'self' blob:;
 | 
			
		||||
            object-src 'none';
 | 
			
		||||
            script-src 'self';
 | 
			
		||||
            style-src 'self' 'unsafe-inline';"
 | 
			
		||||
  >
 | 
			
		||||
  <link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
 | 
			
		||||
<style>
 | 
			
		||||
</style>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
</body>
 | 
			
		||||
<script type='text/x-tmpl-mustache' id='syncSettings'>
 | 
			
		||||
    <hr>
 | 
			
		||||
    <h3>{{ sync }}</h3>
 | 
			
		||||
    <div>
 | 
			
		||||
      <button class='grey sync'>{{ syncNow }}</button>
 | 
			
		||||
      <p>
 | 
			
		||||
        {{ syncExplanation }}
 | 
			
		||||
        <div class='synced_at'>
 | 
			
		||||
            {{ lastSynced }} {{ syncDate }} {{ syncTime }}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class='sync_failed'>{{ syncFailed }}</div>
 | 
			
		||||
        <div class='clearfix'></div>
 | 
			
		||||
      </p>
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
<script type='text/x-tmpl-mustache' id='settings'>
 | 
			
		||||
  <div class='content'>
 | 
			
		||||
    <a class='x close' alt='close settings' href='#'></a>
 | 
			
		||||
    <h2>{{ settings }}</h2>
 | 
			
		||||
    <div class='device-name-settings'>
 | 
			
		||||
      <b>{{ deviceNameLabel }}:</b> {{ deviceName }}
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr>
 | 
			
		||||
    <div class='theme-settings'>
 | 
			
		||||
      <h3>{{ theme }}</h3>
 | 
			
		||||
      <div>
 | 
			
		||||
        <input type='radio' name='theme' id='theme-setting-android' value='android'>
 | 
			
		||||
        <label for='theme-setting-android'>Android</label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div>
 | 
			
		||||
        <input type='radio' name='theme' id='theme-setting-android-dark' value='android-dark'>
 | 
			
		||||
        <label for='theme-setting-android-dark'>{{ themeAndroidDark }}</label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div>
 | 
			
		||||
        <input type='radio' name='theme' id='theme-setting-ios'  value='ios'/>
 | 
			
		||||
        <label for='theme-setting-ios'>iOS</label>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <br />
 | 
			
		||||
    <div class='menu-bar-setting'>
 | 
			
		||||
      <input type='checkbox' name='hide-menu-bar' id='hide-menu-bar'/>
 | 
			
		||||
      <label for='hide-menu-bar'>{{ hideMenuBar }}</label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr>
 | 
			
		||||
    <div class='notification-settings'>
 | 
			
		||||
      <h3>{{ notifications }}</h3>
 | 
			
		||||
      <p>{{ notificationSettingsDialog }}</p>
 | 
			
		||||
      <div>
 | 
			
		||||
        <input type='radio' name='notifications' id='notification-setting-message' value='message'>
 | 
			
		||||
        <label for='notification-setting-message'>{{ nameAndMessage }} </label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div>
 | 
			
		||||
        <input type='radio' name='notifications' id='notification-setting-name'  value='name'/>
 | 
			
		||||
        <label for='notification-setting-name'>{{ nameOnly }} </label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div>
 | 
			
		||||
        <input type='radio' name='notifications' id='notification-setting-count' value='count'/>
 | 
			
		||||
        <label for='notification-setting-count'>{{ noNameOrMessage }} </label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div>
 | 
			
		||||
        <input type='radio' name='notifications' id='notification-setting-off' value='off'/>
 | 
			
		||||
        <label for='notification-setting-off'>{{ disableNotifications }} </label>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <br />
 | 
			
		||||
    {{ #isAudioNotificationSupported }}
 | 
			
		||||
    <div class='audio-notification-setting'>
 | 
			
		||||
      <input type='checkbox' name='audio-notification' id='audio-notification'/>
 | 
			
		||||
      <label for='audio-notification'>{{ audioNotificationDescription }}</label>
 | 
			
		||||
    </div>
 | 
			
		||||
    {{ /isAudioNotificationSupported }}
 | 
			
		||||
    <hr>
 | 
			
		||||
    <div class='permissions-setting'>
 | 
			
		||||
      <h3>{{ permissions }}</h3>
 | 
			
		||||
      <div class='media-permissions'>
 | 
			
		||||
        <input type='checkbox' name='media-permissions' />
 | 
			
		||||
        <label for='media-permissions'>{{ mediaPermissionsDescription }}</label>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='sync-setting'></div>
 | 
			
		||||
    <hr>
 | 
			
		||||
    <div class='clear-data-settings'>
 | 
			
		||||
      <h3>{{ clearDataHeader }}</h3>
 | 
			
		||||
      <div>
 | 
			
		||||
        <button class='grey destructive clear-data'>{{ clearDataButton }}</button>
 | 
			
		||||
        <p>{{ clearDataExplanation }}</p>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</script>
 | 
			
		||||
<script type='text/javascript' src='js/components.js'></script>
 | 
			
		||||
<script type='text/javascript' src='js/views/whisper_view.js'></script>
 | 
			
		||||
<script type='text/javascript' src='js/views/settings_view.js'></script>
 | 
			
		||||
<script type='text/javascript' src='js/settings_start.js'></script>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										76
									
								
								settings_preload.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								settings_preload.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,76 @@
 | 
			
		|||
const { ipcRenderer } = require('electron');
 | 
			
		||||
const url = require('url');
 | 
			
		||||
const i18n = require('./js/modules/i18n');
 | 
			
		||||
 | 
			
		||||
const config = url.parse(window.location.toString(), true).query;
 | 
			
		||||
const { locale } = config;
 | 
			
		||||
const localeMessages = ipcRenderer.sendSync('locale-data');
 | 
			
		||||
 | 
			
		||||
window.i18n = i18n.setup(locale, localeMessages);
 | 
			
		||||
 | 
			
		||||
require('./js/logging');
 | 
			
		||||
 | 
			
		||||
// So far we're only using this for Signal.Types
 | 
			
		||||
const Signal = require('./js/modules/signal');
 | 
			
		||||
 | 
			
		||||
window.Signal = Signal.setup({
 | 
			
		||||
  Attachments: null,
 | 
			
		||||
  userDataPath: null,
 | 
			
		||||
  getRegionCode: () => null,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
window.getEnvironment = () => config.environment;
 | 
			
		||||
window.getVersion = () => config.version;
 | 
			
		||||
window.getAppInstance = () => config.appInstance;
 | 
			
		||||
 | 
			
		||||
window.closeSettings = () => ipcRenderer.send('close-settings');
 | 
			
		||||
 | 
			
		||||
window.getDeviceName = makeGetter('device-name');
 | 
			
		||||
 | 
			
		||||
window.getThemeSetting = makeGetter('theme-setting');
 | 
			
		||||
window.setThemeSetting = makeSetter('theme-setting');
 | 
			
		||||
window.getHideMenuBar = makeGetter('hide-menu-bar');
 | 
			
		||||
window.setHideMenuBar = makeSetter('hide-menu-bar');
 | 
			
		||||
 | 
			
		||||
window.getNotificationSetting = makeGetter('notification-setting');
 | 
			
		||||
window.setNotificationSetting = makeSetter('notification-setting');
 | 
			
		||||
window.getAudioNotification = makeGetter('audio-notification');
 | 
			
		||||
window.setAudioNotification = makeSetter('audio-notification');
 | 
			
		||||
 | 
			
		||||
window.getMediaPermissions = makeGetter('media-permissions');
 | 
			
		||||
window.setMediaPermissions = makeSetter('media-permissions');
 | 
			
		||||
 | 
			
		||||
window.isPrimary = makeGetter('is-primary');
 | 
			
		||||
window.makeSyncRequest = makeGetter('sync-request');
 | 
			
		||||
window.getLastSyncTime = makeGetter('sync-time');
 | 
			
		||||
window.setLastSyncTime = makeSetter('sync-time');
 | 
			
		||||
 | 
			
		||||
window.deleteAllData = () => ipcRenderer.send('delete-all-data');
 | 
			
		||||
 | 
			
		||||
function makeGetter(name) {
 | 
			
		||||
  return () =>
 | 
			
		||||
    new Promise((resolve, reject) => {
 | 
			
		||||
      ipcRenderer.once(`get-success-${name}`, (event, error, value) => {
 | 
			
		||||
        if (error) {
 | 
			
		||||
          return reject(error);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return resolve(value);
 | 
			
		||||
      });
 | 
			
		||||
      ipcRenderer.send(`get-${name}`);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function makeSetter(name) {
 | 
			
		||||
  return value =>
 | 
			
		||||
    new Promise((resolve, reject) => {
 | 
			
		||||
      ipcRenderer.once(`set-success-${name}`, (event, error) => {
 | 
			
		||||
        if (error) {
 | 
			
		||||
          return reject(error);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return resolve();
 | 
			
		||||
      });
 | 
			
		||||
      ipcRenderer.send(`set-${name}`, value);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1320,6 +1320,24 @@ span.status {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.permissions-popup,
 | 
			
		||||
.debug-log-window {
 | 
			
		||||
  .modal {
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .confirmation-dialog .content {
 | 
			
		||||
    box-shadow: 0px 0px 0px 0px;
 | 
			
		||||
    max-width: 1000px;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    margin-left: auto;
 | 
			
		||||
    margin-right: auto;
 | 
			
		||||
    margin-top: 15px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.advisory .icon {
 | 
			
		||||
  height: 1.25em;
 | 
			
		||||
  width: 1.25em;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,17 @@ body {
 | 
			
		|||
  color: $grey_d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dark-overlay {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  background-color: black;
 | 
			
		||||
  opacity: 0.25;
 | 
			
		||||
  z-index: 200;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.clearfix:before,
 | 
			
		||||
.clearfix:after {
 | 
			
		||||
  display: table;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +1,17 @@
 | 
			
		|||
.settings {
 | 
			
		||||
  &.modal {
 | 
			
		||||
    padding: 50px;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
 | 
			
		||||
    .content {
 | 
			
		||||
      margin: 0 auto;
 | 
			
		||||
      margin: 0;
 | 
			
		||||
      margin-left: auto;
 | 
			
		||||
      margin-right: auto;
 | 
			
		||||
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      max-width: 500px;
 | 
			
		||||
      max-width: 450px;
 | 
			
		||||
      border-radius: 0;
 | 
			
		||||
      box-shadow: 0px 0px 0px 0px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  hr {
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +38,10 @@
 | 
			
		|||
      color: red;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .restart-needed {
 | 
			
		||||
    margin-top: 1em;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .clear-data-settings {
 | 
			
		||||
    button {
 | 
			
		||||
      float: right;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -618,11 +618,11 @@
 | 
			
		|||
  <script type='text/javascript' src='../js/views/network_status_view.js'></script>
 | 
			
		||||
  <script type='text/javascript' src='../js/views/confirmation_dialog_view.js' data-cover></script>
 | 
			
		||||
  <script type='text/javascript' src='../js/views/identicon_svg_view.js' data-cover></script>
 | 
			
		||||
  <script type='text/javascript' src='../js/views/settings_view.js' data-cover></script>
 | 
			
		||||
  <script type='text/javascript' src='../js/views/last_seen_indicator_view.js' data-cover></script>
 | 
			
		||||
  <script type='text/javascript' src='../js/views/scroll_down_button_view.js' data-cover></script>
 | 
			
		||||
  <script type='text/javascript' src='../js/views/banner_view.js' data-cover></script>
 | 
			
		||||
  <script type="text/javascript" src='../js/views/identity_key_send_error_view.js' data-cover></script>
 | 
			
		||||
  <script type='text/javascript' src='../js/views/clear_data_view.js'></script>
 | 
			
		||||
 | 
			
		||||
  <script type="text/javascript" src="views/whisper_view_test.js"></script>
 | 
			
		||||
  <script type="text/javascript" src="views/group_update_view_test.js"></script>
 | 
			
		||||
| 
						 | 
				
			
			@ -636,7 +636,6 @@
 | 
			
		|||
  <script type="text/javascript" src="views/last_seen_indicator_view_test.js"></script>
 | 
			
		||||
  <script type='text/javascript' src='views/scroll_down_button_view_test.js'></script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  <script type="text/javascript" src="models/conversations_test.js"></script>
 | 
			
		||||
  <script type="text/javascript" src="models/messages_test.js"></script>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue