Format all source code using Prettier

This commit is contained in:
Daniel Gasienica 2018-04-27 17:25:04 -04:00
parent b4dee3f30b
commit 1dd87ad197
149 changed files with 17847 additions and 15439 deletions

View file

@ -20,21 +20,25 @@ exports.autoOrientImage = (fileOrBlobOrURL, options = {}) => {
);
return new Promise((resolve, reject) => {
loadImage(fileOrBlobOrURL, (canvasOrError) => {
if (canvasOrError.type === 'error') {
const error = new Error('autoOrientImage: Failed to process image');
error.cause = canvasOrError;
reject(error);
return;
}
loadImage(
fileOrBlobOrURL,
canvasOrError => {
if (canvasOrError.type === 'error') {
const error = new Error('autoOrientImage: Failed to process image');
error.cause = canvasOrError;
reject(error);
return;
}
const canvas = canvasOrError;
const dataURL = canvas.toDataURL(
optionsWithDefaults.type,
optionsWithDefaults.quality
);
const canvas = canvasOrError;
const dataURL = canvas.toDataURL(
optionsWithDefaults.type,
optionsWithDefaults.quality
);
resolve(dataURL);
}, optionsWithDefaults);
resolve(dataURL);
},
optionsWithDefaults
);
});
};

View file

