From 6b3397bd9fa9ceb52fd8f02e8630c036bd0d49f1 Mon Sep 17 00:00:00 2001 From: J Date: Fri, 26 Feb 2021 20:18:40 +0000 Subject: [PATCH] interface: supports both S3 and GCP Storage The S3 client has another layer of indirection we missed. To support it expediently, we just make the promise() method on GcpUpload do all the work in GcpClient. --- pkg/interface/src/logic/lib/GcpClient.ts | 27 +++++++++++++++++--- pkg/interface/src/logic/lib/StorageClient.ts | 9 +++++-- pkg/interface/src/logic/lib/useStorage.ts | 27 ++++++++------------ 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/pkg/interface/src/logic/lib/GcpClient.ts b/pkg/interface/src/logic/lib/GcpClient.ts index d5060ccb88..f071896979 100644 --- a/pkg/interface/src/logic/lib/GcpClient.ts +++ b/pkg/interface/src/logic/lib/GcpClient.ts @@ -1,9 +1,15 @@ // Very simple GCP Storage client. // +// It's designed to match a subset of the S3 client upload API. The upload +// function on S3 returns a ManagedUpload, which has a promise() method on +// it. We don't care about any of the other methods on ManagedUpload, so we +// just do the work in its promise() method. +// import querystring from 'querystring'; import { StorageAcl, StorageClient, + StorageUpload, UploadParams, UploadResult } from './StorageClient'; @@ -11,14 +17,17 @@ import { const ENDPOINT = 'storage.googleapis.com'; -export default class GcpClient implements StorageClient { +class GcpUpload implements StorageUpload { + #params: UploadParams; #accessKey: string; - constructor(accessKey: string) { + constructor(params: UploadParams, accessKey: string) { + this.#params = params; this.#accessKey = accessKey; } - async upload({Bucket, Key, ContentType, Body}: UploadParams): UploadResult { + async promise(): UploadResult { + const {Bucket, Key, ContentType, Body} = this.#params; const urlParams = { uploadType: 'media', name: Key, @@ -42,3 +51,15 @@ export default class GcpClient implements StorageClient { return {Location: `https://${ENDPOINT}/${Bucket}/${Key}`}; } } + +export default class GcpClient implements StorageClient { + #accessKey: string; + + constructor(accessKey: string) { + this.#accessKey = accessKey; + } + + upload(params: UploadParams): StorageUpload { + return new GcpUpload(params, this.#accessKey); + } +} diff --git a/pkg/interface/src/logic/lib/StorageClient.ts b/pkg/interface/src/logic/lib/StorageClient.ts index 16cc79087a..31e12f8233 100644 --- a/pkg/interface/src/logic/lib/StorageClient.ts +++ b/pkg/interface/src/logic/lib/StorageClient.ts @@ -22,6 +22,11 @@ export interface UploadResult { Location: string; }; -export interface StorageClient { - upload(params: UploadParams): Promise; +// Extra layer of indirection used by S3 client. +export interface StorageUpload { + promise(): Promise; +}; + +export interface StorageClient { + upload(params: UploadParams): StorageUpload; }; diff --git a/pkg/interface/src/logic/lib/useStorage.ts b/pkg/interface/src/logic/lib/useStorage.ts index a011c17275..2fd01932e2 100644 --- a/pkg/interface/src/logic/lib/useStorage.ts +++ b/pkg/interface/src/logic/lib/useStorage.ts @@ -19,37 +19,32 @@ const useStorage = (s3: S3State, gcp: GcpState, { accept = '*' } = { accept: '*' }): IuseStorage => { const [uploading, setUploading] = useState(false); - const gcpClient = useRef(null); - const s3Client = useRef(null); + const client = useRef(null); useEffect(() => { // prefer GCP if available, else use S3. if (gcp.accessKey) { - gcpClient.current = new GcpClient(gcp.accessKey); - s3Client.current = null; + client.current = new GcpClient(gcp.accessKey); } else { if (!s3.credentials) { return; } - s3Client.current = new S3({ + client.current = new S3({ credentials: s3.credentials, endpoint: s3.credentials.endpoint }); - gcpClient.current = null; } }, [gcp.accessKey, s3.credentials]); const canUpload = useMemo( () => - ((gcpClient || s3Client) && s3.configuration.currentBucket !== "") || false, - [gcpClient, s3Client, s3.configuration.currentBucket] + (client && s3.configuration.currentBucket !== "") || false, + [client, s3.configuration.currentBucket] ); const upload = useCallback( async (file: File, bucket: string) => { - const client: StorageClient | null = - gcpClient.current || s3Client.current; - if (!client) { + if (client.current === null) { throw new Error("Storage not ready"); } @@ -68,21 +63,21 @@ const useStorage = (s3: S3State, gcp: GcpState, setUploading(true); - const { Location } = await client.upload(params); + const { Location } = await client.current.upload(params).promise(); setUploading(false); return Location; }, - [gcpClient, s3Client, setUploading] + [client, setUploading] ); const uploadDefault = useCallback(async (file: File) => { - if (s3.configuration.currentBucket == '') { - throw new Error('current bucket not set'); + if (s3.configuration.currentBucket === '') { + throw new Error("current bucket not set"); } return upload(file, s3.configuration.currentBucket); - }, [s3.configuration, upload]); + }, [s3, upload]); const promptUpload = useCallback( () => {