Allow link-only stories, download previews
This commit is contained in:
parent
5f109d76da
commit
8f62442822
7 changed files with 155 additions and 194 deletions
|
@ -2530,12 +2530,19 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
|
|
||||||
const urls = LinkPreview.findLinks(dataMessage.body || '');
|
const urls = LinkPreview.findLinks(dataMessage.body || '');
|
||||||
const incomingPreview = dataMessage.preview || [];
|
const incomingPreview = dataMessage.preview || [];
|
||||||
const preview = incomingPreview.filter(
|
const preview = incomingPreview.filter((item: LinkPreviewType) => {
|
||||||
(item: LinkPreviewType) =>
|
if (!item.image && !item.title) {
|
||||||
(item.image || item.title) &&
|
return false;
|
||||||
urls.includes(item.url) &&
|
}
|
||||||
LinkPreview.shouldPreviewHref(item.url)
|
// Story link previews don't have to correspond to links in the
|
||||||
);
|
// message body.
|
||||||
|
if (isStory(message.attributes)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
urls.includes(item.url) && LinkPreview.shouldPreviewHref(item.url)
|
||||||
|
);
|
||||||
|
});
|
||||||
if (preview.length < incomingPreview.length) {
|
if (preview.length < incomingPreview.length) {
|
||||||
log.info(
|
log.info(
|
||||||
`${message.idForLogging()}: Eliminated ${
|
`${message.idForLogging()}: Eliminated ${
|
||||||
|
|
|
@ -7,7 +7,11 @@ import type { StoryDataType } from '../state/ducks/stories';
|
||||||
import * as durations from '../util/durations';
|
import * as durations from '../util/durations';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import dataInterface from '../sql/Client';
|
import dataInterface from '../sql/Client';
|
||||||
import { getAttachmentsForMessage } from '../state/selectors/message';
|
import {
|
||||||
|
getAttachmentsForMessage,
|
||||||
|
getPropsForAttachment,
|
||||||
|
} from '../state/selectors/message';
|
||||||
|
import type { LinkPreviewType } from '../types/message/LinkPreviews';
|
||||||
import { isNotNil } from '../util/isNotNil';
|
import { isNotNil } from '../util/isNotNil';
|
||||||
import { strictAssert } from '../util/assert';
|
import { strictAssert } from '../util/assert';
|
||||||
import { dropNull } from '../util/dropNull';
|
import { dropNull } from '../util/dropNull';
|
||||||
|
@ -66,11 +70,38 @@ export function getStoryDataFromMessageAttributes(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [attachment] =
|
let [attachment] =
|
||||||
unresolvedAttachment && unresolvedAttachment.path
|
unresolvedAttachment && unresolvedAttachment.path
|
||||||
? getAttachmentsForMessage(message)
|
? getAttachmentsForMessage(message)
|
||||||
: [unresolvedAttachment];
|
: [unresolvedAttachment];
|
||||||
|
|
||||||
|
let preview: LinkPreviewType | undefined;
|
||||||
|
if (message.preview?.length) {
|
||||||
|
strictAssert(
|
||||||
|
message.preview.length === 1,
|
||||||
|
'getStoryDataFromMessageAttributes: story can have only one preview'
|
||||||
|
);
|
||||||
|
[preview] = message.preview;
|
||||||
|
|
||||||
|
strictAssert(
|
||||||
|
attachment?.textAttachment,
|
||||||
|
'getStoryDataFromMessageAttributes: story must have a ' +
|
||||||
|
'textAttachment with preview'
|
||||||
|
);
|
||||||
|
attachment = {
|
||||||
|
...attachment,
|
||||||
|
textAttachment: {
|
||||||
|
...attachment.textAttachment,
|
||||||
|
preview: {
|
||||||
|
...preview,
|
||||||
|
image: preview.image && getPropsForAttachment(preview.image),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (attachment) {
|
||||||
|
attachment = getPropsForAttachment(attachment);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
attachment,
|
attachment,
|
||||||
messageId: message.id,
|
messageId: message.id,
|
||||||
|
|
|
@ -32,17 +32,15 @@ import { markViewed } from '../../services/MessageUpdater';
|
||||||
import { queueAttachmentDownloads } from '../../util/queueAttachmentDownloads';
|
import { queueAttachmentDownloads } from '../../util/queueAttachmentDownloads';
|
||||||
import { replaceIndex } from '../../util/replaceIndex';
|
import { replaceIndex } from '../../util/replaceIndex';
|
||||||
import { showToast } from '../../util/showToast';
|
import { showToast } from '../../util/showToast';
|
||||||
import {
|
import { hasFailed, isDownloaded, isDownloading } from '../../types/Attachment';
|
||||||
hasFailed,
|
|
||||||
hasNotResolved,
|
|
||||||
isDownloaded,
|
|
||||||
isDownloading,
|
|
||||||
} from '../../types/Attachment';
|
|
||||||
import {
|
import {
|
||||||
getConversationSelector,
|
getConversationSelector,
|
||||||
getHideStoryConversationIds,
|
getHideStoryConversationIds,
|
||||||
} from '../selectors/conversations';
|
} from '../selectors/conversations';
|
||||||
import { getStories } from '../selectors/stories';
|
import {
|
||||||
|
getStories,
|
||||||
|
getStoryDownloadableAttachment,
|
||||||
|
} from '../selectors/stories';
|
||||||
import { getStoryDataFromMessageAttributes } from '../../services/storyLoader';
|
import { getStoryDataFromMessageAttributes } from '../../services/storyLoader';
|
||||||
import { isGroup } from '../../util/whatTypeOfConversation';
|
import { isGroup } from '../../util/whatTypeOfConversation';
|
||||||
import { isNotNil } from '../../util/isNotNil';
|
import { isNotNil } from '../../util/isNotNil';
|
||||||
|
@ -113,7 +111,6 @@ const LIST_MEMBERS_VERIFIED = 'stories/LIST_MEMBERS_VERIFIED';
|
||||||
const LOAD_STORY_REPLIES = 'stories/LOAD_STORY_REPLIES';
|
const LOAD_STORY_REPLIES = 'stories/LOAD_STORY_REPLIES';
|
||||||
const MARK_STORY_READ = 'stories/MARK_STORY_READ';
|
const MARK_STORY_READ = 'stories/MARK_STORY_READ';
|
||||||
const QUEUE_STORY_DOWNLOAD = 'stories/QUEUE_STORY_DOWNLOAD';
|
const QUEUE_STORY_DOWNLOAD = 'stories/QUEUE_STORY_DOWNLOAD';
|
||||||
export const RESOLVE_ATTACHMENT_URL = 'stories/RESOLVE_ATTACHMENT_URL';
|
|
||||||
const SEND_STORY_MODAL_OPEN_STATE_CHANGED =
|
const SEND_STORY_MODAL_OPEN_STATE_CHANGED =
|
||||||
'stories/SEND_STORY_MODAL_OPEN_STATE_CHANGED';
|
'stories/SEND_STORY_MODAL_OPEN_STATE_CHANGED';
|
||||||
const STORY_CHANGED = 'stories/STORY_CHANGED';
|
const STORY_CHANGED = 'stories/STORY_CHANGED';
|
||||||
|
@ -155,14 +152,6 @@ type QueueStoryDownloadActionType = {
|
||||||
payload: string;
|
payload: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ResolveAttachmentUrlActionType = {
|
|
||||||
type: typeof RESOLVE_ATTACHMENT_URL;
|
|
||||||
payload: {
|
|
||||||
messageId: string;
|
|
||||||
attachmentUrl: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
type SendStoryModalOpenStateChanged = {
|
type SendStoryModalOpenStateChanged = {
|
||||||
type: typeof SEND_STORY_MODAL_OPEN_STATE_CHANGED;
|
type: typeof SEND_STORY_MODAL_OPEN_STATE_CHANGED;
|
||||||
payload: number | undefined;
|
payload: number | undefined;
|
||||||
|
@ -195,7 +184,6 @@ export type StoriesActionType =
|
||||||
| MessageDeletedActionType
|
| MessageDeletedActionType
|
||||||
| MessagesAddedActionType
|
| MessagesAddedActionType
|
||||||
| QueueStoryDownloadActionType
|
| QueueStoryDownloadActionType
|
||||||
| ResolveAttachmentUrlActionType
|
|
||||||
| SendStoryModalOpenStateChanged
|
| SendStoryModalOpenStateChanged
|
||||||
| StoryChangedActionType
|
| StoryChangedActionType
|
||||||
| ToggleViewActionType
|
| ToggleViewActionType
|
||||||
|
@ -337,7 +325,7 @@ function queueStoryDownload(
|
||||||
void,
|
void,
|
||||||
RootStateType,
|
RootStateType,
|
||||||
unknown,
|
unknown,
|
||||||
NoopActionType | QueueStoryDownloadActionType | ResolveAttachmentUrlActionType
|
NoopActionType | QueueStoryDownloadActionType
|
||||||
> {
|
> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const { stories } = getState().stories;
|
const { stories } = getState().stories;
|
||||||
|
@ -347,7 +335,7 @@ function queueStoryDownload(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { attachment } = story;
|
const attachment = getStoryDownloadableAttachment(story);
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
log.warn('queueStoryDownload: No attachment found for story', {
|
log.warn('queueStoryDownload: No attachment found for story', {
|
||||||
|
@ -365,21 +353,6 @@ function queueStoryDownload(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function also resolves the attachment's URL in case we've already
|
|
||||||
// downloaded the attachment but haven't pointed its path to an absolute
|
|
||||||
// location on disk.
|
|
||||||
if (hasNotResolved(attachment)) {
|
|
||||||
dispatch({
|
|
||||||
type: RESOLVE_ATTACHMENT_URL,
|
|
||||||
payload: {
|
|
||||||
messageId: storyId,
|
|
||||||
attachmentUrl: window.Signal.Migrations.getAbsoluteAttachmentPath(
|
|
||||||
attachment.path
|
|
||||||
),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,7 +376,10 @@ function queueStoryDownload(
|
||||||
payload: storyId,
|
payload: storyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
await queueAttachmentDownloads(message.attributes);
|
const updatedFields = await queueAttachmentDownloads(message.attributes);
|
||||||
|
if (updatedFields) {
|
||||||
|
message.set(updatedFields);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -627,11 +603,7 @@ const getSelectedStoryDataForDistributionListId = (
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSelectedStoryDataForConversationId = (
|
const getSelectedStoryDataForConversationId = (
|
||||||
dispatch: ThunkDispatch<
|
dispatch: ThunkDispatch<RootStateType, unknown, NoopActionType>,
|
||||||
RootStateType,
|
|
||||||
unknown,
|
|
||||||
NoopActionType | ResolveAttachmentUrlActionType
|
|
||||||
>,
|
|
||||||
getState: () => RootStateType,
|
getState: () => RootStateType,
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
selectedStoryId?: string
|
selectedStoryId?: string
|
||||||
|
@ -671,12 +643,12 @@ const getSelectedStoryDataForConversationId = (
|
||||||
const numStories = storiesByConversationId.length;
|
const numStories = storiesByConversationId.length;
|
||||||
|
|
||||||
// Queue all undownloaded stories once we're viewing someone's stories
|
// Queue all undownloaded stories once we're viewing someone's stories
|
||||||
storiesByConversationId.forEach(item => {
|
storiesByConversationId.forEach(({ attachment, messageId }) => {
|
||||||
if (isDownloaded(item.attachment) || isDownloading(item.attachment)) {
|
if (isDownloaded(attachment) || isDownloading(attachment)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
queueStoryDownload(item.messageId)(dispatch, getState, null);
|
queueStoryDownload(messageId)(dispatch, getState, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1416,41 +1388,6 @@ export function reducer(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === RESOLVE_ATTACHMENT_URL) {
|
|
||||||
const { messageId, attachmentUrl } = action.payload;
|
|
||||||
|
|
||||||
const storyIndex = state.stories.findIndex(
|
|
||||||
existingStory => existingStory.messageId === messageId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (storyIndex < 0) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
const story = state.stories[storyIndex];
|
|
||||||
|
|
||||||
if (!story.attachment) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
const storyWithResolvedAttachment = {
|
|
||||||
...story,
|
|
||||||
attachment: {
|
|
||||||
...story.attachment,
|
|
||||||
url: attachmentUrl,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
stories: replaceIndex(
|
|
||||||
state.stories,
|
|
||||||
storyIndex,
|
|
||||||
storyWithResolvedAttachment
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.type === DOE_STORY) {
|
if (action.type === DOE_STORY) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { pick } from 'lodash';
|
||||||
import type { GetConversationByIdType } from './conversations';
|
import type { GetConversationByIdType } from './conversations';
|
||||||
import type { ConversationType } from '../ducks/conversations';
|
import type { ConversationType } from '../ducks/conversations';
|
||||||
import type { MessageReactionType } from '../../model-types.d';
|
import type { MessageReactionType } from '../../model-types.d';
|
||||||
|
import type { AttachmentType } from '../../types/Attachment';
|
||||||
import type {
|
import type {
|
||||||
ConversationStoryType,
|
ConversationStoryType,
|
||||||
MyStoryType,
|
MyStoryType,
|
||||||
|
@ -133,6 +134,13 @@ function getAvatarData(
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getStoryDownloadableAttachment({
|
||||||
|
attachment,
|
||||||
|
}: StoryDataType): AttachmentType | undefined {
|
||||||
|
// See: getStoryDataFromMessageAttributes for how preview gets populated.
|
||||||
|
return attachment?.textAttachment?.preview?.image ?? attachment;
|
||||||
|
}
|
||||||
|
|
||||||
export function getStoryView(
|
export function getStoryView(
|
||||||
conversationSelector: GetConversationByIdType,
|
conversationSelector: GetConversationByIdType,
|
||||||
ourConversationId: string | undefined,
|
ourConversationId: string | undefined,
|
||||||
|
@ -159,13 +167,7 @@ export function getStoryView(
|
||||||
expireTimer,
|
expireTimer,
|
||||||
readAt,
|
readAt,
|
||||||
timestamp,
|
timestamp,
|
||||||
} = pick(story, [
|
} = story;
|
||||||
'attachment',
|
|
||||||
'expirationStartTimestamp',
|
|
||||||
'expireTimer',
|
|
||||||
'readAt',
|
|
||||||
'timestamp',
|
|
||||||
]);
|
|
||||||
|
|
||||||
const { sendStateByConversationId } = story;
|
const { sendStateByConversationId } = story;
|
||||||
let sendState: Array<StorySendStateType> | undefined;
|
let sendState: Array<StorySendStateType> | undefined;
|
||||||
|
|
|
@ -3,12 +3,9 @@
|
||||||
|
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import casual from 'casual';
|
import casual from 'casual';
|
||||||
import path from 'path';
|
|
||||||
import { assert } from 'chai';
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
DispatchableViewStoryType,
|
DispatchableViewStoryType,
|
||||||
StoriesStateType,
|
|
||||||
StoryDataType,
|
StoryDataType,
|
||||||
} from '../../../state/ducks/stories';
|
} from '../../../state/ducks/stories';
|
||||||
import type { ConversationType } from '../../../state/ducks/conversations';
|
import type { ConversationType } from '../../../state/ducks/conversations';
|
||||||
|
@ -16,19 +13,14 @@ import type { MessageAttributesType } from '../../../model-types.d';
|
||||||
import type { StateType as RootStateType } from '../../../state/reducer';
|
import type { StateType as RootStateType } from '../../../state/reducer';
|
||||||
import type { UUIDStringType } from '../../../types/UUID';
|
import type { UUIDStringType } from '../../../types/UUID';
|
||||||
import { DAY } from '../../../util/durations';
|
import { DAY } from '../../../util/durations';
|
||||||
import { IMAGE_JPEG } from '../../../types/MIME';
|
import { TEXT_ATTACHMENT, IMAGE_JPEG } from '../../../types/MIME';
|
||||||
import { ReadStatus } from '../../../messages/MessageReadStatus';
|
import { ReadStatus } from '../../../messages/MessageReadStatus';
|
||||||
import {
|
import {
|
||||||
StoryViewDirectionType,
|
StoryViewDirectionType,
|
||||||
StoryViewModeType,
|
StoryViewModeType,
|
||||||
} from '../../../types/Stories';
|
} from '../../../types/Stories';
|
||||||
import { UUID } from '../../../types/UUID';
|
import { UUID } from '../../../types/UUID';
|
||||||
import {
|
import { actions, getEmptyState } from '../../../state/ducks/stories';
|
||||||
actions,
|
|
||||||
getEmptyState,
|
|
||||||
reducer,
|
|
||||||
RESOLVE_ATTACHMENT_URL,
|
|
||||||
} from '../../../state/ducks/stories';
|
|
||||||
import { noopAction } from '../../../state/ducks/noop';
|
import { noopAction } from '../../../state/ducks/noop';
|
||||||
import { reducer as rootReducer } from '../../../state/reducer';
|
import { reducer as rootReducer } from '../../../state/reducer';
|
||||||
import { dropNull } from '../../../util/dropNull';
|
import { dropNull } from '../../../util/dropNull';
|
||||||
|
@ -917,86 +909,6 @@ describe('both/state/ducks/stories', () => {
|
||||||
sinon.assert.notCalled(dispatch);
|
sinon.assert.notCalled(dispatch);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('downloaded, but unresolved, we should resolve the path', async function test() {
|
|
||||||
const storyId = UUID.generate().toString();
|
|
||||||
const attachment = {
|
|
||||||
contentType: IMAGE_JPEG,
|
|
||||||
path: 'image.jpg',
|
|
||||||
size: 0,
|
|
||||||
};
|
|
||||||
const messageAttributes = {
|
|
||||||
...getStoryMessage(storyId),
|
|
||||||
attachments: [attachment],
|
|
||||||
};
|
|
||||||
|
|
||||||
const rootState = getEmptyRootState();
|
|
||||||
|
|
||||||
const getState = () => ({
|
|
||||||
...rootState,
|
|
||||||
stories: {
|
|
||||||
...rootState.stories,
|
|
||||||
stories: [
|
|
||||||
{
|
|
||||||
...messageAttributes,
|
|
||||||
attachment: messageAttributes.attachments[0],
|
|
||||||
messageId: messageAttributes.id,
|
|
||||||
expireTimer: messageAttributes.expireTimer,
|
|
||||||
expirationStartTimestamp: dropNull(
|
|
||||||
messageAttributes.expirationStartTimestamp
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
window.MessageController.register(storyId, messageAttributes);
|
|
||||||
|
|
||||||
const dispatch = sinon.spy();
|
|
||||||
await queueStoryDownload(storyId)(dispatch, getState, null);
|
|
||||||
|
|
||||||
const action = dispatch.getCall(0).args[0];
|
|
||||||
|
|
||||||
sinon.assert.calledWith(dispatch, {
|
|
||||||
type: RESOLVE_ATTACHMENT_URL,
|
|
||||||
payload: {
|
|
||||||
messageId: storyId,
|
|
||||||
attachmentUrl: action.payload.attachmentUrl,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
assert.equal(
|
|
||||||
attachment.path,
|
|
||||||
path.basename(action.payload.attachmentUrl)
|
|
||||||
);
|
|
||||||
|
|
||||||
const stateWithStory: StoriesStateType = {
|
|
||||||
...getEmptyRootState().stories,
|
|
||||||
stories: [
|
|
||||||
{
|
|
||||||
...messageAttributes,
|
|
||||||
messageId: storyId,
|
|
||||||
attachment,
|
|
||||||
expireTimer: messageAttributes.expireTimer,
|
|
||||||
expirationStartTimestamp: dropNull(
|
|
||||||
messageAttributes.expirationStartTimestamp
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const nextState = reducer(stateWithStory, action);
|
|
||||||
assert.isDefined(nextState.stories);
|
|
||||||
assert.equal(
|
|
||||||
nextState.stories[0].attachment?.url,
|
|
||||||
action.payload.attachmentUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
const state = getEmptyRootState().stories;
|
|
||||||
|
|
||||||
const sameState = reducer(state, action);
|
|
||||||
assert.isDefined(sameState.stories);
|
|
||||||
assert.equal(sameState, state);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('not downloaded, queued for download', async function test() {
|
it('not downloaded, queued for download', async function test() {
|
||||||
const storyId = UUID.generate().toString();
|
const storyId = UUID.generate().toString();
|
||||||
const messageAttributes = {
|
const messageAttributes = {
|
||||||
|
@ -1039,5 +951,59 @@ describe('both/state/ducks/stories', () => {
|
||||||
payload: storyId,
|
payload: storyId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('preview not downloaded, queued for download', async function test() {
|
||||||
|
const storyId = UUID.generate().toString();
|
||||||
|
const preview = {
|
||||||
|
url: 'https://signal.org',
|
||||||
|
image: {
|
||||||
|
contentType: IMAGE_JPEG,
|
||||||
|
size: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const messageAttributes = {
|
||||||
|
...getStoryMessage(storyId),
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
contentType: TEXT_ATTACHMENT,
|
||||||
|
size: 0,
|
||||||
|
textAttachment: {
|
||||||
|
preview,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
preview: [preview],
|
||||||
|
};
|
||||||
|
|
||||||
|
const rootState = getEmptyRootState();
|
||||||
|
|
||||||
|
const getState = () => ({
|
||||||
|
...rootState,
|
||||||
|
stories: {
|
||||||
|
...rootState.stories,
|
||||||
|
stories: [
|
||||||
|
{
|
||||||
|
...messageAttributes,
|
||||||
|
attachment: messageAttributes.attachments[0],
|
||||||
|
messageId: messageAttributes.id,
|
||||||
|
expireTimer: messageAttributes.expireTimer,
|
||||||
|
expirationStartTimestamp: dropNull(
|
||||||
|
messageAttributes.expirationStartTimestamp
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
window.MessageController.register(storyId, messageAttributes);
|
||||||
|
|
||||||
|
const dispatch = sinon.spy();
|
||||||
|
await queueStoryDownload(storyId)(dispatch, getState, null);
|
||||||
|
|
||||||
|
sinon.assert.calledWith(dispatch, {
|
||||||
|
type: 'stories/QUEUE_STORY_DOWNLOAD',
|
||||||
|
payload: storyId,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
/* eslint-disable no-bitwise */
|
/* eslint-disable no-bitwise */
|
||||||
|
|
||||||
import { isBoolean, isNumber } from 'lodash';
|
import { isBoolean, isNumber, omit } from 'lodash';
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import { v4 as getGuid } from 'uuid';
|
import { v4 as getGuid } from 'uuid';
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ import createTaskWithTimeout from './TaskWithTimeout';
|
||||||
import {
|
import {
|
||||||
processAttachment,
|
processAttachment,
|
||||||
processDataMessage,
|
processDataMessage,
|
||||||
|
processPreview,
|
||||||
processGroupV2Context,
|
processGroupV2Context,
|
||||||
} from './processDataMessage';
|
} from './processDataMessage';
|
||||||
import { processSyncMessage } from './processSyncMessage';
|
import { processSyncMessage } from './processSyncMessage';
|
||||||
|
@ -75,6 +76,7 @@ import * as Bytes from '../Bytes';
|
||||||
import type {
|
import type {
|
||||||
ProcessedAttachment,
|
ProcessedAttachment,
|
||||||
ProcessedDataMessage,
|
ProcessedDataMessage,
|
||||||
|
ProcessedPreview,
|
||||||
ProcessedSyncMessage,
|
ProcessedSyncMessage,
|
||||||
ProcessedSent,
|
ProcessedSent,
|
||||||
ProcessedEnvelope,
|
ProcessedEnvelope,
|
||||||
|
@ -1993,6 +1995,7 @@ export default class MessageReceiver
|
||||||
log.info('MessageReceiver.handleStoryMessage', logId);
|
log.info('MessageReceiver.handleStoryMessage', logId);
|
||||||
|
|
||||||
const attachments: Array<ProcessedAttachment> = [];
|
const attachments: Array<ProcessedAttachment> = [];
|
||||||
|
let preview: ReadonlyArray<ProcessedPreview> | undefined;
|
||||||
|
|
||||||
if (msg.fileAttachment) {
|
if (msg.fileAttachment) {
|
||||||
const attachment = processAttachment(msg.fileAttachment);
|
const attachment = processAttachment(msg.fileAttachment);
|
||||||
|
@ -2000,16 +2003,17 @@ export default class MessageReceiver
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.textAttachment) {
|
if (msg.textAttachment) {
|
||||||
const { text } = msg.textAttachment;
|
const { text, preview: unprocessedPreview } = msg.textAttachment;
|
||||||
if (!text) {
|
if (unprocessedPreview) {
|
||||||
throw new Error('Text attachments must have text!');
|
preview = processPreview([unprocessedPreview]);
|
||||||
|
} else if (!text) {
|
||||||
|
throw new Error('Text attachments must have text or link preview!');
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO DESKTOP-3714 we should download the story link preview image
|
|
||||||
attachments.push({
|
attachments.push({
|
||||||
size: text.length,
|
size: text?.length ?? 0,
|
||||||
contentType: TEXT_ATTACHMENT,
|
contentType: TEXT_ATTACHMENT,
|
||||||
textAttachment: msg.textAttachment,
|
textAttachment: omit(msg.textAttachment, 'preview'),
|
||||||
blurHash: generateBlurHash(
|
blurHash: generateBlurHash(
|
||||||
(msg.textAttachment.color ||
|
(msg.textAttachment.color ||
|
||||||
msg.textAttachment.gradient?.startColor) ??
|
msg.textAttachment.gradient?.startColor) ??
|
||||||
|
@ -2045,6 +2049,7 @@ export default class MessageReceiver
|
||||||
|
|
||||||
const message: ProcessedDataMessage = {
|
const message: ProcessedDataMessage = {
|
||||||
attachments,
|
attachments,
|
||||||
|
preview,
|
||||||
canReplyToStory: Boolean(msg.allowsReplies),
|
canReplyToStory: Boolean(msg.allowsReplies),
|
||||||
expireTimer: durations.DAY / 1000,
|
expireTimer: durations.DAY / 1000,
|
||||||
flags: 0,
|
flags: 0,
|
||||||
|
|
|
@ -697,20 +697,33 @@ export function isGIF(attachments?: ReadonlyArray<AttachmentType>): boolean {
|
||||||
return hasFlag && isVideoAttachment(attachment);
|
return hasFlag && isVideoAttachment(attachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveNestedAttachment(
|
||||||
|
attachment?: AttachmentType
|
||||||
|
): AttachmentType | undefined {
|
||||||
|
if (attachment?.textAttachment?.preview?.image) {
|
||||||
|
return attachment.textAttachment.preview.image;
|
||||||
|
}
|
||||||
|
return attachment;
|
||||||
|
}
|
||||||
|
|
||||||
export function isDownloaded(attachment?: AttachmentType): boolean {
|
export function isDownloaded(attachment?: AttachmentType): boolean {
|
||||||
return Boolean(attachment && (attachment.path || attachment.textAttachment));
|
const resolved = resolveNestedAttachment(attachment);
|
||||||
|
return Boolean(resolved && (resolved.path || resolved.textAttachment));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasNotResolved(attachment?: AttachmentType): boolean {
|
export function hasNotResolved(attachment?: AttachmentType): boolean {
|
||||||
return Boolean(attachment && !attachment.url && !attachment.textAttachment);
|
const resolved = resolveNestedAttachment(attachment);
|
||||||
|
return Boolean(resolved && !resolved.url && !resolved.textAttachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isDownloading(attachment?: AttachmentType): boolean {
|
export function isDownloading(attachment?: AttachmentType): boolean {
|
||||||
return Boolean(attachment && attachment.downloadJobId && attachment.pending);
|
const resolved = resolveNestedAttachment(attachment);
|
||||||
|
return Boolean(resolved && resolved.downloadJobId && resolved.pending);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasFailed(attachment?: AttachmentType): boolean {
|
export function hasFailed(attachment?: AttachmentType): boolean {
|
||||||
return Boolean(attachment && attachment.error);
|
const resolved = resolveNestedAttachment(attachment);
|
||||||
|
return Boolean(resolved && resolved.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasVideoBlurHash(attachments?: Array<AttachmentType>): boolean {
|
export function hasVideoBlurHash(attachments?: Array<AttachmentType>): boolean {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue