native-file-system-adapter
Advanced tools
Comparing version 1.0.1 to 1.1.0
@@ -38,3 +38,6 @@ const WRITE = 0 | ||
// enqueue() will call pull() if needed when there's no backpressure | ||
if (message.type === WRITE) this.controller.enqueue(message.chunk) | ||
if (message.type === WRITE) { | ||
this.controller.enqueue(message.chunk) | ||
this.port.postMessage({ type: PULL }) | ||
} | ||
if (message.type === ABORT) { | ||
@@ -41,0 +44,0 @@ this.controller.error(message.reason) |
@@ -96,4 +96,4 @@ import * as fs from '../src/es6.js' | ||
accept: { | ||
'text/plain': ['txt', 'text'], | ||
'text/html': ['html', 'htm'] | ||
'text/plain': ['.txt', '.text'], | ||
'text/html': ['.html', '.htm'] | ||
} | ||
@@ -104,3 +104,3 @@ }, | ||
accept: { | ||
'image/*': ['png', 'gif', 'jpeg', 'jpg'] | ||
'image/*': ['.png', '.gif', '.jpeg', '.jpg'] | ||
} | ||
@@ -111,11 +111,5 @@ } | ||
$types2.value = JSON.stringify([ | ||
{ | ||
accept: { 'image/jpg': ['jpg'] } | ||
}, | ||
{ | ||
accept: { 'image/png': ['png'] } | ||
}, | ||
{ | ||
accept: { 'image/webp': ['webp'] } | ||
} | ||
{ accept: { 'image/jpg': ['.jpg'] } }, | ||
{ accept: { 'image/png': ['.png'] } }, | ||
{ accept: { 'image/webp': ['.webp'] } } | ||
], null, 2) | ||
@@ -136,3 +130,9 @@ | ||
opts._preferPolyfill = !!opts._preferPolyfill | ||
showOpenFilePicker(opts).then(console.log, console.error) | ||
showOpenFilePicker(opts).then(handles => { | ||
console.log(handles) | ||
alert(handles) | ||
}, err => { | ||
console.error(err) | ||
alert(err) | ||
}) | ||
} | ||
@@ -149,4 +149,4 @@ form_showSaveFilePicker.onsubmit = async evt => { | ||
const ws = await handle.createWritable() | ||
ws.write(image) | ||
ws.close() | ||
await ws.write(image) | ||
await ws.close() | ||
} | ||
@@ -165,3 +165,6 @@ | ||
j++ | ||
if (driver.status === 'rejected') continue | ||
if (driver.status === 'rejected') { | ||
console.error('Driver failed to load:' + driver.reason) | ||
continue | ||
} | ||
const root = driver.value | ||
@@ -194,6 +197,15 @@ await cleanupSandboxedFileSystem(root) | ||
for (const item of evt.dataTransfer.items) { | ||
item.getAsFileSystemHandle().then(showFileStructure) | ||
item.getAsFileSystemHandle().then(async handle => { | ||
if (handle.kind === 'directory') { | ||
showFileStructure(handle) | ||
} else { | ||
const file = await handle.getFile() | ||
console.log(file) | ||
alert(file) | ||
} | ||
}) | ||
} | ||
} | ||
/** @param {fs.FileSystemDirectoryHandle} root */ | ||
async function showFileStructure (root) { | ||
@@ -200,0 +212,0 @@ const result = [] |
{ | ||
"name": "native-file-system-adapter", | ||
"version": "1.0.1", | ||
"version": "1.1.0", | ||
"description": "Native File System API", | ||
@@ -13,5 +13,5 @@ "main": "src/es6.js", | ||
"test": "exit 0", | ||
"test:browser": "airtap --local test/browser.js", | ||
"test:server": "node test/server-side.js", | ||
"test:memory": "node test/memory.js", | ||
"test:browser": "php -S localhost:4444 & open http://localhost:4444/example/test.html", | ||
"test:node": "node test/test-node.js", | ||
"test:deno": "deno run --allow-read test/test-deno.js", | ||
"build": "rm -rf dist && rollup -c rollup.config.js" | ||
@@ -23,3 +23,14 @@ }, | ||
}, | ||
"keywords": ["filesystem", "file", "blob", "stream", "fs", "read", "write", "delete", "file system access", "spec"], | ||
"keywords": [ | ||
"filesystem", | ||
"file", | ||
"blob", | ||
"stream", | ||
"fs", | ||
"read", | ||
"write", | ||
"delete", | ||
"file system access", | ||
"spec" | ||
], | ||
"author": "Jimmy Wärting", | ||
@@ -30,5 +41,10 @@ "license": "MIT", | ||
}, | ||
"contributors": [ | ||
{ | ||
"name": "Alexandru Ciuca" | ||
} | ||
], | ||
"homepage": "https://github.com/jimmywarting/native-file-system-adapter#readme", | ||
"optionalDependencies": { | ||
"web-streams-polyfill": "^2.1.1" | ||
"web-streams-polyfill": "^3.1.1" | ||
}, | ||
@@ -44,3 +60,3 @@ "dependencies": { | ||
"rollup-plugin-terser": "^7.0.2", | ||
"standardjs": "*", | ||
"standard": "^16.0.3", | ||
"tape": "^5.2.2" | ||
@@ -47,0 +63,0 @@ }, |
205
README.md
@@ -5,25 +5,28 @@ # Native File System adapter (ponyfill) | ||
This is file system that follows [native-file-system](https://wicg.github.io/native-file-system/) specification. Thanks to it we can have a unified way of handling data in all browser and even in NodeJS & Deno in a more secure way. | ||
This is a file system API that follows the [File System Access](https://wicg.github.io/file-system-access/) specification. Thanks to it we can have a unified way of handling data in all browsers and even in NodeJS & Deno in a more secure way. | ||
At a high level what we're providing is several bits: | ||
1. A modernized version of `FileSystemFileHandle` and `FileSystemDirectoryHandle` interfaces. | ||
2. A modernized version of `FileSystemWritableFileStream` to truncate and write data. | ||
3. A way to not only use one location to read & write data to and from, but several other sources called adapters | ||
1. Ponyfills for `showDirectoryPicker`, `showOpenFilePicker` and `showSaveFilePicker`, with fallbacks to regular input elements. | ||
2. Ponyfills for `FileSystemFileHandle` and `FileSystemDirectoryHandle` interfaces. | ||
3. Ponyfill for `FileSystemWritableFileStream` to truncate and write data. | ||
4. An implementation of `navigator.storage.getDirectory()` (`getOriginPrivateDirectory`) which can read & write data to and from several sources called adapters, not just the browser sandboxed file system | ||
5. An polyfill for `DataTransferItem.prototype.getAsFileSystemHandle()` | ||
### Adapters | ||
## File system adapters | ||
This polyfill/ponyfill ships with a few backends built in: | ||
When `getOriginPrivateDirectory` is called with no arguments, the browser's native sandboxed file system is used, just like calling `navigator.storage.getDirectory()`. | ||
* `node`: Interact with filesystem using NodeJS `fs` | ||
Optionally, a file system backend adapter can be provided as an argument. This ponyfill ships with a few backends built in: | ||
* `node`: Uses NodeJS's `fs` module | ||
* `deno`: Interact with filesystem using Deno | ||
* `native`: Stores files the `Native Sandboxed` file file system storage | ||
* `Sandbox`: Stores files into the Blinks `Sandboxed FileSystem` API. | ||
* `IndexedDB`: Stores files into the browser's `IndexedDB` object database. | ||
* `Memory`: Stores files in-memory. Thus, it is a temporary file store that clears when the user navigates away. | ||
* `Cache storage`: Stores files in cache storage like a request/response a-like. | ||
* `sandbox` (deprecated): Uses [requestFileSystem](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestFileSystem). Only supported in Chromium-based browsers using the `Blink` engine. | ||
* `indexeddb`: Stores files into the browser's `IndexedDB` object database. | ||
* `memory`: Stores files in-memory. Thus, it is a temporary file store that clears when the user navigates away. | ||
* `cache`: Stores files with the browser's [Cache API](https://web.dev/cache-api-quick-guide/) in request/response pairs. | ||
You can even load in your own underlying adapter and get the same set of API's | ||
The api is designed in such a way that it can work with or without the ponyfill if you choose to remove or add this.<br> | ||
The API is designed in such a way that it can work with or without the ponyfill if you choose to remove or add this.<br> | ||
It's not trying to interfere with the changing spec by using other properties that may conflict with the feature changes to the spec. | ||
@@ -34,59 +37,92 @@ | ||
### Using | ||
### ES import in browser | ||
```html | ||
<script type="module"> | ||
import { getOriginPrivateDirectory } from 'https://cdn.jsdelivr.net/npm/native-file-system-adapter/mod.js' | ||
// Get a directory handle for a sandboxed virtual file system | ||
// same as calling navigator.storage.getDirectory() | ||
const dirHandle1 = await getOriginPrivateDirectory() | ||
// or use an adapter (see adapters table above for a list of available adapters) | ||
const dirHandle2 = await getOriginPrivateDirectory(import('https://cdn.jsdelivr.net/npm/native-file-system-adapter/src/adapters/<adapterName>.js')) | ||
</script> | ||
``` | ||
``` | ||
npm i native-file-system-adapter | ||
``` | ||
```js | ||
import { | ||
showDirectoryPicker, | ||
showOpenFilePicker, | ||
showSaveFilePicker, | ||
FileSystemDirectoryHandle, | ||
FileSystemFileHandle, | ||
FileSystemHandle, | ||
FileSystemWritableFileStream, | ||
getOriginPrivateDirectory | ||
} from 'https://cdn.jsdelivr.net/gh/jimmywarting/native-file-system-adapter/src/es6.js' | ||
import { getOriginPrivateDirectory } from 'native-file-system-adapter' | ||
import indexedDbAdapter from 'native-file-system-adapter/lib/adapters/indexeddb.js' | ||
import nodeAdapter from 'native-file-system-adapter/lib/adapters/node.js' | ||
const dirHandle = await getOriginPrivateDirectory(indexedDbAdapter) | ||
const nodeDirHandle = await getOriginPrivateDirectory(nodeAdapter, './real-dir') | ||
``` | ||
// the getOriginPrivateDirectory is a legacy name that | ||
// native filesystem added, have not bother to change it | ||
## Examples | ||
// same as calling navigator.storage.getDirectory() | ||
handle = await getOriginPrivateDirectory() | ||
// A write only adapter to save files to the disk | ||
handle = await getOriginPrivateDirectory(import('../src/adapters/downloader.js')) | ||
// Blinks old sandboxed api | ||
handle = await getOriginPrivateDirectory(import('../src/adapters/sandbox.js')) | ||
// fast in-memory file system | ||
handle = await getOriginPrivateDirectory(import('../src/adapters/memory.js')) | ||
// Using indexDB | ||
handle = await getOriginPrivateDirectory(import('../src/adapters/indexeddb.js')) | ||
// Store things in the new Cache API as request/responses (bad at mutating data) | ||
handle = await getOriginPrivateDirectory(import('../src/adapters/cache.js')) | ||
### File system sandbox | ||
// Node only variant: | ||
handle = await getOriginPrivateDirectory(import('native-file-system-adapter/src/adapters/memory.js')) | ||
handle = await getOriginPrivateDirectory(import('native-file-system-adapter/src/adapters/node.js'), './starting-path') | ||
You can get a directory handle to a sandboxed virtual file system using the `getOriginPrivateDirectory` function. | ||
This is a legacy name introduced by an older `Native File System` specification and is kept for simplicity. | ||
It is equivalent to the `navigator.storage.getDirectory()` method introduced by the later [File System Access](https://wicg.github.io/file-system-access/) spec. | ||
// Deno only variant: | ||
handle = await getOriginPrivateDirectory(import('native-file-system-adapter/src/adapters/memory.js')) | ||
handle = await getOriginPrivateDirectory(import('native-file-system-adapter/src/adapters/deno.js'), './starting-path') | ||
```js | ||
import { getOriginPrivateDirectory, support } from 'native-file-system-adapter' | ||
// Uses native implementation - same as calling navigator.storage.getDirectory() | ||
await getOriginPrivateDirectory().catch(err => { | ||
// native implementation not supported fallback to any of the adapters | ||
// Blinks old sandboxed api | ||
handle = await getOriginPrivateDirectory(import('native-file-system-adapter/lib/adapters/sandbox.js')) | ||
// fast in-memory file system | ||
handle = await getOriginPrivateDirectory(import('native-file-system-adapter/lib/adapters/memory.js')) | ||
// Using indexDB | ||
handle = await getOriginPrivateDirectory(import('native-file-system-adapter/lib/adapters/indexeddb.js')) | ||
// Store things in the new Cache API as request/responses (bad at mutating data) | ||
handle = await getOriginPrivateDirectory(import('native-file-system-adapter/lib/adapters/cache.js')) | ||
// Node only variant: | ||
handle = await getOriginPrivateDirectory(import('native-file-system-adapter/lib/adapters/memory.js')) | ||
handle = await getOriginPrivateDirectory(import('native-file-system-adapter/lib/adapters/node.js'), './starting-path') | ||
// Deno only variant: | ||
handle = await getOriginPrivateDirectory(import('native-file-system-adapter/src/adapters/memory.js')) | ||
handle = await getOriginPrivateDirectory(import('native-file-system-adapter/src/adapters/deno.js'), './starting-path') | ||
}) | ||
``` | ||
### File and directory pickers | ||
```js | ||
import { showDirectoryPicker, showOpenFilePicker } from 'native-file-system-adapter' | ||
// The polyfilled (file input) version will turn into a memory adapter | ||
// You will have read & write permission on the memory adapter, | ||
// you might want to transfer (copy) the handle to another adapter | ||
showOpenFilePicker({_preferPolyfill: boolean, ...sameOpts}).then(fileHandle => {}) | ||
showDirectoryPicker({_preferPolyfill: boolean, ...sameOpts}).then(directoryHandle => {}) | ||
const [fileHandle] = await showOpenFilePicker({_preferPolyfill: boolean, ...sameOpts}) | ||
const dirHandle = await showDirectoryPicker({_preferPolyfill: boolean, ...sameOpts}) | ||
``` | ||
// Supports drag and drop also | ||
window.ondrop = evt => { | ||
### Drag and drop | ||
```js | ||
// DataTransferItem.prototype.getAsFileSystemHandle() is conditionally polyfilled | ||
import 'native-file-system-adapter' | ||
window.ondrop = async evt => { | ||
evt.preventDefault() | ||
for (let item of evt.dataTransfer.items) { | ||
item.getAsFileSystemHandle(handle => { | ||
console.log(handle) | ||
}) | ||
const handle = await item.getAsFileSystemHandle() | ||
console.log(handle) | ||
} | ||
} | ||
``` | ||
### Copy file handles to sandboxed file system | ||
```js | ||
import { showOpenFilePicker, getOriginPrivateDirectory } from 'native-file-system-adapter' | ||
// request user to select a file | ||
const fileHandle = await showOpenFilePicker({ | ||
const [fileHandle] = await showOpenFilePicker({ | ||
types: [], // default | ||
@@ -104,6 +140,12 @@ multiple: false, // default | ||
const fileHandle = await rootHandle.getFileHandle(file.name, { create: true }) | ||
await fileHandle.write(file) | ||
fileHandle.close() | ||
const writable = await fileHandle.createWritable() | ||
await writable.write(file) | ||
await writable.close() | ||
``` | ||
// save/download a file | ||
### Save / download a file | ||
```js | ||
import { showSaveFilePicker } from 'native-file-system-adapter' | ||
const fileHandle = await showSaveFilePicker({ | ||
@@ -129,20 +171,33 @@ _preferPolyfill: false, | ||
await blob.stream().pipeTo(fileHandle.getWriter()) | ||
await blob.stream().pipeTo(fileHandle.createWritable()) | ||
// or | ||
var writer = fileHandle.getWriter() | ||
writer.write(blob) | ||
writer.close() | ||
var writer = fileHandle.getWritable() | ||
await writer.write(blob) | ||
await writer.close() | ||
``` | ||
PS: storing a file handle in IndexedDB or sharing it with postMessage isn't currently possible unless you use native. | ||
Will get to it at some point in the feature | ||
## Supported browsers | ||
### A note when downloading with the polyfilled version | ||
When importing as an ES module, browsers that support [dynamic imports](https://caniuse.com/es6-module-dynamic-import) and ES2018 features are a minimum requirement. When using a bundler, this restriction is no longer applicable. | ||
Saving/downloading a file borrowing some of ideas from [StreamSaver.js](https://github.com/jimmywarting/StreamSaver.js). | ||
When the directory picker falls back to `input` elements, the browser must support [webkitdirectory](https://caniuse.com/mdn-api_htmlinputelement_webkitdirectory) and [webkitRelativePath](https://caniuse.com/mdn-api_file_webkitrelativepath). Because of this, support for picking directories is generally poor on Mobile browsers. | ||
For drag and drop, the `getAsFileSystemHandle()` polyfill depends on the `File and Directory Entries API` support, more specifically [FileSystemDirectoryEntry](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryEntry), [FileSystemFileEntry](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileEntry) and [webkitGetAsEntry](https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry). | ||
## Limitations | ||
- Storing a file handle in IndexedDB or sharing it with postMessage isn't currently possible unless you use native. | ||
- `showDirectoryPicker` and `showOpenFilePicker` will not throw any `AbortError`s (e.g. user cancellations) when using a fallback input element | ||
- `showSaveFilePicker` may not actually show any prompt when using a fallback with `<a download>` | ||
- Cache adapter only works in secure (HTTPS) contexts `window.isSecureContext === true` | ||
- IndexedDB adapter may not work in some browsers in Private mode | ||
## A note when downloading with the polyfilled version | ||
Saving/downloading a file is borrowing some of ideas from [StreamSaver.js](https://github.com/jimmywarting/StreamSaver.js). | ||
The difference is: | ||
- Using service worker is optional choice with this adapter. | ||
- It dose not rely on some man-in-the-middle or external hosted service worker. | ||
- It does not rely on some man-in-the-middle or external hosted service worker. | ||
- If you want to stream large data to the disk directly instead of taking up much RAM you need to set up a service worker yourself.<br>(note that it requires https - but again worker is optional) | ||
- You don't have to handle web-streams-polyfills it's lazy loaded when needed when you need that writable stream. 😴 | ||
@@ -159,16 +214,18 @@ to set up a service worker you have to basically copy [the example](https://github.com/jimmywarting/native-file-system-adapter/tree/master/example/sw.js) and register it: | ||
### Testing | ||
## Testing | ||
- start up a server and open `/examples/test.html` in your browser. | ||
- for node: `npm i && npm run test` | ||
- For browser tests: In project folder, run `php -S localhost:3000` or `npx http-server -p 3000 .` | ||
- open `http://localhost:3000/example/test.html` in your browser. | ||
- For node: `npm run test-node` | ||
- For deno: `npm run test-deno` | ||
### Resources | ||
## Resources | ||
I recommend to follow up on this links for you to learn more about the API and how it works | ||
- https://web.dev/native-file-system/ | ||
- https://wicg.github.io/native-file-system/ | ||
- https://github.com/wicg/native-file-system | ||
- https://web.dev/file-system-access | ||
- https://wicg.github.io/file-system-access | ||
- https://github.com/WICG/file-system-access | ||
### Alternatives | ||
## Alternatives | ||
- [browser-fs-access](https://github.com/GoogleChromeLabs/browser-fs-access) by [@tomayac](https://github.com/tomayac): A similar, more like a shim (without `getSystemDirectory`). | ||
@@ -178,4 +235,4 @@ - [StreamSaver](https://github.com/jimmywarting/StreamSaver.js) by [@jimmywarting](https://github.com/jimmywarting): A way to save large data to the disk directly with a writable stream <br><small>(same technique can be achieved if service worker are setup properly)</small> | ||
### License | ||
## License | ||
native-file-system-adapter is licensed under the MIT License. See `LICENSE` for details. |
@@ -1,4 +0,2 @@ | ||
// @ts-check | ||
import { join, basename } from 'https://deno.land/std@0.98.0/path/mod.ts' | ||
import { join, basename } from 'https://deno.land/std@0.108.0/path/mod.ts' | ||
import { errors } from '../util.js' | ||
@@ -5,0 +3,0 @@ |
@@ -27,3 +27,3 @@ /* global Blob, DOMException, Response, MessageChannel */ | ||
// @ts-ignore | ||
const ponyfill = await import('https://cdn.jsdelivr.net/npm/web-streams-polyfill@2.1.1/dist/ponyfill.es2018.mjs') | ||
const ponyfill = await import('https://cdn.jsdelivr.net/npm/web-streams-polyfill@3/dist/ponyfill.es2018.mjs') | ||
TransformStream = ponyfill.TransformStream | ||
@@ -30,0 +30,0 @@ WritableStream = ponyfill.WritableStream |
@@ -1,8 +0,7 @@ | ||
import fs from 'fs/promises' | ||
import { errors } from '../util.js' | ||
import { join } from 'path' | ||
import fs from 'node:fs/promises' | ||
import { join } from 'node:path' | ||
import 'node-domexception' | ||
import Blob from 'fetch-blob' | ||
import { fileFrom } from 'fetch-blob/from.js' | ||
import DOMException from 'node-domexception' | ||
import { errors } from '../util.js' | ||
// import mime from 'mime-types' | ||
@@ -9,0 +8,0 @@ |
@@ -19,16 +19,30 @@ const kAdapter = Symbol('adapter') | ||
async queryPermission (options = {}) { | ||
if (options.readable) return 'granted' | ||
async queryPermission ({mode = 'read'} = {}) { | ||
const handle = this[kAdapter] | ||
return handle.queryPermission ? | ||
await handle.queryPermission(options) : | ||
handle.writable | ||
? 'granted' | ||
: 'denied' | ||
if (handle.queryPermission) { | ||
return handle.queryPermission({mode}) | ||
} | ||
if (mode === 'read') { | ||
return 'granted' | ||
} else if (mode === 'readwrite') { | ||
return handle.writable ? 'granted' : 'denied' | ||
} else { | ||
throw new TypeError(`Mode ${mode} must be 'read' or 'readwrite'`) | ||
} | ||
} | ||
async requestPermission (options = {}) { | ||
if (options.readable) return 'granted' | ||
async requestPermission ({mode = 'read'} = {}) { | ||
const handle = this[kAdapter] | ||
return handle.writable ? 'granted' : 'denied' | ||
if (handle.requestPermission) { | ||
return handle.requestPermission({mode}) | ||
} | ||
if (mode === 'read') { | ||
return 'granted' | ||
} else if (mode === 'readwrite') { | ||
return handle.writable ? 'granted' : 'denied' | ||
} else { | ||
throw new TypeError(`Mode ${mode} must be 'read' or 'readwrite'`) | ||
} | ||
} | ||
@@ -51,3 +65,8 @@ | ||
if (this === other) return true | ||
if (this.kind !== other.kind) return false | ||
if ( | ||
(!other) || | ||
(typeof other !== 'object') || | ||
(this.kind !== other.kind) || | ||
(!other[kAdapter]) | ||
) return false | ||
return this[kAdapter].isSameEntry(other[kAdapter]) | ||
@@ -54,0 +73,0 @@ } |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
233
0
1
96099
26
2392