@pistonite/pure 0.27.1 → 0.29.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.
Files changed (60) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +23 -4
  3. package/dist/log/index.js +57 -0
  4. package/dist/log/index.js.map +1 -0
  5. package/dist/memory/index.js +92 -0
  6. package/dist/memory/index.js.map +1 -0
  7. package/dist/result/index.js +29 -0
  8. package/dist/result/index.js.map +1 -0
  9. package/dist/sync/index.js +252 -0
  10. package/dist/sync/index.js.map +1 -0
  11. package/package.json +22 -13
  12. package/src/env.d.ts +1 -0
  13. package/src/log/index.ts +36 -11
  14. package/src/log/logger.ts +93 -115
  15. package/src/memory/cell.ts +21 -11
  16. package/src/memory/emp.ts +34 -23
  17. package/src/memory/idgen.test.ts +1 -1
  18. package/src/memory/index.ts +1 -4
  19. package/src/memory/persist.ts +9 -12
  20. package/src/result/index.ts +12 -4
  21. package/src/sync/batch.test.ts +1 -1
  22. package/src/sync/batch.ts +12 -17
  23. package/src/sync/capture.ts +2 -0
  24. package/src/sync/debounce.test.ts +1 -1
  25. package/src/sync/debounce.ts +12 -15
  26. package/src/sync/index.ts +2 -3
  27. package/src/sync/latest.test.ts +1 -1
  28. package/src/sync/latest.ts +19 -16
  29. package/src/sync/once.test.ts +1 -1
  30. package/src/sync/once.ts +13 -8
  31. package/src/sync/serial.test.ts +1 -1
  32. package/src/sync/serial.ts +14 -12
  33. package/src/sync/util.ts +2 -2
  34. package/src/fs/FsError.ts +0 -55
  35. package/src/fs/FsFile.ts +0 -67
  36. package/src/fs/FsFileImpl.ts +0 -219
  37. package/src/fs/FsFileMgr.ts +0 -29
  38. package/src/fs/FsFileStandalone.ts +0 -21
  39. package/src/fs/FsFileStandaloneImplFileAPI.ts +0 -54
  40. package/src/fs/FsFileStandaloneImplHandleAPI.ts +0 -147
  41. package/src/fs/FsFileSystem.ts +0 -71
  42. package/src/fs/FsFileSystemInternal.ts +0 -30
  43. package/src/fs/FsImplEntryAPI.ts +0 -149
  44. package/src/fs/FsImplFileAPI.ts +0 -116
  45. package/src/fs/FsImplHandleAPI.ts +0 -199
  46. package/src/fs/FsOpen.ts +0 -271
  47. package/src/fs/FsOpenFile.ts +0 -256
  48. package/src/fs/FsPath.ts +0 -137
  49. package/src/fs/FsSave.ts +0 -216
  50. package/src/fs/FsSupportStatus.ts +0 -87
  51. package/src/fs/index.ts +0 -123
  52. package/src/log/internal.ts +0 -14
  53. package/src/memory/async_erc.ts +0 -186
  54. package/src/memory/erc.test.ts +0 -258
  55. package/src/memory/erc.ts +0 -320
  56. package/src/pref/dark.ts +0 -151
  57. package/src/pref/device.ts +0 -118
  58. package/src/pref/index.ts +0 -13
  59. package/src/pref/inject_style.ts +0 -22
  60. package/src/pref/locale.ts +0 -296
