Moves DraftAttachments into redux
This commit is contained in:
parent
f81f61af4e
commit
1c3c971cf4
20 changed files with 818 additions and 444 deletions
15
ts/util/deleteDraftAttachment.ts
Normal file
15
ts/util/deleteDraftAttachment.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { AttachmentType } from '../types/Attachment';
|
||||
|
||||
export async function deleteDraftAttachment(
|
||||
attachment: Pick<AttachmentType, 'screenshotPath' | 'path'>
|
||||
): Promise<void> {
|
||||
if (attachment.screenshotPath) {
|
||||
await window.Signal.Migrations.deleteDraftFile(attachment.screenshotPath);
|
||||
}
|
||||
if (attachment.path) {
|
||||
await window.Signal.Migrations.deleteDraftFile(attachment.path);
|
||||
}
|
||||
}
|
18
ts/util/fileToBytes.ts
Normal file
18
ts/util/fileToBytes.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
export function fileToBytes(file: Blob): Promise<Uint8Array> {
|
||||
return new Promise((resolve, rejectPromise) => {
|
||||
const FR = new FileReader();
|
||||
FR.onload = () => {
|
||||
if (!FR.result || typeof FR.result === 'string') {
|
||||
rejectPromise(new Error('bytesFromFile: No result!'));
|
||||
return;
|
||||
}
|
||||
resolve(new Uint8Array(FR.result));
|
||||
};
|
||||
FR.onerror = rejectPromise;
|
||||
FR.onabort = rejectPromise;
|
||||
FR.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
82
ts/util/handleAttachmentsProcessing.ts
Normal file
82
ts/util/handleAttachmentsProcessing.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import {
|
||||
getPendingAttachment,
|
||||
preProcessAttachment,
|
||||
processAttachment,
|
||||
} from './processAttachment';
|
||||
import { AttachmentType } from '../types/Attachment';
|
||||
import { AttachmentToastType } from '../types/AttachmentToastType';
|
||||
|
||||
export type AddAttachmentActionType = (
|
||||
conversationId: string,
|
||||
attachment: AttachmentType
|
||||
) => unknown;
|
||||
export type AddPendingAttachmentActionType = (
|
||||
conversationId: string,
|
||||
pendingAttachment: AttachmentType
|
||||
) => unknown;
|
||||
export type RemoveAttachmentActionType = (
|
||||
conversationId: string,
|
||||
filePath: string
|
||||
) => unknown;
|
||||
|
||||
export type HandleAttachmentsProcessingArgsType = {
|
||||
addAttachment: AddAttachmentActionType;
|
||||
addPendingAttachment: AddPendingAttachmentActionType;
|
||||
conversationId: string;
|
||||
draftAttachments: ReadonlyArray<AttachmentType>;
|
||||
files: ReadonlyArray<File>;
|
||||
onShowToast: (toastType: AttachmentToastType) => unknown;
|
||||
removeAttachment: RemoveAttachmentActionType;
|
||||
};
|
||||
|
||||
export async function handleAttachmentsProcessing({
|
||||
addAttachment,
|
||||
addPendingAttachment,
|
||||
conversationId,
|
||||
draftAttachments,
|
||||
files,
|
||||
onShowToast,
|
||||
removeAttachment,
|
||||
}: HandleAttachmentsProcessingArgsType): Promise<void> {
|
||||
if (!files.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextDraftAttachments = [...draftAttachments];
|
||||
const filesToProcess: Array<File> = [];
|
||||
for (let i = 0; i < files.length; i += 1) {
|
||||
const file = files[i];
|
||||
const processingResult = preProcessAttachment(file, nextDraftAttachments);
|
||||
if (processingResult) {
|
||||
onShowToast(processingResult);
|
||||
} else {
|
||||
const pendingAttachment = getPendingAttachment(file);
|
||||
if (pendingAttachment) {
|
||||
addPendingAttachment(conversationId, pendingAttachment);
|
||||
filesToProcess.push(file);
|
||||
// we keep a running count of the draft attachments so we can show a
|
||||
// toast in case we add too many attachments at once
|
||||
nextDraftAttachments.push(pendingAttachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
filesToProcess.map(async file => {
|
||||
try {
|
||||
const attachment = await processAttachment(file);
|
||||
if (!attachment) {
|
||||
removeAttachment(conversationId, file.path);
|
||||
return;
|
||||
}
|
||||
addAttachment(conversationId, attachment);
|
||||
} catch (err) {
|
||||
removeAttachment(conversationId, file.path);
|
||||
onShowToast(AttachmentToastType.ToastUnableToLoadAttachment);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
43
ts/util/handleVideoAttachment.ts
Normal file
43
ts/util/handleVideoAttachment.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { blobToArrayBuffer } from 'blob-util';
|
||||
|
||||
import * as log from '../logging/log';
|
||||
import { makeVideoScreenshot } from '../types/VisualAttachment';
|
||||
import { IMAGE_PNG, stringToMIMEType } from '../types/MIME';
|
||||
import { InMemoryAttachmentDraftType } from '../types/Attachment';
|
||||
import { fileToBytes } from './fileToBytes';
|
||||
|
||||
export async function handleVideoAttachment(
|
||||
file: Readonly<File>
|
||||
): Promise<InMemoryAttachmentDraftType> {
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
if (!objectUrl) {
|
||||
throw new Error('Failed to create object url for video!');
|
||||
}
|
||||
try {
|
||||
const screenshotContentType = IMAGE_PNG;
|
||||
const screenshotBlob = await makeVideoScreenshot({
|
||||
objectUrl,
|
||||
contentType: screenshotContentType,
|
||||
logger: log,
|
||||
});
|
||||
const screenshotData = await blobToArrayBuffer(screenshotBlob);
|
||||
const data = await fileToBytes(file);
|
||||
|
||||
return {
|
||||
contentType: stringToMIMEType(file.type),
|
||||
data,
|
||||
fileName: file.name,
|
||||
path: file.name,
|
||||
pending: false,
|
||||
screenshotContentType,
|
||||
screenshotData: new Uint8Array(screenshotData),
|
||||
screenshotSize: screenshotData.byteLength,
|
||||
size: data.byteLength,
|
||||
};
|
||||
} finally {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
}
|
||||
}
|
|
@ -12289,22 +12289,6 @@
|
|||
"updated": "2020-05-20T20:10:43.540Z",
|
||||
"reasonDetail": "Our code, no user input, only clearing out the dom"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/components/CompositionArea.js",
|
||||
"line": " const inputApiRef = React.useRef();",
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2020-10-26T19:12:24.410Z",
|
||||
"reasonDetail": "Doesn't refer to a DOM element."
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/components/CompositionArea.js",
|
||||
"line": " const micCellRef = React.useRef(null);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-10-26T19:12:24.410Z",
|
||||
"reasonDetail": "Needed for the composition area."
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "ts/components/CompositionArea.tsx",
|
||||
|
@ -12316,16 +12300,23 @@
|
|||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/components/CompositionArea.tsx",
|
||||
"line": " const inputApiRef = React.useRef<InputApi | undefined>();",
|
||||
"line": " const micCellRef = useRef<HTMLDivElement>(null);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-07-30T16:57:33.618Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/components/CompositionArea.tsx",
|
||||
"line": " const micCellRef = React.useRef<HTMLDivElement>(null);",
|
||||
"line": " const inputApiRef = useRef<InputApi | undefined>();",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-07-30T16:57:33.618Z"
|
||||
"updated": "2021-09-23T00:07:11.885Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/components/CompositionArea.tsx",
|
||||
"line": " const fileInputRef = useRef<null | HTMLInputElement>(null);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-09-23T00:07:11.885Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
|
@ -14283,4 +14274,4 @@
|
|||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-09-17T21:02:59.414Z"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
122
ts/util/processAttachment.ts
Normal file
122
ts/util/processAttachment.ts
Normal file
|
@ -0,0 +1,122 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import * as log from '../logging/log';
|
||||
import { AttachmentType } from '../types/Attachment';
|
||||
import { AttachmentToastType } from '../types/AttachmentToastType';
|
||||
import { fileToBytes } from './fileToBytes';
|
||||
import { handleImageAttachment } from './handleImageAttachment';
|
||||
import { handleVideoAttachment } from './handleVideoAttachment';
|
||||
import { isAttachmentSizeOkay } from './isAttachmentSizeOkay';
|
||||
import { isFileDangerous } from './isFileDangerous';
|
||||
import { isHeic, isImage, stringToMIMEType } from '../types/MIME';
|
||||
import { isImageTypeSupported, isVideoTypeSupported } from './GoogleChrome';
|
||||
|
||||
export function getPendingAttachment(file: File): AttachmentType | undefined {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileType = stringToMIMEType(file.type);
|
||||
const { name: fileName } = path.parse(file.name);
|
||||
|
||||
return {
|
||||
contentType: fileType,
|
||||
fileName,
|
||||
path: file.name,
|
||||
pending: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function preProcessAttachment(
|
||||
file: File,
|
||||
draftAttachments: Array<AttachmentType>
|
||||
): AttachmentToastType | undefined {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const MB = 1000 * 1024;
|
||||
if (file.size > 100 * MB) {
|
||||
return AttachmentToastType.ToastFileSize;
|
||||
}
|
||||
|
||||
if (isFileDangerous(file.name)) {
|
||||
return AttachmentToastType.ToastDangerousFileType;
|
||||
}
|
||||
|
||||
if (draftAttachments.length >= 32) {
|
||||
return AttachmentToastType.ToastMaxAttachments;
|
||||
}
|
||||
|
||||
const haveNonImage = draftAttachments.some(
|
||||
(attachment: AttachmentType) => !isImage(attachment.contentType)
|
||||
);
|
||||
// You can't add another attachment if you already have a non-image staged
|
||||
if (haveNonImage) {
|
||||
return AttachmentToastType.ToastOneNonImageAtATime;
|
||||
}
|
||||
|
||||
const fileType = stringToMIMEType(file.type);
|
||||
|
||||
// You can't add a non-image attachment if you already have attachments staged
|
||||
if (!isImage(fileType) && draftAttachments.length > 0) {
|
||||
return AttachmentToastType.ToastCannotMixImageAndNonImageAttachments;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function processAttachment(
|
||||
file: File
|
||||
): Promise<AttachmentType | void> {
|
||||
const fileType = stringToMIMEType(file.type);
|
||||
|
||||
let attachment: AttachmentType;
|
||||
try {
|
||||
if (isImageTypeSupported(fileType) || isHeic(fileType)) {
|
||||
attachment = await handleImageAttachment(file);
|
||||
} else if (isVideoTypeSupported(fileType)) {
|
||||
attachment = await handleVideoAttachment(file);
|
||||
} else {
|
||||
const data = await fileToBytes(file);
|
||||
attachment = {
|
||||
contentType: fileType,
|
||||
data,
|
||||
fileName: file.name,
|
||||
path: file.name,
|
||||
pending: false,
|
||||
size: data.byteLength,
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
log.error(
|
||||
`Was unable to generate thumbnail for fileType ${fileType}`,
|
||||
e && e.stack ? e.stack : e
|
||||
);
|
||||
const data = await fileToBytes(file);
|
||||
attachment = {
|
||||
contentType: fileType,
|
||||
data,
|
||||
fileName: file.name,
|
||||
path: file.name,
|
||||
pending: false,
|
||||
size: data.byteLength,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
if (isAttachmentSizeOkay(attachment)) {
|
||||
return attachment;
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(
|
||||
'Error ensuring that image is properly sized:',
|
||||
error && error.stack ? error.stack : error
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
40
ts/util/resolveAttachmentOnDisk.ts
Normal file
40
ts/util/resolveAttachmentOnDisk.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { pick } from 'lodash';
|
||||
|
||||
import * as log from '../logging/log';
|
||||
import { AttachmentType } from '../types/Attachment';
|
||||
|
||||
export function resolveAttachmentOnDisk(
|
||||
attachment: AttachmentType
|
||||
): AttachmentType {
|
||||
let url = '';
|
||||
if (attachment.pending) {
|
||||
return attachment;
|
||||
}
|
||||
|
||||
if (attachment.screenshotPath) {
|
||||
url = window.Signal.Migrations.getAbsoluteDraftPath(
|
||||
attachment.screenshotPath
|
||||
);
|
||||
} else if (attachment.path) {
|
||||
url = window.Signal.Migrations.getAbsoluteDraftPath(attachment.path);
|
||||
} else {
|
||||
log.warn(
|
||||
'resolveOnDiskAttachment: Attachment was missing both screenshotPath and path fields'
|
||||
);
|
||||
}
|
||||
return {
|
||||
...pick(attachment, [
|
||||
'blurHash',
|
||||
'caption',
|
||||
'contentType',
|
||||
'fileName',
|
||||
'path',
|
||||
'size',
|
||||
]),
|
||||
pending: false,
|
||||
url,
|
||||
};
|
||||
}
|
29
ts/util/writeDraftAttachment.ts
Normal file
29
ts/util/writeDraftAttachment.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { omit } from 'lodash';
|
||||
import { AttachmentType } from '../types/Attachment';
|
||||
|
||||
export async function writeDraftAttachment(
|
||||
attachment: AttachmentType
|
||||
): Promise<AttachmentType> {
|
||||
if (attachment.pending) {
|
||||
throw new Error('writeDraftAttachment: Cannot write pending attachment');
|
||||
}
|
||||
|
||||
const result: AttachmentType = {
|
||||
...omit(attachment, ['data', 'screenshotData']),
|
||||
pending: false,
|
||||
};
|
||||
if (attachment.data) {
|
||||
result.path = await window.Signal.Migrations.writeNewDraftData(
|
||||
attachment.data
|
||||
);
|
||||
}
|
||||
if (attachment.screenshotData) {
|
||||
result.screenshotPath = await window.Signal.Migrations.writeNewDraftData(
|
||||
attachment.screenshotData
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue