@environment-safe/file 0.0.1 → 0.1.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/.husky/pre-commit CHANGED
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env sh
2
2
  . "$(dirname -- "$0")/_/husky.sh"
3
3
 
4
- npm run lint
4
+ #npm run lint
5
+ npm run path-test
6
+ npm run headless-browser-path-test
5
7
  #npm run import-test
6
- #npm run headless-browser-test
7
- npm run build-commonjs
8
+ npm run headless-browser-test
9
+ #npm run build-commonjs
8
10
  #npm run require-test
9
11
  npm run build-docs
10
12
  npm run build-types
package/README.md CHANGED
@@ -4,9 +4,24 @@ This is an experimental interface to provide a common file abstraction from clie
4
4
 
5
5
  The design goal is to give the widest possible filesystem access, while minimizing the number of client interactions (via interaction initiation *or* popup) using a common API.
6
6
 
7
+ You may need native paths, relative paths, file paths or web paths and may be running from an environment with a current directory or a web page with both a native location as well as a webroot. This allows all scenarios in all environments.
8
+
9
+ The browser file APIs are a total mess: with 6 different addressable formats, embedded proprietary formats and a bizarre array of interaction scenarios between client and server, and the abstraction does not fully cover all use cases yet.
10
+
7
11
  Usage
8
12
  -----
9
13
 