@@ -1,256 +0,0 @@
1
- import { errstr } from "../result";
2
-
3
- import { fsErr, FsErr, fsFail, type FsResult } from "./FsError.ts";
4
- import type { FsFileStandalone } from "./FsFileStandalone.ts";
5
- import { FsFileStandaloneImplFileAPI } from "./FsFileStandaloneImplFileAPI.ts";
6
- import { FsFileStandaloneImplHandleAPI } from "./FsFileStandaloneImplHandleAPI.ts";
7
-
8
- /**
9
- * Prompt user to open a file
10
- *
11
- * FileSystemAccess API is used on supported platforms, which allows writing after
12
- * user grants the permission once at the time of writing to the file. If failed or not supported,
13
- * the DOM input element is used as a fallback.
14
- */
15
- export const fsOpenFile = async (
16
- options?: FsFileOpenOptions,
17
- ): Promise<FsResult<FsFileStandalone>> => {
18
- const result = await fsOpenFileInternal(false, options || {});
19
- if (result.err) {
20
- return result;
21
- }
22
- if (!result.val.length) {
23
- return { err: fsErr(FsErr.UserAbort, "No files selected") };
24
- }
25
- return { val: result.val[0] };
26
- };
27
-
28
- /**
29
- * Prompt user to open multiple files
30
- *
31
- * FileSystemAccess API is used on supported platforms, which allows writing after
32
- * user grants the permission once at the time of writing to the file. If failed or not supported,
33
- * the DOM input element is used as a fallback.
34
- *
35
- * The returned array is guaranteed to have at least 1 file.
36
- */
37
- export const fsOpenFileMultiple = async (
38
- options?: FsFileOpenOptions,
39
- ): Promise<FsResult<FsFileStandalone[]>> => {
40
- const result = await fsOpenFileInternal(true, options || {});
41
- if (result.err) {
42
- return result;
43
- }
44
- if (!result.val.length) {
45
- return { err: fsErr(FsErr.UserAbort, "No files selected") };
46
- }
47
- return result;
48
- };
49
-
50
- const fsOpenFileInternal = async (
51
- multiple: boolean,
52
- options: FsFileOpenOptions,
53
- ): Promise<FsResult<FsFileStandalone[]>> => {
54
- if (isFileSystemAccessAPISupportedForStandaloneFileOpen()) {
55
- const result = await fsOpenFileWithFileSystemAccessAPI(multiple, options);
56
- if (result.val || result.err.code === FsErr.UserAbort) {
57
- return result;
58
- }
59
- }
60
- // fallback if FileSystemAccessAPI is not supported or fails
61
- return fsOpenFileWithFileAPI(multiple, options);
62
- };
63
-
64
- const fsOpenFileWithFileAPI = async (
65
- multiple: boolean,
66
- { types }: FsFileOpenOptions,
67
- ): Promise<FsResult<FsFileStandalone[]>> => {
68
- const element = document.createElement("input");
69
- element.type = "file";
70
- if (multiple) {
71
- element.multiple = true;
72
- }
73
- if (types?.length) {
74
- const accept = new Set<string>();
75
- const len = types.length;
76
- for (let i = 0; i < len; i++) {
77
- const acceptArray = types[i].accept;
78
- const acceptLen = acceptArray.length;
79
- for (let j = 0; j < acceptLen; j++) {
80
- const acceptValue = acceptArray[j];
81
- if (typeof acceptValue === "string") {
82
- accept.add(acceptValue);
83
- } else {
84
- const { mime, extensions } = acceptValue;
85
- if (mime) {
86
- accept.add(mime);
87
- }
88
- if (extensions.length) {
89
- extensions.forEach((ext) => {
90
- if (ext) {
91
- accept.add(ext);
92
- }
93
- });
94
- }
95
- }
96
- }
97
- }
98
- element.accept = Array.from(accept).join(",");
99
- }
100
- try {
101
- return new Promise((resolve) => {
102
- element.addEventListener("cancel", () => {
103
- resolve({
104
- err: fsErr(FsErr.UserAbort, "cancel listener invoked"),
105
- });
106
- });
107
- element.addEventListener("change", () => {
108
- if (!element.files?.length) {
109
- resolve({
110
- err: fsErr(FsErr.UserAbort, "no files selected"),
111
- });
112
- return;
113
- }
114
- const array = [];
115
- for (let i = 0; i < element.files.length; i++) {
116
- array.push(new FsFileStandaloneImplFileAPI(element.files[i]));
117
- }
118
- resolve({ val: array });
119
- });
120
- element.click();
121
- });
122
- } catch (e) {
123
- return { err: fsFail(errstr(e)) };
124
- }
125
- };
126
-
127
- const fsOpenFileWithFileSystemAccessAPI = async (
128
- multiple: boolean,
129
- { id, types, disallowWildcard }: FsFileOpenOptions,
130
- ): Promise<FsResult<FsFileStandalone[]>> => {
131
- const convertedTypes = types?.map((type) => {
132
- const { description, accept } = type;
133
- const convertedAccept: Record<string, string[]> = {};
134
- const anyMimeType = [];
135
- const len = accept.length;
136
- for (let i = 0; i < len; i++) {
137
- const acceptValue = accept[i];
138
- if (typeof acceptValue === "string") {
139
- if (acceptValue) {
140
- anyMimeType.push(acceptValue);
141
- }
142
- continue;
143
- }
144
- const { mime, extensions } = acceptValue;
145
- if (!mime || mime === "*/*") {
146
- anyMimeType.push(...extensions);
147
- continue;
148
- }
149
- if (mime in convertedAccept) {
150
- convertedAccept[mime].push(...extensions);
151
- } else {
152
- convertedAccept[mime] = [...extensions];
153
- }
154
- }
155
- if (anyMimeType.length) {
156
- convertedAccept["*/*"] = anyMimeType;
157
- }
158
- return { description, accept: convertedAccept };
159
- });
160
- try {
161
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
162
- const output = await (globalThis as any).showOpenFilePicker({
163
- id,
164
- excludeAcceptAllOption: types && types.length && disallowWildcard,
165
- multiple,
166
- types: convertedTypes,
167
- });
168
- const len = output.length;
169
- const convertedOutput = [];
170
- for (let i = 0; i < len; i++) {
171
- convertedOutput.push(new FsFileStandaloneImplHandleAPI(output[i]));
172
- }
173
- return { val: convertedOutput };
174
- } catch (e) {
175
- if (e && typeof e === "object" && "name" in e) {
176
- if (e.name === "AbortError") {
177
- return { err: fsErr(FsErr.UserAbort, "User abort") };
178
- }
179
- if (e.name === "SecurityError") {
180
- return { err: fsErr(FsErr.PermissionDenied, "Security error") };
181
- }
182
- }
183
- return { err: fsFail(errstr(e)) };
184
- }
185
- };
186
-
187
- export type FsFileOpenOptions = {
188
- /**
189
- * ID for the file open operation
190
- *
191
- * Supported implementation can use this to open the picker in the same
192
- * directory for the same ID
193
- */
194
- id?: string;
195
-
196
- /**
197
- * If the "*.*" file type should be hidden in the picker.
198
- *
199
- * In unsupported implementations, this will be ignored, and the "*.*"
200
- * file type will always be visible.
201
- *
202
- * By default, this is false
203
- */
204
- disallowWildcard?: boolean;
205
-
206
- /** List of file types to accept */
207
- types?: FsFileOpenType[];
208
- };
209
-
210
- export type FsFileOpenType = {
211
- /**
212
- * Optional description for the type, which may display in the file picker
213
- *
214
- * In unsupported implementations, this will be ignored.
215
- */
216
- description?: string;
217
-
218
- /**
219
- * List of file mime types or extensions to accept for this file type
220
- *
221
- * String elements are file extensions, with the "." prefix. More context
222
- * can be provided with an object element with mime type and extensions to
223
- * have better file type descriptions in the picker (if supported).
224
- */
225
- accept: (FsFileOpenTypeAccept | string)[];
226
- };
227
-
228
- export type FsFileOpenTypeAccept = {
229
- /** Optional mime type, which the browser can be used to display file type descriptions */
230
- mime?: string;
231
- /** extensions to accept (with the "." prefix) */
232
- extensions: string[];
233
- };
234
-
235
- const isFileSystemAccessAPISupportedForStandaloneFileOpen = () => {
236
- if (!globalThis || !globalThis.isSecureContext) {
237
- return false;
238
- }
239
- if (
240
- !("showOpenFilePicker" in globalThis) ||
241
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
242
- typeof (globalThis as any).showOpenFilePicker !== "function"
243
- ) {
244
- return false;
245
- }
246
- if (!globalThis.FileSystemFileHandle) {
247
- return false;
248
- }
249
- if (!globalThis.FileSystemFileHandle.prototype.getFile) {
250
- return false;
251
- }
252
- if (!globalThis.FileSystemFileHandle.prototype.createWritable) {
253
- return false;
254
- }
255
- return true;
256
- };
package/src/fs/FsPath.ts DELETED
@@ -1,137 +0,0 @@
1
- /**
2
- * Path utilities
3
- *
4
- * The library has the following path standard:
5
- * - All paths are relative (without leading /) to the root
6
- * of the file system (i.e. the uploaded directory)
7
- * - Paths are always separated by /
8
- * - Empty string denotes root
9
- * - Paths cannot lead outside of root
10
- *
11
- * @module
12
- */
13
-
14
- import { FsErr, type FsResult, fsErr } from "./FsError.ts";
15
-
16
- /** Get the root path. Current implementation is empty string. */
17
- export function fsRoot(): string {
18
- return "";
19
- }
20
-
21
- /** Check if a path is the root directory, also handles badly formatted paths like ".///../" */
22
- export function fsIsRoot(p: string): boolean {
23
- if (!p) {
24
- return true;
25
- }
26
- for (let i = 0; i < p.length; i++) {
27
- if (p[i] !== "/" || p[i] !== "." || p[i] !== "\\") {
28
- return false;
29
- }
30
- }
31
- return true;
32
- }
33
-
34
- /**
35
- * Get the base name of a path (i.e. remove the last component)
36
- *
37
- * If this path is the root directory, return InvalidPath.
38
- */
39
- export function fsGetBase(p: string): FsResult<string> {
40
- if (fsIsRoot(p)) {
41
- const err = fsErr(FsErr.InvalidPath, "Trying to get the base of root");
42
- return { err };
43
- }
44
- const i = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\"));
45
- if (i < 0) {
46
- return { val: fsRoot() };
47
- }
48
- return { val: p.substring(0, i) };
49
- }
50
-
51
- /**
52
- * Get the name of a path (i.e. the last component)
53
- *
54
- * Returns the last component of the path.
55
- * Does not include leading or trailing slashes.
56
- *
57
- * If this path is the root directory, return IsRoot.
58
- */
59
- export function fsGetName(p: string): FsResult<string> {
60
- p = stripTrailingSlashes(p);
61
- if (fsIsRoot(p)) {
62
- const err = fsErr(FsErr.IsRoot, "Root directory has no name");
63
- return { err };
64
- }
65
- const i = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\"));
66
- if (i < 0) {
67
- return { val: p };
68
- }
69
- return { val: p.substring(i + 1) };
70
- }
71
-
72
- /**
73
- * Normalize .. and . in a path
74
- *
75
- * Returns InvalidPath if the path tries to escape the root directory.
76
- */
77
- export function fsNormalize(p: string): FsResult<string> {
78
- let s = fsRoot();
79
- for (const comp of fsComponents(p)) {
80
- if (comp === "..") {
81
- const base = fsGetBase(s);
82
- if (base.err) {
83
- return base;
84
- }
85
- s = base.val;
86
- continue;
87
- }
88
- s = fsJoin(s, comp);
89
- }
90
- return { val: s };
91
- }
92
-
93
- /** Join two paths */
94
- export function fsJoin(p1: string, p2: string): string {
95
- if (fsIsRoot(p1)) {
96
- return p2;
97
- }
98
- return p1 + "/" + p2;
99
- }
100
-
101
- /** Iterate through the components of a path. Empty components and . are skipped */
102
- export function* fsComponents(p: string): Iterable<string> {
103
- let i = 0;
104
- while (i < p.length) {
105
- let nextSlash = p.indexOf("/", i);
106
- if (nextSlash < 0) {
107
- nextSlash = p.length;
108
- }
109
- let nextBackslash = p.indexOf("\\", i);
110
- if (nextBackslash < 0) {
111
- nextBackslash = p.length;
112
- }
113
- let j = Math.min(nextSlash, nextBackslash);
114
- if (j < 0) {
115
- j = p.length;
116
- }
117
- const c = p.substring(i, j);
118
- if (c && c !== ".") {
119
- yield c;
120
- }
121
- i = j + 1;
122
- }
123
- }
124
-
125
- /** Remove trailing slashes from a path */
126
- function stripTrailingSlashes(p: string): string {
127
- let i = p.length - 1;
128
- for (; i >= 0; i--) {
129
- if (p[i] !== "/" && p[i] !== "\\") {
130
- break;
131
- }
132
- }
133
- if (i === p.length - 1) {
134
- return p;
135
- }
136
- return p.substring(0, i + 1);
137
- }
package/src/fs/FsSave.ts DELETED
@@ -1,216 +0,0 @@
1
- import { ilog } from "../log/internal.ts";
2
-
3
- import { fsFail, type FsVoid } from "./FsError.ts";
4
-
5
- /** Save (download) a file using Blob */
6
- export function fsSave(content: string | Uint8Array, filename: string): FsVoid {
7
- const blob = new Blob([content], {
8
- // maybe lying, but should be fine
9
- type: "text/plain;charset=utf-8",
10
- });
11
-
12
- try {
13
- saveAs(blob, filename);
14
- return {};
15
- } catch (e) {
16
- ilog.error(e);
17
- return { err: fsFail("save failed") };
18
- }
19
- }
20
-
21
- // The following code is adopted from
22
- // - https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/file-saver/index.d.ts
23
- // under this MIT license:
24
- /*
25
- This project is licensed under the MIT license.
26
- Copyrights are respective of each contributor listed at the beginning of each definition file.
27
-
28
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
29
-
30
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
31
-
32
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33
- */
34
-
35
- type SaveAsFn = (data: Blob | string, filename?: string, options?: SaveAsFnOptions) => void;
36
- type SaveAsFnOptions = { autoBom: boolean };
37
-
38
- /* eslint-disable @typescript-eslint/no-explicit-any */
39
-
40
- // The following code is vendored from
41
- // - https://github.com/eligrey/FileSaver.js/blob/master/src/FileSaver.js
42
- // under this MIT license:
43
- /*
44
- * FileSaver.js
45
- * A saveAs() FileSaver implementation.
46
- *
47
- * By Eli Grey, http://eligrey.com
48
- *
49
- * License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)
50
- * source : http://purl.eligrey.com/github/FileSaver.js
51
- */
52
-
53
- // adoption note: this is now ECMA standard - https://github.com/tc39/proposal-global
54
- // The one and only way of getting global scope in all environments
55
- // https://stackoverflow.com/q/3277182/1008999
56
- // const _global =
57
- // typeof window === "object" && window.window === window
58
- // ? window
59
- // : typeof self === "object" && self.self === self
60
- // ? self
61
- // : typeof global === "object" && global.global === global
62
- // ? global
63
- // : this;
64
-
65
- const download = (url: string | URL, name?: string, opts?: SaveAsFnOptions) => {
66
- const xhr = new XMLHttpRequest();
67
- xhr.open("GET", url);
68
- xhr.responseType = "blob";
69
- xhr.onload = function () {
70
- saveAs(xhr.response, name, opts);
71
- };
72
- xhr.onerror = function () {
73
- console.error("could not download file");
74
- };
75
- xhr.send();
76
- };
77
-
78
- const corsEnabled = (url: string | URL) => {
79
- const xhr = new XMLHttpRequest();
80
- // use sync to avoid popup blocker
81
- xhr.open("HEAD", url, false);
82
- try {
83
- xhr.send();
84
- } catch {
85
- // ignore
86
- }
87
- return xhr.status >= 200 && xhr.status <= 299;
88
- };
89
-
90
- // adoption note: we drop support for old browsers, and just use click()
91
- // original note: `a.click()` doesn't work for all browsers (#465)
92
- // const click = (node: Node) => {
93
- // try {
94
- // node.dispatchEvent(new MouseEvent('click'))
95
- // } catch {
96
- // const evt = document.createEvent('MouseEvents')
97
- // evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
98
- // 20, false, false, false, false, 0, null)
99
- // node.dispatchEvent(evt)
100
- // }
101
- // }
102
-
103
- // adoption note: converted to check at call time, no need to
104
- // do this check at boot load
105
- const saveAs: SaveAsFn = (blob, name?, opts?) => {
106
- // adoption note: this is likely to throw if window is not defined.. ?
107
- if (typeof window !== "object" || window !== globalThis) {
108
- // probably in some web worker
109
- return;
110
- }
111
- // Detect WebView inside a native macOS app by ruling out all browsers
112
- // We just need to check for 'Safari' because all other browsers (besides Firefox) include that too
113
- // https://www.whatismybrowser.com/guides/the-latest-user-agent/macos
114
- const isMacOSWebView =
115
- globalThis.navigator &&
116
- /Macintosh/.test(navigator.userAgent) &&
117
- /AppleWebKit/.test(navigator.userAgent) &&
118
- !/Safari/.test(navigator.userAgent);
119
- // adoption note: removed blob.name, and pulled this line outside
120
- name = name || "download";
121
-
122
- if ("download" in HTMLAnchorElement.prototype && !isMacOSWebView) {
123
- const URL = globalThis.URL || globalThis.webkitURL;
124
- // Namespace is used to prevent conflict w/ Chrome Poper Blocker extension (Issue #561)
125
- const a = document.createElementNS(
126
- "http://www.w3.org/1999/xhtml",
127
- "a",
128
- ) as HTMLAnchorElement;
129
-
130
- a.download = name;
131
- a.rel = "noopener"; // tabnabbing
132
-
133
- // TODO: detect chrome extensions & packaged apps
134
- // a.target = '_blank'
135
-
136
- if (typeof blob === "string") {
137
- // Support regular links
138
- a.href = blob;
139
- if (a.origin !== location.origin) {
140
- // adoption note: removed turnery and changed click()
141
- if (corsEnabled(a.href)) {
142
- download(blob, name, opts);
143
- } else {
144
- a.target = "_blank";
145
- a.click();
146
- }
147
- } else {
148
- // adoption note: changed click()
149
- a.click();
150
- }
151
- } else {
152
- // Support blobs
153
- a.href = URL.createObjectURL(blob);
154
- // adoption note: not sure why 40s
155
- setTimeout(() => {
156
- URL.revokeObjectURL(a.href);
157
- }, 4e4); // 40s
158
- // adoption note: changed click()
159
- setTimeout(() => {
160
- a.click();
161
- }, 0);
162
- }
163
- return;
164
- }
165
-
166
- // adoption note: drop IE support
167
-
168
- // Fallback to using FileReader and a popup
169
- // Open a popup immediately do go around popup blocker
170
- // Mostly only available on user interaction and the fileReader is async so...
171
- let popup = (window as any).popup || open("", "_blank");
172
- if (popup) {
173
- popup.document.title = popup.document.body.innerText = "downloading...";
174
- }
175
-
176
- if (typeof blob === "string") {
177
- return download(blob, name, opts);
178
- }
179
-
180
- const force = blob.type === "application/octet-stream";
181
- // adoption note: add any
182
- const isSafari =
183
- /constructor/i.test((globalThis as any).HTMLElement) || (globalThis as any).safari;
184
- const isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);
185
-
186
- if (
187
- (isChromeIOS || (force && isSafari) || isMacOSWebView) &&
188
- typeof FileReader !== "undefined"
189
- ) {
190
- // Safari doesn't allow downloading of blob URLs
191
- const reader = new FileReader();
192
- reader.onloadend = function () {
193
- let url = reader.result as string;
194
- url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, "data:attachment/file;");
195
- if (popup) {
196
- popup.location.href = url;
197
- } else {
198
- (location as any) = url;
199
- }
200
- popup = null; // reverse-tabnabbing #460
201
- };
202
- reader.readAsDataURL(blob);
203
- } else {
204
- const URL = globalThis.URL || globalThis.webkitURL;
205
- const url = URL.createObjectURL(blob);
206
- if (popup) {
207
- popup.location = url;
208
- } else {
209
- location.href = url;
210
- }
211
- popup = null; // reverse-tabnabbing #460
212
- setTimeout(function () {
213
- URL.revokeObjectURL(url);
214
- }, 4e4); // 40s
215
- }
216
- };
@@ -1,87 +0,0 @@
1
- /** What is supported by the current environment */
2
- export type FsSupportStatus = {
3
- /** Returned by window.isSecureContext */
4
- isSecureContext: boolean;
5
-
6
- /**
7
- * The implementation for FsFileSystem used
8
- *
9
- * See README.md for more information
10
- */
11
- implementation: "File" | "FileSystemAccess" | "FileEntry";
12
- };
13
-
14
- /** Get which implementation will be used for the current environment */
15
- export function fsGetSupportStatus(): FsSupportStatus {
16
- if (isFileSystemAccessSupported()) {
17
- return {
18
- isSecureContext: globalThis.isSecureContext,
19
- implementation: "FileSystemAccess",
20
- };
21
- }
22
- if (isFileEntrySupported()) {
23
- return {
24
- isSecureContext: globalThis.isSecureContext,
25
- implementation: "FileEntry",
26
- };
27
- }
28
-
29
- return {
30
- isSecureContext: !!globalThis && globalThis.isSecureContext,
31
- implementation: "File",
32
- };
33
- }
34
-
35
- function isFileSystemAccessSupported() {
36
- if (!globalThis) {
37
- return false;
38
- }
39
- if (!globalThis.isSecureContext) {
40
- // In Chrome, you can still access the APIs but they just crash the page entirely
41
- return false;
42
- }
43
- if (!globalThis.FileSystemDirectoryHandle) {
44
- return false;
45
- }
46
-
47
- if (!globalThis.FileSystemFileHandle) {
48
- return false;
49
- }
50
-
51
- // since TSlib doesn't have these, let's check here
52
-
53
- // @ts-expect-error FileSystemDirectoryHandle should have a values() method
54
- if (!globalThis.FileSystemDirectoryHandle.prototype.values) {
55
- return false;
56
- }
57
-
58
- // @ts-expect-error window should have showDirectoryPicker
59
- if (!globalThis.showDirectoryPicker) {
60
- return false;
61
- }
62
-
63
- return true;
64
- }
65
-
66
- function isFileEntrySupported(): boolean {
67
- if (!globalThis) {
68
- return false;
69
- }
70
-
71
- // Chrome/Edge has this but it's named DirectoryEntry
72
- // AND, they don't work (I forgot how exactly they don't work)
73
-
74
- if (navigator && navigator.userAgent && navigator.userAgent.includes("Chrome")) {
75
- return false;
76
- }
77
-
78
- if (!globalThis.FileSystemDirectoryEntry) {
79
- return false;
80
- }
81
-
82
- if (!globalThis.FileSystemFileEntry) {
83
- return false;
84
- }
85
-
86
- return true;
87
- }