Move all status/alert dialogs into the Left Pane
This commit is contained in:
parent
101070bf42
commit
18fd44f504
50 changed files with 1298 additions and 607 deletions
|
@ -575,6 +575,10 @@
|
||||||
"message": "Connecting",
|
"message": "Connecting",
|
||||||
"description": "Displayed when the desktop client is currently connecting to the server."
|
"description": "Displayed when the desktop client is currently connecting to the server."
|
||||||
},
|
},
|
||||||
|
"connectingHangOn": {
|
||||||
|
"message": "Shouldn't be long...",
|
||||||
|
"description": "Subtext description for when the client is connecting to the server."
|
||||||
|
},
|
||||||
"offline": {
|
"offline": {
|
||||||
"message": "Offline",
|
"message": "Offline",
|
||||||
"description": "Displayed when the desktop client has no network connection."
|
"description": "Displayed when the desktop client has no network connection."
|
||||||
|
@ -583,15 +587,6 @@
|
||||||
"message": "Check your network connection.",
|
"message": "Check your network connection.",
|
||||||
"description": "Obvious instructions for when a user's computer loses its network connection"
|
"description": "Obvious instructions for when a user's computer loses its network connection"
|
||||||
},
|
},
|
||||||
"attemptingReconnection": {
|
|
||||||
"message": "Attempting reconnect in $reconnect_duration_in_seconds$ seconds",
|
|
||||||
"placeholders": {
|
|
||||||
"reconnect_duration_in_seconds": {
|
|
||||||
"content": "$1",
|
|
||||||
"example": "10"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"submitDebugLog": {
|
"submitDebugLog": {
|
||||||
"message": "Debug log",
|
"message": "Debug log",
|
||||||
"description": "Menu item and header text for debug log modal (sentence case)"
|
"description": "Menu item and header text for debug log modal (sentence case)"
|
||||||
|
@ -839,12 +834,28 @@
|
||||||
"description": "Shown as the title of our update error dialogs on windows"
|
"description": "Shown as the title of our update error dialogs on windows"
|
||||||
},
|
},
|
||||||
"cannotUpdateDetail": {
|
"cannotUpdateDetail": {
|
||||||
"message": "Signal Desktop failed to update, but there is a new version available. Please go to https://signal.org/download and install the new version manually, then either contact support or file a bug about this problem.",
|
"message": "Signal Desktop failed to update, but there is a new version available. Please go to $url$ and install the new version manually, then either contact support or file a bug about this problem.",
|
||||||
"description": "Shown if a general error happened while trying to install update package"
|
"description": "Shown if a general error happened while trying to install update package",
|
||||||
|
"placeholders": {
|
||||||
|
"url": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "https://signal.org/download"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"readOnlyVolume": {
|
"readOnlyVolume": {
|
||||||
"message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.",
|
"message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving $app$ to $folder$ with Finder.",
|
||||||
"description": "Shown on MacOS if running on a read-only volume and we cannot update"
|
"description": "Shown on MacOS if running on a read-only volume and we cannot update",
|
||||||
|
"placeholders": {
|
||||||
|
"app": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Signal.app"
|
||||||
|
},
|
||||||
|
"folder": {
|
||||||
|
"content": "$2",
|
||||||
|
"example": "/Applications"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ok": {
|
"ok": {
|
||||||
"message": "OK"
|
"message": "OK"
|
||||||
|
|
|
@ -54,7 +54,6 @@
|
||||||
|
|
||||||
<script type='text/x-tmpl-mustache' id='two-column'>
|
<script type='text/x-tmpl-mustache' id='two-column'>
|
||||||
<div class='gutter'>
|
<div class='gutter'>
|
||||||
<div class='network-status-container' aria-live='assertive'></div>
|
|
||||||
<div class='left-pane-placeholder'></div>
|
<div class='left-pane-placeholder'></div>
|
||||||
</div>
|
</div>
|
||||||
<div class='conversation-stack'>
|
<div class='conversation-stack'>
|
||||||
|
@ -72,13 +71,6 @@
|
||||||
<div class='lightbox-container'></div>
|
<div class='lightbox-container'></div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type='text/x-tmpl-mustache' id='expired_alert'>
|
|
||||||
<a target='_blank' href='https://signal.org/download/'>
|
|
||||||
<button class='upgrade'>{{ upgrade }}</button>
|
|
||||||
</a>
|
|
||||||
{{ expiredWarning }}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type='text/x-tmpl-mustache' id='banner'>
|
<script type='text/x-tmpl-mustache' id='banner'>
|
||||||
<div class='body'>
|
<div class='body'>
|
||||||
<span class='icon warning'></span>
|
<span class='icon warning'></span>
|
||||||
|
@ -227,23 +219,6 @@
|
||||||
{{/isStep2}}
|
{{/isStep2}}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type='text/x-tmpl-mustache' id='networkStatus'>
|
|
||||||
<div class='network-status-message'>
|
|
||||||
<h3>{{ message }}</h3>
|
|
||||||
<span>{{ instructions }}</span>
|
|
||||||
</div>
|
|
||||||
{{ #reconnectDurationAsSeconds }}
|
|
||||||
<div class="network-status-message">
|
|
||||||
{{ attemptingReconnectionMessage }}
|
|
||||||
</div>
|
|
||||||
{{/reconnectDurationAsSeconds }}
|
|
||||||
{{ #action }}
|
|
||||||
<div class="action">
|
|
||||||
<button class='small blue {{ buttonClass }}'>{{ action }}</button>
|
|
||||||
</div>
|
|
||||||
{{/action }}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type='text/x-tmpl-mustache' id='import-flow-template'>
|
<script type='text/x-tmpl-mustache' id='import-flow-template'>
|
||||||
{{#isStep2}}
|
{{#isStep2}}
|
||||||
<div id='step2' class='step'>
|
<div id='step2' class='step'>
|
||||||
|
@ -464,8 +439,6 @@
|
||||||
<script type='text/javascript' src='js/expiring_tap_to_view_messages.js'></script>
|
<script type='text/javascript' src='js/expiring_tap_to_view_messages.js'></script>
|
||||||
|
|
||||||
<script type='text/javascript' src='js/chromium.js'></script>
|
<script type='text/javascript' src='js/chromium.js'></script>
|
||||||
<script type='text/javascript' src='js/registration.js'></script>
|
|
||||||
<script type='text/javascript' src='js/expire.js'></script>
|
|
||||||
<script type='text/javascript' src='js/conversation_controller.js'></script>
|
<script type='text/javascript' src='js/conversation_controller.js'></script>
|
||||||
<script type='text/javascript' src='js/message_controller.js'></script>
|
<script type='text/javascript' src='js/message_controller.js'></script>
|
||||||
|
|
||||||
|
@ -479,7 +452,6 @@
|
||||||
<script type='text/javascript' src='js/views/recorder_view.js'></script>
|
<script type='text/javascript' src='js/views/recorder_view.js'></script>
|
||||||
<script type='text/javascript' src='js/views/conversation_view.js'></script>
|
<script type='text/javascript' src='js/views/conversation_view.js'></script>
|
||||||
<script type='text/javascript' src='js/views/inbox_view.js'></script>
|
<script type='text/javascript' src='js/views/inbox_view.js'></script>
|
||||||
<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/confirmation_dialog_view.js'></script>
|
||||||
<script type='text/javascript' src='js/views/identicon_svg_view.js'></script>
|
<script type='text/javascript' src='js/views/identicon_svg_view.js'></script>
|
||||||
<script type='text/javascript' src='js/views/install_view.js'></script>
|
<script type='text/javascript' src='js/views/install_view.js'></script>
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22"><circle cx="11" cy="-1041.36" r="8" transform="matrix(1 0 0-1 0-1030.36)" opacity=".98" fill="#da4453"/><path d="m-26.309 18.07c-1.18 0-2.135.968-2.135 2.129v12.82c0 1.176.948 2.129 2.135 2.129 1.183 0 2.135-.968 2.135-2.129v-12.82c0-1.176-.946-2.129-2.135-2.129zm0 21.348c-1.18 0-2.135.954-2.135 2.135 0 1.18.954 2.135 2.135 2.135 1.181 0 2.135-.954 2.135-2.135 0-1.18-.952-2.135-2.135-2.135z" transform="matrix(.30056 0 0 .30056 18.902 1.728)" fill="#fff" stroke="#fff"/></svg>
|
|
Before Width: | Height: | Size: 539 B |
|
@ -244,7 +244,7 @@
|
||||||
};
|
};
|
||||||
Whisper.events.trigger('userChanged', user);
|
Whisper.events.trigger('userChanged', user);
|
||||||
|
|
||||||
Whisper.Registration.markDone();
|
window.Signal.Util.Registration.markDone();
|
||||||
window.log.info('dispatching registration event');
|
window.log.info('dispatching registration event');
|
||||||
Whisper.events.trigger('registration_done');
|
Whisper.events.trigger('registration_done');
|
||||||
});
|
});
|
||||||
|
@ -382,7 +382,10 @@
|
||||||
|
|
||||||
showStickerPack: async (packId, key) => {
|
showStickerPack: async (packId, key) => {
|
||||||
// We can get these events even if the user has never linked this instance.
|
// We can get these events even if the user has never linked this instance.
|
||||||
if (Whisper.Import.isIncomplete() || !Whisper.Registration.everDone()) {
|
if (
|
||||||
|
Whisper.Import.isIncomplete() ||
|
||||||
|
!window.Signal.Util.Registration.everDone()
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -559,6 +562,15 @@
|
||||||
} finally {
|
} finally {
|
||||||
initializeRedux();
|
initializeRedux();
|
||||||
start();
|
start();
|
||||||
|
window.Signal.Services.initializeNetworkObserver(
|
||||||
|
window.reduxActions.network
|
||||||
|
);
|
||||||
|
window.Signal.Services.initializeUpdateListener(
|
||||||
|
window.reduxActions.updates
|
||||||
|
);
|
||||||
|
window.reduxActions.expiration.hydrateExpirationStatus(
|
||||||
|
window.Signal.Util.hasExpired()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -609,10 +621,22 @@
|
||||||
Signal.State.Ducks.emojis.actions,
|
Signal.State.Ducks.emojis.actions,
|
||||||
store.dispatch
|
store.dispatch
|
||||||
);
|
);
|
||||||
|
actions.expiration = Signal.State.bindActionCreators(
|
||||||
|
Signal.State.Ducks.expiration.actions,
|
||||||
|
store.dispatch
|
||||||
|
);
|
||||||
actions.items = Signal.State.bindActionCreators(
|
actions.items = Signal.State.bindActionCreators(
|
||||||
Signal.State.Ducks.items.actions,
|
Signal.State.Ducks.items.actions,
|
||||||
store.dispatch
|
store.dispatch
|
||||||
);
|
);
|
||||||
|
actions.network = Signal.State.bindActionCreators(
|
||||||
|
Signal.State.Ducks.network.actions,
|
||||||
|
store.dispatch
|
||||||
|
);
|
||||||
|
actions.updates = Signal.State.bindActionCreators(
|
||||||
|
Signal.State.Ducks.updates.actions,
|
||||||
|
store.dispatch
|
||||||
|
);
|
||||||
actions.user = Signal.State.bindActionCreators(
|
actions.user = Signal.State.bindActionCreators(
|
||||||
Signal.State.Ducks.user.actions,
|
Signal.State.Ducks.user.actions,
|
||||||
store.dispatch
|
store.dispatch
|
||||||
|
@ -1351,7 +1375,7 @@
|
||||||
if (Whisper.Import.isIncomplete()) {
|
if (Whisper.Import.isIncomplete()) {
|
||||||
window.log.info('Import was interrupted, showing import error screen');
|
window.log.info('Import was interrupted, showing import error screen');
|
||||||
appView.openImporter();
|
appView.openImporter();
|
||||||
} else if (Whisper.Registration.everDone()) {
|
} else if (window.Signal.Util.Registration.everDone()) {
|
||||||
// listeners
|
// listeners
|
||||||
Whisper.RotateSignedPreKeyListener.init(Whisper.events, newVersion);
|
Whisper.RotateSignedPreKeyListener.init(Whisper.events, newVersion);
|
||||||
window.Signal.RefreshSenderCertificate.initialize({
|
window.Signal.RefreshSenderCertificate.initialize({
|
||||||
|
@ -1377,9 +1401,6 @@
|
||||||
Whisper.events.on('unauthorized', () => {
|
Whisper.events.on('unauthorized', () => {
|
||||||
appView.inboxView.networkStatusView.update();
|
appView.inboxView.networkStatusView.update();
|
||||||
});
|
});
|
||||||
Whisper.events.on('reconnectTimer', () => {
|
|
||||||
appView.inboxView.networkStatusView.setSocketReconnectInterval(60000);
|
|
||||||
});
|
|
||||||
Whisper.events.on('contactsync', () => {
|
Whisper.events.on('contactsync', () => {
|
||||||
if (appView.installView) {
|
if (appView.installView) {
|
||||||
appView.openInbox();
|
appView.openInbox();
|
||||||
|
@ -1479,7 +1500,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Whisper.Registration.everDone()) {
|
if (!window.Signal.Util.Registration.everDone()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Whisper.Import.isIncomplete()) {
|
if (Whisper.Import.isIncomplete()) {
|
||||||
|
@ -2299,7 +2320,7 @@
|
||||||
window.log.warn(
|
window.log.warn(
|
||||||
'Client is no longer authorized; deleting local configuration'
|
'Client is no longer authorized; deleting local configuration'
|
||||||
);
|
);
|
||||||
Whisper.Registration.remove();
|
window.Signal.Util.Registration.remove();
|
||||||
|
|
||||||
const NUMBER_ID_KEY = 'number_id';
|
const NUMBER_ID_KEY = 'number_id';
|
||||||
const VERSION_KEY = 'version';
|
const VERSION_KEY = 'version';
|
||||||
|
@ -2317,7 +2338,7 @@
|
||||||
|
|
||||||
// These two bits of data are important to ensure that the app loads up
|
// These two bits of data are important to ensure that the app loads up
|
||||||
// the conversation list, instead of showing just the QR code screen.
|
// the conversation list, instead of showing just the QR code screen.
|
||||||
Whisper.Registration.markEverDone();
|
window.Signal.Util.Registration.markEverDone();
|
||||||
textsecure.storage.put(NUMBER_ID_KEY, previousNumberId);
|
textsecure.storage.put(NUMBER_ID_KEY, previousNumberId);
|
||||||
|
|
||||||
// These two are important to ensure we don't rip through every message
|
// These two are important to ensure we don't rip through every message
|
||||||
|
|
22
js/expire.js
22
js/expire.js
|
@ -1,22 +0,0 @@
|
||||||
// eslint-disable-next-line func-names
|
|
||||||
(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let BUILD_EXPIRATION = 0;
|
|
||||||
try {
|
|
||||||
BUILD_EXPIRATION = parseInt(window.getExpiration(), 10);
|
|
||||||
if (BUILD_EXPIRATION) {
|
|
||||||
window.log.info(
|
|
||||||
'Build expires: ',
|
|
||||||
new Date(BUILD_EXPIRATION).toISOString()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
window.extension = window.extension || {};
|
|
||||||
|
|
||||||
window.extension.expired = () =>
|
|
||||||
BUILD_EXPIRATION && Date.now() > BUILD_EXPIRATION;
|
|
||||||
})();
|
|
|
@ -64,12 +64,16 @@ const {
|
||||||
const { createStore } = require('../../ts/state/createStore');
|
const { createStore } = require('../../ts/state/createStore');
|
||||||
const conversationsDuck = require('../../ts/state/ducks/conversations');
|
const conversationsDuck = require('../../ts/state/ducks/conversations');
|
||||||
const emojisDuck = require('../../ts/state/ducks/emojis');
|
const emojisDuck = require('../../ts/state/ducks/emojis');
|
||||||
|
const expirationDuck = require('../../ts/state/ducks/expiration');
|
||||||
const itemsDuck = require('../../ts/state/ducks/items');
|
const itemsDuck = require('../../ts/state/ducks/items');
|
||||||
|
const networkDuck = require('../../ts/state/ducks/network');
|
||||||
const searchDuck = require('../../ts/state/ducks/search');
|
const searchDuck = require('../../ts/state/ducks/search');
|
||||||
const stickersDuck = require('../../ts/state/ducks/stickers');
|
const stickersDuck = require('../../ts/state/ducks/stickers');
|
||||||
|
const updatesDuck = require('../../ts/state/ducks/updates');
|
||||||
const userDuck = require('../../ts/state/ducks/user');
|
const userDuck = require('../../ts/state/ducks/user');
|
||||||
|
|
||||||
const conversationsSelectors = require('../../ts/state/selectors/conversations');
|
const conversationsSelectors = require('../../ts/state/selectors/conversations');
|
||||||
|
const registrationSelectors = require('../../ts/state/selectors/registration');
|
||||||
const searchSelectors = require('../../ts/state/selectors/search');
|
const searchSelectors = require('../../ts/state/selectors/search');
|
||||||
|
|
||||||
// Migrations
|
// Migrations
|
||||||
|
@ -98,6 +102,14 @@ const Initialization = require('./views/initialization');
|
||||||
const { IdleDetector } = require('./idle_detector');
|
const { IdleDetector } = require('./idle_detector');
|
||||||
const MessageDataMigrator = require('./messages_data_migrator');
|
const MessageDataMigrator = require('./messages_data_migrator');
|
||||||
|
|
||||||
|
// Processes / Services
|
||||||
|
const {
|
||||||
|
initializeNetworkObserver,
|
||||||
|
} = require('../../ts/services/networkObserver');
|
||||||
|
const {
|
||||||
|
initializeUpdateListener,
|
||||||
|
} = require('../../ts/services/updateListener');
|
||||||
|
|
||||||
function initializeMigrations({
|
function initializeMigrations({
|
||||||
userDataPath,
|
userDataPath,
|
||||||
getRegionCode,
|
getRegionCode,
|
||||||
|
@ -284,19 +296,30 @@ exports.setup = (options = {}) => {
|
||||||
createStickerPreviewModal,
|
createStickerPreviewModal,
|
||||||
createTimeline,
|
createTimeline,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Ducks = {
|
const Ducks = {
|
||||||
conversations: conversationsDuck,
|
conversations: conversationsDuck,
|
||||||
emojis: emojisDuck,
|
emojis: emojisDuck,
|
||||||
|
expiration: expirationDuck,
|
||||||
items: itemsDuck,
|
items: itemsDuck,
|
||||||
|
network: networkDuck,
|
||||||
|
updates: updatesDuck,
|
||||||
user: userDuck,
|
user: userDuck,
|
||||||
search: searchDuck,
|
search: searchDuck,
|
||||||
stickers: stickersDuck,
|
stickers: stickersDuck,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Selectors = {
|
const Selectors = {
|
||||||
conversations: conversationsSelectors,
|
conversations: conversationsSelectors,
|
||||||
|
registration: registrationSelectors,
|
||||||
search: searchSelectors,
|
search: searchSelectors,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Services = {
|
||||||
|
initializeNetworkObserver,
|
||||||
|
initializeUpdateListener,
|
||||||
|
};
|
||||||
|
|
||||||
const State = {
|
const State = {
|
||||||
bindActionCreators,
|
bindActionCreators,
|
||||||
createStore,
|
createStore,
|
||||||
|
@ -344,6 +367,7 @@ exports.setup = (options = {}) => {
|
||||||
OS,
|
OS,
|
||||||
RefreshSenderCertificate,
|
RefreshSenderCertificate,
|
||||||
Settings,
|
Settings,
|
||||||
|
Services,
|
||||||
State,
|
State,
|
||||||
Stickers,
|
Stickers,
|
||||||
Types,
|
Types,
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
/* global storage, Whisper */
|
|
||||||
|
|
||||||
// eslint-disable-next-line func-names
|
|
||||||
(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
Whisper.Registration = {
|
|
||||||
markEverDone() {
|
|
||||||
storage.put('chromiumRegistrationDoneEver', '');
|
|
||||||
},
|
|
||||||
markDone() {
|
|
||||||
this.markEverDone();
|
|
||||||
storage.put('chromiumRegistrationDone', '');
|
|
||||||
},
|
|
||||||
isDone() {
|
|
||||||
return storage.get('chromiumRegistrationDone') === '';
|
|
||||||
},
|
|
||||||
everDone() {
|
|
||||||
return (
|
|
||||||
storage.get('chromiumRegistrationDoneEver') === '' ||
|
|
||||||
storage.get('chromiumRegistrationDone') === ''
|
|
||||||
);
|
|
||||||
},
|
|
||||||
remove() {
|
|
||||||
storage.remove('chromiumRegistrationDone');
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -83,7 +83,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
events.on('timetravel', () => {
|
events.on('timetravel', () => {
|
||||||
if (Whisper.Registration.isDone()) {
|
if (window.Signal.Util.Registration.isDone()) {
|
||||||
setTimeoutForNextRun();
|
setTimeoutForNextRun();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,7 +29,6 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
'click .openInstaller': 'openInstaller', // NetworkStatusView has this button
|
|
||||||
openInbox: 'openInbox',
|
openInbox: 'openInbox',
|
||||||
},
|
},
|
||||||
applyTheme() {
|
applyTheme() {
|
||||||
|
|
|
@ -2642,7 +2642,7 @@
|
||||||
this.model.clearTypingTimers();
|
this.model.clearTypingTimers();
|
||||||
|
|
||||||
let ToastView;
|
let ToastView;
|
||||||
if (extension.expired()) {
|
if (window.reduxStore.getState().expiration.hasExpired) {
|
||||||
ToastView = Whisper.ExpiredToast;
|
ToastView = Whisper.ExpiredToast;
|
||||||
}
|
}
|
||||||
if (this.model.isPrivate() && storage.isBlocked(this.model.id)) {
|
if (this.model.isPrivate() && storage.isBlocked(this.model.id)) {
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
/* global
|
/* global
|
||||||
ConversationController,
|
ConversationController,
|
||||||
extension,
|
|
||||||
getInboxCollection,
|
|
||||||
i18n,
|
i18n,
|
||||||
Whisper,
|
Whisper,
|
||||||
Signal
|
Signal
|
||||||
|
@ -95,25 +93,6 @@
|
||||||
this.setupLeftPane();
|
this.setupLeftPane();
|
||||||
}
|
}
|
||||||
|
|
||||||
const inboxCollection = getInboxCollection();
|
|
||||||
|
|
||||||
this.listenTo(inboxCollection, 'messageError', () => {
|
|
||||||
if (this.networkStatusView) {
|
|
||||||
this.networkStatusView.update();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.networkStatusView = new Whisper.NetworkStatusView();
|
|
||||||
this.$el
|
|
||||||
.find('.network-status-container')
|
|
||||||
.append(this.networkStatusView.render().el);
|
|
||||||
|
|
||||||
if (extension.expired()) {
|
|
||||||
const banner = new Whisper.ExpiredAlertBanner().render();
|
|
||||||
banner.$el.prependTo(this.$el);
|
|
||||||
this.$el.addClass('expired');
|
|
||||||
}
|
|
||||||
|
|
||||||
Whisper.events.on('pack-install-failed', () => {
|
Whisper.events.on('pack-install-failed', () => {
|
||||||
const toast = new Whisper.StickerPackInstallFailedToast();
|
const toast = new Whisper.StickerPackInstallFailedToast();
|
||||||
toast.$el.appendTo(this.$el);
|
toast.$el.appendTo(this.$el);
|
||||||
|
@ -225,15 +204,4 @@
|
||||||
this.closeRecording(e);
|
this.closeRecording(e);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Whisper.ExpiredAlertBanner = Whisper.View.extend({
|
|
||||||
templateName: 'expired_alert',
|
|
||||||
className: 'expiredAlert clearfix',
|
|
||||||
render_attributes() {
|
|
||||||
return {
|
|
||||||
expiredWarning: i18n('expiredWarning'),
|
|
||||||
upgrade: i18n('upgrade'),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
|
|
||||||
// Keep data around if it's a re-link, or the middle of a light import
|
// Keep data around if it's a re-link, or the middle of a light import
|
||||||
this.shouldRetainData =
|
this.shouldRetainData =
|
||||||
Whisper.Registration.everDone() || options.hasExistingData;
|
window.Signal.Util.Registration.everDone() || options.hasExistingData;
|
||||||
},
|
},
|
||||||
render_attributes() {
|
render_attributes() {
|
||||||
let errorMessage;
|
let errorMessage;
|
||||||
|
|
|
@ -1,122 +0,0 @@
|
||||||
/* global Whisper, extension, Backbone, moment, i18n */
|
|
||||||
|
|
||||||
// eslint-disable-next-line func-names
|
|
||||||
(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
window.Whisper = window.Whisper || {};
|
|
||||||
|
|
||||||
Whisper.NetworkStatusView = Whisper.View.extend({
|
|
||||||
className: 'network-status',
|
|
||||||
templateName: 'networkStatus',
|
|
||||||
initialize() {
|
|
||||||
this.$el.hide();
|
|
||||||
|
|
||||||
this.renderIntervalHandle = setInterval(this.update.bind(this), 5000);
|
|
||||||
extension.windows.onClosed(() => {
|
|
||||||
clearInterval(this.renderIntervalHandle);
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(this.finishConnectingGracePeriod.bind(this), 5000);
|
|
||||||
|
|
||||||
this.withinConnectingGracePeriod = true;
|
|
||||||
this.setSocketReconnectInterval(null);
|
|
||||||
|
|
||||||
window.addEventListener('online', this.update.bind(this));
|
|
||||||
window.addEventListener('offline', this.update.bind(this));
|
|
||||||
|
|
||||||
this.model = new Backbone.Model();
|
|
||||||
this.listenTo(this.model, 'change', this.onChange);
|
|
||||||
},
|
|
||||||
onReconnectTimer() {
|
|
||||||
this.setSocketReconnectInterval(60000);
|
|
||||||
},
|
|
||||||
finishConnectingGracePeriod() {
|
|
||||||
this.withinConnectingGracePeriod = false;
|
|
||||||
},
|
|
||||||
setSocketReconnectInterval(millis) {
|
|
||||||
this.socketReconnectWaitDuration = moment.duration(millis);
|
|
||||||
},
|
|
||||||
navigatorOnLine() {
|
|
||||||
return navigator.onLine;
|
|
||||||
},
|
|
||||||
getSocketStatus() {
|
|
||||||
return window.getSocketStatus();
|
|
||||||
},
|
|
||||||
getNetworkStatus() {
|
|
||||||
let message = '';
|
|
||||||
let instructions = '';
|
|
||||||
let hasInterruption = false;
|
|
||||||
let action = null;
|
|
||||||
let buttonClass = null;
|
|
||||||
|
|
||||||
const socketStatus = this.getSocketStatus();
|
|
||||||
switch (socketStatus) {
|
|
||||||
case WebSocket.CONNECTING:
|
|
||||||
message = i18n('connecting');
|
|
||||||
this.setSocketReconnectInterval(null);
|
|
||||||
break;
|
|
||||||
case WebSocket.OPEN:
|
|
||||||
this.setSocketReconnectInterval(null);
|
|
||||||
break;
|
|
||||||
case WebSocket.CLOSED:
|
|
||||||
message = i18n('disconnected');
|
|
||||||
instructions = i18n('checkNetworkConnection');
|
|
||||||
hasInterruption = true;
|
|
||||||
break;
|
|
||||||
case WebSocket.CLOSING:
|
|
||||||
default:
|
|
||||||
message = i18n('disconnected');
|
|
||||||
instructions = i18n('checkNetworkConnection');
|
|
||||||
hasInterruption = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
socketStatus === WebSocket.CONNECTING &&
|
|
||||||
!this.withinConnectingGracePeriod
|
|
||||||
) {
|
|
||||||
hasInterruption = true;
|
|
||||||
}
|
|
||||||
if (this.socketReconnectWaitDuration.asSeconds() > 0) {
|
|
||||||
instructions = i18n('attemptingReconnection', [
|
|
||||||
this.socketReconnectWaitDuration.asSeconds(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (!this.navigatorOnLine()) {
|
|
||||||
hasInterruption = true;
|
|
||||||
message = i18n('offline');
|
|
||||||
instructions = i18n('checkNetworkConnection');
|
|
||||||
} else if (!Whisper.Registration.isDone()) {
|
|
||||||
hasInterruption = true;
|
|
||||||
message = i18n('unlinked');
|
|
||||||
instructions = i18n('unlinkedWarning');
|
|
||||||
action = i18n('relink');
|
|
||||||
buttonClass = 'openInstaller';
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
message,
|
|
||||||
instructions,
|
|
||||||
hasInterruption,
|
|
||||||
action,
|
|
||||||
buttonClass,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
update() {
|
|
||||||
const status = this.getNetworkStatus();
|
|
||||||
this.model.set(status);
|
|
||||||
},
|
|
||||||
render_attributes() {
|
|
||||||
return this.model.attributes;
|
|
||||||
},
|
|
||||||
onChange() {
|
|
||||||
this.render();
|
|
||||||
if (this.model.attributes.hasInterruption) {
|
|
||||||
this.$el.slideDown();
|
|
||||||
} else {
|
|
||||||
this.$el.hide();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
})();
|
|
|
@ -296,28 +296,6 @@ $loading-height: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.expiredAlert {
|
|
||||||
background: $color-accent-yellow;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
button {
|
|
||||||
float: right;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 36px;
|
|
||||||
padding: 0 20px;
|
|
||||||
margin-left: 20px;
|
|
||||||
|
|
||||||
color: $color-white;
|
|
||||||
background: $color-signal-blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes loading {
|
@keyframes loading {
|
||||||
50% {
|
50% {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
|
|
|
@ -51,45 +51,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.network-status-container {
|
|
||||||
.network-status {
|
|
||||||
background: url('../images/error_red.svg') no-repeat left 10px center;
|
|
||||||
background-size: 25px 25px;
|
|
||||||
background-color: $color-accent-yellow;
|
|
||||||
padding: 10px;
|
|
||||||
padding-left: 48px;
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
.network-status-message {
|
|
||||||
h3 {
|
|
||||||
padding: 0px;
|
|
||||||
margin: 0px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
span {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include dark-theme {
|
|
||||||
color: $color-gray-90;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.action {
|
|
||||||
button {
|
|
||||||
border-radius: 5px;
|
|
||||||
border: solid 1px $color-gray-25;
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: inherit;
|
|
||||||
color: $color-white;
|
|
||||||
background: $color-signal-blue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-pane-placeholder {
|
.left-pane-placeholder {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4685,8 +4685,10 @@ button.module-image__border-overlay:focus {
|
||||||
.module-search-results__conversations-header {
|
.module-search-results__conversations-header {
|
||||||
@include font-body-1-bold;
|
@include font-body-1-bold;
|
||||||
|
|
||||||
height: 36px;
|
height: 52px;
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
|
||||||
@include dark-theme {
|
@include dark-theme {
|
||||||
color: $color-gray-05;
|
color: $color-gray-05;
|
||||||
|
@ -4716,8 +4718,10 @@ button.module-image__border-overlay:focus {
|
||||||
.module-search-results__messages-header {
|
.module-search-results__messages-header {
|
||||||
@include font-body-1-bold;
|
@include font-body-1-bold;
|
||||||
|
|
||||||
height: 36px;
|
height: 52px;
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
|
||||||
@include dark-theme {
|
@include dark-theme {
|
||||||
color: $color-gray-05;
|
color: $color-gray-05;
|
||||||
|
@ -6381,6 +6385,79 @@ button.module-image__border-overlay:focus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module-left-pane-dialog {
|
||||||
|
background: $color-accent-green;
|
||||||
|
color: $color-white;
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.module-left-pane-dialog__message {
|
||||||
|
h3 {
|
||||||
|
@include font-body-1-bold;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
@include font-body-1;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-left-pane-dialog__actions {
|
||||||
|
margin-top: 8px;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
.module-left-pane-dialog__link {
|
||||||
|
@include keyboard-mode {
|
||||||
|
display: inline-block;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: inherit;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: solid 1px $color-white;
|
||||||
|
color: $color-white;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
margin: 0 4px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
outline: 0;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
@include keyboard-mode {
|
||||||
|
box-shadow: 0 0 0 3px $color-signal-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@include mouse-mode {
|
||||||
|
box-shadow: 0 0 0 3px $color-signal-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-left-pane-dialog__button--no-border {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.module-left-pane-dialog--error {
|
||||||
|
background-color: $color-accent-red;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.module-left-pane-dialog--warning {
|
||||||
|
background-color: $color-accent-yellow;
|
||||||
|
color: $color-black;
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-color: $color-black;
|
||||||
|
color: $color-black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Module: Emoji Picker
|
// Module: Emoji Picker
|
||||||
|
|
||||||
%module-emoji-picker--ribbon {
|
%module-emoji-picker--ribbon {
|
||||||
|
|
|
@ -8,10 +8,6 @@ describe('i18n', () => {
|
||||||
it('returns message for given string', () => {
|
it('returns message for given string', () => {
|
||||||
assert.equal(i18n('reportIssue'), 'Report an issue');
|
assert.equal(i18n('reportIssue'), 'Report an issue');
|
||||||
});
|
});
|
||||||
it('returns message with single substitution', () => {
|
|
||||||
const actual = i18n('attemptingReconnection', 5);
|
|
||||||
assert.equal(actual, 'Attempting reconnect in 5 seconds');
|
|
||||||
});
|
|
||||||
it('returns message with multiple substitutions', () => {
|
it('returns message with multiple substitutions', () => {
|
||||||
const actual = i18n('theyChangedTheTimer', ['Someone', '5 minutes']);
|
const actual = i18n('theyChangedTheTimer', ['Someone', '5 minutes']);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
|
|
|
@ -35,7 +35,6 @@
|
||||||
|
|
||||||
<script type='text/x-tmpl-mustache' id='two-column'>
|
<script type='text/x-tmpl-mustache' id='two-column'>
|
||||||
<div class='gutter'>
|
<div class='gutter'>
|
||||||
<div class='network-status-container'></div>
|
|
||||||
<div class='left-pane-placeholder'></div>
|
<div class='left-pane-placeholder'></div>
|
||||||
</div>
|
</div>
|
||||||
<div class='conversation-stack'>
|
<div class='conversation-stack'>
|
||||||
|
@ -66,13 +65,6 @@
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type='text/x-tmpl-mustache' id='expired_alert'>
|
|
||||||
<a target='_blank' href='https://signal.org/download/'>
|
|
||||||
<button class='upgrade'>{{ upgrade }}</button>
|
|
||||||
</a>
|
|
||||||
{{ expiredWarning }}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type='text/x-tmpl-mustache' id='banner'>
|
<script type='text/x-tmpl-mustache' id='banner'>
|
||||||
<div class='body'>
|
<div class='body'>
|
||||||
<span class='icon warning'></span>
|
<span class='icon warning'></span>
|
||||||
|
@ -237,23 +229,6 @@
|
||||||
{{/isStep2}}
|
{{/isStep2}}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type='text/x-tmpl-mustache' id='networkStatus'>
|
|
||||||
<div class='network-status-message'>
|
|
||||||
<h3>{{ message }}</h3>
|
|
||||||
<span>{{ instructions }}</span>
|
|
||||||
</div>
|
|
||||||
{{ #reconnectDurationAsSeconds }}
|
|
||||||
<div class="network-status-message">
|
|
||||||
{{ attemptingReconnectionMessage }}
|
|
||||||
</div>
|
|
||||||
{{/reconnectDurationAsSeconds }}
|
|
||||||
{{ #action }}
|
|
||||||
<div class="action">
|
|
||||||
<button class='small blue {{ buttonClass }}'>{{ action }}</button>
|
|
||||||
</div>
|
|
||||||
{{/action }}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type='text/x-tmpl-mustache' id='import-flow-template'>
|
<script type='text/x-tmpl-mustache' id='import-flow-template'>
|
||||||
{{#isStep2}}
|
{{#isStep2}}
|
||||||
<div id='step2' class='step'>
|
<div id='step2' class='step'>
|
||||||
|
@ -459,7 +434,6 @@
|
||||||
<script type="text/javascript" src="test.js"></script>
|
<script type="text/javascript" src="test.js"></script>
|
||||||
|
|
||||||
<script type='text/javascript' src='../js/registration.js' data-cover></script>
|
<script type='text/javascript' src='../js/registration.js' data-cover></script>
|
||||||
<script type="text/javascript" src="../js/expire.js" data-cover></script>
|
|
||||||
<script type="text/javascript" src="../js/chromium.js" data-cover></script>
|
<script type="text/javascript" src="../js/chromium.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../js/database.js" data-cover></script>
|
<script type="text/javascript" src="../js/database.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../js/storage.js" data-cover></script>
|
<script type="text/javascript" src="../js/storage.js" data-cover></script>
|
||||||
|
@ -490,7 +464,6 @@
|
||||||
<script type='text/javascript' src='../js/views/recorder_view.js' data-cover></script>
|
<script type='text/javascript' src='../js/views/recorder_view.js' data-cover></script>
|
||||||
<script type='text/javascript' src='../js/views/conversation_view.js' data-cover></script>
|
<script type='text/javascript' src='../js/views/conversation_view.js' data-cover></script>
|
||||||
<script type='text/javascript' src='../js/views/inbox_view.js' data-cover></script>
|
<script type='text/javascript' src='../js/views/inbox_view.js' data-cover></script>
|
||||||
<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/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/identicon_svg_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/banner_view.js' data-cover></script>
|
||||||
|
@ -500,7 +473,6 @@
|
||||||
|
|
||||||
<script type="text/javascript" src="views/whisper_view_test.js"></script>
|
<script type="text/javascript" src="views/whisper_view_test.js"></script>
|
||||||
<script type="text/javascript" src="views/list_view_test.js"></script>
|
<script type="text/javascript" src="views/list_view_test.js"></script>
|
||||||
<script type="text/javascript" src="views/network_status_view_test.js"></script>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="models/messages_test.js"></script>
|
<script type="text/javascript" src="models/messages_test.js"></script>
|
||||||
|
|
||||||
|
|
|
@ -1,180 +0,0 @@
|
||||||
/* global _, $, Whisper */
|
|
||||||
|
|
||||||
describe('NetworkStatusView', () => {
|
|
||||||
describe('getNetworkStatus', () => {
|
|
||||||
let networkStatusView;
|
|
||||||
let socketStatus = WebSocket.OPEN;
|
|
||||||
|
|
||||||
let oldGetSocketStatus;
|
|
||||||
|
|
||||||
/* BEGIN stubbing globals */
|
|
||||||
before(() => {
|
|
||||||
oldGetSocketStatus = window.getSocketStatus;
|
|
||||||
window.getSocketStatus = () => socketStatus;
|
|
||||||
});
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
window.getSocketStatus = oldGetSocketStatus;
|
|
||||||
|
|
||||||
// It turns out that continued calls to window.getSocketStatus happen
|
|
||||||
// because we host NetworkStatusView in three mock interfaces, and the view
|
|
||||||
// checks every N seconds. That results in infinite errors unless there is
|
|
||||||
// something to call.
|
|
||||||
window.getSocketStatus = () => WebSocket.OPEN;
|
|
||||||
});
|
|
||||||
/* END stubbing globals */
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
networkStatusView = new Whisper.NetworkStatusView();
|
|
||||||
$('.network-status-container').append(networkStatusView.el);
|
|
||||||
});
|
|
||||||
afterEach(() => {
|
|
||||||
// prevents huge number of errors on console after running tests
|
|
||||||
clearInterval(networkStatusView.renderIntervalHandle);
|
|
||||||
networkStatusView = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('initialization', () => {
|
|
||||||
it('should have an empty interval', () => {
|
|
||||||
assert.equal(
|
|
||||||
networkStatusView.socketReconnectWaitDuration.asSeconds(),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('network status with no connection', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
networkStatusView.navigatorOnLine = () => false;
|
|
||||||
});
|
|
||||||
it('should be interrupted', () => {
|
|
||||||
networkStatusView.update();
|
|
||||||
const status = networkStatusView.getNetworkStatus();
|
|
||||||
assert(status.hasInterruption);
|
|
||||||
assert.equal(status.instructions, 'Check your network connection.');
|
|
||||||
});
|
|
||||||
it('should display an offline message', () => {
|
|
||||||
networkStatusView.update();
|
|
||||||
assert.match(networkStatusView.$el.text(), /Offline/);
|
|
||||||
});
|
|
||||||
it('should override socket status', () => {
|
|
||||||
_([
|
|
||||||
WebSocket.CONNECTING,
|
|
||||||
WebSocket.OPEN,
|
|
||||||
WebSocket.CLOSING,
|
|
||||||
WebSocket.CLOSED,
|
|
||||||
]).forEach(socketStatusVal => {
|
|
||||||
socketStatus = socketStatusVal;
|
|
||||||
networkStatusView.update();
|
|
||||||
assert.match(networkStatusView.$el.text(), /Offline/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('should override registration status', () => {
|
|
||||||
Whisper.Registration.remove();
|
|
||||||
networkStatusView.update();
|
|
||||||
assert.match(networkStatusView.$el.text(), /Offline/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('network status when registration is not done', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
Whisper.Registration.remove();
|
|
||||||
});
|
|
||||||
it('should display an unlinked message', () => {
|
|
||||||
networkStatusView.update();
|
|
||||||
assert.match(networkStatusView.$el.text(), /Relink/);
|
|
||||||
});
|
|
||||||
it('should override socket status', () => {
|
|
||||||
_([
|
|
||||||
WebSocket.CONNECTING,
|
|
||||||
WebSocket.OPEN,
|
|
||||||
WebSocket.CLOSING,
|
|
||||||
WebSocket.CLOSED,
|
|
||||||
]).forEach(socketStatusVal => {
|
|
||||||
socketStatus = socketStatusVal;
|
|
||||||
networkStatusView.update();
|
|
||||||
assert.match(networkStatusView.$el.text(), /Relink/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('network status when registration is done', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
networkStatusView.navigatorOnLine = () => true;
|
|
||||||
Whisper.Registration.markDone();
|
|
||||||
networkStatusView.update();
|
|
||||||
});
|
|
||||||
it('should not display an unlinked message', () => {
|
|
||||||
networkStatusView.update();
|
|
||||||
assert.notMatch(networkStatusView.$el.text(), /Relink/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('network status when socket is connecting', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
Whisper.Registration.markDone();
|
|
||||||
socketStatus = WebSocket.CONNECTING;
|
|
||||||
networkStatusView.update();
|
|
||||||
});
|
|
||||||
it('it should display a connecting string if connecting and not in the connecting grace period', () => {
|
|
||||||
networkStatusView.withinConnectingGracePeriod = false;
|
|
||||||
networkStatusView.getNetworkStatus();
|
|
||||||
|
|
||||||
assert.match(networkStatusView.$el.text(), /Connecting/);
|
|
||||||
});
|
|
||||||
it('it should not be interrupted if in connecting grace period', () => {
|
|
||||||
assert(networkStatusView.withinConnectingGracePeriod);
|
|
||||||
const status = networkStatusView.getNetworkStatus();
|
|
||||||
|
|
||||||
assert.match(networkStatusView.$el.text(), /Connecting/);
|
|
||||||
assert(!status.hasInterruption);
|
|
||||||
});
|
|
||||||
it('it should be interrupted if connecting grace period is over', () => {
|
|
||||||
networkStatusView.withinConnectingGracePeriod = false;
|
|
||||||
const status = networkStatusView.getNetworkStatus();
|
|
||||||
|
|
||||||
assert(status.hasInterruption);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('network status when socket is open', () => {
|
|
||||||
before(() => {
|
|
||||||
socketStatus = WebSocket.OPEN;
|
|
||||||
});
|
|
||||||
it('should not be interrupted', () => {
|
|
||||||
const status = networkStatusView.getNetworkStatus();
|
|
||||||
assert(!status.hasInterruption);
|
|
||||||
assert.match(
|
|
||||||
networkStatusView.$el
|
|
||||||
.find('.network-status-message')
|
|
||||||
.text()
|
|
||||||
.trim(),
|
|
||||||
/^$/
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('network status when socket is closed or closing', () => {
|
|
||||||
_([WebSocket.CLOSED, WebSocket.CLOSING]).forEach(socketStatusVal => {
|
|
||||||
it('should be interrupted', () => {
|
|
||||||
socketStatus = socketStatusVal;
|
|
||||||
networkStatusView.update();
|
|
||||||
const status = networkStatusView.getNetworkStatus();
|
|
||||||
assert(status.hasInterruption);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('the socket reconnect interval', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
socketStatus = WebSocket.CLOSED;
|
|
||||||
networkStatusView.setSocketReconnectInterval(61000);
|
|
||||||
networkStatusView.update();
|
|
||||||
});
|
|
||||||
it('should format the message based on the socketReconnectWaitDuration property', () => {
|
|
||||||
assert.equal(
|
|
||||||
networkStatusView.socketReconnectWaitDuration.asSeconds(),
|
|
||||||
61
|
|
||||||
);
|
|
||||||
assert.match(
|
|
||||||
networkStatusView.$('.network-status-message:last').text(),
|
|
||||||
/Attempting reconnect/
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it('should be reset by changing the socketStatus to CONNECTING', () => {});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
21
ts/components/ExpiredBuildDialog.stories.tsx
Normal file
21
ts/components/ExpiredBuildDialog.stories.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { ExpiredBuildDialog } from './ExpiredBuildDialog';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import { setup as setupI18n } from '../../js/modules/i18n';
|
||||||
|
// @ts-ignore
|
||||||
|
import enMessages from '../../_locales/en/messages.json';
|
||||||
|
|
||||||
|
import { storiesOf } from '@storybook/react';
|
||||||
|
import { boolean } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
storiesOf('Components/ExpiredBuildDialog', module).add(
|
||||||
|
'ExpiredBuildDialog',
|
||||||
|
() => {
|
||||||
|
const hasExpired = boolean('hasExpired', true);
|
||||||
|
|
||||||
|
return <ExpiredBuildDialog hasExpired={hasExpired} i18n={i18n} />;
|
||||||
|
}
|
||||||
|
);
|
34
ts/components/ExpiredBuildDialog.tsx
Normal file
34
ts/components/ExpiredBuildDialog.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { LocalizerType } from '../types/Util';
|
||||||
|
|
||||||
|
interface PropsType {
|
||||||
|
hasExpired: boolean;
|
||||||
|
i18n: LocalizerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ExpiredBuildDialog = ({
|
||||||
|
hasExpired,
|
||||||
|
i18n,
|
||||||
|
}: PropsType): JSX.Element | null => {
|
||||||
|
if (!hasExpired) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="module-left-pane-dialog module-left-pane-dialog--error">
|
||||||
|
{i18n('expiredWarning')}
|
||||||
|
<div className="module-left-pane-dialog__actions">
|
||||||
|
<a
|
||||||
|
className="module-left-pane-dialog__link"
|
||||||
|
href="https://signal.org/download/"
|
||||||
|
rel="noreferrer"
|
||||||
|
tabIndex={-1}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<button className="upgrade">{i18n('upgrade')}</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -32,8 +32,11 @@ export interface PropsType {
|
||||||
showInbox: () => void;
|
showInbox: () => void;
|
||||||
|
|
||||||
// Render Props
|
// Render Props
|
||||||
|
renderExpiredBuildDialog: () => JSX.Element;
|
||||||
renderMainHeader: () => JSX.Element;
|
renderMainHeader: () => JSX.Element;
|
||||||
renderMessageSearchResult: (id: string) => JSX.Element;
|
renderMessageSearchResult: (id: string) => JSX.Element;
|
||||||
|
renderNetworkStatus: () => JSX.Element;
|
||||||
|
renderUpdateDialog: () => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
// from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5
|
// from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5
|
||||||
|
@ -378,13 +381,22 @@ export class LeftPane extends React.Component<PropsType> {
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
const { renderMainHeader, showArchived } = this.props;
|
const {
|
||||||
|
renderExpiredBuildDialog,
|
||||||
|
renderMainHeader,
|
||||||
|
renderNetworkStatus,
|
||||||
|
renderUpdateDialog,
|
||||||
|
showArchived,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-left-pane">
|
<div className="module-left-pane">
|
||||||
<div className="module-left-pane__header">
|
<div className="module-left-pane__header">
|
||||||
{showArchived ? this.renderArchivedHeader() : renderMainHeader()}
|
{showArchived ? this.renderArchivedHeader() : renderMainHeader()}
|
||||||
</div>
|
</div>
|
||||||
|
{renderExpiredBuildDialog()}
|
||||||
|
{renderNetworkStatus()}
|
||||||
|
{renderUpdateDialog()}
|
||||||
{this.renderList()}
|
{this.renderList()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
98
ts/components/NetworkStatus.stories.tsx
Normal file
98
ts/components/NetworkStatus.stories.tsx
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { NetworkStatus } from './NetworkStatus';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import { setup as setupI18n } from '../../js/modules/i18n';
|
||||||
|
// @ts-ignore
|
||||||
|
import enMessages from '../../_locales/en/messages.json';
|
||||||
|
|
||||||
|
import { storiesOf } from '@storybook/react';
|
||||||
|
import { boolean, select } from '@storybook/addon-knobs';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
hasNetworkDialog: true,
|
||||||
|
i18n,
|
||||||
|
isOnline: true,
|
||||||
|
isRegistrationDone: true,
|
||||||
|
socketStatus: 0,
|
||||||
|
relinkDevice: action('relink-device'),
|
||||||
|
withinConnectingGracePeriod: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const permutations = [
|
||||||
|
{
|
||||||
|
title: 'Connecting',
|
||||||
|
props: {
|
||||||
|
socketStatus: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Closing (online)',
|
||||||
|
props: {
|
||||||
|
socketStatus: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Closed (online)',
|
||||||
|
props: {
|
||||||
|
socketStatus: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Offline',
|
||||||
|
props: {
|
||||||
|
isOnline: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Unlinked (online)',
|
||||||
|
props: {
|
||||||
|
isRegistrationDone: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Unlinked (offline)',
|
||||||
|
props: {
|
||||||
|
isOnline: false,
|
||||||
|
isRegistrationDone: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
storiesOf('Components/NetworkStatus', module)
|
||||||
|
.add('Knobs Playground', () => {
|
||||||
|
const hasNetworkDialog = boolean('hasNetworkDialog', true);
|
||||||
|
const isOnline = boolean('isOnline', true);
|
||||||
|
const isRegistrationDone = boolean('isRegistrationDone', true);
|
||||||
|
const socketStatus = select(
|
||||||
|
'socketStatus',
|
||||||
|
{
|
||||||
|
CONNECTING: 0,
|
||||||
|
OPEN: 1,
|
||||||
|
CLOSING: 2,
|
||||||
|
CLOSED: 3,
|
||||||
|
},
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NetworkStatus
|
||||||
|
{...defaultProps}
|
||||||
|
hasNetworkDialog={hasNetworkDialog}
|
||||||
|
isOnline={isOnline}
|
||||||
|
isRegistrationDone={isRegistrationDone}
|
||||||
|
socketStatus={socketStatus}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.add('Iterations', () => {
|
||||||
|
return permutations.map(({ props, title }) => (
|
||||||
|
<>
|
||||||
|
<h3>{title}</h3>
|
||||||
|
<NetworkStatus {...defaultProps} {...props} />
|
||||||
|
</>
|
||||||
|
));
|
||||||
|
});
|
83
ts/components/NetworkStatus.tsx
Normal file
83
ts/components/NetworkStatus.tsx
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { LocalizerType } from '../types/Util';
|
||||||
|
import { NetworkStateType } from '../state/ducks/network';
|
||||||
|
|
||||||
|
export interface PropsType extends NetworkStateType {
|
||||||
|
hasNetworkDialog: boolean;
|
||||||
|
i18n: LocalizerType;
|
||||||
|
isRegistrationDone: boolean;
|
||||||
|
relinkDevice: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type RenderDialogTypes = {
|
||||||
|
title: string;
|
||||||
|
subtext: string;
|
||||||
|
renderActionableButton?: () => JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
function renderDialog({
|
||||||
|
title,
|
||||||
|
subtext,
|
||||||
|
renderActionableButton,
|
||||||
|
}: RenderDialogTypes): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div className="module-left-pane-dialog module-left-pane-dialog--warning">
|
||||||
|
<div className="module-left-pane-dialog__message">
|
||||||
|
<h3>{title}</h3>
|
||||||
|
<span>{subtext}</span>
|
||||||
|
</div>
|
||||||
|
{renderActionableButton && renderActionableButton()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NetworkStatus = ({
|
||||||
|
hasNetworkDialog,
|
||||||
|
i18n,
|
||||||
|
isOnline,
|
||||||
|
isRegistrationDone,
|
||||||
|
socketStatus,
|
||||||
|
relinkDevice,
|
||||||
|
}: PropsType): JSX.Element | null => {
|
||||||
|
if (!hasNetworkDialog) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOnline) {
|
||||||
|
return renderDialog({
|
||||||
|
subtext: i18n('checkNetworkConnection'),
|
||||||
|
title: i18n('offline'),
|
||||||
|
});
|
||||||
|
} else if (!isRegistrationDone) {
|
||||||
|
return renderDialog({
|
||||||
|
renderActionableButton: (): JSX.Element => (
|
||||||
|
<div className="module-left-pane-dialog__actions">
|
||||||
|
<button onClick={relinkDevice}>{i18n('relink')}</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
subtext: i18n('unlinkedWarning'),
|
||||||
|
title: i18n('unlinked'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let subtext = '';
|
||||||
|
let title = '';
|
||||||
|
|
||||||
|
switch (socketStatus) {
|
||||||
|
case WebSocket.CONNECTING:
|
||||||
|
subtext = i18n('connectingHangOn');
|
||||||
|
title = i18n('connecting');
|
||||||
|
break;
|
||||||
|
case WebSocket.CLOSED:
|
||||||
|
case WebSocket.CLOSING:
|
||||||
|
default:
|
||||||
|
title = i18n('disconnected');
|
||||||
|
subtext = i18n('checkNetworkConnection');
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderDialog({
|
||||||
|
subtext,
|
||||||
|
title,
|
||||||
|
});
|
||||||
|
};
|
73
ts/components/UpdateDialog.stories.tsx
Normal file
73
ts/components/UpdateDialog.stories.tsx
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { UpdateDialog } from './UpdateDialog';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import { setup as setupI18n } from '../../js/modules/i18n';
|
||||||
|
// @ts-ignore
|
||||||
|
import enMessages from '../../_locales/en/messages.json';
|
||||||
|
|
||||||
|
import { storiesOf } from '@storybook/react';
|
||||||
|
import { boolean, select } from '@storybook/addon-knobs';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
ackRender: action('ack-render'),
|
||||||
|
dismissDialog: action('dismiss-dialog'),
|
||||||
|
hasNetworkDialog: false,
|
||||||
|
i18n,
|
||||||
|
startUpdate: action('start-update'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const permutations = [
|
||||||
|
{
|
||||||
|
title: 'Update',
|
||||||
|
props: {
|
||||||
|
dialogType: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Cannot Update',
|
||||||
|
props: {
|
||||||
|
dialogType: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'MacOS Read Only Error',
|
||||||
|
props: {
|
||||||
|
dialogType: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
storiesOf('Components/UpdateDialog', module)
|
||||||
|
.add('Knobs Playground', () => {
|
||||||
|
const dialogType = select(
|
||||||
|
'dialogType',
|
||||||
|
{
|
||||||
|
None: 0,
|
||||||
|
Update: 1,
|
||||||
|
Cannot_Update: 2,
|
||||||
|
MacOS_Read_Only: 3,
|
||||||
|
},
|
||||||
|
1
|
||||||
|
);
|
||||||
|
const hasNetworkDialog = boolean('hasNetworkDialog', false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UpdateDialog
|
||||||
|
{...defaultProps}
|
||||||
|
dialogType={dialogType}
|
||||||
|
hasNetworkDialog={hasNetworkDialog}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.add('Iterations', () => {
|
||||||
|
return permutations.map(({ props, title }) => (
|
||||||
|
<>
|
||||||
|
<h3>{title}</h3>
|
||||||
|
<UpdateDialog {...defaultProps} {...props} />
|
||||||
|
</>
|
||||||
|
));
|
||||||
|
});
|
135
ts/components/UpdateDialog.tsx
Normal file
135
ts/components/UpdateDialog.tsx
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
import React from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import { Dialogs } from '../types/Dialogs';
|
||||||
|
import { Intl } from './Intl';
|
||||||
|
import { LocalizerType } from '../types/Util';
|
||||||
|
|
||||||
|
export interface PropsType {
|
||||||
|
ackRender: () => void;
|
||||||
|
dialogType: Dialogs;
|
||||||
|
dismissDialog: () => void;
|
||||||
|
hasNetworkDialog: boolean;
|
||||||
|
i18n: LocalizerType;
|
||||||
|
startUpdate: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type MaybeMoment = moment.Moment | null;
|
||||||
|
type ReactSnoozeHook = React.Dispatch<React.SetStateAction<MaybeMoment>>;
|
||||||
|
|
||||||
|
const SNOOZE_TIMER = 60 * 1000 * 30;
|
||||||
|
|
||||||
|
function handleSnooze(setSnoozeForLater: ReactSnoozeHook) {
|
||||||
|
setSnoozeForLater(moment().add(SNOOZE_TIMER));
|
||||||
|
setTimeout(() => {
|
||||||
|
setSnoozeForLater(moment());
|
||||||
|
}, SNOOZE_TIMER);
|
||||||
|
}
|
||||||
|
|
||||||
|
function canSnooze(snoozeUntil: MaybeMoment) {
|
||||||
|
return snoozeUntil === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSnoozed(snoozeUntil: MaybeMoment) {
|
||||||
|
if (snoozeUntil === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return moment().isBefore(snoozeUntil);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UpdateDialog = ({
|
||||||
|
ackRender,
|
||||||
|
dialogType,
|
||||||
|
dismissDialog,
|
||||||
|
hasNetworkDialog,
|
||||||
|
i18n,
|
||||||
|
startUpdate,
|
||||||
|
}: PropsType): JSX.Element | null => {
|
||||||
|
const [snoozeUntil, setSnoozeForLater] = React.useState<MaybeMoment>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
ackRender();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasNetworkDialog) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dialogType === Dialogs.None || isSnoozed(snoozeUntil)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dialogType === Dialogs.Cannot_Update) {
|
||||||
|
return (
|
||||||
|
<div className="module-left-pane-dialog module-left-pane-dialog--warning">
|
||||||
|
<div className="module-left-pane-dialog__message">
|
||||||
|
<h3>{i18n('cannotUpdate')}</h3>
|
||||||
|
<span>
|
||||||
|
<Intl
|
||||||
|
components={[
|
||||||
|
<a
|
||||||
|
key="signal-download"
|
||||||
|
href="https://signal.org/download/"
|
||||||
|
rel="noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
https://signal.org/download/
|
||||||
|
</a>,
|
||||||
|
]}
|
||||||
|
i18n={i18n}
|
||||||
|
id="cannotUpdateDetail"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dialogType === Dialogs.MacOS_Read_Only) {
|
||||||
|
return (
|
||||||
|
<div className="module-left-pane-dialog module-left-pane-dialog--warning">
|
||||||
|
<div className="module-left-pane-dialog__message">
|
||||||
|
<h3>{i18n('cannotUpdate')}</h3>
|
||||||
|
<span>
|
||||||
|
<Intl
|
||||||
|
components={[
|
||||||
|
<strong key="app">Signal.app</strong>,
|
||||||
|
<strong key="folder">/Applications</strong>,
|
||||||
|
]}
|
||||||
|
i18n={i18n}
|
||||||
|
id="readOnlyVolume"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="module-left-pane-dialog__actions">
|
||||||
|
<button onClick={dismissDialog}>{i18n('ok')}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="module-left-pane-dialog">
|
||||||
|
<div className="module-left-pane-dialog__message">
|
||||||
|
<h3>{i18n('autoUpdateNewVersionTitle')}</h3>
|
||||||
|
<span>{i18n('autoUpdateNewVersionMessage')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="module-left-pane-dialog__actions">
|
||||||
|
{canSnooze(snoozeUntil) && (
|
||||||
|
<button
|
||||||
|
className="module-left-pane-dialog__button--no-border"
|
||||||
|
onClick={() => {
|
||||||
|
handleSnooze(setSnoozeForLater);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n('autoUpdateLaterButtonLabel')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button onClick={startUpdate}>
|
||||||
|
{i18n('autoUpdateRestartButtonLabel')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
40
ts/services/networkObserver.ts
Normal file
40
ts/services/networkObserver.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import {
|
||||||
|
CheckNetworkStatusPayloadType,
|
||||||
|
NetworkActionType,
|
||||||
|
} from '../state/ducks/network';
|
||||||
|
import { getSocketStatus } from '../shims/socketStatus';
|
||||||
|
|
||||||
|
type NetworkActions = {
|
||||||
|
checkNetworkStatus: (x: CheckNetworkStatusPayloadType) => NetworkActionType;
|
||||||
|
closeConnectingGracePeriod: () => NetworkActionType;
|
||||||
|
};
|
||||||
|
|
||||||
|
const REFRESH_INTERVAL = 5000;
|
||||||
|
|
||||||
|
interface ShimmedWindow extends Window {
|
||||||
|
log: {
|
||||||
|
info: (...args: any) => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const unknownWindow = window as unknown;
|
||||||
|
const shimmedWindow = unknownWindow as ShimmedWindow;
|
||||||
|
|
||||||
|
export function initializeNetworkObserver(networkActions: NetworkActions) {
|
||||||
|
const { log } = shimmedWindow;
|
||||||
|
log.info(`Initializing network observer every ${REFRESH_INTERVAL}ms`);
|
||||||
|
|
||||||
|
const refresh = () => {
|
||||||
|
networkActions.checkNetworkStatus({
|
||||||
|
isOnline: navigator.onLine,
|
||||||
|
socketStatus: getSocketStatus(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('online', refresh);
|
||||||
|
window.addEventListener('offline', refresh);
|
||||||
|
window.setInterval(refresh, REFRESH_INTERVAL);
|
||||||
|
window.setTimeout(() => {
|
||||||
|
networkActions.closeConnectingGracePeriod();
|
||||||
|
}, REFRESH_INTERVAL);
|
||||||
|
}
|
13
ts/services/updateListener.ts
Normal file
13
ts/services/updateListener.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
import { Dialogs } from '../types/Dialogs';
|
||||||
|
import { ShowUpdateDialogAction } from '../state/ducks/updates';
|
||||||
|
|
||||||
|
type UpdatesActions = {
|
||||||
|
showUpdateDialog: (x: Dialogs) => ShowUpdateDialogAction;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function initializeUpdateListener(updatesActions: UpdatesActions) {
|
||||||
|
ipcRenderer.on('show-update-dialog', (_, dialogType: Dialogs) => {
|
||||||
|
updatesActions.showUpdateDialog(dialogType);
|
||||||
|
});
|
||||||
|
}
|
12
ts/shims/socketStatus.ts
Normal file
12
ts/shims/socketStatus.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
interface ShimmedWindow extends Window {
|
||||||
|
getSocketStatus: () => number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unknownWindow = window as unknown;
|
||||||
|
const shimmedWindow = unknownWindow as ShimmedWindow;
|
||||||
|
|
||||||
|
export function getSocketStatus() {
|
||||||
|
const { getSocketStatus: getMessageReceiverStatus } = shimmedWindow;
|
||||||
|
|
||||||
|
return getMessageReceiverStatus();
|
||||||
|
}
|
9
ts/shims/updateIpc.ts
Normal file
9
ts/shims/updateIpc.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
|
||||||
|
export function startUpdate() {
|
||||||
|
ipcRenderer.send('start-update');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ackRender() {
|
||||||
|
ipcRenderer.send('show-update-dialog-ack');
|
||||||
|
}
|
|
@ -1,15 +1,21 @@
|
||||||
import { actions as conversations } from './ducks/conversations';
|
import { actions as conversations } from './ducks/conversations';
|
||||||
import { actions as emojis } from './ducks/emojis';
|
import { actions as emojis } from './ducks/emojis';
|
||||||
|
import { actions as expiration } from './ducks/expiration';
|
||||||
import { actions as items } from './ducks/items';
|
import { actions as items } from './ducks/items';
|
||||||
|
import { actions as network } from './ducks/network';
|
||||||
import { actions as search } from './ducks/search';
|
import { actions as search } from './ducks/search';
|
||||||
import { actions as stickers } from './ducks/stickers';
|
import { actions as stickers } from './ducks/stickers';
|
||||||
|
import { actions as updates } from './ducks/updates';
|
||||||
import { actions as user } from './ducks/user';
|
import { actions as user } from './ducks/user';
|
||||||
|
|
||||||
export const mapDispatchToProps = {
|
export const mapDispatchToProps = {
|
||||||
...conversations,
|
...conversations,
|
||||||
...emojis,
|
...emojis,
|
||||||
|
...expiration,
|
||||||
...items,
|
...items,
|
||||||
|
...network,
|
||||||
...search,
|
...search,
|
||||||
...stickers,
|
...stickers,
|
||||||
|
...updates,
|
||||||
...user,
|
...user,
|
||||||
};
|
};
|
||||||
|
|
50
ts/state/ducks/expiration.ts
Normal file
50
ts/state/ducks/expiration.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// State
|
||||||
|
|
||||||
|
export type ExpirationStateType = {
|
||||||
|
hasExpired: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
|
||||||
|
const HYDRATE_EXPIRATION_STATUS = 'expiration/HYDRATE_EXPIRATION_STATUS';
|
||||||
|
|
||||||
|
type HyrdateExpirationStatusActionType = {
|
||||||
|
type: 'expiration/HYDRATE_EXPIRATION_STATUS';
|
||||||
|
payload: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ExpirationActionType = HyrdateExpirationStatusActionType;
|
||||||
|
|
||||||
|
// Action Creators
|
||||||
|
|
||||||
|
function hydrateExpirationStatus(hasExpired: boolean): ExpirationActionType {
|
||||||
|
return {
|
||||||
|
type: HYDRATE_EXPIRATION_STATUS,
|
||||||
|
payload: hasExpired,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
hydrateExpirationStatus,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reducer
|
||||||
|
|
||||||
|
function getEmptyState(): ExpirationStateType {
|
||||||
|
return {
|
||||||
|
hasExpired: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reducer(
|
||||||
|
state: ExpirationStateType = getEmptyState(),
|
||||||
|
action: ExpirationActionType
|
||||||
|
): ExpirationStateType {
|
||||||
|
if (action.type === HYDRATE_EXPIRATION_STATUS) {
|
||||||
|
return {
|
||||||
|
hasExpired: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
104
ts/state/ducks/network.ts
Normal file
104
ts/state/ducks/network.ts
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import { SocketStatus } from '../../types/SocketStatus';
|
||||||
|
import { trigger } from '../../shims/events';
|
||||||
|
|
||||||
|
// State
|
||||||
|
|
||||||
|
export type NetworkStateType = {
|
||||||
|
isOnline: boolean;
|
||||||
|
socketStatus: SocketStatus;
|
||||||
|
withinConnectingGracePeriod: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
|
||||||
|
const CHECK_NETWORK_STATUS = 'network/CHECK_NETWORK_STATUS';
|
||||||
|
const CLOSE_CONNECTING_GRACE_PERIOD = 'network/CLOSE_CONNECTING_GRACE_PERIOD';
|
||||||
|
const RELINK_DEVICE = 'network/RELINK_DEVICE';
|
||||||
|
|
||||||
|
export type CheckNetworkStatusPayloadType = {
|
||||||
|
isOnline: boolean;
|
||||||
|
socketStatus: SocketStatus;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CheckNetworkStatusAction = {
|
||||||
|
type: 'network/CHECK_NETWORK_STATUS';
|
||||||
|
payload: CheckNetworkStatusPayloadType;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CloseConnectingGracePeriodActionType = {
|
||||||
|
type: 'network/CLOSE_CONNECTING_GRACE_PERIOD';
|
||||||
|
};
|
||||||
|
|
||||||
|
type RelinkDeviceActionType = {
|
||||||
|
type: 'network/RELINK_DEVICE';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NetworkActionType =
|
||||||
|
| CheckNetworkStatusAction
|
||||||
|
| CloseConnectingGracePeriodActionType
|
||||||
|
| RelinkDeviceActionType;
|
||||||
|
|
||||||
|
// Action Creators
|
||||||
|
|
||||||
|
function checkNetworkStatus(
|
||||||
|
payload: CheckNetworkStatusPayloadType
|
||||||
|
): CheckNetworkStatusAction {
|
||||||
|
return {
|
||||||
|
type: CHECK_NETWORK_STATUS,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeConnectingGracePeriod(): CloseConnectingGracePeriodActionType {
|
||||||
|
return {
|
||||||
|
type: CLOSE_CONNECTING_GRACE_PERIOD,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function relinkDevice(): RelinkDeviceActionType {
|
||||||
|
trigger('setupAsNewDevice');
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: RELINK_DEVICE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
checkNetworkStatus,
|
||||||
|
closeConnectingGracePeriod,
|
||||||
|
relinkDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reducer
|
||||||
|
|
||||||
|
function getEmptyState(): NetworkStateType {
|
||||||
|
return {
|
||||||
|
isOnline: navigator.onLine,
|
||||||
|
socketStatus: WebSocket.OPEN,
|
||||||
|
withinConnectingGracePeriod: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reducer(
|
||||||
|
state: NetworkStateType = getEmptyState(),
|
||||||
|
action: NetworkActionType
|
||||||
|
): NetworkStateType {
|
||||||
|
if (action.type === CHECK_NETWORK_STATUS) {
|
||||||
|
const { isOnline, socketStatus } = action.payload;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isOnline,
|
||||||
|
socketStatus,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === CLOSE_CONNECTING_GRACE_PERIOD) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
withinConnectingGracePeriod: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
106
ts/state/ducks/updates.ts
Normal file
106
ts/state/ducks/updates.ts
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import { Dialogs } from '../../types/Dialogs';
|
||||||
|
import * as updateIpc from '../../shims/updateIpc';
|
||||||
|
|
||||||
|
// State
|
||||||
|
|
||||||
|
export type UpdatesStateType = {
|
||||||
|
dialogType: Dialogs;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
|
||||||
|
const ACK_RENDER = 'updates/ACK_RENDER';
|
||||||
|
const DISMISS_DIALOG = 'updates/DISMISS_DIALOG';
|
||||||
|
const SHOW_UPDATE_DIALOG = 'updates/SHOW_UPDATE_DIALOG';
|
||||||
|
const START_UPDATE = 'updates/START_UPDATE';
|
||||||
|
|
||||||
|
type AckRenderAction = {
|
||||||
|
type: 'updates/ACK_RENDER';
|
||||||
|
};
|
||||||
|
|
||||||
|
type DismissDialogAction = {
|
||||||
|
type: 'updates/DISMISS_DIALOG';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ShowUpdateDialogAction = {
|
||||||
|
type: 'updates/SHOW_UPDATE_DIALOG';
|
||||||
|
payload: Dialogs;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StartUpdateAction = {
|
||||||
|
type: 'updates/START_UPDATE';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdatesActionType =
|
||||||
|
| AckRenderAction
|
||||||
|
| DismissDialogAction
|
||||||
|
| ShowUpdateDialogAction
|
||||||
|
| StartUpdateAction;
|
||||||
|
|
||||||
|
// Action Creators
|
||||||
|
|
||||||
|
function ackRender(): AckRenderAction {
|
||||||
|
updateIpc.ackRender();
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: ACK_RENDER,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function dismissDialog(): DismissDialogAction {
|
||||||
|
return {
|
||||||
|
type: DISMISS_DIALOG,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function showUpdateDialog(dialogType: Dialogs): ShowUpdateDialogAction {
|
||||||
|
return {
|
||||||
|
type: SHOW_UPDATE_DIALOG,
|
||||||
|
payload: dialogType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function startUpdate(): StartUpdateAction {
|
||||||
|
updateIpc.startUpdate();
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: START_UPDATE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
ackRender,
|
||||||
|
dismissDialog,
|
||||||
|
showUpdateDialog,
|
||||||
|
startUpdate,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reducer
|
||||||
|
|
||||||
|
function getEmptyState(): UpdatesStateType {
|
||||||
|
return {
|
||||||
|
dialogType: Dialogs.None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reducer(
|
||||||
|
state: UpdatesStateType = getEmptyState(),
|
||||||
|
action: UpdatesActionType
|
||||||
|
): UpdatesStateType {
|
||||||
|
if (action.type === SHOW_UPDATE_DIALOG) {
|
||||||
|
return {
|
||||||
|
dialogType: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
action.type === DISMISS_DIALOG &&
|
||||||
|
state.dialogType === Dialogs.MacOS_Read_Only
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
dialogType: Dialogs.None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
|
@ -10,11 +10,21 @@ import {
|
||||||
EmojisStateType,
|
EmojisStateType,
|
||||||
reducer as emojis,
|
reducer as emojis,
|
||||||
} from './ducks/emojis';
|
} from './ducks/emojis';
|
||||||
|
import {
|
||||||
|
ExpirationActionType,
|
||||||
|
ExpirationStateType,
|
||||||
|
reducer as expiration,
|
||||||
|
} from './ducks/expiration';
|
||||||
import {
|
import {
|
||||||
ItemsActionType,
|
ItemsActionType,
|
||||||
ItemsStateType,
|
ItemsStateType,
|
||||||
reducer as items,
|
reducer as items,
|
||||||
} from './ducks/items';
|
} from './ducks/items';
|
||||||
|
import {
|
||||||
|
NetworkActionType,
|
||||||
|
NetworkStateType,
|
||||||
|
reducer as network,
|
||||||
|
} from './ducks/network';
|
||||||
import {
|
import {
|
||||||
reducer as search,
|
reducer as search,
|
||||||
SEARCH_TYPES as SearchActionType,
|
SEARCH_TYPES as SearchActionType,
|
||||||
|
@ -25,30 +35,44 @@ import {
|
||||||
StickersActionType,
|
StickersActionType,
|
||||||
StickersStateType,
|
StickersStateType,
|
||||||
} from './ducks/stickers';
|
} from './ducks/stickers';
|
||||||
|
import {
|
||||||
|
reducer as updates,
|
||||||
|
UpdatesActionType,
|
||||||
|
UpdatesStateType,
|
||||||
|
} from './ducks/updates';
|
||||||
import { reducer as user, UserStateType } from './ducks/user';
|
import { reducer as user, UserStateType } from './ducks/user';
|
||||||
|
|
||||||
export type StateType = {
|
export type StateType = {
|
||||||
conversations: ConversationsStateType;
|
conversations: ConversationsStateType;
|
||||||
emojis: EmojisStateType;
|
emojis: EmojisStateType;
|
||||||
|
expiration: ExpirationStateType;
|
||||||
items: ItemsStateType;
|
items: ItemsStateType;
|
||||||
|
network: NetworkStateType;
|
||||||
search: SearchStateType;
|
search: SearchStateType;
|
||||||
stickers: StickersStateType;
|
stickers: StickersStateType;
|
||||||
|
updates: UpdatesStateType;
|
||||||
user: UserStateType;
|
user: UserStateType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ActionsType =
|
export type ActionsType =
|
||||||
| EmojisActionType
|
| EmojisActionType
|
||||||
|
| ExpirationActionType
|
||||||
| ConversationActionType
|
| ConversationActionType
|
||||||
| ItemsActionType
|
| ItemsActionType
|
||||||
|
| NetworkActionType
|
||||||
| StickersActionType
|
| StickersActionType
|
||||||
| SearchActionType;
|
| SearchActionType
|
||||||
|
| UpdatesActionType;
|
||||||
|
|
||||||
export const reducers = {
|
export const reducers = {
|
||||||
conversations,
|
conversations,
|
||||||
emojis,
|
emojis,
|
||||||
|
expiration,
|
||||||
items,
|
items,
|
||||||
|
network,
|
||||||
search,
|
search,
|
||||||
stickers,
|
stickers,
|
||||||
|
updates,
|
||||||
user,
|
user,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
21
ts/state/selectors/network.ts
Normal file
21
ts/state/selectors/network.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
import { StateType } from '../reducer';
|
||||||
|
import { NetworkStateType } from '../ducks/network';
|
||||||
|
import { isDone } from './registration';
|
||||||
|
|
||||||
|
const getNetwork = (state: StateType): NetworkStateType => state.network;
|
||||||
|
|
||||||
|
export const hasNetworkDialog = createSelector(
|
||||||
|
getNetwork,
|
||||||
|
isDone,
|
||||||
|
(
|
||||||
|
{ isOnline, socketStatus, withinConnectingGracePeriod }: NetworkStateType,
|
||||||
|
isRegistrationDone: boolean
|
||||||
|
): boolean =>
|
||||||
|
!isOnline ||
|
||||||
|
!isRegistrationDone ||
|
||||||
|
(socketStatus === WebSocket.CONNECTING && !withinConnectingGracePeriod) ||
|
||||||
|
socketStatus === WebSocket.CLOSED ||
|
||||||
|
socketStatus === WebSocket.CLOSING
|
||||||
|
);
|
18
ts/state/selectors/registration.ts
Normal file
18
ts/state/selectors/registration.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
import { StateType } from '../reducer';
|
||||||
|
import { ItemsStateType } from '../ducks/items';
|
||||||
|
|
||||||
|
const getItems = (state: StateType): ItemsStateType => state.items;
|
||||||
|
|
||||||
|
export const isDone = createSelector(
|
||||||
|
getItems,
|
||||||
|
(state: ItemsStateType): boolean => state.chromiumRegistrationDone === ''
|
||||||
|
);
|
||||||
|
|
||||||
|
export const everDone = createSelector(
|
||||||
|
getItems,
|
||||||
|
(state: ItemsStateType): boolean =>
|
||||||
|
state.chromiumRegistrationDoneEver === '' ||
|
||||||
|
state.chromiumRegistrationDone === ''
|
||||||
|
);
|
16
ts/state/smart/ExpiredBuildDialog.tsx
Normal file
16
ts/state/smart/ExpiredBuildDialog.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { mapDispatchToProps } from '../actions';
|
||||||
|
import { ExpiredBuildDialog } from '../../components/ExpiredBuildDialog';
|
||||||
|
import { StateType } from '../reducer';
|
||||||
|
import { getIntl } from '../selectors/user';
|
||||||
|
|
||||||
|
const mapStateToProps = (state: StateType) => {
|
||||||
|
return {
|
||||||
|
hasExpired: state.expiration.hasExpired,
|
||||||
|
i18n: getIntl(state),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||||
|
|
||||||
|
export const SmartExpiredBuildDialog = smart(ExpiredBuildDialog);
|
|
@ -12,20 +12,35 @@ import {
|
||||||
getShowArchived,
|
getShowArchived,
|
||||||
} from '../selectors/conversations';
|
} from '../selectors/conversations';
|
||||||
|
|
||||||
|
import { SmartExpiredBuildDialog } from './ExpiredBuildDialog';
|
||||||
import { SmartMainHeader } from './MainHeader';
|
import { SmartMainHeader } from './MainHeader';
|
||||||
import { SmartMessageSearchResult } from './MessageSearchResult';
|
import { SmartMessageSearchResult } from './MessageSearchResult';
|
||||||
|
import { SmartNetworkStatus } from './NetworkStatus';
|
||||||
|
import { SmartUpdateDialog } from './UpdateDialog';
|
||||||
|
|
||||||
// Workaround: A react component's required properties are filtering up through connect()
|
// Workaround: A react component's required properties are filtering up through connect()
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||||
const FilteredSmartMainHeader = SmartMainHeader as any;
|
const FilteredSmartMainHeader = SmartMainHeader as any;
|
||||||
const FilteredSmartMessageSearchResult = SmartMessageSearchResult as any;
|
const FilteredSmartMessageSearchResult = SmartMessageSearchResult as any;
|
||||||
|
const FilteredSmartNetworkStatus = SmartNetworkStatus as any;
|
||||||
|
const FilteredSmartUpdateDialog = SmartUpdateDialog as any;
|
||||||
|
const FilteredSmartExpiredBuildDialog = SmartExpiredBuildDialog as any;
|
||||||
|
|
||||||
|
function renderExpiredBuildDialog(): JSX.Element {
|
||||||
|
return <FilteredSmartExpiredBuildDialog />;
|
||||||
|
}
|
||||||
function renderMainHeader(): JSX.Element {
|
function renderMainHeader(): JSX.Element {
|
||||||
return <FilteredSmartMainHeader />;
|
return <FilteredSmartMainHeader />;
|
||||||
}
|
}
|
||||||
function renderMessageSearchResult(id: string): JSX.Element {
|
function renderMessageSearchResult(id: string): JSX.Element {
|
||||||
return <FilteredSmartMessageSearchResult id={id} />;
|
return <FilteredSmartMessageSearchResult id={id} />;
|
||||||
}
|
}
|
||||||
|
function renderUpdateDialog(): JSX.Element {
|
||||||
|
return <FilteredSmartUpdateDialog />;
|
||||||
|
}
|
||||||
|
function renderNetworkStatus(): JSX.Element {
|
||||||
|
return <FilteredSmartNetworkStatus />;
|
||||||
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType) => {
|
const mapStateToProps = (state: StateType) => {
|
||||||
const showSearch = isSearching(state);
|
const showSearch = isSearching(state);
|
||||||
|
@ -40,8 +55,11 @@ const mapStateToProps = (state: StateType) => {
|
||||||
selectedConversationId,
|
selectedConversationId,
|
||||||
showArchived: getShowArchived(state),
|
showArchived: getShowArchived(state),
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
|
renderExpiredBuildDialog,
|
||||||
renderMainHeader,
|
renderMainHeader,
|
||||||
renderMessageSearchResult,
|
renderMessageSearchResult,
|
||||||
|
renderNetworkStatus,
|
||||||
|
renderUpdateDialog,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
20
ts/state/smart/NetworkStatus.tsx
Normal file
20
ts/state/smart/NetworkStatus.tsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { mapDispatchToProps } from '../actions';
|
||||||
|
import { NetworkStatus } from '../../components/NetworkStatus';
|
||||||
|
import { StateType } from '../reducer';
|
||||||
|
import { getIntl } from '../selectors/user';
|
||||||
|
import { hasNetworkDialog } from '../selectors/network';
|
||||||
|
import { isDone } from '../selectors/registration';
|
||||||
|
|
||||||
|
const mapStateToProps = (state: StateType) => {
|
||||||
|
return {
|
||||||
|
...state.network,
|
||||||
|
hasNetworkDialog: hasNetworkDialog(state),
|
||||||
|
i18n: getIntl(state),
|
||||||
|
isRegistrationDone: isDone(state),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||||
|
|
||||||
|
export const SmartNetworkStatus = smart(NetworkStatus);
|
18
ts/state/smart/UpdateDialog.tsx
Normal file
18
ts/state/smart/UpdateDialog.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { mapDispatchToProps } from '../actions';
|
||||||
|
import { UpdateDialog } from '../../components/UpdateDialog';
|
||||||
|
import { StateType } from '../reducer';
|
||||||
|
import { getIntl } from '../selectors/user';
|
||||||
|
import { hasNetworkDialog } from '../selectors/network';
|
||||||
|
|
||||||
|
const mapStateToProps = (state: StateType) => {
|
||||||
|
return {
|
||||||
|
...state.updates,
|
||||||
|
hasNetworkDialog: hasNetworkDialog(state),
|
||||||
|
i18n: getIntl(state),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||||
|
|
||||||
|
export const SmartUpdateDialog = smart(UpdateDialog);
|
6
ts/types/Dialogs.ts
Normal file
6
ts/types/Dialogs.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export enum Dialogs {
|
||||||
|
None,
|
||||||
|
Update,
|
||||||
|
Cannot_Update,
|
||||||
|
MacOS_Read_Only,
|
||||||
|
}
|
8
ts/types/SocketStatus.ts
Normal file
8
ts/types/SocketStatus.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// Maps to values found here: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
|
||||||
|
// which are returned by libtextsecure's MessageReceiver
|
||||||
|
export enum SocketStatus {
|
||||||
|
CONNECTING,
|
||||||
|
OPEN,
|
||||||
|
CLOSING,
|
||||||
|
CLOSED,
|
||||||
|
}
|
|
@ -18,9 +18,10 @@ import { v4 as getGuid } from 'uuid';
|
||||||
import pify from 'pify';
|
import pify from 'pify';
|
||||||
import mkdirp from 'mkdirp';
|
import mkdirp from 'mkdirp';
|
||||||
import rimraf from 'rimraf';
|
import rimraf from 'rimraf';
|
||||||
import { app, BrowserWindow, dialog } from 'electron';
|
import { app, BrowserWindow, dialog, ipcMain } from 'electron';
|
||||||
|
|
||||||
import { getTempPath } from '../../app/attachments';
|
import { getTempPath } from '../../app/attachments';
|
||||||
|
import { Dialogs } from '../types/Dialogs';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as packageJson from '../../package.json';
|
import * as packageJson from '../../package.json';
|
||||||
|
@ -49,6 +50,8 @@ const mkdirpPromise = pify(mkdirp);
|
||||||
const rimrafPromise = pify(rimraf);
|
const rimrafPromise = pify(rimraf);
|
||||||
const { platform } = process;
|
const { platform } = process;
|
||||||
|
|
||||||
|
export const ACK_RENDER_TIMEOUT = 10000;
|
||||||
|
|
||||||
export async function checkForUpdates(
|
export async function checkForUpdates(
|
||||||
logger: LoggerType
|
logger: LoggerType
|
||||||
): Promise<{
|
): Promise<{
|
||||||
|
@ -141,10 +144,10 @@ export async function downloadUpdate(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function showUpdateDialog(
|
async function showFallbackUpdateDialog(
|
||||||
mainWindow: BrowserWindow,
|
mainWindow: BrowserWindow,
|
||||||
messages: MessagesType
|
messages: MessagesType
|
||||||
): Promise<boolean> {
|
) {
|
||||||
const RESTART_BUTTON = 0;
|
const RESTART_BUTTON = 0;
|
||||||
const LATER_BUTTON = 1;
|
const LATER_BUTTON = 1;
|
||||||
const options = {
|
const options = {
|
||||||
|
@ -165,10 +168,32 @@ export async function showUpdateDialog(
|
||||||
return response === RESTART_BUTTON;
|
return response === RESTART_BUTTON;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function showCannotUpdateDialog(
|
export function showUpdateDialog(
|
||||||
|
mainWindow: BrowserWindow,
|
||||||
|
messages: MessagesType,
|
||||||
|
performUpdateCallback: () => void
|
||||||
|
): void {
|
||||||
|
let ack = false;
|
||||||
|
|
||||||
|
ipcMain.once('start-update', performUpdateCallback);
|
||||||
|
|
||||||
|
ipcMain.once('show-update-dialog-ack', () => {
|
||||||
|
ack = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.webContents.send('show-update-dialog', Dialogs.Update);
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
if (!ack) {
|
||||||
|
await showFallbackUpdateDialog(mainWindow, messages);
|
||||||
|
}
|
||||||
|
}, ACK_RENDER_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showFallbackCannotUpdateDialog(
|
||||||
mainWindow: BrowserWindow,
|
mainWindow: BrowserWindow,
|
||||||
messages: MessagesType
|
messages: MessagesType
|
||||||
): Promise<any> {
|
) {
|
||||||
const options = {
|
const options = {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
buttons: [messages.ok.message],
|
buttons: [messages.ok.message],
|
||||||
|
@ -179,6 +204,25 @@ export async function showCannotUpdateDialog(
|
||||||
await dialog.showMessageBox(mainWindow, options);
|
await dialog.showMessageBox(mainWindow, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showCannotUpdateDialog(
|
||||||
|
mainWindow: BrowserWindow,
|
||||||
|
messages: MessagesType
|
||||||
|
): void {
|
||||||
|
let ack = false;
|
||||||
|
|
||||||
|
ipcMain.once('show-update-dialog-ack', () => {
|
||||||
|
ack = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.webContents.send('show-update-dialog', Dialogs.Cannot_Update);
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
if (!ack) {
|
||||||
|
await showFallbackCannotUpdateDialog(mainWindow, messages);
|
||||||
|
}
|
||||||
|
}, ACK_RENDER_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
|
|
||||||
export function getUpdateCheckUrl(): string {
|
export function getUpdateCheckUrl(): string {
|
||||||
|
|
|
@ -4,12 +4,13 @@ import { AddressInfo } from 'net';
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
|
|
||||||
import { v4 as getGuid } from 'uuid';
|
import { v4 as getGuid } from 'uuid';
|
||||||
import { app, autoUpdater, BrowserWindow, dialog } from 'electron';
|
import { app, autoUpdater, BrowserWindow, dialog, ipcMain } from 'electron';
|
||||||
import { get as getFromConfig } from 'config';
|
import { get as getFromConfig } from 'config';
|
||||||
import { gt } from 'semver';
|
import { gt } from 'semver';
|
||||||
import got from 'got';
|
import got from 'got';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ACK_RENDER_TIMEOUT,
|
||||||
checkForUpdates,
|
checkForUpdates,
|
||||||
deleteTempDir,
|
deleteTempDir,
|
||||||
downloadUpdate,
|
downloadUpdate,
|
||||||
|
@ -21,6 +22,7 @@ import {
|
||||||
} from './common';
|
} from './common';
|
||||||
import { hexToBinary, verifySignature } from './signature';
|
import { hexToBinary, verifySignature } from './signature';
|
||||||
import { markShouldQuit } from '../../app/window_state';
|
import { markShouldQuit } from '../../app/window_state';
|
||||||
|
import { Dialogs } from '../types/Dialogs';
|
||||||
|
|
||||||
let isChecking = false;
|
let isChecking = false;
|
||||||
const SECOND = 1000;
|
const SECOND = 1000;
|
||||||
|
@ -96,12 +98,12 @@ async function checkDownloadAndInstall(
|
||||||
const message: string = error.message || '';
|
const message: string = error.message || '';
|
||||||
if (message.includes(readOnly)) {
|
if (message.includes(readOnly)) {
|
||||||
logger.info('checkDownloadAndInstall: showing read-only dialog...');
|
logger.info('checkDownloadAndInstall: showing read-only dialog...');
|
||||||
await showReadOnlyDialog(getMainWindow(), messages);
|
showReadOnlyDialog(getMainWindow(), messages);
|
||||||
} else {
|
} else {
|
||||||
logger.info(
|
logger.info(
|
||||||
'checkDownloadAndInstall: showing general update failure dialog...'
|
'checkDownloadAndInstall: showing general update failure dialog...'
|
||||||
);
|
);
|
||||||
await showCannotUpdateDialog(getMainWindow(), messages);
|
showCannotUpdateDialog(getMainWindow(), messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -111,14 +113,12 @@ async function checkDownloadAndInstall(
|
||||||
// because Squirrel has cached the update file and will do the right thing.
|
// because Squirrel has cached the update file and will do the right thing.
|
||||||
|
|
||||||
logger.info('checkDownloadAndInstall: showing update dialog...');
|
logger.info('checkDownloadAndInstall: showing update dialog...');
|
||||||
const shouldUpdate = await showUpdateDialog(getMainWindow(), messages);
|
|
||||||
if (!shouldUpdate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('checkDownloadAndInstall: calling quitAndInstall...');
|
showUpdateDialog(getMainWindow(), messages, () => {
|
||||||
markShouldQuit();
|
logger.info('checkDownloadAndInstall: calling quitAndInstall...');
|
||||||
autoUpdater.quitAndInstall();
|
markShouldQuit();
|
||||||
|
autoUpdater.quitAndInstall();
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('checkDownloadAndInstall: error', getPrintableError(error));
|
logger.error('checkDownloadAndInstall: error', getPrintableError(error));
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -339,10 +339,29 @@ function shutdown(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function showReadOnlyDialog(
|
export function showReadOnlyDialog(
|
||||||
mainWindow: BrowserWindow,
|
mainWindow: BrowserWindow,
|
||||||
messages: MessagesType
|
messages: MessagesType
|
||||||
): Promise<void> {
|
): void {
|
||||||
|
let ack = false;
|
||||||
|
|
||||||
|
ipcMain.once('show-update-dialog-ack', () => {
|
||||||
|
ack = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.webContents.send('show-update-dialog', Dialogs.MacOS_Read_Only);
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
if (!ack) {
|
||||||
|
await showFallbackReadOnlyDialog(mainWindow, messages);
|
||||||
|
}
|
||||||
|
}, ACK_RENDER_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showFallbackReadOnlyDialog(
|
||||||
|
mainWindow: BrowserWindow,
|
||||||
|
messages: MessagesType
|
||||||
|
) {
|
||||||
const options = {
|
const options = {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
buttons: [messages.ok.message],
|
buttons: [messages.ok.message],
|
||||||
|
|
|
@ -93,25 +93,22 @@ async function checkDownloadAndInstall(
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('checkDownloadAndInstall: showing dialog...');
|
logger.info('checkDownloadAndInstall: showing dialog...');
|
||||||
const shouldUpdate = await showUpdateDialog(getMainWindow(), messages);
|
showUpdateDialog(getMainWindow(), messages, async () => {
|
||||||
if (!shouldUpdate) {
|
try {
|
||||||
return;
|
await verifyAndInstall(updateFilePath, version, logger);
|
||||||
}
|
installing = true;
|
||||||
|
} catch (error) {
|
||||||
|
logger.info(
|
||||||
|
'checkDownloadAndInstall: showing general update failure dialog...'
|
||||||
|
);
|
||||||
|
showCannotUpdateDialog(getMainWindow(), messages);
|
||||||
|
|
||||||
try {
|
throw error;
|
||||||
await verifyAndInstall(updateFilePath, version, logger);
|
}
|
||||||
installing = true;
|
|
||||||
} catch (error) {
|
|
||||||
logger.info(
|
|
||||||
'checkDownloadAndInstall: showing general update failure dialog...'
|
|
||||||
);
|
|
||||||
await showCannotUpdateDialog(getMainWindow(), messages);
|
|
||||||
|
|
||||||
throw error;
|
markShouldQuit();
|
||||||
}
|
app.quit();
|
||||||
|
});
|
||||||
markShouldQuit();
|
|
||||||
app.quit();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('checkDownloadAndInstall: error', getPrintableError(error));
|
logger.error('checkDownloadAndInstall: error', getPrintableError(error));
|
||||||
} finally {
|
} finally {
|
||||||
|
|
47
ts/util/hasExpired.ts
Normal file
47
ts/util/hasExpired.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
interface ShimmedWindow extends Window {
|
||||||
|
getExpiration: () => string;
|
||||||
|
log: {
|
||||||
|
info: (...args: any) => void;
|
||||||
|
error: (...args: any) => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const unknownWindow = window as unknown;
|
||||||
|
const shimmedWindow = unknownWindow as ShimmedWindow;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const env = window.getEnvironment();
|
||||||
|
|
||||||
|
const NINETY_ONE_DAYS = 86400 * 91 * 1000;
|
||||||
|
|
||||||
|
export function hasExpired() {
|
||||||
|
const { getExpiration, log } = shimmedWindow;
|
||||||
|
|
||||||
|
let buildExpiration = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
buildExpiration = parseInt(getExpiration(), 10);
|
||||||
|
if (buildExpiration) {
|
||||||
|
log.info('Build expires: ', new Date(buildExpiration).toISOString());
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error('Error retrieving build expiration date', e.stack);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tooFarIntoFuture = Date.now() + NINETY_ONE_DAYS < buildExpiration;
|
||||||
|
|
||||||
|
if (tooFarIntoFuture) {
|
||||||
|
log.error(
|
||||||
|
'Build expiration is set too far into the future',
|
||||||
|
buildExpiration
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (env === 'production') {
|
||||||
|
return Date.now() > buildExpiration && tooFarIntoFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildExpiration && Date.now() > buildExpiration;
|
||||||
|
}
|
|
@ -1,12 +1,14 @@
|
||||||
import * as GoogleChrome from './GoogleChrome';
|
import * as GoogleChrome from './GoogleChrome';
|
||||||
|
import * as Registration from './registration';
|
||||||
import { arrayBufferToObjectURL } from './arrayBufferToObjectURL';
|
import { arrayBufferToObjectURL } from './arrayBufferToObjectURL';
|
||||||
import { combineNames } from './combineNames';
|
import { combineNames } from './combineNames';
|
||||||
import { createBatcher } from './batcher';
|
import { createBatcher } from './batcher';
|
||||||
import { createWaitBatcher } from './waitBatcher';
|
import { createWaitBatcher } from './waitBatcher';
|
||||||
|
import { hasExpired } from './hasExpired';
|
||||||
import { isFileDangerous } from './isFileDangerous';
|
import { isFileDangerous } from './isFileDangerous';
|
||||||
import { missingCaseError } from './missingCaseError';
|
|
||||||
import { migrateColor } from './migrateColor';
|
|
||||||
import { makeLookup } from './makeLookup';
|
import { makeLookup } from './makeLookup';
|
||||||
|
import { migrateColor } from './migrateColor';
|
||||||
|
import { missingCaseError } from './missingCaseError';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
arrayBufferToObjectURL,
|
arrayBufferToObjectURL,
|
||||||
|
@ -14,8 +16,10 @@ export {
|
||||||
createBatcher,
|
createBatcher,
|
||||||
createWaitBatcher,
|
createWaitBatcher,
|
||||||
GoogleChrome,
|
GoogleChrome,
|
||||||
|
hasExpired,
|
||||||
isFileDangerous,
|
isFileDangerous,
|
||||||
makeLookup,
|
makeLookup,
|
||||||
migrateColor,
|
migrateColor,
|
||||||
missingCaseError,
|
missingCaseError,
|
||||||
|
Registration,
|
||||||
};
|
};
|
||||||
|
|
|
@ -309,7 +309,7 @@
|
||||||
"rule": "DOM-innerHTML",
|
"rule": "DOM-innerHTML",
|
||||||
"path": "js/views/app_view.js",
|
"path": "js/views/app_view.js",
|
||||||
"line": " this.el.innerHTML = '';",
|
"line": " this.el.innerHTML = '';",
|
||||||
"lineNumber": 55,
|
"lineNumber": 54,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-15T00:38:04.183Z",
|
"updated": "2018-09-15T00:38:04.183Z",
|
||||||
"reasonDetail": "Hard-coded string"
|
"reasonDetail": "Hard-coded string"
|
||||||
|
@ -318,7 +318,7 @@
|
||||||
"rule": "jQuery-append(",
|
"rule": "jQuery-append(",
|
||||||
"path": "js/views/app_view.js",
|
"path": "js/views/app_view.js",
|
||||||
"line": " this.el.append(view.el);",
|
"line": " this.el.append(view.el);",
|
||||||
"lineNumber": 56,
|
"lineNumber": 55,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-09-19T18:13:29.628Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
"reasonDetail": "Interacting with already-existing DOM nodes"
|
||||||
|
@ -327,7 +327,7 @@
|
||||||
"rule": "jQuery-appendTo(",
|
"rule": "jQuery-appendTo(",
|
||||||
"path": "js/views/app_view.js",
|
"path": "js/views/app_view.js",
|
||||||
"line": " this.debugLogView.$el.appendTo(this.el);",
|
"line": " this.debugLogView.$el.appendTo(this.el);",
|
||||||
"lineNumber": 62,
|
"lineNumber": 61,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-09-19T18:13:29.628Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
"reasonDetail": "Interacting with already-existing DOM nodes"
|
||||||
|
@ -461,7 +461,7 @@
|
||||||
"rule": "jQuery-appendTo(",
|
"rule": "jQuery-appendTo(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " view.$el.appendTo(this.el);",
|
"line": " view.$el.appendTo(this.el);",
|
||||||
"lineNumber": 35,
|
"lineNumber": 33,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
|
@ -470,7 +470,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " this.$('.message').text(message);",
|
"line": " this.$('.message').text(message);",
|
||||||
"lineNumber": 69,
|
"lineNumber": 67,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Hardcoded selector"
|
"reasonDetail": "Hardcoded selector"
|
||||||
|
@ -479,7 +479,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " el: this.$('.conversation-stack'),",
|
"line": " el: this.$('.conversation-stack'),",
|
||||||
"lineNumber": 85,
|
"lineNumber": 83,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Hardcoded selector"
|
"reasonDetail": "Hardcoded selector"
|
||||||
|
@ -488,25 +488,7 @@
|
||||||
"rule": "jQuery-prependTo(",
|
"rule": "jQuery-prependTo(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " this.appLoadingScreen.$el.prependTo(this.el);",
|
"line": " this.appLoadingScreen.$el.prependTo(this.el);",
|
||||||
"lineNumber": 92,
|
"lineNumber": 90,
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
|
||||||
"reasonDetail": "Known DOM elements"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-append(",
|
|
||||||
"path": "js/views/inbox_view.js",
|
|
||||||
"line": " .append(this.networkStatusView.render().el);",
|
|
||||||
"lineNumber": 109,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
|
||||||
"reasonDetail": "Known DOM elements"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-prependTo(",
|
|
||||||
"path": "js/views/inbox_view.js",
|
|
||||||
"line": " banner.$el.prependTo(this.$el);",
|
|
||||||
"lineNumber": 113,
|
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
|
@ -515,7 +497,7 @@
|
||||||
"rule": "jQuery-appendTo(",
|
"rule": "jQuery-appendTo(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " toast.$el.appendTo(this.$el);",
|
"line": " toast.$el.appendTo(this.$el);",
|
||||||
"lineNumber": 119,
|
"lineNumber": 98,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
|
@ -524,7 +506,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
|
"line": " this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
|
||||||
"lineNumber": 139,
|
"lineNumber": 118,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
|
@ -533,7 +515,7 @@
|
||||||
"rule": "jQuery-append(",
|
"rule": "jQuery-append(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
|
"line": " this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
|
||||||
"lineNumber": 139,
|
"lineNumber": 118,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
|
@ -542,7 +524,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " if (e && this.$(e.target).closest('.placeholder').length) {",
|
"line": " if (e && this.$(e.target).closest('.placeholder').length) {",
|
||||||
"lineNumber": 189,
|
"lineNumber": 168,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
|
@ -551,7 +533,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " this.$('#header, .gutter').addClass('inactive');",
|
"line": " this.$('#header, .gutter').addClass('inactive');",
|
||||||
"lineNumber": 193,
|
"lineNumber": 172,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Hardcoded selector"
|
"reasonDetail": "Hardcoded selector"
|
||||||
|
@ -560,7 +542,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " this.$('.conversation-stack').addClass('inactive');",
|
"line": " this.$('.conversation-stack').addClass('inactive');",
|
||||||
"lineNumber": 197,
|
"lineNumber": 176,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Hardcoded selector"
|
"reasonDetail": "Hardcoded selector"
|
||||||
|
@ -569,7 +551,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " this.$('.conversation:first .menu').trigger('close');",
|
"line": " this.$('.conversation:first .menu').trigger('close');",
|
||||||
"lineNumber": 199,
|
"lineNumber": 178,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Hardcoded selector"
|
"reasonDetail": "Hardcoded selector"
|
||||||
|
@ -578,7 +560,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " if (e && this.$(e.target).closest('.capture-audio').length > 0) {",
|
"line": " if (e && this.$(e.target).closest('.capture-audio').length > 0) {",
|
||||||
"lineNumber": 219,
|
"lineNumber": 198,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
|
@ -587,7 +569,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " this.$('.conversation:first .recorder').trigger('close');",
|
"line": " this.$('.conversation:first .recorder').trigger('close');",
|
||||||
"lineNumber": 222,
|
"lineNumber": 201,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Hardcoded selector"
|
"reasonDetail": "Hardcoded selector"
|
||||||
|
@ -9323,4 +9305,4 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-02-07T19:52:28.522Z"
|
"updated": "2020-02-07T19:52:28.522Z"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
27
ts/util/registration.ts
Normal file
27
ts/util/registration.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import * as RegistrationSelectors from '../state/selectors/registration';
|
||||||
|
|
||||||
|
export function markEverDone() {
|
||||||
|
// @ts-ignore
|
||||||
|
window.storage.put('chromiumRegistrationDoneEver', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function markDone() {
|
||||||
|
markEverDone();
|
||||||
|
// @ts-ignore
|
||||||
|
window.storage.put('chromiumRegistrationDone', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remove() {
|
||||||
|
// @ts-ignore
|
||||||
|
window.storage.remove('chromiumRegistrationDone');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDone() {
|
||||||
|
// @ts-ignore
|
||||||
|
return RegistrationSelectors.isDone(window.reduxStore.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function everDone() {
|
||||||
|
// @ts-ignore
|
||||||
|
return RegistrationSelectors.everDone(window.reduxStore.getState());
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue