Send related emoji along with Sticker, fix SendMessage types

This commit is contained in:
Scott Nonnenberg 2021-10-05 15:10:08 -07:00 committed by GitHub
parent 3c91dce993
commit bd380086a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 522 additions and 376 deletions

View file

@ -168,14 +168,16 @@ export const createDoesExist = (
export const copyIntoAttachmentsDirectory = ( export const copyIntoAttachmentsDirectory = (
root: string root: string
): ((sourcePath: string) => Promise<string>) => { ): ((sourcePath: string) => Promise<{ path: string; size: number }>) => {
if (!isString(root)) { if (!isString(root)) {
throw new TypeError("'root' must be a path"); throw new TypeError("'root' must be a path");
} }
const userDataPath = getApp().getPath('userData'); const userDataPath = getApp().getPath('userData');
return async (sourcePath: string): Promise<string> => { return async (
sourcePath: string
): Promise<{ path: string; size: number }> => {
if (!isString(sourcePath)) { if (!isString(sourcePath)) {
throw new TypeError('sourcePath must be a string'); throw new TypeError('sourcePath must be a string');
} }
@ -196,7 +198,12 @@ export const copyIntoAttachmentsDirectory = (
await fse.ensureFile(normalized); await fse.ensureFile(normalized);
await fse.copy(sourcePath, normalized); await fse.copy(sourcePath, normalized);
return relativePath; const { size } = await fse.stat(normalized);
return {
path: relativePath,
size,
};
}; };
}; };

View file

@ -220,6 +220,7 @@ message DataMessage {
optional bytes packKey = 2; optional bytes packKey = 2;
optional uint32 stickerId = 3; optional uint32 stickerId = 3;
optional AttachmentPointer data = 4; optional AttachmentPointer data = 4;
optional string emoji = 5;
} }
message Reaction { message Reaction {

View file

@ -1,4 +1,4 @@
// Copyright 2018-2020 Signal Messenger, LLC // Copyright 2018-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
const fs = require('fs'); const fs = require('fs');
@ -222,14 +222,14 @@ describe('Attachments', () => {
}); });
}); });
it('returns a function that copies the source path into the attachments directory', async function thisNeeded() { it('returns a function that copies the source path into the attachments directory and returns its path and size', async function thisNeeded() {
const attachmentsPath = await this.getFakeAttachmentsDirectory(); const attachmentsPath = await this.getFakeAttachmentsDirectory();
const someOtherPath = path.join(app.getPath('userData'), 'somethingElse'); const someOtherPath = path.join(app.getPath('userData'), 'somethingElse');
await fse.outputFile(someOtherPath, 'hello world'); await fse.outputFile(someOtherPath, 'hello world');
this.filesToRemove.push(someOtherPath); this.filesToRemove.push(someOtherPath);
const copier = Attachments.copyIntoAttachmentsDirectory(attachmentsPath); const copier = Attachments.copyIntoAttachmentsDirectory(attachmentsPath);
const relativePath = await copier(someOtherPath); const { path: relativePath, size } = await copier(someOtherPath);
const absolutePath = path.join(attachmentsPath, relativePath); const absolutePath = path.join(attachmentsPath, relativePath);
assert.notEqual(someOtherPath, absolutePath); assert.notEqual(someOtherPath, absolutePath);
@ -237,6 +237,8 @@ describe('Attachments', () => {
await fs.promises.readFile(absolutePath, 'utf8'), await fs.promises.readFile(absolutePath, 'utf8'),
'hello world' 'hello world'
); );
assert.strictEqual(size, 'hello world'.length);
}); });
}); });

View file

@ -12,17 +12,19 @@ import { AUDIO_MP3, IMAGE_JPEG, VIDEO_MP4 } from '../types/MIME';
import { setupI18n } from '../util/setupI18n'; import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json'; import enMessages from '../../_locales/en/messages.json';
import { fakeAttachment } from '../test-both/helpers/fakeAttachment';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
const stories = storiesOf('Components/Caption Editor', module); const stories = storiesOf('Components/Caption Editor', module);
const createProps = (overrideProps: Partial<Props> = {}): Props => ({ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
attachment: { attachment: fakeAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: '', fileName: '',
url: '', url: '',
...overrideProps.attachment, ...overrideProps.attachment,
}, }),
caption: text('caption', overrideProps.caption || ''), caption: text('caption', overrideProps.caption || ''),
close: action('close'), close: action('close'),
i18n, i18n,
@ -50,11 +52,11 @@ stories.add('Image with Caption', () => {
stories.add('Video', () => { stories.add('Video', () => {
const props = createProps({ const props = createProps({
attachment: { attachment: fakeAttachment({
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
fileName: 'pixabay-Soap-Bubble-7141.mp4', fileName: 'pixabay-Soap-Bubble-7141.mp4',
url: '/fixtures/pixabay-Soap-Bubble-7141.mp4', url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
}, }),
url: '/fixtures/pixabay-Soap-Bubble-7141.mp4', url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
}); });
@ -63,11 +65,11 @@ stories.add('Video', () => {
stories.add('Video with Caption', () => { stories.add('Video with Caption', () => {
const props = createProps({ const props = createProps({
attachment: { attachment: fakeAttachment({
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
fileName: 'pixabay-Soap-Bubble-7141.mp4', fileName: 'pixabay-Soap-Bubble-7141.mp4',
url: '/fixtures/pixabay-Soap-Bubble-7141.mp4', url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
}, }),
caption: caption:
'This is the user-provided caption. We show it overlaid on the image. If it is really long, then it wraps, but it does not get too close to the edges of the image.', 'This is the user-provided caption. We show it overlaid on the image. If it is really long, then it wraps, but it does not get too close to the edges of the image.',
url: '/fixtures/pixabay-Soap-Bubble-7141.mp4', url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
@ -78,11 +80,11 @@ stories.add('Video with Caption', () => {
stories.add('Unsupported Attachment Type', () => { stories.add('Unsupported Attachment Type', () => {
const props = createProps({ const props = createProps({
attachment: { attachment: fakeAttachment({
contentType: AUDIO_MP3, contentType: AUDIO_MP3,
fileName: 'incompetech-com-Agnus-Dei-X.mp3', fileName: 'incompetech-com-Agnus-Dei-X.mp3',
url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3', url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3',
}, }),
url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3', url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3',
}); });

View file

@ -12,6 +12,9 @@ import { CompositionArea, Props } from './CompositionArea';
import { setupI18n } from '../util/setupI18n'; import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json'; import enMessages from '../../_locales/en/messages.json';
import { fakeAttachment } from '../test-both/helpers/fakeAttachment';
import { landscapeGreenUrl } from '../storybook/Fixtures';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
const story = storiesOf('Components/CompositionArea', module); const story = storiesOf('Components/CompositionArea', module);
@ -154,9 +157,10 @@ story.add('SMS-only', () => {
story.add('Attachments', () => { story.add('Attachments', () => {
const props = createProps({ const props = createProps({
draftAttachments: [ draftAttachments: [
{ fakeAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
}, url: landscapeGreenUrl,
}),
], ],
}); });

View file

@ -23,6 +23,7 @@ const createAttachment = (
fileName: text('attachment fileName', props.fileName || ''), fileName: text('attachment fileName', props.fileName || ''),
screenshot: props.screenshot, screenshot: props.screenshot,
url: text('attachment url', props.url || ''), url: text('attachment url', props.url || ''),
size: 3433,
}); });
const story = storiesOf('Components/ForwardMessageModal', module); const story = storiesOf('Components/ForwardMessageModal', module);

View file

@ -19,6 +19,8 @@ import {
stringToMIMEType, stringToMIMEType,
} from '../types/MIME'; } from '../types/MIME';
import { fakeAttachment } from '../test-both/helpers/fakeAttachment';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
const story = storiesOf('Components/Lightbox', module); const story = storiesOf('Components/Lightbox', module);
@ -29,12 +31,12 @@ function createMediaItem(
overrideProps: OverridePropsMediaItemType overrideProps: OverridePropsMediaItemType
): MediaItemType { ): MediaItemType {
return { return {
attachment: { attachment: fakeAttachment({
caption: overrideProps.caption || '', caption: overrideProps.caption || '',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: overrideProps.objectURL, fileName: overrideProps.objectURL,
url: overrideProps.objectURL, url: overrideProps.objectURL,
}, }),
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
index: 0, index: 0,
message: { message: {
@ -63,13 +65,13 @@ story.add('Multimedia', () => {
const props = createProps({ const props = createProps({
media: [ media: [
{ {
attachment: { attachment: fakeAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
caption: caption:
'Still from The Lighthouse, starring Robert Pattinson and Willem Defoe.', 'Still from The Lighthouse, starring Robert Pattinson and Willem Defoe.',
}, }),
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
index: 0, index: 0,
message: { message: {
@ -83,11 +85,11 @@ story.add('Multimedia', () => {
objectURL: '/fixtures/tina-rolf-269345-unsplash.jpg', objectURL: '/fixtures/tina-rolf-269345-unsplash.jpg',
}, },
{ {
attachment: { attachment: fakeAttachment({
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
fileName: 'pixabay-Soap-Bubble-7141.mp4', fileName: 'pixabay-Soap-Bubble-7141.mp4',
url: '/fixtures/pixabay-Soap-Bubble-7141.mp4', url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
}, }),
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
index: 1, index: 1,
message: { message: {
@ -122,11 +124,11 @@ story.add('Missing Media', () => {
const props = createProps({ const props = createProps({
media: [ media: [
{ {
attachment: { attachment: fakeAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
}, }),
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
index: 0, index: 0,
message: { message: {

View file

@ -17,6 +17,8 @@ import {
import { setupI18n } from '../../util/setupI18n'; import { setupI18n } from '../../util/setupI18n';
import enMessages from '../../../_locales/en/messages.json'; import enMessages from '../../../_locales/en/messages.json';
import { fakeAttachment } from '../../test-both/helpers/fakeAttachment';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
const story = storiesOf('Components/Conversation/AttachmentList', module); const story = storiesOf('Components/Conversation/AttachmentList', module);
@ -33,11 +35,11 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
story.add('One File', () => { story.add('One File', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
}, }),
], ],
}); });
return <AttachmentList {...props} />; return <AttachmentList {...props} />;
@ -46,12 +48,12 @@ story.add('One File', () => {
story.add('Multiple Visual Attachments', () => { story.add('Multiple Visual Attachments', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
}, }),
{ fakeAttachment({
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
fileName: 'pixabay-Soap-Bubble-7141.mp4', fileName: 'pixabay-Soap-Bubble-7141.mp4',
url: '/fixtures/pixabay-Soap-Bubble-7141.mp4', url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
@ -62,12 +64,12 @@ story.add('Multiple Visual Attachments', () => {
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
path: 'originalpath', path: 'originalpath',
}, },
}, }),
{ fakeAttachment({
contentType: IMAGE_GIF, contentType: IMAGE_GIF,
fileName: 'giphy-GVNv0UpeYm17e', fileName: 'giphy-GVNv0UpeYm17e',
url: '/fixtures/giphy-GVNvOUpeYmI7e.gif', url: '/fixtures/giphy-GVNvOUpeYmI7e.gif',
}, }),
], ],
}); });
@ -77,22 +79,22 @@ story.add('Multiple Visual Attachments', () => {
story.add('Multiple with Non-Visual Types', () => { story.add('Multiple with Non-Visual Types', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
}, }),
{ fakeAttachment({
contentType: stringToMIMEType('text/plain'), contentType: stringToMIMEType('text/plain'),
fileName: 'lorem-ipsum.txt', fileName: 'lorem-ipsum.txt',
url: '/fixtures/lorem-ipsum.txt', url: '/fixtures/lorem-ipsum.txt',
}, }),
{ fakeAttachment({
contentType: AUDIO_MP3, contentType: AUDIO_MP3,
fileName: 'incompetech-com-Agnus-Dei-X.mp3', fileName: 'incompetech-com-Agnus-Dei-X.mp3',
url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3', url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3',
}, }),
{ fakeAttachment({
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
fileName: 'pixabay-Soap-Bubble-7141.mp4', fileName: 'pixabay-Soap-Bubble-7141.mp4',
url: '/fixtures/pixabay-Soap-Bubble-7141.mp4', url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
@ -103,12 +105,12 @@ story.add('Multiple with Non-Visual Types', () => {
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
path: 'originalpath', path: 'originalpath',
}, },
}, }),
{ fakeAttachment({
contentType: IMAGE_GIF, contentType: IMAGE_GIF,
fileName: 'giphy-GVNv0UpeYm17e', fileName: 'giphy-GVNv0UpeYm17e',
url: '/fixtures/giphy-GVNvOUpeYmI7e.gif', url: '/fixtures/giphy-GVNvOUpeYmI7e.gif',
}, }),
], ],
}); });

View file

@ -13,6 +13,8 @@ import { setupI18n } from '../../util/setupI18n';
import enMessages from '../../../_locales/en/messages.json'; import enMessages from '../../../_locales/en/messages.json';
import { IMAGE_GIF } from '../../types/MIME'; import { IMAGE_GIF } from '../../types/MIME';
import { fakeAttachment } from '../../test-both/helpers/fakeAttachment';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
const story = storiesOf('Components/Conversation/ContactDetail', module); const story = storiesOf('Components/Conversation/ContactDetail', module);
@ -72,10 +74,10 @@ const fullContact = {
}, },
], ],
avatar: { avatar: {
avatar: { avatar: fakeAttachment({
path: '/fixtures/giphy-GVNvOUpeYmI7e.gif', path: '/fixtures/giphy-GVNvOUpeYmI7e.gif',
contentType: IMAGE_GIF, contentType: IMAGE_GIF,
}, }),
isProfile: true, isProfile: true,
}, },
email: [ email: [
@ -209,10 +211,10 @@ story.add('Loading Avatar', () => {
const props = createProps({ const props = createProps({
contact: { contact: {
avatar: { avatar: {
avatar: { avatar: fakeAttachment({
contentType: IMAGE_GIF, contentType: IMAGE_GIF,
pending: true, pending: true,
}, }),
isProfile: true, isProfile: true,
}, },
}, },

View file

@ -13,6 +13,8 @@ import enMessages from '../../../_locales/en/messages.json';
import { ContactFormType } from '../../types/EmbeddedContact'; import { ContactFormType } from '../../types/EmbeddedContact';
import { IMAGE_GIF } from '../../types/MIME'; import { IMAGE_GIF } from '../../types/MIME';
import { fakeAttachment } from '../../test-both/helpers/fakeAttachment';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
const story = storiesOf('Components/Conversation/EmbeddedContact', module); const story = storiesOf('Components/Conversation/EmbeddedContact', module);
@ -35,10 +37,10 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
const fullContact = { const fullContact = {
avatar: { avatar: {
avatar: { avatar: fakeAttachment({
path: '/fixtures/giphy-GVNvOUpeYmI7e.gif', path: '/fixtures/giphy-GVNvOUpeYmI7e.gif',
contentType: IMAGE_GIF, contentType: IMAGE_GIF,
}, }),
isProfile: true, isProfile: true,
}, },
email: [ email: [
@ -134,10 +136,10 @@ story.add('Loading Avatar', () => {
displayName: 'Jerry Jordan', displayName: 'Jerry Jordan',
}, },
avatar: { avatar: {
avatar: { avatar: fakeAttachment({
pending: true, pending: true,
contentType: IMAGE_GIF, contentType: IMAGE_GIF,
}, }),
isProfile: true, isProfile: true,
}, },
}, },

View file

@ -14,17 +14,21 @@ import { ThemeType } from '../../types/Util';
import { setupI18n } from '../../util/setupI18n'; import { setupI18n } from '../../util/setupI18n';
import enMessages from '../../../_locales/en/messages.json'; import enMessages from '../../../_locales/en/messages.json';
import { fakeAttachment } from '../../test-both/helpers/fakeAttachment';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
const story = storiesOf('Components/Conversation/Image', module); const story = storiesOf('Components/Conversation/Image', module);
const createProps = (overrideProps: Partial<Props> = {}): Props => ({ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
alt: text('alt', overrideProps.alt || ''), alt: text('alt', overrideProps.alt || ''),
attachment: overrideProps.attachment || { attachment:
contentType: IMAGE_PNG, overrideProps.attachment ||
fileName: 'sax.png', fakeAttachment({
url: pngUrl, contentType: IMAGE_PNG,
}, fileName: 'sax.png',
url: pngUrl,
}),
blurHash: text('blurHash', overrideProps.blurHash || ''), blurHash: text('blurHash', overrideProps.blurHash || ''),
bottomOverlay: boolean('bottomOverlay', overrideProps.bottomOverlay || false), bottomOverlay: boolean('bottomOverlay', overrideProps.bottomOverlay || false),
closeButton: boolean('closeButton', overrideProps.closeButton || false), closeButton: boolean('closeButton', overrideProps.closeButton || false),
@ -99,11 +103,11 @@ story.add('Close Button', () => {
story.add('No Border or Background', () => { story.add('No Border or Background', () => {
const props = createProps({ const props = createProps({
attachment: { attachment: fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
url: pngUrl, url: pngUrl,
}, }),
noBackground: true, noBackground: true,
noBorder: true, noBorder: true,
url: pngUrl, url: pngUrl,

View file

@ -19,6 +19,7 @@ import {
import { setupI18n } from '../../util/setupI18n'; import { setupI18n } from '../../util/setupI18n';
import enMessages from '../../../_locales/en/messages.json'; import enMessages from '../../../_locales/en/messages.json';
import { pngUrl, squareStickerUrl } from '../../storybook/Fixtures'; import { pngUrl, squareStickerUrl } from '../../storybook/Fixtures';
import { fakeAttachment } from '../../test-both/helpers/fakeAttachment';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
@ -26,13 +27,13 @@ const story = storiesOf('Components/Conversation/ImageGrid', module);
const createProps = (overrideProps: Partial<Props> = {}): Props => ({ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
attachments: overrideProps.attachments || [ attachments: overrideProps.attachments || [
{ fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
height: 1200, height: 1200,
url: pngUrl, url: pngUrl,
width: 800, width: 800,
}, }),
], ],
bottomOverlay: boolean('bottomOverlay', overrideProps.bottomOverlay || false), bottomOverlay: boolean('bottomOverlay', overrideProps.bottomOverlay || false),
i18n, i18n,
@ -60,20 +61,20 @@ story.add('One Image', () => {
story.add('Two Images', () => { story.add('Two Images', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
height: 1200, height: 1200,
url: pngUrl, url: pngUrl,
width: 800, width: 800,
}, }),
{ fakeAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
height: 1680, height: 1680,
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
width: 3000, width: 3000,
}, }),
], ],
}); });
@ -83,27 +84,27 @@ story.add('Two Images', () => {
story.add('Three Images', () => { story.add('Three Images', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
height: 1200, height: 1200,
url: pngUrl, url: pngUrl,
width: 800, width: 800,
}, }),
{ fakeAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
height: 1680, height: 1680,
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
width: 3000, width: 3000,
}, }),
{ fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
height: 1200, height: 1200,
url: pngUrl, url: pngUrl,
width: 800, width: 800,
}, }),
], ],
}); });
@ -113,34 +114,34 @@ story.add('Three Images', () => {
story.add('Four Images', () => { story.add('Four Images', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
height: 1200, height: 1200,
url: pngUrl, url: pngUrl,
width: 800, width: 800,
}, }),
{ fakeAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
height: 1680, height: 1680,
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
width: 3000, width: 3000,
}, }),
{ fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
height: 1200, height: 1200,
url: pngUrl, url: pngUrl,
width: 800, width: 800,
}, }),
{ fakeAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
height: 1680, height: 1680,
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
width: 3000, width: 3000,
}, }),
], ],
}); });
@ -150,41 +151,41 @@ story.add('Four Images', () => {
story.add('Five Images', () => { story.add('Five Images', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
height: 1200, height: 1200,
url: pngUrl, url: pngUrl,
width: 800, width: 800,
}, }),
{ fakeAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
height: 1680, height: 1680,
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
width: 3000, width: 3000,
}, }),
{ fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
height: 1200, height: 1200,
url: pngUrl, url: pngUrl,
width: 800, width: 800,
}, }),
{ fakeAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
height: 1680, height: 1680,
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
width: 3000, width: 3000,
}, }),
{ fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
height: 1200, height: 1200,
url: pngUrl, url: pngUrl,
width: 800, width: 800,
}, }),
], ],
}); });
@ -194,55 +195,55 @@ story.add('Five Images', () => {
story.add('6+ Images', () => { story.add('6+ Images', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
height: 1200, height: 1200,
url: pngUrl, url: pngUrl,
width: 800, width: 800,
}, }),
{ fakeAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
height: 1680, height: 1680,
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
width: 3000, width: 3000,
}, }),
{ fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
height: 1200, height: 1200,
url: pngUrl, url: pngUrl,
width: 800, width: 800,
}, }),
{ fakeAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
height: 1680, height: 1680,
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
width: 3000, width: 3000,
}, }),
{ fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
height: 1200, height: 1200,
url: pngUrl, url: pngUrl,
width: 800, width: 800,
}, }),
{ fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
height: 1200, height: 1200,
url: pngUrl, url: pngUrl,
width: 800, width: 800,
}, }),
{ fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
height: 1200, height: 1200,
url: pngUrl, url: pngUrl,
width: 800, width: 800,
}, }),
], ],
}); });
@ -251,7 +252,7 @@ story.add('6+ Images', () => {
story.add('Mixed Content Types', () => { story.add('Mixed Content Types', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
fileName: 'pixabay-Soap-Bubble-7141.mp4', fileName: 'pixabay-Soap-Bubble-7141.mp4',
height: 112, height: 112,
@ -264,24 +265,24 @@ story.add('Mixed Content Types', () => {
}, },
url: '/fixtures/pixabay-Soap-Bubble-7141.mp4', url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
width: 112, width: 112,
}, }),
{ fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'sax.png', fileName: 'sax.png',
height: 1200, height: 1200,
url: pngUrl, url: pngUrl,
width: 800, width: 800,
}, }),
{ fakeAttachment({
contentType: stringToMIMEType('text/plain'), contentType: stringToMIMEType('text/plain'),
fileName: 'lorem-ipsum.txt', fileName: 'lorem-ipsum.txt',
url: '/fixtures/lorem-ipsum.txt', url: '/fixtures/lorem-ipsum.txt',
}, }),
{ fakeAttachment({
contentType: AUDIO_MP3, contentType: AUDIO_MP3,
fileName: 'incompetech-com-Agnus-Dei-X.mp3', fileName: 'incompetech-com-Agnus-Dei-X.mp3',
url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3', url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3',
}, }),
], ],
}); });
@ -291,13 +292,13 @@ story.add('Mixed Content Types', () => {
story.add('Sticker', () => { story.add('Sticker', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: IMAGE_WEBP, contentType: IMAGE_WEBP,
fileName: 'sticker.webp', fileName: 'sticker.webp',
height: 512, height: 512,
url: squareStickerUrl, url: squareStickerUrl,
width: 512, width: 512,
}, }),
], ],
isSticker: true, isSticker: true,
stickerSize: 128, stickerSize: 128,

