@statewalker/webrun-files-browser 0.5.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -98
- package/dist/browser-files-api.d.ts +19 -47
- package/dist/browser-files-api.d.ts.map +1 -1
- package/dist/cjs/index.cjs +161 -211
- package/dist/esm/index.js +149 -201
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/open-browser-files-api.d.ts +67 -0
- package/dist/open-browser-files-api.d.ts.map +1 -0
- package/package.json +15 -14
- package/src/browser-files-api.ts +123 -103
- package/src/index.ts +7 -4
- package/src/open-browser-files-api.ts +148 -0
- package/LICENSE +0 -21
- package/dist/browser-file-handle.d.ts +0 -42
- package/dist/browser-file-handle.d.ts.map +0 -1
- package/src/browser-file-handle.ts +0 -194
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @statewalker/webrun-files-browser
|
|
2
2
|
|
|
3
|
-
Browser File System Access API implementation
|
|
3
|
+
Browser File System Access API implementation of the `FilesApi` interface from `@statewalker/webrun-files`.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
This package provides
|
|
7
|
+
This package provides a `FilesApi` implementation that works in modern browsers using the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API). It allows web applications to read and write files to:
|
|
8
8
|
|
|
9
9
|
- **User-selected directories** via `showDirectoryPicker()`
|
|
10
10
|
- **Origin Private File System (OPFS)** - a sandboxed filesystem that persists between sessions
|
|
@@ -20,100 +20,95 @@ npm install @statewalker/webrun-files-browser @statewalker/webrun-files
|
|
|
20
20
|
### With User-Selected Directory
|
|
21
21
|
|
|
22
22
|
```typescript
|
|
23
|
-
import { openBrowserFilesApi } from
|
|
24
|
-
import {
|
|
23
|
+
import { openBrowserFilesApi } from '@statewalker/webrun-files-browser';
|
|
24
|
+
import { readText, writeText } from '@statewalker/webrun-files';
|
|
25
25
|
|
|
26
26
|
// Opens a directory picker dialog (requires user gesture)
|
|
27
|
-
const
|
|
28
|
-
const api = new FilesApi(browserFs);
|
|
27
|
+
const files = await openBrowserFilesApi();
|
|
29
28
|
|
|
30
29
|
// Write a file
|
|
31
|
-
await
|
|
32
|
-
new TextEncoder().encode("Hello, World!")
|
|
33
|
-
]);
|
|
30
|
+
await writeText(files, '/notes/hello.txt', 'Hello, World!');
|
|
34
31
|
|
|
35
32
|
// Read a file
|
|
36
|
-
const content = await
|
|
37
|
-
console.log(
|
|
33
|
+
const content = await readText(files, '/notes/hello.txt');
|
|
34
|
+
console.log(content); // "Hello, World!"
|
|
38
35
|
|
|
39
36
|
// List directory contents
|
|
40
|
-
for await (const entry of
|
|
37
|
+
for await (const entry of files.list('/notes')) {
|
|
41
38
|
console.log(entry.name, entry.kind);
|
|
42
39
|
}
|
|
43
40
|
```
|
|
44
41
|
|
|
42
|
+
### With Persistent Directory Handle
|
|
43
|
+
|
|
44
|
+
Persist the directory handle using IndexedDB (via `idb-keyval` or similar) to avoid prompting the user every time:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { openBrowserFilesApi } from '@statewalker/webrun-files-browser';
|
|
48
|
+
import { get, set, del } from 'idb-keyval';
|
|
49
|
+
|
|
50
|
+
// Opens directory picker if no stored handle, otherwise reuses stored one
|
|
51
|
+
const files = await openBrowserFilesApi({
|
|
52
|
+
handlerKey: 'my-project-dir', // Key for storing the handle
|
|
53
|
+
readwrite: true, // Request read-write access
|
|
54
|
+
get, // Retrieve stored handle
|
|
55
|
+
set, // Store the handle
|
|
56
|
+
del, // Delete stored handle if inaccessible
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
45
60
|
### With Origin Private File System (OPFS)
|
|
46
61
|
|
|
47
62
|
OPFS provides a sandboxed filesystem that doesn't require user interaction:
|
|
48
63
|
|
|
49
64
|
```typescript
|
|
50
|
-
import { getOPFSFilesApi } from
|
|
51
|
-
import {
|
|
65
|
+
import { getOPFSFilesApi } from '@statewalker/webrun-files-browser';
|
|
66
|
+
import { writeText } from '@statewalker/webrun-files';
|
|
52
67
|
|
|
53
68
|
// No user gesture required
|
|
54
|
-
const
|
|
55
|
-
const api = new FilesApi(opfsFs);
|
|
69
|
+
const files = await getOPFSFilesApi();
|
|
56
70
|
|
|
57
|
-
// Use the same API
|
|
58
|
-
await
|
|
59
|
-
new TextEncoder().encode('{"theme": "dark"}')
|
|
60
|
-
]);
|
|
71
|
+
// Use the same API
|
|
72
|
+
await writeText(files, '/data/config.json', '{"theme": "dark"}');
|
|
61
73
|
```
|
|
62
74
|
|
|
63
75
|
### With Custom Directory Handle
|
|
64
76
|
|
|
65
|
-
|
|
77
|
+
Create a `BrowserFilesApi` instance with any `FileSystemDirectoryHandle`:
|
|
66
78
|
|
|
67
79
|
```typescript
|
|
68
|
-
import { BrowserFilesApi } from
|
|
69
|
-
import { FilesApi } from "@statewalker/webrun-files";
|
|
80
|
+
import { BrowserFilesApi } from '@statewalker/webrun-files-browser';
|
|
70
81
|
|
|
71
82
|
// Get a directory handle from drag & drop, IndexedDB, etc.
|
|
72
83
|
const directoryHandle = await getDirectoryHandleSomehow();
|
|
73
84
|
|
|
74
|
-
const
|
|
75
|
-
const api = new FilesApi(browserFs);
|
|
85
|
+
const files = new BrowserFilesApi({ rootHandle: directoryHandle });
|
|
76
86
|
```
|
|
77
87
|
|
|
78
88
|
## API Reference
|
|
79
89
|
|
|
80
90
|
### BrowserFilesApi
|
|
81
91
|
|
|
82
|
-
Main class implementing `
|
|
92
|
+
Main class implementing `FilesApi` for browser environments.
|
|
83
93
|
|
|
84
94
|
```typescript
|
|
85
95
|
interface BrowserFilesApiOptions {
|
|
86
96
|
rootHandle: FileSystemDirectoryHandle;
|
|
87
97
|
}
|
|
88
98
|
|
|
89
|
-
class BrowserFilesApi implements
|
|
99
|
+
class BrowserFilesApi implements FilesApi {
|
|
90
100
|
constructor(options: BrowserFilesApiOptions);
|
|
91
101
|
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
move(source:
|
|
101
|
-
copy(source:
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### BrowserFileHandle
|
|
106
|
-
|
|
107
|
-
File handle class for random access read/write operations.
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
class BrowserFileHandle implements FileHandle {
|
|
111
|
-
readonly size: number;
|
|
112
|
-
|
|
113
|
-
close(): Promise<void>;
|
|
114
|
-
appendFile(data: BinaryStream, options?: AppendOptions): Promise<number>;
|
|
115
|
-
createReadStream(options?: ReadStreamOptions): AsyncGenerator<Uint8Array>;
|
|
116
|
-
createWriteStream(data: BinaryStream, options?: WriteStreamOptions): Promise<number>;
|
|
102
|
+
// All FilesApi methods
|
|
103
|
+
read(path: string, options?: ReadOptions): AsyncIterable<Uint8Array>;
|
|
104
|
+
write(path: string, content: Iterable<Uint8Array> | AsyncIterable<Uint8Array>): Promise<void>;
|
|
105
|
+
mkdir(path: string): Promise<void>;
|
|
106
|
+
list(path: string, options?: ListOptions): AsyncIterable<FileInfo>;
|
|
107
|
+
stats(path: string): Promise<FileStats | undefined>;
|
|
108
|
+
exists(path: string): Promise<boolean>;
|
|
109
|
+
remove(path: string): Promise<boolean>;
|
|
110
|
+
move(source: string, target: string): Promise<boolean>; // uses native move when available
|
|
111
|
+
copy(source: string, target: string): Promise<boolean>;
|
|
117
112
|
}
|
|
118
113
|
```
|
|
119
114
|
|
|
@@ -121,60 +116,44 @@ class BrowserFileHandle implements FileHandle {
|
|
|
121
116
|
|
|
122
117
|
```typescript
|
|
123
118
|
// Opens a directory picker and returns a BrowserFilesApi instance
|
|
124
|
-
function openBrowserFilesApi(): Promise<BrowserFilesApi>;
|
|
119
|
+
function openBrowserFilesApi(options?: OpenBrowserFilesApiOptions): Promise<BrowserFilesApi>;
|
|
120
|
+
|
|
121
|
+
interface OpenBrowserFilesApiOptions {
|
|
122
|
+
handlerKey?: string; // Key for storing the handle (default: "root-dir")
|
|
123
|
+
readwrite?: boolean; // Request read-write access (default: true)
|
|
124
|
+
get?: (key: string) => Promise<FileSystemDirectoryHandle | undefined>;
|
|
125
|
+
set?: (key: string, handler: FileSystemDirectoryHandle) => Promise<void>;
|
|
126
|
+
del?: (key: string) => Promise<void>;
|
|
127
|
+
}
|
|
125
128
|
|
|
126
129
|
// Returns a BrowserFilesApi backed by the Origin Private File System
|
|
127
130
|
function getOPFSFilesApi(): Promise<BrowserFilesApi>;
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## How It Works
|
|
131
|
-
|
|
132
|
-
### Directory Navigation
|
|
133
|
-
|
|
134
|
-
The implementation navigates the filesystem by traversing `FileSystemDirectoryHandle` entries. Paths like `/docs/notes/file.txt` are resolved by getting directory handles step by step:
|
|
135
|
-
|
|
136
|
-
```
|
|
137
|
-
root → "docs" → "notes" → "file.txt"
|
|
138
|
-
```
|
|
139
131
|
|
|
140
|
-
|
|
132
|
+
// Checks if a file system handle is still accessible
|
|
133
|
+
function isHandlerAccessible(
|
|
134
|
+
fileHandle: FileSystemFileHandle | FileSystemDirectoryHandle
|
|
135
|
+
): Promise<boolean>;
|
|
141
136
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
for await (const chunk of handle.createReadStream({ start: 1000, end: 2000 })) {
|
|
148
|
-
console.log(chunk);
|
|
149
|
-
}
|
|
150
|
-
await handle.close();
|
|
137
|
+
// Verifies and requests file system permissions for a handle
|
|
138
|
+
function verifyPermission(
|
|
139
|
+
fileHandle: FileSystemFileHandle | FileSystemDirectoryHandle,
|
|
140
|
+
readWrite?: boolean // default: false
|
|
141
|
+
): Promise<boolean>;
|
|
151
142
|
```
|
|
152
143
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
Writes use `FileSystemWritableFileStream`:
|
|
156
|
-
|
|
157
|
-
- **Full writes** create a new writable stream and write all data
|
|
158
|
-
- **Partial writes** (with `start` option) preserve content before the start position
|
|
159
|
-
- **Appends** seek to the end of the file before writing
|
|
160
|
-
|
|
161
|
-
### Directory Creation
|
|
144
|
+
## Native Move Support
|
|
162
145
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
await api.mkdir("/path/to/new/directory");
|
|
167
|
-
```
|
|
146
|
+
The `move()` method attempts to use the browser's native [`FileSystemHandle.move()`](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemHandle/move) when available (Chrome 110+, Edge 110+). This provides an atomic, in-place rename/move without copying file data. On older browsers or polyfills that lack native `move()`, the method falls back to copy-then-delete.
|
|
168
147
|
|
|
169
148
|
## Browser Support
|
|
170
149
|
|
|
171
150
|
The File System Access API is supported in:
|
|
172
151
|
|
|
173
|
-
- Chrome/Edge 86+
|
|
152
|
+
- Chrome/Edge 86+ (native `move()` from 110+)
|
|
174
153
|
- Opera 72+
|
|
175
154
|
- Chrome for Android 86+
|
|
176
155
|
|
|
177
|
-
For other browsers, you may need a polyfill like [native-file-system-adapter](https://github.com/
|
|
156
|
+
For other browsers, you may need a polyfill like [native-file-system-adapter](https://github.com/jimmywarting/native-file-system-adapter).
|
|
178
157
|
|
|
179
158
|
## Security Considerations
|
|
180
159
|
|
|
@@ -183,19 +162,40 @@ For other browsers, you may need a polyfill like [native-file-system-adapter](ht
|
|
|
183
162
|
- **Permission prompts**: Users must grant permission to access directories
|
|
184
163
|
- **OPFS isolation**: Origin Private File System is isolated per-origin and not accessible to the user
|
|
185
164
|
|
|
186
|
-
##
|
|
165
|
+
## Permission and Accessibility Utilities
|
|
187
166
|
|
|
188
|
-
|
|
167
|
+
When working with persistent directory handles:
|
|
189
168
|
|
|
190
169
|
```typescript
|
|
191
|
-
import {
|
|
192
|
-
|
|
170
|
+
import { verifyPermission, isHandlerAccessible } from '@statewalker/webrun-files-browser';
|
|
171
|
+
|
|
172
|
+
// Check if we have read-write permission
|
|
173
|
+
const hasPermission = await verifyPermission(directoryHandle, true);
|
|
193
174
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
175
|
+
// Check if the directory still exists and is accessible
|
|
176
|
+
const isAccessible = await isHandlerAccessible(directoryHandle);
|
|
177
|
+
|
|
178
|
+
if (!hasPermission) {
|
|
179
|
+
console.log('Permission denied - user may need to re-grant access');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!isAccessible) {
|
|
183
|
+
console.log('Directory no longer accessible - may have been deleted or moved');
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Testing
|
|
188
|
+
|
|
189
|
+
For testing in Node.js environments, use [native-file-system-adapter](https://github.com/jimmywarting/native-file-system-adapter) which provides a memory backend:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { getOriginPrivateDirectory } from 'native-file-system-adapter';
|
|
193
|
+
// @ts-expect-error - no type declarations
|
|
194
|
+
import * as driver from 'native-file-system-adapter/src/adapters/memory.js';
|
|
195
|
+
import { BrowserFilesApi } from '@statewalker/webrun-files-browser';
|
|
197
196
|
|
|
198
|
-
const
|
|
197
|
+
const rootHandle = await getOriginPrivateDirectory(driver);
|
|
198
|
+
const files = new BrowserFilesApi({ rootHandle });
|
|
199
199
|
```
|
|
200
200
|
|
|
201
201
|
## License
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Browser implementation of
|
|
2
|
+
* Browser implementation of FilesApi using the File System Access API.
|
|
3
3
|
*
|
|
4
4
|
* Provides a filesystem-like interface over browser's FileSystemDirectoryHandle.
|
|
5
5
|
* Works with directories obtained via showDirectoryPicker() or OPFS.
|
|
6
6
|
*/
|
|
7
|
-
import type {
|
|
7
|
+
import type { FileInfo, FileStats, FilesApi, ListOptions, ReadOptions } from "@statewalker/webrun-files";
|
|
8
8
|
export interface BrowserFilesApiOptions {
|
|
9
9
|
/**
|
|
10
10
|
* Root FileSystemDirectoryHandle to use as the filesystem root.
|
|
@@ -12,58 +12,30 @@ export interface BrowserFilesApiOptions {
|
|
|
12
12
|
*/
|
|
13
13
|
rootHandle: FileSystemDirectoryHandle;
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Browser filesystem implementation using the File System Access API.
|
|
17
|
+
*
|
|
18
|
+
* Provides FilesApi interface over browser's FileSystemDirectoryHandle.
|
|
19
|
+
* This enables web applications to read/write files in user-selected directories
|
|
20
|
+
* or the Origin Private File System (OPFS).
|
|
21
|
+
*/
|
|
22
|
+
export declare class BrowserFilesApi implements FilesApi {
|
|
16
23
|
private rootHandle;
|
|
17
24
|
constructor(options: BrowserFilesApiOptions);
|
|
18
|
-
/**
|
|
19
|
-
* Navigates to a directory handle from a path.
|
|
20
|
-
* Returns null if the directory doesn't exist and create is false.
|
|
21
|
-
*/
|
|
22
25
|
private getDirectoryHandle;
|
|
23
|
-
/**
|
|
24
|
-
* Gets a file handle from a path.
|
|
25
|
-
* Returns null if the file doesn't exist and create is false.
|
|
26
|
-
*/
|
|
27
26
|
private getFileHandle;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
list(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* Removes a file or directory.
|
|
38
|
-
*/
|
|
39
|
-
remove(file: FileRef): Promise<boolean>;
|
|
40
|
-
/**
|
|
41
|
-
* Opens a file for reading and writing.
|
|
42
|
-
* Creates the file and parent directories if they don't exist.
|
|
43
|
-
*/
|
|
44
|
-
open(file: FileRef): Promise<FileHandle>;
|
|
45
|
-
/**
|
|
46
|
-
* Creates a directory (and parent directories if needed).
|
|
47
|
-
*/
|
|
48
|
-
mkdir(file: FileRef): Promise<void>;
|
|
49
|
-
/**
|
|
50
|
-
* Moves a file or directory using copy + delete.
|
|
51
|
-
* The File System Access API doesn't have a native move operation.
|
|
52
|
-
*/
|
|
53
|
-
move(source: FileRef, target: FileRef): Promise<boolean>;
|
|
54
|
-
/**
|
|
55
|
-
* Copies a file or directory.
|
|
56
|
-
*/
|
|
57
|
-
copy(source: FileRef, target: FileRef, options?: CopyOptions): Promise<boolean>;
|
|
27
|
+
read(path: string, options?: ReadOptions): AsyncIterable<Uint8Array>;
|
|
28
|
+
write(path: string, content: Iterable<Uint8Array> | AsyncIterable<Uint8Array>): Promise<void>;
|
|
29
|
+
mkdir(path: string): Promise<void>;
|
|
30
|
+
list(path: string, options?: ListOptions): AsyncIterable<FileInfo>;
|
|
31
|
+
stats(path: string): Promise<FileStats | undefined>;
|
|
32
|
+
exists(path: string): Promise<boolean>;
|
|
33
|
+
remove(path: string): Promise<boolean>;
|
|
34
|
+
move(source: string, target: string): Promise<boolean>;
|
|
35
|
+
copy(source: string, target: string): Promise<boolean>;
|
|
58
36
|
}
|
|
59
|
-
/**
|
|
60
|
-
* Opens a browser directory picker and returns a BrowserFilesApi instance.
|
|
61
|
-
* Only works in secure contexts (HTTPS) and requires user gesture.
|
|
62
|
-
*/
|
|
63
|
-
export declare function openBrowserFilesApi(): Promise<BrowserFilesApi>;
|
|
64
37
|
/**
|
|
65
38
|
* Gets a BrowserFilesApi instance backed by the Origin Private File System (OPFS).
|
|
66
|
-
* OPFS provides a sandboxed filesystem that persists between sessions.
|
|
67
39
|
*/
|
|
68
40
|
export declare function getOPFSFilesApi(): Promise<BrowserFilesApi>;
|
|
69
41
|
//# sourceMappingURL=browser-files-api.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-files-api.d.ts","sourceRoot":"","sources":["../src/browser-files-api.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"browser-files-api.d.ts","sourceRoot":"","sources":["../src/browser-files-api.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,WAAW,EACZ,MAAM,2BAA2B,CAAC;AAQnC,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,UAAU,EAAE,yBAAyB,CAAC;CACvC;AAED;;;;;;GAMG;AACH,qBAAa,eAAgB,YAAW,QAAQ;IAC9C,OAAO,CAAC,UAAU,CAA4B;gBAElC,OAAO,EAAE,sBAAsB;YAI7B,kBAAkB;YAkBlB,aAAa;IAkBpB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,UAAU,CAAC;IA6BrE,KAAK,CACT,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,GACxD,OAAO,CAAC,IAAI,CAAC;IAsBV,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,QAAQ,CAAC;IAqCnE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAqCnD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKtC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBtC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgCtD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CA4E7D;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,eAAe,CAAC,CAGhE"}
|