Linted files

This commit is contained in:
Mikunj 2019-01-16 15:44:13 +11:00
parent c1dfd400f5
commit 6bd2d3962e
75 changed files with 13398 additions and 7848 deletions

View File

@ -26,3 +26,8 @@ test/blanket_mocha.js
# TypeScript generated files
ts/**/*.js
# Libloki specific files
libloki/test/components.js
libloki/mnemonic.js
libloki/sc_reduce32.js

View File

@ -19,6 +19,10 @@ ts/protobuf/*.js
stylesheets/manifest.css
ts/util/lint/exceptions.json
# Libloki specific files
libloki/test/test.js
libloki/test/components.js
# Third-party files
node_modules/**
components/**

View File

@ -181,7 +181,7 @@ module.exports = grunt => {
tasks: ['sass'],
},
transpile: {
files: ['./ts/**/*.ts','./ts/**/*.tsx'],
files: ['./ts/**/*.ts', './ts/**/*.tsx'],
tasks: ['exec:transpile'],
},
},
@ -491,7 +491,11 @@ module.exports = grunt => {
'locale-patch',
]);
grunt.registerTask('dev', ['default', 'watch']);
grunt.registerTask('test', ['unit-tests', 'lib-unit-tests', 'loki-unit-tests']);
grunt.registerTask('test', [
'unit-tests',
'lib-unit-tests',
'loki-unit-tests',
]);
grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
grunt.registerTask('default', [
'exec:build-protobuf',

View File

@ -1084,11 +1084,13 @@
"description": "Title of the Message TTL setting"
},
"messageTTLSettingDescription": {
"message": "Time to live (how long the recipient will have to collect their messages)",
"message":
"Time to live (how long the recipient will have to collect their messages)",
"description": "Description of the time to live setting"
},
"messageTTLSettingWarning": {
"message": "Warning! Lowering the TTL could result in messages being lost if the recipient doesn't collect them in time!",
"message":
"Warning! Lowering the TTL could result in messages being lost if the recipient doesn't collect them in time!",
"description": "Warning for the time to live setting"
},
"notificationSettingsDialog": {
@ -1629,23 +1631,28 @@
},
"friendRequestPending": {
"message": "Friend request",
"description": "Shown in the conversation history when the user sends or recieves a friend request"
"description":
"Shown in the conversation history when the user sends or recieves a friend request"
},
"friendRequestAccepted": {
"message": "Friend request accepted",
"description": "Shown in the conversation history when the user accepts a friend request"
"description":
"Shown in the conversation history when the user accepts a friend request"
},
"friendRequestDeclined": {
"message": "Friend request declined",
"description": "Shown in the conversation history when the user declines a friend request"
"description":
"Shown in the conversation history when the user declines a friend request"
},
"friendRequestExpired": {
"message": "Friend request expired",
"description": "Shown in the conversation history when the users friend request expires"
"description":
"Shown in the conversation history when the users friend request expires"
},
"friendRequestNotificationTitle": {
"message": "Friend request",
"description": "Shown in a notification title when receiving a friend request"
"description":
"Shown in a notification title when receiving a friend request"
},
"friendRequestNotificationMessage": {
"message": "$name$ sent you a friend request",
@ -1660,7 +1667,8 @@
},
"friendRequestAcceptedNotificationTitle": {
"message": "Friend request accepted",
"description": "Shown in a notification title when friend request was accepted by the other user"
"description":
"Shown in a notification title when friend request was accepted by the other user"
},
"friendRequestAcceptedNotificationMessage": {
"message": "$name$ accepted your friend request",
@ -1681,7 +1689,8 @@
},
"settingsUnblockHeader": {
"message": "Blocked Users",
"description": "Shown in the settings page as the heading for the blocked user settings"
"description":
"Shown in the settings page as the heading for the blocked user settings"
},
"editProfileTitle": {
"message": "Change your own display name",
@ -1694,7 +1703,8 @@
"copyPublicKey": {
"message": "Copy public key",
"description": "Button action that the user can click to copy their public keys"
"description":
"Button action that the user can click to copy their public keys"
},
"copiedPublicKey": {
"message": "Copied public key",
@ -1702,21 +1712,25 @@
},
"editDisplayName": {
"message": "Edit display name",
"description": "Button action that the user can click to edit their display name"
"description":
"Button action that the user can click to edit their display name"
},
"showSeed": {
"message": "Show seed",
"description": "Button action that the user can click to view their unique seed"
"description":
"Button action that the user can click to view their unique seed"
},
"copiedMnemonic": {
"message": "Copied seed to clipboard",
"description": "A toast message telling the user that the mnemonic seed was copied"
"description":
"A toast message telling the user that the mnemonic seed was copied"
},
"passwordViewTitle": {
"message": "Type in your password",
"description": "The title shown when user needs to type in a password to unlock the messenger"
"description":
"The title shown when user needs to type in a password to unlock the messenger"
},
"unlock": {
"message": "Unlock"
@ -1765,15 +1779,18 @@
},
"passwordLengthError": {
"message": "Password must be between 6 and 50 characters long",
"description": "Error string shown to the user when password doesn't meet length criteria"
"description":
"Error string shown to the user when password doesn't meet length criteria"
},
"passwordTypeError": {
"message": "Password must be a string",
"description": "Error string shown to the user when password is not a string"
"description":
"Error string shown to the user when password is not a string"
},
"passwordCharacterError": {
"message": "Password must only contain letters, numbers and symbols",
"description": "Error string shown to the user when password contains an invalid character"
"description":
"Error string shown to the user when password contains an invalid character"
},
"change": {
"message": "Change"
@ -1784,5 +1801,4 @@
"remove": {
"message": "Remove"
}
}

View File

@ -6,8 +6,9 @@ const ERRORS = {
CHARACTER: 'Password must only contain letters, numbers and symbols',
};
const generateHash = (phrase) => phrase && sha512(phrase.trim());
const matchesHash = (phrase, hash) => phrase && sha512(phrase.trim()) === hash.trim();
const generateHash = phrase => phrase && sha512(phrase.trim());
const matchesHash = (phrase, hash) =>
phrase && sha512(phrase.trim()) === hash.trim();
const validatePassword = (phrase, i18n) => {
if (!phrase || typeof phrase !== 'string') {
@ -20,13 +21,13 @@ const validatePassword = (phrase, i18n) => {
}
// Restrict characters to letters, numbers and symbols
const characterRegex = /^[a-zA-Z0-9-!()._`~@#$%^&*+=[\]{}|<>,;: ]+$/
const characterRegex = /^[a-zA-Z0-9-!()._`~@#$%^&*+=[\]{}|<>,;: ]+$/;
if (!characterRegex.test(trimmed)) {
return i18n ? i18n('passwordCharacterError') : ERRORS.CHARACTER;
}
return null;
}
};
module.exports = {
generateHash,

View File

@ -15,8 +15,7 @@ const hasImage = pubKey => fs.existsSync(getImagePath(pubKey));
const getImagePath = pubKey => `${PATH}/${pubKey}.png`;
const getOrCreateImagePath = pubKey => {
// If the image doesn't exist then create it
if (!hasImage(pubKey))
return generateImage(pubKey);
if (!hasImage(pubKey)) return generateImage(pubKey);
return getImagePath(pubKey);
};
@ -25,10 +24,11 @@ const removeImage = pubKey => {
if (hasImage(pubKey)) {
fs.unlinkSync(getImagePath(pubKey));
}
}
};
const removeImagesNotInArray = pubKeyArray => {
fs.readdirSync(PATH)
fs
.readdirSync(PATH)
// Get all files that end with png
.filter(file => file.includes('.png'))
// Strip the extension
@ -37,7 +37,7 @@ const removeImagesNotInArray = pubKeyArray => {
.filter(i => !pubKeyArray.includes(i))
// Remove them
.forEach(i => removeImage(i));
}
};
const generateImage = pubKey => {
const imagePath = getImagePath(pubKey);
@ -52,8 +52,8 @@ const generateImage = pubKey => {
background: [0, 0, 0, 0],
}).toString();
fs.writeFileSync(imagePath, png, 'base64');
return imagePath
}
return imagePath;
};
module.exports = {
generateImage,

View File

@ -1,4 +1,3 @@
const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');
const rimraf = require('rimraf');
@ -723,9 +722,12 @@ async function getPreKeyById(id) {
return getById(PRE_KEYS_TABLE, id);
}
async function getPreKeyByRecipient(recipient) {
const row = await db.get(`SELECT * FROM ${PRE_KEYS_TABLE} WHERE recipient = $recipient;`, {
$recipient: recipient,
});
const row = await db.get(
`SELECT * FROM ${PRE_KEYS_TABLE} WHERE recipient = $recipient;`,
{
$recipient: recipient,
}
);
if (!row) {
return null;
@ -768,9 +770,12 @@ async function getContactPreKeyById(id) {
return getById(CONTACT_PRE_KEYS_TABLE, id);
}
async function getContactPreKeyByIdentityKey(key) {
const row = await db.get(`SELECT * FROM ${CONTACT_PRE_KEYS_TABLE} WHERE identityKeyString = $identityKeyString ORDER BY keyId DESC LIMIT 1;`, {
$identityKeyString: key,
});
const row = await db.get(
`SELECT * FROM ${CONTACT_PRE_KEYS_TABLE} WHERE identityKeyString = $identityKeyString ORDER BY keyId DESC LIMIT 1;`,
{
$identityKeyString: key,
}
);
if (!row) {
return null;
@ -791,9 +796,12 @@ async function bulkAddContactPreKeys(array) {
return bulkAdd(CONTACT_PRE_KEYS_TABLE, array);
}
async function removeContactPreKeyByIdentityKey(key) {
await db.run(`DELETE FROM ${CONTACT_PRE_KEYS_TABLE} WHERE identityKeyString = $identityKeyString;`, {
$identityKeyString: key,
});
await db.run(
`DELETE FROM ${CONTACT_PRE_KEYS_TABLE} WHERE identityKeyString = $identityKeyString;`,
{
$identityKeyString: key,
}
);
}
async function removeAllContactPreKeys() {
return removeAllFromTable(CONTACT_PRE_KEYS_TABLE);
@ -824,9 +832,12 @@ async function getContactSignedPreKeyById(id) {
return getById(CONTACT_SIGNED_PRE_KEYS_TABLE, id);
}
async function getContactSignedPreKeyByIdentityKey(key) {
const row = await db.get(`SELECT * FROM ${CONTACT_SIGNED_PRE_KEYS_TABLE} WHERE identityKeyString = $identityKeyString;`, {
$identityKeyString: key,
});
const row = await db.get(
`SELECT * FROM ${CONTACT_SIGNED_PRE_KEYS_TABLE} WHERE identityKeyString = $identityKeyString;`,
{
$identityKeyString: key,
}
);
if (!row) {
return null;
@ -846,9 +857,12 @@ async function bulkAddContactSignedPreKeys(array) {
return bulkAdd(CONTACT_SIGNED_PRE_KEYS_TABLE, array);
}
async function removeContactSignedPreKeyByIdentityKey(key) {
await db.run(`DELETE FROM ${CONTACT_SIGNED_PRE_KEYS_TABLE} WHERE identityKeyString = $identityKeyString;`, {
$identityKeyString: key,
});
await db.run(
`DELETE FROM ${CONTACT_SIGNED_PRE_KEYS_TABLE} WHERE identityKeyString = $identityKeyString;`,
{
$identityKeyString: key,
}
);
}
async function removeAllContactSignedPreKeys() {
return removeAllFromTable(CONTACT_SIGNED_PRE_KEYS_TABLE);
@ -1036,8 +1050,16 @@ async function getConversationCount() {
}
async function saveConversation(data) {
// eslint-disable-next-line camelcase
const { id, active_at, type, members, name, friendRequestStatus, profileName } = data;
const {
id,
// eslint-disable-next-line camelcase
active_at,
type,
members,
name,
friendRequestStatus,
profileName,
} = data;
await db.run(
`INSERT INTO conversations (
@ -1092,8 +1114,16 @@ async function saveConversations(arrayOfConversations) {
}
async function updateConversation(data) {
// eslint-disable-next-line camelcase
const { id, active_at, type, members, name, friendRequestStatus, profileName } = data;
const {
id,
// eslint-disable-next-line camelcase
active_at,
type,
members,
name,
friendRequestStatus,
profileName,
} = data;
await db.run(
`UPDATE conversations SET
@ -1353,10 +1383,7 @@ async function saveSeenMessageHashes(arrayOfHashes) {
}
async function saveSeenMessageHash(data) {
const {
expiresAt,
hash,
} = data;
const { expiresAt, hash } = data;
await db.run(
`INSERT INTO seenMessages (
expiresAt,
@ -1364,7 +1391,8 @@ async function saveSeenMessageHash(data) {
) values (
$expiresAt,
$hash
);`, {
);`,
{
$expiresAt: expiresAt,
$hash: hash,
}
@ -1476,7 +1504,8 @@ async function getMessagesByConversation(
conversationId,
{ limit = 100, receivedAt = Number.MAX_VALUE, type = '%' } = {}
) {
const rows = await db.all(`
const rows = await db.all(
`
SELECT json FROM messages WHERE
conversationId = $conversationId AND
received_at < $received_at AND
@ -1509,7 +1538,9 @@ async function getMessagesBySentAt(sentAt) {
async function getSeenMessagesByHashList(hashes) {
const rows = await db.all(
`SELECT * FROM seenMessages WHERE hash IN ( ${hashes.map(() => '?').join(', ')} );`,
`SELECT * FROM seenMessages WHERE hash IN ( ${hashes
.map(() => '?')
.join(', ')} );`,
hashes
);

View File

@ -12,7 +12,7 @@
*/
// eslint-disable-next-line func-names
(async function () {
(async function() {
'use strict';
// Globally disable drag and drop
@ -240,12 +240,11 @@
setMessageTTL: value => {
// Make sure the ttl is between a given range and is valid
const intValue = parseInt(value, 10);
const ttl = Number.isNaN(intValue) ? 24 : intValue;
const ttl = Number.isNaN(intValue) ? 24 : intValue;
storage.put('message-ttl', ttl);
},
getReadReceiptSetting: () =>
storage.get('read-receipt-setting'),
getReadReceiptSetting: () => storage.get('read-receipt-setting'),
setReadReceiptSetting: value =>
storage.put('read-receipt-setting', value),
getNotificationSetting: () =>
@ -331,12 +330,19 @@
window.log.info('Cleanup: starting...');
const results = await Promise.all([
window.Signal.Data.getOutgoingWithoutExpiresAt({ MessageCollection: Whisper.MessageCollection }),
window.Signal.Data.getAllUnsentMessages({ MessageCollection: Whisper.MessageCollection }),
window.Signal.Data.getOutgoingWithoutExpiresAt({
MessageCollection: Whisper.MessageCollection,
}),
window.Signal.Data.getAllUnsentMessages({
MessageCollection: Whisper.MessageCollection,
}),
]);
// Combine the models
const messagesForCleanup = results.reduce((array, current) => array.concat(current.toArray()), []);
const messagesForCleanup = results.reduce(
(array, current) => array.concat(current.toArray()),
[]
);
window.log.info(
`Cleanup: Found ${messagesForCleanup.length} messages for cleanup`
@ -350,7 +356,10 @@
);
// Make sure we only target outgoing messages
if (message.isFriendRequest() && message.get('direction') === 'incoming') {
if (
message.isFriendRequest() &&
message.get('direction') === 'incoming'
) {
return;
}
@ -392,7 +401,7 @@
let isMigrationWithIndexComplete = false;
window.log.info(
`Starting background data migration. Target version: ${
Message.CURRENT_SCHEMA_VERSION
Message.CURRENT_SCHEMA_VERSION
}`
);
idleDetector.on('idle', async () => {
@ -589,7 +598,7 @@
title: window.i18n('editProfileTitle'),
message: window.i18n('editProfileDisplayNameWarning'),
nickname: displayName,
onOk: async (newName) => {
onOk: async newName => {
await storage.setProfileName(newName);
// Update the conversation if we have it
@ -599,7 +608,7 @@
conversation.setProfile(newProfile);
}
},
})
});
}
});
@ -1346,7 +1355,7 @@
type: 'friend-request',
friendStatus: 'pending',
direction: 'incoming',
}
};
}
const message = new Whisper.Message(messageData);
@ -1371,7 +1380,7 @@
} catch (error) {
window.log.error(
`Failed to send delivery receipt to ${data.source} for message ${
data.timestamp
data.timestamp
}:`,
error && error.stack ? error.stack : error
);

View File

@ -1,4 +1,4 @@
/* global , Whisper, storage, ConversationController */
/* global , Whisper, storage */
/* global textsecure: false */
/* eslint-disable more/no-then */
@ -32,14 +32,14 @@
}
if (!storage) {
throw new Error('BlockedNumberController: Could not load blocked numbers');
throw new Error(
'BlockedNumberController: Could not load blocked numbers'
);
}
// Add the numbers to the collection
const numbers = storage.getBlockedNumbers();
blockedNumbers.add(
numbers.map(number => ({ number }))
);
blockedNumbers.add(numbers.map(number => ({ number })));
},
block(number) {
const ourNumber = textsecure.storage.user.getNumber();
@ -53,8 +53,7 @@
storage.addBlockedNumber(number);
// Make sure we don't add duplicates
if (blockedNumbers.getNumber(number))
return;
if (blockedNumbers.getNumber(number)) return;
blockedNumbers.add({ number });
},

View File

@ -80,9 +80,16 @@
const contactCollection = new (Backbone.Collection.extend({
initialize() {
this.on('change:timestamp change:name change:number change:profileName', this.sort);
this.on(
'change:timestamp change:name change:number change:profileName',
this.sort
);
this.listenTo(conversations, 'add change:active_at change:friendRequestStatus', this.addActive);
this.listenTo(
conversations,
'add change:active_at change:friendRequestStatus',
this.addActive
);
this.listenTo(conversations, 'reset', () => this.reset([]));
this.collator = new Intl.Collator();
@ -192,7 +199,9 @@
return conversation;
};
conversation.initialPromise = create().then(() => conversation.updateProfileAvatar());
conversation.initialPromise = create().then(() =>
conversation.updateProfileAvatar()
);
return conversation;
},
@ -264,7 +273,9 @@
await Promise.all(promises);
// Remove any unused images
window.profileImages.removeImagesNotInArray(conversations.map(c => c.id));
window.profileImages.removeImagesNotInArray(
conversations.map(c => c.id)
);
window.log.info('ConversationController: done with initial fetch');
} catch (error) {

View File

@ -54,7 +54,7 @@ if (window.console) {
console._log = console.log;
console.log = log;
console._trace = console.trace;
console._debug = console.debug
console._debug = console.debug;
console._info = console.info;
console._warn = console.warn;
console._error = console.error;
@ -101,7 +101,7 @@ function fetch() {
}
const publish = debuglogs.upload;
const development = (window.getEnvironment() !== 'production');
const development = window.getEnvironment() !== 'production';
// A modern logging interface for the browser

View File

@ -89,5 +89,4 @@
return this.models.find(m => m.get('number') === number);
},
});
})();

View File

@ -11,7 +11,7 @@
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function () {
(function() {
'use strict';
window.Whisper = window.Whisper || {};
@ -35,6 +35,7 @@
upgradeMessageSchema,
loadAttachmentData,
getAbsoluteAttachmentPath,
// eslint-disable-next-line no-unused-vars
writeNewAttachmentData,
deleteAttachmentData,
} = window.Signal.Migrations;
@ -322,7 +323,9 @@
if (this.id !== pubKey) return;
// Go through our messages and find the one that we need to update
const messages = this.messageCollection.models.filter(m => m.get('sent_at') === timestamp);
const messages = this.messageCollection.models.filter(
m => m.get('sent_at') === timestamp
);
await Promise.all(messages.map(m => m.setCalculatingPoW()));
},
@ -371,8 +374,7 @@
// Get the pending friend requests that match the direction
// If no direction is supplied then return all pending friend requests
return messages.models.filter(m => {
if (m.get('friendStatus') !== 'pending')
return false;
if (m.get('friendStatus') !== 'pending') return false;
return direction === null || m.get('direction') === direction;
});
},
@ -461,7 +463,7 @@
if (!this.isPrivate()) {
throw new Error(
'You cannot verify a group conversation. ' +
'You must verify individual contacts.'
'You must verify individual contacts.'
);
}
@ -546,18 +548,27 @@
},
isPendingFriendRequest() {
const status = this.get('friendRequestStatus');
return status === FriendRequestStatusEnum.requestSent ||
return (
status === FriendRequestStatusEnum.requestSent ||
status === FriendRequestStatusEnum.requestReceived ||
status === FriendRequestStatusEnum.pendingSend;
status === FriendRequestStatusEnum.pendingSend
);
},
hasSentFriendRequest() {
return this.get('friendRequestStatus') === FriendRequestStatusEnum.requestSent;
return (
this.get('friendRequestStatus') === FriendRequestStatusEnum.requestSent
);
},
hasReceivedFriendRequest() {
return this.get('friendRequestStatus') === FriendRequestStatusEnum.requestReceived;
return (
this.get('friendRequestStatus') ===
FriendRequestStatusEnum.requestReceived
);
},
isFriend() {
return this.get('friendRequestStatus') === FriendRequestStatusEnum.friends;
return (
this.get('friendRequestStatus') === FriendRequestStatusEnum.friends
);
},
updateTextInputState() {
switch (this.get('friendRequestStatus')) {
@ -581,8 +592,7 @@
},
async setFriendRequestStatus(newStatus) {
// Ensure that the new status is a valid FriendStatusEnum value
if (!(newStatus in Object.values(FriendRequestStatusEnum)))
return;
if (!(newStatus in Object.values(FriendRequestStatusEnum))) return;
if (this.get('friendRequestStatus') !== newStatus) {
this.set({ friendRequestStatus: newStatus });
await window.Signal.Data.updateConversation(this.id, this.attributes, {
@ -609,7 +619,9 @@
);
},
async resetPendingSend() {
if (this.get('friendRequestStatus') === FriendRequestStatusEnum.pendingSend) {
if (
this.get('friendRequestStatus') === FriendRequestStatusEnum.pendingSend
) {
await this.setFriendRequestStatus(FriendRequestStatusEnum.none);
}
},
@ -644,8 +656,7 @@
},
async onFriendRequestTimeout() {
// Unset the timer
if (this.unlockTimer)
clearTimeout(this.unlockTimer);
if (this.unlockTimer) clearTimeout(this.unlockTimer);
this.unlockTimer = null;
@ -744,7 +755,7 @@
if (!this.isPrivate()) {
throw new Error(
'You cannot set a group conversation as trusted. ' +
'You must set individual contacts as trusted.'
'You must set individual contacts as trusted.'
);
}
@ -1054,9 +1065,9 @@
fileName: fileName || null,
thumbnail: thumbnail
? {
...(await loadAttachmentData(thumbnail)),
objectUrl: getAbsoluteAttachmentPath(thumbnail.path),
}
...(await loadAttachmentData(thumbnail)),
objectUrl: getAbsoluteAttachmentPath(thumbnail.path),
}
: null,
};
})
@ -1066,8 +1077,7 @@
async sendMessage(body, attachments, quote) {
// Input should be blocked if there is a pending friend request
if (this.isPendingFriendRequest())
return;
if (this.isPendingFriendRequest()) return;
this.clearTypingTimers();
@ -1107,7 +1117,9 @@
});
} else {
// Check if we have sent a friend request
const outgoingRequests = await this.getPendingFriendRequests('outgoing');
const outgoingRequests = await this.getPendingFriendRequests(
'outgoing'
);
if (outgoingRequests.length > 0) {
// Check if the requests have errored, if so then remove them
// and send the new request if possible
@ -1127,7 +1139,9 @@
// because one of them was sent successfully
if (friendRequestSent) return null;
}
await this.setFriendRequestStatus(FriendRequestStatusEnum.pendingSend);
await this.setFriendRequestStatus(
FriendRequestStatusEnum.pendingSend
);
// Send the friend request!
messageWithSchema = await upgradeMessageSchema({
@ -1374,8 +1388,8 @@
accessKey && sealedSender === SEALED_SENDER.ENABLED
? accessKey
: window.Signal.Crypto.arrayBufferToBase64(
window.Signal.Crypto.getRandomBytes(16)
),
window.Signal.Crypto.getRandomBytes(16)
),
},
};
},
@ -1532,8 +1546,7 @@
},
async setSessionResetStatus(newStatus) {
// Ensure that the new status is a valid SessionResetEnum value
if (!(newStatus in Object.values(SessionResetEnum)))
return;
if (!(newStatus in Object.values(SessionResetEnum))) return;
if (this.get('sessionResetStatus') !== newStatus) {
this.set({ sessionResetStatus: newStatus });
await window.Signal.Data.updateConversation(this.id, this.attributes, {
@ -1552,7 +1565,9 @@
},
isSessionResetReceived() {
return this.get('sessionResetStatus') === SessionResetEnum.request_received;
return (
this.get('sessionResetStatus') === SessionResetEnum.request_received
);
},
isSessionResetOngoing() {
@ -1584,7 +1599,10 @@
// send empty message to confirm that we have adopted the new session
await window.libloki.api.sendEmptyMessage(this.id);
}
await this.createAndStoreEndSessionMessage({ type: 'incoming', endSessionType: 'done' });
await this.createAndStoreEndSessionMessage({
type: 'incoming',
endSessionType: 'done',
});
await this.setSessionResetStatus(SessionResetEnum.none);
},
@ -1593,13 +1611,22 @@
// Only create a new message if *we* initiated the session reset.
// On the receiver side, the actual message containing the END_SESSION flag
// will ensure the "session reset" message will be added to their conversation.
if (this.get('sessionResetStatus') !== SessionResetEnum.request_received) {
if (
this.get('sessionResetStatus') !== SessionResetEnum.request_received
) {
await this.onSessionResetInitiated();
const message = await this.createAndStoreEndSessionMessage({ type: 'outgoing', endSessionType: 'ongoing' });
const message = await this.createAndStoreEndSessionMessage({
type: 'outgoing',
endSessionType: 'ongoing',
});
const options = this.getSendOptions();
await message.send(
this.wrapSend(
textsecure.messaging.resetSession(this.id, message.get('sent_at'), options)
textsecure.messaging.resetSession(
this.id,
message.get('sent_at'),
options
)
)
);
if (message.hasErrors()) {
@ -1699,7 +1726,7 @@
} else {
window.log.warn(
'Marked a message as read in the database, but ' +
'it was not in messageCollection.'
'it was not in messageCollection.'
);
}
@ -2062,7 +2089,8 @@
const color = this.getColor();
const avatar = this.get('avatar') || this.get('profileAvatar');
const url = avatar && avatar.path ? getAbsoluteAttachmentPath(avatar.path) : avatar;
const url =
avatar && avatar.path ? getAbsoluteAttachmentPath(avatar.path) : avatar;
if (url) {
return { url, color };
@ -2090,7 +2118,7 @@
notify(message) {
if (message.isFriendRequest()) {
if (this.hasSentFriendRequest())
return this.notifyFriendRequest(message.get('source'), 'accepted')
return this.notifyFriendRequest(message.get('source'), 'accepted');
return this.notifyFriendRequest(message.get('source'), 'requested');
}
if (!message.isIncoming()) return Promise.resolve();
@ -2126,8 +2154,7 @@
// Notification for friend request received
async notifyFriendRequest(source, type) {
// Data validation
if (!source)
throw new Error('Invalid source');
if (!source) throw new Error('Invalid source');
if (!['accepted', 'requested'].includes(type))
throw new Error('Type must be accepted or requested.');

View File

@ -12,7 +12,7 @@
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function () {
(function() {
'use strict';
window.Whisper = window.Whisper || {};
@ -202,8 +202,7 @@
getNotificationText() {
const description = this.getDescription();
if (description) {
if (this.isFriendRequest())
return `Friend Request: ${description}`;
if (this.isFriendRequest()) return `Friend Request: ${description}`;
return description;
}
if (this.get('attachments').length > 0) {
@ -364,7 +363,7 @@
onBlockUser,
onUnblockUser,
onRetrySend,
}
};
},
findContact(phoneNumber) {
return ConversationController.get(phoneNumber);
@ -444,7 +443,8 @@
// Handle friend request statuses
const isFriendRequest = this.isFriendRequest();
const isOutgoingFriendRequest = isFriendRequest && this.get('direction') === 'outgoing';
const isOutgoingFriendRequest =
isFriendRequest && this.get('direction') === 'outgoing';
const isOutgoing = this.isOutgoing() || isOutgoingFriendRequest;
// Only return the status on outgoing messages
@ -562,8 +562,8 @@
contact.number && contact.number[0] && contact.number[0].value;
const onSendMessage = firstNumber
? () => {
this.trigger('open-conversation', firstNumber);
}
this.trigger('open-conversation', firstNumber);
}
: null;
const onClick = async () => {
// First let's be sure that the signal account check is complete.
@ -602,8 +602,8 @@
!path && !objectUrl
? null
: Object.assign({}, attachment.thumbnail || {}, {
objectUrl: path || objectUrl,
});
objectUrl: path || objectUrl,
});
return Object.assign({}, attachment, {
isVoiceMessage: Signal.Types.Attachment.isVoiceMessage(attachment),
@ -670,15 +670,15 @@
url: getAbsoluteAttachmentPath(path),
screenshot: screenshot
? {
...screenshot,
url: getAbsoluteAttachmentPath(screenshot.path),
}
...screenshot,
url: getAbsoluteAttachmentPath(screenshot.path),
}
: null,
thumbnail: thumbnail
? {
...thumbnail,
url: getAbsoluteAttachmentPath(thumbnail.path),
}
...thumbnail,
url: getAbsoluteAttachmentPath(thumbnail.path),
}
: null,
};
},
@ -1143,7 +1143,7 @@
errors = errors.concat(this.get('errors') || []);
if (this.isEndSession) {
this.set({ endSessionType: 'failed'});
this.set({ endSessionType: 'failed' });
}
this.set({ errors });
@ -1407,7 +1407,9 @@
autoAccept = true;
message.set({ friendStatus: 'accepted' });
await conversation.onFriendRequestAccepted();
window.libloki.api.sendFriendRequestAccepted(message.get('source'));
window.libloki.api.sendFriendRequestAccepted(
message.get('source')
);
} else if (!conversation.isFriend()) {
await conversation.onFriendRequestReceived();
}
@ -1445,7 +1447,7 @@
if (previousUnread !== message.get('unread')) {
window.log.warn(
'Caught race condition on new message read state! ' +
'Manually starting timers.'
'Manually starting timers.'
);
// We call markRead() even though the message is already
// marked read because we need to start expiration
@ -1464,8 +1466,7 @@
// Need to do this here because the conversation has already changed states
if (autoAccept)
await conversation.notifyFriendRequest(source, 'accepted');
else
await conversation.notify(message);
else await conversation.notify(message);
}
confirm();

View File

@ -14,9 +14,9 @@
storage.getLocalProfile = () => {
const profile = storage.get(PROFILE_ID, null);
return profile;
}
};
storage.setProfileName = async (newName) => {
storage.setProfileName = async newName => {
// Update our profiles accordingly'
const trimmed = newName && newName.trim();
@ -29,13 +29,13 @@
} else {
newProfile.name = {
displayName: trimmed,
}
};
}
await storage.saveLocalProfile(newProfile);
}
};
storage.saveLocalProfile = async (profile) => {
storage.saveLocalProfile = async profile => {
const storedProfile = storage.get(PROFILE_ID, null);
// Only store the profile if we have a different object
@ -45,10 +45,10 @@
window.log.info('saving local profile ', profile);
await storage.put(PROFILE_ID, profile);
}
};
storage.removeLocalProfile = async () => {
window.log.info('removing local profile');
await storage.remove(PROFILE_ID);
}
};
})();

View File

@ -571,7 +571,6 @@ async function removeAllContactSignedPreKeys() {
await channels.removeAllContactSignedPreKeys();
}
// Items
const ITEM_KEYS = {
@ -874,9 +873,7 @@ async function getMessagesByConversation(
return new MessageCollection(messages);
}
async function getSeenMessagesByHashList(
hashes
) {
async function getSeenMessagesByHashList(hashes) {
const seenMessages = await channels.getSeenMessagesByHashList(hashes);
return seenMessages;
}

View File

@ -4,7 +4,6 @@ const fetch = require('node-fetch');
const is = require('@sindresorhus/is');
class LokiServer {
constructor({ urls }) {
this.nodes = [];
urls.forEach(url => {
@ -29,7 +28,14 @@ class LokiServer {
timestamp: messageTimeStamp,
});
const development = window.getEnvironment() !== 'production';
nonce = await callWorker('calcPoW', timestamp, ttl, pubKey, data64, development);
nonce = await callWorker(
'calcPoW',
timestamp,
ttl,
pubKey,
data64,
development
);
} catch (err) {
// Something went horribly wrong
// TODO: Handle gracefully
@ -134,7 +140,11 @@ class LokiServer {
return result;
}
log.error(options.type, options.url, response.status, 'Error');
throw HTTPError('retrieveMessages: error response', response.status, result);
throw HTTPError(
'retrieveMessages: error response',
response.status,
result
);
}
}

View File

@ -9,7 +9,7 @@ class TimedOutError extends Error {
if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, this.constructor);
} else {
this.stack = (new Error(message)).stack;
this.stack = new Error(message).stack;
}
}
}
@ -46,7 +46,7 @@ class WorkerInterface {
};
}
_makeJob (fnName) {
_makeJob(fnName) {
this._jobCounter += 1;
const id = this._jobCounter;
@ -59,7 +59,7 @@ class WorkerInterface {
};
return id;
};
}
_updateJob(id, data) {
const { resolve, reject } = data;
@ -85,7 +85,7 @@ class WorkerInterface {
return reject(error);
},
};
};
}
_removeJob(id) {
if (this._DEBUG) {
@ -97,7 +97,7 @@ class WorkerInterface {
_getJob(id) {
return this._jobs[id];
};
}
callWorker(fnName, ...args) {
const jobId = this._makeJob(fnName);
@ -112,11 +112,14 @@ class WorkerInterface {
});
setTimeout(
() => reject(new TimedOutError(`Worker job ${jobId} (${fnName}) timed out`)),
() =>
reject(
new TimedOutError(`Worker job ${jobId} (${fnName}) timed out`)
),
this.timeout
);
});
};
}
}
module.exports = {

View File

@ -87,14 +87,18 @@
let iconUrl;
// The number of notifications excluding friend request
const messagesNotificationCount = this.models.filter(n => !n.get('isFriendRequest')).length;
const messagesNotificationCount = this.models.filter(
n => !n.get('isFriendRequest')
).length;
// NOTE: i18n has more complex rules for pluralization than just
// distinguishing between zero (0) and other (non-zero),
// e.g. Russian:
// http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html
const newMessageCountLabel = `${messagesNotificationCount} ${
messagesNotificationCount === 1 ? i18n('newMessage') : i18n('newMessages')
messagesNotificationCount === 1
? i18n('newMessage')
: i18n('newMessages')
}`;
const last = this.last().toJSON();

View File

@ -1,5 +1,14 @@
/* global
dcodeIO, Backbone, _, libsignal, textsecure, ConversationController, stringObject, BlockedNumberController */
/*
global
dcodeIO,
Backbone,
_,
libsignal,
textsecure,
ConversationController,
stringObject,
BlockedNumberController
*/
/* eslint-disable no-proto */
@ -187,7 +196,10 @@
const key = await window.Signal.Data.getPreKeyByRecipient(contactPubKey);
if (key) {
window.log.info('Successfully fetched prekey for recipient:', contactPubKey);
window.log.info(
'Successfully fetched prekey for recipient:',
contactPubKey
);
return {
pubKey: key.publicKey,
privKey: key.privateKey,

View File

@ -41,6 +41,24 @@ function stringToArrayBufferBase64(string) {
function arrayBufferToStringBase64(arrayBuffer) {
return dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64');
}
function calcPoW(timestamp, ttl, pubKey, data, development, nonceTrials = undefined, increment = 1, startNonce = 0) {
return pow.calcPoW(timestamp, ttl, pubKey, data, development, nonceTrials, increment, startNonce);
function calcPoW(
timestamp,
ttl,
pubKey,
data,
development,
nonceTrials = undefined,
increment = 1,
startNonce = 0
) {
return pow.calcPoW(
timestamp,
ttl,
pubKey,
data,
development,
nonceTrials,
increment,
startNonce
);
}

View File

@ -7,86 +7,88 @@
// eslint-disable-next-line func-names
(function() {
'use strict';
'use strict';
window.Whisper = window.Whisper || {};
window.Whisper = window.Whisper || {};
Whisper.BlockedNumberView = Whisper.View.extend({
templateName: 'blockedUserSettings',
className: 'blockedUserSettings',
events: {
'click .unblock-button': 'onUnblock',
},
initialize() {
storage.onready(() => {
this.collection = BlockedNumberController.getAll();
this.listView = new Whisper.BlockedNumberListView({
collection: this.collection,
});
this.listView.render();
this.blockedUserSettings = this.$('.blocked-user-settings');
this.blockedUserSettings.prepend(this.listView.el);
Whisper.BlockedNumberView = Whisper.View.extend({
templateName: 'blockedUserSettings',
className: 'blockedUserSettings',
events: {
'click .unblock-button': 'onUnblock',
},
initialize() {
storage.onready(() => {
this.collection = BlockedNumberController.getAll();
this.listView = new Whisper.BlockedNumberListView({
collection: this.collection,
});
},
render_attributes() {
return {
blockedHeader: i18n('settingsUnblockHeader'),
unblockMessage: i18n('unblockUser'),
};
},
onUnblock() {
const number = this.$('select option:selected').val();
if (!number) return;
if (BlockedNumberController.isBlocked(number)) {
BlockedNumberController.unblock(number);
window.onUnblockNumber(number);
this.listView.collection.remove(this.listView.collection.where({ number }));
}
},
});
this.listView.render();
this.blockedUserSettings = this.$('.blocked-user-settings');
this.blockedUserSettings.prepend(this.listView.el);
});
},
render_attributes() {
return {
blockedHeader: i18n('settingsUnblockHeader'),
unblockMessage: i18n('unblockUser'),
};
},
onUnblock() {
const number = this.$('select option:selected').val();
if (!number) return;
if (BlockedNumberController.isBlocked(number)) {
BlockedNumberController.unblock(number);
window.onUnblockNumber(number);
this.listView.collection.remove(
this.listView.collection.where({ number })
);
}
},
});
Whisper.BlockedNumberListView = Whisper.View.extend({
tagName: 'select',
initialize(options) {
this.options = options || {};
this.listenTo(this.collection, 'add', this.addOne);
this.listenTo(this.collection, 'reset', this.addAll);
this.listenTo(this.collection, 'remove', this.addAll);
},
addOne(model) {
const number = model.get('number');
if (number) {
this.$el.append(`<option value="${number}">${this.truncate(number, 25)}</option>`);
}
},
addAll() {
this.$el.html('');
this.collection.each(this.addOne, this);
},
truncate(string, limit) {
// Make sure an element and number of items to truncate is provided
if (!string || !limit) return string;
tagName: 'select',
initialize(options) {
this.options = options || {};
this.listenTo(this.collection, 'add', this.addOne);
this.listenTo(this.collection, 'reset', this.addAll);
this.listenTo(this.collection, 'remove', this.addAll);
},
addOne(model) {
const number = model.get('number');
if (number) {
this.$el.append(
`<option value="${number}">${this.truncate(number, 25)}</option>`
);
}
},
addAll() {
this.$el.html('');
this.collection.each(this.addOne, this);
},
truncate(string, limit) {
// Make sure an element and number of items to truncate is provided
if (!string || !limit) return string;
// Get the inner content of the element
let content = string.trim();
// Get the inner content of the element
let content = string.trim();
// Convert the content into an array of words
// Remove any words above the limit
content = content.slice(0, limit);
// Convert the content into an array of words
// Remove any words above the limit
content = content.slice(0, limit);
// Convert the array of words back into a string
// If there's content to add after it, add it
if (string.length > limit)
content = `${content}...`;
// Convert the array of words back into a string
// If there's content to add after it, add it
if (string.length > limit) content = `${content}...`;
return content;
},
render() {
this.addAll();
return this;
},
});
})();
return content;
},
render() {
this.addAll();
return this;
},
});
})();

View File

@ -42,8 +42,7 @@
props,
});
const update = () =>
this.childView.update(this.getProps());
const update = () => this.childView.update(this.getProps());
this.listenTo(this.model, 'change', update);
@ -54,14 +53,16 @@
});
// list of conversations, showing user/group and last message sent
Whisper.ConversationContactListItemView = Whisper.ConversationListItemView.extend({
getProps() {
// We don't want to show a timestamp or a message
const props = this.model.getPropsForListItem();
delete props.lastMessage;
delete props.lastUpdated;
Whisper.ConversationContactListItemView = Whisper.ConversationListItemView.extend(
{
getProps() {
// We don't want to show a timestamp or a message
const props = this.model.getPropsForListItem();
delete props.lastMessage;
delete props.lastUpdated;
return props;
},
});
return props;
},
}
);
})();

View File

@ -21,7 +21,10 @@
},
render_attributes() {
// Show the appropriate message based on model validity
const message = this.model && this.model.isValid() ? i18n('startConversation') : i18n('invalidNumberError');
const message =
this.model && this.model.isValid()
? i18n('startConversation')
: i18n('invalidNumberError');
return {
number: message,
title: this.model.getNumber(),
@ -70,7 +73,6 @@
filterContacts() {
const query = this.$input.val().trim();
if (query.length) {
// Update the contact model
this.new_contact_view.model.set('id', query);
this.new_contact_view.render().$el.hide();

View File

@ -13,7 +13,7 @@
*/
// eslint-disable-next-line func-names
(function () {
(function() {
'use strict';
window.Whisper = window.Whisper || {};
@ -321,8 +321,7 @@
},
onChangePlaceholder(type) {
if (!this.$messageField)
return;
if (!this.$messageField) return;
let placeholder;
switch (type) {
case 'friend-request':
@ -1623,10 +1622,15 @@
}
const input = this.$messageField;
const inputMessage = window.Signal.Emoji.replaceColons(input.val()).trim();
const inputMessage = window.Signal.Emoji.replaceColons(
input.val()
).trim();
// Limit the message to 2000 characters
const message = inputMessage.substring(0, Math.min(2000, inputMessage.length));
const message = inputMessage.substring(
0,
Math.min(2000, inputMessage.length)
);
try {
if (!message.length && !this.fileInput.hasFiles()) {

View File

@ -150,11 +150,7 @@
);
// Listen to any conversation remove
this.listenTo(
inboxCollection,
'remove',
this.closeConversation
);
this.listenTo(inboxCollection, 'remove', this.closeConversation);
// Friends
const contactCollection = getContactCollection();
@ -335,8 +331,14 @@
},
updateInboxSectionUnread() {
const $section = this.$('.section-conversations-unread-counter');
const models = (this.inboxListView.collection && this.inboxListView.collection.models) || [];
const unreadCount = models.reduce((count, m) => count + Math.max(0, m.get('unreadCount')), 0);
const models =
(this.inboxListView.collection &&
this.inboxListView.collection.models) ||
[];
const unreadCount = models.reduce(
(count, m) => count + Math.max(0, m.get('unreadCount')),
0
);
$section.text(unreadCount);
if (unreadCount > 0) {
$section.show();
@ -350,7 +352,7 @@
getMainHeaderItems() {
return [
this._mainHeaderItem('copyPublicKey', () => {
const ourNumber = textsecure.storage.user.getNumber();
const ourNumber = textsecure.storage.user.getNumber();
clipboard.writeText(ourNumber);
this.showToastMessageInGutter(i18n('copiedPublicKey'));
@ -367,17 +369,18 @@
const hasPassword = await Signal.Data.getPasswordHash();
const items = this.getMainHeaderItems();
const showPasswordDialog = (type, resolve) => Whisper.events.trigger('showPasswordDialog', {
type,
resolve,
});
const showPasswordDialog = (type, resolve) =>
Whisper.events.trigger('showPasswordDialog', {
type,
resolve,
});
const passwordItem = (textKey, type) => this._mainHeaderItem(
textKey,
() => showPasswordDialog(type, () => {
this.showToastMessageInGutter(i18n(`${textKey}Success`));
})
);
const passwordItem = (textKey, type) =>
this._mainHeaderItem(textKey, () =>
showPasswordDialog(type, () => {
this.showToastMessageInGutter(i18n(`${textKey}Success`));
})
);
if (hasPassword) {
items.push(
@ -385,9 +388,7 @@
passwordItem('removePassword', 'remove')
);
} else {
items.push(
passwordItem('setPassword', 'set')
);
items.push(passwordItem('setPassword', 'set'));
}
this.mainHeaderView.updateItems(items);

View File

@ -2,59 +2,61 @@
// eslint-disable-next-line func-names
(function() {
'use strict';
'use strict';
window.Whisper = window.Whisper || {};
window.Whisper = window.Whisper || {};
Whisper.MainHeaderView = Whisper.View.extend({
templateName: 'main-header-placeholder',
events: {
'click .main-header-title-wrapper': 'onClick',
'click .edit-name': 'onEditProfile',
'click .copy-key': 'onCopyKey',
},
initialize(options) {
this.ourNumber = textsecure.storage.user.getNumber();
const me = ConversationController.getOrCreate(this.ourNumber, 'private');
Whisper.MainHeaderView = Whisper.View.extend({
templateName: 'main-header-placeholder',
events: {
'click .main-header-title-wrapper': 'onClick',
'click .edit-name': 'onEditProfile',
'click .copy-key': 'onCopyKey',
},
initialize(options) {
this.ourNumber = textsecure.storage.user.getNumber();
const me = ConversationController.getOrCreate(this.ourNumber, 'private');
this.mainHeaderView = new Whisper.ReactWrapperView({
className: 'main-header-wrapper',
Component: Signal.Components.MainHeader,
props: me.format(),
});
const update = () => this.mainHeaderView.update(me.format());
this.listenTo(me, 'change', update);
this.mainHeaderView = new Whisper.ReactWrapperView({
className: 'main-header-wrapper',
Component: Signal.Components.MainHeader,
props: me.format(),
});
const update = () => this.mainHeaderView.update(me.format());
this.listenTo(me, 'change', update);
this.render();
this.$('.main-header-title-wrapper').prepend(this.mainHeaderView.el);
this.render();
this.$('.main-header-title-wrapper').prepend(this.mainHeaderView.el);
this.$toggle = this.$('.main-header-content-toggle');
this.$content = this.$('.main-header-content-wrapper');
this.$content.hide();
this.$toggle = this.$('.main-header-content-toggle');
this.$content = this.$('.main-header-content-wrapper');
this.$content.hide();
this.updateItems(options.items);
},
updateItems(items) {
this.$content.html('');
(items || []).forEach(item => {
// Add the item
this.$content.append(`<div role='button' id='${item.id}'>${item.text}</div>`);
this.updateItems(options.items);
},
updateItems(items) {
this.$content.html('');
(items || []).forEach(item => {
// Add the item
this.$content.append(
`<div role='button' id='${item.id}'>${item.text}</div>`
);
// Register its callback
if (item.onClick) {
this.$(`#${item.id}`).click(item.onClick);
}
});
},
render_attributes() {
return {
items: this.items,
};
},
onClick() {
// Toggle section visibility
this.$content.slideToggle('fast');
this.$toggle.toggleClass('main-header-content-toggle-visible');
},
});
})();
// Register its callback
if (item.onClick) {
this.$(`#${item.id}`).click(item.onClick);
}
});
},
render_attributes() {
return {
items: this.items,
};
},
onClick() {
// Toggle section visibility
this.$content.slideToggle('fast');
this.$toggle.toggleClass('main-header-content-toggle-visible');
},
});
})();

