Uint8Array migration
This commit is contained in:
parent
daf75190b8
commit
4ef0bf96cc
137 changed files with 2202 additions and 3170 deletions
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
};
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
|
@ -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);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue