Changeset View
Changeset View
Standalone View
Standalone View
native/input/input-state-container.react.js
// @flow | // @flow | ||||
import invariant from 'invariant'; | import invariant from 'invariant'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { Platform } from 'react-native'; | import { Platform } from 'react-native'; | ||||
import * as Upload from 'react-native-background-upload'; | import * as Upload from 'react-native-background-upload'; | ||||
import { useDispatch } from 'react-redux'; | import { useDispatch } from 'react-redux'; | ||||
import { createSelector } from 'reselect'; | import { createSelector } from 'reselect'; | ||||
import * as uuid from 'uuid'; | |||||
import { | import { | ||||
createLocalMessageActionType, | createLocalMessageActionType, | ||||
sendMultimediaMessageActionTypes, | sendMultimediaMessageActionTypes, | ||||
sendMultimediaMessage, | sendMultimediaMessage, | ||||
sendTextMessageActionTypes, | sendTextMessageActionTypes, | ||||
sendTextMessage, | sendTextMessage, | ||||
} from 'lib/actions/message-actions.js'; | } from 'lib/actions/message-actions.js'; | ||||
import { queueReportsActionType } from 'lib/actions/report-actions.js'; | import { queueReportsActionType } from 'lib/actions/report-actions.js'; | ||||
import { newThread } from 'lib/actions/thread-actions.js'; | import { newThread } from 'lib/actions/thread-actions.js'; | ||||
import { | import { | ||||
uploadMultimedia, | uploadMultimedia, | ||||
uploadMediaMetadata, | |||||
updateMultimediaMessageMediaActionType, | updateMultimediaMessageMediaActionType, | ||||
type MultimediaUploadCallbacks, | type MultimediaUploadCallbacks, | ||||
type MultimediaUploadExtras, | type MultimediaUploadExtras, | ||||
} from 'lib/actions/upload-actions.js'; | } from 'lib/actions/upload-actions.js'; | ||||
import blobService from 'lib/facts/blob-service.js'; | |||||
import commStaffCommunity from 'lib/facts/comm-staff-community.js'; | import commStaffCommunity from 'lib/facts/comm-staff-community.js'; | ||||
import { pathFromURI, replaceExtension } from 'lib/media/file-utils.js'; | import { pathFromURI, replaceExtension } from 'lib/media/file-utils.js'; | ||||
import { | import { | ||||
isLocalUploadID, | isLocalUploadID, | ||||
getNextLocalUploadID, | getNextLocalUploadID, | ||||
} from 'lib/media/media-utils.js'; | } from 'lib/media/media-utils.js'; | ||||
import { videoDurationLimit } from 'lib/media/video-utils.js'; | import { videoDurationLimit } from 'lib/media/video-utils.js'; | ||||
import { | import { | ||||
Show All 10 Lines | import { | ||||
createRealThreadFromPendingThread, | createRealThreadFromPendingThread, | ||||
threadIsPending, | threadIsPending, | ||||
threadIsPendingSidebar, | threadIsPendingSidebar, | ||||
patchThreadInfoToIncludeMentionedMembersOfParent, | patchThreadInfoToIncludeMentionedMembersOfParent, | ||||
threadInfoInsideCommunity, | threadInfoInsideCommunity, | ||||
} from 'lib/shared/thread-utils.js'; | } from 'lib/shared/thread-utils.js'; | ||||
import type { CalendarQuery } from 'lib/types/entry-types.js'; | import type { CalendarQuery } from 'lib/types/entry-types.js'; | ||||
import type { | import type { | ||||
Dimensions, | |||||
UploadMultimediaResult, | UploadMultimediaResult, | ||||
Media, | Media, | ||||
NativeMediaSelection, | NativeMediaSelection, | ||||
MediaMissionResult, | MediaMissionResult, | ||||
MediaMission, | MediaMission, | ||||
UploadMediaMetadataRequest, | |||||
} from 'lib/types/media-types.js'; | } from 'lib/types/media-types.js'; | ||||
import { messageTypes } from 'lib/types/message-types-enum.js'; | import { messageTypes } from 'lib/types/message-types-enum.js'; | ||||
import { | import { | ||||
type RawMessageInfo, | type RawMessageInfo, | ||||
type RawMultimediaMessageInfo, | type RawMultimediaMessageInfo, | ||||
type SendMessageResult, | type SendMessageResult, | ||||
type SendMessagePayload, | type SendMessagePayload, | ||||
type MessageInfo, | type MessageInfo, | ||||
Show All 16 Lines | import { | ||||
type ThreadInfo, | type ThreadInfo, | ||||
threadTypes, | threadTypes, | ||||
} from 'lib/types/thread-types.js'; | } from 'lib/types/thread-types.js'; | ||||
import { | import { | ||||
type DispatchActionPromise, | type DispatchActionPromise, | ||||
useServerCall, | useServerCall, | ||||
useDispatchActionPromise, | useDispatchActionPromise, | ||||
} from 'lib/utils/action-utils.js'; | } from 'lib/utils/action-utils.js'; | ||||
import { toBase64URL } from 'lib/utils/base64.js'; | |||||
import { makeBlobServiceEndpointURL } from 'lib/utils/blob-service.js'; | |||||
import type { | import type { | ||||
CallServerEndpointOptions, | CallServerEndpointOptions, | ||||
CallServerEndpointResponse, | CallServerEndpointResponse, | ||||
} from 'lib/utils/call-server-endpoint.js'; | } from 'lib/utils/call-server-endpoint.js'; | ||||
import { getConfig } from 'lib/utils/config.js'; | import { getConfig } from 'lib/utils/config.js'; | ||||
import { getMessageForException, cloneError } from 'lib/utils/errors.js'; | import { getMessageForException, cloneError } from 'lib/utils/errors.js'; | ||||
import { values } from 'lib/utils/objects.js'; | import { values } from 'lib/utils/objects.js'; | ||||
import { useIsReportEnabled } from 'lib/utils/report-utils.js'; | import { useIsReportEnabled } from 'lib/utils/report-utils.js'; | ||||
Show All 37 Lines | type Props = { | ||||
+dispatch: Dispatch, | +dispatch: Dispatch, | ||||
+staffCanSee: boolean, | +staffCanSee: boolean, | ||||
+dispatchActionPromise: DispatchActionPromise, | +dispatchActionPromise: DispatchActionPromise, | ||||
+uploadMultimedia: ( | +uploadMultimedia: ( | ||||
multimedia: Object, | multimedia: Object, | ||||
extras: MultimediaUploadExtras, | extras: MultimediaUploadExtras, | ||||
callbacks: MultimediaUploadCallbacks, | callbacks: MultimediaUploadCallbacks, | ||||
) => Promise<UploadMultimediaResult>, | ) => Promise<UploadMultimediaResult>, | ||||
+uploadMediaMetadata: ( | |||||
input: UploadMediaMetadataRequest, | |||||
) => Promise<UploadMultimediaResult>, | |||||
+sendMultimediaMessage: ( | +sendMultimediaMessage: ( | ||||
threadID: string, | threadID: string, | ||||
localID: string, | localID: string, | ||||
mediaMessageContents: $ReadOnlyArray<MediaMessageServerDBContent>, | mediaMessageContents: $ReadOnlyArray<MediaMessageServerDBContent>, | ||||
sidebarCreation?: boolean, | sidebarCreation?: boolean, | ||||
) => Promise<SendMessageResult>, | ) => Promise<SendMessageResult>, | ||||
+sendTextMessage: ( | +sendTextMessage: ( | ||||
threadID: string, | threadID: string, | ||||
▲ Show 20 Lines • Show All 918 Lines • ▼ Show 20 Lines | this.setState(prevState => { | ||||
pendingUploads: { | pendingUploads: { | ||||
...prevState.pendingUploads, | ...prevState.pendingUploads, | ||||
[localMessageID]: newPendingUploads, | [localMessageID]: newPendingUploads, | ||||
}, | }, | ||||
}; | }; | ||||
}); | }); | ||||
} | } | ||||
async blobServiceUpload( | |||||
input: { | |||||
uri: string, | |||||
filename: string, | |||||
mimeType: string, | |||||
blobHash: string, | |||||
encryptionKey: string, | |||||
dimensions: Dimensions, | |||||
loop?: boolean, | |||||
}, | |||||
options?: ?CallServerEndpointOptions, | |||||
): Promise<void> { | |||||
const newHolder = uuid.v4(); | |||||
const blobHash = toBase64URL(input.blobHash); | |||||
// 1. Assign new holder for blob with given blobHash | |||||
let blobAlreadyExists: boolean; | |||||
try { | |||||
const assignHolderEndpoint = blobService.httpEndpoints.ASSIGN_HOLDER; | |||||
const assignHolderResponse = await fetch( | |||||
makeBlobServiceEndpointURL(assignHolderEndpoint), | |||||
{ | |||||
method: assignHolderEndpoint.method, | |||||
body: JSON.stringify({ | |||||
holder: newHolder, | |||||
blob_hash: blobHash, | |||||
}), | |||||
headers: { | |||||
'content-type': 'application/json', | |||||
}, | |||||
}, | |||||
); | |||||
if (!assignHolderResponse.ok) { | |||||
const { status, statusText } = assignHolderResponse; | |||||
throw new Error(`Server responded with HTTP ${status}: ${statusText}`); | |||||
} | |||||
const { data_exists: dataExistsResponse } = | |||||
await assignHolderResponse.json(); | |||||
blobAlreadyExists = dataExistsResponse; | |||||
} catch (e) { | |||||
throw new Error( | |||||
`Failed to assign holder: ${ | |||||
getMessageForException(e) ?? 'unknown error' | |||||
}`, | |||||
); | |||||
} | |||||
// 2. Upload blob contents if blob doesn't exist | |||||
if (!blobAlreadyExists) { | |||||
let path = input.uri; | |||||
if (Platform.OS === 'android') { | |||||
const resolvedPath = pathFromURI(input.uri); | |||||
if (resolvedPath) { | |||||
path = resolvedPath; | |||||
} | |||||
} | |||||
const uploadEndpoint = blobService.httpEndpoints.UPLOAD_BLOB; | |||||
const { method } = uploadEndpoint; | |||||
const uploadID = await Upload.startUpload({ | |||||
url: makeBlobServiceEndpointURL(uploadEndpoint), | |||||
method, | |||||
path, | |||||
type: 'multipart', | |||||
field: 'blob_data', | |||||
parameters: { | |||||
blob_hash: blobHash, | |||||
}, | |||||
}); | |||||
if (options && options.abortHandler) { | |||||
options.abortHandler(() => { | |||||
Upload.cancelUpload(uploadID); | |||||
}); | |||||
} | |||||
await new Promise((resolve, reject) => { | |||||
Upload.addListener('error', uploadID, data => { | |||||
reject(data.error); | |||||
}); | |||||
Upload.addListener('cancelled', uploadID, () => { | |||||
reject(new Error('request aborted')); | |||||
}); | |||||
Upload.addListener('completed', uploadID, data => { | |||||
resolve(data); | |||||
}); | |||||
if (options && options.onProgress) { | |||||
const { onProgress } = options; | |||||
Upload.addListener('progress', uploadID, data => | |||||
onProgress(data.progress / 100), | |||||
); | |||||
} | |||||
}); | |||||
} | |||||
// 3. Send upload metadata to the keyserver, return response | |||||
const { filename, mimeType, loop, dimensions, encryptionKey } = input; | |||||
return await this.props.uploadMediaMetadata({ | |||||
...dimensions, | |||||
filename, | |||||
mimeType, | |||||
blobHolder: newHolder, | |||||
encryptionKey, | |||||
loop: loop ?? false, | |||||
}); | |||||
} | |||||
uploadBlob = async ( | uploadBlob = async ( | ||||
url: string, | url: string, | ||||
cookie: ?string, | cookie: ?string, | ||||
sessionID: ?string, | sessionID: ?string, | ||||
input: { [key: string]: mixed }, | input: { [key: string]: mixed }, | ||||
options?: ?CallServerEndpointOptions, | options?: ?CallServerEndpointOptions, | ||||
): Promise<CallServerEndpointResponse> => { | ): Promise<CallServerEndpointResponse> => { | ||||
invariant( | invariant( | ||||
▲ Show 20 Lines • Show All 534 Lines • ▼ Show 20 Lines | const ongoingMessageCreation = useSelector( | ||||
combineLoadingStatuses( | combineLoadingStatuses( | ||||
mediaCreationLoadingStatusSelector(state), | mediaCreationLoadingStatusSelector(state), | ||||
textCreationLoadingStatusSelector(state), | textCreationLoadingStatusSelector(state), | ||||
) === 'loading', | ) === 'loading', | ||||
); | ); | ||||
const hasWiFi = useSelector(state => state.connectivity.hasWiFi); | const hasWiFi = useSelector(state => state.connectivity.hasWiFi); | ||||
const calendarQuery = useCalendarQuery(); | const calendarQuery = useCalendarQuery(); | ||||
const callUploadMultimedia = useServerCall(uploadMultimedia); | const callUploadMultimedia = useServerCall(uploadMultimedia); | ||||
const callUploadMediaMetadata = useServerCall(uploadMediaMetadata); | |||||
const callSendMultimediaMessage = useServerCall(sendMultimediaMessage); | const callSendMultimediaMessage = useServerCall(sendMultimediaMessage); | ||||
const callSendTextMessage = useServerCall(sendTextMessage); | const callSendTextMessage = useServerCall(sendTextMessage); | ||||
const callNewThread = useServerCall(newThread); | const callNewThread = useServerCall(newThread); | ||||
const dispatchActionPromise = useDispatchActionPromise(); | const dispatchActionPromise = useDispatchActionPromise(); | ||||
const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
const mediaReportsEnabled = useIsReportEnabled('mediaReports'); | const mediaReportsEnabled = useIsReportEnabled('mediaReports'); | ||||
const staffCanSee = useStaffCanSee(); | const staffCanSee = useStaffCanSee(); | ||||
const textMessageCreationSideEffectsFunc = | const textMessageCreationSideEffectsFunc = | ||||
useMessageCreationSideEffectsFunc(messageTypes.TEXT); | useMessageCreationSideEffectsFunc(messageTypes.TEXT); | ||||
return ( | return ( | ||||
<InputStateContainer | <InputStateContainer | ||||
{...props} | {...props} | ||||
uploadMediaMetadata={callUploadMediaMetadata} | |||||
viewerID={viewerID} | viewerID={viewerID} | ||||
nextLocalID={nextLocalID} | nextLocalID={nextLocalID} | ||||
messageStoreMessages={messageStoreMessages} | messageStoreMessages={messageStoreMessages} | ||||
ongoingMessageCreation={ongoingMessageCreation} | ongoingMessageCreation={ongoingMessageCreation} | ||||
hasWiFi={hasWiFi} | hasWiFi={hasWiFi} | ||||
mediaReportsEnabled={mediaReportsEnabled} | mediaReportsEnabled={mediaReportsEnabled} | ||||
calendarQuery={calendarQuery} | calendarQuery={calendarQuery} | ||||
uploadMultimedia={callUploadMultimedia} | uploadMultimedia={callUploadMultimedia} | ||||
Show All 12 Lines |