Page MenuHomePhabricator

D7689.diff
No OneTemporary

D7689.diff

diff --git a/web/input/input-state-container.react.js b/web/input/input-state-container.react.js
--- a/web/input/input-state-container.react.js
+++ b/web/input/input-state-container.react.js
@@ -8,9 +8,11 @@
import _partition from 'lodash/fp/partition.js';
import _sortBy from 'lodash/fp/sortBy.js';
import _memoize from 'lodash/memoize.js';
+import _throttle from 'lodash/throttle.js';
import * as React from 'react';
import { useDispatch } from 'react-redux';
import { createSelector } from 'reselect';
+import uuid from 'uuid';
import {
createLocalMessageActionType,
@@ -23,6 +25,7 @@
import { newThread } from 'lib/actions/thread-actions.js';
import {
uploadMultimedia,
+ uploadMediaMetadata,
updateMultimediaMessageMediaActionType,
deleteUpload,
type MultimediaUploadCallbacks,
@@ -32,6 +35,7 @@
useModalContext,
type PushModal,
} from 'lib/components/modal-provider.react.js';
+import blobService from 'lib/facts/blob-service.js';
import commStaffCommunity from 'lib/facts/comm-staff-community.js';
import { getNextLocalUploadID } from 'lib/media/media-utils.js';
import { pendingToRealizedThreadIDsSelector } from 'lib/selectors/thread-selectors.js';
@@ -52,10 +56,12 @@
import type { CalendarQuery } from 'lib/types/entry-types.js';
import type {
UploadMultimediaResult,
+ UploadMediaMetadataRequest,
MediaMissionStep,
MediaMissionFailure,
MediaMissionResult,
MediaMission,
+ Dimensions,
} from 'lib/types/media-types.js';
import { messageTypes } from 'lib/types/message-types-enum.js';
import {
@@ -81,6 +87,9 @@
useServerCall,
useDispatchActionPromise,
} from 'lib/utils/action-utils.js';
+import { toBase64URL } from 'lib/utils/base64.js';
+import { makeBlobServiceEndpointURL } from 'lib/utils/blob-service.js';
+import type { CallServerEndpointOptions } from 'lib/utils/call-server-endpoint.js';
import { getConfig } from 'lib/utils/config.js';
import { getMessageForException, cloneError } from 'lib/utils/errors.js';
@@ -119,6 +128,9 @@
extras: MultimediaUploadExtras,
callbacks: MultimediaUploadCallbacks,
) => Promise<UploadMultimediaResult>,
+ +uploadMediaMetadata: (
+ input: UploadMediaMetadataRequest,
+ ) => Promise<UploadMultimediaResult>,
+deleteUpload: (id: string) => Promise<void>,
+sendMultimediaMessage: (
threadID: string,
@@ -992,6 +1004,123 @@
});
}
+ async blobServiceUpload(
+ input: {
+ file: File,
+ 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) {
+ const formData = new FormData();
+ formData.append('blob_hash', blobHash);
+ formData.append('blob_data', input.file);
+
+ const xhr = new XMLHttpRequest();
+ const uploadEndpoint = blobService.httpEndpoints.UPLOAD_BLOB;
+ xhr.open(
+ uploadEndpoint.method,
+ makeBlobServiceEndpointURL(uploadEndpoint),
+ );
+ if (options?.timeout) {
+ xhr.timeout = options.timeout;
+ }
+ if (options && options.onProgress) {
+ const { onProgress } = options;
+ xhr.upload.onprogress = _throttle(
+ ({ loaded, total }) => onProgress(loaded / total),
+ 50,
+ );
+ }
+
+ let failed = false;
+ const responsePromise = new Promise((resolve, reject) => {
+ xhr.onload = () => {
+ if (failed) {
+ return;
+ }
+ resolve();
+ };
+ xhr.onabort = () => {
+ failed = true;
+ reject(new Error('request aborted'));
+ };
+ xhr.onerror = event => {
+ failed = true;
+ reject(event);
+ };
+ if (options && options.timeout) {
+ xhr.ontimeout = event => {
+ failed = true;
+ reject(event);
+ };
+ }
+ if (options && options.abortHandler) {
+ options.abortHandler(() => {
+ failed = true;
+ reject(new Error('request aborted'));
+ xhr.abort();
+ });
+ }
+ });
+
+ if (!failed) {
+ xhr.send(formData);
+ }
+ await responsePromise;
+ }
+
+ // 3. Send upload metadata to the keyserver, return response
+ return await this.props.uploadMediaMetadata({
+ ...input.dimensions,
+ loop: input.loop ?? false,
+ blobHolder: newHolder,
+ encryptionKey: input.encryptionKey,
+ mimeType: input.file.type,
+ filename: input.file.name,
+ });
+ }
+
handleAbortCallback(
threadID: string,
localUploadID: string,
@@ -1521,6 +1650,7 @@
);
const calendarQuery = useSelector(nonThreadCalendarQuery);
const callUploadMultimedia = useServerCall(uploadMultimedia);
+ const callUploadMediaMetadata = useServerCall(uploadMediaMetadata);
const callDeleteUpload = useServerCall(deleteUpload);
const callSendMultimediaMessage = useServerCall(
legacySendMultimediaMessage,
@@ -1559,6 +1689,7 @@
pendingRealizedThreadIDs={pendingToRealizedThreadIDs}
calendarQuery={calendarQuery}
uploadMultimedia={callUploadMultimedia}
+ uploadMediaMetadata={callUploadMediaMetadata}
deleteUpload={callDeleteUpload}
sendMultimediaMessage={callSendMultimediaMessage}
sendTextMessage={callSendTextMessage}

File Metadata

Mime Type
text/plain
Expires
Thu, Nov 28, 5:02 AM (21 h, 5 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2592835
Default Alt Text
D7689.diff (6 KB)

Event Timeline