Skip to content

Commit 3077810

Browse files
committed
Add FileSystem API guide for WebUI X
Add a new WIP documentation page describing the FileSystem API for WebUI X: Portable. The page introduces the async, origin-restricted API (no native JS interface), includes a complete HTML example for reading and saving JSON, and ships full TypeScript typings covering FileSystem, FileSystemStat, FileSystemAccessResult, FileSystemStreamInit, and Window.fs extensions. This documents available methods (newInputStream, newOutputStream, readTextFile, stat, access, accessInfo) and usage notes for developers.
1 parent 745b703 commit 3077810

File tree

1 file changed

+306
-0
lines changed

1 file changed

+306
-0
lines changed

docs/en/guide/webuix/filesystem.md

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
# FileSystem API (WIP)
2+
3+
Starting with `v?`, WebUI X: Portable introduces a new FileSystem API for interacting with files on Android devices. This API is fully asynchronous and is restricted to the website origin for improved security. Unlike the previous implementation, it does not expose a native JavaScript interface, ensuring better isolation and compatibility.
4+
5+
## Example
6+
7+
```html
8+
<!DOCTYPE html>
9+
<html>
10+
<head>
11+
<meta charset="UTF-8" />
12+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
13+
<title>JSON formatter</title>
14+
<style>
15+
body {
16+
margin: 0;
17+
}
18+
.container {
19+
width: 100vw;
20+
padding: 10px;
21+
box-sizing: border-box;
22+
}
23+
#jsonDisplay {
24+
width: 100%;
25+
height: 80vh;
26+
word-break: break-all;
27+
resize: none;
28+
}
29+
#fileInput {
30+
display: none;
31+
}
32+
</style>
33+
</head>
34+
<body style="padding-top: var(--window-inset-top)">
35+
<div class="container">
36+
<button id="uploadBtn">Upload JSON</button>
37+
<button id="downloadBtn">Download JSON</button>
38+
<br /><br />
39+
<textarea id="jsonDisplay" readonly></textarea>
40+
<input type="file" id="fileInput" accept=".json" />
41+
<!-- input can be create only when needed too -->
42+
</div>
43+
44+
<script>
45+
const uploadBtn = document.getElementById("uploadBtn");
46+
const downloadBtn = document.getElementById("downloadBtn");
47+
const fileInput = document.getElementById("fileInput");
48+
const jsonDisplay = document.getElementById("jsonDisplay");
49+
50+
uploadBtn.addEventListener("click", () => {
51+
fileInput.click();
52+
});
53+
54+
fileInput.addEventListener("change", (event) => {
55+
const file = event.target.files[0];
56+
if (file) {
57+
const reader = new FileReader();
58+
reader.onload = (e) => {
59+
try {
60+
const json = JSON.parse(e.target.result);
61+
jsonDisplay.value = JSON.stringify(json, null, 2);
62+
} catch (error) {
63+
ksu.toast("Invalid JSON file");
64+
}
65+
};
66+
reader.readAsText(file);
67+
}
68+
});
69+
70+
downloadBtn.addEventListener("click", async () => {
71+
const stream = fs.newSaveFileStream("formatted.json", "text/plain");
72+
const jsonText = jsonDisplay.value;
73+
if (jsonText) {
74+
const writer = stream.getWriter();
75+
await writer.write(new TextEncoder().encode(jsonText));
76+
await writer.close();
77+
} else {
78+
ksu.toast("No JSON to download");
79+
}
80+
});
81+
</script>
82+
</body>
83+
</html>
84+
```
85+
86+
## Typings
87+
88+
<details>
89+
<summary>Expand to see the full Typings</summary>
90+
91+
```ts
92+
/**
93+
* Options for initializing a file system stream, extending the standard ResponseInit.
94+
* @see ResponseInit
95+
*/
96+
interface FileSystemStreamInit extends ResponseInit {
97+
/**
98+
* An AbortSignal to allow aborting the stream operation.
99+
*/
100+
signal?: AbortSignal;
101+
}
102+
103+
/**
104+
* Represents file or directory statistics.
105+
*/
106+
interface FileSystemStat {
107+
/**
108+
* Size of the file in bytes.
109+
*/
110+
size: number;
111+
/**
112+
* Last modified timestamp (milliseconds since epoch).
113+
*/
114+
lastModified: number;
115+
/**
116+
* Returns true if the entry is a file.
117+
*/
118+
isFile: () => boolean;
119+
/**
120+
* Returns true if the entry is a directory.
121+
*/
122+
isDirectory: () => boolean;
123+
/**
124+
* Returns true if the entry is a symbolic link.
125+
*/
126+
isSymbolicLink: () => boolean;
127+
}
128+
129+
/**
130+
* Result of checking file system access permissions.
131+
*/
132+
interface FileSystemAccessResult {
133+
/**
134+
* Whether the file or directory exists.
135+
*/
136+
exists: boolean;
137+
/**
138+
* Whether the file or directory can be read.
139+
*/
140+
canRead: boolean;
141+
/**
142+
* Whether the file or directory can be written to.
143+
*/
144+
canWrite: boolean;
145+
/**
146+
* Whether the file or directory can be executed.
147+
*/
148+
canExecute: boolean;
149+
/**
150+
* Whether the file or directory is hidden.
151+
*/
152+
isHidden: boolean;
153+
}
154+
155+
/**
156+
* Provides an interface for file system operations.
157+
*/
158+
interface FileSystem {
159+
/**
160+
* File exists flag.
161+
*/
162+
F_OK: number;
163+
/**
164+
* File is readable flag.
165+
*/
166+
R_OK: number;
167+
/**
168+
* File is writable flag.
169+
*/
170+
W_OK: number;
171+
/**
172+
* File is executable flag.
173+
*/
174+
X_OK: number;
175+
/**
176+
* Opens a readable input stream for the specified file path.
177+
*
178+
* Initiates a file read operation and returns a promise that resolves to a Response object,
179+
* allowing you to consume the file's contents as a stream. Supports aborting via AbortSignal.
180+
*
181+
* @param path - The path to the file to read from.
182+
* @param init - Optional configuration for the stream, such as an AbortSignal.
183+
* @returns A promise that resolves to a Response containing the file data as a stream.
184+
*
185+
* @throws {TypeError} If the path is not a string.
186+
* @throws {Error} If the path is empty.
187+
* @throws {Error} If the input stream interface is unavailable or access is denied.
188+
*
189+
* @example
190+
* const response = await fs.newInputStream("/data/adb/file.txt");
191+
* const text = await response.text();
192+
*/
193+
newInputStream(path: string, init?: FileSystemStreamInit): Promise<Response>;
194+
195+
/**
196+
* Opens a writable output stream for the specified file path.
197+
*
198+
* Initiates a file write operation and returns a WritableStream for writing binary data (Uint8Array) to the file.
199+
* The stream supports chunked writing and can be closed or aborted. If the file does not exist, it will be created.
200+
* If the file exists, its contents will be overwritten.
201+
*
202+
* @param path - The path to the file to write to.
203+
* @returns A WritableStream for writing Uint8Array data to the file.
204+
*
205+
* @throws {TypeError} If the path is not a string.
206+
* @throws {Error} If the path is empty.
207+
* @throws {Error} If the output stream interface is unavailable or access is denied.
208+
*
209+
* @example
210+
* const stream = fs.newOutputStream("/data/adb/file.txt");
211+
* const writer = stream.getWriter();
212+
* await writer.write(new Uint8Array([1, 2, 3]));
213+
* await writer.close();
214+
*/
215+
newOutputStream(path: string): WritableStream<Uint8Array>;
216+
217+
/**
218+
* Reads a file as text using the input stream
219+
* @param path - The file path to read
220+
* @param encoding - Text encoding (default: 'utf-8')
221+
* @param signal - Optional abort signal
222+
* @returns Promise that resolves to the file content as text
223+
*/
224+
readTextFile(path: string, encoding?: string, signal?: AbortSignal): Promise<string>;
225+
226+
/**
227+
* Retrieves metadata for a file or directory, similar to Node's fs.stat().
228+
*
229+
* Returns detailed statistics such as size, last modified time, and type checks.
230+
* The returned {@link FileSystemStat} object provides methods to determine if the entry is a file, directory, or symbolic link.
231+
*
232+
* @param path - The path to the file or directory to query.
233+
* @returns A promise that resolves to a {@link FileSystemStat} object containing stat information.
234+
*
235+
* @throws {TypeError} If the path is not a string.
236+
* @throws {Error} If the path is empty.
237+
* @throws {Error} If the stat operation is not permitted.
238+
* @throws {Error} If the stat operation fails or returns invalid data.
239+
*
240+
* @example
241+
* const stat = await fs.stat("/data/adb/file.txt");
242+
* if (stat.isFile()) {
243+
* console.log("File size:", stat.size);
244+
* }
245+
*/
246+
stat(path: string): Promise<FileSystemStat>;
247+
248+
/**
249+
* Tests the accessibility of a file or directory, similar to Node's fs.access().
250+
*
251+
* Checks whether the specified path exists and/or has the requested permissions.
252+
* The mode can be a combination of F_OK (existence), R_OK (read), W_OK (write), and X_OK (execute).
253+
* If the check fails, the returned promise rejects with an Error.
254+
*
255+
* @param path - The file or directory path to check.
256+
* @param mode - Accessibility check mode. Use F_OK, R_OK, W_OK, X_OK constants or combine them with |.
257+
* Defaults to F_OK (existence check only).
258+
* @returns A promise that resolves if the check passes, or rejects with an Error if it fails.
259+
*
260+
* @example
261+
* // Check existence only
262+
* await fs.access("/data/adb/file.txt");
263+
*
264+
* // Check readable + writable
265+
* await fs.access("/data/adb/file.txt", fs.R_OK | fs.W_OK);
266+
*/
267+
access(path: string, mode?: number): Promise<void>;
268+
269+
/**
270+
* Retrieves detailed access information for the specified file or directory.
271+
*
272+
* Returns an object describing whether the path exists and its read/write/execute/hidden status.
273+
* Unlike {@link access}, this method never throws for missing permissions or non-existent paths.
274+
*
275+
* @param path - The file or directory path to check.
276+
* @returns A promise resolving to a FileSystemAccessResult object with detailed access flags.
277+
*
278+
* @example
279+
* const info = await fs.accessInfo("/data/adb/file.txt");
280+
* if (info.canRead && info.canWrite) {
281+
* // File is both readable and writable
282+
* }
283+
*/
284+
accessInfo(path: string): Promise<FileSystemAccessResult>;
285+
}
286+
287+
/**
288+
* Extends the Window interface to optionally include a file system API.
289+
*/
290+
interface Window {
291+
/**
292+
* Optional file system API instance.
293+
*/
294+
fs?: FileSystem;
295+
}
296+
297+
export {};
298+
299+
declare global {
300+
interface Window {
301+
fs?: FileSystem;
302+
}
303+
}
304+
```
305+
306+
</details>

0 commit comments

Comments
 (0)