@ -23,12 +23,7 @@ const electronRemote = require('electron').remote;
const Attachment = require('./types/attachment');
const crypto = require('./crypto');
const {
dialog,
BrowserWindow,
} = electronRemote;
const { dialog, BrowserWindow } = electronRemote;
module.exports = {
getDirectoryForExport,
@ -44,7 +39,6 @@ module.exports = {
_getConversationLoggingName,
};
function stringify(object) {
// eslint-disable-next-line no-restricted-syntax
for (const key in object) {
@ -69,10 +63,12 @@ function unstringify(object) {
// eslint-disable-next-line no-restricted-syntax
for (const key in object) {
const val = object[key];
if (val &&
val.type === 'ArrayBuffer' &&
val.encoding === 'base64' &&
typeof val.data === 'string') {
if (
val &&
val.type === 'ArrayBuffer' &&
val.encoding === 'base64' &&
typeof val.data === 'string'
) {
object[key] = dcodeIO.ByteBuffer.wrap(val.data, 'base64').toArrayBuffer();
} else if (val instanceof Object) {
object[key] = unstringify(object[key]);
@ -86,19 +82,22 @@ function createOutputStream(writer) {
return {
write(string) {
// eslint-disable-next-line more/no-then
wait = wait.then(() => new Promise((resolve) => {
if (writer.write(string)) {
resolve();
return;
}
wait = wait.then(
() =>
new Promise(resolve => {
if (writer.write(string)) {
resolve();
return;
}
// If write() returns true, we don't need to wait for the drain event
// https://nodejs.org/dist/latest-v7.x/docs/api/stream.html#stream_class_stream_writable
writer.once('drain', resolve);
// If write() returns true, we don't need to wait for the drain event
// https://nodejs.org/dist/latest-v7.x/docs/api/stream.html#stream_class_stream_writable
writer.once('drain', resolve);
// We don't register for the 'error' event here, only in close(). Otherwise,
// we'll get "Possible EventEmitter memory leak detected" warnings.
}));
// We don't register for the 'error' event here, only in close(). Otherwise,
// we'll get "Possible EventEmitter memory leak detected" warnings.
})
);
return wait;
},
async close() {
@ -141,7 +140,7 @@ function exportContactsAndGroups(db, fileWriter) {
stream.write('{');
_.each(storeNames, (storeName) => {
_.each(storeNames, storeName => {
// Both the readwrite permission and the multi-store transaction are required to
// keep this function working. They serve to serialize all of these transactions,
// one per store to be exported.
@ -167,7 +166,7 @@ function exportContactsAndGroups(db, fileWriter) {
reject
);
};
request.onsuccess = async (event) => {
request.onsuccess = async event => {
if (count === 0) {
console.log('cursor opened');
stream.write(`"${storeName}": [`);
@ -180,10 +179,7 @@ function exportContactsAndGroups(db, fileWriter) {
}
// Preventing base64'd images from reaching the disk, making db.json too big
const item = _.omit(
cursor.value,
['avatar', 'profileAvatar']
);
const item = _.omit(cursor.value, ['avatar', 'profileAvatar']);
const jsonString = JSON.stringify(stringify(item));
stream.write(jsonString);
@ -235,10 +231,7 @@ function importFromJsonString(db, jsonString, targetPath, options) {
groupLookup: {},
});
const {
conversationLookup,
groupLookup,
} = options;
const { conversationLookup, groupLookup } = options;
const result = {
fullImport: true,
};
@ -269,7 +262,7 @@ function importFromJsonString(db, jsonString, targetPath, options) {
console.log('Importing to these stores:', storeNames.join(', '));
let finished = false;
const finish = (via) => {
const finish = via => {
console.log('non-messages import done via', via);
if (finished) {
resolve(result);
@ -287,7 +280,7 @@ function importFromJsonString(db, jsonString, targetPath, options) {
};
transaction.oncomplete = finish.bind(null, 'transaction complete');
_.each(storeNames, (storeName) => {
_.each(storeNames, storeName => {
console.log('Importing items for store', storeName);
if (!importObject[storeName].length) {
@ -316,14 +309,14 @@ function importFromJsonString(db, jsonString, targetPath, options) {
}
};
_.each(importObject[storeName], (toAdd) => {
_.each(importObject[storeName], toAdd => {
toAdd = unstringify(toAdd);
const haveConversationAlready =
storeName === 'conversations' &&
conversationLookup[getConversationKey(toAdd)];
storeName === 'conversations' &&
conversationLookup[getConversationKey(toAdd)];
const haveGroupAlready =
storeName === 'groups' && groupLookup[getGroupKey(toAdd)];
storeName === 'groups' && groupLookup[getGroupKey(toAdd)];
if (haveConversationAlready || haveGroupAlready) {
skipCount += 1;
@ -365,7 +358,7 @@ function createDirectory(parent, name) {
return;
}
fs.mkdir(targetDir, (error) => {
fs.mkdir(targetDir, error => {
if (error) {
reject(error);
return;
@ -377,7 +370,7 @@ function createDirectory(parent, name) {
}
function createFileAndWriter(parent, name) {
return new Promise((resolve) => {
return new Promise(resolve => {
const sanitized = _sanitizeFileName(name);
const targetPath = path.join(parent, sanitized);
const options = {
@ -430,7 +423,6 @@ function _trimFileName(filename) {
return `${name.join('.').slice(0, 24)}.${extension}`;
}
function _getExportAttachmentFileName(message, index, attachment) {
if (attachment.fileName) {
return _trimFileName(attachment.fileName);
@ -440,7 +432,9 @@ function _getExportAttachmentFileName(message, index, attachment) {
if (attachment.contentType) {
const components = attachment.contentType.split('/');
name += `.${components.length > 1 ? components[1] : attachment.contentType}`;
name += `.${
components.length > 1 ? components[1] : attachment.contentType
}`;
}
return name;
@ -477,14 +471,11 @@ async function readAttachment(dir, attachment, name, options) {
}
async function writeThumbnail(attachment, options) {
const {
dir,
const { dir, message, index, key, newKey } = options;
const filename = `${_getAnonymousAttachmentFileName(
message,
index,
key,
newKey,
} = options;
const filename = `${_getAnonymousAttachmentFileName(message, index)}-thumbnail`;
index
)}-thumbnail`;
const target = path.join(dir, filename);
const { thumbnail } = attachment;
@ -504,26 +495,28 @@ async function writeThumbnails(rawQuotedAttachments, options) {
const { name } = options;
const { loadAttachmentData } = Signal.Migrations;
const promises = rawQuotedAttachments.map(async (attachment) => {
const promises = rawQuotedAttachments.map(async attachment => {
if (!attachment || !attachment.thumbnail || !attachment.thumbnail.path) {
return attachment;
}
return Object.assign(
{},
attachment,
{ thumbnail: await loadAttachmentData(attachment.thumbnail) }
);
return Object.assign({}, attachment, {
thumbnail: await loadAttachmentData(attachment.thumbnail),
});
});
const attachments = await Promise.all(promises);
try {
await Promise.all(_.map(
attachments,
(attachment, index) => writeThumbnail(attachment, Object.assign({}, options, {
index,
}))
));
await Promise.all(
_.map(attachments, (attachment, index) =>
writeThumbnail(
attachment,
Object.assign({}, options, {
index,
})
)
)
);
} catch (error) {
console.log(
'writeThumbnails: error exporting conversation',
@ -536,13 +529,7 @@ async function writeThumbnails(rawQuotedAttachments, options) {
}
async function writeAttachment(attachment, options) {
const {
dir,
message,
index,
key,
newKey,
} = options;
const { dir, message, index, key, newKey } = options;
const filename = _getAnonymousAttachmentFileName(message, index);
const target = path.join(dir, filename);
if (!Attachment.hasData(attachment)) {
@ -562,11 +549,13 @@ async function writeAttachments(rawAttachments, options) {
const { loadAttachmentData } = Signal.Migrations;
const attachments = await Promise.all(rawAttachments.map(loadAttachmentData));
const promises = _.map(
attachments,
(attachment, index) => writeAttachment(attachment, Object.assign({}, options, {
index,
}))
const promises = _.map(attachments, (attachment, index) =>
writeAttachment(
attachment,
Object.assign({}, options, {
index,
})
)
);
try {
await Promise.all(promises);
@ -582,12 +571,7 @@ async function writeAttachments(rawAttachments, options) {
}
async function writeEncryptedAttachment(target, data, options = {}) {
const {
key,
newKey,
filename,
dir,
} = options;
const { key, newKey, filename, dir } = options;
if (fs.existsSync(target)) {
if (newKey) {
@ -613,13 +597,7 @@ function _sanitizeFileName(filename) {
async function exportConversation(db, conversation, options) {
options = options || {};
const {
name,
dir,
attachmentsDir,
key,
newKey,
} = options;
const { name, dir, attachmentsDir, key, newKey } = options;
if (!name) {
throw new Error('Need a name!');
}
@ -670,7 +648,7 @@ async function exportConversation(db, conversation, options) {
reject
);
};
request.onsuccess = async (event) => {
request.onsuccess = async event => {
const cursor = event.target.result;
if (cursor) {
const message = cursor.value;
@ -688,13 +666,12 @@ async function exportConversation(db, conversation, options) {
// eliminate attachment data from the JSON, since it will go to disk
// Note: this is for legacy messages only, which stored attachment data in the db
message.attachments = _.map(
attachments,
attachment => _.omit(attachment, ['data'])
message.attachments = _.map(attachments, attachment =>
_.omit(attachment, ['data'])
);
// completely drop any attachments in messages cached in error objects
// TODO: move to lodash. Sadly, a number of the method signatures have changed!
message.errors = _.map(message.errors, (error) => {
message.errors = _.map(message.errors, error => {
if (error && error.args) {
error.args = [];
}
@ -709,13 +686,14 @@ async function exportConversation(db, conversation, options) {
console.log({ backupMessage: message });
if (attachments && attachments.length > 0) {
const exportAttachments = () => writeAttachments(attachments, {
dir: attachmentsDir,
name,
message,
key,
newKey,
});
const exportAttachments = () =>
writeAttachments(attachments, {
dir: attachmentsDir,
name,
message,
key,
newKey,
});
// eslint-disable-next-line more/no-then
promiseChain = promiseChain.then(exportAttachments);
@ -723,13 +701,14 @@ async function exportConversation(db, conversation, options) {
const quoteThumbnails = message.quote && message.quote.attachments;
if (quoteThumbnails && quoteThumbnails.length > 0) {
const exportQuoteThumbnails = () => writeThumbnails(quoteThumbnails, {
dir: attachmentsDir,
name,
message,
key,
newKey,
});
const exportQuoteThumbnails = () =>
writeThumbnails(quoteThumbnails, {
dir: attachmentsDir,
name,
message,
key,
newKey,
});
// eslint-disable-next-line more/no-then
promiseChain = promiseChain.then(exportQuoteThumbnails);
@ -739,11 +718,7 @@ async function exportConversation(db, conversation, options) {
cursor.continue();
} else {
try {
await Promise.all([
stream.write(']}'),
promiseChain,
stream.close(),
]);
await Promise.all([stream.write(']}'), promiseChain, stream.close()]);
} catch (error) {
console.log(
'exportConversation: error exporting conversation',
@ -791,12 +766,7 @@ function _getConversationLoggingName(conversation) {
function exportConversations(db, options) {
options = options || {};
const {
messagesDir,
attachmentsDir,
key,
newKey,
} = options;
const { messagesDir, attachmentsDir, key, newKey } = options;
if (!messagesDir) {
return Promise.reject(new Error('Need a messages directory!'));
@ -828,7 +798,7 @@ function exportConversations(db, options) {
reject
);
};
request.onsuccess = async (event) => {
request.onsuccess = async event => {
const cursor = event.target.result;
if (cursor && cursor.value) {
const conversation = cursor.value;
@ -873,7 +843,7 @@ function getDirectory(options) {
buttonLabel: options.buttonLabel,
};
dialog.showOpenDialog(browserWindow, dialogOptions, (directory) => {
dialog.showOpenDialog(browserWindow, dialogOptions, directory => {
if (!directory || !directory[0]) {
const error = new Error('Error choosing directory');
error.name = 'ChooseError';
@ -940,7 +910,7 @@ async function saveAllMessages(db, rawMessages) {
return new Promise((resolve, reject) => {
let finished = false;
const finish = (via) => {
const finish = via => {
console.log('messages done saving via', via);
if (finished) {
resolve();
@ -962,7 +932,7 @@ async function saveAllMessages(db, rawMessages) {
const { conversationId } = messages[0];
let count = 0;
_.forEach(messages, (message) => {
_.forEach(messages, message => {
const request = store.put(message, message.id);
request.onsuccess = () => {
count += 1;
@ -997,11 +967,7 @@ async function importConversation(db, dir, options) {
options = options || {};
_.defaults(options, { messageLookup: {} });
const {
messageLookup,
attachmentsDir,
key,
} = options;
const { messageLookup, attachmentsDir, key } = options;
let conversationId = 'unknown';
let total = 0;
@ -1018,11 +984,13 @@ async function importConversation(db, dir, options) {
const json = JSON.parse(contents);
if (json.messages && json.messages.length) {
conversationId = `[REDACTED]${(json.messages[0].conversationId || '').slice(-3)}`;
conversationId = `[REDACTED]${(json.messages[0].conversationId || '').slice(
-3
)}`;
}
total = json.messages.length;
const messages = _.filter(json.messages, (message) => {
const messages = _.filter(json.messages, message => {
message = unstringify(message);
if (messageLookup[getMessageKey(message)]) {
@ -1031,7 +999,9 @@ async function importConversation(db, dir, options) {
}
const hasAttachments = message.attachments && message.attachments.length;
const hasQuotedAttachments = message.quote && message.quote.attachments &&
const hasQuotedAttachments =
message.quote &&
message.quote.attachments &&
message.quote.attachments.length > 0;
if (hasAttachments || hasQuotedAttachments) {
@ -1039,8 +1009,8 @@ async function importConversation(db, dir, options) {
const getName = attachmentsDir
? _getAnonymousAttachmentFileName
: _getExportAttachmentFileName;
const parentDir = attachmentsDir ||
path.join(dir, message.received_at.toString());
const parentDir =
attachmentsDir || path.join(dir, message.received_at.toString());
await loadAttachments(parentDir, getName, {
message,
@ -1075,12 +1045,13 @@ async function importConversations(db, dir, options) {
const contents = await getDirContents(dir);
let promiseChain = Promise.resolve();
_.forEach(contents, (conversationDir) => {
_.forEach(contents, conversationDir => {
if (!fs.statSync(conversationDir).isDirectory()) {
return;
}
const loadConversation = () => importConversation(db, conversationDir, options);
const loadConversation = () =>
importConversation(db, conversationDir, options);
// eslint-disable-next-line more/no-then
promiseChain = promiseChain.then(loadConversation);
@ -1142,7 +1113,7 @@ function assembleLookup(db, storeName, keyFunction) {
reject
);
};
request.onsuccess = (event) => {
request.onsuccess = event => {
const cursor = event.target.result;
if (cursor && cursor.value) {
lookup[keyFunction(cursor.value)] = true;
@ -1175,7 +1146,7 @@ function createZip(zipDir, targetDir) {
resolve(target);
});
archive.on('warning', (error) => {
archive.on('warning', error => {
console.log(`Archive generation warning: ${error.stack}`);
});
archive.on('error', reject);
@ -1247,10 +1218,13 @@ async function exportToDirectory(directory, options) {
const attachmentsDir = await createDirectory(directory, 'attachments');
await exportContactAndGroupsToFile(db, stagingDir);
await exportConversations(db, Object.assign({}, options, {
messagesDir: stagingDir,
attachmentsDir,
}));
await exportConversations(
db,
Object.assign({}, options, {
messagesDir: stagingDir,
attachmentsDir,
})
);
const zip = await createZip(encryptionDir, stagingDir);
await encryptFile(zip, path.join(directory, 'messages.zip'), options);
@ -1302,7 +1276,9 @@ async function importFromDirectory(directory, options) {
if (fs.existsSync(zipPath)) {
// we're in the world of an encrypted, zipped backup
if (!options.key) {
throw new Error('Importing an encrypted backup; decryption key is required!');
throw new Error(
'Importing an encrypted backup; decryption key is required!'
);
}
let stagingDir;

View file

@ -19,8 +19,15 @@ async function encryptSymmetric(key, plaintext) {
const cipherKey = await _hmac_SHA256(key, nonce);
const macKey = await _hmac_SHA256(key, cipherKey);
const cipherText = await _encrypt_aes256_CBC_PKCSPadding(cipherKey, iv, plaintext);
const mac = _getFirstBytes(await _hmac_SHA256(macKey, cipherText), MAC_LENGTH);
const cipherText = await _encrypt_aes256_CBC_PKCSPadding(
cipherKey,
iv,
plaintext
);
const mac = _getFirstBytes(
await _hmac_SHA256(macKey, cipherText),
MAC_LENGTH
);
return _concatData([nonce, cipherText, mac]);
}
@ -39,9 +46,14 @@ async function decryptSymmetric(key, data) {
const cipherKey = await _hmac_SHA256(key, nonce);
const macKey = await _hmac_SHA256(key, cipherKey);
const ourMac = _getFirstBytes(await _hmac_SHA256(macKey, cipherText), MAC_LENGTH);
const ourMac = _getFirstBytes(
await _hmac_SHA256(macKey, cipherText),
MAC_LENGTH
);
if (!constantTimeEqual(theirMac, ourMac)) {
throw new Error('decryptSymmetric: Failed to decrypt; MAC verification failed');
throw new Error(
'decryptSymmetric: Failed to decrypt; MAC verification failed'
);
}
return _decrypt_aes256_CBC_PKCSPadding(cipherKey, iv, cipherText);
@ -61,7 +73,6 @@ function constantTimeEqual(left, right) {
return result === 0;
}
async function _hmac_SHA256(key, data) {
const extractable = false;
const cryptoKey = await window.crypto.subtle.importKey(
@ -72,7 +83,11 @@ async function _hmac_SHA256(key, data) {
['sign']
);
return window.crypto.subtle.sign({ name: 'HMAC', hash: 'SHA-256' }, cryptoKey, data);
return window.crypto.subtle.sign(
{ name: 'HMAC', hash: 'SHA-256' },
cryptoKey,
data
);
}
async function _encrypt_aes256_CBC_PKCSPadding(key, iv, data) {
@ -101,7 +116,6 @@ async function _decrypt_aes256_CBC_PKCSPadding(key, iv, data) {
return window.crypto.subtle.decrypt({ name: 'AES-CBC', iv }, cryptoKey, data);
}
function _getRandomBytes(n) {
const bytes = new Uint8Array(n);
window.crypto.getRandomValues(bytes);

View file

@ -6,14 +6,12 @@
const { isObject, isNumber } = require('lodash');
exports.open = (name, version, { onUpgradeNeeded } = {}) => {
const request = indexedDB.open(name, version);
return new Promise((resolve, reject) => {
request.onblocked = () =>
reject(new Error('Database blocked'));
request.onblocked = () => reject(new Error('Database blocked'));
request.onupgradeneeded = (event) => {
request.onupgradeneeded = event => {
const hasRequestedSpecificVersion = isNumber(version);
if (!hasRequestedSpecificVersion) {
return;
@ -26,14 +24,17 @@ exports.open = (name, version, { onUpgradeNeeded } = {}) => {
return;
}
reject(new Error('Database upgrade required:' +
` oldVersion: ${oldVersion}, newVersion: ${newVersion}`));
reject(
new Error(
'Database upgrade required:' +
` oldVersion: ${oldVersion}, newVersion: ${newVersion}`
)
);
};
request.onerror = event =>
reject(event.target.error);
request.onerror = event => reject(event.target.error);
request.onsuccess = (event) => {
request.onsuccess = event => {
const connection = event.target.result;
resolve(connection);
};
@ -47,7 +48,7 @@ exports.completeTransaction = transaction =>
transaction.addEventListener('complete', () => resolve());
});
exports.getVersion = async (name) => {
exports.getVersion = async name => {
const connection = await exports.open(name);
const { version } = connection;
connection.close();
@ -61,9 +62,7 @@ exports.getCount = async ({ store } = {}) => {
const request = store.count();
return new Promise((resolve, reject) => {
request.onerror = event =>
reject(event.target.error);
request.onsuccess = event =>
resolve(event.target.result);
request.onerror = event => reject(event.target.error);
request.onsuccess = event => resolve(event.target.result);
});
};

View file

@ -18,7 +18,6 @@ const Message = require('./types/message');
const { deferredToPromise } = require('./deferred_to_promise');
const { sleep } = require('./sleep');
// See: https://en.wikipedia.org/wiki/Fictitious_telephone_number#North_American_Numbering_Plan
const SENDER_ID = '+12126647665';
@ -27,8 +26,10 @@ exports.createConversation = async ({
numMessages,
WhisperMessage,
} = {}) => {
if (!isObject(ConversationController) ||
!isFunction(ConversationController.getOrCreateAndWait)) {
if (
!isObject(ConversationController) ||
!isFunction(ConversationController.getOrCreateAndWait)
) {
throw new TypeError("'ConversationController' is required");
}
@ -40,8 +41,10 @@ exports.createConversation = async ({
throw new TypeError("'WhisperMessage' is required");
}
const conversation =
await ConversationController.getOrCreateAndWait(SENDER_ID, 'private');
const conversation = await ConversationController.getOrCreateAndWait(
SENDER_ID,
'private'
);
conversation.set({
active_at: Date.now(),
unread: numMessages,
@ -50,13 +53,15 @@ exports.createConversation = async ({
const conversationId = conversation.get('id');
await Promise.all(range(0, numMessages).map(async (index) => {
await sleep(index * 100);
console.log(`Create message ${index + 1}`);
const messageAttributes = await createRandomMessage({ conversationId });
const message = new WhisperMessage(messageAttributes);
return deferredToPromise(message.save());
}));
await Promise.all(
range(0, numMessages).map(async index => {
await sleep(index * 100);
console.log(`Create message ${index + 1}`);
const messageAttributes = await createRandomMessage({ conversationId });
const message = new WhisperMessage(messageAttributes);
return deferredToPromise(message.save());
})
);
};
const SAMPLE_MESSAGES = [
@ -88,7 +93,8 @@ const createRandomMessage = async ({ conversationId } = {}) => {
const hasAttachment = Math.random() <= ATTACHMENT_SAMPLE_RATE;
const attachments = hasAttachment
? [await createRandomInMemoryAttachment()] : [];
? [await createRandomInMemoryAttachment()]
: [];
const type = sample(['incoming', 'outgoing']);
const commonProperties = {
attachments,
@ -145,7 +151,7 @@ const createFileEntry = fileName => ({
fileName,
contentType: fileNameToContentType(fileName),
});
const fileNameToContentType = (fileName) => {
const fileNameToContentType = fileName => {
const fileExtension = path.extname(fileName).toLowerCase();
switch (fileExtension) {
case '.gif':

View file

@ -3,7 +3,6 @@
const FormData = require('form-data');
const got = require('got');
const BASE_URL = 'https://debuglogs.org';
// Workaround: Submitting `FormData` using native `FormData::submit` procedure
@ -12,7 +11,7 @@ const BASE_URL = 'https://debuglogs.org';
// https://github.com/sindresorhus/got/pull/466
const submitFormData = (form, url) =>
new Promise((resolve, reject) => {
form.submit(url, (error) => {
form.submit(url, error => {
if (error) {
return reject(error);
}
@ -22,7 +21,7 @@ const submitFormData = (form, url) =>
});
// upload :: String -> Promise URL
exports.upload = async (content) => {
exports.upload = async content => {
const signedForm = await got.get(BASE_URL, { json: true });
const { fields, url } = signedForm.body;

View file

@ -2,11 +2,10 @@ const addUnhandledErrorHandler = require('electron-unhandled');
const Errors = require('./types/errors');
// addHandler :: Unit -> Unit
exports.addHandler = () => {
addUnhandledErrorHandler({
logger: (error) => {
logger: error => {
console.error(
'Uncaught error or unhandled promise rejection:',
Errors.toLogFormat(error)

View file

@ -11,7 +11,9 @@ exports.setup = (locale, messages) => {
function getMessage(key, substitutions) {
const entry = messages[key];
if (!entry) {
console.error(`i18n: Attempted to get translation for nonexistent key '${key}'`);
console.error(
`i18n: Attempted to get translation for nonexistent key '${key}'`
);
return '';
}

View file

@ -2,7 +2,6 @@
const EventEmitter = require('events');
const POLL_INTERVAL_MS = 5 * 1000;
const IDLE_THRESHOLD_MS = 20;
@ -35,14 +34,17 @@ class IdleDetector extends EventEmitter {
_scheduleNextCallback() {
this._clearScheduledCallbacks();
this.handle = window.requestIdleCallback((deadline) => {
this.handle = window.requestIdleCallback(deadline => {
const { didTimeout } = deadline;
const timeRemaining = deadline.timeRemaining();
const isIdle = timeRemaining >= IDLE_THRESHOLD_MS;
if (isIdle || didTimeout) {
this.emit('idle', { timestamp: Date.now(), didTimeout, timeRemaining });
}
this.timeoutId = setTimeout(() => this._scheduleNextCallback(), POLL_INTERVAL_MS);
this.timeoutId = setTimeout(
() => this._scheduleNextCallback(),
POLL_INTERVAL_MS
);
});
}
}

View file

@ -7,7 +7,7 @@ function createLink(url, text, attrs = {}) {
const html = [];
html.push('<a ');
html.push(`href="${url}"`);
Object.keys(attrs).forEach((key) => {
Object.keys(attrs).forEach(key => {
html.push(` ${key}="${attrs[key]}"`);
});
html.push('>');
@ -23,7 +23,7 @@ module.exports = (text, attrs = {}) => {
const result = [];
let last = 0;
matchData.forEach((match) => {
matchData.forEach(match => {
if (last < match.index) {
result.push(text.slice(last, match.index));
}

View file

@ -6,20 +6,13 @@
/* global IDBKeyRange */
const {
isFunction,
isNumber,
isObject,
isString,
last,
} = require('lodash');
const { isFunction, isNumber, isObject, isString, last } = require('lodash');
const database = require('./database');
const Message = require('./types/message');
const settings = require('./settings');
const { deferredToPromise } = require('./deferred_to_promise');
const MESSAGES_STORE_NAME = 'messages';
exports.processNext = async ({
@ -29,12 +22,16 @@ exports.processNext = async ({
upgradeMessageSchema,
} = {}) => {
if (!isFunction(BackboneMessage)) {
throw new TypeError("'BackboneMessage' (Whisper.Message) constructor is required");
throw new TypeError(
"'BackboneMessage' (Whisper.Message) constructor is required"
);
}
if (!isFunction(BackboneMessageCollection)) {
throw new TypeError("'BackboneMessageCollection' (Whisper.MessageCollection)" +
' constructor is required');
throw new TypeError(
"'BackboneMessageCollection' (Whisper.MessageCollection)" +
' constructor is required'
);
}
if (!isNumber(numMessagesPerBatch)) {
@ -48,16 +45,18 @@ exports.processNext = async ({
const startTime = Date.now();
const fetchStartTime = Date.now();
const messagesRequiringSchemaUpgrade =
await _fetchMessagesRequiringSchemaUpgrade({
const messagesRequiringSchemaUpgrade = await _fetchMessagesRequiringSchemaUpgrade(
{
BackboneMessageCollection,
count: numMessagesPerBatch,
});
}
);
const fetchDuration = Date.now() - fetchStartTime;
const upgradeStartTime = Date.now();
const upgradedMessages =
await Promise.all(messagesRequiringSchemaUpgrade.map(upgradeMessageSchema));
const upgradedMessages = await Promise.all(
messagesRequiringSchemaUpgrade.map(upgradeMessageSchema)
);
const upgradeDuration = Date.now() - upgradeStartTime;
const saveStartTime = Date.now();
@ -109,8 +108,10 @@ exports.dangerouslyProcessAllWithoutIndex = async ({
minDatabaseVersion,
});
if (!isValidDatabaseVersion) {
throw new Error(`Expected database version (${databaseVersion})` +
` to be at least ${minDatabaseVersion}`);
throw new Error(
`Expected database version (${databaseVersion})` +
` to be at least ${minDatabaseVersion}`
);
}
// NOTE: Even if we make this async using `then`, requesting `count` on an
@ -132,10 +133,13 @@ exports.dangerouslyProcessAllWithoutIndex = async ({
break;
}
numCumulativeMessagesProcessed += status.numMessagesProcessed;
console.log('Upgrade message schema:', Object.assign({}, status, {
numTotalMessages,
numCumulativeMessagesProcessed,
}));
console.log(
'Upgrade message schema:',
Object.assign({}, status, {
numTotalMessages,
numCumulativeMessagesProcessed,
})
);
}
console.log('Close database connection');
@ -181,8 +185,10 @@ const _getConnection = async ({ databaseName, minDatabaseVersion }) => {
const databaseVersion = connection.version;
const isValidDatabaseVersion = databaseVersion >= minDatabaseVersion;
if (!isValidDatabaseVersion) {
throw new Error(`Expected database version (${databaseVersion})` +
` to be at least ${minDatabaseVersion}`);
throw new Error(
`Expected database version (${databaseVersion})` +
` to be at least ${minDatabaseVersion}`
);
}
return connection;
@ -205,29 +211,33 @@ const _processBatch = async ({
throw new TypeError("'numMessagesPerBatch' is required");
}
const isAttachmentMigrationComplete =
await settings.isAttachmentMigrationComplete(connection);
const isAttachmentMigrationComplete = await settings.isAttachmentMigrationComplete(
connection
);
if (isAttachmentMigrationComplete) {
return {
done: true,
};
}
const lastProcessedIndex =
await settings.getAttachmentMigrationLastProcessedIndex(connection);
const lastProcessedIndex = await settings.getAttachmentMigrationLastProcessedIndex(
connection
);
const fetchUnprocessedMessagesStartTime = Date.now();
const unprocessedMessages =
await _dangerouslyFetchMessagesRequiringSchemaUpgradeWithoutIndex({
const unprocessedMessages = await _dangerouslyFetchMessagesRequiringSchemaUpgradeWithoutIndex(
{
connection,
count: numMessagesPerBatch,
lastIndex: lastProcessedIndex,
});
}
);
const fetchDuration = Date.now() - fetchUnprocessedMessagesStartTime;
const upgradeStartTime = Date.now();
const upgradedMessages =
await Promise.all(unprocessedMessages.map(upgradeMessageSchema));
const upgradedMessages = await Promise.all(
unprocessedMessages.map(upgradeMessageSchema)
);
const upgradeDuration = Date.now() - upgradeStartTime;
const saveMessagesStartTime = Date.now();
@ -266,12 +276,12 @@ const _processBatch = async ({
};
};
const _saveMessageBackbone = ({ BackboneMessage } = {}) => (message) => {
const _saveMessageBackbone = ({ BackboneMessage } = {}) => message => {
const backboneMessage = new BackboneMessage(message);
return deferredToPromise(backboneMessage.save());
};
const _saveMessage = ({ transaction } = {}) => (message) => {
const _saveMessage = ({ transaction } = {}) => message => {
if (!isObject(transaction)) {
throw new TypeError("'transaction' is required");
}
@ -279,83 +289,91 @@ const _saveMessage = ({ transaction } = {}) => (message) => {
const messagesStore = transaction.objectStore(MESSAGES_STORE_NAME);
const request = messagesStore.put(message, message.id);
return new Promise((resolve, reject) => {
request.onsuccess = () =>
resolve();
request.onerror = event =>
reject(event.target.error);
request.onsuccess = () => resolve();
request.onerror = event => reject(event.target.error);
});
};
const _fetchMessagesRequiringSchemaUpgrade =
async ({ BackboneMessageCollection, count } = {}) => {
if (!isFunction(BackboneMessageCollection)) {
throw new TypeError("'BackboneMessageCollection' (Whisper.MessageCollection)" +
' constructor is required');
}
const _fetchMessagesRequiringSchemaUpgrade = async ({
BackboneMessageCollection,
count,
} = {}) => {
if (!isFunction(BackboneMessageCollection)) {
throw new TypeError(
"'BackboneMessageCollection' (Whisper.MessageCollection)" +
' constructor is required'
);
}
if (!isNumber(count)) {
throw new TypeError("'count' is required");
}
if (!isNumber(count)) {
throw new TypeError("'count' is required");
}
const collection = new BackboneMessageCollection();
return new Promise(resolve => collection.fetch({
limit: count,
index: {
name: 'schemaVersion',
upper: Message.CURRENT_SCHEMA_VERSION,
excludeUpper: true,
order: 'desc',
},
}).always(() => {
const models = collection.models || [];
const messages = models.map(model => model.toJSON());
resolve(messages);
}));
};
const collection = new BackboneMessageCollection();
return new Promise(resolve =>
collection
.fetch({
limit: count,
index: {
name: 'schemaVersion',
upper: Message.CURRENT_SCHEMA_VERSION,
excludeUpper: true,
order: 'desc',
},
})
.always(() => {
const models = collection.models || [];
const messages = models.map(model => model.toJSON());
resolve(messages);
})
);
};
// NOTE: Named dangerous because it is not as efficient as using our
// `messages` `schemaVersion` index:
const _dangerouslyFetchMessagesRequiringSchemaUpgradeWithoutIndex =
({ connection, count, lastIndex } = {}) => {
if (!isObject(connection)) {
throw new TypeError("'connection' is required");
}
const _dangerouslyFetchMessagesRequiringSchemaUpgradeWithoutIndex = ({
connection,
count,
lastIndex,
} = {}) => {
if (!isObject(connection)) {
throw new TypeError("'connection' is required");
}
if (!isNumber(count)) {
throw new TypeError("'count' is required");
}
if (!isNumber(count)) {
throw new TypeError("'count' is required");
}
if (lastIndex && !isString(lastIndex)) {
throw new TypeError("'lastIndex' must be a string");
}
if (lastIndex && !isString(lastIndex)) {
throw new TypeError("'lastIndex' must be a string");
}
const hasLastIndex = Boolean(lastIndex);
const hasLastIndex = Boolean(lastIndex);
const transaction = connection.transaction(MESSAGES_STORE_NAME, 'readonly');
const messagesStore = transaction.objectStore(MESSAGES_STORE_NAME);
const transaction = connection.transaction(MESSAGES_STORE_NAME, 'readonly');
const messagesStore = transaction.objectStore(MESSAGES_STORE_NAME);
const excludeLowerBound = true;
const range = hasLastIndex
? IDBKeyRange.lowerBound(lastIndex, excludeLowerBound)
: undefined;
return new Promise((resolve, reject) => {
const items = [];
const request = messagesStore.openCursor(range);
request.onsuccess = (event) => {
const cursor = event.target.result;
const hasMoreData = Boolean(cursor);
if (!hasMoreData || items.length === count) {
resolve(items);
return;
}
const item = cursor.value;
items.push(item);
cursor.continue();
};
request.onerror = event =>
reject(event.target.error);
});
};
const excludeLowerBound = true;
const range = hasLastIndex
? IDBKeyRange.lowerBound(lastIndex, excludeLowerBound)
: undefined;
return new Promise((resolve, reject) => {
const items = [];
const request = messagesStore.openCursor(range);
request.onsuccess = event => {
const cursor = event.target.result;
const hasMoreData = Boolean(cursor);
if (!hasMoreData || items.length === count) {
resolve(items);
return;
}
const item = cursor.value;
items.push(item);
cursor.continue();
};
request.onerror = event => reject(event.target.error);
});
};
const _getNumMessages = async ({ connection } = {}) => {
if (!isObject(connection)) {

View file

@ -1,4 +1,4 @@
exports.run = (transaction) => {
exports.run = transaction => {
const messagesStore = transaction.objectStore('messages');
console.log("Create message attachment metadata index: 'hasAttachments'");
@ -8,12 +8,10 @@ exports.run = (transaction) => {
{ unique: false }
);
['hasVisualMediaAttachments', 'hasFileAttachments'].forEach((name) => {
['hasVisualMediaAttachments', 'hasFileAttachments'].forEach(name => {
console.log(`Create message attachment metadata index: '${name}'`);
messagesStore.createIndex(
name,
['conversationId', 'received_at', name],
{ unique: false }
);
messagesStore.createIndex(name, ['conversationId', 'received_at', name], {
unique: false,
});
});
};

View file

@ -1,23 +1,22 @@
const Migrations0DatabaseWithAttachmentData =
require('./migrations_0_database_with_attachment_data');
const Migrations1DatabaseWithoutAttachmentData =
require('./migrations_1_database_without_attachment_data');
const Migrations0DatabaseWithAttachmentData = require('./migrations_0_database_with_attachment_data');
const Migrations1DatabaseWithoutAttachmentData = require('./migrations_1_database_without_attachment_data');
exports.getPlaceholderMigrations = () => {
const last0MigrationVersion =
Migrations0DatabaseWithAttachmentData.getLatestVersion();
const last1MigrationVersion =
Migrations1DatabaseWithoutAttachmentData.getLatestVersion();
const last0MigrationVersion = Migrations0DatabaseWithAttachmentData.getLatestVersion();
const last1MigrationVersion = Migrations1DatabaseWithoutAttachmentData.getLatestVersion();
const lastMigrationVersion = last1MigrationVersion || last0MigrationVersion;
return [{
version: lastMigrationVersion,
migrate() {
throw new Error('Unexpected invocation of placeholder migration!' +
'\n\nMigrations must explicitly be run upon application startup instead' +
' of implicitly via Backbone IndexedDB adapter at any time.');
return [
{
version: lastMigrationVersion,
migrate() {
throw new Error(
'Unexpected invocation of placeholder migration!' +
'\n\nMigrations must explicitly be run upon application startup instead' +
' of implicitly via Backbone IndexedDB adapter at any time.'
);
},
},
}];
];
};

View file

@ -3,7 +3,6 @@ const { isString, last } = require('lodash');
const { runMigrations } = require('./run_migrations');
const Migration18 = require('./18');
// IMPORTANT: The migrations below are run on a database that may be very large
// due to attachments being directly stored inside the database. Please avoid
// any expensive operations, e.g. modifying all messages / attachments, etc., as
@ -20,7 +19,9 @@ const migrations = [
unique: false,
});
messages.createIndex('receipt', 'sent_at', { unique: false });
messages.createIndex('unread', ['conversationId', 'unread'], { unique: false });
messages.createIndex('unread', ['conversationId', 'unread'], {
unique: false,
});
messages.createIndex('expires_at', 'expires_at', { unique: false });
const conversations = transaction.db.createObjectStore('conversations');
@ -59,7 +60,7 @@ const migrations = [
const identityKeys = transaction.objectStore('identityKeys');
const request = identityKeys.openCursor();
const promises = [];
request.onsuccess = (event) => {
request.onsuccess = event => {
const cursor = event.target.result;
if (cursor) {
const attributes = cursor.value;
@ -67,14 +68,16 @@ const migrations = [
attributes.firstUse = false;
attributes.nonblockingApproval = false;
attributes.verified = 0;
promises.push(new Promise(((resolve, reject) => {
const putRequest = identityKeys.put(attributes, attributes.id);
putRequest.onsuccess = resolve;
putRequest.onerror = (e) => {
console.log(e);
reject(e);
};
})));
promises.push(
new Promise((resolve, reject) => {
const putRequest = identityKeys.put(attributes, attributes.id);
putRequest.onsuccess = resolve;
putRequest.onerror = e => {
console.log(e);
reject(e);
};
})
);
cursor.continue();
} else {
// no more results
@ -84,7 +87,7 @@ const migrations = [
});
}
};
request.onerror = (event) => {
request.onerror = event => {
console.log(event);
};
},
@ -129,7 +132,9 @@ const migrations = [
const messagesStore = transaction.objectStore('messages');
console.log('Create index from attachment schema version to attachment');
messagesStore.createIndex('schemaVersion', 'schemaVersion', { unique: false });
messagesStore.createIndex('schemaVersion', 'schemaVersion', {
unique: false,
});
const duration = Date.now() - start;

View file

@ -4,7 +4,6 @@ const db = require('../database');
const settings = require('../settings');
const { runMigrations } = require('./run_migrations');
// IMPORTANT: Add new migrations that need to traverse entire database, e.g.
// messages store, below. Whenever we need this, we need to force attachment
// migration on startup:
@ -20,7 +19,9 @@ const migrations = [
exports.run = async ({ Backbone, database } = {}) => {
const { canRun } = await exports.getStatus({ database });
if (!canRun) {
throw new Error('Cannot run migrations on database without attachment data');
throw new Error(
'Cannot run migrations on database without attachment data'
);
}
await runMigrations({ Backbone, database });
@ -28,8 +29,9 @@ exports.run = async ({ Backbone, database } = {}) => {
exports.getStatus = async ({ database } = {}) => {
const connection = await db.open(database.id, database.version);
const isAttachmentMigrationComplete =
await settings.isAttachmentMigrationComplete(connection);
const isAttachmentMigrationComplete = await settings.isAttachmentMigrationComplete(
connection
);
const hasMigrations = migrations.length > 0;
const canRun = isAttachmentMigrationComplete && hasMigrations;

View file

@ -1,29 +1,27 @@
/* eslint-env browser */
const {
head,
isFunction,
isObject,
isString,
last,
} = require('lodash');
const { head, isFunction, isObject, isString, last } = require('lodash');
const db = require('../database');
const { deferredToPromise } = require('../deferred_to_promise');
const closeDatabaseConnection = ({ Backbone } = {}) =>
deferredToPromise(Backbone.sync('closeall'));
exports.runMigrations = async ({ Backbone, database } = {}) => {
if (!isObject(Backbone) || !isObject(Backbone.Collection) ||
!isFunction(Backbone.Collection.extend)) {
if (
!isObject(Backbone) ||
!isObject(Backbone.Collection) ||
!isFunction(Backbone.Collection.extend)
) {
throw new TypeError("'Backbone' is required");
}
if (!isObject(database) || !isString(database.id) ||
!Array.isArray(database.migrations)) {
if (
!isObject(database) ||
!isString(database.id) ||
!Array.isArray(database.migrations)
) {
throw new TypeError("'database' is required");
}
@ -56,7 +54,7 @@ exports.runMigrations = async ({ Backbone, database } = {}) => {
await closeDatabaseConnection({ Backbone });
};
const getMigrationVersions = (database) => {
const getMigrationVersions = database => {
if (!isObject(database) || !Array.isArray(database.migrations)) {
throw new TypeError("'database' is required");
}
@ -64,8 +62,12 @@ const getMigrationVersions = (database) => {
const firstMigration = head(database.migrations);
const lastMigration = last(database.migrations);
const firstVersion = firstMigration ? parseInt(firstMigration.version, 10) : null;
const lastVersion = lastMigration ? parseInt(lastMigration.version, 10) : null;
const firstVersion = firstMigration
? parseInt(firstMigration.version, 10)
: null;
const lastVersion = lastMigration
? parseInt(lastMigration.version, 10)
: null;
return { firstVersion, lastVersion };
};

View file

@ -1,10 +1,7 @@
/* eslint-env node */
exports.isMacOS = () =>
process.platform === 'darwin';
exports.isMacOS = () => process.platform === 'darwin';
exports.isLinux = () =>
process.platform === 'linux';
exports.isLinux = () => process.platform === 'linux';
exports.isWindows = () =>
process.platform === 'win32';
exports.isWindows = () => process.platform === 'win32';

View file

@ -6,22 +6,20 @@ const path = require('path');
const { compose } = require('lodash/fp');
const { escapeRegExp } = require('lodash');
const APP_ROOT_PATH = path.join(__dirname, '..', '..', '..');
const PHONE_NUMBER_PATTERN = /\+\d{7,12}(\d{3})/g;
const GROUP_ID_PATTERN = /(group\()([^)]+)(\))/g;
const REDACTION_PLACEHOLDER = '[REDACTED]';
// _redactPath :: Path -> String -> String
exports._redactPath = (filePath) => {
exports._redactPath = filePath => {
if (!is.string(filePath)) {
throw new TypeError("'filePath' must be a string");
}
const filePathPattern = exports._pathToRegExp(filePath);
return (text) => {
return text => {
if (!is.string(text)) {
throw new TypeError("'text' must be a string");
}
@ -35,7 +33,7 @@ exports._redactPath = (filePath) => {
};
// _pathToRegExp :: Path -> Maybe RegExp
exports._pathToRegExp = (filePath) => {
exports._pathToRegExp = filePath => {
try {
const pathWithNormalizedSlashes = filePath.replace(/\//g, '\\');
const pathWithEscapedSlashes = filePath.replace(/\\/g, '\\\\');
@ -47,7 +45,9 @@ exports._pathToRegExp = (filePath) => {
pathWithNormalizedSlashes,
pathWithEscapedSlashes,
urlEncodedPath,
].map(escapeRegExp).join('|');
]
.map(escapeRegExp)
.join('|');
return new RegExp(patternString, 'g');
} catch (error) {
return null;
@ -56,7 +56,7 @@ exports._pathToRegExp = (filePath) => {
// Public API
// redactPhoneNumbers :: String -> String
exports.redactPhoneNumbers = (text) => {
exports.redactPhoneNumbers = text => {
if (!is.string(text)) {
throw new TypeError("'text' must be a string");
}
@ -65,7 +65,7 @@ exports.redactPhoneNumbers = (text) => {
};
// redactGroupIds :: String -> String
exports.redactGroupIds = (text) => {
exports.redactGroupIds = text => {
if (!is.string(text)) {
throw new TypeError("'text' must be a string");
}

View file

@ -1,6 +1,5 @@
const { isObject, isString } = require('lodash');
const ITEMS_STORE_NAME = 'items';
const LAST_PROCESSED_INDEX_KEY = 'attachmentMigration_lastProcessedIndex';
const IS_MIGRATION_COMPLETE_KEY = 'attachmentMigration_isComplete';
@ -37,8 +36,7 @@ exports._getItem = (connection, key) => {
const itemsStore = transaction.objectStore(ITEMS_STORE_NAME);
const request = itemsStore.get(key);
return new Promise((resolve, reject) => {
request.onerror = event =>
reject(event.target.error);
request.onerror = event => reject(event.target.error);
request.onsuccess = event =>
resolve(event.target.result ? event.target.result.value : null);
@ -58,11 +56,9 @@ exports._setItem = (connection, key, value) => {
const itemsStore = transaction.objectStore(ITEMS_STORE_NAME);
const request = itemsStore.put({ id: key, value }, key);
return new Promise((resolve, reject) => {
request.onerror = event =>
reject(event.target.error);
request.onerror = event => reject(event.target.error);
request.onsuccess = () =>
resolve();
request.onsuccess = () => resolve();
});
};
@ -79,10 +75,8 @@ exports._deleteItem = (connection, key) => {
const itemsStore = transaction.objectStore(ITEMS_STORE_NAME);
const request = itemsStore.delete(key);
return new Promise((resolve, reject) => {
request.onerror = event =>
reject(event.target.error);
request.onerror = event => reject(event.target.error);
request.onsuccess = () =>
resolve();
request.onsuccess = () => resolve();
});
};

View file

@ -1,4 +1,3 @@
/* global setTimeout */
exports.sleep = ms =>
new Promise(resolve => setTimeout(resolve, ms));
exports.sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

View file

@ -3,7 +3,6 @@ const is = require('@sindresorhus/is');
const Errors = require('./types/errors');
const Settings = require('./settings');
exports.syncReadReceiptConfiguration = async ({
deviceId,
sendRequestConfigurationSyncMessage,

View file

@ -1,4 +1,4 @@
exports.stringToArrayBuffer = (string) => {
exports.stringToArrayBuffer = string => {
if (typeof string !== 'string') {
throw new TypeError("'string' must be a string");
}

View file

@ -2,9 +2,15 @@ const is = require('@sindresorhus/is');
const AttachmentTS = require('../../../ts/types/Attachment');
const MIME = require('../../../ts/types/MIME');
const { arrayBufferToBlob, blobToArrayBuffer, dataURLToBlob } = require('blob-util');
const {
arrayBufferToBlob,
blobToArrayBuffer,
dataURLToBlob,
} = require('blob-util');
const { autoOrientImage } = require('../auto_orient_image');
const { migrateDataToFileSystem } = require('./attachment/migrate_data_to_file_system');
const {
migrateDataToFileSystem,
} = require('./attachment/migrate_data_to_file_system');
// // Incoming message attachment fields
// {
@ -30,7 +36,7 @@ const { migrateDataToFileSystem } = require('./attachment/migrate_data_to_file_s
// Returns true if `rawAttachment` is a valid attachment based on our current schema.
// Over time, we can expand this definition to become more narrow, e.g. require certain
// fields, etc.
exports.isValid = (rawAttachment) => {
exports.isValid = rawAttachment => {
// NOTE: We cannot use `_.isPlainObject` because `rawAttachment` is
// deserialized by protobuf:
if (!rawAttachment) {
@ -41,12 +47,15 @@ exports.isValid = (rawAttachment) => {
};
// Upgrade steps
exports.autoOrientJPEG = async (attachment) => {
exports.autoOrientJPEG = async attachment => {
if (!MIME.isJPEG(attachment.contentType)) {
return attachment;
}
const dataBlob = await arrayBufferToBlob(attachment.data, attachment.contentType);
const dataBlob = await arrayBufferToBlob(
attachment.data,
attachment.contentType
);
const newDataBlob = await dataURLToBlob(await autoOrientImage(dataBlob));
const newDataArrayBuffer = await blobToArrayBuffer(newDataBlob);
@ -76,7 +85,7 @@ const INVALID_CHARACTERS_PATTERN = new RegExp(
// NOTE: Expose synchronous version to do property-based testing using `testcheck`,
// which currently doesnt support async testing:
// https://github.com/leebyron/testcheck-js/issues/45
exports._replaceUnicodeOrderOverridesSync = (attachment) => {
exports._replaceUnicodeOrderOverridesSync = attachment => {
if (!is.string(attachment.fileName)) {
return attachment;
}
@ -95,9 +104,12 @@ exports._replaceUnicodeOrderOverridesSync = (attachment) => {
exports.replaceUnicodeOrderOverrides = async attachment =>
exports._replaceUnicodeOrderOverridesSync(attachment);
exports.removeSchemaVersion = (attachment) => {
exports.removeSchemaVersion = attachment => {
if (!exports.isValid(attachment)) {
console.log('Attachment.removeSchemaVersion: Invalid input attachment:', attachment);
console.log(
'Attachment.removeSchemaVersion: Invalid input attachment:',
attachment
);
return attachment;
}
@ -115,12 +127,12 @@ exports.hasData = attachment =>
// loadData :: (RelativePath -> IO (Promise ArrayBuffer))
// Attachment ->
// IO (Promise Attachment)
exports.loadData = (readAttachmentData) => {
exports.loadData = readAttachmentData => {
if (!is.function(readAttachmentData)) {
throw new TypeError("'readAttachmentData' must be a function");
}
return async (attachment) => {
return async attachment => {
if (!exports.isValid(attachment)) {
throw new TypeError("'attachment' is not valid");
}
@ -142,12 +154,12 @@ exports.loadData = (readAttachmentData) => {
// deleteData :: (RelativePath -> IO Unit)
// Attachment ->
// IO Unit
exports.deleteData = (deleteAttachmentData) => {
exports.deleteData = deleteAttachmentData => {
if (!is.function(deleteAttachmentData)) {
throw new TypeError("'deleteAttachmentData' must be a function");
}
return async (attachment) => {
return async attachment => {
if (!exports.isValid(attachment)) {
throw new TypeError("'attachment' is not valid");
}

View file

@ -1,10 +1,4 @@
const {
isArrayBuffer,
isFunction,
isUndefined,
omit,
} = require('lodash');
const { isArrayBuffer, isFunction, isUndefined, omit } = require('lodash');
// type Context :: {
// writeNewAttachmentData :: ArrayBuffer -> Promise (IO Path)
@ -13,7 +7,10 @@ const {
// migrateDataToFileSystem :: Attachment ->
// Context ->
// Promise Attachment
exports.migrateDataToFileSystem = async (attachment, { writeNewAttachmentData } = {}) => {
exports.migrateDataToFileSystem = async (
attachment,
{ writeNewAttachmentData } = {}
) => {
if (!isFunction(writeNewAttachmentData)) {
throw new TypeError("'writeNewAttachmentData' must be a function");
}
@ -28,15 +25,16 @@ exports.migrateDataToFileSystem = async (attachment, { writeNewAttachmentData }
const isValidData = isArrayBuffer(data);
if (!isValidData) {
throw new TypeError('Expected `attachment.data` to be an array buffer;' +
` got: ${typeof attachment.data}`);
throw new TypeError(
'Expected `attachment.data` to be an array buffer;' +
` got: ${typeof attachment.data}`
);
}
const path = await writeNewAttachmentData(data);
const attachmentWithoutData = omit(
Object.assign({}, attachment, { path }),
['data']
);
const attachmentWithoutData = omit(Object.assign({}, attachment, { path }), [
'data',
]);
return attachmentWithoutData;
};

View file

@ -1,5 +1,5 @@
// toLogFormat :: Error -> String
exports.toLogFormat = (error) => {
exports.toLogFormat = error => {
if (!error) {
return error;
}

View file

@ -3,9 +3,9 @@ const { isFunction, isString, omit } = require('lodash');
const Attachment = require('./attachment');
const Errors = require('./errors');
const SchemaVersion = require('./schema_version');
const { initializeAttachmentMetadata } =
require('../../../ts/types/message/initializeAttachmentMetadata');
const {
initializeAttachmentMetadata,
} = require('../../../ts/types/message/initializeAttachmentMetadata');
const GROUP = 'group';
const PRIVATE = 'private';
@ -37,19 +37,17 @@ const INITIAL_SCHEMA_VERSION = 0;
// how we do database migrations:
exports.CURRENT_SCHEMA_VERSION = 5;
// Public API
exports.GROUP = GROUP;
exports.PRIVATE = PRIVATE;
// Placeholder until we have stronger preconditions:
exports.isValid = () =>
true;
exports.isValid = () => true;
// Schema
exports.initializeSchemaVersion = (message) => {
const isInitialized = SchemaVersion.isValid(message.schemaVersion) &&
message.schemaVersion >= 1;
exports.initializeSchemaVersion = message => {
const isInitialized =
SchemaVersion.isValid(message.schemaVersion) && message.schemaVersion >= 1;
if (isInitialized) {
return message;
}
@ -59,27 +57,23 @@ exports.initializeSchemaVersion = (message) => {
: 0;
const hasAttachments = numAttachments > 0;
if (!hasAttachments) {
return Object.assign(
{},
message,
{ schemaVersion: INITIAL_SCHEMA_VERSION }
);
return Object.assign({}, message, {
schemaVersion: INITIAL_SCHEMA_VERSION,
});
}
// All attachments should have the same schema version, so we just pick
// the first one:
const firstAttachment = message.attachments[0];
const inheritedSchemaVersion = SchemaVersion.isValid(firstAttachment.schemaVersion)
const inheritedSchemaVersion = SchemaVersion.isValid(
firstAttachment.schemaVersion
)
? firstAttachment.schemaVersion
: INITIAL_SCHEMA_VERSION;
const messageWithInitialSchema = Object.assign(
{},
message,
{
schemaVersion: inheritedSchemaVersion,
attachments: message.attachments.map(Attachment.removeSchemaVersion),
}
);
const messageWithInitialSchema = Object.assign({}, message, {
schemaVersion: inheritedSchemaVersion,
attachments: message.attachments.map(Attachment.removeSchemaVersion),
});
return messageWithInitialSchema;
};
@ -98,7 +92,10 @@ exports._withSchemaVersion = (schemaVersion, upgrade) => {
return async (message, context) => {
if (!exports.isValid(message)) {
console.log('Message._withSchemaVersion: Invalid input message:', message);
console.log(
'Message._withSchemaVersion: Invalid input message:',
message
);
return message;
}
@ -138,15 +135,10 @@ exports._withSchemaVersion = (schemaVersion, upgrade) => {
return message;
}
return Object.assign(
{},
upgradedMessage,
{ schemaVersion }
);
return Object.assign({}, upgradedMessage, { schemaVersion });
};
};
// Public API
// _mapAttachments :: (Attachment -> Promise Attachment) ->
// (Message, Context) ->
@ -154,19 +146,24 @@ exports._withSchemaVersion = (schemaVersion, upgrade) => {
exports._mapAttachments = upgradeAttachment => async (message, context) => {
const upgradeWithContext = attachment =>
upgradeAttachment(attachment, context);
const attachments = await Promise.all(message.attachments.map(upgradeWithContext));
const attachments = await Promise.all(
message.attachments.map(upgradeWithContext)
);
return Object.assign({}, message, { attachments });
};
// _mapQuotedAttachments :: (QuotedAttachment -> Promise QuotedAttachment) ->
// (Message, Context) ->
// Promise Message
exports._mapQuotedAttachments = upgradeAttachment => async (message, context) => {
exports._mapQuotedAttachments = upgradeAttachment => async (
message,
context
) => {
if (!message.quote) {
return message;
}
const upgradeWithContext = async (attachment) => {
const upgradeWithContext = async attachment => {
const { thumbnail } = attachment;
if (!thumbnail) {
return attachment;
@ -185,7 +182,9 @@ exports._mapQuotedAttachments = upgradeAttachment => async (message, context) =>
const quotedAttachments = (message.quote && message.quote.attachments) || [];
const attachments = await Promise.all(quotedAttachments.map(upgradeWithContext));
const attachments = await Promise.all(
quotedAttachments.map(upgradeWithContext)
);
return Object.assign({}, message, {
quote: Object.assign({}, message.quote, {
attachments,
@ -193,8 +192,7 @@ exports._mapQuotedAttachments = upgradeAttachment => async (message, context) =>
});
};
const toVersion0 = async message =>
exports.initializeSchemaVersion(message);
const toVersion0 = async message => exports.initializeSchemaVersion(message);
const toVersion1 = exports._withSchemaVersion(
1,
@ -241,25 +239,28 @@ exports.upgradeSchema = async (rawMessage, { writeNewAttachmentData } = {}) => {
return message;
};
exports.createAttachmentLoader = (loadAttachmentData) => {
exports.createAttachmentLoader = loadAttachmentData => {
if (!isFunction(loadAttachmentData)) {
throw new TypeError('`loadAttachmentData` is required');
}
return async message => (Object.assign({}, message, {
attachments: await Promise.all(message.attachments.map(loadAttachmentData)),
}));
return async message =>
Object.assign({}, message, {
attachments: await Promise.all(
message.attachments.map(loadAttachmentData)
),
});
};
// createAttachmentDataWriter :: (RelativePath -> IO Unit)
// Message ->
// IO (Promise Message)
exports.createAttachmentDataWriter = (writeExistingAttachmentData) => {
exports.createAttachmentDataWriter = writeExistingAttachmentData => {
if (!isFunction(writeExistingAttachmentData)) {
throw new TypeError("'writeExistingAttachmentData' must be a function");
}
return async (rawMessage) => {
return async rawMessage => {
if (!exports.isValid(rawMessage)) {
throw new TypeError("'rawMessage' is not valid");
}
@ -282,17 +283,21 @@ exports.createAttachmentDataWriter = (writeExistingAttachmentData) => {
return message;
}
(attachments || []).forEach((attachment) => {
(attachments || []).forEach(attachment => {
if (!Attachment.hasData(attachment)) {
throw new TypeError("'attachment.data' is required during message import");
throw new TypeError(
"'attachment.data' is required during message import"
);
}
if (!isString(attachment.path)) {
throw new TypeError("'attachment.path' is required during message import");
throw new TypeError(
"'attachment.path' is required during message import"
);
}
});
const writeThumbnails = exports._mapQuotedAttachments(async (thumbnail) => {
const writeThumbnails = exports._mapQuotedAttachments(async thumbnail => {
const { data, path } = thumbnail;
// we want to be bulletproof to thumbnails without data
@ -315,10 +320,12 @@ exports.createAttachmentDataWriter = (writeExistingAttachmentData) => {
{},
await writeThumbnails(message),
{
attachments: await Promise.all((attachments || []).map(async (attachment) => {
await writeExistingAttachmentData(attachment);
return omit(attachment, ['data']);
})),
attachments: await Promise.all(
(attachments || []).map(async attachment => {
await writeExistingAttachmentData(attachment);
return omit(attachment, ['data']);
})
),
}
);

View file

@ -1,5 +1,3 @@
const { isNumber } = require('lodash');
exports.isValid = value =>
isNumber(value) && value >= 0;
exports.isValid = value => isNumber(value) && value >= 0;

View file

@ -1,4 +1,3 @@
const OS = require('../os');
exports.isAudioNotificationSupported = () =>
!OS.isLinux();
exports.isAudioNotificationSupported = () => !OS.isLinux();

View file

@ -2,7 +2,6 @@
/* global i18n: false */
const OPTIMIZATION_MESSAGE_DISPLAY_THRESHOLD = 1000; // milliseconds
const setMessage = () => {