View file

@ -28,6 +28,8 @@ import enMessages from '../../../_locales/en/messages.json';
import { pngUrl } from '../../storybook/Fixtures'; import { pngUrl } from '../../storybook/Fixtures';
import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation'; import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation';
import { fakeAttachment } from '../../test-both/helpers/fakeAttachment';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
function getJoyReaction() { function getJoyReaction() {
@ -444,13 +446,13 @@ story.add('Avatar in Group', () => {
story.add('Sticker', () => { story.add('Sticker', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
url: '/fixtures/512x515-thumbs-up-lincoln.webp', url: '/fixtures/512x515-thumbs-up-lincoln.webp',
fileName: '512x515-thumbs-up-lincoln.webp', fileName: '512x515-thumbs-up-lincoln.webp',
contentType: IMAGE_WEBP, contentType: IMAGE_WEBP,
width: 128, width: 128,
height: 128, height: 128,
}, }),
], ],
isSticker: true, isSticker: true,
status: 'sent', status: 'sent',
@ -524,13 +526,13 @@ story.add('Link Preview', () => {
previews: [ previews: [
{ {
domain: 'signal.org', domain: 'signal.org',
image: { image: fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'the-sax.png', fileName: 'the-sax.png',
height: 240, height: 240,
url: pngUrl, url: pngUrl,
width: 320, width: 320,
}, }),
isStickerPack: false, isStickerPack: false,
title: 'Signal', title: 'Signal',
description: description:
@ -551,13 +553,13 @@ story.add('Link Preview with Small Image', () => {
previews: [ previews: [
{ {
domain: 'signal.org', domain: 'signal.org',
image: { image: fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'the-sax.png', fileName: 'the-sax.png',
height: 50, height: 50,
url: pngUrl, url: pngUrl,
width: 50, width: 50,
}, }),
isStickerPack: false, isStickerPack: false,
title: 'Signal', title: 'Signal',
description: description:
@ -639,13 +641,13 @@ story.add('Link Preview with small image, long description', () => {
previews: [ previews: [
{ {
domain: 'signal.org', domain: 'signal.org',
image: { image: fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'the-sax.png', fileName: 'the-sax.png',
height: 50, height: 50,
url: pngUrl, url: pngUrl,
width: 50, width: 50,
}, }),
isStickerPack: false, isStickerPack: false,
title: 'Signal', title: 'Signal',
description: Array(10) description: Array(10)
@ -669,13 +671,13 @@ story.add('Link Preview with no date', () => {
previews: [ previews: [
{ {
domain: 'signal.org', domain: 'signal.org',
image: { image: fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'the-sax.png', fileName: 'the-sax.png',
height: 240, height: 240,
url: pngUrl, url: pngUrl,
width: 320, width: 320,
}, }),
isStickerPack: false, isStickerPack: false,
title: 'Signal', title: 'Signal',
description: description:
@ -695,13 +697,13 @@ story.add('Link Preview with too new a date', () => {
previews: [ previews: [
{ {
domain: 'signal.org', domain: 'signal.org',
image: { image: fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'the-sax.png', fileName: 'the-sax.png',
height: 240, height: 240,
url: pngUrl, url: pngUrl,
width: 320, width: 320,
}, }),
isStickerPack: false, isStickerPack: false,
title: 'Signal', title: 'Signal',
description: description:
@ -720,13 +722,13 @@ story.add('Link Preview with too new a date', () => {
story.add('Image', () => { story.add('Image', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
width: 128, width: 128,
height: 128, height: 128,
}, }),
], ],
status: 'sent', status: 'sent',
}); });
@ -738,41 +740,41 @@ for (let i = 2; i <= 5; i += 1) {
story.add(`Multiple Images x${i}`, () => { story.add(`Multiple Images x${i}`, () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
width: 128, width: 128,
height: 128, height: 128,
}, }),
{ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
width: 128, width: 128,
height: 128, height: 128,
}, }),
{ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
width: 128, width: 128,
height: 128, height: 128,
}, }),
{ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
width: 128, width: 128,
height: 128, height: 128,
}, }),
{ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
width: 128, width: 128,
height: 128, height: 128,
}, }),
].slice(0, i), ].slice(0, i),
status: 'sent', status: 'sent',
}); });
@ -784,13 +786,13 @@ for (let i = 2; i <= 5; i += 1) {
story.add('Image with Caption', () => { story.add('Image with Caption', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
width: 128, width: 128,
height: 128, height: 128,
}, }),
], ],
status: 'sent', status: 'sent',
text: 'This is my home.', text: 'This is my home.',
@ -802,14 +804,14 @@ story.add('Image with Caption', () => {
story.add('GIF', () => { story.add('GIF', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
flags: SignalService.AttachmentPointer.Flags.GIF, flags: SignalService.AttachmentPointer.Flags.GIF,
fileName: 'cat-gif.mp4', fileName: 'cat-gif.mp4',
url: '/fixtures/cat-gif.mp4', url: '/fixtures/cat-gif.mp4',
width: 400, width: 400,
height: 332, height: 332,
}, }),
], ],
status: 'sent', status: 'sent',
}); });
@ -820,14 +822,14 @@ story.add('GIF', () => {
story.add('GIF in a group', () => { story.add('GIF in a group', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
flags: SignalService.AttachmentPointer.Flags.GIF, flags: SignalService.AttachmentPointer.Flags.GIF,
fileName: 'cat-gif.mp4', fileName: 'cat-gif.mp4',
url: '/fixtures/cat-gif.mp4', url: '/fixtures/cat-gif.mp4',
width: 400, width: 400,
height: 332, height: 332,
}, }),
], ],
conversationType: 'group', conversationType: 'group',
status: 'sent', status: 'sent',
@ -839,7 +841,7 @@ story.add('GIF in a group', () => {
story.add('Not Downloaded GIF', () => { story.add('Not Downloaded GIF', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
flags: SignalService.AttachmentPointer.Flags.GIF, flags: SignalService.AttachmentPointer.Flags.GIF,
fileName: 'cat-gif.mp4', fileName: 'cat-gif.mp4',
@ -847,7 +849,7 @@ story.add('Not Downloaded GIF', () => {
blurHash: 'LDA,FDBnm+I=p{tkIUI;~UkpELV]', blurHash: 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
width: 400, width: 400,
height: 332, height: 332,
}, }),
], ],
status: 'sent', status: 'sent',
}); });
@ -858,7 +860,7 @@ story.add('Not Downloaded GIF', () => {
story.add('Pending GIF', () => { story.add('Pending GIF', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
pending: true, pending: true,
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
flags: SignalService.AttachmentPointer.Flags.GIF, flags: SignalService.AttachmentPointer.Flags.GIF,
@ -867,7 +869,7 @@ story.add('Pending GIF', () => {
blurHash: 'LDA,FDBnm+I=p{tkIUI;~UkpELV]', blurHash: 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
width: 400, width: 400,
height: 332, height: 332,
}, }),
], ],
status: 'sent', status: 'sent',
}); });
@ -881,11 +883,11 @@ story.add('Audio', () => {
const messageProps = createProps({ const messageProps = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: AUDIO_MP3, contentType: AUDIO_MP3,
fileName: 'incompetech-com-Agnus-Dei-X.mp3', fileName: 'incompetech-com-Agnus-Dei-X.mp3',
url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3', url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3',
}, }),
], ],
...(isPlayed ...(isPlayed
? { ? {
@ -923,11 +925,11 @@ story.add('Audio', () => {
story.add('Long Audio', () => { story.add('Long Audio', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: AUDIO_MP3, contentType: AUDIO_MP3,
fileName: 'long-audio.mp3', fileName: 'long-audio.mp3',
url: '/fixtures/long-audio.mp3', url: '/fixtures/long-audio.mp3',
}, }),
], ],
status: 'sent', status: 'sent',
}); });
@ -938,11 +940,11 @@ story.add('Long Audio', () => {
story.add('Audio with Caption', () => { story.add('Audio with Caption', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: AUDIO_MP3, contentType: AUDIO_MP3,
fileName: 'incompetech-com-Agnus-Dei-X.mp3', fileName: 'incompetech-com-Agnus-Dei-X.mp3',
url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3', url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3',
}, }),
], ],
status: 'sent', status: 'sent',
text: 'This is what I sound like.', text: 'This is what I sound like.',
@ -954,10 +956,10 @@ story.add('Audio with Caption', () => {
story.add('Audio with Not Downloaded Attachment', () => { story.add('Audio with Not Downloaded Attachment', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: AUDIO_MP3, contentType: AUDIO_MP3,
fileName: 'incompetech-com-Agnus-Dei-X.mp3', fileName: 'incompetech-com-Agnus-Dei-X.mp3',
}, }),
], ],
status: 'sent', status: 'sent',
}); });
@ -968,11 +970,11 @@ story.add('Audio with Not Downloaded Attachment', () => {
story.add('Audio with Pending Attachment', () => { story.add('Audio with Pending Attachment', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: AUDIO_MP3, contentType: AUDIO_MP3,
fileName: 'incompetech-com-Agnus-Dei-X.mp3', fileName: 'incompetech-com-Agnus-Dei-X.mp3',
pending: true, pending: true,
}, }),
], ],
status: 'sent', status: 'sent',
}); });
@ -983,11 +985,11 @@ story.add('Audio with Pending Attachment', () => {
story.add('Other File Type', () => { story.add('Other File Type', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: stringToMIMEType('text/plain'), contentType: stringToMIMEType('text/plain'),
fileName: 'my-resume.txt', fileName: 'my-resume.txt',
url: 'my-resume.txt', url: 'my-resume.txt',
}, }),
], ],
status: 'sent', status: 'sent',
}); });
@ -998,11 +1000,11 @@ story.add('Other File Type', () => {
story.add('Other File Type with Caption', () => { story.add('Other File Type with Caption', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: stringToMIMEType('text/plain'), contentType: stringToMIMEType('text/plain'),
fileName: 'my-resume.txt', fileName: 'my-resume.txt',
url: 'my-resume.txt', url: 'my-resume.txt',
}, }),
], ],
status: 'sent', status: 'sent',
text: 'This is what I have done.', text: 'This is what I have done.',
@ -1014,12 +1016,12 @@ story.add('Other File Type with Caption', () => {
story.add('Other File Type with Long Filename', () => { story.add('Other File Type with Long Filename', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: stringToMIMEType('text/plain'), contentType: stringToMIMEType('text/plain'),
fileName: fileName:
'INSERT-APP-NAME_INSERT-APP-APPLE-ID_AppStore_AppsGamesWatch.psd.zip', 'INSERT-APP-NAME_INSERT-APP-APPLE-ID_AppStore_AppsGamesWatch.psd.zip',
url: 'a2/a2334324darewer4234', url: 'a2/a2334324darewer4234',
}, }),
], ],
status: 'sent', status: 'sent',
text: 'This is what I have done.', text: 'This is what I have done.',
@ -1031,13 +1033,13 @@ story.add('Other File Type with Long Filename', () => {
story.add('TapToView Image', () => { story.add('TapToView Image', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
width: 128, width: 128,
height: 128, height: 128,
}, }),
], ],
isTapToView: true, isTapToView: true,
status: 'sent', status: 'sent',
@ -1049,13 +1051,13 @@ story.add('TapToView Image', () => {
story.add('TapToView Video', () => { story.add('TapToView Video', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
fileName: 'pixabay-Soap-Bubble-7141.mp4', fileName: 'pixabay-Soap-Bubble-7141.mp4',
height: 128, height: 128,
url: '/fixtures/pixabay-Soap-Bubble-7141.mp4', url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
width: 128, width: 128,
}, }),
], ],
isTapToView: true, isTapToView: true,
status: 'sent', status: 'sent',
@ -1067,14 +1069,14 @@ story.add('TapToView Video', () => {
story.add('TapToView GIF', () => { story.add('TapToView GIF', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
flags: SignalService.AttachmentPointer.Flags.GIF, flags: SignalService.AttachmentPointer.Flags.GIF,
fileName: 'cat-gif.mp4', fileName: 'cat-gif.mp4',
url: '/fixtures/cat-gif.mp4', url: '/fixtures/cat-gif.mp4',
width: 400, width: 400,
height: 332, height: 332,
}, }),
], ],
isTapToView: true, isTapToView: true,
status: 'sent', status: 'sent',
@ -1086,13 +1088,13 @@ story.add('TapToView GIF', () => {
story.add('TapToView Expired', () => { story.add('TapToView Expired', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
width: 128, width: 128,
height: 128, height: 128,
}, }),
], ],
isTapToView: true, isTapToView: true,
isTapToViewExpired: true, isTapToViewExpired: true,
@ -1105,13 +1107,13 @@ story.add('TapToView Expired', () => {
story.add('TapToView Error', () => { story.add('TapToView Error', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
width: 128, width: 128,
height: 128, height: 128,
}, }),
], ],
isTapToView: true, isTapToView: true,
isTapToViewError: true, isTapToViewError: true,
@ -1124,13 +1126,13 @@ story.add('TapToView Error', () => {
story.add('Dangerous File Type', () => { story.add('Dangerous File Type', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
contentType: stringToMIMEType( contentType: stringToMIMEType(
'application/vnd.microsoft.portable-executable' 'application/vnd.microsoft.portable-executable'
), ),
fileName: 'terrible.exe', fileName: 'terrible.exe',
url: 'terrible.exe', url: 'terrible.exe',
}, }),
], ],
status: 'sent', status: 'sent',
}); });
@ -1174,13 +1176,13 @@ story.add('@Mentions', () => {
story.add('All the context menus', () => { story.add('All the context menus', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
{ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
width: 128, width: 128,
height: 128, height: 128,
}, }),
], ],
status: 'partial-sent', status: 'partial-sent',
canDeleteForEveryone: true, canDeleteForEveryone: true,
@ -1194,13 +1196,13 @@ story.add('Not approved, with link preview', () => {
previews: [ previews: [
{ {
domain: 'signal.org', domain: 'signal.org',
image: { image: fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
fileName: 'the-sax.png', fileName: 'the-sax.png',
height: 240, height: 240,
url: pngUrl, url: pngUrl,
width: 320, width: 320,
}, }),
isStickerPack: false, isStickerPack: false,
title: 'Signal', title: 'Signal',
description: description:

View file

@ -33,6 +33,7 @@ const createAttachment = (
), ),
fileName: text('attachment fileName', props.fileName || ''), fileName: text('attachment fileName', props.fileName || ''),
url: '', url: '',
size: 14243,
}); });
story.add('Text File', () => { story.add('Text File', () => {

View file

@ -32,6 +32,7 @@ const createAttachment = (
), ),
fileName: text('attachment fileName', props.fileName || ''), fileName: text('attachment fileName', props.fileName || ''),
url: text('attachment url', props.url || ''), url: text('attachment url', props.url || ''),
size: 24325,
}); });
const createProps = (overrideProps: Partial<Props> = {}): Props => ({ const createProps = (overrideProps: Partial<Props> = {}): Props => ({

View file

@ -27,8 +27,8 @@ import type {
AttachmentType, AttachmentType,
GroupV1InfoType, GroupV1InfoType,
GroupV2InfoType, GroupV2InfoType,
PreviewType,
} from '../textsecure/SendMessage'; } from '../textsecure/SendMessage';
import type { LinkPreviewType } from '../types/message/LinkPreviews';
import type { BodyRangesType } from '../types/Util'; import type { BodyRangesType } from '../types/Util';
import type { WhatIsThis } from '../window.d'; import type { WhatIsThis } from '../window.d';
@ -299,7 +299,7 @@ export class NormalMessageSendJobQueue extends JobQueue<NormalMessageSendJobData
quote, quote,
preview, preview,
sticker, sticker,
reaction: null, reaction: undefined,
deletedForEveryoneTimestamp, deletedForEveryoneTimestamp,
timestamp: messageTimestamp, timestamp: messageTimestamp,
expireTimer, expireTimer,
@ -464,7 +464,7 @@ async function getMessageSendData({
expireTimer: undefined | number; expireTimer: undefined | number;
mentions: undefined | BodyRangesType; mentions: undefined | BodyRangesType;
messageTimestamp: number; messageTimestamp: number;
preview: Array<PreviewType>; preview: Array<LinkPreviewType>;
profileKey: undefined | Uint8Array; profileKey: undefined | Uint8Array;
quote: WhatIsThis; quote: WhatIsThis;
sticker: WhatIsThis; sticker: WhatIsThis;

View file

@ -23,6 +23,7 @@ import { CapabilityError } from '../types/errors';
import type { import type {
GroupV1InfoType, GroupV1InfoType,
GroupV2InfoType, GroupV2InfoType,
StickerType,
} from '../textsecure/SendMessage'; } from '../textsecure/SendMessage';
import createTaskWithTimeout from '../textsecure/TaskWithTimeout'; import createTaskWithTimeout from '../textsecure/TaskWithTimeout';
import { CallbackResultType } from '../textsecure/Types.d'; import { CallbackResultType } from '../textsecure/Types.d';
@ -60,6 +61,7 @@ import { sendReadReceiptsFor } from '../util/sendReadReceiptsFor';
import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup'; import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup';
import { ReadStatus } from '../messages/MessageReadStatus'; import { ReadStatus } from '../messages/MessageReadStatus';
import { SendState, SendStatus } from '../messages/MessageSendState'; import { SendState, SendStatus } from '../messages/MessageSendState';
import type { LinkPreviewType } from '../types/message/LinkPreviews';
import * as durations from '../util/durations'; import * as durations from '../util/durations';
import { import {
concat, concat,
@ -3279,8 +3281,8 @@ export class ConversationModel extends window.Backbone
} }
const { key } = packData; const { key } = packData;
const { path, width, height } = stickerData; const { emoji, path, width, height } = stickerData;
const arrayBuffer = await readStickerData(path); const data = await readStickerData(path);
// We need this content type to be an image so we can display an `<img>` instead of a // We need this content type to be an image so we can display an `<img>` instead of a
// `<video>` or an error, but it's not critical that we get the full type correct. // `<video>` or an error, but it's not critical that we get the full type correct.
@ -3289,11 +3291,13 @@ export class ConversationModel extends window.Backbone
// the MIME type here, but it's okay if we have to use a possibly-incorrect // the MIME type here, but it's okay if we have to use a possibly-incorrect
// fallback. // fallback.
let contentType: MIMEType; let contentType: MIMEType;
const sniffedMimeType = sniffImageMimeType(arrayBuffer); const sniffedMimeType = sniffImageMimeType(data);
if (sniffedMimeType) { if (sniffedMimeType) {
contentType = sniffedMimeType; contentType = sniffedMimeType;
} else { } else {
log.warn('Unable to sniff sticker MIME type; falling back to WebP'); log.warn(
'sendStickerMessage: Unable to sniff sticker MIME type; falling back to WebP'
);
contentType = IMAGE_WEBP; contentType = IMAGE_WEBP;
} }
@ -3301,9 +3305,10 @@ export class ConversationModel extends window.Backbone
packId, packId,
stickerId, stickerId,
packKey: key, packKey: key,
emoji,
data: { data: {
size: arrayBuffer.byteLength, size: data.byteLength,
data: arrayBuffer, data,
contentType, contentType,
width, width,
height, height,
@ -3630,8 +3635,8 @@ export class ConversationModel extends window.Backbone
body: string | undefined, body: string | undefined,
attachments: Array<AttachmentType>, attachments: Array<AttachmentType>,
quote?: QuotedMessageType, quote?: QuotedMessageType,
preview?: WhatIsThis, preview?: Array<LinkPreviewType>,
sticker?: WhatIsThis, sticker?: StickerType,
mentions?: BodyRangesType, mentions?: BodyRangesType,
{ {
dontClearDraft, dontClearDraft,

View file

@ -45,7 +45,7 @@ import * as Errors from '../types/errors';
import * as EmbeddedContact from '../types/EmbeddedContact'; import * as EmbeddedContact from '../types/EmbeddedContact';
import { AttachmentType, isImage, isVideo } from '../types/Attachment'; import { AttachmentType, isImage, isVideo } from '../types/Attachment';
import * as Attachment from '../types/Attachment'; import * as Attachment from '../types/Attachment';
import { IMAGE_WEBP, stringToMIMEType } from '../types/MIME'; import { stringToMIMEType } from '../types/MIME';
import * as MIME from '../types/MIME'; import * as MIME from '../types/MIME';
import { ReadStatus } from '../messages/MessageReadStatus'; import { ReadStatus } from '../messages/MessageReadStatus';
import { import {
@ -117,7 +117,7 @@ import * as LinkPreview from '../types/LinkPreview';
import { SignalService as Proto } from '../protobuf'; import { SignalService as Proto } from '../protobuf';
import { normalMessageSendJobQueue } from '../jobs/normalMessageSendJobQueue'; import { normalMessageSendJobQueue } from '../jobs/normalMessageSendJobQueue';
import { notificationService } from '../services/notifications'; import { notificationService } from '../services/notifications';
import type { PreviewType as OutgoingPreviewType } from '../textsecure/SendMessage'; import type { LinkPreviewType } from '../types/message/LinkPreviews';
import * as log from '../logging/log'; import * as log from '../logging/log';
import * as Bytes from '../Bytes'; import * as Bytes from '../Bytes';
import { computeHash } from '../Crypto'; import { computeHash } from '../Crypto';
@ -188,7 +188,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
syncPromise?: Promise<CallbackResultType | void>; syncPromise?: Promise<CallbackResultType | void>;
cachedOutgoingPreviewData?: Array<OutgoingPreviewType>; cachedOutgoingPreviewData?: Array<LinkPreviewType>;
cachedOutgoingQuoteData?: WhatIsThis; cachedOutgoingQuoteData?: WhatIsThis;
@ -2012,14 +2012,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
if (status && (status === 'downloaded' || status === 'installed')) { if (status && (status === 'downloaded' || status === 'installed')) {
try { try {
const copiedSticker = await copyStickerToAttachments( data = await copyStickerToAttachments(packId, stickerId);
packId,
stickerId
);
data = {
...copiedSticker,
contentType: IMAGE_WEBP,
};
} catch (error) { } catch (error) {
log.error( log.error(
`Problem copying sticker (${packId}, ${stickerId}) to attachments:`, `Problem copying sticker (${packId}, ${stickerId}) to attachments:`,

View file

@ -0,0 +1,15 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { AttachmentType } from '../../types/Attachment';
import { IMAGE_JPEG } from '../../types/MIME';
export const fakeAttachment = (
overrides: Partial<AttachmentType> = {}
): AttachmentType => ({
contentType: IMAGE_JPEG,
width: 800,
height: 600,
size: 10304,
...overrides,
});

View file

@ -2,8 +2,9 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai'; import { assert } from 'chai';
import { IMAGE_JPEG, VIDEO_MP4 } from '../../types/MIME'; import { VIDEO_MP4 } from '../../types/MIME';
import { AttachmentType } from '../../types/Attachment';
import { fakeAttachment } from '../helpers/fakeAttachment';
import { shouldUseFullSizeLinkPreviewImage } from '../../linkPreviews/shouldUseFullSizeLinkPreviewImage'; import { shouldUseFullSizeLinkPreviewImage } from '../../linkPreviews/shouldUseFullSizeLinkPreviewImage';
@ -15,17 +16,6 @@ describe('shouldUseFullSizeLinkPreviewImage', () => {
isStickerPack: false, isStickerPack: false,
}; };
const fakeAttachment = (
overrides: Partial<AttachmentType> = {}
): AttachmentType => ({
contentType: IMAGE_JPEG,
fileName: 'foo.jpg',
url: '/tmp/foo.jpg',
width: 800,
height: 600,
...overrides,
});
it('returns false if there is no image', () => { it('returns false if there is no image', () => {
assert.isFalse( assert.isFalse(
shouldUseFullSizeLinkPreviewImage({ shouldUseFullSizeLinkPreviewImage({

View file

@ -10,6 +10,7 @@ import { reducer as rootReducer } from '../../../state/reducer';
import { IMAGE_JPEG } from '../../../types/MIME'; import { IMAGE_JPEG } from '../../../types/MIME';
import { AttachmentType } from '../../../types/Attachment'; import { AttachmentType } from '../../../types/Attachment';
import { fakeAttachment } from '../../helpers/fakeAttachment';
describe('both/state/ducks/composer', () => { describe('both/state/ducks/composer', () => {
const QUOTED_MESSAGE = { const QUOTED_MESSAGE = {
@ -40,7 +41,7 @@ describe('both/state/ducks/composer', () => {
const dispatch = sinon.spy(); const dispatch = sinon.spy();
const attachments: Array<AttachmentType> = [ const attachments: Array<AttachmentType> = [
{ contentType: IMAGE_JPEG, pending: false, url: '' }, { contentType: IMAGE_JPEG, pending: false, url: '', size: 2433 },
]; ];
replaceAttachments('123', attachments)( replaceAttachments('123', attachments)(
dispatch, dispatch,
@ -82,7 +83,7 @@ describe('both/state/ducks/composer', () => {
const { replaceAttachments } = actions; const { replaceAttachments } = actions;
const dispatch = sinon.spy(); const dispatch = sinon.spy();
const attachments: Array<AttachmentType> = [{ contentType: IMAGE_JPEG }]; const attachments = [fakeAttachment()];
replaceAttachments('123', attachments)( replaceAttachments('123', attachments)(
dispatch, dispatch,
getRootStateFunction('456'), getRootStateFunction('456'),

View file

@ -10,6 +10,7 @@ import {
Section, Section,
} from '../../../components/conversation/media-gallery/groupMediaItemsByDate'; } from '../../../components/conversation/media-gallery/groupMediaItemsByDate';
import { MediaItemType } from '../../../types/MediaItem'; import { MediaItemType } from '../../../types/MediaItem';
import { fakeAttachment } from '../../../test-both/helpers/fakeAttachment';
const testDate = ( const testDate = (
year: number, year: number,
@ -31,11 +32,11 @@ const toMediaItem = (date: Date): MediaItemType => ({
attachments: [], attachments: [],
sent_at: date.getTime(), sent_at: date.getTime(),
}, },
attachment: { attachment: fakeAttachment({
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
}, }),
}); });
describe('groupMediaItemsByDate', () => { describe('groupMediaItemsByDate', () => {
@ -74,11 +75,11 @@ describe('groupMediaItemsByDate', () => {
attachments: [], attachments: [],
sent_at: 1523534400000, sent_at: 1523534400000,
}, },
attachment: { attachment: fakeAttachment({
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
}, }),
}, },
{ {
objectURL: 'Thu, 12 Apr 2018 00:01:00 GMT', objectURL: 'Thu, 12 Apr 2018 00:01:00 GMT',
@ -91,11 +92,11 @@ describe('groupMediaItemsByDate', () => {
attachments: [], attachments: [],
sent_at: 1523491260000, sent_at: 1523491260000,
}, },
attachment: { attachment: fakeAttachment({
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
}, }),
}, },
], ],
}, },
@ -113,11 +114,11 @@ describe('groupMediaItemsByDate', () => {
attachments: [], attachments: [],
sent_at: 1523491140000, sent_at: 1523491140000,
}, },
attachment: { attachment: fakeAttachment({
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
}, }),
}, },
], ],
}, },
@ -135,11 +136,11 @@ describe('groupMediaItemsByDate', () => {
attachments: [], attachments: [],
sent_at: 1523232060000, sent_at: 1523232060000,
}, },
attachment: { attachment: fakeAttachment({
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
}, }),
}, },
], ],
}, },
@ -157,11 +158,11 @@ describe('groupMediaItemsByDate', () => {
attachments: [], attachments: [],
sent_at: 1523231940000, sent_at: 1523231940000,
}, },
attachment: { attachment: fakeAttachment({
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
}, }),
}, },
{ {
objectURL: 'Sun, 01 Apr 2018 00:01:00 GMT', objectURL: 'Sun, 01 Apr 2018 00:01:00 GMT',
@ -174,11 +175,11 @@ describe('groupMediaItemsByDate', () => {
attachments: [], attachments: [],
sent_at: 1522540860000, sent_at: 1522540860000,
}, },
attachment: { attachment: fakeAttachment({
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
}, }),
}, },
], ],
}, },
@ -198,11 +199,11 @@ describe('groupMediaItemsByDate', () => {
attachments: [], attachments: [],
sent_at: 1522540740000, sent_at: 1522540740000,
}, },
attachment: { attachment: fakeAttachment({
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
}, }),
}, },
{ {
objectURL: 'Thu, 01 Mar 2018 14:00:00 GMT', objectURL: 'Thu, 01 Mar 2018 14:00:00 GMT',
@ -215,11 +216,11 @@ describe('groupMediaItemsByDate', () => {
attachments: [], attachments: [],
sent_at: 1519912800000, sent_at: 1519912800000,
}, },
attachment: { attachment: fakeAttachment({
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
}, }),
}, },
], ],
}, },
@ -239,11 +240,11 @@ describe('groupMediaItemsByDate', () => {
attachments: [], attachments: [],
sent_at: 1298937540000, sent_at: 1298937540000,
}, },
attachment: { attachment: fakeAttachment({
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
}, }),
}, },
{ {
objectURL: 'Tue, 01 Feb 2011 10:00:00 GMT', objectURL: 'Tue, 01 Feb 2011 10:00:00 GMT',
@ -256,11 +257,11 @@ describe('groupMediaItemsByDate', () => {
attachments: [], attachments: [],
sent_at: 1296554400000, sent_at: 1296554400000,
}, },
attachment: { attachment: fakeAttachment({
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
}, }),
}, },
], ],
}, },

View file

@ -9,6 +9,8 @@ import { SignalService } from '../../protobuf';
import * as Bytes from '../../Bytes'; import * as Bytes from '../../Bytes';
import * as logger from '../../logging/log'; import * as logger from '../../logging/log';
import { fakeAttachment } from '../../test-both/helpers/fakeAttachment';
describe('Attachment', () => { describe('Attachment', () => {
describe('getUploadSizeLimitKb', () => { describe('getUploadSizeLimitKb', () => {
const { getUploadSizeLimitKb } = Attachment; const { getUploadSizeLimitKb } = Attachment;
@ -37,18 +39,18 @@ describe('Attachment', () => {
describe('getFileExtension', () => { describe('getFileExtension', () => {
it('should return file extension from content type', () => { it('should return file extension from content type', () => {
const input: Attachment.AttachmentType = { const input: Attachment.AttachmentType = fakeAttachment({
data: Bytes.fromString('foo'), data: Bytes.fromString('foo'),
contentType: MIME.IMAGE_GIF, contentType: MIME.IMAGE_GIF,
}; });
assert.strictEqual(Attachment.getFileExtension(input), 'gif'); assert.strictEqual(Attachment.getFileExtension(input), 'gif');
}); });
it('should return file extension for QuickTime videos', () => { it('should return file extension for QuickTime videos', () => {
const input: Attachment.AttachmentType = { const input: Attachment.AttachmentType = fakeAttachment({
data: Bytes.fromString('foo'), data: Bytes.fromString('foo'),
contentType: MIME.VIDEO_QUICKTIME, contentType: MIME.VIDEO_QUICKTIME,
}; });
assert.strictEqual(Attachment.getFileExtension(input), 'mov'); assert.strictEqual(Attachment.getFileExtension(input), 'mov');
}); });
}); });
@ -56,11 +58,11 @@ describe('Attachment', () => {
describe('getSuggestedFilename', () => { describe('getSuggestedFilename', () => {
context('for attachment with filename', () => { context('for attachment with filename', () => {
it('should return existing filename if present', () => { it('should return existing filename if present', () => {
const attachment: Attachment.AttachmentType = { const attachment: Attachment.AttachmentType = fakeAttachment({
fileName: 'funny-cat.mov', fileName: 'funny-cat.mov',
data: Bytes.fromString('foo'), data: Bytes.fromString('foo'),
contentType: MIME.VIDEO_QUICKTIME, contentType: MIME.VIDEO_QUICKTIME,
}; });
const actual = Attachment.getSuggestedFilename({ attachment }); const actual = Attachment.getSuggestedFilename({ attachment });
const expected = 'funny-cat.mov'; const expected = 'funny-cat.mov';
assert.strictEqual(actual, expected); assert.strictEqual(actual, expected);
@ -68,10 +70,10 @@ describe('Attachment', () => {
}); });
context('for attachment without filename', () => { context('for attachment without filename', () => {
it('should generate a filename based on timestamp', () => { it('should generate a filename based on timestamp', () => {
const attachment: Attachment.AttachmentType = { const attachment: Attachment.AttachmentType = fakeAttachment({
data: Bytes.fromString('foo'), data: Bytes.fromString('foo'),
contentType: MIME.VIDEO_QUICKTIME, contentType: MIME.VIDEO_QUICKTIME,
}; });
const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000); const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000);
const actual = Attachment.getSuggestedFilename({ const actual = Attachment.getSuggestedFilename({
attachment, attachment,
@ -83,10 +85,10 @@ describe('Attachment', () => {
}); });
context('for attachment with index', () => { context('for attachment with index', () => {
it('should generate a filename based on timestamp', () => { it('should generate a filename based on timestamp', () => {
const attachment: Attachment.AttachmentType = { const attachment: Attachment.AttachmentType = fakeAttachment({
data: Bytes.fromString('foo'), data: Bytes.fromString('foo'),
contentType: MIME.VIDEO_QUICKTIME, contentType: MIME.VIDEO_QUICKTIME,
}; });
const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000); const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000);
const actual = Attachment.getSuggestedFilename({ const actual = Attachment.getSuggestedFilename({
attachment, attachment,
@ -101,107 +103,107 @@ describe('Attachment', () => {
describe('isVisualMedia', () => { describe('isVisualMedia', () => {
it('should return true for images', () => { it('should return true for images', () => {
const attachment: Attachment.AttachmentType = { const attachment: Attachment.AttachmentType = fakeAttachment({
fileName: 'meme.gif', fileName: 'meme.gif',
data: Bytes.fromString('gif'), data: Bytes.fromString('gif'),
contentType: MIME.IMAGE_GIF, contentType: MIME.IMAGE_GIF,
}; });
assert.isTrue(Attachment.isVisualMedia(attachment)); assert.isTrue(Attachment.isVisualMedia(attachment));
}); });
it('should return true for videos', () => { it('should return true for videos', () => {
const attachment: Attachment.AttachmentType = { const attachment: Attachment.AttachmentType = fakeAttachment({
fileName: 'meme.mp4', fileName: 'meme.mp4',
data: Bytes.fromString('mp4'), data: Bytes.fromString('mp4'),
contentType: MIME.VIDEO_MP4, contentType: MIME.VIDEO_MP4,
}; });
assert.isTrue(Attachment.isVisualMedia(attachment)); assert.isTrue(Attachment.isVisualMedia(attachment));
}); });
it('should return false for voice message attachment', () => { it('should return false for voice message attachment', () => {
const attachment: Attachment.AttachmentType = { const attachment: Attachment.AttachmentType = fakeAttachment({
fileName: 'Voice Message.aac', fileName: 'Voice Message.aac',
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE, flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
data: Bytes.fromString('voice message'), data: Bytes.fromString('voice message'),
contentType: MIME.AUDIO_AAC, contentType: MIME.AUDIO_AAC,
}; });
assert.isFalse(Attachment.isVisualMedia(attachment)); assert.isFalse(Attachment.isVisualMedia(attachment));
}); });
it('should return false for other attachments', () => { it('should return false for other attachments', () => {
const attachment: Attachment.AttachmentType = { const attachment: Attachment.AttachmentType = fakeAttachment({
fileName: 'foo.json', fileName: 'foo.json',
data: Bytes.fromString('{"foo": "bar"}'), data: Bytes.fromString('{"foo": "bar"}'),
contentType: MIME.APPLICATION_JSON, contentType: MIME.APPLICATION_JSON,
}; });
assert.isFalse(Attachment.isVisualMedia(attachment)); assert.isFalse(Attachment.isVisualMedia(attachment));
}); });
}); });
describe('isFile', () => { describe('isFile', () => {
it('should return true for JSON', () => { it('should return true for JSON', () => {
const attachment: Attachment.AttachmentType = { const attachment: Attachment.AttachmentType = fakeAttachment({
fileName: 'foo.json', fileName: 'foo.json',
data: Bytes.fromString('{"foo": "bar"}'), data: Bytes.fromString('{"foo": "bar"}'),
contentType: MIME.APPLICATION_JSON, contentType: MIME.APPLICATION_JSON,
}; });
assert.isTrue(Attachment.isFile(attachment)); assert.isTrue(Attachment.isFile(attachment));
}); });
it('should return false for images', () => { it('should return false for images', () => {
const attachment: Attachment.AttachmentType = { const attachment: Attachment.AttachmentType = fakeAttachment({
fileName: 'meme.gif', fileName: 'meme.gif',
data: Bytes.fromString('gif'), data: Bytes.fromString('gif'),
contentType: MIME.IMAGE_GIF, contentType: MIME.IMAGE_GIF,
}; });
assert.isFalse(Attachment.isFile(attachment)); assert.isFalse(Attachment.isFile(attachment));
}); });
it('should return false for videos', () => { it('should return false for videos', () => {
const attachment: Attachment.AttachmentType = { const attachment: Attachment.AttachmentType = fakeAttachment({
fileName: 'meme.mp4', fileName: 'meme.mp4',
data: Bytes.fromString('mp4'), data: Bytes.fromString('mp4'),
contentType: MIME.VIDEO_MP4, contentType: MIME.VIDEO_MP4,
}; });
assert.isFalse(Attachment.isFile(attachment)); assert.isFalse(Attachment.isFile(attachment));
}); });
it('should return false for voice message attachment', () => { it('should return false for voice message attachment', () => {
const attachment: Attachment.AttachmentType = { const attachment: Attachment.AttachmentType = fakeAttachment({
fileName: 'Voice Message.aac', fileName: 'Voice Message.aac',
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE, flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
data: Bytes.fromString('voice message'), data: Bytes.fromString('voice message'),
contentType: MIME.AUDIO_AAC, contentType: MIME.AUDIO_AAC,
}; });
assert.isFalse(Attachment.isFile(attachment)); assert.isFalse(Attachment.isFile(attachment));
}); });
}); });
describe('isVoiceMessage', () => { describe('isVoiceMessage', () => {
it('should return true for voice message attachment', () => { it('should return true for voice message attachment', () => {
const attachment: Attachment.AttachmentType = { const attachment: Attachment.AttachmentType = fakeAttachment({
fileName: 'Voice Message.aac', fileName: 'Voice Message.aac',
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE, flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
data: Bytes.fromString('voice message'), data: Bytes.fromString('voice message'),
contentType: MIME.AUDIO_AAC, contentType: MIME.AUDIO_AAC,
}; });
assert.isTrue(Attachment.isVoiceMessage(attachment)); assert.isTrue(Attachment.isVoiceMessage(attachment));
}); });
it('should return true for legacy Android voice message attachment', () => { it('should return true for legacy Android voice message attachment', () => {
const attachment: Attachment.AttachmentType = { const attachment: Attachment.AttachmentType = fakeAttachment({
data: Bytes.fromString('voice message'), data: Bytes.fromString('voice message'),
contentType: MIME.AUDIO_MP3, contentType: MIME.AUDIO_MP3,
}; });
assert.isTrue(Attachment.isVoiceMessage(attachment)); assert.isTrue(Attachment.isVoiceMessage(attachment));
}); });
it('should return false for other attachments', () => { it('should return false for other attachments', () => {
const attachment: Attachment.AttachmentType = { const attachment: Attachment.AttachmentType = fakeAttachment({
fileName: 'foo.gif', fileName: 'foo.gif',
data: Bytes.fromString('foo'), data: Bytes.fromString('foo'),
contentType: MIME.IMAGE_GIF, contentType: MIME.IMAGE_GIF,
}; });
assert.isFalse(Attachment.isVoiceMessage(attachment)); assert.isFalse(Attachment.isVoiceMessage(attachment));
}); });
}); });

View file

@ -15,6 +15,7 @@ import {
getName, getName,
parseAndWriteAvatar, parseAndWriteAvatar,
} from '../../types/EmbeddedContact'; } from '../../types/EmbeddedContact';
import { fakeAttachment } from '../../test-both/helpers/fakeAttachment';
describe('Contact', () => { describe('Contact', () => {
const NUMBER = '+12025550099'; const NUMBER = '+12025550099';
@ -127,10 +128,10 @@ describe('Contact', () => {
organization: 'Somewhere, Inc.', organization: 'Somewhere, Inc.',
avatar: { avatar: {
isProfile: true, isProfile: true,
avatar: { avatar: fakeAttachment({
error: true, error: true,
contentType: IMAGE_GIF, contentType: IMAGE_GIF,
}, }),
}, },
}; };
const expected = { const expected = {
@ -164,10 +165,10 @@ describe('Contact', () => {
organization: 'Somewhere, Inc.', organization: 'Somewhere, Inc.',
avatar: { avatar: {
isProfile: true, isProfile: true,
avatar: { avatar: fakeAttachment({
pending: true, pending: true,
contentType: IMAGE_GIF, contentType: IMAGE_GIF,
}, }),
}, },
}; };
const expected = { const expected = {
@ -179,11 +180,11 @@ describe('Contact', () => {
organization: 'Somewhere, Inc.', organization: 'Somewhere, Inc.',
avatar: { avatar: {
isProfile: true, isProfile: true,
avatar: { avatar: fakeAttachment({
pending: true, pending: true,
path: undefined, path: undefined,
contentType: IMAGE_GIF, contentType: IMAGE_GIF,
}, }),
}, },
firstNumber, firstNumber,
isNumberOnSignal, isNumberOnSignal,
@ -208,10 +209,10 @@ describe('Contact', () => {
organization: 'Somewhere, Inc.', organization: 'Somewhere, Inc.',
avatar: { avatar: {
isProfile: true, isProfile: true,
avatar: { avatar: fakeAttachment({
path: 'somewhere', path: 'somewhere',
contentType: IMAGE_GIF, contentType: IMAGE_GIF,
}, }),
}, },
}; };
const expected = { const expected = {
@ -223,10 +224,10 @@ describe('Contact', () => {
organization: 'Somewhere, Inc.', organization: 'Somewhere, Inc.',
avatar: { avatar: {
isProfile: true, isProfile: true,
avatar: { avatar: fakeAttachment({
path: 'absolute:somewhere', path: 'absolute:somewhere',
contentType: IMAGE_GIF, contentType: IMAGE_GIF,
}, }),
}, },
firstNumber, firstNumber,
isNumberOnSignal: true, isNumberOnSignal: true,
@ -363,10 +364,10 @@ describe('Contact', () => {
it('writes avatar to disk', async () => { it('writes avatar to disk', async () => {
const upgradeAttachment = async () => { const upgradeAttachment = async () => {
return { return fakeAttachment({
path: 'abc/abcdefg', path: 'abc/abcdefg',
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
}; });
}; };
const upgradeVersion = parseAndWriteAvatar(upgradeAttachment); const upgradeVersion = parseAndWriteAvatar(upgradeAttachment);
@ -430,10 +431,10 @@ describe('Contact', () => {
avatar: { avatar: {
otherKey: 'otherValue', otherKey: 'otherValue',
isProfile: false, isProfile: false,
avatar: { avatar: fakeAttachment({
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
path: 'abc/abcdefg', path: 'abc/abcdefg',
}, }),
}, },
}; };

View file

@ -5,7 +5,6 @@
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
/* eslint-disable more/no-then */ /* eslint-disable more/no-then */
/* eslint-disable no-bitwise */ /* eslint-disable no-bitwise */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable max-classes-per-file */ /* eslint-disable max-classes-per-file */
import { Dictionary } from 'lodash'; import { Dictionary } from 'lodash';
@ -23,6 +22,9 @@ import { Address } from '../types/Address';
import { QualifiedAddress } from '../types/QualifiedAddress'; import { QualifiedAddress } from '../types/QualifiedAddress';
import { UUID } from '../types/UUID'; import { UUID } from '../types/UUID';
import { SenderKeys } from '../LibSignalStores'; import { SenderKeys } from '../LibSignalStores';
import type { LinkPreviewType } from '../types/message/LinkPreviews';
import { MIMETypeToString } from '../types/MIME';
import * as Attachment from '../types/Attachment';
import { import {
ChallengeType, ChallengeType,
GroupCredentialsType, GroupCredentialsType,
@ -77,12 +79,6 @@ export type SendOptionsType = {
online?: boolean; online?: boolean;
}; };
export type PreviewType = {
url: string;
title: string;
image?: AttachmentType;
};
type QuoteAttachmentType = { type QuoteAttachmentType = {
thumbnail?: AttachmentType; thumbnail?: AttachmentType;
attachmentPointer?: Proto.IAttachmentPointer; attachmentPointer?: Proto.IAttachmentPointer;
@ -103,22 +99,65 @@ type GroupCallUpdateType = {
eraId: string; eraId: string;
}; };
export type StickerType = {
packId: string;
stickerId: number;
packKey: string;
data: Readonly<AttachmentType>;
emoji?: string;
attachmentPointer?: Proto.IAttachmentPointer;
};
export type QuoteType = {
id?: number;
authorUuid?: string;
text?: string;
attachments?: Array<AttachmentType>;
bodyRanges?: BodyRangesType;
};
export type ReactionType = {
emoji?: string;
remove?: boolean;
targetAuthorUuid?: string;
targetTimestamp?: number;
};
export type AttachmentType = { export type AttachmentType = {
size: number; size: number;
data: Uint8Array; data: Uint8Array;
contentType: string; contentType: string;
fileName: string; fileName?: string;
flags: number; flags?: number;
width: number; width?: number;
height: number; height?: number;
caption: string; caption?: string;
attachmentPointer?: Proto.IAttachmentPointer; attachmentPointer?: Proto.IAttachmentPointer;
blurHash?: string; blurHash?: string;
}; };
function makeAttachmentSendReady(
attachment: Attachment.AttachmentType
): AttachmentType | undefined {
const { data } = attachment;
if (!data) {
throw new Error(
'makeAttachmentSendReady: Missing data, returning undefined'
);
}
return {
...attachment,
contentType: MIMETypeToString(attachment.contentType),
data,
};
}
export type MessageOptionsType = { export type MessageOptionsType = {
attachments?: ReadonlyArray<AttachmentType> | null; attachments?: ReadonlyArray<AttachmentType> | null;
body?: string; body?: string;
@ -130,12 +169,12 @@ export type MessageOptionsType = {
}; };
groupV2?: GroupV2InfoType; groupV2?: GroupV2InfoType;
needsSync?: boolean; needsSync?: boolean;
preview?: ReadonlyArray<PreviewType> | null; preview?: ReadonlyArray<LinkPreviewType>;
profileKey?: Uint8Array; profileKey?: Uint8Array;
quote?: any; quote?: QuoteType;
recipients: ReadonlyArray<string>; recipients: ReadonlyArray<string>;
sticker?: any; sticker?: StickerType;
reaction?: any; reaction?: ReactionType;
deletedForEveryoneTimestamp?: number; deletedForEveryoneTimestamp?: number;
timestamp: number; timestamp: number;
mentions?: BodyRangesType; mentions?: BodyRangesType;
@ -147,11 +186,11 @@ export type GroupSendOptionsType = {
groupV2?: GroupV2InfoType; groupV2?: GroupV2InfoType;
groupV1?: GroupV1InfoType; groupV1?: GroupV1InfoType;
messageText?: string; messageText?: string;
preview?: any; preview?: ReadonlyArray<LinkPreviewType>;
profileKey?: Uint8Array; profileKey?: Uint8Array;
quote?: any; quote?: QuoteType;
reaction?: any; reaction?: ReactionType;
sticker?: any; sticker?: StickerType;
deletedForEveryoneTimestamp?: number; deletedForEveryoneTimestamp?: number;
timestamp: number; timestamp: number;
mentions?: BodyRangesType; mentions?: BodyRangesType;
@ -159,7 +198,7 @@ export type GroupSendOptionsType = {
}; };
class Message { class Message {
attachments: ReadonlyArray<any>; attachments: ReadonlyArray<AttachmentType>;
body?: string; body?: string;
@ -176,28 +215,17 @@ class Message {
needsSync?: boolean; needsSync?: boolean;
preview: any; preview?: ReadonlyArray<LinkPreviewType>;
profileKey?: Uint8Array; profileKey?: Uint8Array;
quote?: { quote?: QuoteType;
id?: number;
authorUuid?: string;
text?: string;
attachments?: Array<AttachmentType>;
bodyRanges?: BodyRangesType;
};
recipients: ReadonlyArray<string>; recipients: ReadonlyArray<string>;
sticker?: any; sticker?: StickerType;
reaction?: { reaction?: ReactionType;
emoji?: string;
remove?: boolean;
targetAuthorUuid?: string;
targetTimestamp?: number;
};
timestamp: number; timestamp: number;
@ -325,6 +353,7 @@ class Message {
proto.sticker.packId = Bytes.fromHex(this.sticker.packId); proto.sticker.packId = Bytes.fromHex(this.sticker.packId);
proto.sticker.packKey = Bytes.fromBase64(this.sticker.packKey); proto.sticker.packKey = Bytes.fromBase64(this.sticker.packKey);
proto.sticker.stickerId = this.sticker.stickerId; proto.sticker.stickerId = this.sticker.stickerId;
proto.sticker.emoji = this.sticker.emoji;
if (this.sticker.attachmentPointer) { if (this.sticker.attachmentPointer) {
proto.sticker.data = this.sticker.attachmentPointer; proto.sticker.data = this.sticker.attachmentPointer;
@ -345,7 +374,9 @@ class Message {
item.url = preview.url; item.url = preview.url;
item.description = preview.description || null; item.description = preview.description || null;
item.date = preview.date || null; item.date = preview.date || null;
item.image = preview.image || null; if (preview.attachmentPointer) {
item.image = preview.attachmentPointer;
}
return item; return item;
}); });
} }
@ -364,7 +395,9 @@ class Message {
const quotedAttachment = new QuotedAttachment(); const quotedAttachment = new QuotedAttachment();
quotedAttachment.contentType = attachment.contentType; quotedAttachment.contentType = attachment.contentType;
quotedAttachment.fileName = attachment.fileName; if (attachment.fileName) {
quotedAttachment.fileName = attachment.fileName;
}
if (attachment.attachmentPointer) { if (attachment.attachmentPointer) {
quotedAttachment.thumbnail = attachment.attachmentPointer; quotedAttachment.thumbnail = attachment.attachmentPointer;
} }
@ -442,10 +475,10 @@ export default class MessageSender {
this.pendingMessages = {}; this.pendingMessages = {};
} }
async queueJobForIdentifier( async queueJobForIdentifier<T>(
identifier: string, identifier: string,
runJob: () => Promise<any> runJob: () => Promise<T>
): Promise<void> { ): Promise<T> {
const { id } = await window.ConversationController.getOrCreateAndWait( const { id } = await window.ConversationController.getOrCreateAndWait(
identifier, identifier,
'private' 'private'
@ -546,8 +579,10 @@ export default class MessageSender {
} }
async uploadAttachments(message: Message): Promise<void> { async uploadAttachments(message: Message): Promise<void> {
return Promise.all( await Promise.all(
message.attachments.map(this.makeAttachmentPointer.bind(this)) message.attachments.map(attachment =>
this.makeAttachmentPointer(attachment)
)
) )
.then(attachmentPointers => { .then(attachmentPointers => {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
@ -565,12 +600,20 @@ export default class MessageSender {
async uploadLinkPreviews(message: Message): Promise<void> { async uploadLinkPreviews(message: Message): Promise<void> {
try { try {
const preview = await Promise.all( const preview = await Promise.all(
(message.preview || []).map(async (item: PreviewType) => ({ (message.preview || []).map(async (item: Readonly<LinkPreviewType>) => {
...item, if (!item.image) {
image: item.image return item;
? await this.makeAttachmentPointer(item.image) }
: undefined, const attachment = makeAttachmentSendReady(item.image);
})) if (!attachment) {
return item;
}
return {
...item,
attachmentPointer: await this.makeAttachmentPointer(attachment),
};
})
); );
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
message.preview = preview; message.preview = preview;
@ -968,10 +1011,10 @@ export default class MessageSender {
identifier: string; identifier: string;
messageText: string | undefined; messageText: string | undefined;
attachments: ReadonlyArray<AttachmentType> | undefined; attachments: ReadonlyArray<AttachmentType> | undefined;
quote: unknown; quote?: QuoteType;
preview: ReadonlyArray<PreviewType> | undefined; preview?: ReadonlyArray<LinkPreviewType> | undefined;
sticker: unknown; sticker?: StickerType;
reaction: unknown; reaction?: ReactionType;
deletedForEveryoneTimestamp: number | undefined; deletedForEveryoneTimestamp: number | undefined;
timestamp: number; timestamp: number;
expireTimer: number | undefined; expireTimer: number | undefined;
@ -2080,7 +2123,7 @@ export default class MessageSender {
profileKeyVersion?: string; profileKeyVersion?: string;
profileKeyCredentialRequest?: string; profileKeyCredentialRequest?: string;
}> = {} }> = {}
): Promise<any> { ): Promise<ReturnType<WebAPIType['getProfile']>> {
const { accessKey } = options; const { accessKey } = options;
if (accessKey) { if (accessKey) {
@ -2100,15 +2143,20 @@ export default class MessageSender {
return this.server.getUuidsForE164s(numbers); return this.server.getUuidsForE164s(numbers);
} }
async getAvatar(path: string): Promise<any> { async getAvatar(path: string): Promise<ReturnType<WebAPIType['getAvatar']>> {
return this.server.getAvatar(path); return this.server.getAvatar(path);
} }
async getSticker(packId: string, stickerId: number): Promise<any> { async getSticker(
packId: string,
stickerId: number
): Promise<ReturnType<WebAPIType['getSticker']>> {
return this.server.getSticker(packId, stickerId); return this.server.getSticker(packId, stickerId);
} }
async getStickerPackManifest(packId: string): Promise<any> { async getStickerPackManifest(
packId: string
): Promise<ReturnType<WebAPIType['getStickerPackManifest']>> {
return this.server.getStickerPackManifest(packId); return this.server.getStickerPackManifest(packId);
} }
@ -2184,7 +2232,7 @@ export default class MessageSender {
async makeProxiedRequest( async makeProxiedRequest(
url: string, url: string,
options?: Readonly<ProxiedRequestOptionsType> options?: Readonly<ProxiedRequestOptionsType>
): Promise<any> { ): Promise<ReturnType<WebAPIType['makeProxiedRequest']>> {
return this.server.makeProxiedRequest(url, options); return this.server.makeProxiedRequest(url, options);
} }

View file

@ -678,7 +678,8 @@ export type ProfileType = Readonly<{
username?: string; username?: string;
uuid?: string; uuid?: string;
credential?: string; credential?: string;
capabilities?: unknown; capabilities?: CapabilitiesType;
paymentAddress?: string;
}>; }>;
export type GetIceServersResultType = Readonly<{ export type GetIceServersResultType = Readonly<{

View file

@ -52,6 +52,7 @@ export async function downloadAttachment(
return { return {
...omit(attachment, 'digest', 'key'), ...omit(attachment, 'digest', 'key'),
size,
contentType: contentType contentType: contentType
? MIME.stringToMIMEType(contentType) ? MIME.stringToMIMEType(contentType)
: MIME.APPLICATION_OCTET_STREAM, : MIME.APPLICATION_OCTET_STREAM,

View file

@ -42,7 +42,7 @@ export type AttachmentType = {
isVoiceMessage?: boolean; isVoiceMessage?: boolean;
/** For messages not already on disk, this will be a data url */ /** For messages not already on disk, this will be a data url */
url?: string; url?: string;
size?: number; size: number;
fileSize?: string; fileSize?: string;
pending?: boolean; pending?: boolean;
width?: number; width?: number;
@ -95,6 +95,7 @@ export type InMemoryAttachmentDraftType =
fileName: string; fileName: string;
path: string; path: string;
pending: true; pending: true;
size: number;
}; };
export type AttachmentDraftType = export type AttachmentDraftType =
@ -109,6 +110,7 @@ export type AttachmentDraftType =
fileName: string; fileName: string;
path: string; path: string;
pending: true; pending: true;
size: number;
}; };
export type ThumbnailType = { export type ThumbnailType = {
@ -621,7 +623,9 @@ export function isImage(attachments?: ReadonlyArray<AttachmentType>): boolean {
); );
} }
export function isImageAttachment(attachment?: AttachmentType): boolean { export function isImageAttachment(
attachment?: Pick<AttachmentType, 'contentType'>
): boolean {
return Boolean( return Boolean(
attachment && attachment &&
attachment.contentType && attachment.contentType &&
@ -630,8 +634,8 @@ export function isImageAttachment(attachment?: AttachmentType): boolean {
} }
export function canBeTranscoded( export function canBeTranscoded(
attachment?: AttachmentType attachment?: Pick<AttachmentType, 'contentType'>
): attachment is AttachmentType { ): boolean {
return Boolean( return Boolean(
attachment && attachment &&
isImageAttachment(attachment) && isImageAttachment(attachment) &&

View file

@ -6,6 +6,9 @@ export type MIMEType = string & { _mimeTypeBrand: never };
export const stringToMIMEType = (value: string): MIMEType => { export const stringToMIMEType = (value: string): MIMEType => {
return value as MIMEType; return value as MIMEType;
}; };
export const MIMETypeToString = (value: MIMEType): string => {
return value as string;
};
export const APPLICATION_OCTET_STREAM = stringToMIMEType( export const APPLICATION_OCTET_STREAM = stringToMIMEType(
'application/octet-stream' 'application/octet-stream'

View file

@ -11,6 +11,9 @@ import { makeLookup } from '../util/makeLookup';
import { maybeParseUrl } from '../util/url'; import { maybeParseUrl } from '../util/url';
import * as Bytes from '../Bytes'; import * as Bytes from '../Bytes';
import { deriveStickerPackKey, decryptAttachment } from '../Crypto'; import { deriveStickerPackKey, decryptAttachment } from '../Crypto';
import { IMAGE_WEBP, MIMEType } from './MIME';
import { sniffImageMimeType } from '../util/sniffImageMimeType';
import type { AttachmentType } from './Attachment';
import type { import type {
StickerType, StickerType,
StickerPackType, StickerPackType,
@ -749,21 +752,41 @@ export function getSticker(
export async function copyStickerToAttachments( export async function copyStickerToAttachments(
packId: string, packId: string,
stickerId: number stickerId: number
): Promise<StickerType | undefined> { ): Promise<AttachmentType> {
const sticker = getSticker(packId, stickerId); const sticker = getSticker(packId, stickerId);
if (!sticker) { if (!sticker) {
return undefined; throw new Error(
`copyStickerToAttachments: Failed to find sticker ${packId}/${stickerId}`
);
} }
const { path } = sticker; const { path: stickerPath } = sticker;
const absolutePath = window.Signal.Migrations.getAbsoluteStickerPath(path); const absolutePath = window.Signal.Migrations.getAbsoluteStickerPath(
const newPath = await window.Signal.Migrations.copyIntoAttachmentsDirectory( stickerPath
absolutePath
); );
const {
path,
size,
} = await window.Signal.Migrations.copyIntoAttachmentsDirectory(absolutePath);
const data = window.Signal.Migrations.loadAttachmentData(path);
let contentType: MIMEType;
const sniffedMimeType = sniffImageMimeType(data);
if (sniffedMimeType) {
contentType = sniffedMimeType;
} else {
log.warn(
'copyStickerToAttachments: Unable to sniff sticker MIME type; falling back to WebP'
);
contentType = IMAGE_WEBP;
}
return { return {
...sticker, ...sticker,
path: newPath, contentType,
path,
size,
}; };
} }

View file

@ -9,6 +9,6 @@ export type LinkPreviewType = {
domain: string; domain: string;
url: string; url: string;
isStickerPack: boolean; isStickerPack: boolean;
image?: AttachmentType; image?: Readonly<AttachmentType>;
date?: number; date?: number;
}; };

View file

@ -114,19 +114,21 @@ export async function getProfile(
}); });
} }
const identityKey = Bytes.fromBase64(profile.identityKey); if (profile.identityKey) {
const changed = await window.textsecure.storage.protocol.saveIdentity( const identityKey = Bytes.fromBase64(profile.identityKey);
new Address(targetUuid, 1), const changed = await window.textsecure.storage.protocol.saveIdentity(
identityKey, new Address(targetUuid, 1),
false identityKey,
); false
if (changed) {
// save identity will close all sessions except for .1, so we
// must close that one manually.
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
await window.textsecure.storage.protocol.archiveSession(
new QualifiedAddress(ourUuid, new Address(targetUuid, 1))
); );
if (changed) {
// save identity will close all sessions except for .1, so we
// must close that one manually.
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
await window.textsecure.storage.protocol.archiveSession(
new QualifiedAddress(ourUuid, new Address(targetUuid, 1))
);
}
} }
const accessKey = c.get('accessKey'); const accessKey = c.get('accessKey');
@ -238,15 +240,22 @@ export async function getProfile(
} }
} }
try { if (profile.name) {
await c.setEncryptedProfileName(profile.name); try {
} catch (error) { await c.setEncryptedProfileName(profile.name);
log.warn( } catch (error) {
'getProfile decryption failure:', log.warn(
c.idForLogging(), 'getProfile decryption failure:',
error && error.stack ? error.stack : error c.idForLogging(),
); error && error.stack ? error.stack : error
await c.set({ );
await c.set({
profileName: undefined,
profileFamilyName: undefined,
});
}
} else {
c.set({
profileName: undefined, profileName: undefined,
profileFamilyName: undefined, profileFamilyName: undefined,
}); });

View file

@ -25,6 +25,7 @@ export function getPendingAttachment(file: File): AttachmentType | undefined {
return { return {
contentType: fileType, contentType: fileType,
fileName, fileName,
size: file.size,
path: file.name, path: file.name,
pending: true, pending: true,
}; };

View file

@ -20,6 +20,7 @@ import {
MIMEType, MIMEType,
stringToMIMEType, stringToMIMEType,
} from '../types/MIME'; } from '../types/MIME';
import { sniffImageMimeType } from '../util/sniffImageMimeType';
import { ConversationModel } from '../models/conversations'; import { ConversationModel } from '../models/conversations';
import { import {
GroupV2PendingMemberType, GroupV2PendingMemberType,
@ -3499,6 +3500,17 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
return null; return null;
} }
let contentType: MIMEType;
const sniffedMimeType = sniffImageMimeType(data);
if (sniffedMimeType) {
contentType = sniffedMimeType;
} else {
log.warn(
'getStickerPackPreview: Unable to sniff sticker MIME type; falling back to WebP'
);
contentType = IMAGE_WEBP;
}
return { return {
date: null, date: null,
description: null, description: null,
@ -3506,7 +3518,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
...sticker, ...sticker,
data, data,
size: data.byteLength, size: data.byteLength,
contentType: IMAGE_WEBP, contentType,
}, },
title, title,
url, url,

4
ts/window.d.ts vendored
View file

@ -329,7 +329,9 @@ declare global {
width: number; width: number;
height: number; height: number;
}; };
copyIntoAttachmentsDirectory: (path: string) => Promise<string>; copyIntoAttachmentsDirectory: (
path: string
) => Promise<{ path: string; size: number }>;
upgradeMessageSchema: (attributes: unknown) => WhatIsThis; upgradeMessageSchema: (attributes: unknown) => WhatIsThis;
processNewAttachment: ( processNewAttachment: (
attachment: DownloadedAttachmentType attachment: DownloadedAttachmentType