OPFS(Origin Private File System)を使うとブラウザの中だけでストレージを利用でき、ローカルの開発環境でのみ実際に画像のアップロードをしたくないケースなどでいい感じに使える。
実装にはmemfsを使う。memfsにはcrudfsというOPFS(a.k.a File System Access API)のCRUDラッパーが実装されている。さらに、それをget/putだけに抽象化したcasfsというラッパーも用意されており、これを使うとファイルストレージ的にOPFSを扱うにあたっては非常に便利。
以下が実装例。
import { FsaCrud } from "memfs/lib/fsa-to-crud";
import { CrudCas } from "memfs/lib/crud-to-cas";
import { IFileSystemDirectoryHandle } from "memfs/lib/fsa/types";
import uuid4 from "uuid4";
class InBrowserImageStorage {
async uploadImage(file: File): Promise<string> {
const buffer = await file.arrayBuffer();
const manager = await this.getOPFSManager();
return await manager.put(Buffer.from(buffer));
}
async getImage(fileName: string): Promise<string> {
const manager = await this.getOPFSManager();
const file = await manager.get(fileName);
const blob = new Blob([file]);
return URL.createObjectURL(blob);
}
private async getOPFSManager() {
// ここの型はいい感じにできないものか...
const dir =
(await navigator.storage.getDirectory()) as unknown as IFileSystemDirectoryHandle;
const crud = new FsaCrud(dir);
return new CrudCas(crud, {
async hash(blob: Uint8Array): Promise<string> {
return createHash("sha1").update(blob).digest("hex");
},
});
}
}
あとはinterfaceで抽象化して、実際のアップロードを行う実装と NODE_ENV
かなんかで切り替えればよさそう。
interface ImageStorage {
uploadImage(file: File): Promise<string>;
getImage(fileName: string): Promise<string>;
}
class InBrowserImageStorage implements ImageStorage {
// ...
}
class RemoteImageStorage implements ImageStorage {
// ...
}
export const imageStorage: ImageStorage =
NODE_ENV === "production"
? new RemoteImageStorage()
: new InBrowserImageStorage()