View File

@ -50,10 +50,13 @@
const password = this.$('#password').val();
const passwordConfirmation = this.$('#password-confirmation').val();
const pairValidation = this.validatePasswordPair(password, passwordConfirmation);
const pairValidation = this.validatePasswordPair(
password,
passwordConfirmation
);
const hashValidation = await this.validatePasswordHash(password);
return (pairValidation || hashValidation);
return pairValidation || hashValidation;
},
async validatePasswordHash(password) {
// Check if the password matches the hash we have stored
@ -65,15 +68,20 @@
},
validatePasswordPair(password, passwordConfirmation) {
if (!_.isEmpty(password)) {
// Check if the password is first valid
const passwordValidation = passwordUtil.validatePassword(password, i18n);
const passwordValidation = passwordUtil.validatePassword(
password,
i18n
);
if (passwordValidation) {
return passwordValidation;
}
// Check if the confirmation password is the same
if (!passwordConfirmation || password.trim() !== passwordConfirmation.trim()) {
if (
!passwordConfirmation ||
password.trim() !== passwordConfirmation.trim()
) {
return i18n('passwordsDoNotMatch');
}
}
@ -161,8 +169,11 @@
const oldPassword = this.$('#old-password').val();
// Validate the old password
if (!_.isEmpty(oldPassword) ) {
const oldPasswordValidation = passwordUtil.validatePassword(oldPassword, i18n);
if (!_.isEmpty(oldPassword)) {
const oldPasswordValidation = passwordUtil.validatePassword(
oldPassword,
i18n
);
if (oldPasswordValidation) {
return oldPasswordValidation;
}
@ -173,7 +184,10 @@
const password = this.$('#new-password').val();
const passwordConfirmation = this.$('#new-password-confirmation').val();
const pairValidation = this.validatePasswordPair(password, passwordConfirmation);
const pairValidation = this.validatePasswordPair(
password,
passwordConfirmation
);
const hashValidation = await this.validatePasswordHash(oldPassword);
return pairValidation || hashValidation;
@ -189,7 +203,6 @@
});
Whisper.getPasswordDialogView = (type, resolve, reject) => {
// This is a differently styled dialog
if (type === 'change') {
return new ChangePasswordDialogView({
@ -201,7 +214,8 @@
}
// Set and Remove is basically the same UI
const title = type === 'remove' ? i18n('removePassword') : i18n('setPassword');
const title =
type === 'remove' ? i18n('removePassword') : i18n('setPassword');
const okTitle = type === 'remove' ? i18n('remove') : i18n('set');
return new PasswordDialogView({
title,

View File

@ -38,5 +38,4 @@
this.$('.error').text(string);
},
});
})();

View File

@ -1,4 +1,4 @@
/* global Whisper, $, getAccountManager, textsecure, i18n, passwordUtil, ConversationController */
/* global Whisper, $, getAccountManager, textsecure, i18n, passwordUtil */
/* eslint-disable more/no-then */
@ -102,7 +102,7 @@
async onGenerateMnemonic() {
const language = this.$('#mnemonic-display-language').val();
const mnemonic = await this.accountManager.generateMnemonic(language);
this.$('#mnemonic-display').text(mnemonic)
this.$('#mnemonic-display').text(mnemonic);
},
onCopyMnemonic() {
window.clipboard.writeText(this.$('#mnemonic-display').text());
@ -178,8 +178,12 @@
$target.toggleClass('section-toggle-visible');
// Hide the other sections
this.$('.section-toggle').not($target).removeClass('section-toggle-visible')
this.$('.section-content').not($next).slideUp('fast');
this.$('.section-toggle')
.not($target)
.removeClass('section-toggle-visible');
this.$('.section-content')
.not($next)
.slideUp('fast');
},
onPasswordChange() {
const input = this.$passwordInput.val();
@ -193,7 +197,9 @@
},
validatePassword() {
const input = this.trim(this.$passwordInput.val());
const confirmationInput = this.trim(this.$passwordConfirmationInput.val());
const confirmationInput = this.trim(
this.$passwordConfirmationInput.val()
);
// If user hasn't set a value then skip
if (!input && !confirmationInput) {
@ -206,7 +212,7 @@
}
if (input !== confirmationInput) {
return 'Password don\'t match';
return "Password don't match";
}
return null;
@ -222,7 +228,6 @@
this.$passwordInputError.text(passwordValidation);
this.$passwordInputError.show();
} else {
this.$passwordInput.removeClass('error-input');
this.$passwordConfirmationInput.removeClass('error-input');
@ -232,7 +237,9 @@
// Show green box around inputs that match
const input = this.trim(this.$passwordInput.val());
const confirmationInput = this.trim(this.$passwordConfirmationInput.val());
const confirmationInput = this.trim(
this.$passwordConfirmationInput.val()
);
if (input && input === confirmationInput) {
this.$passwordInput.addClass('match-input');
this.$passwordConfirmationInput.addClass('match-input');

View File

@ -36,5 +36,5 @@
render_attributes() {
return { toastMessage: this.message };
},
})
});
})();

View File

@ -1,7 +1,7 @@
/* global window, textsecure, log */
// eslint-disable-next-line func-names
(function () {
(function() {
window.libloki = window.libloki || {};
async function sendFriendRequestAccepted(pubKey) {

View File

@ -1,15 +1,14 @@
/* global window, libsignal, textsecure, StringView */
// eslint-disable-next-line func-names
(function () {
(function() {
window.libloki = window.libloki || {};
class FallBackDecryptionError extends Error { }
class FallBackDecryptionError extends Error {}
const IV_LENGTH = 16;
class FallBackSessionCipher {
constructor(address) {
this.identityKeyString = address.getName();
this.pubKey = StringView.hexToArrayBuffer(address.getName());
@ -18,10 +17,19 @@
async encrypt(plaintext) {
const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
const myPrivateKey = myKeyPair.privKey;
const symmetricKey = libsignal.Curve.calculateAgreement(this.pubKey, myPrivateKey);
const symmetricKey = libsignal.Curve.calculateAgreement(
this.pubKey,
myPrivateKey
);
const iv = libsignal.crypto.getRandomBytes(IV_LENGTH);
const ciphertext = await libsignal.crypto.encrypt(symmetricKey, plaintext, iv);
const ivAndCiphertext = new Uint8Array(iv.byteLength + ciphertext.byteLength);
const ciphertext = await libsignal.crypto.encrypt(
symmetricKey,
plaintext,
iv
);
const ivAndCiphertext = new Uint8Array(
iv.byteLength + ciphertext.byteLength
);
ivAndCiphertext.set(new Uint8Array(iv));
ivAndCiphertext.set(new Uint8Array(ciphertext), iv.byteLength);
return {
@ -36,12 +44,18 @@
const cipherText = ivAndCiphertext.slice(IV_LENGTH);
const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
const myPrivateKey = myKeyPair.privKey;
const symmetricKey = libsignal.Curve.calculateAgreement(this.pubKey, myPrivateKey);
const symmetricKey = libsignal.Curve.calculateAgreement(
this.pubKey,
myPrivateKey
);
try {
return await libsignal.crypto.decrypt(symmetricKey, cipherText, iv);
}
catch (e) {
throw new FallBackDecryptionError(`Could not decrypt message from ${this.identityKeyString} using FallBack encryption.`);
} catch (e) {
throw new FallBackDecryptionError(
`Could not decrypt message from ${
this.identityKeyString
} using FallBack encryption.`
);
}
}
}

View File

@ -15,7 +15,7 @@ const pow = {
newNonce[idx] = sum % 256;
increment = Math.floor(sum / 256);
idx -= 1;
} while(increment > 0 && idx >= 0);
} while (increment > 0 && idx >= 0);
return newNonce;
},
@ -36,10 +36,7 @@ const pow = {
n = NONCE_LEN - (idx + 1);
// 256 ** n is the value of one bit in arr[idx], modulus to carry over
// (bigInt / 256**n) % 256;
const denominator = JSBI.exponentiate(
JSBI.BigInt('256'),
JSBI.BigInt(n)
);
const denominator = JSBI.exponentiate(JSBI.BigInt('256'), JSBI.BigInt(n));
const fraction = JSBI.divide(bigInt, denominator);
const uint8Val = JSBI.remainder(fraction, JSBI.BigInt(256));
arr[idx] = JSBI.toNumber(uint8Val);
@ -60,7 +57,16 @@ const pow = {
},
// Return nonce that hashes together with payload lower than the target
async calcPoW(timestamp, ttl, pubKey, data, development = false, _nonceTrials = null, increment = 1, startNonce = 0) {
async calcPoW(
timestamp,
ttl,
pubKey,
data,
development = false,
_nonceTrials = null,
increment = 1,
startNonce = 0
) {
const payload = new Uint8Array(
dcodeIO.ByteBuffer.wrap(
timestamp.toString() + ttl.toString() + pubKey + data,
@ -68,7 +74,8 @@ const pow = {
).toArrayBuffer()
);
const nonceTrials = _nonceTrials || (development ? DEV_NONCE_TRIALS : PROD_NONCE_TRIALS);
const nonceTrials =
_nonceTrials || (development ? DEV_NONCE_TRIALS : PROD_NONCE_TRIALS);
const target = pow.calcTarget(ttl, payload.length, nonceTrials);
let nonce = new Uint8Array(NONCE_LEN);
@ -98,25 +105,16 @@ const pow = {
calcTarget(ttl, payloadLen, nonceTrials = PROD_NONCE_TRIALS) {
// payloadLength + NONCE_LEN
const totalLen = JSBI.add(
JSBI.BigInt(payloadLen),
JSBI.BigInt(NONCE_LEN)
);
const totalLen = JSBI.add(JSBI.BigInt(payloadLen), JSBI.BigInt(NONCE_LEN));
// ttl * totalLen
const ttlMult = JSBI.multiply(
JSBI.BigInt(ttl),
JSBI.BigInt(totalLen)
);
const ttlMult = JSBI.multiply(JSBI.BigInt(ttl), JSBI.BigInt(totalLen));
// 2^16 - 1
const two16 = JSBI.subtract(
JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(16)), // 2^16
JSBI.BigInt(1)
);
// ttlMult / two16
const innerFrac = JSBI.divide(
ttlMult,
two16
);
const innerFrac = JSBI.divide(ttlMult, two16);
// totalLen + innerFrac
const lenPlusInnerFrac = JSBI.add(totalLen, innerFrac);
// nonceTrials * lenPlusInnerFrac

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
/* global window */
// eslint-disable-next-line func-names
(function () {
(function() {
window.libloki = window.libloki || {};
function consolidateLists(lists, threshold = 1){
function consolidateLists(lists, threshold = 1) {
if (typeof threshold !== 'number') {
throw Error('Provided threshold is not a number');
}

View File

@ -1,7 +1,7 @@
/* global window, libsignal, textsecure */
// eslint-disable-next-line func-names
(function () {
(function() {
window.libloki = window.libloki || {};
async function getPreKeyBundleForContact(pubKey) {
@ -107,14 +107,16 @@
};
store.loadContactPreKey = async pubKey => {
const preKey = await window.Signal.Data.getContactPreKeyByIdentityKey(pubKey);
const preKey = await window.Signal.Data.getContactPreKeyByIdentityKey(
pubKey
);
if (preKey) {
return {
id: preKey.id,
keyId: preKey.keyId,
publicKey: preKey.publicKey,
identityKeyString: preKey.identityKeyString,
}
};
}
window.log.warn('Failed to fetch contact prekey:', pubKey);
@ -123,7 +125,10 @@
store.loadContactPreKeys = async filters => {
const { keyId, identityKeyString } = filters;
const keys = await window.Signal.Data.getContactPreKeys(keyId, identityKeyString);
const keys = await window.Signal.Data.getContactPreKeys(
keyId,
identityKeyString
);
if (keys) {
return keys.map(preKey => ({
id: preKey.id,
@ -133,10 +138,7 @@
}));
}
window.log.warn(
'Failed to fetch signed prekey with filters',
filters
);
window.log.warn('Failed to fetch signed prekey with filters', filters);
return undefined;
};
@ -162,7 +164,9 @@
};
store.loadContactSignedPreKey = async pubKey => {
const preKey = await window.Signal.Data.getContactSignedPreKeyByIdentityKey(pubKey);
const preKey = await window.Signal.Data.getContactSignedPreKeyByIdentityKey(
pubKey
);
if (preKey) {
return {
id: preKey.id,
@ -180,7 +184,10 @@
store.loadContactSignedPreKeys = async filters => {
const { keyId, identityKeyString } = filters;
const keys = await window.Signal.Data.getContactSignedPreKeys(keyId, identityKeyString);
const keys = await window.Signal.Data.getContactSignedPreKeys(
keyId,
identityKeyString
);
if (keys) {
return keys.map(preKey => ({
id: preKey.id,

View File

@ -15,21 +15,21 @@ describe('Crypto', () => {
store.put('identityKey', identityKey);
const key = libsignal.crypto.getRandomBytes(32);
const pubKeyString = StringView.arrayBufferToHex(key);
address = new libsignal.SignalProtocolAddress(
pubKeyString,
1
);
address = new libsignal.SignalProtocolAddress(pubKeyString, 1);
fallbackCipher = new libloki.crypto.FallBackSessionCipher(address);
});
it('should encrypt fallback cipher messages as friend requests', async () => {
const buffer = new ArrayBuffer(10);
const { type } = await fallbackCipher.encrypt(buffer);
assert.strictEqual(type, textsecure.protobuf.Envelope.Type.FRIEND_REQUEST);
assert.strictEqual(
type,
textsecure.protobuf.Envelope.Type.FRIEND_REQUEST
);
});
it('should encrypt and then decrypt a message with the same result', async () => {
const arr = new Uint8Array([1,2,3,4,5]);
const arr = new Uint8Array([1, 2, 3, 4, 5]);
const { body } = await fallbackCipher.encrypt(arr.buffer);
const result = await fallbackCipher.decrypt(body);
assert.deepEqual(result, arr.buffer);

View File

@ -1,13 +1,17 @@
/* global dcodeIO, Plotly */
let jobId = 0;
let currentTrace = 0
let currentTrace = 0;
let plotlyDiv;
const workers = [];
async function run(messageLength, numWorkers = 1, nonceTrials = 100, ttl = 72) {
const timestamp = Math.floor(Date.now() / 1000);
const pubKey = '05ec8635a07a13743516c7c9b3412f3e8252efb7fcaf67eb1615ffba62bebc6802';
const pubKey =
'05ec8635a07a13743516c7c9b3412f3e8252efb7fcaf67eb1615ffba62bebc6802';
const message = randomString(messageLength);
const messageBuffer = dcodeIO.ByteBuffer.wrap(message, 'utf8').toArrayBuffer();
const messageBuffer = dcodeIO.ByteBuffer.wrap(
message,
'utf8'
).toArrayBuffer();
const data = dcodeIO.ByteBuffer.wrap(messageBuffer).toString('base64');
const promises = [];
const t0 = performance.now();
@ -17,9 +21,20 @@ async function run(messageLength, numWorkers = 1, nonceTrials = 100, ttl = 72) {
jobId += 1;
const increment = numWorkers;
const index = w;
worker.postMessage([jobId, 'calcPoW', timestamp, ttl * 60 * 60, pubKey, data, false, nonceTrials, increment, index]);
worker.postMessage([
jobId,
'calcPoW',
timestamp,
ttl * 60 * 60,
pubKey,
data,
false,
nonceTrials,
increment,
index,
]);
const p = new Promise(resolve => {
worker.onmessage = (nonce) => {
worker.onmessage = nonce => {
resolve(nonce);
};
});
@ -33,9 +48,15 @@ async function run(messageLength, numWorkers = 1, nonceTrials = 100, ttl = 72) {
workers.forEach(worker => worker.terminate());
}
async function runPoW({ iteration, nonceTrials, numWorkers, messageLength = 50, ttl = 72 }) {
async function runPoW({
iteration,
nonceTrials,
numWorkers,
messageLength = 50,
ttl = 72,
}) {
const name = `W:${numWorkers} - NT: ${nonceTrials} - L:${messageLength} - TTL:${ttl}`;
Plotly.addTraces(plotlyDiv ,{
Plotly.addTraces(plotlyDiv, {
y: [],
type: 'box',
boxpoints: 'all',
@ -46,84 +67,148 @@ async function runPoW({ iteration, nonceTrials, numWorkers, messageLength = 50,
await run(messageLength, numWorkers, nonceTrials, ttl);
}
currentTrace += 1;
// eslint-disable-next-line
console.log(`done for ${name}`);
}
function randomString(length) {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const possible =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < length; i += 1)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
function addPoint(duration) {
Plotly.extendTraces(plotlyDiv, {y: [[duration]]}, [currentTrace]);
Plotly.extendTraces(plotlyDiv, { y: [[duration]] }, [currentTrace]);
}
async function startMessageLengthRun() {
const iteration0 = parseFloat(document.getElementById('iteration0').value);
const nonceTrials0 = parseFloat(document.getElementById('nonceTrials0').value);
const nonceTrials0 = parseFloat(
document.getElementById('nonceTrials0').value
);
const numWorkers0 = parseFloat(document.getElementById('numWorkers0').value);
const messageLengthStart0 = parseFloat(document.getElementById('messageLengthStart0').value);
const messageLengthStop0 = parseFloat(document.getElementById('messageLengthStop0').value);
const messageLengthStep0 = parseFloat(document.getElementById('messageLengthStep0').value);
const messageLengthStart0 = parseFloat(
document.getElementById('messageLengthStart0').value
);
const messageLengthStop0 = parseFloat(
document.getElementById('messageLengthStop0').value
);
const messageLengthStep0 = parseFloat(
document.getElementById('messageLengthStep0').value
);
const TTL0 = parseFloat(document.getElementById('TTL0').value);
for (let l = messageLengthStart0; l < messageLengthStop0; l += messageLengthStep0) {
for (
let l = messageLengthStart0;
l < messageLengthStop0;
l += messageLengthStep0
) {
// eslint-disable-next-line no-await-in-loop
await runPoW({ iteration: iteration0, nonceTrials: nonceTrials0, numWorkers: numWorkers0, messageLength: l, ttl: TTL0 });
await runPoW({
iteration: iteration0,
nonceTrials: nonceTrials0,
numWorkers: numWorkers0,
messageLength: l,
ttl: TTL0,
});
}
}
async function startNumWorkerRun() {
const iteration1 = parseFloat(document.getElementById('iteration1').value);
const nonceTrials1 = parseFloat(document.getElementById('nonceTrials1').value);
const numWorkersStart1 = parseFloat(document.getElementById('numWorkersStart1').value);
const numWorkersEnd1 = parseFloat(document.getElementById('numWorkersEnd1').value);
const messageLength1 = parseFloat(document.getElementById('messageLength1').value);
const nonceTrials1 = parseFloat(
document.getElementById('nonceTrials1').value
);
const numWorkersStart1 = parseFloat(
document.getElementById('numWorkersStart1').value
);
const numWorkersEnd1 = parseFloat(
document.getElementById('numWorkersEnd1').value
);
const messageLength1 = parseFloat(
document.getElementById('messageLength1').value
);
const TTL1 = parseFloat(document.getElementById('TTL1').value);
for (let numWorkers = numWorkersStart1; numWorkers <= numWorkersEnd1; numWorkers +=1) {
for (
let numWorkers = numWorkersStart1;
numWorkers <= numWorkersEnd1;
numWorkers += 1
) {
// eslint-disable-next-line no-await-in-loop
await runPoW({ iteration: iteration1, nonceTrials: nonceTrials1, numWorkers, messageLength: messageLength1, ttl: TTL1 });
await runPoW({
iteration: iteration1,
nonceTrials: nonceTrials1,
numWorkers,
messageLength: messageLength1,
ttl: TTL1,
});
}
}
async function startNonceTrialsRun() {
const iteration2 = parseFloat(document.getElementById('iteration2').value);
const messageLength2 = parseFloat(document.getElementById('messageLength2').value);
const messageLength2 = parseFloat(
document.getElementById('messageLength2').value
);
const numWorkers2 = parseFloat(document.getElementById('numWorkers2').value);
const nonceTrialsStart2 = parseFloat(document.getElementById('nonceTrialsStart2').value);
const nonceTrialsStop2 = parseFloat(document.getElementById('nonceTrialsStop2').value);
const nonceTrialsStep2 = parseFloat(document.getElementById('nonceTrialsStep2').value);
const nonceTrialsStart2 = parseFloat(
document.getElementById('nonceTrialsStart2').value
);
const nonceTrialsStop2 = parseFloat(
document.getElementById('nonceTrialsStop2').value
);
const nonceTrialsStep2 = parseFloat(
document.getElementById('nonceTrialsStep2').value
);
const TTL2 = parseFloat(document.getElementById('TTL2').value);
for (let n = nonceTrialsStart2; n < nonceTrialsStop2; n += nonceTrialsStep2) {
// eslint-disable-next-line no-await-in-loop
await runPoW({ iteration: iteration2, nonceTrials: n, numWorkers: numWorkers2, messageLength: messageLength2, ttl: TTL2 });
await runPoW({
iteration: iteration2,
nonceTrials: n,
numWorkers: numWorkers2,
messageLength: messageLength2,
ttl: TTL2,
});
}
}
async function starTTLRun() {
const iteration3 = parseFloat(document.getElementById('iteration3').value);
const nonceTrials3 = parseFloat(document.getElementById('nonceTrials3').value);
const messageLength3 = parseFloat(document.getElementById('messageLength3').value);
const nonceTrials3 = parseFloat(
document.getElementById('nonceTrials3').value
);
const messageLength3 = parseFloat(
document.getElementById('messageLength3').value
);
const numWorkers3 = parseFloat(document.getElementById('numWorkers3').value);
const TTLStart3 = parseFloat(document.getElementById('TTLStart3').value);
const TTLStop3 = parseFloat(document.getElementById('TTLStop3').value);
const TTLStep3 = parseFloat(document.getElementById('TTLStep3').value);
for (let ttl = TTLStart3; ttl < TTLStop3; ttl += TTLStep3) {
// eslint-disable-next-line no-await-in-loop
await runPoW({ iteration: iteration3, nonceTrials: nonceTrials3, numWorkers: numWorkers3, messageLength: messageLength3, ttl });
await runPoW({
iteration: iteration3,
nonceTrials: nonceTrials3,
numWorkers: numWorkers3,
messageLength: messageLength3,
ttl,
});
}
}
// eslint-disable-next-line no-unused-vars
async function start(index) {
const data = [];
const layout = {};
const options = {
responsive: true,
};
plotlyDiv =`plotly${index}`;
plotlyDiv = `plotly${index}`;
currentTrace = 0;
window.chart = Plotly.newPlot(plotlyDiv, data, layout, options);
workers.forEach(worker => worker.terminate());
switch(index) {
switch (index) {
case 0:
await startMessageLengthRun();
break;

View File

@ -11,7 +11,7 @@ const {
describe('Proof of Work', () => {
describe('#incrementNonce', () => {
it('should increment a Uint8Array nonce correctly', () => {
const arr1Before = new Uint8Array([0,0,0,0,0,0,0,0]);
const arr1Before = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
const arr1After = incrementNonce(arr1Before);
assert.strictEqual(arr1After[0], 0);
assert.strictEqual(arr1After[1], 0);
@ -24,15 +24,21 @@ describe('Proof of Work', () => {
});
it('should increment a Uint8Array nonce correctly in a loop', () => {
let arr = new Uint8Array([0,0,0,0,0,0,0,0]);
assert.deepEqual(incrementNonce(arr), new Uint8Array([0,0,0,0,0,0,0,1]));
arr = new Uint8Array([0,0,0,0,0,0,0,0]);
for(let i = 0; i <= 255; i += 1) {
let arr = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
assert.deepEqual(
incrementNonce(arr),
new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1])
);
arr = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
for (let i = 0; i <= 255; i += 1) {
arr = incrementNonce(arr);
}
assert.deepEqual(arr, new Uint8Array([0,0,0,0,0,0,1,0]));
arr = new Uint8Array([255,255,255,255,255,255,255,255]);
assert.deepEqual(incrementNonce(arr), new Uint8Array([0,0,0,0,0,0,0,0]));
assert.deepEqual(arr, new Uint8Array([0, 0, 0, 0, 0, 0, 1, 0]));
arr = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255]);
assert.deepEqual(
incrementNonce(arr),
new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])
);
});
});
@ -41,12 +47,12 @@ describe('Proof of Work', () => {
// These values will need to be updated if we adjust the difficulty settings
let payloadLen = 625;
const ttl = 86400;
let expectedTarget = new Uint8Array([0,4,119,164,35,224,222,64]);
let expectedTarget = new Uint8Array([0, 4, 119, 164, 35, 224, 222, 64]);
let actualTarget = calcTarget(ttl, payloadLen, 10);
assert.deepEqual(actualTarget, expectedTarget);
payloadLen = 6597;
expectedTarget = new Uint8Array([0,0,109,145,174,146,124,3]);
expectedTarget = new Uint8Array([0, 0, 109, 145, 174, 146, 124, 3]);
actualTarget = calcTarget(ttl, payloadLen, 10);
assert.deepEqual(actualTarget, expectedTarget);
});
@ -54,30 +60,30 @@ describe('Proof of Work', () => {
describe('#greaterThan', () => {
it('should correclty compare two Uint8Arrays', () => {
let arr1 = new Uint8Array([0,0,0,0,0,0,0,0,0,1]);
let arr2 = new Uint8Array([0,0,0,0,0,0,0,0,0,1]);
assert.isFalse(greaterThan(arr1, arr2))
arr1 = new Uint8Array([0,0,0,0,0,0,0,0,0,2]);
arr2 = new Uint8Array([0,0,0,0,0,0,0,0,0,1]);
assert.isTrue(greaterThan(arr1, arr2))
arr1 = new Uint8Array([255,255,255,255,255,255,255,255,255,255]);
arr2 = new Uint8Array([255,255,255,255,255,255,255,255,255,254]);
assert.isTrue(greaterThan(arr1, arr2))
arr1 = new Uint8Array([254,255,255,255,255,255,255,255,255,255]);
arr2 = new Uint8Array([255,255,255,255,255,255,255,255,255,255]);
let arr1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
let arr2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
assert.isFalse(greaterThan(arr1, arr2));
arr1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
arr2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
assert.isTrue(greaterThan(arr1, arr2));
arr1 = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255, 255]);
arr2 = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255, 254]);
assert.isTrue(greaterThan(arr1, arr2));
arr1 = new Uint8Array([254, 255, 255, 255, 255, 255, 255, 255, 255, 255]);
arr2 = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255, 255]);
assert.isFalse(greaterThan(arr1, arr2));
arr1 = new Uint8Array([0]);
arr2 = new Uint8Array([0,0]);
assert.isFalse(greaterThan(arr1, arr2))
arr2 = new Uint8Array([0, 0]);
assert.isFalse(greaterThan(arr1, arr2));
});
});
describe('#bufferToBase64', () => {
it('should correclty convert a Uint8Array to a base64 string', () => {
let arr = new Uint8Array([1,2,3]);
let arr = new Uint8Array([1, 2, 3]);
let expected = 'AQID';
assert.strictEqual(bufferToBase64(arr), expected);
arr = new Uint8Array([123,25,3,121,45,87,24,111]);
arr = new Uint8Array([123, 25, 3, 121, 45, 87, 24, 111]);
expected = 'exkDeS1XGG8=';
assert.strictEqual(bufferToBase64(arr), expected);
arr = new Uint8Array([]);

View File

@ -3,11 +3,17 @@
describe('ServiceNodes', () => {
describe('#consolidateLists', () => {
it('should throw when provided a non-iterable list', () => {
assert.throws(() => libloki.serviceNodes.consolidateLists(null, 1), Error);
assert.throws(
() => libloki.serviceNodes.consolidateLists(null, 1),
Error
);
});
it('should throw when provided a non-iterable item in the list', () => {
assert.throws(() => libloki.serviceNodes.consolidateLists([1, 2, 3], 1), Error);
assert.throws(
() => libloki.serviceNodes.consolidateLists([1, 2, 3], 1),
Error
);
});
it('should throw when provided a non-number threshold', () => {
@ -28,40 +34,44 @@ describe('ServiceNodes', () => {
});
it('should return the union of all lists when threshold is 0', () => {
const result = libloki.serviceNodes.consolidateLists([
['a', 'b', 'c', 'h'],
['d', 'e', 'f', 'g'],
['g', 'h'],
], 0);
const result = libloki.serviceNodes.consolidateLists(
[['a', 'b', 'c', 'h'], ['d', 'e', 'f', 'g'], ['g', 'h']],
0
);
assert.deepEqual(result.sort(), ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']);
});
it('should return the intersection of all lists when threshold is 1', () => {
const result = libloki.serviceNodes.consolidateLists([
['a', 'b', 'c', 'd'],
['a', 'e', 'f', 'g'],
['a', 'h'],
], 1);
const result = libloki.serviceNodes.consolidateLists(
[['a', 'b', 'c', 'd'], ['a', 'e', 'f', 'g'], ['a', 'h']],
1
);
assert.deepEqual(result, ['a']);
});
it('should return the elements that have an occurence >= the provided threshold', () => {
const result = libloki.serviceNodes.consolidateLists([
['a', 'b', 'c', 'd', 'e', 'f', 'g'],
['a', 'b', 'c', 'd', 'e', 'f', 'h'],
['a', 'b', 'c', 'd', 'e', 'f', 'g'],
['a', 'b', 'c', 'd', 'e', 'g', 'h'],
], 3/4);
const result = libloki.serviceNodes.consolidateLists(
[
['a', 'b', 'c', 'd', 'e', 'f', 'g'],
['a', 'b', 'c', 'd', 'e', 'f', 'h'],
['a', 'b', 'c', 'd', 'e', 'f', 'g'],
['a', 'b', 'c', 'd', 'e', 'g', 'h'],
],
3 / 4
);
assert.deepEqual(result, ['a', 'b', 'c', 'd', 'e', 'f', 'g']);
});
it('should work with sets as well', () => {
const result = libloki.serviceNodes.consolidateLists(new Set([
new Set(['a', 'b', 'c', 'd', 'e', 'f', 'g']),
new Set(['a', 'b', 'c', 'd', 'e', 'f', 'h']),
new Set(['a', 'b', 'c', 'd', 'e', 'f', 'g']),
new Set(['a', 'b', 'c', 'd', 'e', 'g', 'h']),
]), 3/4);
const result = libloki.serviceNodes.consolidateLists(
new Set([
new Set(['a', 'b', 'c', 'd', 'e', 'f', 'g']),
new Set(['a', 'b', 'c', 'd', 'e', 'f', 'h']),
new Set(['a', 'b', 'c', 'd', 'e', 'f', 'g']),
new Set(['a', 'b', 'c', 'd', 'e', 'g', 'h']),
]),
3 / 4
);
assert.deepEqual(result, ['a', 'b', 'c', 'd', 'e', 'f', 'g']);
});
});

View File

@ -21,7 +21,9 @@ describe('Storage', () => {
const pubKey = libsignal.crypto.getRandomBytes(32);
const pubKeyString = StringView.arrayBufferToHex(pubKey);
const preKeyIdBefore = textsecure.storage.get('maxPreKeyId', 1);
const newBundle = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
const newBundle = await libloki.storage.getPreKeyBundleForContact(
pubKeyString
);
const preKeyIdAfter = textsecure.storage.get('maxPreKeyId', 1);
assert.strictEqual(preKeyIdAfter, preKeyIdBefore + 1);
@ -34,16 +36,23 @@ describe('Storage', () => {
assert.isDefined(newBundle.preKey);
assert.isDefined(newBundle.signedKey);
assert.isDefined(newBundle.signature);
assert.strictEqual(testKeyArray.byteLength, newBundle.signedKey.byteLength);
for (let i = 0 ; i !== testKeyArray.byteLength; i += 1)
assert.strictEqual(
testKeyArray.byteLength,
newBundle.signedKey.byteLength
);
for (let i = 0; i !== testKeyArray.byteLength; i += 1)
assert.strictEqual(testKeyArray[i], newBundle.signedKey[i]);
});
it('should return the same prekey bundle after creating a contact', async () => {
const pubKey = libsignal.crypto.getRandomBytes(32);
const pubKeyString = StringView.arrayBufferToHex(pubKey);
const bundle1 = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
const bundle2 = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
const bundle1 = await libloki.storage.getPreKeyBundleForContact(
pubKeyString
);
const bundle2 = await libloki.storage.getPreKeyBundleForContact(
pubKeyString
);
assert.isDefined(bundle1);
assert.isDefined(bundle2);
assert.deepEqual(bundle1, bundle2);
@ -53,7 +62,9 @@ describe('Storage', () => {
const pubKey = libsignal.crypto.getRandomBytes(32);
const pubKeyString = StringView.arrayBufferToHex(pubKey);
const preKeyIdBefore = textsecure.storage.get('maxPreKeyId', 1);
const newBundle = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
const newBundle = await libloki.storage.getPreKeyBundleForContact(
pubKeyString
);
const preKeyIdAfter = textsecure.storage.get('maxPreKeyId', 1);
assert.strictEqual(preKeyIdAfter, preKeyIdBefore + 1);

View File

@ -15,6 +15,8 @@
*/
/* eslint-disable more/no-then */
/* eslint-disable no-unused-vars */
/* eslint-disable no-await-in-loop */
// eslint-disable-next-line func-names
(function() {
@ -70,17 +72,18 @@
generateKeypair = libsignal.KeyHelper.generateIdentityKeyPair;
}
return this.queueTask(() =>
generateKeypair().then(async identityKeyPair => {
return createAccount(identityKeyPair)
.then(() => this.saveMnemonic(mnemonic))
.then(clearSessionsAndPreKeys)
.then(generateKeys)
.then(confirmKeys)
.then(() => {
const pubKeyString = StringView.arrayBufferToHex(identityKeyPair.pubKey);
registrationDone(pubKeyString, profileName);
});
}
generateKeypair().then(async identityKeyPair =>
createAccount(identityKeyPair)
.then(() => this.saveMnemonic(mnemonic))
.then(clearSessionsAndPreKeys)
.then(generateKeys)
.then(confirmKeys)
.then(() => {
const pubKeyString = StringView.arrayBufferToHex(
identityKeyPair.pubKey
);
registrationDone(pubKeyString, profileName);
})
)
);
},
@ -138,12 +141,13 @@
log.info('Added mock contact');
},
registerSecondDevice(setProvisioningUrl, confirmNumber, progressCallback) {
throw new Error('account_manager: registerSecondDevice has not been implemented!');
throw new Error(
'account_manager: registerSecondDevice has not been implemented!'
);
},
refreshPreKeys() {
// const generateKeys = this.generateKeys.bind(this, 0);
// const registerKeys = this.server.registerKeys.bind(this.server);
// return this.queueTask(() =>
// this.server.getMyKeys().then(preKeyCount => {
// window.log.info(`prekey count ${preKeyCount}`);
@ -312,50 +316,51 @@
password = password.substring(0, password.length - 2);
const registrationId = libsignal.KeyHelper.generateRegistrationId();
// update our own identity key, which may have changed
// if we're relinking after a reinstall on the master device
const pubKeyString = StringView.arrayBufferToHex(
identityKeyPair.pubKey
);
// update our own identity key, which may have changed
// if we're relinking after a reinstall on the master device
const pubKeyString = StringView.arrayBufferToHex(identityKeyPair.pubKey);
return Promise.resolve().then(async () => {
await Promise.all([
textsecure.storage.remove('identityKey'),
textsecure.storage.remove('signaling_key'),
textsecure.storage.remove('password'),
textsecure.storage.remove('registrationId'),
textsecure.storage.remove('number_id'),
textsecure.storage.remove('device_name'),
textsecure.storage.remove('userAgent'),
textsecure.storage.remove('read-receipts-setting'),
]);
await Promise.all([
textsecure.storage.remove('identityKey'),
textsecure.storage.remove('signaling_key'),
textsecure.storage.remove('password'),
textsecure.storage.remove('registrationId'),
textsecure.storage.remove('number_id'),
textsecure.storage.remove('device_name'),
textsecure.storage.remove('userAgent'),
textsecure.storage.remove('read-receipts-setting'),
]);
// update our own identity key, which may have changed
// if we're relinking after a reinstall on the master device
await textsecure.storage.protocol.saveIdentityWithAttributes(pubKeyString, {
// update our own identity key, which may have changed
// if we're relinking after a reinstall on the master device
await textsecure.storage.protocol.saveIdentityWithAttributes(
pubKeyString,
{
id: pubKeyString,
publicKey: identityKeyPair.pubKey,
firstUse: true,
timestamp: Date.now(),
verified: textsecure.storage.protocol.VerifiedStatus.VERIFIED,
nonblockingApproval: true,
});
await textsecure.storage.put('identityKey', identityKeyPair);
await textsecure.storage.put('signaling_key', signalingKey);
await textsecure.storage.put('password', password);
await textsecure.storage.put('registrationId', registrationId);
if (userAgent) {
await textsecure.storage.put('userAgent', userAgent);
}
);
await textsecure.storage.put(
'read-receipt-setting',
Boolean(readReceipts)
);
await textsecure.storage.put('identityKey', identityKeyPair);
await textsecure.storage.put('signaling_key', signalingKey);
await textsecure.storage.put('password', password);
await textsecure.storage.put('registrationId', registrationId);
if (userAgent) {
await textsecure.storage.put('userAgent', userAgent);
}
await textsecure.storage.user.setNumberAndDeviceId(pubKeyString, 1);
});
await textsecure.storage.put(
'read-receipt-setting',
Boolean(readReceipts)
);
await textsecure.storage.user.setNumberAndDeviceId(pubKeyString, 1);
});
},
clearSessionsAndPreKeys() {
const store = textsecure.storage.protocol;
@ -462,7 +467,10 @@
window.log.info('registration done');
// Ensure that we always have a conversation for ourself
const conversation = await ConversationController.getOrCreateAndWait(number, 'private');
const conversation = await ConversationController.getOrCreateAndWait(
number,
'private'
);
await storage.setProfileName(profileName);

View File

@ -128,17 +128,17 @@
inherit(Error, UnregisteredUserError);
function PoWError(number, error) {
// eslint-disable-next-line prefer-destructuring
this.number = number.split('.')[0];
// eslint-disable-next-line prefer-destructuring
this.number = number.split('.')[0];
ReplayableError.call(this, {
name: 'PoWError',
message: 'Failed to calculate PoW',
});
ReplayableError.call(this, {
name: 'PoWError',
message: 'Failed to calculate PoW',
});
if (error) {
appendStack(this, error);
}
if (error) {
appendStack(this, error);
}
}
inherit(ReplayableError, PoWError);

View File

@ -1,9 +1,9 @@
/* global window, dcodeIO, textsecure, StringView */
// eslint-disable-next-line func-names
(function () {
(function() {
let server;
const development = (window.getEnvironment() !== 'production');
const development = window.getEnvironment() !== 'production';
const pollTime = development ? 100 : 5000;
function stringToArrayBufferBase64(string) {
@ -42,9 +42,13 @@
};
};
const filterIncomingMessages = async function filterIncomingMessages(messages) {
const filterIncomingMessages = async function filterIncomingMessages(
messages
) {
const incomingHashes = messages.map(m => m.hash);
const dupHashes = await window.Signal.Data.getSeenMessagesByHashList(incomingHashes);
const dupHashes = await window.Signal.Data.getSeenMessagesByHashList(
incomingHashes
);
const newMessages = messages.filter(m => !dupHashes.includes(m.hash));
const newHashes = newMessages.map(m => ({
expiresAt: m.expiration,
@ -59,34 +63,42 @@
let { handleRequest } = opts;
if (typeof handleRequest !== 'function') {
handleRequest = request => request.respond(404, 'Not found');
};
}
let connected = false;
this.startPolling = async function pollServer(callBack) {
const myKeys = await textsecure.storage.protocol.getIdentityKeyPair();
const pubKey = StringView.arrayBufferToHex(myKeys.pubKey)
const pubKey = StringView.arrayBufferToHex(myKeys.pubKey);
let result;
try {
result = await server.retrieveMessages(pubKey);
connected = true;
} catch (err) {
connected = false;
setTimeout(() => { pollServer(callBack); }, pollTime);
setTimeout(() => {
pollServer(callBack);
}, pollTime);
return;
}
if (typeof callBack === 'function') {
callBack(connected);
}
if (!result.messages) {
setTimeout(() => { pollServer(callBack); }, pollTime);
setTimeout(() => {
pollServer(callBack);
}, pollTime);
return;
}
const newMessages = await filterIncomingMessages(result.messages);
newMessages.forEach(async message => {
const { data } = message;
const dataPlaintext = stringToArrayBufferBase64(data);
const messageBuf = textsecure.protobuf.WebSocketMessage.decode(dataPlaintext);
if (messageBuf.type === textsecure.protobuf.WebSocketMessage.Type.REQUEST) {
const messageBuf = textsecure.protobuf.WebSocketMessage.decode(
dataPlaintext
);
if (
messageBuf.type === textsecure.protobuf.WebSocketMessage.Type.REQUEST
) {
handleRequest(
new IncomingHttpResponse({
verb: messageBuf.request.verb,
@ -97,11 +109,13 @@
);
}
});
setTimeout(() => { pollServer(callBack); }, pollTime);
setTimeout(() => {
pollServer(callBack);
}, pollTime);
};
this.isConnected = function isConnected() {
return connected;
}
};
};
})();

View File

@ -70,7 +70,7 @@ MessageReceiver.prototype.extend({
this.httpPollingResource = new HttpResource(this.lokiserver, {
handleRequest: this.handleRequest.bind(this),
});
this.httpPollingResource.startPolling((connected) => {
this.httpPollingResource.startPolling(connected => {
// Emulate receiving an 'empty' websocket messages from the server.
// This is required to update the internal logic that checks
// if we are connected to the server. Without this, for example,
@ -328,7 +328,8 @@ MessageReceiver.prototype.extend({
envelope.sourceDevice = envelope.sourceDevice || item.sourceDevice;
envelope.serverTimestamp =
envelope.serverTimestamp || item.serverTimestamp;
envelope.preKeyBundleMessage = envelope.preKeyBundleMessage || item.preKeyBundleMessage;
envelope.preKeyBundleMessage =
envelope.preKeyBundleMessage || item.preKeyBundleMessage;
const { decrypted } = item;
if (decrypted) {
@ -381,7 +382,7 @@ MessageReceiver.prototype.extend({
if (envelope.source) {
return `${envelope.source}.${
envelope.sourceDevice
} ${envelope.timestamp.toNumber()} (${envelope.id})`;
} ${envelope.timestamp.toNumber()} (${envelope.id})`;
}
return envelope.id;
@ -540,7 +541,9 @@ MessageReceiver.prototype.extend({
},
getStatus() {
if (this.httpPollingResource) {
return this.httpPollingResource.isConnected() ? WebSocket.OPEN : WebSocket.CLOSED;
return this.httpPollingResource.isConnected()
? WebSocket.OPEN
: WebSocket.CLOSED;
}
if (this.socket) {
return this.socket.readyState;
@ -616,47 +619,57 @@ MessageReceiver.prototype.extend({
let conversation;
try {
conversation = await window.ConversationController.getOrCreateAndWait(envelope.source, 'private');
conversation = await window.ConversationController.getOrCreateAndWait(
envelope.source,
'private'
);
} catch (e) {
window.log.info('Error getting conversation: ', envelope.source);
}
const getCurrentSessionBaseKey = async () => {
const record = await sessionCipher.getRecord(address.toString());
if (!record)
return null;
if (!record) return null;
const openSession = record.getOpenSession();
if (!openSession)
return null;
if (!openSession) return null;
const { baseKey } = openSession.indexInfo;
return baseKey
return baseKey;
};
const captureActiveSession = async () => {
this.activeSessionBaseKey = await getCurrentSessionBaseKey(sessionCipher);
};
const restoreActiveSession = async () => {
const record = await sessionCipher.getRecord(address.toString());
if (!record)
return
if (!record) return;
record.archiveCurrentState();
const sessionToRestore = record.sessions[this.activeSessionBaseKey];
record.promoteState(sessionToRestore);
record.updateSessionState(sessionToRestore);
await textsecure.storage.protocol.storeSession(address.toString(), record.serialize());
await textsecure.storage.protocol.storeSession(
address.toString(),
record.serialize()
);
};
const deleteAllSessionExcept = async (sessionBaseKey) => {
const deleteAllSessionExcept = async sessionBaseKey => {
const record = await sessionCipher.getRecord(address.toString());
if (!record)
return
if (!record) return;
const sessionToKeep = record.sessions[sessionBaseKey];
record.sessions = {}
record.sessions = {};
record.updateSessionState(sessionToKeep);
await textsecure.storage.protocol.storeSession(address.toString(), record.serialize());
await textsecure.storage.protocol.storeSession(
address.toString(),
record.serialize()
);
};
let handleSessionReset;
if (conversation.isSessionResetOngoing()) {
handleSessionReset = async (result) => {
const currentSessionBaseKey = await getCurrentSessionBaseKey(sessionCipher);
if (this.activeSessionBaseKey && currentSessionBaseKey !== this.activeSessionBaseKey) {
handleSessionReset = async result => {
const currentSessionBaseKey = await getCurrentSessionBaseKey(
sessionCipher
);
if (
this.activeSessionBaseKey &&
currentSessionBaseKey !== this.activeSessionBaseKey
) {
if (conversation.isSessionResetReceived()) {
await restoreActiveSession();
} else {
@ -670,7 +683,7 @@ MessageReceiver.prototype.extend({
return result;
};
} else {
handleSessionReset = async (result) => result;
handleSessionReset = async result => result;
}
switch (envelope.type) {
@ -683,18 +696,17 @@ MessageReceiver.prototype.extend({
break;
case textsecure.protobuf.Envelope.Type.FRIEND_REQUEST: {
window.log.info('friend-request message from ', envelope.source);
promise = fallBackSessionCipher.decrypt(ciphertext.toArrayBuffer())
promise = fallBackSessionCipher
.decrypt(ciphertext.toArrayBuffer())
.then(this.unpad);
break;
}
case textsecure.protobuf.Envelope.Type.PREKEY_BUNDLE:
window.log.info('prekey message from', this.getEnvelopeId(envelope));
promise = captureActiveSession(sessionCipher)
.then(() => this.decryptPreKeyWhisperMessage(
ciphertext,
sessionCipher,
address
))
.then(() =>
this.decryptPreKeyWhisperMessage(ciphertext, sessionCipher, address)
)
.then(handleSessionReset);
break;
case textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER:
@ -840,7 +852,7 @@ MessageReceiver.prototype.extend({
const isMe = envelope.source === textsecure.storage.user.getNumber();
const isLeavingGroup = Boolean(
message.group &&
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
);
if (groupId && isBlocked && !(isMe && isLeavingGroup)) {
@ -883,7 +895,7 @@ MessageReceiver.prototype.extend({
const conversation = window.ConversationController.get(envelope.source);
const isLeavingGroup = Boolean(
message.group &&
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
);
const friendRequest =
envelope.type === textsecure.protobuf.Envelope.Type.FRIEND_REQUEST;
@ -900,10 +912,8 @@ MessageReceiver.prototype.extend({
}
if (friendRequest && isMe) {
window.log.info(
'refusing to add a friend request to ourselves'
);
throw new Error('Cannot add a friend request for ourselves!')
window.log.info('refusing to add a friend request to ourselves');
throw new Error('Cannot add a friend request for ourselves!');
}
if (groupId && isBlocked && !(isMe && isLeavingGroup)) {
@ -1227,10 +1237,7 @@ MessageReceiver.prototype.extend({
preKeyBundleMessage.signature,
].map(k => dcodeIO.ByteBuffer.wrap(k).toArrayBuffer());
const {
preKeyId,
signedKeyId,
} = preKeyBundleMessage;
const { preKeyId, signedKeyId } = preKeyBundleMessage;
if (pubKey !== StringView.arrayBufferToHex(identityKey)) {
throw new Error(
@ -1312,7 +1319,13 @@ MessageReceiver.prototype.extend({
if (preKey === undefined || signedPreKey === undefined) {
return;
}
const device = { identityKey, deviceId, preKey, signedPreKey, registrationId: 0 }
const device = {
identityKey,
deviceId,
preKey,
signedPreKey,
registrationId: 0,
};
const builder = new libsignal.SessionBuilder(
textsecure.storage.protocol,
address
@ -1513,7 +1526,9 @@ textsecure.MessageReceiver = function MessageReceiverWrapper(
);
this.getStatus = messageReceiver.getStatus.bind(messageReceiver);
this.close = messageReceiver.close.bind(messageReceiver);
this.savePreKeyBundleMessage = messageReceiver.savePreKeyBundleMessage.bind(messageReceiver);
this.savePreKeyBundleMessage = messageReceiver.savePreKeyBundleMessage.bind(
messageReceiver
);
messageReceiver.connect();
};

View File

@ -119,22 +119,24 @@ OutgoingMessage.prototype = {
if (device.registrationId === 0) {
window.log.info('device registrationId 0!');
}
return builder.processPreKey(device).then(async () => {
// TODO: only remove the keys that were used above!
await window.libloki.storage.removeContactPreKeyBundle(number);
return true;
}
).catch(error => {
if (error.message === 'Identity key changed') {
// eslint-disable-next-line no-param-reassign
error.timestamp = this.timestamp;
// eslint-disable-next-line no-param-reassign
error.originalMessage = this.message.toArrayBuffer();
// eslint-disable-next-line no-param-reassign
error.identityKey = device.identityKey;
}
throw error;
});
return builder
.processPreKey(device)
.then(async () => {
// TODO: only remove the keys that were used above!
await window.libloki.storage.removeContactPreKeyBundle(number);
return true;
})
.catch(error => {
if (error.message === 'Identity key changed') {
// eslint-disable-next-line no-param-reassign
error.timestamp = this.timestamp;
// eslint-disable-next-line no-param-reassign
error.originalMessage = this.message.toArrayBuffer();
// eslint-disable-next-line no-param-reassign
error.identityKey = device.identityKey;
}
throw error;
});
}
return false;
@ -184,7 +186,12 @@ OutgoingMessage.prototype = {
async transmitMessage(number, data, timestamp, ttl = 24 * 60 * 60) {
const pubKey = number;
try {
const result = await this.lokiserver.sendMessage(pubKey, data, timestamp, ttl);
const result = await this.lokiserver.sendMessage(
pubKey,
data,
timestamp,
ttl
);
return result;
} catch (e) {
if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) {
@ -288,17 +295,24 @@ OutgoingMessage.prototype = {
const address = new libsignal.SignalProtocolAddress(number, deviceId);
const ourKey = textsecure.storage.user.getNumber();
const options = {};
const fallBackCipher = new libloki.crypto.FallBackSessionCipher(address);
const fallBackCipher = new libloki.crypto.FallBackSessionCipher(
address
);
// Check if we need to attach the preKeys
let sessionCipher;
const isFriendRequest = this.messageType === 'friend-request';
const flags = this.message.dataMessage ? this.message.dataMessage.get_flags() : null;
const isEndSession = flags === textsecure.protobuf.DataMessage.Flags.END_SESSION;
const flags = this.message.dataMessage
? this.message.dataMessage.get_flags()
: null;
const isEndSession =
flags === textsecure.protobuf.DataMessage.Flags.END_SESSION;
if (isFriendRequest || isEndSession) {
// Encrypt them with the fallback
const pkb = await libloki.storage.getPreKeyBundleForContact(number);
const preKeyBundleMessage = new textsecure.protobuf.PreKeyBundleMessage(pkb);
const preKeyBundleMessage = new textsecure.protobuf.PreKeyBundleMessage(
pkb
);
this.message.preKeyBundleMessage = preKeyBundleMessage;
window.log.info('attaching prekeys to outgoing message');
}
@ -325,10 +339,7 @@ OutgoingMessage.prototype = {
if (!this.fallBackEncryption) {
// eslint-disable-next-line no-param-reassign
ciphertext.body = new Uint8Array(
dcodeIO.ByteBuffer.wrap(
ciphertext.body,
'binary'
).toArrayBuffer()
dcodeIO.ByteBuffer.wrap(ciphertext.body, 'binary').toArrayBuffer()
);
}
return {
@ -345,7 +356,10 @@ OutgoingMessage.prototype = {
const outgoingObject = outgoingObjects[0];
const socketMessage = await this.wrapInWebsocketMessage(outgoingObject);
let ttl;
if (outgoingObject.type === textsecure.protobuf.Envelope.Type.FRIEND_REQUEST) {
if (
outgoingObject.type ===
textsecure.protobuf.Envelope.Type.FRIEND_REQUEST
) {
ttl = 4 * 24 * 60 * 60; // 4 days for friend request message
} else {
const hours = window.getMessageTTL() || 24; // 1 day default for any other message

View File

@ -1,6 +1,6 @@
/* global window, StringView */
/* eslint-disable no-bitwise, no-nested-ternary */
/* eslint-disable no-bitwise, no-nested-ternary, */
// eslint-disable-next-line func-names
(function() {
@ -102,16 +102,14 @@
arrayBufferToHex(aArrayBuffer) {
return Array.prototype.map
.call(new Uint8Array(aArrayBuffer), x =>
('00' + x.toString(16)).slice(-2)
`00${x.toString(16)}`.slice(-2)
)
.join('');
},
hexToArrayBuffer(aString) {
return new Uint8Array(
aString.match(/[\da-f]{2}/gi).map(function(h) {
return parseInt(h, 16);
})
aString.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16))
).buffer;
},
};

16
main.js
View File

@ -451,11 +451,11 @@ function showPasswordWindow() {
captureClicks(passwordWindow);
passwordWindow.on('close', e => {
// If the application is terminating, just do the default
if (
// If the application is terminating, just do the default
if (
config.environment === 'test' ||
config.environment === 'test-lib' ||
(windowState.shouldQuit())
windowState.shouldQuit()
) {
return;
}
@ -963,7 +963,8 @@ ipc.on('update-tray-icon', (event, unreadCount) => {
// Password screen related IPC calls
ipc.on('password-window-login', async (event, passPhrase) => {
const sendResponse = (e) => event.sender.send('password-window-login-response', e);
const sendResponse = e =>
event.sender.send('password-window-login-response', e);
try {
await showMainWindow(passPhrase);
@ -979,7 +980,7 @@ ipc.on('password-window-login', async (event, passPhrase) => {
});
ipc.on('set-password', async (event, passPhrase, oldPhrase) => {
const sendResponse = (e) => event.sender.send('set-password-response', e);
const sendResponse = e => event.sender.send('set-password-response', e);
try {
// Check if the hash we have stored matches the hash of the old passphrase.
@ -987,7 +988,10 @@ ipc.on('set-password', async (event, passPhrase, oldPhrase) => {
const hashMatches = oldPhrase && passwordUtil.matchesHash(oldPhrase, hash);
if (hash && !hashMatches) {
const incorrectOldPassword = locale.messages.invalidOldPassword.message;
sendResponse(incorrectOldPassword || 'Failed to set password: Old password provided is invalid');
sendResponse(
incorrectOldPassword ||
'Failed to set password: Old password provided is invalid'
);
return;
}

View File

@ -20,42 +20,48 @@ const mimeType = {
'.pdf': 'application/pdf',
'.doc': 'application/msword',
'.eot': 'appliaction/vnd.ms-fontobject',
'.ttf': 'aplication/font-sfnt'
'.ttf': 'aplication/font-sfnt',
};
http.createServer(function (req, res) {
// console.log(`${req.method} ${req.url}`);
// parse URL
const parsedUrl = url.parse(req.url);
// extract URL path
// Avoid https://en.wikipedia.org/wiki/Directory_traversal_attack
// e.g curl --path-as-is http://localhost:9000/../fileInDanger.txt
// by limiting the path to current directory only
const sanitizePath = path.normalize(parsedUrl.pathname).replace(/^(\.\.[\/\\])+/, '');
let pathname = path.join(__dirname, sanitizePath);
fs.exists(pathname, function (exist) {
if(!exist) {
// if the file is not found, return 404
res.statusCode = 404;
res.end(`File ${pathname} not found!`);
return;
}
// if is a directory, then look for index.html
if (fs.statSync(pathname).isDirectory()) {
pathname += '/index.html';
}
// read file from file system
fs.readFile(pathname, function(err, data){
if(err){
res.statusCode = 500;
res.end(`Error getting the file: ${err}.`);
} else {
// based on the URL path, extract the file extention. e.g. .js, .doc, ...
const ext = path.parse(pathname).ext;
// if the file is found, set Content-type and send data
res.setHeader('Content-type', mimeType[ext] || 'text/plain' );
res.end(data);
http
.createServer((req, res) => {
// console.log(`${req.method} ${req.url}`);
// parse URL
const parsedUrl = url.parse(req.url);
// extract URL path
// Avoid https://en.wikipedia.org/wiki/Directory_traversal_attack
// e.g curl --path-as-is http://localhost:9000/../fileInDanger.txt
// by limiting the path to current directory only
const sanitizePath = path
.normalize(parsedUrl.pathname)
.replace(/^(\.\.[/\\])+/, '');
let pathname = path.join(__dirname, sanitizePath);
fs.exists(pathname, exist => {
if (!exist) {
// if the file is not found, return 404
res.statusCode = 404;
res.end(`File ${pathname} not found!`);
return;
}
// if is a directory, then look for index.html
if (fs.statSync(pathname).isDirectory()) {
pathname += '/index.html';
}
// read file from file system
fs.readFile(pathname, (err, data) => {
if (err) {
res.statusCode = 500;
res.end(`Error getting the file: ${err}.`);
} else {
// based on the URL path, extract the file extention. e.g. .js, .doc, ...
const { ext } = path.parse(pathname);
// if the file is found, set Content-type and send data
res.setHeader('Content-type', mimeType[ext] || 'text/plain');
res.end(data);
}
});
});
});
}).listen(parseInt(port), hostname);
console.log(`metrics running on http://${hostname}:${port}/metrics.html`);
})
.listen(parseInt(port, 10), hostname);
// eslint-disable-next-line
console.log(`metrics running on http://${hostname}:${port}/metrics.html`);

View File

@ -26,14 +26,15 @@ window.getAppInstance = () => config.appInstance;
window.passwordUtil = require('./app/password_util');
window.onLogin = (passPhrase) => new Promise((resolve, reject) => {
ipcRenderer.once('password-window-login-response', (event, error) => {
if (error) {
return reject(error);
}
return resolve();
window.onLogin = passPhrase =>
new Promise((resolve, reject) => {
ipcRenderer.once('password-window-login-response', (event, error) => {
if (error) {
return reject(error);
}
return resolve();
});
ipcRenderer.send('password-window-login', passPhrase);
});
ipcRenderer.send('password-window-login', passPhrase);
});
require('./js/logging');

View File

@ -50,16 +50,17 @@ const localeMessages = ipc.sendSync('locale-data');
window.setBadgeCount = count => ipc.send('set-badge-count', count);
// Set the password for the database
window.setPassword = (passPhrase, oldPhrase) => new Promise((resolve, reject) => {
ipc.once('set-password-response', (event, error) => {
if (error) {
return reject(error);
}
Whisper.events.trigger('password-updated');
return resolve();
window.setPassword = (passPhrase, oldPhrase) =>
new Promise((resolve, reject) => {
ipc.once('set-password-response', (event, error) => {
if (error) {
return reject(error);
}
Whisper.events.trigger('password-updated');
return resolve();
});
ipc.send('set-password', passPhrase, oldPhrase);
});
ipc.send('set-password', passPhrase, oldPhrase);
});
// We never do these in our code, so we'll prevent it everywhere
window.open = () => null;
@ -107,7 +108,10 @@ ipc.on('on-unblock-number', (event, number) => {
const conversation = window.ConversationController.get(number);
conversation.unblock();
} catch (e) {
window.log.info('IPC on unblock: failed to fetch conversation for number: ', number);
window.log.info(
'IPC on unblock: failed to fetch conversation for number: ',
number
);
}
}
});
@ -155,7 +159,7 @@ installGetter('hide-menu-bar', 'getHideMenuBar');
installSetter('hide-menu-bar', 'setHideMenuBar');
// Get the message TTL setting
window.getMessageTTL = () => window.storage.get('message-ttl', 24)
window.getMessageTTL = () => window.storage.get('message-ttl', 24);
installGetter('message-ttl', 'getMessageTTL');
installSetter('message-ttl', 'setMessageTTL');
@ -281,7 +285,7 @@ window.callWorker = (fnName, ...args) => utilWorker.callWorker(fnName, ...args);
// Linux seems to periodically let the event loop stop, so this is a global workaround
setInterval(() => {
window.nodeSetImmediate(() => { });
window.nodeSetImmediate(() => {});
}, 1000);
const { autoOrientImage } = require('./js/modules/auto_orient_image');

View File

@ -29,7 +29,8 @@ window.closeSettings = () => ipcRenderer.send('close-settings');
// Events for updating block number states across different windows.
// In this case we need these to update the blocked number
// collection on the main window from the settings window.
window.onUnblockNumber = number => ipcRenderer.send('on-unblock-number', number);
window.onUnblockNumber = number =>
ipcRenderer.send('on-unblock-number', number);
window.getDeviceName = makeGetter('device-name');

View File

@ -382,7 +382,8 @@
color: white;
outline: none;
&:hover, &:disabled {
&:hover,
&:disabled {
background-color: $color-loki-green-dark;
border-color: $color-loki-green-dark;
}

View File

@ -925,7 +925,8 @@ textarea {
font-size: 14px;
}
#generate-mnemonic, #copy-mnemonic {
#generate-mnemonic,
#copy-mnemonic {
background: $grey;
}
@ -936,7 +937,7 @@ textarea {
line-height: 2.8;
background: white;
overflow: hidden;
border-radius: .25em;
border-radius: 0.25em;
select {
// Reset select
@ -954,7 +955,7 @@ textarea {
width: 100%;
height: 100%;
margin: 0;
padding: 0 0 0 .5em;
padding: 0 0 0 0.5em;
color: black;
cursor: pointer;
@ -972,9 +973,9 @@ textarea {
padding: 0 1em;
background: $color-dark-85;
pointer-events: none;
-webkit-transition: .25s all ease;
-o-transition: .25s all ease;
transition: .25s all ease;
-webkit-transition: 0.25s all ease;
-o-transition: 0.25s all ease;
transition: 0.25s all ease;
}
}

View File

@ -92,7 +92,8 @@
user-select: none;
}
h4.section-toggle, .section-toggle h4 {
h4.section-toggle,
.section-toggle h4 {
margin-top: 1px;
margin-bottom: 1px;
}
@ -106,10 +107,10 @@ h4.section-toggle, .section-toggle h4 {
height: 3em;
line-height: 3;
text-align: center;
-webkit-transition: all .35s;
-o-transition: all .35s;
transition: all .35s;
content: "+";
-webkit-transition: all 0.35s;
-o-transition: all 0.35s;
transition: all 0.35s;
content: '+';
}
.section-toggle-visible::after {
@ -123,7 +124,7 @@ h4.section-toggle, .section-toggle h4 {
align-items: center;
h4 {
flex: 1,
flex: 1;
}
.section-conversations-unread-counter {

View File

@ -974,7 +974,7 @@
}
.module-message-friend-request__container {
flex: 1,
flex: 1;
}
.module-friend-request__buttonContainer {
@ -994,7 +994,7 @@
background-color: transparent;
border: 0;
overflow: hidden;
outline:none;
outline: none;
color: $color-gray-90;
}
@ -1028,7 +1028,6 @@
color: $color-white-08;
}
// Module: Contact Detail
.module-contact-detail {
@ -2156,9 +2155,9 @@
text-align: center;
&:after {
-webkit-transition: all .35s;
-o-transition: all .35s;
transition: all .35s;
-webkit-transition: all 0.35s;
-o-transition: all 0.35s;
transition: all 0.35s;
width: 3em;
line-height: 3em;
height: 3em;
@ -2171,7 +2170,6 @@
transform: rotate(180deg);
}
.module-main-header__app-name {
font-size: 16px;
line-height: 24px;

View File

@ -26,4 +26,4 @@
font-size: 16px;
margin-top: 1em;
}
}
}

View File

@ -67,12 +67,12 @@
}
.wordwrap {
white-space: pre-wrap; /* CSS3 */
white-space: pre-wrap; /* CSS3 */
white-space: -moz-pre-wrap; /* Firefox */
white-space: -pre-wrap; /* Opera <7 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* IE */
}
white-space: -pre-wrap; /* Opera <7 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* IE */
}
.restart-needed {
margin-top: 1em;
@ -103,7 +103,11 @@
color: red;
}
input { flex: 1; }
label { padding-left: 12px; }
input {
flex: 1;
}
label {
padding-left: 12px;
}
}
}

View File

@ -31,7 +31,11 @@ $color-loki-dark-gray: #323232;
$color-loki-extra-dark-gray: #101010;
$color-loki-green: #3bd110;
$color-loki-green-dark: #32b10e;
$color-loki-green-gradient: linear-gradient(to right, rgb(120, 190, 32) 0%, rgb(0, 133, 34) 100%);
$color-loki-green-gradient: linear-gradient(
to right,
rgb(120, 190, 32) 0%,
rgb(0, 133, 34) 100%
);
// New colors

View File

@ -49,28 +49,22 @@ describe('Password Util', () => {
});
it('should return an error if password is not a string', () => {
const invalid = [
0,
123456,
[],
{},
null,
undefined,
];
const invalid = [0, 123456, [], {}, null, undefined];
invalid.forEach(pass => {
assert.strictEqual(passwordUtil.validatePassword(pass), 'Password must be a string');
assert.strictEqual(
passwordUtil.validatePassword(pass),
'Password must be a string'
);
});
});
it('should return an error if password is not between 6 and 50 characters',() => {
const invalid = [
'a',
'abcde',
'#'.repeat(51),
'#'.repeat(100),
];
it('should return an error if password is not between 6 and 50 characters', () => {
const invalid = ['a', 'abcde', '#'.repeat(51), '#'.repeat(100)];
invalid.forEach(pass => {
assert.strictEqual(passwordUtil.validatePassword(pass), 'Password must be between 6 and 50 characters long');
assert.strictEqual(
passwordUtil.validatePassword(pass),
'Password must be between 6 and 50 characters long'
);
});
});
@ -87,7 +81,10 @@ describe('Password Util', () => {
'<@ȦƘΉوۉaҋ<',
];
invalid.forEach(pass => {
assert.strictEqual(passwordUtil.validatePassword(pass), 'Password must only contain letters, numbers and symbols');
assert.strictEqual(
passwordUtil.validatePassword(pass),
'Password must only contain letters, numbers and symbols'
);
});
});
});

View File

@ -366,12 +366,12 @@ describe('Backup', () => {
(message.contact || []).map(async contact => {
return contact && contact.avatar && contact.avatar.avatar
? Object.assign({}, contact, {
avatar: Object.assign({}, contact.avatar, {
avatar: await wrappedLoadAttachment(
contact.avatar.avatar
),
}),
})
avatar: Object.assign({}, contact.avatar, {
avatar: await wrappedLoadAttachment(
contact.avatar.avatar
),
}),
})
: contact;
})
),

View File

@ -29,7 +29,10 @@ describe('ConversationCollection', () => {
});
describe('Conversation', () => {
const attributes = { type: 'private', id: '051d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab' };
const attributes = {
type: 'private',
id: '051d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab',
};
before(async () => {
const convo = new Whisper.ConversationCollection().add(attributes);
await window.Signal.Data.saveConversation(convo.attributes, {
@ -50,7 +53,9 @@ describe('Conversation', () => {
after(clearDatabase);
it('sorts its contacts in an intl-friendly way', () => {
const convo = new Whisper.Conversation({ id: '051d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab' });
const convo = new Whisper.Conversation({
id: '051d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab',
});
convo.contactCollection.add(
new Whisper.Conversation({
name: 'C',
@ -100,7 +105,10 @@ describe('Conversation', () => {
it('has a title', () => {
const convos = new Whisper.ConversationCollection();
let convo = convos.add(attributes);
assert.equal(convo.getTitle(), '051d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab');
assert.equal(
convo.getTitle(),
'051d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab'
);
convo = convos.add({ type: '' });
assert.equal(convo.getTitle(), 'Unknown group');
@ -112,7 +120,10 @@ describe('Conversation', () => {
it('returns the number', () => {
const convos = new Whisper.ConversationCollection();
let convo = convos.add(attributes);
assert.equal(convo.getNumber(), '051d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab');
assert.equal(
convo.getNumber(),
'051d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab'
);
convo = convos.add({ type: '' });
assert.equal(convo.getNumber(), '');
@ -127,17 +138,39 @@ describe('Conversation', () => {
describe('when set to private', () => {
it('correctly validates hex numbers', () => {
const regularId = new Whisper.Conversation({ type: 'private', id: '051d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab' });
const invalidId = new Whisper.Conversation({ type: 'private', id: 'j71d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab' });
const regularId = new Whisper.Conversation({
type: 'private',
id:
'051d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab',
});
const invalidId = new Whisper.Conversation({
type: 'private',
id:
'j71d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab',
});
assert.ok(regularId.isValid());
assert.notOk(invalidId.isValid());
});
it('correctly validates length', () => {
const regularId33 = new Whisper.Conversation({ type: 'private', id: '051d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab' });
const regularId32 = new Whisper.Conversation({ type: 'private', id: '1d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab' });
const shortId = new Whisper.Conversation({ type: 'private', id: '771d11d' });
const longId = new Whisper.Conversation({ type: 'private', id: '771d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94abaa' });
const regularId33 = new Whisper.Conversation({
type: 'private',
id:
'051d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab',
});
const regularId32 = new Whisper.Conversation({
type: 'private',
id: '1d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab',
});
const shortId = new Whisper.Conversation({
type: 'private',
id: '771d11d',
});
const longId = new Whisper.Conversation({
type: 'private',
id:
'771d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94abaa',
});
assert.ok(regularId33.isValid());
assert.ok(regularId32.isValid());
assert.notOk(shortId.isValid());
@ -152,7 +185,8 @@ describe('Conversation', () => {
beforeEach(async () => {
convo = new Whisper.ConversationCollection().add({
id: '771d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab',
id:
'771d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab',
type: 'private',
name: 'John Doe',
});
@ -169,7 +203,10 @@ describe('Conversation', () => {
const collection = new Whisper.ConversationCollection();
await collection.search(query);
assert.isDefined(collection.get(convo.id), `no result for "${query}"`);
assert.isDefined(
collection.get(convo.id),
`no result for "${query}"`
);
})
);
}

View File

@ -23,7 +23,7 @@ describe('Message', () => {
body: 'Imagine there is no heaven…',
schemaVersion: 2,
};
const writeExistingAttachmentData = () => { };
const writeExistingAttachmentData = () => {};
const actual = await Message.createAttachmentDataWriter({
writeExistingAttachmentData,
@ -43,7 +43,7 @@ describe('Message', () => {
schemaVersion: 4,
attachments: [],
};
const writeExistingAttachmentData = () => { };
const writeExistingAttachmentData = () => {};
const actual = await Message.createAttachmentDataWriter({
writeExistingAttachmentData,
@ -432,7 +432,7 @@ describe('Message', () => {
describe('_withSchemaVersion', () => {
it('should require a version number', () => {
const toVersionX = () => { };
const toVersionX = () => {};
assert.throws(
() =>
Message._withSchemaVersion({ schemaVersion: toVersionX, upgrade: 2 }),

View File

@ -19,7 +19,8 @@ describe('SignalProtocolStore', () => {
privKey: libsignal.crypto.getRandomBytes(32),
};
storage.put('registrationId', 1337)
storage
.put('registrationId', 1337)
.then(() => storage.put('identityKey', identityKey))
.then(() => storage.fetch())
.then(done, done);

View File

@ -20,7 +20,7 @@ describe('InboxView', () => {
inboxView = new Whisper.InboxView({
model: {},
window,
initialLoadComplete() { },
initialLoadComplete() {},
}).render();
conversation = new Whisper.Conversation({

View File

@ -174,7 +174,7 @@ describe('NetworkStatusView', () => {
/Attempting reconnect/
);
});
it('should be reset by changing the socketStatus to CONNECTING', () => { });
it('should be reset by changing the socketStatus to CONNECTING', () => {});
});
});
});

View File

@ -165,7 +165,13 @@ export class ConversationListItem extends React.Component<Props> {
}
public render() {
const { unreadCount, onClick, isSelected, showFriendRequestIndicator, isBlocked } = this.props;
const {
unreadCount,
onClick,
isSelected,
showFriendRequestIndicator,
isBlocked,
} = this.props;
return (
<div
@ -175,8 +181,10 @@ export class ConversationListItem extends React.Component<Props> {
'module-conversation-list-item',
unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null,
isSelected ? 'module-conversation-list-item--is-selected' : null,
showFriendRequestIndicator ? 'module-conversation-list-item--has-friend-request' : null,
isBlocked ? 'module-conversation-list-item--is-blocked' : null,
showFriendRequestIndicator
? 'module-conversation-list-item--has-friend-request'
: null,
isBlocked ? 'module-conversation-list-item--is-blocked' : null
)}
>
{this.renderAvatar()}

View File

@ -26,11 +26,11 @@ export class MainHeader extends React.Component<Props> {
name,
phoneNumber,
profileName,
onClick
onClick,
} = this.props;
return (
<div role='button' className="module-main-header" onClick={onClick}>
<div role="button" className="module-main-header" onClick={onClick}>
<Avatar
avatarPath={avatarPath}
color={color}

View File

@ -28,7 +28,10 @@ export class ContactName extends React.Component<Props> {
return (
<span className={prefix}>
{profileElement}
<span title={phoneNumber} className={shouldShowProfile ? `${prefix}__profile-number` : ''}>
<span
title={phoneNumber}
className={shouldShowProfile ? `${prefix}__profile-number` : ''}
>
<Emojify text={title} i18n={i18n} />
</span>
</span>

View File

@ -95,12 +95,7 @@ export class ConversationHeader extends React.Component<Props> {
}
public renderTitle() {
const {
phoneNumber,
i18n,
profileName,
isKeysPending,
} = this.props;
const { phoneNumber, i18n, profileName, isKeysPending } = this.props;
return (
<div className="module-conversation-header__title">
@ -236,7 +231,9 @@ export class ConversationHeader extends React.Component<Props> {
<MenuItem onClick={blockHandler}>{blockTitle}</MenuItem>
) : null}
{!isMe ? (
<MenuItem onClick={onChangeNickname}>{i18n('changeNickname')}</MenuItem>
<MenuItem onClick={onChangeNickname}>
{i18n('changeNickname')}
</MenuItem>
) : null}
{!isMe && hasNickname ? (
<MenuItem onClick={onClearNickname}>{i18n('clearNickname')}</MenuItem>

View File

@ -1,9 +1,10 @@
### Friend Requests
#### Parameters
| Name | Values |
| -------------------- | -------------------------------------------- |
| text | string |
| text | string |
| direction | 'outgoing' \| 'incoming |
| status | 'sending' \| 'sent' \| 'read' \| 'delivered' |
| friendStatus | 'pending' \| 'accepted' \| 'declined' |
@ -13,8 +14,8 @@
| onDeleteConversation | function |
| onRetrySend | function |
#### Example
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<li>

View File

@ -31,7 +31,7 @@ export class FriendRequest extends React.Component<Props> {
case 'declined':
return 'friendRequestDeclined';
case 'expired':
return 'friendRequestExpired'
return 'friendRequestExpired';
default:
throw new Error(`Invalid friend request status: ${friendStatus}`);
}
@ -43,7 +43,9 @@ export class FriendRequest extends React.Component<Props> {
return (
<div>
<div className={`module-friend-request__title module-friend-request__title--${direction}`}>
<div
className={`module-friend-request__title module-friend-request__title--${direction}`}
>
{i18n(id)}
</div>
<div dir="auto">
@ -54,55 +56,69 @@ export class FriendRequest extends React.Component<Props> {
}
public renderButtons() {
const { i18n, friendStatus, direction, status, onAccept, onDecline, onDeleteConversation, onRetrySend, isBlocked, onBlockUser, onUnblockUser } = this.props;
const {
i18n,
friendStatus,
direction,
status,
onAccept,
onDecline,
onDeleteConversation,
onRetrySend,
isBlocked,
onBlockUser,
onUnblockUser,
} = this.props;
if (direction === 'incoming') {
if (friendStatus === 'pending') {
return (
<div
className={classNames(
'module-message__metadata',
'module-friend-request__buttonContainer',
`module-friend-request__buttonContainer--${direction}`
)}
>
<button onClick={onAccept}>Accept</button>
<button onClick={onDecline}>Decline</button>
</div>
);
} else if (friendStatus === 'declined') {
const blockTitle = isBlocked ? i18n('unblockUser') : i18n('blockUser');
const blockHandler = isBlocked ? onUnblockUser : onBlockUser;
return (
<div
className={classNames(
'module-message__metadata',
'module-friend-request__buttonContainer',
`module-friend-request__buttonContainer--${direction}`
)}
>
<button onClick={onDeleteConversation}>Delete Conversation</button>
<button onClick={blockHandler}>{blockTitle}</button>
</div>
);
}
} else {
// Render the retry button if we errored
if (status === 'error' && friendStatus === 'pending') {
return (
<div
className={classNames(
'module-message__metadata',
'module-friend-request__buttonContainer',
`module-friend-request__buttonContainer--${direction}`
)}
>
<button onClick={onRetrySend}>Retry Send</button>
</div>
);
}
if (direction === 'incoming') {
if (friendStatus === 'pending') {
return (
<div
className={classNames(
'module-message__metadata',
'module-friend-request__buttonContainer',
`module-friend-request__buttonContainer--${direction}`
)}
>
<button onClick={onAccept}>Accept</button>
<button onClick={onDecline}>Decline</button>
</div>
);
} else if (friendStatus === 'declined') {
const blockTitle = isBlocked ? i18n('unblockUser') : i18n('blockUser');
const blockHandler = isBlocked ? onUnblockUser : onBlockUser;
return (
<div
className={classNames(
'module-message__metadata',
'module-friend-request__buttonContainer',
`module-friend-request__buttonContainer--${direction}`
)}
>
<button onClick={onDeleteConversation}>Delete Conversation</button>
<button onClick={blockHandler}>{blockTitle}</button>
</div>
);
}
return null;
} else {
// Render the retry button if we errored
if (status === 'error' && friendStatus === 'pending') {
return (
<div
className={classNames(
'module-message__metadata',
'module-friend-request__buttonContainer',
`module-friend-request__buttonContainer--${direction}`
)}
>
<button onClick={onRetrySend}>Retry Send</button>
</div>
);
}
}
return null;
}
public renderError(isCorrectSide: boolean) {
@ -127,7 +143,9 @@ export class FriendRequest extends React.Component<Props> {
// Renders 'sending', 'read' icons
public renderStatusIndicator() {
const { direction, status } = this.props;
if (direction !== 'outgoing' || status === 'error') return null;
if (direction !== 'outgoing' || status === 'error') {
return null;
}
return (
<div className="module-message__metadata">
@ -135,7 +153,7 @@ export class FriendRequest extends React.Component<Props> {
<div
className={classNames(
'module-message__metadata__status-icon',
`module-message__metadata__status-icon--${status}`,
`module-message__metadata__status-icon--${status}`
)}
/>
</div>
@ -149,27 +167,27 @@ export class FriendRequest extends React.Component<Props> {
<div
className={classNames(
`module-message module-message--${direction}`,
'module-message-friend-request',
'module-message-friend-request'
)}
>
{this.renderError(direction === 'incoming')}
{this.renderError(direction === 'incoming')}
<div
className={classNames(
'module-message__container',
`module-message__container--${direction}`,
'module-message-friend-request__container',
'module-message-friend-request__container'
)}
>
<div
className={classNames(
'module-message__text',
`module-message__text--${direction}`,
)}
>
{this.renderContents()}
{this.renderStatusIndicator()}
{this.renderButtons()}
</div>
<div
className={classNames(
'module-message__text',
`module-message__text--${direction}`
)}
>
{this.renderContents()}
{this.renderStatusIndicator()}
{this.renderButtons()}
</div>
</div>
{this.renderError(direction === 'outgoing')}
</div>

View File

@ -13,7 +13,7 @@ export class ResetSessionNotification extends React.Component<Props> {
return (
<div className="module-reset-session-notification">
{ i18n(sessionResetMessageKey) }
{i18n(sessionResetMessageKey)}
</div>
);
}

View File

@ -46,7 +46,9 @@ describe('Attachment', () => {
contentType: MIME.VIDEO_QUICKTIME,
};
// Unix timestamp of start of year 2000 to fix odd sudo timezone bug
const timestamp = new Date(946684800000 - moment().utcOffset() * 60 * 1000);
const timestamp = new Date(
946684800000 - moment().utcOffset() * 60 * 1000
);
const actual = Attachment.getSuggestedFilename({
attachment,
timestamp,