@statewalker/webrun-files-browser 0.5.0 → 0.7.0
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/LICENSE +1 -1
- package/README.md +95 -99
- package/dist/browser-files-api.d.ts +19 -47
- package/dist/browser-files-api.d.ts.map +1 -1
- package/dist/cjs/index.cjs +137 -199
- package/dist/esm/index.js +136 -199
- 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 +2 -2
- package/src/browser-files-api.ts +96 -107
- package/src/index.ts +7 -4
- package/src/open-browser-files-api.ts +148 -0
- 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/src/browser-files-api.ts
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
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
7
|
|
|
8
8
|
import type {
|
|
9
|
-
CopyOptions,
|
|
10
|
-
FileHandle,
|
|
11
9
|
FileInfo,
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
FileStats,
|
|
11
|
+
FilesApi,
|
|
14
12
|
ListOptions,
|
|
13
|
+
ReadOptions,
|
|
15
14
|
} from "@statewalker/webrun-files";
|
|
16
|
-
import { basename, dirname, joinPath,
|
|
17
|
-
import { BrowserFileHandle } from "./browser-file-handle.js";
|
|
15
|
+
import { basename, dirname, joinPath, normalizePath } from "@statewalker/webrun-files";
|
|
18
16
|
|
|
19
17
|
export interface BrowserFilesApiOptions {
|
|
20
18
|
/**
|
|
@@ -24,17 +22,20 @@ export interface BrowserFilesApiOptions {
|
|
|
24
22
|
rootHandle: FileSystemDirectoryHandle;
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Browser filesystem implementation using the File System Access API.
|
|
27
|
+
*
|
|
28
|
+
* Provides FilesApi interface over browser's FileSystemDirectoryHandle.
|
|
29
|
+
* This enables web applications to read/write files in user-selected directories
|
|
30
|
+
* or the Origin Private File System (OPFS).
|
|
31
|
+
*/
|
|
32
|
+
export class BrowserFilesApi implements FilesApi {
|
|
28
33
|
private rootHandle: FileSystemDirectoryHandle;
|
|
29
34
|
|
|
30
35
|
constructor(options: BrowserFilesApiOptions) {
|
|
31
36
|
this.rootHandle = options.rootHandle;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
/**
|
|
35
|
-
* Navigates to a directory handle from a path.
|
|
36
|
-
* Returns null if the directory doesn't exist and create is false.
|
|
37
|
-
*/
|
|
38
39
|
private async getDirectoryHandle(
|
|
39
40
|
path: string,
|
|
40
41
|
options: { create?: boolean } = {},
|
|
@@ -53,15 +54,11 @@ export class BrowserFilesApi implements IFilesApi {
|
|
|
53
54
|
return current;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
/**
|
|
57
|
-
* Gets a file handle from a path.
|
|
58
|
-
* Returns null if the file doesn't exist and create is false.
|
|
59
|
-
*/
|
|
60
57
|
private async getFileHandle(
|
|
61
58
|
path: string,
|
|
62
59
|
options: { create?: boolean } = {},
|
|
63
60
|
): Promise<FileSystemFileHandle | null> {
|
|
64
|
-
const normalized =
|
|
61
|
+
const normalized = normalizePath(path);
|
|
65
62
|
const parentPath = dirname(normalized);
|
|
66
63
|
const fileName = basename(normalized);
|
|
67
64
|
|
|
@@ -75,15 +72,71 @@ export class BrowserFilesApi implements IFilesApi {
|
|
|
75
72
|
}
|
|
76
73
|
}
|
|
77
74
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
async *read(path: string, options?: ReadOptions): AsyncIterable<Uint8Array> {
|
|
76
|
+
const fileHandle = await this.getFileHandle(path);
|
|
77
|
+
if (!fileHandle) return;
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const file = await fileHandle.getFile();
|
|
81
|
+
const start = options?.start ?? 0;
|
|
82
|
+
const length = options?.length;
|
|
83
|
+
const end = length !== undefined ? start + length : file.size;
|
|
84
|
+
|
|
85
|
+
if (start >= file.size) return;
|
|
86
|
+
|
|
87
|
+
const bufferSize = 8192;
|
|
88
|
+
let position = start;
|
|
89
|
+
const actualEnd = Math.min(end, file.size);
|
|
90
|
+
|
|
91
|
+
while (position < actualEnd) {
|
|
92
|
+
const remaining = actualEnd - position;
|
|
93
|
+
const toRead = Math.min(bufferSize, remaining);
|
|
94
|
+
const slice = file.slice(position, position + toRead);
|
|
95
|
+
const buffer = await slice.arrayBuffer();
|
|
96
|
+
yield new Uint8Array(buffer);
|
|
97
|
+
position += buffer.byteLength;
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async write(
|
|
105
|
+
path: string,
|
|
106
|
+
content: Iterable<Uint8Array> | AsyncIterable<Uint8Array>,
|
|
107
|
+
): Promise<void> {
|
|
108
|
+
const normalized = normalizePath(path);
|
|
109
|
+
const parentPath = dirname(normalized);
|
|
110
|
+
const fileName = basename(normalized);
|
|
111
|
+
|
|
112
|
+
const parent = await this.getDirectoryHandle(parentPath, { create: true });
|
|
113
|
+
if (!parent) {
|
|
114
|
+
throw new Error(`Cannot create directory: ${parentPath}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const fileHandle = await parent.getFileHandle(fileName, { create: true });
|
|
118
|
+
const writable = await fileHandle.createWritable();
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
for await (const chunk of content) {
|
|
122
|
+
await writable.write(chunk as Uint8Array<ArrayBuffer>);
|
|
123
|
+
}
|
|
124
|
+
} finally {
|
|
125
|
+
await writable.close();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async mkdir(path: string): Promise<void> {
|
|
130
|
+
const normalized = normalizePath(path);
|
|
131
|
+
await this.getDirectoryHandle(normalized, { create: true });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async *list(path: string, options?: ListOptions): AsyncIterable<FileInfo> {
|
|
135
|
+
const normalized = normalizePath(path);
|
|
83
136
|
const dirHandle = await this.getDirectoryHandle(normalized);
|
|
84
137
|
if (!dirHandle) return;
|
|
85
138
|
|
|
86
|
-
const
|
|
139
|
+
const recursive = options?.recursive ?? false;
|
|
87
140
|
|
|
88
141
|
for await (const [name, handle] of dirHandle.entries()) {
|
|
89
142
|
const entryPath = joinPath(normalized, name);
|
|
@@ -102,7 +155,6 @@ export class BrowserFilesApi implements IFilesApi {
|
|
|
102
155
|
const file = await fileHandle.getFile();
|
|
103
156
|
info.size = file.size;
|
|
104
157
|
info.lastModified = file.lastModified;
|
|
105
|
-
info.type = file.type;
|
|
106
158
|
} catch {
|
|
107
159
|
// Ignore errors getting file metadata
|
|
108
160
|
}
|
|
@@ -116,19 +168,12 @@ export class BrowserFilesApi implements IFilesApi {
|
|
|
116
168
|
}
|
|
117
169
|
}
|
|
118
170
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
*/
|
|
122
|
-
async stats(file: FileRef): Promise<FileInfo | undefined> {
|
|
123
|
-
const normalized = resolveFileRef(file);
|
|
124
|
-
const fileName = basename(normalized);
|
|
171
|
+
async stats(path: string): Promise<FileStats | undefined> {
|
|
172
|
+
const normalized = normalizePath(path);
|
|
125
173
|
|
|
126
|
-
// Handle root directory
|
|
127
174
|
if (normalized === "/") {
|
|
128
175
|
return {
|
|
129
176
|
kind: "directory",
|
|
130
|
-
name: "",
|
|
131
|
-
path: "/",
|
|
132
177
|
lastModified: 0,
|
|
133
178
|
};
|
|
134
179
|
}
|
|
@@ -140,11 +185,8 @@ export class BrowserFilesApi implements IFilesApi {
|
|
|
140
185
|
const f = await fileHandle.getFile();
|
|
141
186
|
return {
|
|
142
187
|
kind: "file",
|
|
143
|
-
name: fileName,
|
|
144
|
-
path: normalized,
|
|
145
188
|
size: f.size,
|
|
146
189
|
lastModified: f.lastModified,
|
|
147
|
-
type: f.type,
|
|
148
190
|
};
|
|
149
191
|
} catch {
|
|
150
192
|
return undefined;
|
|
@@ -156,8 +198,6 @@ export class BrowserFilesApi implements IFilesApi {
|
|
|
156
198
|
if (dirHandle) {
|
|
157
199
|
return {
|
|
158
200
|
kind: "directory",
|
|
159
|
-
name: fileName,
|
|
160
|
-
path: normalized,
|
|
161
201
|
lastModified: 0,
|
|
162
202
|
};
|
|
163
203
|
}
|
|
@@ -165,11 +205,13 @@ export class BrowserFilesApi implements IFilesApi {
|
|
|
165
205
|
return undefined;
|
|
166
206
|
}
|
|
167
207
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
208
|
+
async exists(path: string): Promise<boolean> {
|
|
209
|
+
const stats = await this.stats(path);
|
|
210
|
+
return stats !== undefined;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async remove(path: string): Promise<boolean> {
|
|
214
|
+
const normalized = normalizePath(path);
|
|
173
215
|
const parentPath = dirname(normalized);
|
|
174
216
|
const name = basename(normalized);
|
|
175
217
|
|
|
@@ -184,65 +226,26 @@ export class BrowserFilesApi implements IFilesApi {
|
|
|
184
226
|
}
|
|
185
227
|
}
|
|
186
228
|
|
|
187
|
-
|
|
188
|
-
* Opens a file for reading and writing.
|
|
189
|
-
* Creates the file and parent directories if they don't exist.
|
|
190
|
-
*/
|
|
191
|
-
async open(file: FileRef): Promise<FileHandle> {
|
|
192
|
-
const normalized = resolveFileRef(file);
|
|
193
|
-
const parentPath = dirname(normalized);
|
|
194
|
-
const fileName = basename(normalized);
|
|
195
|
-
|
|
196
|
-
// Ensure parent directory exists
|
|
197
|
-
const parent = await this.getDirectoryHandle(parentPath, { create: true });
|
|
198
|
-
if (!parent) {
|
|
199
|
-
throw new Error(`Cannot create directory: ${parentPath}`);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Get or create file
|
|
203
|
-
const fileHandle = await parent.getFileHandle(fileName, { create: true });
|
|
204
|
-
const f = await fileHandle.getFile();
|
|
205
|
-
|
|
206
|
-
return new BrowserFileHandle({ fileHandle, initialFile: f });
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Creates a directory (and parent directories if needed).
|
|
211
|
-
*/
|
|
212
|
-
async mkdir(file: FileRef): Promise<void> {
|
|
213
|
-
const normalized = resolveFileRef(file);
|
|
214
|
-
await this.getDirectoryHandle(normalized, { create: true });
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Moves a file or directory using copy + delete.
|
|
219
|
-
* The File System Access API doesn't have a native move operation.
|
|
220
|
-
*/
|
|
221
|
-
async move(source: FileRef, target: FileRef): Promise<boolean> {
|
|
229
|
+
async move(source: string, target: string): Promise<boolean> {
|
|
222
230
|
const copied = await this.copy(source, target);
|
|
223
231
|
if (!copied) return false;
|
|
224
232
|
return this.remove(source);
|
|
225
233
|
}
|
|
226
234
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
async copy(source: FileRef, target: FileRef, options: CopyOptions = {}): Promise<boolean> {
|
|
231
|
-
const sourcePath = resolveFileRef(source);
|
|
232
|
-
const targetPath = resolveFileRef(target);
|
|
233
|
-
const { recursive = true } = options;
|
|
235
|
+
async copy(source: string, target: string): Promise<boolean> {
|
|
236
|
+
const sourcePath = normalizePath(source);
|
|
237
|
+
const targetPath = normalizePath(target);
|
|
234
238
|
|
|
235
|
-
const
|
|
236
|
-
if (!
|
|
239
|
+
const sourceStats = await this.stats(source);
|
|
240
|
+
if (!sourceStats) return false;
|
|
237
241
|
|
|
238
|
-
if (
|
|
242
|
+
if (sourceStats.kind === "file") {
|
|
239
243
|
// Copy single file
|
|
240
244
|
const sourceHandle = await this.getFileHandle(sourcePath);
|
|
241
245
|
if (!sourceHandle) return false;
|
|
242
246
|
|
|
243
247
|
const sourceFile = await sourceHandle.getFile();
|
|
244
248
|
|
|
245
|
-
// Create target file
|
|
246
249
|
const targetParentPath = dirname(targetPath);
|
|
247
250
|
const targetFileName = basename(targetPath);
|
|
248
251
|
|
|
@@ -253,7 +256,6 @@ export class BrowserFilesApi implements IFilesApi {
|
|
|
253
256
|
const writable = await targetHandle.createWritable();
|
|
254
257
|
|
|
255
258
|
try {
|
|
256
|
-
// Stream the file content
|
|
257
259
|
const reader = sourceFile.stream().getReader();
|
|
258
260
|
while (true) {
|
|
259
261
|
const { done, value } = await reader.read();
|
|
@@ -267,13 +269,9 @@ export class BrowserFilesApi implements IFilesApi {
|
|
|
267
269
|
return true;
|
|
268
270
|
}
|
|
269
271
|
|
|
270
|
-
// Copy directory
|
|
271
|
-
if (!recursive) return false;
|
|
272
|
-
|
|
273
|
-
// Create target directory
|
|
272
|
+
// Copy directory recursively
|
|
274
273
|
await this.getDirectoryHandle(targetPath, { create: true });
|
|
275
274
|
|
|
276
|
-
// Copy all entries recursively
|
|
277
275
|
for await (const entry of this.list(source, { recursive: true })) {
|
|
278
276
|
if (entry.kind === "directory") continue;
|
|
279
277
|
|
|
@@ -291,7 +289,9 @@ export class BrowserFilesApi implements IFilesApi {
|
|
|
291
289
|
const newTargetParent = await this.getDirectoryHandle(newTargetParentPath, { create: true });
|
|
292
290
|
if (!newTargetParent) continue;
|
|
293
291
|
|
|
294
|
-
const newTargetHandle = await newTargetParent.getFileHandle(newTargetFileName, {
|
|
292
|
+
const newTargetHandle = await newTargetParent.getFileHandle(newTargetFileName, {
|
|
293
|
+
create: true,
|
|
294
|
+
});
|
|
295
295
|
const writable = await newTargetHandle.createWritable();
|
|
296
296
|
|
|
297
297
|
try {
|
|
@@ -310,19 +310,8 @@ export class BrowserFilesApi implements IFilesApi {
|
|
|
310
310
|
}
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
/**
|
|
314
|
-
* Opens a browser directory picker and returns a BrowserFilesApi instance.
|
|
315
|
-
* Only works in secure contexts (HTTPS) and requires user gesture.
|
|
316
|
-
*/
|
|
317
|
-
export async function openBrowserFilesApi(): Promise<BrowserFilesApi> {
|
|
318
|
-
// Cast to any since TypeScript's lib.dom.d.ts may not include showDirectoryPicker
|
|
319
|
-
const rootHandle = await (globalThis as unknown as { showDirectoryPicker: () => Promise<FileSystemDirectoryHandle> }).showDirectoryPicker();
|
|
320
|
-
return new BrowserFilesApi({ rootHandle });
|
|
321
|
-
}
|
|
322
|
-
|
|
323
313
|
/**
|
|
324
314
|
* Gets a BrowserFilesApi instance backed by the Origin Private File System (OPFS).
|
|
325
|
-
* OPFS provides a sandboxed filesystem that persists between sessions.
|
|
326
315
|
*/
|
|
327
316
|
export async function getOPFSFilesApi(): Promise<BrowserFilesApi> {
|
|
328
317
|
const rootHandle = await navigator.storage.getDirectory();
|
package/src/index.ts
CHANGED
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
export {
|
|
6
6
|
BrowserFilesApi,
|
|
7
|
-
getOPFSFilesApi,
|
|
8
|
-
openBrowserFilesApi,
|
|
9
7
|
type BrowserFilesApiOptions,
|
|
8
|
+
getOPFSFilesApi,
|
|
10
9
|
} from "./browser-files-api.js";
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
export {
|
|
11
|
+
isHandlerAccessible,
|
|
12
|
+
type OpenBrowserFilesApiOptions,
|
|
13
|
+
openBrowserFilesApi,
|
|
14
|
+
verifyPermission,
|
|
15
|
+
} from "./open-browser-files-api.js";
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for opening and managing browser file system access.
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for permission verification, handle accessibility checking,
|
|
5
|
+
* and opening directory pickers with optional persistent storage support.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { BrowserFilesApi } from "./browser-files-api.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extended FileSystemHandle interface with permission methods.
|
|
12
|
+
* These are part of the File System Access API but not fully typed in TypeScript's lib.dom.d.ts.
|
|
13
|
+
*/
|
|
14
|
+
interface FileSystemHandleWithPermissions {
|
|
15
|
+
queryPermission(descriptor?: { mode?: "read" | "readwrite" }): Promise<PermissionState>;
|
|
16
|
+
requestPermission(descriptor?: { mode?: "read" | "readwrite" }): Promise<PermissionState>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Options for opening a browser files API with persistent storage support.
|
|
21
|
+
*/
|
|
22
|
+
export interface OpenBrowserFilesApiOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Key used to store the directory handler in persistent storage.
|
|
25
|
+
* @default "root-dir"
|
|
26
|
+
*/
|
|
27
|
+
handlerKey?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Whether to request read-write access (true) or read-only (false).
|
|
30
|
+
* @default true
|
|
31
|
+
*/
|
|
32
|
+
readwrite?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Gets a stored directory handler by key.
|
|
35
|
+
* @default Returns undefined (no persistence)
|
|
36
|
+
*/
|
|
37
|
+
get?: (key: string) => Promise<FileSystemDirectoryHandle | undefined>;
|
|
38
|
+
/**
|
|
39
|
+
* Stores a directory handler with a key.
|
|
40
|
+
* @default No-op (no persistence)
|
|
41
|
+
*/
|
|
42
|
+
set?: (key: string, handler: FileSystemDirectoryHandle) => Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Deletes a stored directory handler by key.
|
|
45
|
+
* @default No-op (no persistence)
|
|
46
|
+
*/
|
|
47
|
+
del?: (key: string) => Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Checks if a file system handle is still accessible by performing a real read operation.
|
|
52
|
+
* Handles may become inaccessible if the underlying file/directory was moved or deleted.
|
|
53
|
+
*
|
|
54
|
+
* @param fileHandle - The file or directory handle to check
|
|
55
|
+
* @returns True if the handle is accessible, false otherwise
|
|
56
|
+
*/
|
|
57
|
+
export async function isHandlerAccessible(
|
|
58
|
+
fileHandle: FileSystemFileHandle | FileSystemDirectoryHandle,
|
|
59
|
+
): Promise<boolean> {
|
|
60
|
+
try {
|
|
61
|
+
if (fileHandle.kind === "file") {
|
|
62
|
+
await (fileHandle as FileSystemFileHandle).getFile();
|
|
63
|
+
} else {
|
|
64
|
+
// For directories, iterate to check accessibility
|
|
65
|
+
for await (const _item of (fileHandle as FileSystemDirectoryHandle).values()) {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Verifies and requests file system permissions for a handle.
|
|
77
|
+
* First checks if permission was already granted, then requests if needed.
|
|
78
|
+
*
|
|
79
|
+
* @param fileHandle - The file or directory handle to verify permissions for
|
|
80
|
+
* @param readWrite - Whether to request read-write access (true) or read-only (false)
|
|
81
|
+
* @returns True if permission is granted, false otherwise
|
|
82
|
+
*/
|
|
83
|
+
export async function verifyPermission(
|
|
84
|
+
fileHandle: FileSystemFileHandle | FileSystemDirectoryHandle,
|
|
85
|
+
readWrite: boolean = false,
|
|
86
|
+
): Promise<boolean> {
|
|
87
|
+
const handle = fileHandle as unknown as FileSystemHandleWithPermissions;
|
|
88
|
+
const options: { mode?: "read" | "readwrite" } = {};
|
|
89
|
+
if (readWrite) {
|
|
90
|
+
options.mode = "readwrite";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check if permission was already granted
|
|
94
|
+
if ((await handle.queryPermission(options)) === "granted") {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Request permission if not already granted
|
|
99
|
+
if ((await handle.requestPermission(options)) === "granted") {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Opens a browser directory picker and returns a BrowserFilesApi instance.
|
|
108
|
+
* Only works in secure contexts (HTTPS) and requires user gesture.
|
|
109
|
+
*
|
|
110
|
+
* Supports persistent storage of directory handles via optional get/set/del functions.
|
|
111
|
+
* When persistence is configured, will attempt to reuse a previously selected directory.
|
|
112
|
+
*
|
|
113
|
+
* @param options - Configuration options for opening the files API
|
|
114
|
+
* @returns A BrowserFilesApi instance for the selected directory
|
|
115
|
+
* @throws Error if access is not granted or the directory is not accessible
|
|
116
|
+
*/
|
|
117
|
+
export async function openBrowserFilesApi(
|
|
118
|
+
options: OpenBrowserFilesApiOptions = {},
|
|
119
|
+
): Promise<BrowserFilesApi> {
|
|
120
|
+
const {
|
|
121
|
+
handlerKey = "root-dir",
|
|
122
|
+
readwrite = true,
|
|
123
|
+
get = async () => undefined,
|
|
124
|
+
set = async () => {},
|
|
125
|
+
del = async () => {},
|
|
126
|
+
} = options;
|
|
127
|
+
|
|
128
|
+
let rootHandle = await get(handlerKey);
|
|
129
|
+
|
|
130
|
+
if (!rootHandle) {
|
|
131
|
+
// Cast to any since TypeScript's lib.dom.d.ts may not include showDirectoryPicker
|
|
132
|
+
rootHandle = await (
|
|
133
|
+
globalThis as unknown as { showDirectoryPicker: () => Promise<FileSystemDirectoryHandle> }
|
|
134
|
+
).showDirectoryPicker();
|
|
135
|
+
await set(handlerKey, rootHandle);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!(await verifyPermission(rootHandle, readwrite))) {
|
|
139
|
+
throw new Error("Access was not granted");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!(await isHandlerAccessible(rootHandle))) {
|
|
143
|
+
await del(handlerKey);
|
|
144
|
+
throw new Error("Cannot access the folder. Please try again.");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return new BrowserFilesApi({ rootHandle });
|
|
148
|
+
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser FileHandle implementation using File System Access API.
|
|
3
|
-
*
|
|
4
|
-
* Provides random access read/write operations for browser files.
|
|
5
|
-
* Uses File.slice() for efficient partial reads and FileSystemWritableFileStream
|
|
6
|
-
* for writes.
|
|
7
|
-
*/
|
|
8
|
-
import type { AppendOptions, BinaryStream, FileHandle, ReadStreamOptions, WriteStreamOptions } from "@statewalker/webrun-files";
|
|
9
|
-
export interface BrowserFileHandleOptions {
|
|
10
|
-
fileHandle: FileSystemFileHandle;
|
|
11
|
-
initialFile: File;
|
|
12
|
-
}
|
|
13
|
-
export declare class BrowserFileHandle implements FileHandle {
|
|
14
|
-
private fileHandle;
|
|
15
|
-
private file;
|
|
16
|
-
private _size;
|
|
17
|
-
private closed;
|
|
18
|
-
constructor(options: BrowserFileHandleOptions);
|
|
19
|
-
get size(): number;
|
|
20
|
-
close(): Promise<void>;
|
|
21
|
-
/**
|
|
22
|
-
* Appends data to the end of the file.
|
|
23
|
-
*/
|
|
24
|
-
appendFile(data: BinaryStream, options?: AppendOptions): Promise<number>;
|
|
25
|
-
/**
|
|
26
|
-
* Creates a read stream for the file.
|
|
27
|
-
* Uses File.slice() for efficient partial reads.
|
|
28
|
-
*/
|
|
29
|
-
createReadStream(options?: ReadStreamOptions): AsyncGenerator<Uint8Array>;
|
|
30
|
-
/**
|
|
31
|
-
* Writes data to the file starting at the specified position.
|
|
32
|
-
* If start > 0, preserves content before start position.
|
|
33
|
-
* Truncates file at start position before writing.
|
|
34
|
-
*/
|
|
35
|
-
createWriteStream(data: BinaryStream, options?: WriteStreamOptions): Promise<number>;
|
|
36
|
-
/**
|
|
37
|
-
* Random access read from file at a specific position.
|
|
38
|
-
* Uses File.slice() for efficient partial reads.
|
|
39
|
-
*/
|
|
40
|
-
read(buffer: Uint8Array, offset: number, length: number, position: number): Promise<number>;
|
|
41
|
-
}
|
|
42
|
-
//# sourceMappingURL=browser-file-handle.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"browser-file-handle.d.ts","sourceRoot":"","sources":["../src/browser-file-handle.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,2BAA2B,CAAC;AAGnC,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,oBAAoB,CAAC;IACjC,WAAW,EAAE,IAAI,CAAC;CACnB;AAED,qBAAa,iBAAkB,YAAW,UAAU;IAClD,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,EAAE,wBAAwB;IAM7C,IAAI,IAAI,IAAI,MAAM,CAEjB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,MAAM,CAAC;IAgClF;;;OAGG;IACI,gBAAgB,CAAC,OAAO,GAAE,iBAAsB,GAAG,cAAc,CAAC,UAAU,CAAC;IAmCpF;;;;OAIG;IACG,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC;IAyC9F;;;OAGG;IACG,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CA0BlG"}
|