14
+ If you want absolute URLs to work (raw, file://, etc.), you must include a base tag in your html with a `filesystem` attribute that denotes the filesystem path of the server root. If not present, it assumes we are on a remote server.
15
+
16
+ ```html
17
+ <html>
18
+ <head>
19
+ <base filesystem="/Users/foo/webroot/" user="foo">
20
+ </head>
21
+ <body></body>
22
+ </html>
23
+ ```
24
+
10
25
  ### listing
11
26
 
12
27
  You can list contents from an arbitrary location or from one of a few predefined locations (`desktop`, `documents`, `downloads`, `music`, `pictures`, `videos`). For example to list all the files in your `documents` directory:
@@ -18,27 +33,26 @@ const list = await File.list('documents', {
18
33
  });
19
34
  ```
20
35
 
36
+ ### loading
21
37
  You can load a file relative to the current directory, for example `foo.bar`
22
38
 
23
39
  ```javascript
24
40
  const file = new File('foo.bar');
25
- //or
26
- const file = new File('foo.bar', '.');
27
41
  ```
28
42
 
29
43
  You can load a file relative to the a predefined directory, for example `baz.mpg` in `videos`:
30
44
  ```javascript
31
- const file = new File('baz.mpg', 'videos');
45
+ const file = new File(Path.join(Path.location('videos'), 'baz.mpg'));
32
46
  ```
33
47
 
34
48
  You can load a file relative to the a fully specified directory, for example `baz.info` in `/Users/me/`:
35
49
  ```javascript
36
- const file = new File('baz.info', '/Users/me/');
50
+ const file = new File(Path.join(Path.location('home'), 'baz.info'));
37
51
  ```
38
52
 
39
- You can load a file relative to the a relative directory, for example `package.json` in `../node_modules/dep`:
53
+ You can load a file relative to the web root, for example `package.json` in `../node_modules/dep`:
40
54
  ```javascript
41
- const file = new File('package.json', '../node_modules/dep');
55
+ const file = new File('../node_modules/dep/package.json'));
42
56
  ```
43
57
 
44
58
  You can load a file directly from a fully specified path:
@@ -52,7 +66,11 @@ Other scenarios may work in isolated circumstances, but are not supported client
52
66
  Roadmap
53
67
  -------
54
68
 
55
- - [ ] - test existing suite in windows + linux
69
+ - [x] - test existing suite in mac node
70
+ - [ ] - test existing suite in in chrome + server
71
+ - [ ] - test existing suite in in chrome + file
72
+ - [ ] - test existing suite in windows node
73
+ - [ ] - test existing suite in linux node
56
74
  - [ ] - safari directory returns
57
75
  - [ ] - firefox directory returns
58
76
  - [ ] - edge directory returns
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@environment-safe/file",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.mjs",
6
6
  "module": "src/index.mjs",
@@ -58,10 +58,13 @@
58
58
  "stubs": [
59
59
  "express",
60
60
  "module",
61
+ "fs",
62
+ "os",
61
63
  "path"
62
64
  ],
63
65
  "shims": {
64
66
  "chai": "node_modules/chai/chai.js",
67
+ "@environment-safe/runtime-context": "node_modules/@environment-safe/runtime-context/./src/index.mjs",
65
68
  "browser-or-node": "node_modules/browser-or-node/src/index.js"
66
69
  }
67
70
  },
@@ -73,7 +76,7 @@
73
76
  "@environment-safe/chai": "^0.1.0",
74
77
  "@environment-safe/commonjs-builder": "^0.0.3",
75
78
  "@environment-safe/jsdoc-builder": "^0.0.2",
76
- "@open-automaton/moka": "^0.0.4",
79
+ "@open-automaton/moka": "^0.4.0",
77
80
  "babel-plugin-search-and-replace": "^1.1.1",
78
81
  "babel-plugin-transform-import-meta": "^2.2.0",
79
82
  "chai": "^4.3.7",
@@ -86,15 +89,23 @@
86
89
  "lint": "./node_modules/.bin/eslint src/*.mjs test/*.mjs",
87
90
  "test": "npm run import-test; npm run browser-test; npm run require-test",
88
91
  "import-test": "./node_modules/.bin/mocha test/test.mjs",
92
+ "import-file-test": "moka --file test/test.html --browser safari file://$(pwd)/test/test.mjs",
93
+ "browser-file-test": "moka --verbose --file test/test.html --local-browser file://$(pwd)/test/test.mjs",
89
94
  "require-test": "./node_modules/.bin/mocha test/test.cjs",
95
+ "path-test": "mocha test/isolated/path.mjs",
96
+ "browser-path-test": "moka --server . --local-browser test/isolated/path.mjs",
97
+ "headless-browser-path-test": "npm run headless-chrome-path-test; npm run headless-firefox-path-test; npm run headless-safari-path-test",
98
+ "headless-chrome-path-test": "moka --server . --browser chrome test/isolated/path.mjs",
99
+ "headless-firefox-path-test": "moka --server . --browser firefox test/isolated/path.mjs",
100
+ "headless-safari-path-test": "moka --server . --browser safari test/isolated/path.mjs",
90
101
  "build-test-importmap": "wing-kong -i .import-config.json -f test/index.html rewrite dependencies",
91
102
  "build-commonjs": "./node_modules/@environment-safe/commonjs-builder/bin/build.mjs",
92
103
  "local-server": "open 'http://localhost:8085/' ; npx http-server -p 8085",
93
104
  "headless-browser-test": "npm run headless-chrome-test; npm run headless-firefox-test; npm run headless-safari-test",
94
- "headless-chrome-test": "moka --server . --browser chrome --relaxed --prefix ../ test/test.mjs",
105
+ "headless-chrome-test": "moka --server . --browser chrome test/test.mjs",
95
106
  "headless-firefox-test": "moka --server . --browser firefox --relaxed --prefix ../ test/test.mjs",
96
107
  "headless-safari-test": "moka --server . --browser safari --relaxed --prefix ../ test/test.mjs",
97
- "browser-test": "moka --server . --local-browser --relaxed --prefix ../ test/test.mjs",
108
+ "browser-test": "moka --server . --local-browser test/test.mjs",
98
109
  "container-test": "docker build . -t environment-safe-package.json -f ./containers/test.dockerfile; docker logs --follow \"$(docker run -d environment-safe-package.json)\"",
99
110
  "build-docs": "build-jsdoc docs",
100
111
  "build-types": "build-jsdoc types",
@@ -102,6 +113,9 @@
102
113
  "prepare": "husky install"
103
114
  },
104
115
  "dependencies": {
105
- "browser-or-node": "^2.1.1"
116
+ "@environment-safe/package": "^0.2.0",
117
+ "@environment-safe/runtime-context": "^0.0.1",
118
+ "browser-or-node": "^2.1.1",
119
+ "typescript": "^5.2.2"
106
120
  }
107
121
  }
@@ -0,0 +1,5 @@
1
+ export function FileBuffer(): void;
2
+ /**
3
+ * A JSON object
4
+ */
5
+ export type JSON = object;
package/src/buffer.mjs CHANGED
@@ -21,10 +21,11 @@ let InternalBuffer = null;
21
21
 
22
22
  if(isBrowser || isJsDom){
23
23
  InternalBuffer = function(){};
24
- var enc = new TextEncoder();
25
- var dec = new TextDecoder('utf-8');
24
+ const enc = new TextEncoder();
25
+ const dec = new TextDecoder(); //'utf-8'
26
26
  InternalBuffer.from = (ob)=>{
27
27
  const type = Array.isArray(ob)?'array':(typeof ob);
28
+ if(InternalBuffer.is(ob)) return ob;
28
29
  switch(type){
29
30
  case 'object':
30
31
  case 'array':
@@ -33,12 +34,17 @@ if(isBrowser || isJsDom){
33
34
  case '':
34
35
  }
35
36
  };
37
+ InternalBuffer.is = (buffer)=>{
38
+ return buffer instanceof ArrayBuffer;
39
+ };
36
40
  InternalBuffer.to = (type, buffer)=>{
41
+ let result = null;
37
42
  switch(type){
38
43
  case 'object':
39
44
  case 'array':
40
45
  case 'string':
41
- return dec.decode(buffer);
46
+ result = dec.decode(buffer);
47
+ return result;
42
48
  case '':
43
49
  }
44
50
  };
@@ -0,0 +1,47 @@
1
+ export namespace localFile {
2
+ function initialize(): Promise<{
3
+ exists: (path: any, options?: {}) => Promise<boolean>;
4
+ list: (path: any, options?: {}) => Promise<void>;
5
+ create: (path: any, options?: {}) => Promise<boolean>;
6
+ read: (path: any, options?: {}) => Promise<any>;
7
+ write: (path: any, buffer: any, options?: {}) => Promise<boolean>;
8
+ delete: (path: any, options?: {}) => Promise<void>;
9
+ }>;
10
+ }
11
+ export namespace serverFile {
12
+ export function initialize_1(): Promise<{
13
+ exists: (filePath: any, options?: {}) => Promise<any>;
14
+ list: (path: any, options?: {}) => Promise<any>;
15
+ create: (path: any, options?: {}) => Promise<any>;
16
+ read: (path: any, options?: {}) => Promise<any>;
17
+ write: (path: any, buffer: any, options?: {}) => Promise<any>;
18
+ delete: (path: any, options?: {}) => Promise<any>;
19
+ }>;
20
+ export { initialize_1 as initialize };
21
+ }
22
+ export namespace file {
23
+ export function initialize_2(): Promise<{
24
+ exists: (path: any, options?: {}) => Promise<any>;
25
+ list: (path: any, options?: {}) => Promise<void>;
26
+ create: (path: any, options?: {}) => Promise<never>;
27
+ read: (path: any, options?: {}) => Promise<any>;
28
+ write: (path: any, buffer: any, options?: {}) => Promise<never>;
29
+ delete: (path: any, options?: {}) => Promise<never>;
30
+ }>;
31
+ export { initialize_2 as initialize };
32
+ }
33
+ export namespace remote {
34
+ export function initialize_3(): Promise<{
35
+ exists: (path: any, options?: {}) => Promise<boolean>;
36
+ list: (path: any, options?: {}) => Promise<void>;
37
+ create: (path: any, options?: {}) => Promise<never>;
38
+ read: (path: any, options?: {}) => Promise<any>;
39
+ write: (path: any, buffer: any, options?: {}) => Promise<never>;
40
+ delete: (path: any, options?: {}) => Promise<never>;
41
+ }>;
42
+ export { initialize_3 as initialize };
43
+ }
44
+ /**
45
+ * A JSON object
46
+ */
47
+ export type JSON = object;
@@ -0,0 +1,449 @@
1
+ /*
2
+ import { isBrowser, isJsDom } from 'browser-or-node';
3
+ import * as mod from 'module';
4
+ import * as path from 'path';
5
+ let internalRequire = null;
6
+ if(typeof require !== 'undefined') internalRequire = require;
7
+ const ensureRequire = ()=> (!internalRequire) && (internalRequire = mod.createRequire(import.meta.url));
8
+ //*/
9
+
10
+ /**
11
+ * A JSON object
12
+ * @typedef { object } JSON
13
+ */
14
+
15
+ // Isn't the fact that we had 2 path scenarios driven by OS, fixed it, (win, unix)
16
+ // and we now have 4 an obvious indicator that we went wrong? (win, unix, file, known dirs)
17
+
18
+ import { FileBuffer } from './buffer.mjs';
19
+ import * as fs from 'fs';
20
+ import {
21
+ isClient // is running a client
22
+ } from '@environment-safe/runtime-context';
23
+ import { Path } from './path.mjs';
24
+ //TODO: Streaming
25
+
26
+ //TODO: browser filesystem contexts
27
+
28
+ const mimesBySuffix = {
29
+ json : 'application/json',
30
+ jpg : 'image/jpeg',
31
+ jpeg : 'image/jpeg',
32
+ gif : 'image/gif',
33
+ png : 'image/png',
34
+ svg : 'image/sxg+xml',
35
+ webp : 'image/webp',
36
+ csv : 'text/csv',
37
+ tsv : 'text/tsv',
38
+ ssv : 'text/ssv',
39
+ js : 'text/javascript',
40
+ mjs : 'text/javascript',
41
+ cjs : 'text/javascript',
42
+ css : 'text/css',
43
+ };
44
+
45
+ const getFilePickerOptions = (path)=>{
46
+ const isLocation = Path.isLocation(path);
47
+ const parsedPath = new Path(path);
48
+ let suffix = path.split('.').pop();
49
+ if(suffix.length > 6) suffix = '';
50
+ const options = {
51
+ suggestedName: name
52
+ };
53
+ if(isLocation){
54
+ options.suggestedName = isLocation.remainingPath;
55
+ options.startIn = isLocation.location;
56
+ //eat leading slash
57
+ if(options.suggestedName[0] === '/') options.suggestedName = options.suggestedName.substring(1);
58
+ }else{
59
+ const posixPath = parsedPath.toUrl('posix', true);
60
+ const parts = posixPath.split('/');
61
+ options.suggestedName = parts.pop();
62
+ options.startIn = parts.join('/');
63
+ }
64
+ if(suffix){
65
+ const accept = {};
66
+
67
+ accept[mimesBySuffix[suffix]] = '.'+suffix;
68
+ options.types = [{
69
+ description: suffix,
70
+ accept,
71
+ }];
72
+ options.excludeAcceptAllOption = true;
73
+ }
74
+ //global hell
75
+ // here we need to push this into a global because there is no way to scope drill
76
+ globalThis._lastFilePickerOptions = options;
77
+ return options;
78
+ };
79
+
80
+
81
+ /*
82
+ const filesystem = ()=>{ //get a browser filesystem context
83
+ return new Promise((resolve, reject)=>{
84
+ variables.requestFileSystem(
85
+ TEMPORARY,
86
+ 1024 * 1024 //1MB,
87
+ (fs) => {
88
+ resolve(fs);
89
+ },
90
+ (err)=>{
91
+ reject(fs);
92
+ },
93
+ );
94
+ });
95
+ };
96
+ */
97
+
98
+ // INPUT HACK: most file ops need to happen on a user input action and cannot be deferred:
99
+
100
+ const inputQueue = [];
101
+
102
+ let inputHandler = (event, inputQueue)=>{
103
+ if(inputQueue.length){
104
+ const input = inputQueue.shift();
105
+ try{
106
+ input.handler(event, input.resolve, input.reject);
107
+ }catch(ex){
108
+ inputQueue.unshift(input);
109
+ }
110
+ }
111
+ };
112
+
113
+ const attachInputGenerator = (eventType)=>{
114
+ const handler = (event)=>{
115
+ return inputHandler(event, inputQueue);
116
+ };
117
+ window.addEventListener('load', (event) => {
118
+ document.body.addEventListener(eventType, handler, false);
119
+ });
120
+ };
121
+
122
+ export const setInputHandler = (handler)=>{
123
+ inputHandler = handler;
124
+ };
125
+
126
+ let wantInput = async (type, id, handler, cache)=>{
127
+ const promise = new Promise((resolve, reject)=>{
128
+ inputQueue.push({ resolve, reject, handler });
129
+ });
130
+ const input = await promise;
131
+ return await input;
132
+ };
133
+
134
+ //this delivers a set of bindables to a bind target so they can respond to events
135
+ export const bindInput = ()=>{
136
+ return (wants)=>{
137
+ wantInput = wants;
138
+ return {
139
+ 'mousedown':(e)=>{
140
+ // event monitor (not currently used)
141
+ },
142
+ 'click':(e)=>{
143
+ // event monitor (not currently used)
144
+ }
145
+ };
146
+ };
147
+ };
148
+
149
+ if(isClient){
150
+ // this is only so things work out of the box by clicking on the page
151
+ // anything other than a test/demo needs better behavior
152
+ attachInputGenerator('mousedown');
153
+ // mousemove is cleanest, but seems unreliable
154
+ // attachInputGenerator('mousemove');
155
+ }
156
+
157
+ //const globalFileHandleCache = {read:{}, write:{}};
158
+
159
+ const fileHandle = async (path, options)=>{
160
+ if(options.isDirectory){
161
+ const dirHandle = await wantInput('click', location, (event, resolve, reject)=>{
162
+ const options = getFilePickerOptions(path);
163
+ try{
164
+ let found = false;
165
+ window.showDirectoryPicker(options).then(async (thisHandle)=>{
166
+ const values = (await thisHandle.values());
167
+ if(values){
168
+ for await (const entry of values){
169
+ found = found || entry.name === options.suggestedName;
170
+ }
171
+ }
172
+ resolve(found);
173
+ }).catch((ex)=>{
174
+ reject(ex);
175
+ });
176
+ }catch(ex){
177
+ reject(ex);
178
+ }
179
+ }, options.cache && options.cache.write);
180
+ return dirHandle;
181
+ }else{
182
+ if(options.isWritable){
183
+ const newHandle = await wantInput('click', location, (event, resolve, reject)=>{
184
+ const options = getFilePickerOptions(path);
185
+ try{
186
+ window.showSaveFilePicker(options).then((thisHandle)=>{
187
+ resolve(thisHandle);
188
+ }).catch((ex)=>{
189
+ reject(ex);
190
+ });
191
+ }catch(ex){
192
+ reject(ex);
193
+ }
194
+ }, options.cache && options.cache.write);
195
+ return newHandle;
196
+ }else{
197
+ return await wantInput('click', location, (event, resolve, reject)=>{
198
+ const options = getFilePickerOptions(path);
199
+ try{
200
+ window.showOpenFilePicker(options).then(([ handle ])=>{
201
+ resolve(handle);
202
+ }).catch((ex)=>{
203
+ reject(ex);
204
+ });
205
+ }catch(ex){
206
+ reject(ex);
207
+ }
208
+ }, options.cache && options.cache.read);
209
+ }
210
+ }
211
+ };
212
+
213
+ // END INPUT HACK
214
+
215
+ export const localFile = {
216
+ initialize : async ()=>{
217
+ return {
218
+ exists: async(path, options={})=>{
219
+ options.isDirectory = true;
220
+ try{
221
+ const handle = await fileHandle(path, options);
222
+ return !!handle;
223
+ }catch(ex){
224
+ return false;
225
+ }
226
+ },
227
+ list: async(path, options={})=>{
228
+ //const dirHandle = await window.showDirectoryPicker();
229
+ },
230
+ create: async (path, options={})=>{
231
+ try{
232
+ options.isWritable = true;
233
+ const handle = await fileHandle(path, options);
234
+ const writableStream = await handle.createWritable();
235
+ // write our file
236
+ await writableStream.write(FileBuffer.from(''));
237
+ // close the file and write the contents to disk.
238
+ await writableStream.close();
239
+ }catch(ex){
240
+ console.log(ex);
241
+ return false;
242
+ }
243
+ },
244
+ read: async (path, options={})=>{
245
+ try{
246
+ const handle = await fileHandle(path, options);
247
+ const file = await handle.getFile();
248
+ const result = await file.arrayBuffer();
249
+ return result;
250
+ }catch(ex){
251
+ console.log(ex);
252
+ return false;
253
+ }
254
+ },
255
+ write: async (path, buffer, options={})=>{
256
+ try{
257
+ options.isWritable = true;
258
+ const handle = await fileHandle(path, options);
259
+ const writableStream = await handle.createWritable();
260
+ // write our file
261
+ await writableStream.write(FileBuffer.from(buffer));
262
+ // close the file and write the contents to disk.
263
+ await writableStream.close();
264
+ }catch(ex){
265
+ console.log(ex);
266
+ return false;
267
+ }
268
+ },
269
+ delete: async (path, options={})=>{
270
+
271
+ }
272
+ };
273
+ }
274
+ };
275
+ export const serverFile = {
276
+ initialize : async ()=>{
277
+ return {
278
+ exists: async(filePath, options={})=>{
279
+ const parsed = new Path(filePath);
280
+ const url = parsed.toUrl('native');
281
+ return await new Promise((resolve, reject)=>{
282
+ fs.stat(url, (err, res)=>{
283
+ if(err){
284
+ return resolve(false);
285
+ }
286
+ resolve(true);
287
+ });
288
+ });
289
+ },
290
+ list: async(path, options={})=>{
291
+ const parsed = new Path(path);
292
+ const url = parsed.toUrl('native');
293
+ return await new Promise((resolve, reject)=>{
294
+ fs.readdir(url, { withFileTypes: true }, (err, files)=>{
295
+ if(err) return reject(err);
296
+ let results = files;
297
+ if(Object.keys(options).length){
298
+ if(options.files === false){
299
+ results = results.filter((file)=>{
300
+ return !file.isFile();
301
+ });
302
+ }
303
+ if(options.directories === false){
304
+ results = results.filter((file)=>{
305
+ return file.isFile();
306
+ });
307
+ }
308
+ if(!options.hidden){
309
+ results = results.filter((file)=>{
310
+ return file !== '.' && file !== '..';
311
+ });
312
+ }
313
+ }
314
+ resolve(results.map((file)=>{
315
+ return file.name;
316
+ }));
317
+ });
318
+ });
319
+ },
320
+ create: async (path, options={})=>{
321
+ return await new Promise((resolve, reject)=>{
322
+ const parsed = new Path(path);
323
+ const url = parsed.toUrl('native');
324
+ fs.writeFile(url, '', (err)=>{
325
+ if(err) return reject(err);
326
+ resolve();
327
+ });
328
+ });
329
+ },
330
+ read: async (path, options={})=>{
331
+ const parsed = new Path(path);
332
+ const url = parsed.toUrl('native');
333
+ return await new Promise((resolve, reject)=>{
334
+ fs.readFile(url, (err, buffer)=>{
335
+ if(err) return reject(err);
336
+ resolve(buffer);
337
+ });
338
+ });
339
+ },
340
+ write: async (path, buffer, options={})=>{
341
+ const parsed = new Path(path);
342
+ const url = parsed.toUrl('native');
343
+ return await new Promise((resolve, reject)=>{
344
+ fs.writeFile(url, buffer, (err)=>{
345
+ if(err) return reject(err);
346
+ resolve();
347
+ });
348
+ });
349
+ },
350
+ delete: async (path, options={})=>{
351
+ return await new Promise((resolve, reject)=>{
352
+ const parsed = new Path(path);
353
+ const url = parsed.toUrl('native');
354
+ fs.unlink(url, (err)=>{
355
+ if(err) return reject(err);
356
+ resolve();
357
+ });
358
+ });
359
+ }
360
+ };
361
+ }
362
+ };
363
+
364
+
365
+ export const file = { //using a file url uses different rules
366
+ initialize : async ()=>{
367
+ return {
368
+ exists: async(path, options={})=>{
369
+ const url = (new Path(path)).toUrl(options.type);
370
+ if(url.parsed){
371
+ if(Path.browserLocations.indexOf(url.parsed.location) !== -1 ){
372
+ return localFile.exists(url.parsed, {});
373
+ }
374
+ }
375
+ const response = await fetch(url);
376
+ return response.status === 200;
377
+ },
378
+ list: async(path, options={})=>{
379
+ //const dirHandle = await window.showDirectoryPicker();
380
+
381
+ },
382
+ create: async (path, options={})=>{
383
+ throw new Error('Unsupported');
384
+ },
385
+ read: async (path, options={})=>{
386
+ const url = (new Path(path)).toUrl(options.type);
387
+ const response = await fetch(url);
388
+ return await response.json();
389
+ },
390
+ write: async (path, buffer, options={})=>{
391
+ throw new Error('Unsupported');
392
+ },
393
+ delete: async (path, options={})=>{
394
+ throw new Error('Unsupported');
395
+ }
396
+ };
397
+ }
398
+ };
399
+ export const remote = {
400
+ initialize : async ()=>{
401
+ //todo: support http
402
+ //const protocol = 'https:';
403
+ return {
404
+ exists: async(path, options={})=>{
405
+ try{
406
+ const response = await fetch(path);
407
+ return response.status === 200;
408
+ }catch(ex){
409
+ return false;
410
+ }
411
+ },
412
+ list: async(path, options={})=>{
413
+ // the only real option here is to scrape by browser
414
+ const response = await fetch('file://'+path);
415
+ const text = await response.text();
416
+ if(text.indexOf('Cannot GET '+path)===-1){
417
+ throw new Error(`Could not find directory: ${path}`);
418
+ }
419
+ return text;
420
+ },
421
+ create: async (path, options={})=>{
422
+ throw new Error('Unsupported');
423
+ },
424
+ read: async (path, options={})=>{
425
+ const response = await fetch(path);
426
+ return await response.arrayBuffer();
427
+ },
428
+ write: async (path, buffer, options={})=>{
429
+ return await new Promise((resolve, reject)=>{
430
+ var element = document.createElement('a');
431
+ element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(
432
+ FileBuffer.to('string', buffer)
433
+ ));
434
+ const filename = path.split('/').pop();
435
+ element.setAttribute('download', filename);
436
+ element.style.display = 'none';
437
+ document.body.appendChild(element);
438
+ element.click();
439
+ document.body.removeChild(element);
440
+ resolve();
441
+ });
442
+ },
443
+ delete: async (path, options={})=>{
444
+ throw new Error('Unsupported');
445
+ }
446
+ };
447
+ }
448
+ };
449
+