merge dialog refctor with clearnet

pull/1703/head
Audric Ackermann 4 years ago
parent c9d7f4a1ab
commit a5d7995168
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

File diff suppressed because it is too large Load Diff

@ -1,14 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta content='width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0'
name='viewport'>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Security-Policy" content="default-src 'none';
<head>
<meta charset="utf-8" />
<meta
content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"
name="viewport"
/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none';
child-src 'self';
connect-src 'self' https: wss: blob:;
font-src 'self';
@ -18,145 +21,137 @@
media-src 'self' blob:;
object-src 'none';
script-src 'self' 'unsafe-eval';
style-src 'self' 'unsafe-inline';">
<title>Session</title>
<link href='images/sesion/session_icon_128.png' rel='shortcut icon'>
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
style-src 'self' 'unsafe-inline';"
/>
<title>Session</title>
<link href="images/sesion/session_icon_128.png" rel="shortcut icon" />
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
<!--
<!--
When making changes to these templates, be sure to update test/index.html as well
-->
<script type='text/x-tmpl-mustache' id='identicon-svg'>
<script type="text/x-tmpl-mustache" id="identicon-svg">
<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100'>
<circle cx='50' cy='50' r='40' fill='{{ color }}' />
<text text-anchor='middle' fill='white' font-family='sans-serif' font-size='24px' x='50' y='50' baseline-shift='-8px'>
{{ content }}
</text>
</svg>
</script>
<script type='text/x-tmpl-mustache' id='import-flow-template'>
{{#isStep2}}
<div id='step2' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon folder-outline'></span>
<div class='header'>{{ chooseHeader }}</div>
<div class='body-text'>{{ choose }}</div>
</div>
<div class='nav'>
<div>
<a class='button choose'>{{ chooseButton }}</a>
</script>
<script type="text/x-tmpl-mustache" id="import-flow-template">
{{#isStep2}}
<div id='step2' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon folder-outline'></span>
<div class='header'>{{ chooseHeader }}</div>
<div class='body-text'>{{ choose }}</div>
</div>
<div class='nav'>
<div>
<a class='button choose'>{{ chooseButton }}</a>
</div>
</div>
</div>
</div>
</div>
{{/isStep2}}
{{#isStep3}}
<div id='step3' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon import'></span>
<div class='header'>{{ importingHeader }}</div>
{{/isStep2}}
{{#isStep3}}
<div id='step3' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon import'></span>
<div class='header'>{{ importingHeader }}</div>
</div>
</div>
</div>
</div>
{{/isStep3}}
{{#isStep4}}
<div id='step4' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon'></span>
<div class='header'>{{ completeHeader }}</div>
</div>
<div class='nav'>
{{#restartButton}}
<div>
<a class='button restart'>{{ restartButton }}</a>
{{/isStep3}}
{{#isStep4}}
<div id='step4' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon'></span>
<div class='header'>{{ completeHeader }}</div>
</div>
{{/restartButton}}
{{#registerButton}}
<div>
<a class='button register'>{{ registerButton }}</a>
<div class='nav'>
{{#restartButton}}
<div>
<a class='button restart'>{{ restartButton }}</a>
</div>
{{/restartButton}}
{{#registerButton}}
<div>
<a class='button register'>{{ registerButton }}</a>
</div>
{{/registerButton}}
</div>
{{/registerButton}}
</div>
</div>
</div>
{{/isStep4}}
{{#isError}}
<div id='error' class='step'>
<div class='clearfix inner error-dialog'>
<div class='step-body'>
<span class='banner-icon alert-outline'></span>
<div class='header'>{{ errorHeader }}</div>
<div class='body-text-wide'>
{{ errorMessageFirst }}
<p>{{ errorMessageSecond }}</p>
{{/isStep4}}
{{#isError}}
<div id='error' class='step'>
<div class='clearfix inner error-dialog'>
<div class='step-body'>
<span class='banner-icon alert-outline'></span>
<div class='header'>{{ errorHeader }}</div>
<div class='body-text-wide'>
{{ errorMessageFirst }}
<p>{{ errorMessageSecond }}</p>
</div>
</div>
</div>
<div class='nav'>
<div>
<a class='button choose'>{{ chooseButton }}</a>
<div class='nav'>
<div>
<a class='button choose'>{{ chooseButton }}</a>
</div>
</div>
</div>
</div>
{{/isError}}
</script>
<script type="text/javascript" src="js/components.js"></script>
<script type="text/javascript" src="js/reliable_trigger.js"></script>
<script type="text/javascript" src="js/database.js"></script>
<script type="text/javascript" src="js/storage.js"></script>
<script type="text/javascript" src="js/legacy_storage.js"></script>
<script type="text/javascript" src="js/libtextsecure.js"></script>
<script type="text/javascript" src="js/libloki.js"></script>
<script type="text/javascript" src="js/focus_listener.js"></script>
<script type="text/javascript" src="js/notifications.js"></script>
<script type="text/javascript" src="js/read_receipts.js"></script>
<script type="text/javascript" src="js/read_syncs.js"></script>
<script type="text/javascript" src="js/expiring_messages.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/views/react_wrapper_view.js"></script>
<script type="text/javascript" src="js/views/whisper_view.js"></script>
<script type="text/javascript" src="js/views/session_inbox_view.js"></script>
<script type="text/javascript" src="js/views/identicon_svg_view.js"></script>
<script type="text/javascript" src="js/views/session_registration_view.js"></script>
<script type="text/javascript" src="js/views/app_view.js"></script>
<script type="text/javascript" src="js/views/import_view.js"></script>
<script type="text/javascript" src="js/views/session_id_reset_view.js"></script>
<!-- CRYPTO -->
<script type="text/javascript" src="js/wall_clock_listener.js"></script>
</head>
<body>
<div class="app-loading-screen">
<div class="content session-full-logo">
<img src="images/session/brand.svg" class="session-brand-logo" />
<img src="images/session/session-text.svg" class="session-text-logo" />
</div>
</div>
{{/isError}}
</script>
<script type='text/javascript' src='js/components.js'></script>
<script type='text/javascript' src='js/reliable_trigger.js'></script>
<script type='text/javascript' src='js/database.js'></script>
<script type='text/javascript' src='js/storage.js'></script>
<script type='text/javascript' src='js/legacy_storage.js'></script>
<script type='text/javascript' src='js/libtextsecure.js'></script>
<script type='text/javascript' src='js/libloki.js'></script>
<script type='text/javascript' src='js/focus_listener.js'></script>
<script type='text/javascript' src='js/notifications.js'></script>
<script type='text/javascript' src='js/read_receipts.js'></script>
<script type='text/javascript' src='js/read_syncs.js'></script>
<script type='text/javascript' src='js/expiring_messages.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/views/react_wrapper_view.js'></script>
<script type='text/javascript' src='js/views/whisper_view.js'></script>
<script type='text/javascript' src='js/views/session_inbox_view.js'></script>
<script type='text/javascript' src='js/views/identicon_svg_view.js'></script>
<script type='text/javascript' src='js/views/session_registration_view.js'></script>
<script type='text/javascript' src='js/views/app_view.js'></script>
<script type='text/javascript' src='js/views/import_view.js'></script>
<!-- DIALOGS-->
<!-- <script type='text/javascript' src='js/views/update_group_dialog_view.js'></script> -->
<!-- <script type='text/javascript' src='js/views/invite_contacts_dialog_view.js'></script> -->
<!-- <script type='text/javascript' src='js/views/admin_leave_closed_group_dialog_view.js'></script> -->
<!-- <script type='text/javascript' src='js/views/user_details_dialog_view.js'></script> -->
<script type='text/javascript' src='js/views/session_id_reset_view.js'></script>
<!-- CRYPTO -->
<script type='text/javascript' src='js/wall_clock_listener.js'></script>
</head>
<body>
<div class='app-loading-screen'>
<div class="content session-full-logo">
<img src="images/session/brand.svg" class="session-brand-logo" />
<img src="images/session/session-text.svg" class="session-text-logo" />
</div>
</div>
<script type='text/javascript' src='js/background.js'></script>
</body>
<script type="text/javascript" src="js/background.js"></script>
</body>
</html>

@ -1,14 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta content='width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0'
name='viewport'>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Security-Policy" content="default-src 'none';
<head>
<meta charset="utf-8" />
<meta
content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"
name="viewport"
/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none';
child-src 'self';
connect-src 'self' https: wss:;
font-src 'self';
@ -18,156 +21,144 @@
media-src 'self' blob:;
object-src 'none';
script-src 'self' 'unsafe-eval';
style-src 'self' 'unsafe-inline';">
<title>Session</title>
<link href='images/session/session_icon_128.png' rel='shortcut icon'>
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
style-src 'self' 'unsafe-inline';"
/>
<title>Session</title>
<link href="images/session/session_icon_128.png" rel="shortcut icon" />
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
<!--
<!--
When making changes to these templates, be sure to update test/index.html as well
-->
<script type='text/x-tmpl-mustache' id='identicon-svg'>
<script type="text/x-tmpl-mustache" id="identicon-svg">
<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100'>
<circle cx='50' cy='50' r='40' fill='{{ color }}' />
<text text-anchor='middle' fill='white' font-family='sans-serif' font-size='24px' x='50' y='50' baseline-shift='-8px'>
{{ content }}
</text>
</svg>
</script>
<script type='text/x-tmpl-mustache' id='import-flow-template'>
{{#isStep2}}
<div id='step2' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon folder-outline'></span>
<div class='header'>{{ chooseHeader }}</div>
<div class='body-text'>{{ choose }}</div>
</div>
<div class='nav'>
<div>
<a class='button choose'>{{ chooseButton }}</a>
</script>
<script type="text/x-tmpl-mustache" id="import-flow-template">
{{#isStep2}}
<div id='step2' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon folder-outline'></span>
<div class='header'>{{ chooseHeader }}</div>
<div class='body-text'>{{ choose }}</div>
</div>
<div class='nav'>
<div>
<a class='button choose'>{{ chooseButton }}</a>
</div>
</div>
</div>
</div>
</div>
{{/isStep2}}
{{#isStep3}}
<div id='step3' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon import'></span>
<div class='header'>{{ importingHeader }}</div>
</div>
<div class='progress'>
<div class='bar-container'>
<div class='bar progress-bar progress-bar-striped active'></div>
{{/isStep2}}
{{#isStep3}}
<div id='step3' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon import'></span>
<div class='header'>{{ importingHeader }}</div>
</div>
<div class='progress'>
<div class='bar-container'>
<div class='bar progress-bar progress-bar-striped active'></div>
</div>
</div>
</div>
</div>
</div>
{{/isStep3}}
{{#isStep4}}
<div id='step4' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon'></span>
<div class='header'>{{ completeHeader }}</div>
</div>
<div class='nav'>
{{#restartButton}}
<div>
<a class='button restart'>{{ restartButton }}</a>
{{/isStep3}}
{{#isStep4}}
<div id='step4' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon'></span>
<div class='header'>{{ completeHeader }}</div>
</div>
{{/restartButton}}
{{#registerButton}}
<div>
<a class='button register'>{{ registerButton }}</a>
<div class='nav'>
{{#restartButton}}
<div>
<a class='button restart'>{{ restartButton }}</a>
</div>
{{/restartButton}}
{{#registerButton}}
<div>
<a class='button register'>{{ registerButton }}</a>
</div>
{{/registerButton}}
</div>
{{/registerButton}}
</div>
</div>
</div>
{{/isStep4}}
{{#isError}}
<div id='error' class='step'>
<div class='clearfix inner error-dialog'>
<div class='step-body'>
<span class='banner-icon alert-outline'></span>
<div class='header'>{{ errorHeader }}</div>
<div class='body-text-wide'>
{{ errorMessageFirst }}
<p>{{ errorMessageSecond }}</p>
{{/isStep4}}
{{#isError}}
<div id='error' class='step'>
<div class='clearfix inner error-dialog'>
<div class='step-body'>
<span class='banner-icon alert-outline'></span>
<div class='header'>{{ errorHeader }}</div>
<div class='body-text-wide'>
{{ errorMessageFirst }}
<p>{{ errorMessageSecond }}</p>
</div>
</div>
</div>
<div class='nav'>
<div>
<a class='button choose'>{{ chooseButton }}</a>
<div class='nav'>
<div>
<a class='button choose'>{{ chooseButton }}</a>
</div>
</div>
</div>
</div>
{{/isError}}
</script>
<script type="text/javascript" src="js/components.js"></script>
<script type="text/javascript" src="js/reliable_trigger.js"></script>
<script type="text/javascript" src="js/database.js"></script>
<script type="text/javascript" src="js/storage.js"></script>
<script type="text/javascript" src="js/legacy_storage.js"></script>
<script type="text/javascript" src="js/libtextsecure.js"></script>
<script type="text/javascript" src="js/libloki.js"></script>
<script type="text/javascript" src="js/focus_listener.js"></script>
<script type="text/javascript" src="js/notifications.js"></script>
<script type="text/javascript" src="js/read_receipts.js"></script>
<script type="text/javascript" src="js/read_syncs.js"></script>
<script type="text/javascript" src="js/expiring_messages.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/views/react_wrapper_view.js"></script>
<script type="text/javascript" src="js/views/whisper_view.js"></script>
<script type="text/javascript" src="js/views/session_confirm_view.js"></script>
<script type="text/javascript" src="js/views/session_inbox_view.js"></script>
<script type="text/javascript" src="js/views/identicon_svg_view.js"></script>
<script type="text/javascript" src="js/views/session_registration_view.js"></script>
<script type="text/javascript" src="js/views/app_view.js"></script>
<script type="text/javascript" src="js/views/import_view.js"></script>
<script type="text/javascript" src="js/views/password_dialog_view.js"></script>
<script type="text/javascript" src="js/views/seed_dialog_view.js"></script>
<!-- CRYPTO -->
<script type="text/javascript" src="js/wall_clock_listener.js"></script>
</head>
<body>
<div class="app-loading-screen">
<div class="content session-full-logo">
<img src="images/session/brand.svg" class="session-brand-logo" />
<img src="images/session/session-text.svg" class="session-text-logo" />
</div>
</div>
{{/isError}}
</script>
<script type='text/javascript' src='js/components.js'></script>
<script type='text/javascript' src='js/reliable_trigger.js'></script>
<script type='text/javascript' src='js/database.js'></script>
<script type='text/javascript' src='js/storage.js'></script>
<script type='text/javascript' src='js/legacy_storage.js'></script>
<script type='text/javascript' src='js/libtextsecure.js'></script>
<script type='text/javascript' src='js/libloki.js'></script>
<script type='text/javascript' src='js/focus_listener.js'></script>
<script type='text/javascript' src='js/notifications.js'></script>
<script type='text/javascript' src='js/read_receipts.js'></script>
<script type='text/javascript' src='js/read_syncs.js'></script>
<script type='text/javascript' src='js/expiring_messages.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/views/react_wrapper_view.js'></script>
<script type='text/javascript' src='js/views/whisper_view.js'></script>
<script type='text/javascript' src='js/views/session_confirm_view.js'></script>
<script type='text/javascript' src='js/views/session_inbox_view.js'></script>
<script type='text/javascript' src='js/views/identicon_svg_view.js'></script>
<script type='text/javascript' src='js/views/session_registration_view.js'></script>
<script type='text/javascript' src='js/views/app_view.js'></script>
<script type='text/javascript' src='js/views/import_view.js'></script>
<!-- DIALOGS-->
<script type='text/javascript' src='js/views/update_group_dialog_view.js'></script>
<script type='text/javascript' src='js/views/edit_profile_dialog_view.js'></script>
<script type='text/javascript' src='js/views/onion_status_dialog_view.js'></script>
<script type='text/javascript' src='js/views/invite_contacts_dialog_view.js'></script>
<script type='text/javascript' src='js/views/admin_leave_closed_group_dialog_view.js'></script>
<script type='text/javascript' src='js/views/moderators_add_dialog_view.js'></script>
<script type='text/javascript' src='js/views/moderators_remove_dialog_view.js'></script>
<script type='text/javascript' src='js/views/user_details_dialog_view.js'></script>
<script type='text/javascript' src='js/views/password_dialog_view.js'></script>
<script type='text/javascript' src='js/views/seed_dialog_view.js'></script>
<!-- CRYPTO -->
<script type='text/javascript' src='js/wall_clock_listener.js'></script>
</head>
<body>
<div class='app-loading-screen'>
<div class="content session-full-logo">
<img src="images/session/brand.svg" class="session-brand-logo" />
<img src="images/session/session-text.svg" class="session-text-logo" />
</div>
</div>
<script type='text/javascript' src='js/background.js'></script>
</body>
</html>
<script type="text/javascript" src="js/background.js"></script>
</body>
</html>

@ -365,41 +365,6 @@
window.setMediaPermissions(!value);
};
// Whisper.events.on('updateGroupName', async groupConvo => {
// if (appView) {
// appView.showUpdateGroupNameDialog(groupConvo);
// }
// });
// Whisper.events.on('updateGroupMembers', async groupConvo => {
// if (appView) {
// appView.showUpdateGroupMembersDialog(groupConvo);
// }
// });
// Whisper.events.on('inviteContacts', async groupConvo => {
// if (appView) {
// appView.showInviteContactsDialog(groupConvo);
// }
// });
// Whisper.events.on('addModerators', async groupConvo => {
// if (appView) {
// appView.showAddModeratorsDialog(groupConvo);
// }
// });
// Whisper.events.on('removeModerators', async groupConvo => {
// if (appView) {
// appView.showRemoveModeratorsDialog(groupConvo);
// }
// });
// Whisper.events.on('leaveClosedGroup', async groupConvo => {
// if (appView) {
// appView.showLeaveGroupDialog(groupConvo);
// }
// });
Whisper.Notifications.on('click', (id, messageId) => {
window.showWindow();
if (id) {
@ -417,29 +382,6 @@
});
});
// Whisper.events.on('onShowUserDetails', async ({ userPubKey }) => {
// const conversation = await window
// .getConversationController()
// .getOrCreateAndWait(userPubKey, 'private');
// const avatarPath = conversation.getAvatarPath();
// const profile = conversation.getLokiProfile();
// const displayName = profile && profile.displayName;
// if (appView) {
// appView.showUserDetailsDialog({
// profileName: displayName,
// pubkey: userPubKey,
// avatarPath,
// onStartConversation: () => {
// window.inboxStore.dispatch(
// window.actionsCreators.openConversationExternal(conversation.id)
// );
// },
// });
// }
// });
Whisper.events.on('password-updated', () => {
if (appView && appView.inboxView) {
appView.inboxView.trigger('password-updated');

@ -12,8 +12,6 @@ const LinkPreviews = require('./link_previews');
const { Message } = require('../../ts/components/conversation/Message');
// Components
const { EditProfileDialog } = require('../../ts/components/EditProfileDialog');
const { UserDetailsDialog } = require('../../ts/components/UserDetailsDialog');
const { SessionSeedModal } = require('../../ts/components/session/SessionSeedModal');
const { SessionNicknameDialog } = require('../../ts/components/session/SessionNicknameDialog');
const { SessionIDResetDialog } = require('../../ts/components/session/SessionIDResetDialog');
@ -140,8 +138,6 @@ exports.setup = (options = {}) => {
});
const Components = {
EditProfileDialog,
UserDetailsDialog,
SessionInboxView,
UpdateGroupNameDialog,
UpdateGroupMembersDialog,

@ -1,43 +0,0 @@
/* global Whisper */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.AdminLeaveClosedGroupDialog = Whisper.View.extend({
className: 'loki-dialog modal',
initialize(convo) {
this.close = this.close.bind(this);
this.submit = this.submit.bind(this);
this.theme = convo.theme;
this.groupName = convo.get('name');
this.convo = convo;
this.$el.focus();
this.render();
},
render() {
const view = new Whisper.ReactWrapperView({
className: 'admin-leave-closed-group',
Component: window.Signal.Components.AdminLeaveClosedGroupDialog,
props: {
onSubmit: this.submit,
onClose: this.close,
groupName: this.groupName,
theme: this.theme,
},
});
this.$el.append(view.el);
return this;
},
close() {
this.remove();
},
submit() {
this.convo.leaveClosedGroup();
},
});
})();

@ -15,7 +15,6 @@
this.applyTheme();
this.applyRtl();
this.applyHideMenu();
},
events: {
openInbox: 'openInbox',
@ -117,79 +116,10 @@
this.el.prepend(resetSessionIDDialog.el);
},
// showUserDetailsDialog(options) {
// // eslint-disable-next-line no-param-reassign
// options.theme = this.getThemeObject();
// const dialog = new Whisper.UserDetailsDialogView(options);
// this.el.prepend(dialog.el);
// },
getThemeObject() {
const themeSettings = storage.get('theme-setting') || 'light';
const theme = themeSettings === 'light' ? window.lightTheme : window.darkTheme;
return theme;
},
// showUpdateGroupNameDialog(groupConvo) {
// // eslint-disable-next-line no-param-reassign
// groupConvo.theme = this.getThemeObject();
// const dialog = new Whisper.UpdateGroupNameDialogView(groupConvo);
// this.el.append(dialog.el);
// },
// showUpdateGroupMembersDialog(groupConvo) {
// // eslint-disable-next-line no-param-reassign
// groupConvo.theme = this.getThemeObject();
// const dialog = new Whisper.UpdateGroupMembersDialogView(groupConvo);
// this.el.append(dialog.el);
// },
// showLeaveGroupDialog(groupConvo) {
// if (!groupConvo.isGroup()) {
// throw new Error('showLeaveGroupDialog() called with a non group convo.');
// }
// const title = i18n('leaveGroup');
// const message = i18n('leaveGroupConfirmation');
// const ourPK = window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache();
// const isAdmin = (groupConvo.get('groupAdmins') || []).includes(ourPK);
// const isClosedGroup = groupConvo.get('is_medium_group') || false;
// // if this is not a closed group, or we are not admin, we can just show a confirmation dialog
// if (!isClosedGroup || (isClosedGroup && !isAdmin)) {
// window.confirmationDialog({
// title,
// message,
// resolve: () => groupConvo.leaveClosedGroup(),
// theme: this.getThemeObject(),
// });
// } else {
// // we are the admin on a closed group. We have to warn the user about the group Deletion
// this.showAdminLeaveClosedGroupDialog(groupConvo);
// }
// },
// showInviteContactsDialog(groupConvo) {
// // eslint-disable-next-line no-param-reassign
// groupConvo.theme = this.getThemeObject();
// const dialog = new Whisper.InviteContactsDialogView(groupConvo);
// this.el.append(dialog.el);
// },
// showAdminLeaveClosedGroupDialog(groupConvo) {
// // eslint-disable-next-line no-param-reassign
// groupConvo.theme = this.getThemeObject();
// const dialog = new Whisper.AdminLeaveClosedGroupDialog(groupConvo);
// this.el.append(dialog.el);
// },
// showAddModeratorsDialog(groupConvo) {
// // eslint-disable-next-line no-param-reassign
// groupConvo.theme = this.getThemeObject();
// const dialog = new Whisper.AddModeratorsDialogView(groupConvo);
// this.el.append(dialog.el);
// },
// showRemoveModeratorsDialog(groupConvo) {
// // eslint-disable-next-line no-param-reassign
// groupConvo.theme = this.getThemeObject();
// const dialog = new Whisper.RemoveModeratorsDialogView(groupConvo);
// this.el.append(dialog.el);
// },
});
})();

@ -1,36 +0,0 @@
/* global Whisper */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.AddModeratorsDialogView = Whisper.View.extend({
className: 'loki-dialog modal',
async initialize(convo) {
this.close = this.close.bind(this);
this.theme = convo.theme;
this.convo = convo;
this.$el.focus();
this.render();
},
render() {
const view = new Whisper.ReactWrapperView({
className: 'add-moderators-dialog',
Component: window.Signal.Components.AddModeratorsDialog,
props: {
onClose: this.close,
convo: this.convo,
theme: this.theme,
},
});
this.$el.append(view.el);
return this;
},
close() {
this.remove();
},
});
})();

@ -1,36 +0,0 @@
/* global Whisper */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.RemoveModeratorsDialogView = Whisper.View.extend({
className: 'loki-dialog modal',
async initialize(convo) {
this.close = this.close.bind(this);
this.convo = convo;
this.$el.focus();
this.render();
},
render() {
const view = new Whisper.ReactWrapperView({
className: 'remove-moderators-dialog',
Component: window.Signal.Components.RemoveModeratorsDialog,
props: {
onClose: this.close,
convo: this.convo,
theme: this.convo.theme,
},
});
this.$el.append(view.el);
return this;
},
close() {
this.remove();
},
});
})();

@ -1,196 +0,0 @@
/* global Whisper, i18n, _ */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.UpdateGroupNameDialogView = Whisper.View.extend({
className: 'loki-dialog modal',
initialize(groupConvo) {
this.groupName = groupConvo.get('name');
this.conversation = groupConvo;
this.titleText = i18n('updateGroupDialogTitle', this.groupName);
this.close = this.close.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.isPublic = groupConvo.isPublic();
this.groupId = groupConvo.id;
this.members = groupConvo.get('members') || [];
this.avatarPath = groupConvo.getAvatarPath();
this.theme = groupConvo.theme;
// any member can update a closed group name
this.isAdmin = true;
// public chat settings overrides
if (this.isPublic) {
// fix the title
this.titleText = i18n('updateGroupDialogTitle', this.groupName);
// I'd much prefer to integrate mods with groupAdmins
// but lets discuss first...
this.isAdmin = groupConvo.isAdmin(window.storage.get('primaryDevicePubKey'));
}
this.$el.focus();
this.render();
},
render() {
this.dialogView = new Whisper.ReactWrapperView({
className: 'create-group-dialog',
Component: window.Signal.Components.UpdateGroupNameDialog,
props: {
titleText: this.titleText,
isPublic: this.isPublic,
pubkey: this.groupId,
groupName: this.groupName,
okText: i18n('ok'),
cancelText: i18n('cancel'),
isAdmin: this.isAdmin,
i18n,
onSubmit: this.onSubmit,
onClose: this.close,
avatarPath: this.avatarPath,
theme: this.theme,
},
});
this.$el.append(this.dialogView.el);
return this;
},
onSubmit(groupName, avatar) {
if (groupName !== this.groupName || avatar !== this.avatarPath) {
window.libsession.ClosedGroup.initiateGroupUpdate(
this.groupId,
groupName,
this.members,
avatar
);
}
},
close() {
this.remove();
},
});
Whisper.UpdateGroupMembersDialogView = Whisper.View.extend({
className: 'loki-dialog modal',
initialize(groupConvo) {
this.groupName = groupConvo.get('name');
this.close = this.close.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.isPublic = groupConvo.isPublic();
this.groupId = groupConvo.id;
this.avatarPath = groupConvo.getAvatarPath();
this.theme = groupConvo.theme;
if (this.isPublic) {
throw new Error('UpdateGroupMembersDialog is only made for Closed/Medium groups');
}
this.titleText = i18n('updateGroupDialogTitle', this.groupName);
// anybody can edit a closed group name or members
const ourPK = window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache();
this.isAdmin = groupConvo.get('groupAdmins').includes(ourPK);
this.admins = groupConvo.get('groupAdmins');
const convos = window
.getConversationController()
.getConversations()
.filter(d => !!d);
this.existingMembers = groupConvo.get('members') || [];
this.existingZombies = groupConvo.get('zombies') || [];
// Show a contact if they are our friend or if they are a member
this.contactsAndMembers = convos.filter(
d => this.existingMembers.includes(d.id) && d.isPrivate() && !d.isMe()
);
this.contactsAndMembers = _.uniq(this.contactsAndMembers, true, d => d.id);
// at least make sure it's an array
if (!Array.isArray(this.existingMembers)) {
this.existingMembers = [];
}
this.$el.focus();
this.render();
},
render() {
this.dialogView = new Whisper.ReactWrapperView({
className: 'create-group-dialog',
Component: window.Signal.Components.UpdateGroupMembersDialog,
props: {
titleText: this.titleText,
okText: i18n('ok'),
cancelText: i18n('cancel'),
isPublic: this.isPublic,
existingMembers: this.existingMembers,
existingZombies: this.existingZombies,
contactList: this.contactsAndMembers,
isAdmin: this.isAdmin,
admins: this.admins,
onClose: this.close,
onSubmit: this.onSubmit,
groupId: this.groupId,
theme: this.theme,
},
});
this.$el.append(this.dialogView.el);
return this;
},
async onSubmit(newMembers) {
const _ = window.Lodash;
const ourPK = window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache();
const allMembersAfterUpdate = window.Lodash.concat(newMembers, [ourPK]);
if (!this.isAdmin) {
window.log.warn('Skipping update of members, we are not the admin');
return;
}
// new members won't include the zombies. We are the admin and we want to remove them not matter what
// We need to NOT trigger an group update if the list of member is the same.
// we need to merge all members, including zombies for this call.
// we consider that the admin ALWAYS wants to remove zombies (actually they should be removed
// automatically by him when the LEFT message is received)
const allExistingMembersWithZombies = _.uniq(
this.existingMembers.concat(this.existingZombies)
);
const notPresentInOld = allMembersAfterUpdate.filter(
m => !allExistingMembersWithZombies.includes(m)
);
// be sure to include zombies in here
const membersToRemove = allExistingMembersWithZombies.filter(
m => !allMembersAfterUpdate.includes(m)
);
const xor = _.xor(membersToRemove, notPresentInOld);
if (xor.length === 0) {
window.log.info('skipping group update: no detected changes in group member list');
return;
}
// If any extra devices of removed exist in newMembers, ensure that you filter them
// Note: I think this is useless
const filteredMembers = allMembersAfterUpdate.filter(
member => !_.includes(membersToRemove, member)
);
window.libsession.ClosedGroup.initiateGroupUpdate(
this.groupId,
this.groupName,
filteredMembers,
this.avatarPath
);
},
close() {
this.remove();
},
});
})();

@ -1,47 +0,0 @@
/* global i18n, Whisper */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.UserDetailsDialogView = Whisper.View.extend({
className: 'loki-dialog modal',
initialize({ profileName, avatarPath, pubkey, onOk, onStartConversation, theme }) {
this.close = this.close.bind(this);
this.profileName = profileName;
this.pubkey = pubkey;
this.avatarPath = avatarPath;
this.onOk = onOk;
this.onStartConversation = onStartConversation;
this.theme = theme;
this.$el.focus();
this.render();
},
render() {
this.dialogView = new Whisper.ReactWrapperView({
className: 'user-details-dialog',
Component: window.Signal.Components.UserDetailsDialog,
props: {
onOk: this.onOk,
onClose: this.close,
onStartConversation: this.onStartConversation,
profileName: this.profileName,
pubkey: this.pubkey,
avatarPath: this.avatarPath,
i18n,
theme: this.theme,
},
});
this.$el.append(this.dialogView.el);
return this;
},
close() {
this.remove();
},
});
})();

@ -48,7 +48,6 @@
.avatar-center-inner {
display: flex;
// padding-top: 30px;
}
.upload-btn-background {

@ -310,18 +310,6 @@
}
}
// .modal {
// position: fixed;
// top: 0;
// left: 0;
// width: 100%;
// height: 100%;
// z-index: 10000;
// padding: 2rem;
// background-color: rgba(0, 0, 0, 0.55);
// }
.onion-status-dialog {
.session-modal__header__title {
font-size: $session-font-lg;
@ -380,26 +368,5 @@
.lineContainer {
height: 50px;
}
// .line {
// height: 100%;
// @include themify($themes) {
// border-left: 1px solid themed('textColor');
// }
// display: relative;
// // align-self: flex-start;
// margin-left: 7px;
// // z-index: -1;
// }
// .dot:after {
// position: absolute;
// left: 0;
// top: 0;
// content: '';
// border-left: 2px solid black;
// margin-left: 5px;
// height: 100%;
// }
}
}

@ -186,16 +186,6 @@
<script type="text/javascript" src="../js/views/import_view.js"></script>
<!-- DIALOGS-->
<script type="text/javascript" src="../js/views/update_group_dialog_view.js"></script>
<script type="text/javascript" src="../js/views/edit_profile_dialog_view.js"></script>
<script type='text/javascript' src='js/views/onion_status_dialog_view.js'></script>
<script type="text/javascript" src="../js/views/invite_contacts_dialog_view.js"></script>
<script type='text/javascript' src='../js/views/admin_leave_closed_group_dialog_view.js'></script>
<script type="text/javascript" src="../js/views/moderators_add_dialog_view.js"></script>
<script type="text/javascript" src="../js/views/moderators_remove_dialog_view.js"></script>
<script type="text/javascript" src="../js/views/user_details_dialog_view.js"></script>
<script type="text/javascript" src="../js/views/password_dialog_view.js"></script>
<script type="text/javascript" src="../js/views/seed_dialog_view.js"></script>

@ -8,21 +8,17 @@ import { SessionButton, SessionButtonColor, SessionButtonType } from './session/
import { SessionIconButton, SessionIconSize, SessionIconType } from './session/icon';
import { PillDivider } from './session/PillDivider';
import { AttachmentUtils, SyncUtils, ToastUtils, UserUtils } from '../session/utils';
import { DefaultTheme, useTheme } from 'styled-components';
import { SyncUtils, ToastUtils, UserUtils } from '../session/utils';
import { DefaultTheme } from 'styled-components';
import { MAX_USERNAME_LENGTH } from './session/registration/RegistrationTabs';
import { SessionSpinner } from './session/SessionSpinner';
import { ConversationTypeEnum } from '../models/conversation';
import { SessionWrapperModal } from './session/SessionWrapperModal';
import { AttachmentUtil, } from '../util';
import { LocalizerType } from '../types/Util';
import { AttachmentUtil } from '../util';
import { ConversationController } from '../session/conversations';
interface Props {
i18n?: LocalizerType;
profileName?: string;
avatarPath?: string;
pubkey?: string;
@ -41,7 +37,6 @@ interface State {
export class EditProfileDialog extends React.Component<Props, State> {
private readonly inputEl: any;
private conversationController = ConversationController.getInstance();
constructor(props: any) {
super(props);
@ -66,13 +61,14 @@ export class EditProfileDialog extends React.Component<Props, State> {
window.addEventListener('keyup', this.onKeyUp);
}
async componentDidMount() {
public async componentDidMount() {
const ourNumber = window.storage.get('primaryDevicePubKey');
const conversation = await this.conversationController.getOrCreateAndWait(ourNumber, ConversationTypeEnum.PRIVATE);
const conversation = await ConversationController.getInstance().getOrCreateAndWait(
ourNumber,
ConversationTypeEnum.PRIVATE
);
const readFile = (attachment: any) =>
const readFile = async (attachment: any) =>
new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = (e: any) => {
@ -96,9 +92,8 @@ export class EditProfileDialog extends React.Component<Props, State> {
...this.state,
profileName: profile.profileName,
avatar: avatarPath || '',
setProfileName: profile.profileName
})
setProfileName: profile.profileName,
});
}
public render() {
@ -114,19 +109,18 @@ export class EditProfileDialog extends React.Component<Props, State> {
const backButton =
viewEdit || viewQR
? [
{
iconType: SessionIconType.Chevron,
iconRotation: 90,
onClick: () => {
this.setState({ mode: 'default' });
{
iconType: SessionIconType.Chevron,
iconRotation: 90,
onClick: () => {
this.setState({ mode: 'default' });
},
},
},
]
]
: undefined;
return (
<div className="edit-profile-dialog">
<SessionWrapperModal
title={i18n('editProfileModalTitle')}
onClose={this.closeDialog}
@ -142,7 +136,9 @@ export class EditProfileDialog extends React.Component<Props, State> {
<div className="session-id-section">
<PillDivider text={window.i18n('yourSessionID')} />
<p className={classNames('text-selectable', 'session-id-section-display')}>{sessionID}</p>
<p className={classNames('text-selectable', 'session-id-section-display')}>
{sessionID}
</p>
<div className="spacer-lg" />
<SessionSpinner loading={this.state.loading} />
@ -175,8 +171,6 @@ export class EditProfileDialog extends React.Component<Props, State> {
);
}
private renderProfileHeader() {
return (
<>
@ -324,7 +318,6 @@ export class EditProfileDialog extends React.Component<Props, State> {
/**
* Tidy the profile name input text and save the new profile name and avatar
* @returns
*/
private onClickOK() {
const newName = this.state.profileName ? this.state.profileName.trim() : '';
@ -335,9 +328,9 @@ export class EditProfileDialog extends React.Component<Props, State> {
const avatar =
this.inputEl &&
this.inputEl.current &&
this.inputEl.current.files &&
this.inputEl.current.files.length > 0
this.inputEl.current &&
this.inputEl.current.files &&
this.inputEl.current.files.length > 0
? this.inputEl.current.files[0]
: null;
@ -364,8 +357,11 @@ export class EditProfileDialog extends React.Component<Props, State> {
}
private async commitProfileEdits(newName: string, avatar: any) {
let ourNumber = window.storage.get('primaryDevicePubKey');
const conversation = await this.conversationController.getOrCreateAndWait(ourNumber, ConversationTypeEnum.PRIVATE);
const ourNumber = window.storage.get('primaryDevicePubKey');
const conversation = await ConversationController.getInstance().getOrCreateAndWait(
ourNumber,
ConversationTypeEnum.PRIVATE
);
let newAvatarPath = '';
let url: any = null;
@ -375,7 +371,6 @@ export class EditProfileDialog extends React.Component<Props, State> {
// Ensure that this file is either small enough or is resized to meet our
// requirements for attachments
try {
const withBlob = await AttachmentUtil.autoScale(
{
contentType: avatar.type,
@ -388,16 +383,14 @@ export class EditProfileDialog extends React.Component<Props, State> {
maxSize: 1000 * 1024,
}
);
const dataResized = await window.Signal.Types.Attachment.arrayBufferFromFile(
withBlob.file
);
const dataResized = await window.Signal.Types.Attachment.arrayBufferFromFile(withBlob.file);
// For simplicity we use the same attachment pointer that would send to
// others, which means we need to wait for the database response.
// To avoid the wait, we create a temporary url for the local image
// and use it until we the the response from the server
const tempUrl = window.URL.createObjectURL(avatar);
conversation.setLokiProfile({ displayName: newName });
await conversation.setLokiProfile({ displayName: newName });
conversation.set('avatar', tempUrl);
// Encrypt with a new key every time
@ -407,32 +400,34 @@ export class EditProfileDialog extends React.Component<Props, State> {
profileKey
);
const avatarPointer = await AttachmentUtils.uploadAvatarV1({
...dataResized,
data: encryptedData,
size: encryptedData.byteLength,
});
url = avatarPointer ? avatarPointer.url : null;
window.storage.put('profileKey', profileKey);
conversation.set('avatarPointer', url);
const upgraded = await window.Signal.Migrations.processNewAttachment({
isRaw: true,
data: data.data,
url,
});
newAvatarPath = upgraded.path;
// Replace our temporary image with the attachment pointer from the server:
conversation.set('avatar', null);
conversation.setLokiProfile({
displayName: newName,
avatar: newAvatarPath,
});
await conversation.commit();
UserUtils.setLastProfileUpdateTimestamp(Date.now());
await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
throw new Error('uploadAvatarV1 to move to v2');
// const avatarPointer = await AttachmentUtils.uploadAvatarV1({
// ...dataResized,
// data: encryptedData,
// size: encryptedData.byteLength,
// });
// url = avatarPointer ? avatarPointer.url : null;
// window.storage.put('profileKey', profileKey);
// conversation.set('avatarPointer', url);
// const upgraded = await window.Signal.Migrations.processNewAttachment({
// isRaw: true,
// data: data.data,
// url,
// });
// newAvatarPath = upgraded.path;
// // Replace our temporary image with the attachment pointer from the server:
// conversation.set('avatar', null);
// await conversation.setLokiProfile({
// displayName: newName,
// avatar: newAvatarPath,
// });
// await conversation.commit();
// UserUtils.setLastProfileUpdateTimestamp(Date.now());
// await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
} catch (error) {
window.log.error(
'showEditProfileDialog Error ensuring that image is properly sized:',
@ -441,7 +436,7 @@ export class EditProfileDialog extends React.Component<Props, State> {
}
} else {
// do not update the avatar if it did not change
conversation.setLokiProfile({
await conversation.setLokiProfile({
displayName: newName,
});
// might be good to not trigger a sync if the name did not change
@ -450,15 +445,9 @@ export class EditProfileDialog extends React.Component<Props, State> {
await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
}
// inform all your registered public servers
// could put load on all the servers
// if they just keep changing their names without sending messages
// so we could disable this here
// or least it enable for the quickest response
window.lokiPublicChatAPI.setProfileName(newName);
if (avatar) {
this.conversationController.getConversations()
ConversationController.getInstance()
.getConversations()
.filter(convo => convo.isPublic())
.forEach(convo => convo.trigger('ourAvatarChanged', { url, profileKey }));
}

@ -4,19 +4,20 @@ import _ from 'lodash';
import { getTheme } from '../state/selectors/theme';
import electron from 'electron';
import Electron from 'electron';
const { shell } = Electron;
import { useSelector } from 'react-redux';
import { StateType } from '../state/reducer';
import { SessionIcon, SessionIconButton, SessionIconSize, SessionIconType } from './session/icon';
const { shell } = electron;
import { SessionWrapperModal } from '../components/session/SessionWrapperModal';
import { Snode } from '../session/onions';
import ip2country from 'ip2country';
import countryLookup from 'country-code-lookup';
import { useTheme } from 'styled-components';
import { useNetwork } from '../hooks/useNetwork';
import { Snode } from '../data/data';
export type OnionPathModalType = {
onConfirm?: () => void;
@ -36,7 +37,7 @@ const OnionPathModalInner = (props: any) => {
const onionNodes = useSelector((state: StateType) => state.onionPaths.snodePath);
const confirmModalState = useSelector((state: StateType) => state);
console.log('onion path: ', confirmModalState);
const onionPath = onionNodes.path;
const onionPath = onionNodes;
// including the device and destination in calculation
const glowDuration = onionPath.length + 2;
@ -44,7 +45,7 @@ const OnionPathModalInner = (props: any) => {
{
label: window.i18n('device'),
},
...onionNodes.path,
...onionNodes,
,
{
label: window.i18n('destination'),
@ -126,16 +127,18 @@ export const ModalStatusLight = (props: StatusLightType) => {
);
};
/**
* A status light specifically for the action panel. Color is based on aggregate node states instead of individual onion node state
*/
export const ActionPanelOnionStatusLight = (props: { isSelected: boolean, handleClick: () => void }) => {
export const ActionPanelOnionStatusLight = (props: {
isSelected: boolean;
handleClick: () => void;
}) => {
const { isSelected, handleClick } = props;
let iconColor;
const theme = useTheme();
const firstOnionPath = useSelector((state: StateType) => state.onionPaths.snodePath.path);
const firstOnionPath = useSelector((state: StateType) => state.onionPaths.snodePath);
const hasOnionPath = firstOnionPath.length > 2;
// Set icon color based on result
@ -143,7 +146,6 @@ export const ActionPanelOnionStatusLight = (props: { isSelected: boolean, handle
const green = theme.colors.accent;
const orange = theme.colors.warning;
iconColor = hasOnionPath ? theme.colors.accent : theme.colors.destructive;
const onionState = useSelector((state: StateType) => state.onionPaths);
@ -153,21 +155,23 @@ export const ActionPanelOnionStatusLight = (props: { isSelected: boolean, handle
iconColor = red;
} else {
const onionSnodePath = onionState.snodePath;
if (onionState && onionSnodePath && onionSnodePath.path.length > 0) {
let onionNodeCount = onionSnodePath.path.length;
if (onionState && onionSnodePath && onionSnodePath.length > 0) {
let onionNodeCount = onionSnodePath.length;
iconColor = onionNodeCount > 2 ? green : onionNodeCount > 1 ? orange : red;
}
}
return <SessionIconButton
iconSize={SessionIconSize.Medium}
iconType={SessionIconType.Circle}
iconColor={iconColor}
onClick={handleClick}
isSelected={isSelected}
theme={theme}
/>
}
return (
<SessionIconButton
iconSize={SessionIconSize.Medium}
iconType={SessionIconType.Circle}
iconColor={iconColor}
onClick={handleClick}
isSelected={isSelected}
theme={theme}
/>
);
};
export const OnionPathModal = (props: OnionPathModalType) => {
const onConfirm = () => {

@ -79,13 +79,6 @@ const Section = (props: { setModal?: any; type: SectionType; avatarPath?: string
const handleClick = () => {
/* tslint:disable:no-void-expression */
if (type === SectionType.Profile) {
// window.showEditProfileDialog();
// window.inboxStore?.dispatch(updateConfirmModal({ title: "title test" }));
// dispatch(updateConfirmModal({ title: "title test" }));
// setModal(<EditProfileDialog2 onClose={() => setModal(null)}></EditProfileDialog2>);
setModal(<EditProfileDialog onClose={handleModalClose} theme={theme} />);
} else if (type === SectionType.Moon) {
const themeFromSettings = window.Events.getThemeSetting();

@ -39,7 +39,7 @@ const SessionNicknameInner = (props: Props) => {
*/
const saveNickname = async () => {
if (!conversationId) {
throw "Cant save withou conversation id"
throw 'Cant save withou conversation id';
// return;
}
const conversation = ConversationController.getInstance().get(conversationId);
@ -60,36 +60,33 @@ const SessionNicknameInner = (props: Props) => {
// >
// TODO: Implement showHeader option for modal
<SessionWrapperModal
title={window.i18n('changeNickname')}
onClose={onClickClose}
showExitIcon={false}
// showHeader={true}
theme={theme}
>
<div className="session-modal__centered">
<span className="subtle">{window.i18n('changeNicknameMessage')}</span>
<div className="spacer-lg" />
</div>
<input
autoFocus
type="nickname"
id="nickname-modal-input"
placeholder={window.i18n('nicknamePlaceholder')}
onKeyUp={e => {
void onNicknameInput(_.cloneDeep(e));
}}
/>
<div className="session-modal__button-group">
<SessionButton text={window.i18n('ok')} onClick={saveNickname} />
<SessionButton text={window.i18n('cancel')} onClick={onClickClose} />
</div>
</SessionWrapperModal>
<SessionWrapperModal
title={window.i18n('changeNickname')}
onClose={onClickClose}
showExitIcon={false}
showHeader={true}
theme={theme}
>
<div className="session-modal__centered">
<span className="subtle">{window.i18n('changeNicknameMessage')}</span>
<div className="spacer-lg" />
</div>
<input
autoFocus={true}
type="nickname"
id="nickname-modal-input"
placeholder={window.i18n('nicknamePlaceholder')}
onKeyUp={e => {
void onNicknameInput(_.cloneDeep(e));
}}
/>
<div className="session-modal__button-group">
<SessionButton text={window.i18n('ok')} onClick={saveNickname} />
<SessionButton text={window.i18n('cancel')} onClick={onClickClose} />
</div>
</SessionWrapperModal>
);
};

@ -63,19 +63,11 @@ class SessionSeedModalInner extends React.Component<Props, State> {
return (
<>
{!loading && (
// <SessionModal
// title={i18n('showRecoveryPhrase')}
// onClose={onClose}
// theme={this.props.theme}
// >
<SessionWrapperModal
<SessionWrapperModal
title={i18n('showRecoveryPhrase')}
onClose={onClose}
theme={this.props.theme}
>
>
<div className="spacer-sm" />
{hasPassword && !passwordValid ? (
@ -83,8 +75,7 @@ class SessionSeedModalInner extends React.Component<Props, State> {
) : (
<>{this.renderSeedView()}</>
)}
</SessionWrapperModal>
// </SessionModal>
</SessionWrapperModal>
)}
</>
);

@ -714,8 +714,6 @@ export class SessionConversation extends React.Component<Props, State> {
/>
),
});
// warrick: delete old code
},
onInviteContacts: () => {
this.setState({

@ -1,317 +1,3 @@
import * as OnionPaths from './onionPath';
<<<<<<< HEAD
export { OnionPaths };
=======
import { updateOnionPaths } from '../../state/ducks/onion';
export type Snode = SnodePool.Snode;
const desiredGuardCount = 3;
const minimumGuardCount = 2;
export interface SnodePath {
path: Array<Snode>;
bad: boolean;
}
export class OnionPaths {
private static instance: OnionPaths | null;
private static readonly onionRequestHops = 3;
private onionPaths: Array<SnodePath> = [];
// This array is meant to store nodes will full info,
// so using GuardNode would not be correct (there is
// some naming issue here it seems)
private guardNodes: Array<Snode> = [];
private onionRequestCounter = 0; // Request index for debugging
private constructor() {}
public static getInstance() {
if (OnionPaths.instance) {
return OnionPaths.instance;
}
OnionPaths.instance = new OnionPaths();
return OnionPaths.instance;
}
public async buildNewOnionPaths() {
// this function may be called concurrently make sure we only have one inflight
return allowOnlyOneAtATime('buildNewOnionPaths', async () => {
await this.buildNewOnionPathsWorker();
});
}
public async getOnionPath(toExclude?: { pubkey_ed25519: string }): Promise<Array<Snode>> {
const { log, CONSTANTS } = window;
let goodPaths = this.onionPaths.filter(x => !x.bad);
let attemptNumber = 0;
while (goodPaths.length < minimumGuardCount) {
log.error(
`Must have at least 2 good onion paths, actual: ${goodPaths.length}, attempt #${attemptNumber} fetching more...`
);
// eslint-disable-next-line no-await-in-loop
await this.buildNewOnionPaths();
// should we add a delay? buildNewOnionPaths should act as one
// reload goodPaths now
attemptNumber += 1;
goodPaths = this.onionPaths.filter(x => !x.bad);
}
if (goodPaths.length <= 0) {
window.inboxStore?.dispatch(updateOnionPaths({ path: new Array<Snode>(), bad: true }));
} else {
window.inboxStore?.dispatch(updateOnionPaths(goodPaths[0]));
}
const paths = _.shuffle(goodPaths);
if (!toExclude) {
if (!paths[0]) {
log.error('LokiSnodeAPI::getOnionPath - no path in', paths);
return [];
}
if (!paths[0].path) {
log.error('LokiSnodeAPI::getOnionPath - no path in', paths[0]);
}
return paths[0].path;
}
// Select a path that doesn't contain `toExclude`
const otherPaths = paths.filter(
path => !_.some(path.path, node => node.pubkey_ed25519 === toExclude.pubkey_ed25519)
);
if (otherPaths.length === 0) {
// This should never happen!
// well it did happen, should we
// await this.buildNewOnionPaths();
// and restart call?
log.error(
'LokiSnodeAPI::getOnionPath - no paths without',
toExclude.pubkey_ed25519,
'path count',
paths.length,
'goodPath count',
goodPaths.length,
'paths',
paths
);
throw new Error('No onion paths available after filtering');
}
if (!otherPaths[0].path) {
log.error('LokiSnodeAPI::getOnionPath - otherPaths no path in', otherPaths[0]);
}
return otherPaths[0].path;
}
public hasOnionPath(): boolean {
// returns true if there exists a valid onion path
return this.onionPaths.length !== 0 && this.onionPaths[0].path.length !== 0;
}
public getOnionPathNoRebuild() {
return this.onionPaths ? this.onionPaths[0].path : [];
}
public markPathAsBad(path: Array<Snode>) {
// TODO: we might want to remove the nodes from the
// node pool (but we don't know which node on the path
// is causing issues)
this.onionPaths.forEach(p => {
if (_.isEqual(p.path, path)) {
// eslint-disable-next-line no-param-reassign
p.bad = true;
}
});
}
public assignOnionRequestNumber() {
this.onionRequestCounter += 1;
return this.onionRequestCounter;
}
private async testGuardNode(snode: Snode) {
const { log } = window;
log.info('Testing a candidate guard node ', snode);
// Send a post request and make sure it is OK
const endpoint = '/storage_rpc/v1';
const url = `https://${snode.ip}:${snode.port}${endpoint}`;
const ourPK = UserUtils.getOurPubKeyStrFromCache();
const pubKey = window.getStoragePubKey(ourPK); // truncate if testnet
const method = 'get_snodes_for_pubkey';
const params = { pubKey };
const body = {
jsonrpc: '2.0',
id: '0',
method,
params,
};
const fetchOptions = {
method: 'POST',
body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json' },
timeout: 10000, // 10s, we want a smaller timeout for testing
agent: snodeHttpsAgent,
};
let response;
try {
// Log this line for testing
// curl -k -X POST -H 'Content-Type: application/json' -d '"+fetchOptions.body.replace(/"/g, "\\'")+"'", url
window.log.info('insecureNodeFetch => plaintext for testGuardNode');
response = await insecureNodeFetch(url, fetchOptions);
} catch (e) {
if (e.type === 'request-timeout') {
log.warn('test timeout for node,', snode);
}
return false;
}
if (!response.ok) {
const tg = await response.text();
log.info('Node failed the guard test:', snode);
}
return response.ok;
}
private async selectGuardNodes(): Promise<Array<Snode>> {
const { log } = window;
// `getRandomSnodePool` is expected to refresh itself on low nodes
const nodePool = await SnodePool.getRandomSnodePool();
if (nodePool.length < desiredGuardCount) {
log.error('Could not select guard nodes. Not enough nodes in the pool: ', nodePool.length);
return [];
}
const shuffled = _.shuffle(nodePool);
let guardNodes: Array<Snode> = [];
console.log('@@@@ guardNodes: ', guardNodes);
// The use of await inside while is intentional:
// we only want to repeat if the await fails
// eslint-disable-next-line-no-await-in-loop
while (guardNodes.length < 3) {
if (shuffled.length < desiredGuardCount) {
log.error('Not enought nodes in the pool');
break;
}
const candidateNodes = shuffled.splice(0, desiredGuardCount);
// Test all three nodes at once
// eslint-disable-next-line no-await-in-loop
const idxOk = await Promise.all(candidateNodes.map(n => this.testGuardNode(n)));
const goodNodes = _.zip(idxOk, candidateNodes)
.filter(x => x[0])
.map(x => x[1]) as Array<Snode>;
guardNodes = _.concat(guardNodes, goodNodes);
}
if (guardNodes.length < desiredGuardCount) {
log.error(`COULD NOT get enough guard nodes, only have: ${guardNodes.length}`);
}
log.info('new guard nodes: ', guardNodes);
const edKeys = guardNodes.map(n => n.pubkey_ed25519);
await updateGuardNodes(edKeys);
return guardNodes;
}
private async buildNewOnionPathsWorker() {
const { log } = window;
log.info('LokiSnodeAPI::buildNewOnionPaths - building new onion paths');
const allNodes = await SnodePool.getRandomSnodePool();
if (this.guardNodes.length === 0) {
// Not cached, load from DB
const nodes = await getGuardNodes();
if (nodes.length === 0) {
log.warn(
'LokiSnodeAPI::buildNewOnionPaths - no guard nodes in DB. Will be selecting new guards nodes...'
);
} else {
// We only store the nodes' keys, need to find full entries:
const edKeys = nodes.map(x => x.ed25519PubKey);
this.guardNodes = allNodes.filter(x => edKeys.indexOf(x.pubkey_ed25519) !== -1);
if (this.guardNodes.length < edKeys.length) {
log.warn(
`LokiSnodeAPI::buildNewOnionPaths - could not find some guard nodes: ${this.guardNodes.length}/${edKeys.length} left`
);
}
}
// If guard nodes is still empty (the old nodes are now invalid), select new ones:
if (this.guardNodes.length < minimumGuardCount) {
// TODO: don't throw away potentially good guard nodes
this.guardNodes = await this.selectGuardNodes();
}
}
// TODO: select one guard node and 2 other nodes randomly
let otherNodes = _.difference(allNodes, this.guardNodes);
if (otherNodes.length < 2) {
log.warn(
'LokiSnodeAPI::buildNewOnionPaths - Too few nodes to build an onion path! Refreshing pool and retrying'
);
await SnodePool.refreshRandomPool();
await this.buildNewOnionPaths();
return;
}
otherNodes = _.shuffle(otherNodes);
const guards = _.shuffle(this.guardNodes);
// Create path for every guard node:
const nodesNeededPerPaths = OnionPaths.onionRequestHops - 1;
// Each path needs X (nodesNeededPerPaths) nodes in addition to the guard node:
const maxPath = Math.floor(
Math.min(
guards.length,
nodesNeededPerPaths ? otherNodes.length / nodesNeededPerPaths : otherNodes.length
)
);
// TODO: might want to keep some of the existing paths
this.onionPaths = [];
for (let i = 0; i < maxPath; i += 1) {
const path = [guards[i]];
for (let j = 0; j < nodesNeededPerPaths; j += 1) {
path.push(otherNodes[i * nodesNeededPerPaths + j]);
}
this.onionPaths.push({ path, bad: false });
}
log.info(`Built ${this.onionPaths.length} onion paths`);
}
}
>>>>>>> w/onion-paths

@ -10,11 +10,10 @@ import pRetry from 'p-retry';
const desiredGuardCount = 3;
const minimumGuardCount = 2;
export type SnodePath = Array<Snode>;
import { updateOnionPaths } from '../../state/ducks/onion';
const onionRequestHops = 3;
let onionPaths: Array<SnodePath> = [];
let onionPaths: Array<Array<Snode>> = [];
/**
* Used for testing only
@ -135,9 +134,9 @@ export async function getOnionPath(toExclude?: Snode): Promise<Array<Snode>> {
}
if (onionPaths.length <= 0) {
window.inboxStore?.dispatch(updateOnionPaths({ path: new Array<Snode>(), bad: true }));
window.inboxStore?.dispatch(updateOnionPaths([]));
} else {
window.inboxStore?.dispatch(updateOnionPaths(goodPaths[0]));
window.inboxStore?.dispatch(updateOnionPaths(onionPaths[0]));
}
const onionPathsWithoutExcluded = toExclude

@ -1,16 +1,12 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { SnodePath, Snode } from '../../session/onions/index';
import { Snode } from '../../data/data';
export type OnionState = {
snodePath: SnodePath;
snodePath: Array<Snode>;
};
const initialState = {
snodePath: {
path: new Array<Snode>(),
bad: false,
},
snodePath: new Array<Snode>(),
};
/**
@ -20,11 +16,8 @@ const onionSlice = createSlice({
name: 'onionPaths',
initialState,
reducers: {
updateOnionPaths(state, action: PayloadAction<SnodePath>) {
let newPayload = { snodePath: action.payload };
let isEqual = JSON.stringify(state, null, 2) == JSON.stringify(newPayload, null, 2);
return isEqual ? state : newPayload;
updateOnionPaths(state: OnionState, action: PayloadAction<Array<Snode>>) {
return { snodePath: action.payload };
},
},
});

@ -17,7 +17,7 @@ import {
} from '../../../../session/snode_api/onions';
import AbortController from 'abort-controller';
import * as Data from '../../../../../ts/data/data';
import { pathFailureCount, SnodePath } from '../../../../session/onions/onionPath';
import { pathFailureCount } from '../../../../session/onions/onionPath';
chai.use(chaiAsPromised as any);
chai.should();
@ -62,7 +62,7 @@ describe('OnionPathsErrors', () => {
associatedWith: string,
fakeSwarmForAssociatedWith: Array<string>;
let oldOnionPaths: Array<SnodePath>;
let oldOnionPaths: Array<Array<Data.Snode>>;
const fakeIP = '8.8.8.8';
let fakePortCurrent = 20000;

@ -10,6 +10,7 @@ import * as SNodeAPI from '../../../../session/snode_api';
import chaiAsPromised from 'chai-as-promised';
import * as OnionPaths from '../../../../session/onions/onionPath';
import { Snode } from '../../../../data/data';
chai.use(chaiAsPromised as any);
chai.should();
@ -120,7 +121,7 @@ const fakeGuardNodes = fakeSnodePool.filter(m => fakeGuardNodesEd25519.includes(
describe('OnionPaths', () => {
// Initialize new stubbed cache
const sandbox = sinon.createSandbox();
let oldOnionPaths: Array<OnionPaths.SnodePath>;
let oldOnionPaths: Array<Array<Snode>>;
beforeEach(async () => {
// Utils Stubs

@ -1,5 +1,4 @@
import * as crypto from 'crypto';
import { LocalizerType } from '../types/Util';
const ERRORS = {
TYPE: 'Password must be a string',

Loading…
Cancel
Save