Merge branch 'clearnet' into tls-fix

pull/759/head
Ryan Tharp 5 years ago committed by GitHub
commit 615a41fa21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -63,7 +63,7 @@ yarn install --frozen-lockfile # Install and build dependencies (this will take
yarn grunt # Generate final JS and CSS assets
yarn icon-gen # Generate full set of icons for Electron
yarn test # A good idea to make sure tests run first
yarn start # Start Session Messenger!
yarn start # Start Session!
```
You'll need to restart the application regularly to see your changes, as there
@ -84,7 +84,7 @@ yarn grunt dev # runs until you stop it, re-generating built assets on file chan
## Additional storage profiles
Since there is no registration for Session Messenger, you can create as many accounts as you
Since there is no registration for Session, you can create as many accounts as you
can public keys. To test the P2P functionality on the same machine, however, requries
that each client binds their message server to a different port.

@ -1,8 +1,8 @@
# Session Messenger
# Session
[![Build Status](https://travis-ci.org/loki-project/loki-messenger.svg?branch=development)](https://travis-ci.org/loki-project/loki-messenger)
Session Messenger allows for truly decentralized, end to end, and private encrypted chats. Session Messenger is built to handle both online and fully Asynchronous offline messages. Loki messenger implements the Signal protocol for message encryption. Our Client interface is a fork of [Signal Messenger](https://signal.org/). All communication that passes through Loki messenger is routed through [Lokinet](https://github.com/loki-project/loki-network).
Session allows for truly decentralized, end to end, and private encrypted chats. Session is built to handle both online and fully Asynchronous offline messages. Loki messenger implements the Signal protocol for message encryption. Our Client interface is a fork of [Signal Messenger](https://signal.org/). All communication that passes through Loki messenger is routed through [Lokinet](https://github.com/loki-project/loki-network).
## Summary
@ -14,11 +14,11 @@ If Alice and Bob are both online they can simply resolve each others public keys
**Offline messages**
Offline messaging uses Swarms, given any users public key the user can resolve a public key to a specific grouping of Service Nodes (AKA Swarm) each user in Session Messenger belongs to a Swarm. When routing a message offline the user selects a Service node in the destination users Swarm, when the user comes online they query any node in their Swarm, if the Swarm is holding any messages for the user they disseminate those messages to the user.
Offline messaging uses Swarms, given any users public key the user can resolve a public key to a specific grouping of Service Nodes (AKA Swarm) each user in Session belongs to a Swarm. When routing a message offline the user selects a Service node in the destination users Swarm, when the user comes online they query any node in their Swarm, if the Swarm is holding any messages for the user they disseminate those messages to the user.
![Swarm Messaging](https://i.imgur.com/o13Knds.png)
Spam protections for Session Messenger are based on a Proof of Work which is attached to any message that exceeds a default size or Time To Live, this process is discussed further in the [Loki whitepaper](https://loki.network/whitepaper).
Spam protections for Session are based on a Proof of Work which is attached to any message that exceeds a default size or Time To Live, this process is discussed further in the [Loki whitepaper](https://loki.network/whitepaper).
## Want to Contribute? Found a Bug or Have a feature request?

@ -52,7 +52,7 @@
"description": "Application menu command to show all application windows"
},
"appMenuQuit": {
"message": "Quit Session Messenger",
"message": "Quit Session",
"description": "Application menu command to close the application"
},
"editMenuUndo": {

@ -52,7 +52,7 @@
"description": "Application menu command to show all application windows"
},
"appMenuQuit": {
"message": "Quit Session Messenger",
"message": "Quit Session",
"description": "Application menu command to close the application"
},
"editMenuUndo": {

@ -52,7 +52,7 @@
"description": "Application menu command to show all application windows"
},
"appMenuQuit": {
"message": "Opustit Session Messenger",
"message": "Opustit Session",
"description": "Application menu command to close the application"
},
"editMenuUndo": {

@ -66,7 +66,7 @@
"description": "Application menu command to show all application windows"
},
"appMenuQuit": {
"message": "Quit Session Messenger",
"message": "Quit Session",
"description": "Application menu command to close the application"
},
"editMenuUndo": {
@ -265,7 +265,7 @@
"Header shown on the screen at the end of a successful import process"
},
"importCompleteStartButton": {
"message": "Start using Session Messenger",
"message": "Start using Session",
"description":
"Button shown at end of successful import process, nothing left but a restart"
},
@ -295,6 +295,9 @@
}
}
},
"capsLockOn": {
"message": "Caps lock is on."
},
"me": {
"message": "Me",
"description": "The label for yourself when shown in a group member list"
@ -719,12 +722,12 @@
"Item under the Help menu, takes you to GitHub new issue form (title case)"
},
"signalDesktopPreferences": {
"message": "Session Messenger Preferences",
"message": "Session Preferences",
"description":
"Title of the window that pops up with Signal Desktop preferences in it"
},
"aboutSignalDesktop": {
"message": "About Session Messenger",
"message": "About Session",
"description": "Item under the Help menu, which opens a small about window"
},
"speech": {
@ -745,7 +748,7 @@
"description": "Command in the tray icon menu, to quit the application"
},
"lokiMessenger": {
"message": "Session Messenger"
"message": "Session"
},
"search": {
"message": "Search",
@ -774,7 +777,7 @@
"description": "Shown to separate the types of search results"
},
"messagesHeader": {
"message": "Sessions",
"message": "Session",
"description": "Shown to separate the types of search results"
},
"settingsHeader": {
@ -895,7 +898,7 @@
},
"audioPermissionNeeded": {
"message":
"To send audio messages, allow Session Messenger to access your microphone.",
"To send audio messages, allow Session to access your microphone.",
"description":
"Shown if the user attempts to send an audio message without audio permssions turned on"
},
@ -1093,7 +1096,7 @@
"Placeholder text in the message entry field when it is disabled while we are waiting for a friend request approval"
},
"sendMessageFriendRequest": {
"message": "Hi there! This is <insert name here> !",
"message": "Hi there! This is ...!",
"description":
"Placeholder text in the message entry field when it is the first message sent to that contact"
},
@ -1148,6 +1151,10 @@
"description":
"Shown on the drop-down menu for an individual message, deletes single message"
},
"messages": {
"message": "Messages",
"description": "Message search result"
},
"deleteMessages": {
"message": "Delete Messages",
"description": "Menu item for deleting messages, title case."
@ -1169,7 +1176,7 @@
"Confirmation dialog title that asks the user if they really wish to delete a public channel. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone."
},
"deletePublicChannelConfirmation": {
"message": "Leave this public channel?",
"message": "Leave this Open Group?",
"description":
"Confirmation dialog text that tells the user what will happen if they leave the public channel."
},
@ -1212,7 +1219,7 @@
"This is a past tense, informational message. In other words, your secure session has been reset."
},
"betaDisclaimerTitle": {
"message": "Thanks for using Session Messenger!",
"message": "Thanks for using Session!",
"description": "Title for beta disclaimer modal"
},
"betaDisclaimerSubtitle": {
@ -1273,7 +1280,7 @@
}
},
"installWelcome": {
"message": "Welcome to Session Messenger",
"message": "Welcome to Session",
"description": "Welcome title on the install page"
},
"installTagline": {
@ -1282,12 +1289,12 @@
"Tagline displayed under 'installWelcome' string on the install page"
},
"linkYourPhone": {
"message": "Link your phone to Session Messenger",
"message": "Link your phone to Session",
"description":
"Shown on the front page when the application first starst, above the QR code"
},
"signalSettings": {
"message": "Session Messenger Settings",
"message": "Session Settings",
"description":
"Used in the guidance to help people find the 'link new device' area of their Signal mobile app"
},
@ -1507,7 +1514,7 @@
},
"expiredWarning": {
"message":
"This version of Session Messenger has expired. Please upgrade to the latest version to continue messaging.",
"This version of Session has expired. Please upgrade to the latest version to continue messaging.",
"description":
"Warning notification that this version of the app has expired"
},

@ -36,7 +36,7 @@
"description": "Application menu command to show all application windows"
},
"appMenuQuit": {
"message": "Quit Session Messenger",
"message": "Quit Session",
"description": "Application menu command to close the application"
},
"editMenuUndo": {

@ -52,7 +52,7 @@
"description": "Application menu command to show all application windows"
},
"appMenuQuit": {
"message": "Quit Session Messenger",
"message": "Quit Session",
"description": "Application menu command to close the application"
},
"editMenuUndo": {

@ -52,7 +52,7 @@
"description": "Application menu command to show all application windows"
},
"appMenuQuit": {
"message": "Quit Session Messenger",
"message": "Quit Session",
"description": "Application menu command to close the application"
},
"editMenuUndo": {

@ -52,7 +52,7 @@
"description": "Application menu command to show all application windows"
},
"appMenuQuit": {
"message": "Quit Session Messenger",
"message": "Quit Session",
"description": "Application menu command to close the application"
},
"editMenuUndo": {

@ -52,7 +52,7 @@
"description": "Application menu command to show all application windows"
},
"appMenuQuit": {
"message": "Quit Session Messenger",
"message": "Quit Session",
"description": "Application menu command to close the application"
},
"editMenuUndo": {

@ -52,7 +52,7 @@
"description": "Application menu command to show all application windows"
},
"appMenuQuit": {
"message": "Quit Session Messenger",
"message": "Quit Session",
"description": "Application menu command to close the application"
},
"editMenuUndo": {

@ -52,7 +52,7 @@
"description": "Application menu command to show all application windows"
},
"appMenuQuit": {
"message": "Quit Session Messenger",
"message": "Quit Session",
"description": "Application menu command to close the application"
},
"editMenuUndo": {

@ -52,7 +52,7 @@
"description": "Application menu command to show all application windows"
},
"appMenuQuit": {
"message": "Quit Session Messenger",
"message": "Quit Session",
"description": "Application menu command to close the application"
},
"editMenuUndo": {

@ -52,7 +52,7 @@
"description": "Application menu command to show all application windows"
},
"appMenuQuit": {
"message": "Quit Session Messenger",
"message": "Quit Session",
"description": "Application menu command to close the application"
},
"editMenuUndo": {

@ -902,7 +902,7 @@ async function updateToLokiSchemaVersion1(currentVersion, instance) {
rssFeed: 'https://loki.network/feed/',
closable: true,
name: 'Loki.network News',
profileAvatar: 'images/loki/loki_icon.png',
profileAvatar: 'images/loki/session_icon.png',
};
const updatesRssFeedData = {
@ -911,7 +911,7 @@ async function updateToLokiSchemaVersion1(currentVersion, instance) {
rssFeed: 'https://loki.network/category/messenger-updates/feed/',
closable: false,
name: 'Messenger updates',
profileAvatar: 'images/loki/loki_icon.png',
profileAvatar: 'images/loki/session_icon.png',
};
const autoJoinLokiChats = false;

@ -19,7 +19,7 @@
script-src 'self';
style-src 'self' 'unsafe-inline';"
>
<title>Session Messenger</title>
<title>Session</title>
<link href='images/loki/loki_icon_128.png' rel='shortcut icon'>
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 725 B

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

@ -122,7 +122,7 @@
'x_white.svg',
'icon-paste.svg',
'loki/loki_icon_text.png',
'loki/loki_icon_128.png',
'loki/session_icon_128.png',
]);
// Set server-client time difference
@ -258,13 +258,6 @@
}
// are there limits on tracking, is this unneeded?
// window.mixpanel.track("Desktop boot");
window.lokiP2pAPI = new window.LokiP2pAPI(ourKey);
window.lokiP2pAPI.on('pingContact', pubKey => {
const isPing = true;
libloki.api.sendOnlineBroadcastMessage(pubKey, isPing);
});
window.lokiP2pAPI.on('online', ConversationController._handleOnline);
window.lokiP2pAPI.on('offline', ConversationController._handleOffline);
window.initialisedAPI = true;
if (storage.get('isSecondaryDevice')) {
@ -286,14 +279,6 @@
}
}
function startLocalLokiServer() {
if (window.localLokiServer) {
return;
}
const pems = window.getSelfSignedCert();
window.localLokiServer = new window.LocalLokiServer(pems);
}
// We need this 'first' check because we don't want to start the app up any other time
// than the first time. And storage.fetch() will cause onready() to fire.
let first = true;
@ -391,8 +376,6 @@
},
shutdown: async () => {
await window.localLokiServer.close();
// Stop background processing
window.Signal.AttachmentDownloads.stop();
if (idleDetector) {
@ -1262,15 +1245,6 @@
}
});
Whisper.events.on('p2pMessageSent', ({ pubKey, timestamp }) => {
try {
const conversation = ConversationController.get(pubKey);
conversation.onP2pMessageSent(pubKey, timestamp);
} catch (e) {
window.log.error('Error setting p2p on message');
}
});
Whisper.events.on(
'publicMessageSent',
({ pubKey, timestamp, serverId }) => {
@ -1418,7 +1392,6 @@
window.lokiFileServerAPI = await window.lokiFileServerAPIFactory.establishHomeConnection(
window.getDefaultFileServer()
);
window.localLokiServer = null;
window.lokiPublicChatAPI = null;
window.feeds = [];
messageReceiver = new textsecure.MessageReceiver(
@ -1436,8 +1409,6 @@
return;
}
// initialize the socket and start listening for messages
startLocalLokiServer();
await initAPIs();
await initSpecialConversations();
messageReceiver = new textsecure.MessageReceiver(
@ -2054,7 +2025,6 @@
unidentifiedDeliveryReceived: data.unidentifiedDeliveryReceived,
type: 'incoming',
unread: 1,
isP2p: data.isP2p,
isPublic: data.isPublic,
isRss: data.isRss,
};

@ -12,7 +12,6 @@
profileImages,
clipboard,
BlockedNumberController,
lokiP2pAPI,
lokiPublicChatAPI,
JobQueue
*/
@ -182,13 +181,6 @@
if (this.id === this.ourNumber) {
this.set({ friendRequestStatus: FriendRequestStatusEnum.friends });
} else if (typeof lokiP2pAPI !== 'undefined') {
// Online status handling, only for contacts that aren't us
this.set({ isOnline: lokiP2pAPI.isOnline(this.id) });
} else {
window.log.warn(
'lokiP2pAPI not initialised when spawning conversation!'
);
}
this.messageSendQueue = new JobQueue();
@ -484,11 +476,6 @@
await Promise.all(messages.map(m => m.setCalculatingPoW()));
},
async onP2pMessageSent(pubKey, timestamp) {
const messages = this._getMessagesWithTimestamp(pubKey, timestamp);
await Promise.all(messages.map(m => m.setIsP2p(true)));
},
async onPublicMessageSent(pubKey, timestamp, serverId) {
const messages = this._getMessagesWithTimestamp(pubKey, timestamp);
await Promise.all(

@ -694,7 +694,6 @@
expirationTimestamp,
selected: this.selected,
multiSelectMode: conversation && conversation.selectedMessages.size > 0,
isP2p: !!this.get('isP2p'),
isPublic: !!this.get('isPublic'),
isRss: !!this.get('isRss'),
senderIsModerator:
@ -1360,19 +1359,6 @@
Message: Whisper.Message,
});
},
async setIsP2p(isP2p) {
if (_.isEqual(this.get('isP2p'), isP2p)) {
return;
}
this.set({
isP2p: !!isP2p,
});
await window.Signal.Data.saveMessage(this.attributes, {
Message: Whisper.Message,
});
},
getServerId() {
return this.get('serverId');
},

@ -6,7 +6,7 @@ const got = require('got');
const BASE_URL = 'https://debuglogs.org';
const VERSION = window.getVersion();
const USER_AGENT = `Session Messenger ${VERSION}`;
const USER_AGENT = `Session ${VERSION}`;
// Workaround: Submitting `FormData` using native `FormData::submit` procedure
// as integration with `got` results in S3 error saying we havent set the

@ -1,6 +1,6 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable no-loop-func */
/* global log, dcodeIO, window, callWorker, lokiP2pAPI, lokiSnodeAPI, textsecure */
/* global log, dcodeIO, window, callWorker, lokiSnodeAPI, textsecure */
const _ = require('lodash');
const { lokiRpc } = require('./loki_rpc');
@ -38,38 +38,6 @@ const calcNonce = (messageEventData, pubKey, data64, timestamp, ttl) => {
return callWorker('calcPoW', timestamp, ttl, pubKey, data64, difficulty);
};
const trySendP2p = async (pubKey, data64, isPing, messageEventData) => {
if (typeof lokiP2pAPI === 'undefined') {
return false;
}
const p2pDetails = lokiP2pAPI.getContactP2pDetails(pubKey);
if (!p2pDetails || (!isPing && !p2pDetails.isOnline)) {
return false;
}
try {
await lokiRpc(p2pDetails.address, p2pDetails.port, 'store', {
data: data64,
});
lokiP2pAPI.setContactOnline(pubKey);
window.Whisper.events.trigger('p2pMessageSent', messageEventData);
if (isPing) {
log.info(`Successfully pinged ${pubKey}`);
} else {
log.info(`Successful p2p message to ${pubKey}`);
}
return true;
} catch (e) {
lokiP2pAPI.setContactOffline(pubKey);
if (isPing) {
// If this was just a ping, we don't bother sending to storage server
log.warn('Ping failed, contact marked offline', e);
return true;
}
log.warn('Failed to send P2P message, falling back to storage', e);
return false;
}
};
class LokiMessageAPI {
constructor(ourKey) {
this.jobQueue = new window.JobQueue();
@ -79,7 +47,6 @@ class LokiMessageAPI {
async sendMessage(pubKey, data, messageTimeStamp, ttl, options = {}) {
const {
isPing = false,
isPublic = false,
numConnections = DEFAULT_CONNECTIONS,
publicSendData = null,
@ -108,15 +75,6 @@ class LokiMessageAPI {
}
const data64 = dcodeIO.ByteBuffer.wrap(data).toString('base64');
const p2pSuccess = await trySendP2p(
pubKey,
data64,
isPing,
messageEventData
);
if (p2pSuccess) {
return;
}
const timestamp = Date.now();
const nonce = await calcNonce(

@ -1,122 +0,0 @@
/* global setTimeout, clearTimeout */
const EventEmitter = require('events');
const { isEmpty } = require('lodash');
const offlinePingTime = 2 * 60 * 1000; // 2 minutes
class LokiP2pAPI extends EventEmitter {
constructor(ourKey) {
super();
this.contactP2pDetails = {};
this.ourKey = ourKey;
}
reset() {
Object.keys(this.contactP2pDetails).forEach(key => {
clearTimeout(this.contactP2pDetails[key].pingTimer);
delete this.contactP2pDetails[key];
});
}
updateContactP2pDetails(pubKey, address, port, isP2PMessage = false) {
// Stagger the timers so the friends don't ping each other at the same time
const timerDuration =
pubKey < this.ourKey
? 60 * 1000 // 1 minute
: 2 * 60 * 1000; // 2 minutes
// Get the current contact details
// This will be empty if we don't have them
const baseDetails = { ...(this.contactP2pDetails[pubKey] || {}) };
// Always set the new contact details
this.contactP2pDetails[pubKey] = {
address,
port,
timerDuration,
pingTimer: null,
isOnline: false,
};
const contactExists = !isEmpty(baseDetails);
const { isOnline } = baseDetails;
const detailsChanged =
baseDetails.address !== address || baseDetails.port !== port;
// If we had the contact details
// And we got a P2P message
// And the contact was online
// And the new details that we got matched the old
// Then we don't need to bother pinging
if (contactExists && isP2PMessage && isOnline && !detailsChanged) {
// We also need to set the current contact details to show online
// because they get reset to `false` above
this.setContactOnline(pubKey);
return;
}
/*
Ping the contact.
This happens in the following scenarios:
1. We didn't have the contact, we need to ping them to let them know our details.
2. isP2PMessage = false, so we assume the contact doesn't have our details.
3. We had the contact marked as offline,
we need to make sure that we can reach their server.
4. The other contact details have changed,
we need to make sure that we can reach their new server.
*/
this.pingContact(pubKey);
}
getContactP2pDetails(pubKey) {
if (!this.contactP2pDetails[pubKey]) {
return null;
}
return { ...this.contactP2pDetails[pubKey] };
}
setContactOffline(pubKey) {
this.emit('offline', pubKey);
if (!this.contactP2pDetails[pubKey]) {
return;
}
clearTimeout(this.contactP2pDetails[pubKey].pingTimer);
this.contactP2pDetails[pubKey].pingTimer = setTimeout(
this.pingContact.bind(this),
offlinePingTime,
pubKey
);
this.contactP2pDetails[pubKey].isOnline = false;
}
setContactOnline(pubKey) {
if (!this.contactP2pDetails[pubKey]) {
return;
}
this.emit('online', pubKey);
clearTimeout(this.contactP2pDetails[pubKey].pingTimer);
this.contactP2pDetails[pubKey].isOnline = true;
this.contactP2pDetails[pubKey].pingTimer = setTimeout(
this.pingContact.bind(this),
this.contactP2pDetails[pubKey].timerDuration,
pubKey
);
}
isOnline(pubKey) {
return !!(
this.contactP2pDetails[pubKey] && this.contactP2pDetails[pubKey].isOnline
);
}
pingContact(pubKey) {
if (!this.contactP2pDetails[pubKey]) {
// Don't ping if we don't have their details
return;
}
this.emit('pingContact', pubKey);
}
}
module.exports = LokiP2pAPI;

@ -2,32 +2,7 @@
/* global window, ConversationController, _, log */
const is = require('@sindresorhus/is');
const dns = require('dns');
const process = require('process');
const { lokiRpc } = require('./loki_rpc');
const natUpnp = require('nat-upnp');
const resolve4 = url =>
new Promise((resolve, reject) => {
dns.resolve4(url, (err, ip) => {
if (err) {
reject(err);
} else {
resolve(ip);
}
});
});
const resolveCname = url =>
new Promise((resolve, reject) => {
dns.resolveCname(url, (err, address) => {
if (err) {
reject(err);
} else {
resolve(address[0]);
}
});
});
class LokiSnodeAPI {
constructor({ serverUrl, localUrl }) {
@ -38,40 +13,6 @@ class LokiSnodeAPI {
this.localUrl = localUrl;
this.randomSnodePool = [];
this.swarmsPendingReplenish = {};
// When we package lokinet with messenger we can ensure this ip is correct
if (process.platform === 'win32') {
dns.setServers(['127.0.0.1']);
}
}
async getMyClearIp() {
const upnpClient = natUpnp.createClient();
return new Promise((resolve, reject) => {
upnpClient.externalIp((err, ip) => {
if (err) {
reject(err);
} else {
resolve(ip);
}
});
});
}
async getMyLokiIp() {
try {
const address = await resolveCname(this.localUrl);
return resolve4(address);
} catch (e) {
throw new window.textsecure.LokiIpError(
'Failed to resolve localhost.loki',
e
);
}
}
getMyLokiAddress() {
/* resolve our local loki address */
return resolveCname(this.localUrl);
}
async getRandomSnodeAddress() {

@ -222,7 +222,7 @@ function _promiseAjax(providedUrl, options) {
method: options.type,
body: options.data || null,
headers: {
'User-Agent': 'Session Messenger',
'User-Agent': 'Session',
'X-Loki-Messenger-Agent': 'OWD',
...options.headers,
},

@ -108,7 +108,7 @@
const last = this.last().toJSON();
switch (userSetting) {
case SettingNames.COUNT:
title = 'Session Messenger';
title = 'Session';
if (last.isFriendRequest) {
message = `Friend request ${last.friendRequestType}`;

@ -350,6 +350,8 @@
props: getGroupSettingsProp(this.model),
});
this.$('.conversation-content-right').append(this.groupSettings.el);
} else {
this.groupSettings.update(getGroupSettingsProp(this.model));
}
this.$('.conversation-content-right').show();
};
@ -875,9 +877,7 @@
},
updateScrollDownButton(count) {
if (this.scrollDownButton) {
this.scrollDownButton.increment(count);
} else {
if (!this.scrollDownButton) {
this.scrollDownButton = new Whisper.ScrollDownButtonView({ count });
this.scrollDownButton.render();
const container = this.$('.discussion-container');
@ -1738,8 +1738,12 @@
if (event.key !== 'Escape') {
return;
}
// TODO: this view is not always in focus (e.g. after I've selected a message),
// so need to make Esc more robust
// Perhaps look into ConversationHeader.tsx and add an event listener in there.
// Up and down arrows should scroll
// Alt + up and down should swap between conversations / setting categories
this.model.resetMessageSelection();
this.closeEmojiPanel();
},

@ -7,22 +7,12 @@
window.Whisper = window.Whisper || {};
Whisper.ScrollDownButtonView = Whisper.View.extend({
initialize(options = {}) {
this.count = options.count || 0;
},
increment(count = 0) {
this.count += count;
this.render();
},
initialize() {},
render() {
this.scrollButtonView = new Whisper.ReactWrapperView({
className: 'module-scroll-down',
Component: window.Signal.Components.SessionScrollButton,
props: {
count: this.count,
},
});
this.$el.append(this.scrollButtonView.el);

@ -51,12 +51,12 @@
class TextScramble {
constructor(el) {
this.el = el;
this.chars = '0123456789qwertyuiopasdfghjklzxcvbnm';
this.chars = '0123456789abcdef';
this.update = this.update.bind(this);
}
setText(newText) {
const oldText = this.el.innerText;
const oldText = this.el.value;
const length = Math.max(oldText.length, newText.length);
// eslint-disable-next-line no-return-assign
const promise = new Promise(resolve => (this.resolve = resolve));
@ -97,14 +97,13 @@
char = this.randomChar();
this.queue[i].char = char;
}
output += `<span class="dud">${char}</span>`;
output += char;
} else {
output += from;
}
}
this.el.innerHTML = output;
this.el.value = output;
if (complete === this.queue.length) {
this.resolve();
@ -121,13 +120,9 @@
window.Session = window.Session || {};
window.Session.setNewSessionID = sessionID => {
const el = document.querySelector('.session-id-editable');
const el = document.querySelector('.session-id-editable-textarea');
const fx = new TextScramble(el);
el.innerHTML = sessionID;
el.value = sessionID;
fx.setText(sessionID);
};
window.Session.emptyContentEditableDivs = () => {
window.$('div[contenteditable]').html('');
};
})();

@ -1,4 +1,4 @@
/* global window, textsecure, log, Whisper, dcodeIO, StringView, ConversationController */
/* global window, textsecure, Whisper, dcodeIO, StringView, ConversationController */
// eslint-disable-next-line func-names
(function() {
@ -8,24 +8,6 @@
return sendOnlineBroadcastMessage(pubKey);
}
async function broadcastOnlineStatus() {
const friendKeys = await window.Signal.Data.getPubKeysWithFriendStatus(
window.friends.friendRequestStatusEnum.friends
);
await Promise.all(
friendKeys.map(async pubKey => {
if (pubKey === textsecure.storage.user.getNumber()) {
return;
}
try {
await sendOnlineBroadcastMessage(pubKey);
} catch (e) {
log.warn(`Failed to send online broadcast message to ${pubKey}`);
}
})
);
}
async function sendOnlineBroadcastMessage(pubKey, isPing = false) {
const authorisation = await window.libloki.storage.getGrantAuthorisationForSecondaryPubKey(
pubKey
@ -34,27 +16,10 @@
sendOnlineBroadcastMessage(authorisation.primaryDevicePubKey);
return;
}
let p2pAddress = null;
let p2pPort = null;
let type;
let myIp;
if (window.localLokiServer && window.localLokiServer.isListening()) {
try {
// clearnet change: getMyLokiAddress -> getMyClearIP
// const myLokiAddress = await window.lokiSnodeAPI.getMyLokiAddress();
myIp = await window.lokiSnodeAPI.getMyClearIp();
} catch (e) {
log.warn(`Failed to get clear IP for local server ${e}`);
}
}
if (myIp) {
p2pAddress = `https://${myIp}`;
p2pPort = window.localLokiServer.getPublicPort();
type = textsecure.protobuf.LokiAddressMessage.Type.HOST_REACHABLE;
} else {
type = textsecure.protobuf.LokiAddressMessage.Type.HOST_UNREACHABLE;
}
const p2pAddress = null;
const p2pPort = null;
// We result loki address message for sending "background" messages
const type = textsecure.protobuf.LokiAddressMessage.Type.HOST_UNREACHABLE;
const lokiAddressMessage = new textsecure.protobuf.LokiAddressMessage({
p2pAddress,
@ -252,7 +217,6 @@
window.libloki.api = {
sendBackgroundMessage,
sendOnlineBroadcastMessage,
broadcastOnlineStatus,
sendPairingAuthorisation,
createPairingAuthorisationProtoMessage,
sendUnpairingMessageToSecondary,

@ -1,219 +0,0 @@
/* global textsecure */
const https = require('https');
const EventEmitter = require('events');
const natUpnp = require('nat-upnp');
const STATUS = {
OK: 200,
BAD_REQUEST: 400,
NOT_FOUND: 404,
METHOD_NOT_ALLOWED: 405,
INTERNAL_SERVER_ERROR: 500,
};
class LocalLokiServer extends EventEmitter {
/**
* Creates an instance of LocalLokiServer.
* Sends out a `message` event when a new message is received.
*/
constructor(pems, options = {}) {
super();
const httpsOptions = {
key: pems.private,
cert: pems.cert,
};
if (!options.skipUpnp) {
this.upnpClient = natUpnp.createClient();
}
this.server = https.createServer(httpsOptions, (req, res) => {
let body = [];
const sendResponse = (statusCode, message = null) => {
const headers = message && {
'Content-Type': 'text/plain',
};
res.writeHead(statusCode, headers);
res.end(message);
};
if (req.method !== 'POST') {
sendResponse(STATUS.METHOD_NOT_ALLOWED);
return;
}
// Check endpoints
req
.on('error', () => {
// Internal server error
sendResponse(STATUS.INTERNAL_SERVER_ERROR);
})
.on('data', chunk => {
body.push(chunk);
})
.on('end', () => {
try {
body = Buffer.concat(body).toString();
} catch (e) {
// Internal server error: failed to convert body to string
sendResponse(STATUS.INTERNAL_SERVER_ERROR);
}
// Check endpoints here
if (req.url === '/storage_rpc/v1') {
try {
const bodyObject = JSON.parse(body);
if (bodyObject.method !== 'store') {
sendResponse(STATUS.NOT_FOUND, 'Invalid endpoint!');
return;
}
this.emit('message', {
message: bodyObject.params.data,
onSuccess: () => sendResponse(STATUS.OK),
onFailure: () => sendResponse(STATUS.NOT_FOUND),
});
} catch (e) {
// Bad Request: Failed to decode json
sendResponse(STATUS.BAD_REQUEST, 'Failed to decode JSON');
}
} else {
sendResponse(STATUS.NOT_FOUND, 'Invalid endpoint!');
}
});
});
}
async start(port, ip) {
// Close the old server
await this.close();
// Start a listening on new server
return new Promise((res, rej) => {
this.server.listen(port, ip, async err => {
if (err) {
rej(err);
} else if (this.upnpClient) {
try {
const publicPort = await this.punchHole();
res(publicPort);
} catch (e) {
if (e instanceof textsecure.HolePunchingError) {
await this.close();
}
rej(e);
}
} else {
res(port);
}
});
});
}
async punchHole() {
const privatePort = this.server.address().port;
const portStart = 22100;
const portEnd = 22200;
const ttl = 60 * 15; // renew upnp every 15 minutes
const publicPortsInUse = await new Promise((resolve, reject) => {
this.upnpClient.getMappings({ local: true }, (err, results) => {
if (err) {
// We assume an error here means upnp not enabled
reject(
new textsecure.HolePunchingError(
'Could not get mapping from upnp. Upnp not available?',
err
)
);
} else {
// remove the current private port from the current mapping
// to allow reusing that port.
resolve(
results
.filter(entry => entry.private.port !== privatePort)
.map(entry => entry.public.port)
);
}
});
});
for (let publicPort = portStart; publicPort <= portEnd; publicPort += 1) {
if (publicPortsInUse.includes(publicPort)) {
// eslint-disable-next-line no-continue
continue;
}
const p = new Promise((resolve, reject) => {
this.upnpClient.portMapping(
{
public: publicPort,
private: privatePort,
ttl,
},
err => {
if (err) {
reject(err);
} else {
resolve();
}
}
);
});
try {
// eslint-disable-next-line no-await-in-loop
await p;
this.publicPort = publicPort;
this.timerHandler = setTimeout(async () => {
try {
this.publicPort = await this.punchHole();
} catch (e) {
this.close();
}
}, ttl * 1000);
return publicPort;
} catch (e) {
throw new textsecure.HolePunchingError(
'Could not punch hole. Disabled upnp?',
e
);
}
}
const e = new Error();
throw new textsecure.HolePunchingError(
`Could not punch hole: no available port. Public ports: ${portStart}-${portEnd}`,
e
);
}
// Async wrapper for http server close
close() {
clearInterval(this.timerHandler);
if (this.upnpClient) {
this.upnpClient.portUnmapping({
public: this.publicPort,
});
this.publicPort = null;
}
if (this.server) {
return new Promise(res => {
this.server.close(() => res());
});
}
return Promise.resolve();
}
getPort() {
if (this.server.listening) {
return this.server.address().port;
}
return null;
}
getPublicPort() {
return this.publicPort;
}
isListening() {
return this.server.listening;
}
}
module.exports = LocalLokiServer;

@ -1,111 +0,0 @@
const axios = require('axios');
const { assert } = require('chai');
const LocalLokiServer = require('../../modules/local_loki_server');
const selfsigned = require('selfsigned');
const https = require('https');
class HolePunchingError extends Error {
constructor(message, err) {
super(message);
this.name = 'HolePunchingError';
this.error = err;
}
}
describe('LocalLokiServer', () => {
before(async () => {
const attrs = [{ name: 'commonName', value: 'mypubkey' }];
const pems = selfsigned.generate(attrs, { days: 365 * 10 });
global.textsecure = {};
global.textsecure.HolePunchingError = HolePunchingError;
this.server = new LocalLokiServer(pems, { skipUpnp: true });
await this.server.start(8000);
this.axiosClient = axios.create({
httpsAgent: new https.Agent({
rejectUnauthorized: false,
}),
});
});
after(async () => {
await this.server.close();
});
it('should return 405 if not a POST request', async () => {
try {
await this.axiosClient.get('https://localhost:8000');
assert.fail('Got a successful response');
} catch (error) {
if (error.response) {
assert.equal(405, error.response.status);
return;
}
assert.isNotOk(error, 'Another error was receieved');
}
});
it('should return 404 if no endpoint provided', async () => {
try {
await this.axiosClient.post('https://localhost:8000', { name: 'Test' });
assert.fail('Got a successful response');
} catch (error) {
if (error.response) {
assert.equal(404, error.response.status);
return;
}
assert.isNotOk(error, 'Another error was receieved');
}
});
it('should return 404 and a string if invalid enpoint is provided', async () => {
try {
await this.axiosClient.post('https://localhost:8000/invalid', {
name: 'Test',
});
assert.fail('Got a successful response');
} catch (error) {
if (error.response) {
assert.equal(404, error.response.status);
assert.equal('Invalid endpoint!', error.response.data);
return;
}
assert.isNotOk(error, 'Another error was receieved');
}
});
describe('/store', async () => {
it('should pass the POSTed data to the callback', async () => {
const attrs = [{ name: 'commonName', value: 'mypubkey' }];
const pems = selfsigned.generate(attrs, { days: 365 * 10 });
const server = new LocalLokiServer(pems, { skipUpnp: true });
await server.start(8001);
const messageData = {
method: 'store',
params: {
data: 'This is data',
},
};
const promise = new Promise(res => {
server.on('message', eventData => {
const { message, onSuccess } = eventData;
assert.equal(message, 'This is data');
onSuccess();
server.close();
res();
});
});
try {
await this.axiosClient.post(
'https://localhost:8001/storage_rpc/v1',
messageData
);
} catch (error) {
assert.isNotOk(error, 'Error occured');
}
return promise;
});
});
});

@ -1,193 +0,0 @@
const { assert } = require('chai');
const LokiP2pAPI = require('../../../js/modules/loki_p2p_api');
describe('LokiP2pAPI', () => {
const usedKey = 'aPubKey';
const usedAddress = 'anAddress';
const usedPort = 'aPort';
const usedDetails = {
address: usedAddress,
port: usedPort,
timerDuration: 100,
pingTimer: null,
isOnline: false,
};
beforeEach(() => {
this.lokiP2pAPI = new LokiP2pAPI();
});
afterEach(() => {
this.lokiP2pAPI.removeAllListeners();
this.lokiP2pAPI.reset();
});
describe('getContactP2pDetails', () => {
it('Should return null if no contact details exist', () => {
const details = this.lokiP2pAPI.getContactP2pDetails(usedKey);
assert.isNull(details);
});
it('Should return the exact same object if contact details exist', () => {
this.lokiP2pAPI.contactP2pDetails[usedKey] = usedDetails;
const details = this.lokiP2pAPI.getContactP2pDetails(usedKey);
assert.deepEqual(details, usedDetails);
});
});
describe('pingContact', () => {
it("Should not emit a pingContact event if that contact doesn't exits", () => {
this.lokiP2pAPI.on('pingContact', () => {
assert.fail();
});
this.lokiP2pAPI.pingContact('not stored');
});
});
describe('updateContactP2pDetails', () => {
it("Shouldn't ping a contact if contact exists, p2p message was sent, contact was online and details didn't change", () => {
this.lokiP2pAPI.on('pingContact', () => {
assert.fail();
});
// contact exists
const details = { ...usedDetails };
// P2p message
const isP2P = true;
// Contact was online
details.isOnline = true;
// details were the same
const { address, port } = details;
this.lokiP2pAPI.contactP2pDetails[usedKey] = details;
this.lokiP2pAPI.updateContactP2pDetails(usedKey, address, port, isP2P);
// They should also be marked as online
assert.isTrue(this.lokiP2pAPI.isOnline(usedKey));
});
it("Should ping a contact if we don't have details for it", done => {
this.lokiP2pAPI.on('pingContact', pubKey => {
assert.strictEqual(pubKey, usedKey);
assert.isFalse(this.lokiP2pAPI.isOnline(usedKey));
done();
});
this.lokiP2pAPI.updateContactP2pDetails(
usedKey,
usedAddress,
usedPort,
true
);
});
it("Should ping a contact if a P2P message wasn't received", done => {
// The precondition for this is that we had the contact stored
this.lokiP2pAPI.contactP2pDetails[usedKey] = { ...usedDetails };
this.lokiP2pAPI.on('pingContact', pubKey => {
assert.strictEqual(pubKey, usedKey);
assert.isFalse(this.lokiP2pAPI.isOnline(usedKey));
done();
});
this.lokiP2pAPI.updateContactP2pDetails(
usedKey,
usedAddress,
usedPort,
false // We didn't get a p2p message
);
});
it('Should ping a contact if they were marked as offline', done => {
// The precondition for this is that we had the contact stored
// And that p2p message was true
this.lokiP2pAPI.contactP2pDetails[usedKey] = { ...usedDetails };
this.lokiP2pAPI.on('pingContact', pubKey => {
assert.strictEqual(pubKey, usedKey);
assert.isFalse(this.lokiP2pAPI.isOnline(usedKey));
done();
});
this.lokiP2pAPI.updateContactP2pDetails(
usedKey,
usedAddress,
usedPort,
true // We got a p2p message
);
});
it('Should ping a contact if the address was different', done => {
// The precondition for this is that we had the contact stored
// And that p2p message was true
// And that the user was online
this.lokiP2pAPI.contactP2pDetails[usedKey] = { ...usedDetails };
this.lokiP2pAPI.contactP2pDetails[usedKey].isOnline = true;
this.lokiP2pAPI.on('pingContact', pubKey => {
assert.strictEqual(pubKey, usedKey);
done();
});
this.lokiP2pAPI.updateContactP2pDetails(
usedKey,
'different address',
usedPort,
true // We got a p2p message
);
});
it('Should ping a contact if the port was different', done => {
// The precondition for this is that we had the contact stored
// And that p2p message was true
// And that the user was online
this.lokiP2pAPI.contactP2pDetails[usedKey] = { ...usedDetails };
this.lokiP2pAPI.contactP2pDetails[usedKey].isOnline = true;
this.lokiP2pAPI.on('pingContact', pubKey => {
assert.strictEqual(pubKey, usedKey);
done();
});
this.lokiP2pAPI.updateContactP2pDetails(
usedKey,
usedAddress,
'different port',
true // We got a p2p message
);
});
it('Should emit an online event if the contact is online', done => {
this.lokiP2pAPI.on('online', pubKey => {
assert.strictEqual(pubKey, usedKey);
done();
});
this.lokiP2pAPI.contactP2pDetails[usedKey] = { ...usedDetails };
this.lokiP2pAPI.setContactOnline(usedKey);
}).timeout(1000);
it('Should store a contacts p2p details', () => {
this.lokiP2pAPI.updateContactP2pDetails(
usedKey,
usedAddress,
usedPort,
true
);
const p2pDetails = this.lokiP2pAPI.getContactP2pDetails(usedKey);
assert.strictEqual(usedAddress, p2pDetails.address);
assert.strictEqual(usedPort, p2pDetails.port);
});
it('Should set a contact as offline and online', () => {
this.lokiP2pAPI.contactP2pDetails[usedKey] = { ...usedDetails };
let p2pDetails = this.lokiP2pAPI.getContactP2pDetails(usedKey);
assert.isNotNull(p2pDetails);
assert.isFalse(p2pDetails.isOnline);
this.lokiP2pAPI.setContactOnline(usedKey);
p2pDetails = this.lokiP2pAPI.getContactP2pDetails(usedKey);
assert.isTrue(p2pDetails.isOnline);
this.lokiP2pAPI.setContactOffline(usedKey);
p2pDetails = this.lokiP2pAPI.getContactP2pDetails(usedKey);
assert.isFalse(p2pDetails.isOnline);
});
});
});

@ -75,7 +75,7 @@ describe('Snode Channel', () => {
});
it('should encrypt data correctly', async () => {
// message sent by Session Messenger
// message sent by Session
const snode = await generateSnodeKeysAndAddress();
const messageSent = 'I am Groot';
const textEncoder = new TextEncoder();
@ -133,7 +133,7 @@ describe('Snode Channel', () => {
const encryptedBase64 = dcodeIO.ByteBuffer.wrap(encrypted).toString(
'base64'
);
// message received by Session Messenger
// message received by Session
const decrypted = await channel.decrypt(snode.address, encryptedBase64);
assert.strictEqual(messageSent, decrypted);
});

@ -163,38 +163,6 @@
}
inherit(ReplayableError, DNSResolutionError);
function HolePunchingError(message, error) {
this.name = 'HolePunchingError';
this.message = message;
this.error = error;
Error.call(this, message);
// Maintains proper stack trace, where our error was thrown (only available on V8)
// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
if (Error.captureStackTrace) {
Error.captureStackTrace(this);
}
appendStack(this, error);
}
function LokiIpError(message, resolutionError) {
this.name = 'LokiIpError';
this.message = message;
this.error = resolutionError;
Error.call(this, message);
// Maintains proper stack trace, where our error was thrown (only available on V8)
// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
if (Error.captureStackTrace) {
Error.captureStackTrace(this);
}
appendStack(this, resolutionError);
}
function NotFoundError(message, error) {
this.name = 'NotFoundError';
this.message = message;
@ -307,8 +275,6 @@
window.textsecure.EmptySwarmError = EmptySwarmError;
window.textsecure.SeedNodeError = SeedNodeError;
window.textsecure.DNSResolutionError = DNSResolutionError;
window.textsecure.LokiIpError = LokiIpError;
window.textsecure.HolePunchingError = HolePunchingError;
window.textsecure.HTTPError = HTTPError;
window.textsecure.NotFoundError = NotFoundError;
window.textsecure.WrongSwarmError = WrongSwarmError;

@ -12,11 +12,8 @@
/* global ContactBuffer: false */
/* global GroupBuffer: false */
/* global WebSocketResource: false */
/* global localLokiServer: false */
/* global lokiPublicChatAPI: false */
/* global localServerPort: false */
/* global lokiMessageAPI: false */
/* global lokiP2pAPI: false */
/* global feeds: false */
/* global Whisper: false */
/* global lokiFileServerAPI: false */
@ -82,9 +79,6 @@ MessageReceiver.prototype.extend({
handleRequest: this.handleRequest.bind(this),
});
this.httpPollingResource.pollServer();
if (localLokiServer) {
localLokiServer.on('message', this.handleP2pMessage.bind(this));
}
if (lokiPublicChatAPI) {
lokiPublicChatAPI.on(
'publicMessage',
@ -95,7 +89,6 @@ MessageReceiver.prototype.extend({
feeds.forEach(feed => {
feed.on('rssMessage', this.handleUnencryptedMessage.bind(this));
});
this.startLocalServer();
// TODO: Rework this socket stuff to work with online messaging
const useWebSocket = false;
@ -126,45 +119,6 @@ MessageReceiver.prototype.extend({
// all cached envelopes are processed.
this.incoming = [this.pending];
},
async startLocalServer() {
if (!localLokiServer) {
return;
}
try {
// clearnet change: getMyLokiIp -> getMyClearIp
// const myLokiIp = await window.lokiSnodeAPI.getMyLokiIp();
const myLokiIp = '0.0.0.0';
const myServerPort = await localLokiServer.start(
localServerPort,
myLokiIp
);
window.log.info(`Local Server started at ${myLokiIp}:${myServerPort}`);
libloki.api.broadcastOnlineStatus();
} catch (e) {
if (e instanceof textsecure.HolePunchingError) {
window.log.warn(e.message);
window.log.warn('Abdandoning starting p2p server.');
return;
} else if (e instanceof textsecure.LokiIpError) {
window.log.warn(
'Failed to get my loki address to bind server to, will retry in 30 seconds'
);
} else {
window.log.warn(
'Failed to start local loki server, will retry in 30 seconds'
);
}
setTimeout(this.startLocalServer.bind(this), 30 * 1000);
}
},
handleP2pMessage({ message, onSuccess, onFailure }) {
const options = {
isP2p: true,
onSuccess,
onFailure,
};
this.httpPollingResource.handleMessage(message, options);
},
async handleUnencryptedMessage({ message }) {
const isMe = message.source === textsecure.storage.user.getNumber();
if (!isMe && message.message.profile) {
@ -201,13 +155,6 @@ MessageReceiver.prototype.extend({
this.wsr.removeEventListener('close', this._onClose);
this.wsr = null;
}
if (localLokiServer) {
localLokiServer.removeListener(
'message',
this.handleP2pMessage.bind(this)
);
}
},
async close() {
window.log.info('MessageReceiver.close()');
@ -219,10 +166,6 @@ MessageReceiver.prototype.extend({
this.wsr.close(3000, 'called close');
}
if (localLokiServer) {
localLokiServer.close();
}
if (lokiPublicChatAPI) {
await lokiPublicChatAPI.close();
}
@ -279,7 +222,7 @@ MessageReceiver.prototype.extend({
// });
},
handleRequest(request, options) {
const { isP2p, onSuccess, onFailure } = options;
const { onSuccess, onFailure } = options;
this.incoming = this.incoming || [];
const lastPromise = _.last(this.incoming);
@ -299,9 +242,6 @@ MessageReceiver.prototype.extend({
const promise = Promise.resolve(request.body.toArrayBuffer()) // textsecure.crypto
.then(plaintext => {
const envelope = textsecure.protobuf.Envelope.decode(plaintext);
if (isP2p) {
lokiP2pAPI.setContactOnline(envelope.source);
}
// After this point, decoding errors are not the server's
// fault, and we should handle them gracefully and tell the
// user they received an invalid message
@ -311,7 +251,6 @@ MessageReceiver.prototype.extend({
}
envelope.id = envelope.serverGuid || window.getGuid();
envelope.isP2p = isP2p;
envelope.serverTimestamp = envelope.serverTimestamp
? envelope.serverTimestamp.toNumber()
: null;
@ -1089,16 +1028,8 @@ MessageReceiver.prototype.extend({
})
);
},
async handleLokiAddressMessage(envelope, lokiAddressMessage) {
const { p2pAddress, p2pPort, type } = lokiAddressMessage;
if (type === textsecure.protobuf.LokiAddressMessage.Type.HOST_REACHABLE) {
lokiP2pAPI.updateContactP2pDetails(
envelope.source,
p2pAddress,
p2pPort,
envelope.isP2p
);
}
async handleLokiAddressMessage(envelope) {
window.log.warn('Ignoring a Loki address message');
return this.removeFromCache(envelope);
},
async handlePairingRequest(envelope, pairingRequest) {
@ -1319,14 +1250,6 @@ MessageReceiver.prototype.extend({
await conversation.setLokiProfile(newProfile);
},
handleDataMessage(envelope, msg) {
if (!envelope.isP2p) {
const timestamp = envelope.timestamp.toNumber();
const now = Date.now();
const ageInSeconds = (now - timestamp) / 1000;
if (ageInSeconds <= 120) {
lokiP2pAPI.pingContact(envelope.source);
}
}
window.log.info('data message from', this.getEnvelopeId(envelope));
let p = Promise.resolve();
// eslint-disable-next-line no-bitwise
@ -1465,7 +1388,6 @@ MessageReceiver.prototype.extend({
timestamp: envelope.timestamp.toNumber(),
receivedAt: envelope.receivedAt,
unidentifiedDeliveryReceived: envelope.unidentifiedDeliveryReceived,
isP2p: envelope.isP2p,
message,
};
return this.dispatchAndWait(ev);

@ -308,7 +308,7 @@ OutgoingMessage.prototype = {
return Promise.all(
devicesPubKeys.map(async devicePubKey => {
// Session Messenger doesn't use the deviceId scheme, it's always 1.
// Session doesn't use the deviceId scheme, it's always 1.
// Instead, there are multiple device public keys.
const deviceId = 1;
const updatedDevices = await this.getStaleDeviceIdsForNumber(

@ -14,6 +14,7 @@ const packageJson = require('./package.json');
const GlobalErrors = require('./app/global_errors');
GlobalErrors.addHandler();
const { globalShortcut } = electron;
const getRealPath = pify(fs.realpath);
const {
@ -186,7 +187,7 @@ function captureClicks(window) {
}
const DEFAULT_WIDTH = 800;
const DEFAULT_HEIGHT = 710;
const DEFAULT_HEIGHT = 720;
const MIN_WIDTH = 1125;
const MIN_HEIGHT = 750;
const BOUNDS_BUFFER = 100;
@ -285,6 +286,13 @@ function createWindow() {
// Disable system main menu
mainWindow.setMenu(null);
globalShortcut.register('f5', () => {
mainWindow.reload();
});
globalShortcut.register('CommandOrControl+R', () => {
mainWindow.reload();
});
function captureAndSaveWindowStats() {
if (!mainWindow) {
return;
@ -741,7 +749,7 @@ app.on('ready', async () => {
logger.info(`starting version ${packageJson.version}`);
if (!locale) {
const appLocale = process.env.NODE_ENV === 'test' ? 'en' : app.getLocale();
const appLocale = process.env.NODE_ENV === 'test' ? 'en' : 'en'; // app.getLocale(); // FIXME reenable once we have translated our files
locale = loadLocale({ appLocale, logger });
}

@ -1,9 +1,9 @@
{
"name": "session-messenger-desktop",
"productName": "Session Messenger",
"productName": "Session",
"description": "Private messaging from your desktop",
"repository": "https://github.com/loki-project/loki-messenger.git",
"version": "1.0.0-beta10",
"version": "1.0.0",
"license": "GPL-3.0",
"author": {
"name": "Loki Project",
@ -41,19 +41,23 @@
"test-node": "mocha --recursive --exit test/app test/modules ts/test libloki/test/node",
"test-node-coverage": "nyc --reporter=lcov --reporter=text mocha --recursive test/app test/modules ts/test libloki/test/node",
"test-node-coverage-html": "nyc --reporter=lcov --reporter=html mocha --recursive test/a/* */pp test/modules ts/test libloki/test/node",
"eslint": "eslint .",
"eslint": "eslint --cache .",
"eslint-full": "eslint .",
"lint": "yarn format --list-different && yarn lint-windows",
"lint-full": "yarn format-full --list-different; yarn lint-windows-full",
"dev-lint": "yarn format --list-different; yarn lint-windows",
"lint-windows": "yarn eslint && yarn tslint",
"lint-windows-full": "yarn eslint-full && yarn tslint",
"lint-deps": "node ts/util/lint/linter.js",
"tslint": "tslint --format stylish --project .",
"format": "prettier --write \"*.{css,js,json,md,scss,ts,tsx}\" \"./**/*.{css,js,json,md,scss,ts,tsx}\"",
"format": "prettier --write `git ls-files --modified *.{css,js,json,md,scss,ts,tsx}` `git ls-files --modified ./**/*.{css,js,json,md,scss,ts,tsx}`",
"format-full": "prettier --write \"*.{css,js,json,md,scss,ts,tsx}\" \"./**/*.{css,js,json,md,scss,ts,tsx}\"",
"transpile": "tsc",
"clean-transpile": "rimraf ts/**/*.js && rimraf ts/*.js",
"open-coverage": "open coverage/lcov-report/index.html",
"styleguide": "styleguidist server",
"pow-metrics": "node metrics_app.js localhost 9000",
"ready": "yarn clean-transpile && yarn grunt && yarn lint && yarn test-node && yarn test-electron && yarn lint-deps"
"ready": "yarn clean-transpile && yarn grunt && yarn lint-full && yarn test-node && yarn test-electron && yarn lint-deps"
},
"dependencies": {
"@journeyapps/sqlcipher": "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#2e28733b61640556b0272a3bfc78b0357daf71e6",
@ -98,7 +102,6 @@
"mkdirp": "0.5.1",
"moment": "2.21.0",
"mustache": "2.3.0",
"nat-upnp": "^1.1.1",
"node-fetch": "2.3.0",
"node-gyp": "3.8.0",
"node-sass": "4.9.3",
@ -120,7 +123,6 @@
"redux-promise-middleware": "6.1.0",
"reselect": "4.0.0",
"rimraf": "2.6.2",
"selfsigned": "^1.10.4",
"semver": "5.4.1",
"spellchecker": "3.5.1",
"tar": "4.4.8",
@ -160,7 +162,6 @@
"@types/uuid": "3.4.4",
"arraybuffer-loader": "1.0.3",
"asar": "0.14.0",
"axios": "0.18.0",
"bower": "1.8.2",
"chai": "4.1.2",
"dashdash": "1.14.1",
@ -243,7 +244,7 @@
"linux": {
"category": "Network",
"desktop": {
"StartupWMClass": "Session Messenger"
"StartupWMClass": "Session"
},
"asarUnpack": "node_modules/spellchecker/vendor/hunspell_dictionaries",
"target": [

@ -3,7 +3,6 @@
const path = require('path');
const electron = require('electron');
const semver = require('semver');
const selfsigned = require('selfsigned');
const { deferredToPromise } = require('./js/modules/deferred_to_promise');
const { JobQueue } = require('./js/modules/job_queue');
@ -72,21 +71,6 @@ window.versionInfo = {
appInstance: window.getAppInstance(),
};
// temporary clearnet fix
window.getSelfSignedCert = () => {
let pems = window.storage.get('self-signed-certificate', null);
if (!pems) {
const pubKey = window.storage.get('number_id');
const attrs = [{ name: 'commonName', value: pubKey }];
pems = selfsigned.generate(attrs, { days: 365 * 10 });
window.storage.put('self-signed-certificate', pems);
window.log.info(`Created PEM for p2p:\n${pems}`);
} else {
window.log.info(`Found existing PEM for p2p:\n${pems}`);
}
return pems;
};
window.wrapDeferred = deferredToPromise;
const ipc = electron.ipcRenderer;
@ -376,8 +360,6 @@ window.lokiSnodeAPI = new LokiSnodeAPI({
localUrl: config.localUrl,
});
window.LokiP2pAPI = require('./js/modules/loki_p2p_api');
window.LokiMessageAPI = require('./js/modules/loki_message_api');
window.LokiPublicChatAPI = require('./js/modules/loki_public_chat_api');
@ -390,8 +372,6 @@ const LokiMixpanelAPI = require('./js/modules/loki_mixpanel.js');
window.mixpanel = new LokiMixpanelAPI();
window.LocalLokiServer = require('./libloki/modules/local_loki_server');
window.localServerPort = config.localServerPort;
window.mnemonic = require('./libloki/modules/mnemonic');

@ -27,16 +27,16 @@ const PRODUCTION_NAME = 'loki-messenger-desktop';
const BETA_NAME = 'loki-messenger-desktop-beta';
const PRODUCT_NAME_PATH = 'productName';
const PRODUCTION_PRODUCT_NAME = 'Session Messenger';
const BETA_PRODUCT_NAME = 'Session Messenger Beta';
const PRODUCTION_PRODUCT_NAME = 'Session';
const BETA_PRODUCT_NAME = 'Session Beta';
const APP_ID_PATH = 'build.appId';
const PRODUCTION_APP_ID = 'com.loki-project.messenger-desktop';
const BETA_APP_ID = 'com.loki-project.messenger-desktop-beta';
const STARTUP_WM_CLASS_PATH = 'build.linux.desktop.StartupWMClass';
const PRODUCTION_STARTUP_WM_CLASS = 'Session Messenger';
const BETA_STARTUP_WM_CLASS = 'Session Messenger Beta';
const PRODUCTION_STARTUP_WM_CLASS = 'Session';
const BETA_STARTUP_WM_CLASS = 'Session Beta';
// -------

@ -457,10 +457,6 @@
resize: none;
font-size: 1em;
font-family: inherit;
&[disabled='disabled'] {
background: $color-light-35;
}
}
.capture-audio {
float: right;

@ -1469,6 +1469,7 @@
}
.module-conversation-header__avatar {
cursor: pointer;
min-width: 28px;
user-select: none;
}
@ -1735,6 +1736,7 @@
// Module: Document List Item
.module-document-list-item {
padding-right: 10px;
width: 100%;
height: 72px;
}
@ -2595,7 +2597,7 @@
.module-typing-animation {
display: inline-flex;
flex-directin: row;
flex-direction: row;
align-items: center;
height: 8px;

@ -476,6 +476,12 @@ $session-element-border-green: 4px solid $session-color-green;
width: 100%;
display: flex;
&-text {
@include session-color-subtle($session-color-white);
font-family: 'SF Pro Text';
font-weight: 300;
}
.module-contact-name {
width: 100%;
}
@ -1277,6 +1283,10 @@ label {
}
}
.dark-theme .bottom-bar .send-message[disabled='disabled'] {
background: $session-shade-4 !important;
}
.session-radio-group fieldset {
border: none;
margin-left: $session-margin-sm;
@ -1323,11 +1333,18 @@ label {
border-color: $session-color-white;
}
}
.user-details-dialog {
.session-id-editable {
.session-id-editable {
padding: $session-margin-lg;
textarea {
width: 30vh;
}
}
.session-id-editable textarea {
resize: none;
overflow: hidden;
}
input {
user-select: text;

@ -79,8 +79,15 @@
}
.module-attachment-section__items {
display: grid;
grid-template-columns: repeat(3, 1fr);
&-media {
display: grid;
grid-template-columns: repeat(3, 1fr);
width: 100%;
}
&-documents {
width: 100%;
}
}
.module-media {

@ -296,12 +296,13 @@ $session-compose-margin: 20px;
margin: 0px 20px;
}
.session-id-editable {
.session-id-editable textarea::-webkit-inner-spin-button {
margin: 0px 20px;
width: -webkit-fill-available;
flex-shrink: 0;
user-select: all;
overflow: hidden;
resize: none;
}
.session-button {
@ -361,6 +362,7 @@ $session-compose-margin: 20px;
height: inherit;
border: none;
flex-grow: 1;
font-size: $session-font-md;
&:focus {
outline: none !important;

@ -224,22 +224,30 @@
}
&-id-editable {
display: flex;
align-items: center;
justify-content: center;
height: 94px;
width: 100%;
padding: 20px;
border-radius: 8px;
border: 2px solid $session-color-dark-grey;
outline: 0;
background: transparent;
color: $session-color-white;
font-size: 15px;
line-height: 18px;
text-align: center;
border: 2px solid #353535;
margin-bottom: 20px;
overflow-wrap: break-word;
padding: 20px 5px 20px 5px;
display: inline-block;
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
user-select: all;
textarea {
width: 100%;
outline: 0;
border: none;
background: transparent;
color: #fff;
font-size: 15px;
line-height: 18px;
text-align: center;
overflow-wrap: break-word;
padding: 20px 5px 20px 5px;
display: inline-block;
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
user-select: all;
}
}
}

@ -49,7 +49,7 @@ body.dark-theme {
outline: 0;
&[disabled='disabled'] {
background: $color-light-90;
cursor: not-allowed;
}
}
}

@ -82,6 +82,5 @@ window.clearDatabase = async () => {
await window.storage.fetch();
};
window.lokiP2pAPI = new window.LokiP2pAPI('ourKey');
window.Whisper = window.Whisper || {};
window.Whisper.events = _.clone(Backbone.Events);

@ -1,9 +1,9 @@
[
{
"label": "Session Messenger",
"label": "Session",
"submenu": [
{
"label": "About Session Messenger",
"label": "About Session",
"click": null
},
{
@ -28,7 +28,7 @@
"type": "separator"
},
{
"label": "Quit Session Messenger",
"label": "Quit Session",
"role": "quit"
}
]

@ -1,9 +1,9 @@
[
{
"label": "Session Messenger",
"label": "Session",
"submenu": [
{
"label": "About Session Messenger",
"label": "About Session",
"click": null
},
{
@ -28,7 +28,7 @@
"type": "separator"
},
{
"label": "Quit Session Messenger",
"label": "Quit Session",
"role": "quit"
}
]

@ -17,7 +17,7 @@
"type": "separator"
},
{
"label": "Quit Session Messenger",
"label": "Quit Session",
"role": "quit"
}
]
@ -134,7 +134,7 @@
"type": "separator"
},
{
"label": "About Session Messenger",
"label": "About Session",
"click": null
}
]

@ -6,7 +6,7 @@
"type": "separator"
},
{
"label": "Quit Session Messenger",
"label": "Quit Session",
"role": "quit"
}
]
@ -123,7 +123,7 @@
"type": "separator"
},
{
"label": "About Session Messenger",
"label": "About Session",
"click": null
}
]

@ -146,15 +146,15 @@
<div class="content">
<div class="betaDisclaimerView" style="display: none;">
<h2>
Thanks for testing Session Messenger!
Thanks for testing Session!
</h2>
<p>
Thanks for testing Session Messenger! This software is a beta version of the full Session Messenger software suite, and so is missing some of the features the full version will have.
Thanks for testing Session! This software is a beta version of the full Session software suite, and so is missing some of the features the full version will have.
</p>
<p>
<b>
This version of Session Messenger provides no guarantees of metadata privacy.
This version of Session provides no guarantees of metadata privacy.
</b>
</p>
@ -167,7 +167,7 @@
</p>
<p>
However, no one except you and your intended recipients will be able to see the contents of your messages. We recommend using existing methods, like Tor or I2P to mask your IP address while using Session Messenger beta version.
However, no one except you and your intended recipients will be able to see the contents of your messages. We recommend using existing methods, like Tor or I2P to mask your IP address while using Session beta version.
</p>
<p>

@ -1,35 +1,12 @@
/* global Whisper */
describe('ScrollDownButtonView', () => {
it('renders with count = 0', () => {
it('renders ', () => {
const view = new Whisper.ScrollDownButtonView();
view.render();
assert.equal(view.count, 0);
assert.match(view.$el.html(), /Scroll to bottom/);
});
it('renders with count = 1', () => {
const view = new Whisper.ScrollDownButtonView({ count: 1 });
view.render();
assert.equal(view.count, 1);
assert.match(view.$el.html(), /New message below/);
});
it('renders with count = 2', () => {
const view = new Whisper.ScrollDownButtonView({ count: 2 });
view.render();
assert.equal(view.count, 2);
assert.match(view.$el.html(), /New messages below/);
});
it('increments count and re-renders', () => {
const view = new Whisper.ScrollDownButtonView();
view.render();
assert.equal(view.count, 0);
assert.notMatch(view.$el.html(), /New message below/);
view.increment(1);
assert.equal(view.count, 1);
assert.match(view.$el.html(), /New message below/);
assert.match(
view.$el.html(),
/<div class="session-icon-button huge" role="button"><svg /
);
});
});

@ -78,13 +78,14 @@ export class ConversationListItem extends React.PureComponent<Props> {
profileName,
isOnline,
isPendingFriendRequest,
hasSentFriendRequest,
} = this.props;
let borderColor;
if (!isPendingFriendRequest) {
if (!(isPendingFriendRequest && !hasSentFriendRequest)) {
borderColor = isOnline ? Colors.ONLINE : Colors.OFFLINE;
}
const iconSize = isPendingFriendRequest ? 28 : 48;
const iconSize = isPendingFriendRequest && !hasSentFriendRequest ? 28 : 48;
return (
<div className="module-conversation-list-item__avatar-container">

@ -79,7 +79,7 @@ export class SearchResults extends React.Component<Props> {
<div className="module-search-results__messages">
{hideMessagesHeader ? null : (
<div className="module-search-results__messages-header">
{i18n('messagesHeader')}
{i18n('messages')}
</div>
)}
{messages.map(message => (

@ -23,7 +23,7 @@ export class GroupInvitation extends React.Component<Props> {
<div className="contents">
<img
alt="group-avatar"
src="images/loki/loki_icon.png"
src="images/loki/session_icon.png"
className="invite-group-avatar"
/>
<span className="group-details">

@ -97,7 +97,6 @@ export interface Props {
expirationLength?: number;
expirationTimestamp?: number;
convoId: string;
isP2p?: boolean;
isPublic?: boolean;
isRss?: boolean;
selected: boolean;
@ -214,13 +213,9 @@ export class Message extends React.PureComponent<Props, State> {
}
public renderMetadataBadges() {
const { direction, isP2p, isPublic, senderIsModerator } = this.props;
const { direction, isPublic, senderIsModerator } = this.props;
const badges = [
isPublic && 'Public',
isP2p && 'P2p',
senderIsModerator && 'Mod',
];
const badges = [isPublic && 'Public', senderIsModerator && 'Mod'];
return badges
.map(badgeText => {

@ -16,10 +16,14 @@ interface Props {
export class AttachmentSection extends React.Component<Props> {
public render() {
const { type } = this.props;
return (
<div className="module-attachment-section">
<div className="module-attachment-section__items">
{this.renderItems()}
<div className={`module-attachment-section__items-${type}`}>
{this.renderItems()}
</div>
</div>
</div>
);

@ -212,9 +212,7 @@ export class LeftPaneChannelSection extends React.Component<Props, State> {
return;
}
this.setState({ channelUrlPasted: '' }, () => {
window.Session.emptyContentEditableDivs();
});
this.setState({ channelUrlPasted: '' });
if (updateSearchTerm) {
updateSearchTerm(searchTerm);
@ -297,11 +295,8 @@ export class LeftPaneChannelSection extends React.Component<Props, State> {
);
}
private handleOnPasteUrl(e: any) {
if (e.target.innerHTML) {
const cleanText = e.target.innerHTML.replace(/<\/?[^>]+(>|$)/g, '');
this.setState({ channelUrlPasted: cleanText });
}
private handleOnPasteUrl(value: string) {
this.setState({ channelUrlPasted: value });
}
private handleJoinChannelButtonClick() {

@ -164,9 +164,7 @@ export class LeftPaneContactSection extends React.Component<Props, State> {
return;
}
this.setState({ pubKeyPasted: '' }, () => {
window.Session.emptyContentEditableDivs();
});
this.setState({ pubKeyPasted: '' });
if (updateSearchTerm) {
updateSearchTerm(searchTerm);
@ -239,7 +237,7 @@ export class LeftPaneContactSection extends React.Component<Props, State> {
}
private handleOnAddContact() {
const sessionID = this.state.addContactRecipientID;
const sessionID = this.state.addContactRecipientID.trim();
const error = validateNumber(sessionID, window.i18n);
if (error) {
@ -253,12 +251,8 @@ export class LeftPaneContactSection extends React.Component<Props, State> {
}
}
private handleRecipientSessionIDChanged(event: any) {
if (event.target.innerHTML) {
// remove br elements or div elements
const cleanText = event.target.innerHTML.replace(/<\/?[^>]+(>|$)/g, '');
this.setState({ addContactRecipientID: cleanText });
}
private handleRecipientSessionIDChanged(value: string) {
this.setState({ addContactRecipientID: value });
}
private renderContacts() {

@ -197,9 +197,7 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
return;
}
// reset our pubKeyPasted, we can either have a pasted sessionID or a sessionID got from a search
this.setState({ pubKeyPasted: '' }, () => {
window.Session.emptyContentEditableDivs();
});
this.setState({ pubKeyPasted: '' });
if (updateSearchTerm) {
updateSearchTerm(searchTerm);
@ -259,12 +257,11 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
this.updateSearch('');
}
private handleOnPasteSessionID(e: any) {
private handleOnPasteSessionID(value: string) {
// reset our search, we can either have a pasted sessionID or a sessionID got from a search
this.updateSearch('');
const cleanText = e.target.innerHTML.replace(/<\/?[^>]+(>|$)/g, '');
this.setState({ pubKeyPasted: cleanText });
this.setState({ pubKeyPasted: value });
}
private handleMessageButtonClick() {
@ -281,6 +278,7 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
}
let pubkey: string;
pubkey = this.state.pubKeyPasted || this.props.searchTerm;
pubkey = pubkey.trim();
const error = validateNumber(pubkey);
if (!error) {

@ -499,19 +499,16 @@ export class RegistrationTabs extends React.Component<{}, State> {
<SessionIdEditable
editable={contentEditable}
placeholder={enterSessionIDHere}
onChange={(e: any) => {
this.onSecondDeviceSessionIDChanged(e);
onChange={(value: string) => {
this.onSecondDeviceSessionIDChanged(value);
}}
/>
);
}
private onSecondDeviceSessionIDChanged(e: any) {
e.preventDefault();
const cleanText = e.target.innerHTML.replace(/<\/?[^>]+(>|$)/g, '');
const hexEncodedPubKey = cleanText;
private onSecondDeviceSessionIDChanged(value: string) {
this.setState({
primaryDevicePubKey: hexEncodedPubKey,
primaryDevicePubKey: value,
});
}

@ -49,6 +49,18 @@ export class SessionChannelSettings extends React.Component<Props, any> {
.ignore();
}
public componentDidUpdate() {
this.getMediaGalleryProps()
.then(({ documents, media, onItemClick }) => {
this.setState({
documents,
media,
onItemClick,
});
})
.ignore();
}
public async getMediaGalleryProps() {
// We fetch more documents than media as they dont require to be loaded
// into memory right away. Revisit this once we have infinite scrolling:

@ -5,19 +5,17 @@ interface Props {
text?: string;
editable?: boolean;
onChange?: any;
onPressEnter?: any;
}
export class SessionIdEditable extends React.PureComponent<Props> {
private readonly inputRef: React.RefObject<HTMLInputElement>;
private readonly inputRef: any;
public constructor(props: Props) {
super(props);
this.inputRef = React.createRef();
}
public componentWillUnmount() {
//FIXME ugly hack to empty the content editable div used on enter session ID
window.Session.emptyContentEditableDivs();
this.handleChange = this.handleChange.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
public focus() {
@ -27,22 +25,37 @@ export class SessionIdEditable extends React.PureComponent<Props> {
}
public render() {
const { placeholder, editable, onChange, text } = this.props;
const { placeholder, editable, text } = this.props;
return (
<div
ref={this.inputRef}
className="session-id-editable"
placeholder={placeholder}
contentEditable={editable}
onInput={(e: any) => {
if (editable) {
onChange(e);
}
}}
>
{text}
<div className="session-id-editable">
<textarea
className="session-id-editable-textarea"
ref={this.inputRef}
placeholder={placeholder}
disabled={!editable}
spellCheck={false}
onKeyDown={this.handleKeyDown}
onChange={this.handleChange}
value={text}
/>
</div>
);
}
private handleChange(e: any) {
const { editable, onChange } = this.props;
if (editable) {
onChange(e.target.value);
}
}
private handleKeyDown(e: any) {
const { editable, onPressEnter } = this.props;
if (editable && e.keyCode === 13) {
e.preventDefault();
// tslint:disable-next-line: no-unused-expression
onPressEnter && onPressEnter();
}
}
}

@ -31,6 +31,12 @@ export class SessionPasswordModal extends React.Component<Props, State> {
this.setPassword = this.setPassword.bind(this);
this.closeDialog = this.closeDialog.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
}
public componentDidMount() {
setTimeout(() => $('#password-modal-input').focus(), 100);
}
public render() {
@ -58,6 +64,7 @@ export class SessionPasswordModal extends React.Component<Props, State> {
type="password"
id="password-modal-input"
placeholder={placeholders[0]}
onKeyUp={this.onKeyUp}
maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
/>
{action !== PasswordAction.Remove && (
@ -65,6 +72,7 @@ export class SessionPasswordModal extends React.Component<Props, State> {
type="password"
id="password-modal-input-confirm"
placeholder={placeholders[1]}
onKeyUp={this.onKeyUp}
maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
/>
)}
@ -182,4 +190,14 @@ export class SessionPasswordModal extends React.Component<Props, State> {
this.props.onClose();
}
}
private async onKeyUp(event: any) {
const { onOk } = this.props;
if (event.key === 'Enter') {
await this.setPassword(onOk);
}
event.preventDefault();
}
}

@ -27,8 +27,10 @@ export class SessionPasswordPrompt extends React.PureComponent<{}, State> {
this.onKeyUp = this.onKeyUp.bind(this);
this.initLogin = this.initLogin.bind(this);
this.initClearDataView = this.initClearDataView.bind(this);
}
window.addEventListener('keyup', this.onKeyUp);
public componentDidMount() {
setTimeout(() => $('#password-prompt-input').focus(), 100);
}
public render() {
@ -56,9 +58,9 @@ export class SessionPasswordPrompt extends React.PureComponent<{}, State> {
<input
id="password-prompt-input"
type="password"
autoFocus={true}
defaultValue=""
placeholder={' '}
onKeyUp={this.onKeyUp}
maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
/>
);

@ -36,8 +36,10 @@ export class SessionSeedModal extends React.Component<Props, State> {
this.confirmPassword = this.confirmPassword.bind(this);
this.checkHasPassword = this.checkHasPassword.bind(this);
this.onEnter = this.onEnter.bind(this);
}
window.addEventListener('keyup', this.onEnter);
public componentDidMount() {
setTimeout(() => $('#seed-input-password').focus(), 100);
}
public render() {
@ -84,6 +86,7 @@ export class SessionSeedModal extends React.Component<Props, State> {
type="password"
id="seed-input-password"
placeholder={i18n('password')}
onKeyUp={this.onEnter}
maxLength={maxPasswordLen}
/>
@ -218,9 +221,7 @@ export class SessionSeedModal extends React.Component<Props, State> {
private onEnter(event: any) {
if (event.key === 'Enter') {
if ($('#seed-input-password').is(':focus')) {
this.confirmPassword();
}
this.confirmPassword();
}
}
}

File diff suppressed because one or more lines are too long

@ -68,9 +68,14 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
this.hasPassword();
this.refreshLinkedDevice = this.refreshLinkedDevice.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
window.addEventListener('keyup', this.onKeyUp);
}
public componentDidMount() {
setTimeout(() => $('#password-lock-input').focus(), 100);
window.Whisper.events.on('refreshLinkedDeviceList', async () => {
setTimeout(() => {
this.refreshLinkedDevice();
@ -398,7 +403,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
id: 'media-permissions',
title: window.i18n('mediaPermissionsTitle'),
description: window.i18n('mediaPermissionsDescription'),
hidden: true, // Hidden until feature works
hidden: false,
type: SessionSettingType.Toggle,
category: SessionSettingCategory.Permissions,
setFn: window.toggleMediaPermissions,
@ -576,4 +581,14 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
});
});
}
private async onKeyUp(event: any) {
const lockPasswordFocussed = $('#password-lock-input').is(':focus');
if (event.key === 'Enter' && lockPasswordFocussed) {
await this.validatePasswordLock();
}
event.preventDefault();
}
}

@ -907,7 +907,7 @@ async@^1.5.0, async@~1.5.2:
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
async@^2.0.0, async@^2.1.4, async@^2.1.5, async@^2.6.2:
async@^2.0.0, async@^2.1.4, async@^2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
@ -951,14 +951,6 @@ aws4@^1.6.0, aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
axios@0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102"
integrity sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=
dependencies:
follow-redirects "^1.3.0"
is-buffer "^1.1.5"
babel-code-frame@6.26.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
@ -3792,7 +3784,7 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
follow-redirects@^1.0.0, follow-redirects@^1.3.0:
follow-redirects@^1.0.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.9.0.tgz#8d5bcdc65b7108fe1508649c79c12d732dcedb4f"
integrity sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==
@ -4989,7 +4981,7 @@ ip-regex@^1.0.1:
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-1.0.3.tgz#dc589076f659f419c222039a33316f1c7387effd"
integrity sha1-3FiQdvZZ9BnCIgOaMzFvHHOH7/0=
ip@1.1.5, ip@^1.1.0, ip@^1.1.4, ip@^1.1.5:
ip@1.1.5, ip@^1.1.0, ip@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
@ -6575,16 +6567,6 @@ nanomatch@^1.2.9:
snapdragon "^0.8.1"
to-regex "^3.0.1"
nat-upnp@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/nat-upnp/-/nat-upnp-1.1.1.tgz#b18365e4faf44652549bb593c69e6b690df22043"
integrity sha512-b1Q+sf9fHGCXhlWErNgTTEto8A02MnNysw3vx3kD1657+/Ae23vPEAB6QBh+9RqLL4+xw/LmjVTiLy6A7Cx0xw==
dependencies:
async "^2.1.5"
ip "^1.1.4"
request "^2.79.0"
xml2js "~0.1.14"
native-or-lie@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/native-or-lie/-/native-or-lie-1.0.2.tgz#c870ee0ba0bf0ff11350595d216cfea68a6d8086"
@ -8867,7 +8849,7 @@ request@2.87.0:
tunnel-agent "^0.6.0"
uuid "^3.1.0"
request@2.x, request@^2.45.0, request@^2.65.0, request@^2.79.0, request@^2.81.0, request@^2.83.0, request@^2.87.0:
request@2.x, request@^2.45.0, request@^2.65.0, request@^2.81.0, request@^2.83.0, request@^2.87.0:
version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
@ -9114,7 +9096,7 @@ sass-graph@^2.2.4:
scss-tokenizer "^0.2.3"
yargs "^7.0.0"
sax@>=0.1.1, sax@>=0.6.0, sax@^1.2.4, sax@~1.2.1:
sax@>=0.6.0, sax@^1.2.4, sax@~1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
@ -9148,7 +9130,7 @@ select-hose@^2.0.0:
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=
selfsigned@^1.10.4, selfsigned@^1.9.1:
selfsigned@^1.9.1:
version "1.10.7"
resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b"
integrity sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==
@ -11104,13 +11086,6 @@ xml2js@^0.4.5:
sax ">=0.6.0"
xmlbuilder "~11.0.0"
xml2js@~0.1.14:
version "0.1.14"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.1.14.tgz#5274e67f5a64c5f92974cd85139e0332adc6b90c"
integrity sha1-UnTmf1pkxfkpdM2FE54DMq3GuQw=
dependencies:
sax ">=0.1.1"
xmlbuilder@~11.0.0:
version "11.0.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"

Loading…
Cancel
Save