Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32164266
D15319.1765047947.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
D15319.1765047947.diff
View Options
diff --git a/lib/facts/blob-service.js b/lib/facts/blob-service.js
--- a/lib/facts/blob-service.js
+++ b/lib/facts/blob-service.js
@@ -9,6 +9,8 @@
+UPLOAD_BLOB: { +path: '/blob', +method: 'PUT' },
+DELETE_BLOB: { +path: '/blob', +method: 'DELETE' },
+REMOVE_MULTIPLE_HOLDERS: { +path: '/holders', +method: 'DELETE' },
+ +GET_MEDIA: { +path: '/media/:mediaID', +method: 'GET' },
+ +UPLOAD_MEDIA: { +path: '/media', +method: 'POST' },
};
export type BlobServiceHTTPEndpoint =
@@ -44,6 +46,8 @@
path: '/holders',
method: 'DELETE',
},
+ GET_MEDIA: { path: '/media/:mediaID', method: 'GET' },
+ UPLOAD_MEDIA: { path: '/media', method: 'POST' },
});
const config: BlobServiceConfig = {
diff --git a/lib/types/blob-service-types.js b/lib/types/blob-service-types.js
--- a/lib/types/blob-service-types.js
+++ b/lib/types/blob-service-types.js
@@ -76,3 +76,17 @@
tShape<RemoveHoldersResponse>({
failedRequests: t.list(blobInfoValidator),
});
+
+export type UploadPlaintextMediaResponse = {
+ +mediaID: string,
+ +blobHash: string,
+ +contentType: ?string,
+ +metadata: ?string,
+};
+export const uploadPlaintextMediaResponseValidator: TInterface<UploadPlaintextMediaResponse> =
+ tShape<UploadPlaintextMediaResponse>({
+ mediaID: t.String,
+ blobHash: t.String,
+ contentType: t.maybe(t.String),
+ metadata: t.maybe(t.String),
+ });
diff --git a/lib/utils/blob-service-upload.js b/lib/utils/blob-service-upload.js
--- a/lib/utils/blob-service-upload.js
+++ b/lib/utils/blob-service-upload.js
@@ -3,12 +3,16 @@
import invariant from 'invariant';
import _throttle from 'lodash/throttle.js';
+import { getMessageForException } from './errors.js';
import { createHTTPAuthorizationHeader } from './services-utils.js';
+import { assertWithValidator } from './validation-utils.js';
import type {
MultimediaUploadCallbacks,
BlobServiceUploadFile,
} from '../actions/upload-actions.js';
import type { AuthMetadata } from '../shared/identity-client-context.js';
+import type { UploadPlaintextMediaResponse } from '../types/blob-service-types.js';
+import { uploadPlaintextMediaResponseValidator } from '../types/blob-service-types.js';
function blobServiceUploadHandler(
url: string,
@@ -90,6 +94,106 @@
return responsePromise;
}
+async function plaintextMediaUploadHandler(
+ url: string,
+ input: BlobServiceUploadFile,
+ authMetadata: AuthMetadata,
+ customMetadata: ?string,
+ options: MultimediaUploadCallbacks,
+): Promise<UploadPlaintextMediaResponse> {
+ if (input.type !== 'file') {
+ throw new Error('Use file to upload blob to blob service!');
+ }
+ invariant(input.file, 'file should be defined');
+
+ const formData = new FormData();
+ if (customMetadata) {
+ formData.append('metadata', customMetadata);
+ }
+ formData.append('mime_type', input.file.type);
+ formData.append('file', input.file);
+
+ const xhr = new XMLHttpRequest();
+ xhr.responseType = 'json';
+ xhr.open('POST', url);
+
+ const authHeader = createHTTPAuthorizationHeader(authMetadata);
+ xhr.setRequestHeader('Authorization', authHeader);
+
+ const { timeout, onProgress, abortHandler } = options ?? {};
+
+ if (timeout) {
+ xhr.timeout = timeout;
+ }
+
+ if (onProgress) {
+ xhr.upload.onprogress = _throttle(
+ ({ loaded, total }) => onProgress(loaded / total),
+ 50,
+ );
+ }
+
+ let failed = false;
+ const responsePromise = new Promise<UploadPlaintextMediaResponse>(
+ (resolve, reject) => {
+ xhr.onload = () => {
+ if (failed) {
+ return;
+ }
+ if (xhr.status === 401 || xhr.status === 403) {
+ failed = true;
+ reject(new Error('invalid_csat'));
+ return;
+ }
+
+ try {
+ const rawResponse =
+ typeof xhr.response === 'string'
+ ? JSON.parse(xhr.responseText)
+ : xhr.response;
+ const response = assertWithValidator(
+ rawResponse,
+ uploadPlaintextMediaResponseValidator,
+ );
+ resolve(response);
+ } catch (e) {
+ reject(
+ `Invalid response: ${getMessageForException(e) ?? 'unknown error'}`,
+ );
+ }
+ };
+ xhr.onabort = () => {
+ failed = true;
+ reject(new Error('request aborted'));
+ };
+ xhr.onerror = event => {
+ failed = true;
+ reject(event);
+ };
+ if (timeout) {
+ xhr.ontimeout = event => {
+ failed = true;
+ reject(event);
+ };
+ }
+ if (abortHandler) {
+ abortHandler(() => {
+ failed = true;
+ reject(new Error('request aborted'));
+ xhr.abort();
+ });
+ }
+ },
+ );
+
+ if (!failed) {
+ xhr.send(formData);
+ }
+
+ return responsePromise;
+}
+
export type BlobServiceUploadHandler = typeof blobServiceUploadHandler;
+export type PlaintextMediaUploadHandler = typeof plaintextMediaUploadHandler;
-export { blobServiceUploadHandler };
+export { blobServiceUploadHandler, plaintextMediaUploadHandler };
diff --git a/native/avatars/avatar-hooks.js b/native/avatars/avatar-hooks.js
--- a/native/avatars/avatar-hooks.js
+++ b/native/avatars/avatar-hooks.js
@@ -37,7 +37,7 @@
import { useSelector } from '../redux/redux-utils.js';
import { useStyles } from '../themes/colors.js';
import Alert from '../utils/alert.js';
-import blobServiceUploadHandler from '../utils/blob-service-upload.js';
+import { blobServiceUploadHandler } from '../utils/blob-service-upload.js';
import { useStaffCanSee } from '../utils/staff-utils.js';
function displayAvatarUpdateFailureAlert(): void {
diff --git a/native/input/input-state-container.react.js b/native/input/input-state-container.react.js
--- a/native/input/input-state-container.react.js
+++ b/native/input/input-state-container.react.js
@@ -114,7 +114,7 @@
import { displayActionResultModal } from '../navigation/action-result-modal.js';
import { useCalendarQuery } from '../navigation/nav-selectors.js';
import { useSelector } from '../redux/redux-utils.js';
-import blobServiceUploadHandler from '../utils/blob-service-upload.js';
+import { blobServiceUploadHandler } from '../utils/blob-service-upload.js';
import { useStaffCanSee } from '../utils/staff-utils.js';
type MediaIDs =
diff --git a/native/utils/blob-service-upload.js b/native/utils/blob-service-upload.js
--- a/native/utils/blob-service-upload.js
+++ b/native/utils/blob-service-upload.js
@@ -3,10 +3,18 @@
import * as FileSystem from 'expo-file-system';
import { Platform } from 'react-native';
+import type {
+ BlobServiceUploadFile,
+ MultimediaUploadCallbacks,
+} from 'lib/actions/upload-actions.js';
import { pathFromURI } from 'lib/media/file-utils.js';
+import type { AuthMetadata } from 'lib/shared/identity-client-context.js';
+import { uploadPlaintextMediaResponseValidator } from 'lib/types/blob-service-types.js';
+import type { UploadPlaintextMediaResponse } from 'lib/types/blob-service-types.js';
import type { BlobServiceUploadHandler } from 'lib/utils/blob-service-upload.js';
import { getMessageForException } from 'lib/utils/errors.js';
import { createDefaultHTTPRequestHeaders } from 'lib/utils/services-utils.js';
+import { assertWithValidator } from 'lib/utils/validation-utils.js';
const blobServiceUploadHandler: BlobServiceUploadHandler = async (
url,
@@ -68,4 +76,64 @@
}
};
-export default blobServiceUploadHandler;
+async function plaintextMediaUploadHandler(
+ url: string,
+ input: BlobServiceUploadFile,
+ authMetadata: AuthMetadata,
+ customMetadata: ?string,
+ options: MultimediaUploadCallbacks,
+): Promise<UploadPlaintextMediaResponse> {
+ if (input.type !== 'uri') {
+ throw new Error('Wrong blob data type');
+ }
+
+ let path = input.uri;
+ if (Platform.OS === 'android') {
+ const resolvedPath = pathFromURI(path);
+ if (resolvedPath) {
+ path = resolvedPath;
+ }
+ }
+
+ const headers = authMetadata && createDefaultHTTPRequestHeaders(authMetadata);
+ const optionalParams = customMetadata ? { metadata: customMetadata } : {};
+ const uploadOptions = {
+ uploadType: FileSystem.FileSystemUploadType.MULTIPART,
+ fieldName: 'file',
+ httpMethod: 'POST',
+ parameters: {
+ ...optionalParams,
+ mime_type: input.mimeType,
+ },
+ headers,
+ };
+
+ const uploadTask = FileSystem.createUploadTask(
+ url,
+ path,
+ uploadOptions,
+ uploadProgress => {
+ if (options?.onProgress) {
+ const { totalBytesSent, totalBytesExpectedToSend } = uploadProgress;
+ options.onProgress(totalBytesSent / totalBytesExpectedToSend);
+ }
+ },
+ );
+ if (options?.abortHandler) {
+ options.abortHandler(() => uploadTask.cancelAsync());
+ }
+ try {
+ const result = await uploadTask.uploadAsync();
+ const response = assertWithValidator(
+ JSON.parse(result.body),
+ uploadPlaintextMediaResponseValidator,
+ );
+ return response;
+ } catch (e) {
+ throw new Error(
+ `Failed to upload farcaster media: ${getMessageForException(e) ?? 'unknown error'}`,
+ );
+ }
+}
+
+export { blobServiceUploadHandler, plaintextMediaUploadHandler };
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Dec 6, 7:05 PM (22 h, 7 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5840296
Default Alt Text
D15319.1765047947.diff (9 KB)
Attached To
Mode
D15319: [lib][native] Add functions to upload FC mutimedia to Blob Service
Attached
Detach File
Event Timeline
Log In to Comment