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.
This commit is contained in:
J 2021-02-26 20:18:40 +00:00
parent 6ec574d32b
commit 6b3397bd9f
3 changed files with 42 additions and 21 deletions

View File

@ -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);
}
}

View File

@ -22,6 +22,11 @@ export interface UploadResult {
Location: string;
};
export interface StorageClient {
upload(params: UploadParams): Promise<UploadResult>;
// Extra layer of indirection used by S3 client.
export interface StorageUpload {
promise(): Promise<UploadResult>;
};
export interface StorageClient {
upload(params: UploadParams): StorageUpload;
};

View File

@ -19,37 +19,32 @@ const useStorage = (s3: S3State, gcp: GcpState,
{ accept = '*' } = { accept: '*' }): IuseStorage => {
const [uploading, setUploading] = useState(false);
const gcpClient = useRef<GcpClient | null>(null);
const s3Client = useRef<S3Client | null>(null);
const client = useRef<StorageClient | null>(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(
() => {