Skip to content

feat(storageState): add OPFS support#41420

Draft
gwennlbh wants to merge 1 commit into
microsoft:mainfrom
gwennlbh:storagestate-opfs
Draft

feat(storageState): add OPFS support#41420
gwennlbh wants to merge 1 commit into
microsoft:mainfrom
gwennlbh:storagestate-opfs

Conversation

@gwennlbh

Copy link
Copy Markdown
Contributor

Closes #41400

@gwennlbh gwennlbh force-pushed the storagestate-opfs branch from 6f6684c to 18b71cc Compare June 23, 2026 08:53
for (const [name, entry] of tree) {
if (!Array.isArray(entry)) {
const handle = await base.getFileHandle(name, { create: true });
const writable = await handle.createWritable();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FileSystemFileHandle#createWritable is only Baseline 2025... but the alternative (createSyncAccessHandle()) only works inside Web Workers...

is the storageScript running in a worker or not? if it isn't we might wanna document that this won't work on older browsers.

afaik there's no other way to write to OPFS unfortunately

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it does work:

image

but it's only baseline 2025, that's what I was saying. The aternative is createSyncAccessHandle, but it needs to run in web workers so i was wondering if the storage script

  • already runs in a web worker
  • would be feasible to move into a web worker
  • or feasible to move just the opfs handling in a web worker
  • or if baseline 2025 is acceptable, as this doesn't break anything else (we just have to document that the opfs option for (set)StorageState is only supported on baseline >=2025 browsers)

@gwennlbh gwennlbh left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'll wait for an answer on the comment i added since if baseline 2025 is not acceptable and neither is putting the script in a web worker, i don't wanna spend time on writing tests 😅

Comment on lines +46 to +49
type OPFSTree = Array<
[name: string, contents: Extract<SerializedValue, {f: any}> | OPFSTree]
>;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a Record (object) cuz otherwise differenciation between leaves and branches (files and directories) wouldnt be as easy

}

// Getting a File object's contents requires async
export async function serializeFile(value: File): Promise<Extract<SerializedValue, { f: any; }>> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is kinda ugly but I don't see any other way to do it, except making serialzeAsCallArgument async which would be a whole other can of worms

for (const [name, entry] of tree) {
if (!Array.isArray(entry)) {
const handle = await base.getFileHandle(name, { create: true });
const writable = await handle.createWritable();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

};

type OPFSTree = Array<
[name: string, contents: Extract<SerializedValue, {f: any}> | OPFSTree]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use conservative types:

type FSEntry {
  type: 'file' | 'folder';
  name: string;
  lastModified: number;
}

type File = FSEntry & {
  type: 'file';
  base64: string;
}

type Folder = FSEntry & {
  type: 'folder';
  entries: (File | Folder)[];
}

{ ta: { b: string, k: TypedArrayKind } } |
{ ab: { b: string } };
{ ab: { b: string } } |
{ f: { b: string, n: string, t: string, m: number } };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to serialize and restore opfs without this.

return base64ToTypedArray(value.ta.b, typedArrayConstructors[value.ta.k]);
if ('ab' in value)
return base64ToTypedArray(value.ab.b, Uint8Array).buffer;
if ('f' in value) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto


async storageState(params: channels.BrowserContextStorageStateParams, progress: Progress): Promise<channels.BrowserContextStorageStateResult> {
return await this._context.storageState(progress, params.indexedDB, params.credentials);
return await this._context.storageState(progress, params.indexedDB, params.credentials, params.opfs);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pass params as object

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Capture OPFS in storageState

2 participants