Merge pull request #139 from Mikunj/lint

Linting
This commit is contained in:
Mikunj Varsani 2019-01-17 13:25:59 +11:00 committed by GitHub
commit 5e72521b21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 13526 additions and 7907 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

@ -39,6 +39,9 @@ module.exports = {
// consistently place operators at end of line except ternaries
'operator-linebreak': 'error',
// Use LF to stay consistent
'linebreak-style': ['error', 'unix'],
quotes: [
'error',
'single',

10
.gitattributes vendored Normal file
View File

@ -0,0 +1,10 @@
* text=auto
*.js text eol=lf
*.ts text eol=lf
*.tsx text eol=lf
*.scss text eol=lf
*.json text eol=lf
*.css text eol=lf
*.html text eol=lf
*.yaml text eol=lf
*.yml text eol=lf

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

@ -9,6 +9,7 @@ install:
- travis_wait 30 yarn install --frozen-lockfile --network-timeout 1000000
script:
- yarn generate
- yarn lint-windows
- yarn test
env:
global:
@ -35,3 +36,9 @@ matrix:
cache: false
env:
- YARN_GPG=no
before_install:
- cd ../..
- mv $TRAVIS_REPO_SLUG _old
- git config --global core.autocrlf false
- git clone --depth=50 _old $TRAVIS_REPO_SLUG
- cd $TRAVIS_REPO_SLUG

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

@ -724,9 +724,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;
@ -769,9 +772,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;
@ -792,9 +798,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);
@ -825,9 +834,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;
@ -847,9 +859,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);
@ -1049,8 +1064,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 (
@ -1105,8 +1128,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
@ -1366,10 +1397,7 @@ async function saveSeenMessageHashes(arrayOfHashes) {
}
async function saveSeenMessageHash(data) {
const {
expiresAt,
hash,
} = data;
const { expiresAt, hash } = data;
await db.run(
`INSERT INTO seenMessages (
expiresAt,
@ -1377,7 +1405,8 @@ async function saveSeenMessageHash(data) {
) values (
$expiresAt,
$hash
);`, {
);`,
{
$expiresAt: expiresAt,
$hash: hash,
}
@ -1489,7 +1518,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
@ -1522,7 +1552,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

@ -21,7 +21,9 @@
window.log.info('BlockedNumberController: starting initial fetch');
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
@ -40,8 +42,7 @@
storage.addBlockedNumber(number);
// Make sure we don't add duplicates
if (blockedNumbers.getModel(number))
return;
if (blockedNumbers.getModel(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();
@ -193,7 +200,9 @@
return conversation;
};
conversation.initialPromise = create().then(() => conversation.updateProfileAvatar());
conversation.initialPromise = create().then(() =>
conversation.updateProfileAvatar()
);
return conversation;
},
@ -265,7 +274,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;
@ -323,7 +324,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()));
},
@ -372,8 +375,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;
});
},
@ -462,7 +464,7 @@
if (!this.isPrivate()) {
throw new Error(
'You cannot verify a group conversation. ' +
'You must verify individual contacts.'
'You must verify individual contacts.'
);
}
@ -547,18 +549,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')) {
@ -582,8 +593,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, {
@ -610,7 +620,9 @@
);
},
async resetPendingSend() {
if (this.get('friendRequestStatus') === FriendRequestStatusEnum.pendingSend) {
if (
this.get('friendRequestStatus') === FriendRequestStatusEnum.pendingSend
) {
await this.setFriendRequestStatus(FriendRequestStatusEnum.none);
}
},
@ -645,8 +657,7 @@
},
async onFriendRequestTimeout() {
// Unset the timer
if (this.unlockTimer)
clearTimeout(this.unlockTimer);
if (this.unlockTimer) clearTimeout(this.unlockTimer);
this.unlockTimer = null;
@ -745,7 +756,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.'
);
}
@ -1055,9 +1066,9 @@
fileName: fileName || null,
thumbnail: thumbnail
? {
...(await loadAttachmentData(thumbnail)),
objectUrl: getAbsoluteAttachmentPath(thumbnail.path),
}
...(await loadAttachmentData(thumbnail)),
objectUrl: getAbsoluteAttachmentPath(thumbnail.path),
}
: null,
};
})
@ -1067,8 +1078,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();
@ -1108,7 +1118,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
@ -1128,7 +1140,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({
@ -1213,7 +1227,7 @@
options
)
)
)
);
});
return true;
@ -1375,8 +1389,8 @@
accessKey && sealedSender === SEALED_SENDER.ENABLED
? accessKey
: window.Signal.Crypto.arrayBufferToBase64(
window.Signal.Crypto.getRandomBytes(16)
),
window.Signal.Crypto.getRandomBytes(16)
),
},
};
},
@ -1533,8 +1547,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, {
@ -1553,7 +1566,9 @@
},
isSessionResetReceived() {
return this.get('sessionResetStatus') === SessionResetEnum.request_received;
return (
this.get('sessionResetStatus') === SessionResetEnum.request_received
);
},
isSessionResetOngoing() {
@ -1585,7 +1600,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);
},
@ -1594,13 +1612,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()) {
@ -1700,7 +1727,7 @@
} else {
window.log.warn(
'Marked a message as read in the database, but ' +
'it was not in messageCollection.'
'it was not in messageCollection.'
);
}
@ -2063,7 +2090,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 };
@ -2091,7 +2119,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();
@ -2127,8 +2155,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 => {
if (typeof newName !== 'string' && newName !== null) {
throw Error('Name must be a string!');
}
@ -33,13 +33,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
@ -49,10 +49,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

@ -573,7 +573,6 @@ async function removeAllContactSignedPreKeys() {
await channels.removeAllContactSignedPreKeys();
}
// Items
const ITEM_KEYS = {
@ -690,7 +689,7 @@ async function saveConversations(data) {
}
async function getConversationById(id, { Conversation }) {
const rawData = await channels.getConversationById(id)
const rawData = await channels.getConversationById(id);
const data = setifyProperty(rawData, 'swarmNodes');
return new Conversation(data);
}
@ -725,8 +724,9 @@ async function _removeConversations(ids) {
}
async function getAllConversations({ ConversationCollection }) {
const conversations = (await channels.getAllConversations())
.map(c => setifyProperty(c, 'swarmNodes'));
const conversations = (await channels.getAllConversations()).map(c =>
setifyProperty(c, 'swarmNodes')
);
const collection = new ConversationCollection();
collection.add(conversations);
@ -739,8 +739,9 @@ async function getAllConversationIds() {
}
async function getAllPrivateConversations({ ConversationCollection }) {
const conversations = (await channels.getAllPrivateConversations())
.map(c => setifyProperty(c, 'swarmNodes'));
const conversations = (await channels.getAllPrivateConversations()).map(c =>
setifyProperty(c, 'swarmNodes')
);
const collection = new ConversationCollection();
collection.add(conversations);
@ -748,8 +749,9 @@ async function getAllPrivateConversations({ ConversationCollection }) {
}
async function getAllGroupsInvolvingId(id, { ConversationCollection }) {
const conversations = (await channels.getAllGroupsInvolvingId(id))
.map(c => setifyProperty(c, 'swarmNodes'));
const conversations = (await channels.getAllGroupsInvolvingId(id)).map(c =>
setifyProperty(c, 'swarmNodes')
);
const collection = new ConversationCollection();
collection.add(conversations);
@ -757,8 +759,9 @@ async function getAllGroupsInvolvingId(id, { ConversationCollection }) {
}
async function searchConversations(query, { ConversationCollection }) {
const conversations = (await channels.searchConversations(query))
.map(c => setifyProperty(c, 'swarmNodes'));
const conversations = (await channels.searchConversations(query)).map(c =>
setifyProperty(c, 'swarmNodes')
);
const collection = new ConversationCollection();
collection.add(conversations);
@ -905,9 +908,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,21 +4,18 @@
const fetch = require('node-fetch');
// eslint-disable-next-line
const invert = p => new Promise((res, rej) => p.then(rej, res));
const invert = p => new Promise((res, rej) => p.then(rej, res));
const firstOf = ps => invert(Promise.all(ps.map(invert)));
// Will be raised (to 3?) when we get more nodes
const MINIMUM_SUCCESSFUL_REQUESTS = 2;
class LokiMessageAPI {
constructor({ messageServerPort }) {
this.messageServerPort = messageServerPort
? `:${messageServerPort}`
: '';
this.messageServerPort = messageServerPort ? `:${messageServerPort}` : '';
}
async sendMessage(pubKey, data, messageTimeStamp, ttl) {
const swarmNodes = await window.LokiSnodeAPI.getSwarmNodesByPubkey(pubKey)
const swarmNodes = await window.LokiSnodeAPI.getSwarmNodesByPubkey(pubKey);
if (!swarmNodes || swarmNodes.size === 0) {
throw Error('No swarm nodes to query!');
}
@ -33,7 +30,14 @@ class LokiMessageAPI {
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
throw err;
@ -84,14 +88,19 @@ class LokiMessageAPI {
if (response.status >= 0 && response.status < 400) {
return result;
}
log.error(options.type, options.url, response.status, 'Error sending message');
log.error(
options.type,
options.url,
response.status,
'Error sending message'
);
throw HTTPError('sendMessage: error response', response.status, result);
});
try {
// TODO: Possibly change this to require more than a single response?
const result = await firstOf(requests);
return result;
} catch(err) {
} catch (err) {
throw err;
}
}
@ -128,7 +137,12 @@ class LokiMessageAPI {
} catch (e) {
// TODO: Maybe we shouldn't immediately delete?
// And differentiate between different connectivity issues
log.error(options.type, options.url, 0, `Error retrieving messages from ${nodeUrl}`);
log.error(
options.type,
options.url,
0,
`Error retrieving messages from ${nodeUrl}`
);
window.LokiSnodeAPI.unreachableNode(ourKey, nodeUrl);
return;
}
@ -155,7 +169,7 @@ class LokiMessageAPI {
}
// Handle error from snode
log.error(options.type, options.url, response.status, 'Error');
}
};
while (completedRequests < MINIMUM_SUCCESSFUL_REQUESTS) {
const remainingRequests = MINIMUM_SUCCESSFUL_REQUESTS - completedRequests;

View File

@ -8,15 +8,12 @@ const dns = require('dns');
const MINIMUM_SWARM_NODES = 1;
class LokiSnodeAPI {
constructor({ url, swarmServerPort }) {
if (!is.string(url)) {
throw new Error('WebAPI.initialize: Invalid server url');
}
this.url = url;
this.swarmServerPort = swarmServerPort
? `:${swarmServerPort}`
: '';
this.swarmServerPort = swarmServerPort ? `:${swarmServerPort}` : '';
this.swarmsPendingReplenish = {};
this.ourSwarmNodes = {};
}
@ -25,7 +22,7 @@ class LokiSnodeAPI {
/* resolve random snode */
return new Promise((resolve, reject) => {
dns.resolveCname(this.url, (err, address) => {
if(err) {
if (err) {
reject(err);
} else {
resolve(address[0]);
@ -43,9 +40,13 @@ class LokiSnodeAPI {
const swarmNodes = conversation.get('swarmNodes');
if (swarmNodes.delete(nodeUrl)) {
conversation.set({ swarmNodes });
await window.Signal.Data.updateConversation(conversation.id, conversation.attributes, {
Conversation: Whisper.Conversation,
});
await window.Signal.Data.updateConversation(
conversation.id,
conversation.attributes,
{
Conversation: Whisper.Conversation,
}
);
}
}
@ -53,7 +54,7 @@ class LokiSnodeAPI {
if (!this.ourSwarmNodes[nodeUrl]) {
this.ourSwarmNodes[nodeUrl] = {
lastHash: hash,
}
};
} else {
this.ourSwarmNodes[nodeUrl].lastHash = hash;
}
@ -69,7 +70,7 @@ class LokiSnodeAPI {
const ourKey = window.textsecure.storage.user.getNumber();
const nodeAddresses = await window.LokiSnodeAPI.getSwarmNodes(ourKey);
if (!nodeAddresses || nodeAddresses.length === 0) {
throw Error('Could not load our swarm')
throw Error('Could not load our swarm');
}
nodeAddresses.forEach(url => {
@ -91,8 +92,8 @@ class LokiSnodeAPI {
async replenishSwarm(pubKey) {
const conversation = window.ConversationController.get(pubKey);
if (!(pubKey in this.swarmsPendingReplenish)) {
this.swarmsPendingReplenish[pubKey] = new Promise(async (resolve) => {
let newSwarmNodes
this.swarmsPendingReplenish[pubKey] = new Promise(async resolve => {
let newSwarmNodes;
try {
newSwarmNodes = new Set(await this.getSwarmNodes(pubKey));
} catch (e) {
@ -100,9 +101,13 @@ class LokiSnodeAPI {
newSwarmNodes = new Set([]);
}
conversation.set({ swarmNodes: newSwarmNodes });
await window.Signal.Data.updateConversation(conversation.id, conversation.attributes, {
Conversation: Whisper.Conversation,
});
await window.Signal.Data.updateConversation(
conversation.id,
conversation.attributes,
{
Conversation: Whisper.Conversation,
}
);
resolve(newSwarmNodes);
});
}
@ -129,7 +134,7 @@ class LokiSnodeAPI {
params: {
pubkey: pubKey,
},
}
};
const fetchOptions = {
method: options.type,
@ -144,7 +149,12 @@ class LokiSnodeAPI {
try {
response = await fetch(options.url, fetchOptions);
} catch (e) {
log.error(options.type, options.url, 0, `Error getting swarm nodes for ${pubKey}`);
log.error(
options.type,
options.url,
0,
`Error getting swarm nodes for ${pubKey}`
);
throw HTTPError('fetch error', 0, e.toString());
}
@ -163,7 +173,12 @@ class LokiSnodeAPI {
if (response.status >= 0 && response.status < 400) {
return result.nodes;
}
log.error(options.type, options.url, response.status, `Error getting swarm nodes for ${pubKey}`);
log.error(
options.type,
options.url,
response.status,
`Error getting swarm nodes for ${pubKey}`
);
throw HTTPError('sendMessage: 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

@ -8,87 +8,89 @@
// 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(() => {
BlockedNumberController.refresh();
this.collection = getBlockedNumbers();
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(() => {
BlockedNumberController.refresh();
this.collection = getBlockedNumbers();
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, selector = (x) => x){
function consolidateLists(lists, threshold, selector = x => x) {
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', () => {
@ -19,10 +25,10 @@ describe('ServiceNodes', () => {
it('should throw when provided a non-function selector', () => {
[1, 'a', 0xffffffff, { really: 'not a function' }].forEach(x => {
assert.throws(() =>
libloki.serviceNodes.consolidateLists([], 1, x),
assert.throws(
() => libloki.serviceNodes.consolidateLists([], 1, x),
'Provided selector is not a function'
)
);
});
});
@ -32,64 +38,85 @@ describe('ServiceNodes', () => {
});
it('should return the input when only 1 list is provided', () => {
const result = libloki.serviceNodes.consolidateLists([['a', 'b', 'c']], 1);
const result = libloki.serviceNodes.consolidateLists(
[['a', 'b', 'c']],
1
);
assert.deepEqual(result, ['a', 'b', 'c']);
});
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 use the selector to identify the elements', () => {
const result = libloki.serviceNodes.consolidateLists([
[{ id: 1, val: 'a'}, { id: 2, val: 'b'}, { id: 3, val: 'c'}, { id: 8, val: 'h'}],
[{ id: 4, val: 'd'}, { id: 5, val: 'e'}, { id: 6, val: 'f'}, { id: 7, val: 'g'}],
[{ id: 7, val: 'g'}, { id: 8, val: 'h'}],
], 0, x => x.id);
const result = libloki.serviceNodes.consolidateLists(
[
[
{ id: 1, val: 'a' },
{ id: 2, val: 'b' },
{ id: 3, val: 'c' },
{ id: 8, val: 'h' },
],
[
{ id: 4, val: 'd' },
{ id: 5, val: 'e' },
{ id: 6, val: 'f' },
{ id: 7, val: 'g' },
],
[{ id: 7, val: 'g' }, { id: 8, val: 'h' }],
],
0,
x => x.id
);
const expected = [
{ id: 1, val: 'a'},
{ id: 2, val: 'b'},
{ id: 3, val: 'c'},
{ id: 4, val: 'd'},
{ id: 5, val: 'e'},
{ id: 6, val: 'f'},
{ id: 7, val: 'g'},
{ id: 8, val: 'h'},
{ id: 1, val: 'a' },
{ id: 2, val: 'b' },
{ id: 3, val: 'c' },
{ id: 4, val: 'd' },
{ id: 5, val: 'e' },
{ id: 6, val: 'f' },
{ id: 7, val: 'g' },
{ id: 8, val: 'h' },
];
assert.deepEqual(result.sort((a, b) => a.val > b.val), expected);
});
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 */
// 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,7 +63,7 @@
let { handleRequest } = opts;
if (typeof handleRequest !== 'function') {
handleRequest = request => request.respond(404, 'Not found');
};
}
let connected = false;
const processMessages = async messages => {
@ -67,8 +71,12 @@
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,
@ -79,7 +87,7 @@
);
}
});
}
};
this.startPolling = async function pollServer(callback) {
try {
@ -89,11 +97,13 @@
connected = false;
}
callback(connected);
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.lokiMessageAPI, {
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.lokiMessageAPI.sendMessage(pubKey, data, timestamp, ttl);
const result = await this.lokiMessageAPI.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

@ -453,11 +453,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;
}
@ -965,7 +965,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);
@ -981,7 +982,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.
@ -989,7 +990,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');
@ -289,7 +293,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

@ -3,7 +3,6 @@
'use strict';
describe('Blocked Number Controller', () => {
beforeEach(async () => {
// Purge everything manually
const numbers = storage.getBlockedNumbers();
@ -43,18 +42,25 @@ describe('Blocked Number Controller', () => {
storage.addBlockedNumber('1');
BlockedNumberController.refresh();
assert.isNotEmpty(window.getBlockedNumbers().find(m => m.get('number') === '1'));
assert.isNotEmpty(
window.getBlockedNumbers().find(m => m.get('number') === '1')
);
storage.removeBlockedNumber('1');
storage.addBlockedNumber('2');
BlockedNumberController.refresh();
assert.isNotEmpty(window.getBlockedNumbers().find(m => m.get('number') === '2'));
assert.isNotEmpty(
window.getBlockedNumbers().find(m => m.get('number') === '2')
);
});
it('throws if storage is invalid', () => {
const _storage = window.storage;
window.storage = null;
assert.throws(() => BlockedNumberController.refresh(), 'BlockedNumberController: Could not load blocked numbers');
assert.throws(
() => BlockedNumberController.refresh(),
'BlockedNumberController: Could not load blocked numbers'
);
window.storage = _storage;
});
});
@ -71,7 +77,7 @@ describe('Blocked Number Controller', () => {
const numbers = window.getBlockedNumbers().models;
assert.lengthOf(numbers, 1);
assert.strictEqual('1', numbers[0].get('number'))
assert.strictEqual('1', numbers[0].get('number'));
assert.deepEqual(['1'], storage.getBlockedNumbers());
});

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

@ -15,7 +15,7 @@ describe('Profile', () => {
describe('getLocalProfile', () => {
it('returns the local profile', async () => {
const values = [null, 'hello', { a: 'b' }];
for(let i = 0; i < values.length; i += 1) {
for (let i = 0; i < values.length; i += 1) {
await storage.put(PROFILE_ID, values[i]);
assert.strictEqual(values[i], storage.getLocalProfile());
}
@ -25,7 +25,7 @@ describe('Profile', () => {
describe('saveLocalProfile', () => {
it('saves a profile', async () => {
const values = [null, 'hello', { a: 'b' }];
for(let i = 0; i < values.length; i += 1) {
for (let i = 0; i < values.length; i += 1) {
await storage.saveLocalProfile(values[i]);
assert.strictEqual(values[i], storage.get(PROFILE_ID));
}
@ -44,20 +44,24 @@ describe('Profile', () => {
describe('setProfileName', () => {
it('throws if a name is not a string', async () => {
const values = [0, { a: 'b'}, [1, 2]];
for(let i = 0; i < values.length; i += 1) {
const values = [0, { a: 'b' }, [1, 2]];
for (let i = 0; i < values.length; i += 1) {
try {
await storage.setProfileName(values[i]);
assert.fail(`setProfileName did not throw an error for ${typeof values[i]}`);
assert.fail(
`setProfileName did not throw an error for ${typeof values[i]}`
);
} catch (e) {
assert.throws(() => { throw e; }, 'Name must be a string!');
assert.throws(() => {
throw e;
}, 'Name must be a string!');
}
}
});
it('does not throw if we pass a string or null', async () => {
const values = [null, '1'];
for(let i = 0; i < values.length; i += 1) {
for (let i = 0; i < values.length; i += 1) {
try {
await storage.setProfileName(values[i]);
} catch (e) {

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,