Skip to content

Instantly share code, notes, and snippets.

@reold
Last active April 16, 2025 11:59
Show Gist options
  • Save reold/86a64385efd4ec10674f4744da655505 to your computer and use it in GitHub Desktop.
Save reold/86a64385efd4ec10674f4744da655505 to your computer and use it in GitHub Desktop.
Origin Private File System for all browsers. Pollyfill and an Abstraction Layer

📁 reold-opfs

A robust abstraction layer for the Origin Private File System (OPFS) API, allowing seamless file operations across all browsers. This package resolves Safari's limitations with OPFS, providing a consistent experience.

To learn more, watch this quick video:

thumbnail of a video about OPFS

📦 Installation

Install reold-opfs using npm:

npm install gist:86a64385efd4ec10674f4744da655505

💡 Usage/Examples

This package provides a simple API for interacting with OPFS using a worker thread. Use the useOPFS object for file operations. Here is how you can set up and use it:

🛠️ Initialize the Worker

import fsWorker from "reold-opfs/opfsWorker.ts?worker";
import { useOPFS } from "reold-opfs/opfs";

// Set the worker instance
useOPFS.setWorker(new fsWorker());

// Optionally enable debug mode
useOPFS.setDebug(true);

✍️ Writing to a File

await useOPFS.write("hello.txt", "Hello, world!");
console.info("File written successfully");

API

useOPFS.setWorker(workerInstance: Worker): void

Sets the intermediary worker that performs OPFS operations.

  • workerInstance: An instance of the opfsWorker to handle file system operations.

useOPFS.setDebug(showDebug: boolean = false): void

Enables or disables debug messages for communication between the main and worker threads.

  • showDebug: A boolean value to toggle debug messages.

useOPFS.write(path: string, content: string | Blob): Promise<void>

Writes content to a file in OPFS.

  • path: The path to the file in OPFS (e.g., "documents/example.txt").
  • content: The content to write to the file, which can be a string or a Blob.
  • Returns: A promise that resolves when the write operation is complete.

💬 Feedback

If you have any feedback, feel free to reach out to me via my socials or email. ✨


Originally designed for Cartier Manager.

/**
* Read/Write directly to OPFS using an intermediary worker.
*
* Use the `setWorker` method by passing an `opfsWorker` instance.
* Then, methods like `write` become available for file operations.
*/
export const useOPFS = (() => {
let worker: Worker;
let debug: boolean = false;
return {
/**
* Set the intermediary worker.
* @param workerInstance The `opfsWorker` instance to perform OPFS operations as worker.
*/
setWorker: (workerInstance: Worker) => {
worker = workerInstance;
},
/**
* Enable or disable debug logging.
* Logs communication between the main thread and worker for troubleshooting.
* @param showDebug Whether to show debug messages through console.log (default: false).
*/
setDebug: (showDebug: boolean = false) => {
debug = showDebug;
},
/**
* Write `content` to a file at `path`.
* @param {string} path Path to the file in OPFS to write to (e.g., "documents/hello.pdf").
* @param {string | Blob} content The content to write to the file. It can be a string or Blob.
* @return {Promise<void>} A promise that resolves when the write operation is complete.
* @throws Will throw an error if the worker is not set or if the operation fails.
*/
write: async (path: string, content: string | Blob): Promise<void> => {
if (!worker) {
throw new Error("[opfsWorker/opfs.ts]: Worker not set. Call setWorker() first.");
}
const id = Math.random();
worker.postMessage({
mode: "write",
path,
content,
id,
});
return new Promise<void>((resolve, reject) => {
worker.addEventListener("message", (event) => {
if (event.data === id) {
debug
? console.log(`[opfsWorker/opfs.ts]: ${id} - Write operation completed successfully.`)
: undefined;
resolve();
}
});
});
},
};
})();
const getHandleFromPath: any = async (
base: FileSystemDirectoryHandle,
path: string
) => {
if (path.includes("/")) {
let dirName = path.split("/")[0];
return await getHandleFromPath(
await base.getDirectoryHandle(dirName),
path.slice(dirName.length + 1)
);
} else {
return await base.getFileHandle(path, { create: true });
}
};
self.onmessage = async (
event: MessageEvent<{
mode: string;
path: string;
content: string | Blob;
id: string;
}>
) => {
let { mode, path, content: src_content, id } = event.data;
let content;
if (typeof src_content == "string") {
const textEncoder = new TextEncoder();
content = textEncoder.encode(src_content);
} else if (src_content instanceof Blob) {
content = await src_content.arrayBuffer();
}
let root = await navigator.storage.getDirectory();
let handle: FileSystemFileHandle = await getHandleFromPath(root, path);
switch (mode) {
case "write":
const syncHandle = await handle.createSyncAccessHandle();
syncHandle.truncate(0);
syncHandle.write(content);
syncHandle.flush();
syncHandle.close();
break;
default:
break;
}
self.postMessage(id);
};
export {};
{
"version": "0.1.1",
"name": "reold-opfs",
"main": "opfs.ts"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment