Page MenuHomePhorge

D15319.1765041698.diff
No OneTemporary

Size
9 KB
Referenced Files
None
Subscribers
None

D15319.1765041698.diff

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 UploadFarcasterMediaResponse = {
+ +mediaID: string,
+ +blobHash: string,
+ +contentType: ?string,
+ +metadata: ?string,
+};
+export const uploadFarcasterMediaResponseValidator: TInterface<UploadFarcasterMediaResponse> =
+ tShape<UploadFarcasterMediaResponse>({
+ 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 { UploadFarcasterMediaResponse } from '../types/blob-service-types.js';
+import { uploadFarcasterMediaResponseValidator } from '../types/blob-service-types.js';
function blobServiceUploadHandler(
url: string,
@@ -90,6 +94,106 @@
return responsePromise;
}
+async function farcasterMediaUploadHandler(
+ url: string,
+ input: BlobServiceUploadFile,
+ authMetadata: AuthMetadata,
+ customMetadata: ?string,
+ options: MultimediaUploadCallbacks,
+): Promise<UploadFarcasterMediaResponse> {
+ 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<UploadFarcasterMediaResponse>(
+ (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,
+ uploadFarcasterMediaResponseValidator,
+ );
+ 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 FarcasterMediaUploadHandler = typeof farcasterMediaUploadHandler;
-export { blobServiceUploadHandler };
+export { blobServiceUploadHandler, farcasterMediaUploadHandler };
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 { uploadFarcasterMediaResponseValidator } from 'lib/types/blob-service-types.js';
+import type { UploadFarcasterMediaResponse } 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 farcasterMediaUploadHandler(
+ url: string,
+ input: BlobServiceUploadFile,
+ authMetadata: AuthMetadata,
+ customMetadata: ?string,
+ options: MultimediaUploadCallbacks,
+): Promise<UploadFarcasterMediaResponse> {
+ 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),
+ uploadFarcasterMediaResponseValidator,
+ );
+ return response;
+ } catch (e) {
+ throw new Error(
+ `Failed to upload farcaster media: ${getMessageForException(e) ?? 'unknown error'}`,
+ );
+ }
+}
+
+export { blobServiceUploadHandler, farcasterMediaUploadHandler };

File Metadata

Mime Type
text/plain
Expires
Sat, Dec 6, 5:21 PM (23 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5839760
Default Alt Text
D15319.1765041698.diff (9 KB)

Event Timeline