Uint8Array migration

This commit is contained in:
Fedor Indutny 2021-09-23 17:49:05 -07:00 committed by GitHub
parent daf75190b8
commit 4ef0bf96cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
137 changed files with 2202 additions and 3170 deletions

View file

@ -130,13 +130,8 @@ const searchSelectors = require('../../ts/state/selectors/search');
// Types
const AttachmentType = require('../../ts/types/Attachment');
const VisualAttachment = require('./types/visual_attachment');
const EmbeddedContact = require('../../ts/types/EmbeddedContact');
const Conversation = require('./types/conversation');
const Errors = require('../../ts/types/errors');
const VisualAttachment = require('../../ts/types/VisualAttachment');
const MessageType = require('./types/message');
const MIME = require('../../ts/types/MIME');
const SettingsType = require('../../ts/types/Settings');
const { UUID } = require('../../ts/types/UUID');
const { Address } = require('../../ts/types/Address');
const { QualifiedAddress } = require('../../ts/types/QualifiedAddress');
@ -417,14 +412,9 @@ exports.setup = (options = {}) => {
};
const Types = {
Attachment: AttachmentType,
EmbeddedContact,
Conversation,
Errors,
Message: MessageType,
MIME,
Settings: SettingsType,
VisualAttachment,
// Mostly for debugging
UUID,
Address,
QualifiedAddress,

View file

@ -1,167 +0,0 @@
// Copyright 2018-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global window */
const { isFunction, isNumber } = require('lodash');
const {
arrayBufferToBase64,
base64ToArrayBuffer,
computeHash,
} = require('../../../ts/Crypto');
function buildAvatarUpdater({ field }) {
return async (conversation, data, options = {}) => {
if (!conversation) {
return conversation;
}
const avatar = conversation[field];
const {
deleteAttachmentData,
doesAttachmentExist,
writeNewAttachmentData,
} = options;
if (!isFunction(deleteAttachmentData)) {
throw new Error(
'Conversation.buildAvatarUpdater: deleteAttachmentData must be a function'
);
}
if (!isFunction(doesAttachmentExist)) {
throw new Error(
'Conversation.buildAvatarUpdater: deleteAttachmentData must be a function'
);
}
if (!isFunction(writeNewAttachmentData)) {
throw new Error(
'Conversation.buildAvatarUpdater: writeNewAttachmentData must be a function'
);
}
const newHash = await computeHash(data);
if (!avatar || !avatar.hash) {
return {
...conversation,
[field]: {
hash: newHash,
path: await writeNewAttachmentData(data),
},
};
}
const { hash, path } = avatar;
const exists = await doesAttachmentExist(path);
if (!exists) {
window.SignalWindow.log.warn(
`Conversation.buildAvatarUpdater: attachment ${path} did not exist`
);
}
if (exists && hash === newHash) {
return conversation;
}
await deleteAttachmentData(path);
return {
...conversation,
[field]: {
hash: newHash,
path: await writeNewAttachmentData(data),
},
};
};
}
const maybeUpdateAvatar = buildAvatarUpdater({ field: 'avatar' });
const maybeUpdateProfileAvatar = buildAvatarUpdater({
field: 'profileAvatar',
});
async function upgradeToVersion2(conversation, options) {
if (conversation.version >= 2) {
return conversation;
}
const { writeNewAttachmentData } = options;
if (!isFunction(writeNewAttachmentData)) {
throw new Error(
'Conversation.upgradeToVersion2: writeNewAttachmentData must be a function'
);
}
let { avatar, profileAvatar, profileKey } = conversation;
if (avatar && avatar.data) {
avatar = {
hash: await computeHash(avatar.data),
path: await writeNewAttachmentData(avatar.data),
};
}
if (profileAvatar && profileAvatar.data) {
profileAvatar = {
hash: await computeHash(profileAvatar.data),
path: await writeNewAttachmentData(profileAvatar.data),
};
}
if (profileKey && profileKey.byteLength) {
profileKey = arrayBufferToBase64(profileKey);
}
return {
...conversation,
version: 2,
avatar,
profileAvatar,
profileKey,
};
}
async function migrateConversation(conversation, options = {}) {
if (!conversation) {
return conversation;
}
if (!isNumber(conversation.version)) {
// eslint-disable-next-line no-param-reassign
conversation.version = 1;
}
return upgradeToVersion2(conversation, options);
}
async function deleteExternalFiles(conversation, options = {}) {
if (!conversation) {
return;
}
const { deleteAttachmentData } = options;
if (!isFunction(deleteAttachmentData)) {
throw new Error(
'Conversation.buildAvatarUpdater: deleteAttachmentData must be a function'
);
}
const { avatar, profileAvatar } = conversation;
if (avatar && avatar.path) {
await deleteAttachmentData(avatar.path);
}
if (profileAvatar && profileAvatar.path) {
await deleteAttachmentData(profileAvatar.path);
}
}
module.exports = {
arrayBufferToBase64,
base64ToArrayBuffer,
computeHash,
deleteExternalFiles,
maybeUpdateAvatar,
maybeUpdateProfileAvatar,
migrateConversation,
};

View file

@ -6,7 +6,7 @@ const { isFunction, isObject, isString, omit } = require('lodash');
const Contact = require('../../../ts/types/EmbeddedContact');
const Attachment = require('../../../ts/types/Attachment');
const Errors = require('../../../ts/types/errors');
const SchemaVersion = require('./schema_version');
const SchemaVersion = require('../../../ts/types/SchemaVersion');
const {
initializeAttachmentMetadata,
} = require('../../../ts/types/message/initializeAttachmentMetadata');

View file

@ -1,6 +0,0 @@
// Copyright 2018-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
const { isNumber } = require('lodash');
exports.isValid = value => isNumber(value) && value >= 0;

View file

@ -1,167 +0,0 @@
// Copyright 2018-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global document, URL, Blob */
const loadImage = require('blueimp-load-image');
const { blobToArrayBuffer } = require('blob-util');
const { toLogFormat } = require('../../../ts/types/errors');
const {
arrayBufferToObjectURL,
} = require('../../../ts/util/arrayBufferToObjectURL');
const { canvasToBlob } = require('../../../ts/util/canvasToBlob');
exports.blobToArrayBuffer = blobToArrayBuffer;
exports.getImageDimensions = ({ objectUrl, logger }) =>
new Promise((resolve, reject) => {
const image = document.createElement('img');
image.addEventListener('load', () => {
resolve({
height: image.naturalHeight,
width: image.naturalWidth,
});
});
image.addEventListener('error', error => {
logger.error('getImageDimensions error', toLogFormat(error));
reject(error);
});
image.src = objectUrl;
});
exports.makeImageThumbnail = ({
size,
objectUrl,
contentType = 'image/png',
logger,
}) =>
new Promise((resolve, reject) => {
const image = document.createElement('img');
image.addEventListener('load', async () => {
// using components/blueimp-load-image
// first, make the correct size
let canvas = loadImage.scale(image, {
canvas: true,
cover: true,
maxWidth: size,
maxHeight: size,
minWidth: size,
minHeight: size,
});
// then crop
canvas = loadImage.scale(canvas, {
canvas: true,
crop: true,
maxWidth: size,
maxHeight: size,
minWidth: size,
minHeight: size,
});
try {
const blob = await canvasToBlob(canvas, contentType);
resolve(blob);
} catch (err) {
reject(err);
}
});
image.addEventListener('error', error => {
logger.error('makeImageThumbnail error', toLogFormat(error));
reject(error);
});
image.src = objectUrl;
});
exports.makeVideoScreenshot = ({
objectUrl,
contentType = 'image/png',
logger,
}) =>
new Promise((resolve, reject) => {
const video = document.createElement('video');
function seek() {
video.currentTime = 1.0;
}
async function capture() {
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas
.getContext('2d')
.drawImage(video, 0, 0, canvas.width, canvas.height);
video.addEventListener('loadeddata', seek);
video.removeEventListener('seeked', capture);
try {
const image = canvasToBlob(canvas, contentType);
resolve(image);
} catch (err) {
reject(err);
}
}
video.addEventListener('loadeddata', seek);
video.addEventListener('seeked', capture);
video.addEventListener('error', error => {
logger.error('makeVideoScreenshot error', toLogFormat(error));
reject(error);
});
video.src = objectUrl;
});
exports.makeVideoThumbnail = async ({
size,
videoObjectUrl,
logger,
contentType,
}) => {
let screenshotObjectUrl;
try {
const blob = await exports.makeVideoScreenshot({
objectUrl: videoObjectUrl,
contentType,
logger,
});
const data = await blobToArrayBuffer(blob);
screenshotObjectUrl = arrayBufferToObjectURL({
data,
type: contentType,
});
// We need to wait for this, otherwise the finally below will run first
const resultBlob = await exports.makeImageThumbnail({
size,
objectUrl: screenshotObjectUrl,
contentType,
logger,
});
return resultBlob;
} finally {
exports.revokeObjectUrl(screenshotObjectUrl);
}
};
exports.makeObjectUrl = (data, contentType) => {
const blob = new Blob([data], {
type: contentType,
});
return URL.createObjectURL(blob);
};
exports.revokeObjectUrl = objectUrl => {
URL.revokeObjectURL(objectUrl);
};