diff --git a/keyserver/package.json b/keyserver/package.json --- a/keyserver/package.json +++ b/keyserver/package.json @@ -35,6 +35,7 @@ "concurrently": "^5.3.0", "flow-bin": "^0.182.0", "flow-typed": "^3.2.1", + "internal-ip": "4.3.0", "jest": "^26.6.3", "nodemon": "^2.0.4" }, diff --git a/keyserver/src/creators/upload-creator.js b/keyserver/src/creators/upload-creator.js --- a/keyserver/src/creators/upload-creator.js +++ b/keyserver/src/creators/upload-creator.js @@ -2,7 +2,6 @@ import crypto from 'crypto'; -import { shimUploadURI } from 'lib/media/media-utils.js'; import type { MediaType, UploadMultimediaResult, @@ -39,7 +38,7 @@ return { uploadResult: { id, - uri: shimUploadURI(getUploadURL(id, secret), viewer.platformDetails), + uri: getUploadURL(id, secret), dimensions, mediaType, loop, diff --git a/keyserver/src/endpoints.js b/keyserver/src/endpoints.js --- a/keyserver/src/endpoints.js +++ b/keyserver/src/endpoints.js @@ -41,6 +41,7 @@ threadLeaveResponder, threadUpdateResponder, threadCreationResponder, + threadFetchMediaResponder, threadJoinResponder, } from './responders/thread-responders.js'; import { @@ -133,6 +134,10 @@ responder: messageFetchResponder, requiredPolicies: baseLegalPolicies, }, + fetch_thread_media: { + responder: threadFetchMediaResponder, + requiredPolicies: baseLegalPolicies, + }, get_session_public_keys: { responder: getSessionPublicKeysResponder, requiredPolicies: baseLegalPolicies, diff --git a/keyserver/src/fetchers/upload-fetchers.js b/keyserver/src/fetchers/upload-fetchers.js --- a/keyserver/src/fetchers/upload-fetchers.js +++ b/keyserver/src/fetchers/upload-fetchers.js @@ -1,10 +1,16 @@ // @flow +import ip from 'internal-ip'; import _keyBy from 'lodash/fp/keyBy.js'; import type { Media } from 'lib/types/media-types.js'; import type { MediaMessageServerDBContent } from 'lib/types/messages/media.js'; import { getUploadIDsFromMediaMessageServerDBContents } from 'lib/types/messages/media.js'; +import type { + ThreadFetchMediaResult, + ThreadFetchMediaRequest, +} from 'lib/types/thread-types.js'; +import { isDev } from 'lib/utils/dev-utils.js'; import { ServerError } from 'lib/utils/errors.js'; import { dbQuery, SQL } from '../database/database.js'; @@ -80,7 +86,13 @@ function getUploadURL(id: string, secret: string): string { const { baseDomain, basePath } = getAndAssertCommAppURLFacts(); - return `${baseDomain}${basePath}upload/${id}/${secret}`; + const uploadPath = `${basePath}upload/${id}/${secret}`; + if (isDev) { + const ipV4 = ip.v4.sync() || 'localhost'; + const port = parseInt(process.env.PORT, 10) || 3000; + return `http://${ipV4}:${port}${uploadPath}`; + } + return `${baseDomain}${uploadPath}`; } function mediaFromRow(row: Object): Media { @@ -117,23 +129,20 @@ } async function fetchMediaForThread( - threadID: string, - limit: number, - offset: number, -): Promise<$ReadOnlyArray> { - const limitQuery = SQL`LIMIT ${limit} `; - const offsetQuery = SQL`OFFSET ${offset} `; + request: ThreadFetchMediaRequest, +): Promise { const query = SQL` SELECT id AS uploadID, secret AS uploadSecret, type AS uploadType, extra AS uploadExtra FROM uploads - WHERE thread = ${threadID} AND filename NOT LIKE 'thumb%' + WHERE thread = ${request.threadID} AND filename NOT LIKE 'thumb%' ORDER BY creation_time DESC - ` - .append(limitQuery) - .append(offsetQuery); + LIMIT ${request.limit} OFFSET ${request.offset} + `; const [uploads] = await dbQuery(query); - return uploads.map(mediaFromRow); + return { + media: uploads.map(mediaFromRow), + }; } async function fetchUploadsForMessage( diff --git a/keyserver/src/responders/thread-responders.js b/keyserver/src/responders/thread-responders.js --- a/keyserver/src/responders/thread-responders.js +++ b/keyserver/src/responders/thread-responders.js @@ -15,6 +15,8 @@ type NewThreadResponse, type ServerThreadJoinRequest, type ThreadJoinResult, + type ThreadFetchMediaResult, + type ThreadFetchMediaRequest, threadTypes, } from 'lib/types/thread-types.js'; import { values } from 'lib/utils/objects.js'; @@ -31,6 +33,7 @@ } from './entry-responders.js'; import { createThread } from '../creators/thread-creator.js'; import { deleteThread } from '../deleters/thread-deleters.js'; +import { fetchMediaForThread } from '../fetchers/upload-fetchers.js'; import type { Viewer } from '../session/viewer.js'; import { updateRole, @@ -176,6 +179,20 @@ return await joinThread(viewer, request); } +const threadFetchMediaRequestInputValidator = tShape({ + threadID: t.String, + limit: t.Number, + offset: t.Number, +}); +async function threadFetchMediaResponder( + viewer: Viewer, + input: any, +): Promise { + const request: ThreadFetchMediaRequest = input; + await validateInput(viewer, threadFetchMediaRequestInputValidator, request); + return await fetchMediaForThread(request); +} + export { threadDeletionResponder, roleUpdateResponder, @@ -184,5 +201,6 @@ threadUpdateResponder, threadCreationResponder, threadJoinResponder, + threadFetchMediaResponder, newThreadRequestInputValidator, }; diff --git a/lib/actions/thread-actions.js b/lib/actions/thread-actions.js --- a/lib/actions/thread-actions.js +++ b/lib/actions/thread-actions.js @@ -10,6 +10,8 @@ NewThreadResult, ClientThreadJoinRequest, ThreadJoinPayload, + ThreadFetchMediaRequest, + ThreadFetchMediaResult, } from '../types/thread-types.js'; import type { CallServerEndpoint } from '../utils/call-server-endpoint.js'; import { values } from '../utils/objects.js'; @@ -162,6 +164,15 @@ }; }; +const fetchThreadMedia = ( + callServerEndpoint: CallServerEndpoint, +): (( + request: ThreadFetchMediaRequest, +) => Promise) => async request => { + const response = await callServerEndpoint('fetch_thread_media', request); + return response; +}; + export { deleteThreadActionTypes, deleteThread, @@ -177,4 +188,5 @@ joinThread, leaveThreadActionTypes, leaveThread, + fetchThreadMedia, }; diff --git a/lib/media/media-utils.js b/lib/media/media-utils.js --- a/lib/media/media-utils.js +++ b/lib/media/media-utils.js @@ -2,7 +2,6 @@ import invariant from 'invariant'; -import type { PlatformDetails } from '../types/device-types.js'; import type { Media } from '../types/media-types.js'; import type { MultimediaMessageInfo, @@ -11,15 +10,6 @@ const maxDimensions = Object.freeze({ width: 1920, height: 1920 }); -const localhostRegex = /^http:\/\/localhost/; -function shimUploadURI(uri: string, platformDetails: ?PlatformDetails): string { - if (!platformDetails || platformDetails.platform !== 'android') { - return uri; - } - // We do this for testing in the Android emulator - return uri.replace(localhostRegex, 'http://10.0.2.2'); -} - function contentStringForMediaArray(media: $ReadOnlyArray): string { if (media.length === 0) { return 'corrupted media'; @@ -64,7 +54,6 @@ export { maxDimensions, - shimUploadURI, contentStringForMediaArray, multimediaMessagePreview, isLocalUploadID, diff --git a/lib/shared/messages/multimedia-message-spec.js b/lib/shared/messages/multimedia-message-spec.js --- a/lib/shared/messages/multimedia-message-spec.js +++ b/lib/shared/messages/multimedia-message-spec.js @@ -12,10 +12,8 @@ import { contentStringForMediaArray, multimediaMessagePreview, - shimUploadURI, } from '../../media/media-utils.js'; import type { PlatformDetails } from '../../types/device-types.js'; -import type { Media, Video, Image } from '../../types/media-types.js'; import { messageTypes, assertMessageType, @@ -24,7 +22,6 @@ import type { MessageInfo, RawMessageInfo, - RawMultimediaMessageInfo, ClientDBMessageInfo, } from '../../types/message-types.js'; import type { @@ -202,32 +199,22 @@ platformDetails: ?PlatformDetails, ): RawMediaMessageInfo | RawImagesMessageInfo | RawUnsupportedMessageInfo { if (rawMessageInfo.type === messageTypes.IMAGES) { - const shimmedRawMessageInfo = shimMediaMessageInfo( - rawMessageInfo, - platformDetails, - ); - return shimmedRawMessageInfo; - } else { - const shimmedRawMessageInfo = shimMediaMessageInfo( - rawMessageInfo, - platformDetails, - ); - // TODO figure out first native codeVersion supporting video playback - if (hasMinCodeVersion(platformDetails, 158)) { - return shimmedRawMessageInfo; - } - const { id } = shimmedRawMessageInfo; - invariant(id !== null && id !== undefined, 'id should be set on server'); - return { - type: messageTypes.UNSUPPORTED, - id, - threadID: shimmedRawMessageInfo.threadID, - creatorID: shimmedRawMessageInfo.creatorID, - time: shimmedRawMessageInfo.time, - robotext: multimediaMessagePreview(shimmedRawMessageInfo), - unsupportedMessageInfo: shimmedRawMessageInfo, - }; + return rawMessageInfo; } + if (hasMinCodeVersion(platformDetails, 158)) { + return rawMessageInfo; + } + const { id } = rawMessageInfo; + invariant(id !== null && id !== undefined, 'id should be set on server'); + return { + type: messageTypes.UNSUPPORTED, + id, + threadID: rawMessageInfo.threadID, + creatorID: rawMessageInfo.creatorID, + time: rawMessageInfo.time, + robotext: multimediaMessagePreview(rawMessageInfo), + unsupportedMessageInfo: rawMessageInfo, + }; }, unshimMessageInfo( @@ -313,54 +300,6 @@ includedInRepliesCount: true, }); -function shimMediaMessageInfo( - rawMessageInfo: RawMultimediaMessageInfo, - platformDetails: ?PlatformDetails, -): RawMultimediaMessageInfo { - if (rawMessageInfo.type === messageTypes.IMAGES) { - let uriChanged = false; - const newMedia: Image[] = []; - for (const singleMedia of rawMessageInfo.media) { - const shimmedURI = shimUploadURI(singleMedia.uri, platformDetails); - if (shimmedURI === singleMedia.uri) { - newMedia.push(singleMedia); - } else { - newMedia.push(({ ...singleMedia, uri: shimmedURI }: Image)); - uriChanged = true; - } - } - if (!uriChanged) { - return rawMessageInfo; - } - return ({ - ...rawMessageInfo, - media: newMedia, - }: RawImagesMessageInfo); - } else { - let uriChanged = false; - const newMedia: Media[] = []; - for (const singleMedia of rawMessageInfo.media) { - const shimmedURI = shimUploadURI(singleMedia.uri, platformDetails); - if (shimmedURI === singleMedia.uri) { - newMedia.push(singleMedia); - } else if (singleMedia.type === 'photo') { - newMedia.push(({ ...singleMedia, uri: shimmedURI }: Image)); - uriChanged = true; - } else { - newMedia.push(({ ...singleMedia, uri: shimmedURI }: Video)); - uriChanged = true; - } - } - if (!uriChanged) { - return rawMessageInfo; - } - return ({ - ...rawMessageInfo, - media: newMedia, - }: RawMediaMessageInfo); - } -} - // Four photos were uploaded before dimensions were calculated server-side, // and delivered to clients without dimensions in the MultimediaMessageInfo. const preDimensionUploads = { diff --git a/lib/types/endpoints.js b/lib/types/endpoints.js --- a/lib/types/endpoints.js +++ b/lib/types/endpoints.js @@ -61,6 +61,7 @@ FETCH_ENTRY_REVISIONS: 'fetch_entry_revisions', FETCH_ERROR_REPORT_INFOS: 'fetch_error_report_infos', FETCH_MESSAGES: 'fetch_messages', + FETCH_THREAD_MEDIA: 'fetch_thread_media', GET_SESSION_PUBLIC_KEYS: 'get_session_public_keys', JOIN_THREAD: 'join_thread', LEAVE_THREAD: 'leave_thread', diff --git a/lib/types/thread-types.js b/lib/types/thread-types.js --- a/lib/types/thread-types.js +++ b/lib/types/thread-types.js @@ -4,6 +4,7 @@ import type { Shape } from './core.js'; import type { CalendarQuery, RawEntryInfo } from './entry-types.js'; +import type { Media } from './media-types.js'; import type { RawMessageInfo, MessageTruncationStatuses, @@ -453,6 +454,15 @@ +userInfos: $ReadOnlyArray, }; +export type ThreadFetchMediaResult = { + +media: $ReadOnlyArray, +}; +export type ThreadFetchMediaRequest = { + +threadID: string, + +limit: number, + +offset: number, +}; + export type SidebarInfo = { +threadInfo: ThreadInfo, +lastUpdatedTime: number,